15 Commits

Author SHA1 Message Date
242be02050 path_utils.c: fix mismatched printf specifier 2024-12-27 00:39:59 +00:00
1f737a42fb include some missing includes 2024-12-27 00:38:52 +00:00
8a4635e811 cache.c: fix some compiler warnings 2024-12-27 00:12:06 +00:00
d0affffc5c ftpfs.c: remove unused variable "range" 2024-12-27 00:00:09 +00:00
7b7d769d59 ftpfs_utimens: update for fuse3, which uses timespec instead of utimbuf 2024-12-27 00:00:09 +00:00
2e7b724b2b ftpfs.c: include utime.h for struct utimbuf definition 2024-12-27 00:00:09 +00:00
ed6a9cee3d ftpfs.c: fix several compiler warnings, especially around printf specifiers 2024-12-27 00:00:09 +00:00
761dac3d64 ftpfs_readdir: specify all parameters to filler
else they may be filled with garbage instead
2024-10-23 10:03:00 +00:00
8f2d6449f7 op_return: also log the inner error, since we use that for matching against timeouts 2024-10-12 01:19:23 +00:00
ee70864395 propagate curl errors in ftpfs_getattr,ftpfs_readlink
this should address a hang, where certain timeouts ('server response timeout') wouldn't trigger fuse_exit
2024-10-12 00:55:08 +00:00
ff6f1a45dd new options: stderr_fd, stderr_path
the mount subsystem is screwy and lots of things will eat logs, so it's nice to be able to directly log to a path
2024-10-11 23:58:09 +00:00
a02006d8ec curl_easy_setopt: error path: log the option being set 2024-08-20 09:35:22 +00:00
3d81b832e5 implement -o exit_after_connect
this is useful as a connectivity check
2024-08-06 05:19:09 +00:00
5c68307010 implement readdir 2024-08-06 04:09:49 +00:00
a431e8615e port to fuse3
this is only very loosely tested, may be errors
2024-08-06 02:56:57 +00:00
11 changed files with 215 additions and 313 deletions

View File

@@ -5,7 +5,7 @@ DIST_SUBDIRS = $(SUBDIRS) tests
LIBICONV = @LIBICONV@ LIBICONV = @LIBICONV@
AM_CPPFLAGS = -DFUSE_USE_VERSION=26 AM_CPPFLAGS = -DFUSE_USE_VERSION=316
AM_LDFLAGS = $(LIBICONV) AM_LDFLAGS = $(LIBICONV)
bin_PROGRAMS = curlftpfs bin_PROGRAMS = curlftpfs

View File

@@ -215,7 +215,7 @@ sysconfdir = @sysconfdir@
target_alias = @target_alias@ target_alias = @target_alias@
SUBDIRS = compat doc SUBDIRS = compat doc
DIST_SUBDIRS = $(SUBDIRS) tests DIST_SUBDIRS = $(SUBDIRS) tests
AM_CPPFLAGS = -DFUSE_USE_VERSION=26 AM_CPPFLAGS = -DFUSE_USE_VERSION=316
AM_LDFLAGS = $(LIBICONV) AM_LDFLAGS = $(LIBICONV)
curlftpfs_SOURCES = ftpfs.c curlftpfs_SOURCES = ftpfs.c
@FUSE_OPT_COMPAT_FALSE@curlftpfs_LDADD = libcurlftpfs.a @FUSE_OPT_COMPAT_FALSE@curlftpfs_LDADD = libcurlftpfs.a

150
cache.c
View File

@@ -38,13 +38,6 @@ struct node {
time_t valid; time_t valid;
}; };
struct fuse_cache_dirhandle {
const char *path;
fuse_dirh_t h;
fuse_dirfil_t filler;
GPtrArray *dir;
};
static void free_node(gpointer node_) static void free_node(gpointer node_)
{ {
struct node *node = (struct node *) node_; struct node *node = (struct node *) node_;
@@ -112,8 +105,9 @@ static void cache_invalidate_dir(const char *path)
pthread_mutex_unlock(&cache.lock); pthread_mutex_unlock(&cache.lock);
} }
static void cache_do_rename(const char *from, const char *to) static void cache_do_rename(const char *from, const char *to, unsigned int flags)
{ {
(void) flags; //< TODO(colin): is this safe to ignore?
pthread_mutex_lock(&cache.lock); pthread_mutex_lock(&cache.lock);
cache_purge(from); cache_purge(from);
cache_purge(to); cache_purge(to);
@@ -136,11 +130,9 @@ static struct node *cache_get(const char *path)
void cache_add_attr(const char *path, const struct stat *stbuf) void cache_add_attr(const char *path, const struct stat *stbuf)
{ {
struct node *node; struct node *node;
time_t now;
pthread_mutex_lock(&cache.lock); pthread_mutex_lock(&cache.lock);
node = cache_get(path); node = cache_get(path);
now = time(NULL);
if (stbuf) { if (stbuf) {
node->stat = *stbuf; node->stat = *stbuf;
node->not_found = 0; node->not_found = 0;
@@ -157,11 +149,9 @@ void cache_add_attr(const char *path, const struct stat *stbuf)
void cache_add_dir(const char *path, char **dir) void cache_add_dir(const char *path, char **dir)
{ {
struct node *node; struct node *node;
time_t now;
pthread_mutex_lock(&cache.lock); pthread_mutex_lock(&cache.lock);
node = cache_get(path); node = cache_get(path);
now = time(NULL);
g_strfreev(node->dir); g_strfreev(node->dir);
node->dir = dir; node->dir = dir;
node->not_found = 0; node->not_found = 0;
@@ -182,11 +172,9 @@ static size_t my_strnlen(const char *s, size_t maxsize)
void cache_add_link(const char *path, const char *link, size_t size) void cache_add_link(const char *path, const char *link, size_t size)
{ {
struct node *node; struct node *node;
time_t now;
pthread_mutex_lock(&cache.lock); pthread_mutex_lock(&cache.lock);
node = cache_get(path); node = cache_get(path);
now = time(NULL);
g_free(node->link); g_free(node->link);
node->link = g_strndup(link, my_strnlen(link, size-1)); node->link = g_strndup(link, my_strnlen(link, size-1));
node->not_found = 0; node->not_found = 0;
@@ -218,11 +206,11 @@ static int cache_get_attr(const char *path, struct stat *stbuf)
return err; return err;
} }
static int cache_getattr(const char *path, struct stat *stbuf) static int cache_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
{ {
int err = cache_get_attr(path, stbuf); int err = cache_get_attr(path, stbuf);
if (err == -EAGAIN) { if (err == -EAGAIN) {
err = cache.next_oper->oper.getattr(path, stbuf); err = cache.next_oper->oper.getattr(path, stbuf, fi);
if (!err) if (!err)
cache_add_attr(path, stbuf); cache_add_attr(path, stbuf);
else if (err == -ENOENT) else if (err == -ENOENT)
@@ -255,71 +243,6 @@ static int cache_readlink(const char *path, char *buf, size_t size)
return err; return err;
} }
static int cache_dirfill(fuse_cache_dirh_t ch, const char *name,
const struct stat *stbuf)
{
int err = ch->filler(ch->h, name, 0, 0);
if (!err) {
char *fullpath;
g_ptr_array_add(ch->dir, g_strdup(name));
fullpath = g_strdup_printf("%s/%s", !ch->path[1] ? "" : ch->path, name);
cache_add_attr(fullpath, stbuf);
g_free(fullpath);
}
return err;
}
static int cache_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler)
{
struct fuse_cache_dirhandle ch;
int err;
char **dir;
struct node *node;
pthread_mutex_lock(&cache.lock);
node = cache_lookup(path);
if (node != NULL && node->dir != NULL) {
time_t now = time(NULL);
if (node->dir_valid - now >= 0) {
for(dir = node->dir; *dir != NULL; dir++)
filler(h, *dir, 0, 0);
pthread_mutex_unlock(&cache.lock);
return 0;
}
}
pthread_mutex_unlock(&cache.lock);
ch.path = path;
ch.h = h;
ch.filler = filler;
ch.dir = g_ptr_array_new();
err = cache.next_oper->cache_getdir(path, &ch, cache_dirfill);
g_ptr_array_add(ch.dir, NULL);
dir = (char **) ch.dir->pdata;
if (!err)
cache_add_dir(path, dir);
else
g_strfreev(dir);
g_ptr_array_free(ch.dir, FALSE);
return err;
}
static int cache_unity_dirfill(fuse_cache_dirh_t ch, const char *name,
const struct stat *stbuf)
{
(void) stbuf;
return ch->filler(ch->h, name, 0, 0);
}
static int cache_unity_getdir(const char *path, fuse_dirh_t h,
fuse_dirfil_t filler)
{
struct fuse_cache_dirhandle ch;
ch.h = h;
ch.filler = filler;
return cache.next_oper->cache_getdir(path, &ch, cache_unity_dirfill);
}
static int cache_mknod(const char *path, mode_t mode, dev_t rdev) static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
{ {
int err = cache.next_oper->oper.mknod(path, mode, rdev); int err = cache.next_oper->oper.mknod(path, mode, rdev);
@@ -360,11 +283,11 @@ static int cache_symlink(const char *from, const char *to)
return err; return err;
} }
static int cache_rename(const char *from, const char *to) static int cache_rename(const char *from, const char *to, unsigned int flags)
{ {
int err = cache.next_oper->oper.rename(from, to); int err = cache.next_oper->oper.rename(from, to, flags);
if (!err) if (!err)
cache_do_rename(from, to); cache_do_rename(from, to, flags);
return err; return err;
} }
@@ -378,33 +301,33 @@ static int cache_link(const char *from, const char *to)
return err; return err;
} }
static int cache_chmod(const char *path, mode_t mode) static int cache_chmod(const char *path, mode_t mode, struct fuse_file_info* fi)
{ {
int err = cache.next_oper->oper.chmod(path, mode); int err = cache.next_oper->oper.chmod(path, mode, fi);
if (!err) if (!err)
cache_invalidate(path); cache_invalidate(path);
return err; return err;
} }
static int cache_chown(const char *path, uid_t uid, gid_t gid) static int cache_chown(const char *path, uid_t uid, gid_t gid, struct fuse_file_info* fi)
{ {
int err = cache.next_oper->oper.chown(path, uid, gid); int err = cache.next_oper->oper.chown(path, uid, gid, fi);
if (!err) if (!err)
cache_invalidate(path); cache_invalidate(path);
return err; return err;
} }
static int cache_truncate(const char *path, off_t size) static int cache_truncate(const char *path, off_t size, struct fuse_file_info *fi)
{ {
int err = cache.next_oper->oper.truncate(path, size); int err = cache.next_oper->oper.truncate(path, size, fi);
if (!err) if (!err)
cache_invalidate(path); cache_invalidate(path);
return err; return err;
} }
static int cache_utime(const char *path, struct utimbuf *buf) static int cache_utimens(const char *path, const struct timespec *buf, struct fuse_file_info* fi)
{ {
int err = cache.next_oper->oper.utime(path, buf); int err = cache.next_oper->oper.utimens(path, buf, fi);
if (!err) if (!err)
cache_invalidate(path); cache_invalidate(path);
return err; return err;
@@ -419,7 +342,6 @@ static int cache_write(const char *path, const char *buf, size_t size,
return res; return res;
} }
#if FUSE_VERSION >= 25
static int cache_create(const char *path, mode_t mode, static int cache_create(const char *path, mode_t mode,
struct fuse_file_info *fi) struct fuse_file_info *fi)
{ {
@@ -429,39 +351,12 @@ static int cache_create(const char *path, mode_t mode,
return err; return err;
} }
static int cache_ftruncate(const char *path, off_t size,
struct fuse_file_info *fi)
{
int err = cache.next_oper->oper.ftruncate(path, size, fi);
if (!err)
cache_invalidate(path);
return err;
}
static int cache_fgetattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi)
{
int err = cache_get_attr(path, stbuf);
if (err == -EAGAIN) {
err = cache.next_oper->oper.fgetattr(path, stbuf, fi);
if (!err)
cache_add_attr(path, stbuf);
else if (err == -ENOENT)
cache_add_attr(path, NULL);
}
return err;
}
#endif
static void cache_unity_fill(struct fuse_cache_operations *oper, static void cache_unity_fill(struct fuse_cache_operations *oper,
struct fuse_operations *cache_oper) struct fuse_operations *cache_oper)
{ {
#if FUSE_VERSION >= 23
cache_oper->init = oper->oper.init; cache_oper->init = oper->oper.init;
#endif
cache_oper->getattr = oper->oper.getattr; cache_oper->getattr = oper->oper.getattr;
cache_oper->readlink = oper->oper.readlink; cache_oper->readlink = oper->oper.readlink;
cache_oper->getdir = cache_unity_getdir;
cache_oper->mknod = oper->oper.mknod; cache_oper->mknod = oper->oper.mknod;
cache_oper->mkdir = oper->oper.mkdir; cache_oper->mkdir = oper->oper.mkdir;
cache_oper->symlink = oper->oper.symlink; cache_oper->symlink = oper->oper.symlink;
@@ -472,7 +367,7 @@ static void cache_unity_fill(struct fuse_cache_operations *oper,
cache_oper->chmod = oper->oper.chmod; cache_oper->chmod = oper->oper.chmod;
cache_oper->chown = oper->oper.chown; cache_oper->chown = oper->oper.chown;
cache_oper->truncate = oper->oper.truncate; cache_oper->truncate = oper->oper.truncate;
cache_oper->utime = oper->oper.utime; cache_oper->utimens = oper->oper.utimens;
cache_oper->open = oper->oper.open; cache_oper->open = oper->oper.open;
cache_oper->read = oper->oper.read; cache_oper->read = oper->oper.read;
cache_oper->write = oper->oper.write; cache_oper->write = oper->oper.write;
@@ -484,11 +379,8 @@ static void cache_unity_fill(struct fuse_cache_operations *oper,
cache_oper->getxattr = oper->oper.getxattr; cache_oper->getxattr = oper->oper.getxattr;
cache_oper->listxattr = oper->oper.listxattr; cache_oper->listxattr = oper->oper.listxattr;
cache_oper->removexattr = oper->oper.removexattr; cache_oper->removexattr = oper->oper.removexattr;
#if FUSE_VERSION >= 25
cache_oper->create = oper->oper.create; cache_oper->create = oper->oper.create;
cache_oper->ftruncate = oper->oper.ftruncate; cache_oper->readdir = oper->oper.readdir;
cache_oper->fgetattr = oper->oper.fgetattr;
#endif
} }
struct fuse_operations *cache_init(struct fuse_cache_operations *oper) struct fuse_operations *cache_init(struct fuse_cache_operations *oper)
@@ -500,7 +392,6 @@ struct fuse_operations *cache_init(struct fuse_cache_operations *oper)
if (cache.on) { if (cache.on) {
cache_oper.getattr = oper->oper.getattr ? cache_getattr : NULL; cache_oper.getattr = oper->oper.getattr ? cache_getattr : NULL;
cache_oper.readlink = oper->oper.readlink ? cache_readlink : NULL; cache_oper.readlink = oper->oper.readlink ? cache_readlink : NULL;
cache_oper.getdir = oper->cache_getdir ? cache_getdir : NULL;
cache_oper.mknod = oper->oper.mknod ? cache_mknod : NULL; cache_oper.mknod = oper->oper.mknod ? cache_mknod : NULL;
cache_oper.mkdir = oper->oper.mkdir ? cache_mkdir : NULL; cache_oper.mkdir = oper->oper.mkdir ? cache_mkdir : NULL;
cache_oper.symlink = oper->oper.symlink ? cache_symlink : NULL; cache_oper.symlink = oper->oper.symlink ? cache_symlink : NULL;
@@ -511,13 +402,10 @@ struct fuse_operations *cache_init(struct fuse_cache_operations *oper)
cache_oper.chmod = oper->oper.chmod ? cache_chmod : NULL; cache_oper.chmod = oper->oper.chmod ? cache_chmod : NULL;
cache_oper.chown = oper->oper.chown ? cache_chown : NULL; cache_oper.chown = oper->oper.chown ? cache_chown : NULL;
cache_oper.truncate = oper->oper.truncate ? cache_truncate : NULL; cache_oper.truncate = oper->oper.truncate ? cache_truncate : NULL;
cache_oper.utime = oper->oper.utime ? cache_utime : NULL; cache_oper.utimens = oper->oper.utimens ? cache_utimens : NULL;
cache_oper.write = oper->oper.write ? cache_write : NULL; cache_oper.write = oper->oper.write ? cache_write : NULL;
#if FUSE_VERSION >= 25
cache_oper.create = oper->oper.create ? cache_create : NULL; cache_oper.create = oper->oper.create ? cache_create : NULL;
cache_oper.ftruncate = oper->oper.ftruncate ? cache_ftruncate : NULL; // TODO: cache `readdir`
cache_oper.fgetattr = oper->oper.fgetattr ? cache_fgetattr : NULL;
#endif
pthread_mutex_init(&cache.lock, NULL); pthread_mutex_init(&cache.lock, NULL);
cache.table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, cache.table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
free_node); free_node);

View File

@@ -12,22 +12,15 @@
#include <fuse.h> #include <fuse.h>
#include <fuse_opt.h> #include <fuse_opt.h>
#ifndef FUSE_VERSION
#define FUSE_VERSION (FUSE_MAJOR_VERSION * 10 + FUSE_MINOR_VERSION)
#endif
#define DEFAULT_CACHE_TIMEOUT 10 #define DEFAULT_CACHE_TIMEOUT 10
#define MAX_CACHE_SIZE 10000 #define MAX_CACHE_SIZE 10000
#define MIN_CACHE_CLEAN_INTERVAL 5 #define MIN_CACHE_CLEAN_INTERVAL 5
#define CACHE_CLEAN_INTERVAL 60 #define CACHE_CLEAN_INTERVAL 60
typedef struct fuse_cache_dirhandle *fuse_cache_dirh_t; typedef struct fuse_cache_dirhandle *fuse_cache_dirh_t;
typedef int (*fuse_cache_dirfil_t) (fuse_cache_dirh_t h, const char *name,
const struct stat *stbuf);
struct fuse_cache_operations { struct fuse_cache_operations {
struct fuse_operations oper; struct fuse_operations oper;
int (*cache_getdir) (const char *, fuse_cache_dirh_t, fuse_cache_dirfil_t);
}; };
struct fuse_operations *cache_init(struct fuse_cache_operations *oper); struct fuse_operations *cache_init(struct fuse_cache_operations *oper);

View File

@@ -11,15 +11,20 @@ AC_PROG_LIBTOOL
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH
PKG_CHECK_MODULES(GLIB, [glib-2.0]) PKG_CHECK_MODULES(GLIB, [glib-2.0])
PKG_CHECK_MODULES(FUSE, [fuse >= 2.2]) PKG_CHECK_MODULES(FUSE, [fuse3 >= 3.16])
PKG_CHECK_MODULES(BSD, [libbsd])
LIBCURL_CHECK_CONFIG([yes], [7.17.0], [], [AC_MSG_ERROR(["libcurl not found"])]) LIBCURL_CHECK_CONFIG([yes], [7.17.0], [], [AC_MSG_ERROR(["libcurl not found"])])
if test "$libcurl_protocol_FTP" != yes; then if test "$libcurl_protocol_FTP" != yes; then
AC_MSG_ERROR(["We need libcurl with support for FTP protocol."]) AC_MSG_ERROR(["We need libcurl with support for FTP protocol."])
fi fi
CFLAGS="$CFLAGS -Wall -W -Wno-sign-compare -D_REENTRANT $GLIB_CFLAGS $FUSE_CFLAGS $LIBCURL_CPPFLAGS" # _POSIX_C_SOURCE>=200809L causes glibc to provide `dprintf` in stdio.h
LIBS="$GLIB_LIBS $FUSE_LIBS $LIBCURL" # - <https://linux.die.net/man/3/dprintf>
# also used by time.h for strptime
# fuse3 requires _off_t to be 8 bytes, and advises to add _FILE_OFFSET_BITS=64 for 32bit platforms.
CFLAGS="$CFLAGS -Wall -W -Wno-sign-compare -D_REENTRANT -D_POSIX_C_SOURCE=200809L -D_FILE_OFFSET_BITS=64 $BSD_CFLAGS $GLIB_CFLAGS $FUSE_CFLAGS $LIBCURL_CPPFLAGS"
LIBS="$BSD_LIBS $GLIB_LIBS $FUSE_LIBS $LIBCURL"
have_fuse_opt_parse=no have_fuse_opt_parse=no
AC_CHECK_FUNC([fuse_opt_parse], [have_fuse_opt_parse=yes]) AC_CHECK_FUNC([fuse_opt_parse], [have_fuse_opt_parse=yes])

View File

@@ -21,6 +21,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <glib.h> #include <glib.h>
#include "cache.h"
#include "ftpfs.h" #include "ftpfs.h"
#include "charset_utils.h" #include "charset_utils.h"
#include "ftpfs-ls.h" #include "ftpfs-ls.h"
@@ -190,7 +191,7 @@ static int parse_dir_netware(const char *line,
int parse_dir(const char* list, const char* dir, int parse_dir(const char* list, const char* dir,
const char* name, struct stat* sbuf, const char* name, struct stat* sbuf,
char* linkbuf, int linklen, char* linkbuf, int linklen,
fuse_cache_dirh_t h, fuse_cache_dirfil_t filler) { void *dbuf, fuse_fill_dir_t filler) {
char *file; char *file;
char *link; char *link;
const char *start = list; const char *start = list;
@@ -253,9 +254,9 @@ int parse_dir(const char* list, const char* dir,
free(reallink); free(reallink);
} }
if (h && filler) { if (dbuf && filler) {
DEBUG(1, "filler: %s\n", file); DEBUG(1, "filler: %s\n", file);
filler(h, file, &stat_buf); filler(dbuf, file, &stat_buf, 0, 0);
} else { } else {
DEBUG(1, "cache_add_attr: %s\n", full_path); DEBUG(1, "cache_add_attr: %s\n", full_path);
cache_add_attr(full_path, &stat_buf); cache_add_attr(full_path, &stat_buf);

View File

@@ -9,11 +9,11 @@
See the file COPYING. See the file COPYING.
*/ */
#include "cache.h" #include <fuse.h>
int parse_dir(const char* list, const char* dir, int parse_dir(const char* list, const char* dir,
const char* name, struct stat* sbuf, const char* name, struct stat* sbuf,
char* linkbuf, int linklen, char* linkbuf, int linklen,
fuse_cache_dirh_t h, fuse_cache_dirfil_t filler); void *dbuf, fuse_fill_dir_t filler);
#endif /* __CURLFTPFS_FTPFS_LS_H__ */ #endif /* __CURLFTPFS_FTPFS_LS_H__ */

283
ftpfs.c
View File

@@ -8,6 +8,7 @@
#include "config.h" #include "config.h"
#include <bsd/readpassphrase.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <ctype.h> #include <ctype.h>
@@ -72,7 +73,7 @@ static int buf_resize(struct buffer *buf, size_t len)
buf->size = (buf->len + len + 63) & ~31; buf->size = (buf->len + len + 63) & ~31;
buf->p = (uint8_t *) realloc(buf->p, buf->size); buf->p = (uint8_t *) realloc(buf->p, buf->size);
if (!buf->p) { if (!buf->p) {
fprintf(stderr, "ftpfs: memory allocation failed\n"); dprintf(ftpfs.stderr_fd, "ftpfs: memory allocation failed\n");
return -1; return -1;
} }
return 0; return 0;
@@ -129,6 +130,8 @@ enum {
static struct fuse_opt ftpfs_opts[] = { static struct fuse_opt ftpfs_opts[] = {
FTPFS_OPT("ftpfs_debug=%u", debug, 0), FTPFS_OPT("ftpfs_debug=%u", debug, 0),
FTPFS_OPT("stderr_path=%s", stderr_path, 0),
FTPFS_OPT("stderr_fd=%u", stderr_fd, 2),
FTPFS_OPT("transform_symlinks", transform_symlinks, 1), FTPFS_OPT("transform_symlinks", transform_symlinks, 1),
FTPFS_OPT("disable_epsv", disable_epsv, 1), FTPFS_OPT("disable_epsv", disable_epsv, 1),
FTPFS_OPT("enable_epsv", disable_epsv, 0), FTPFS_OPT("enable_epsv", disable_epsv, 0),
@@ -174,6 +177,7 @@ static struct fuse_opt ftpfs_opts[] = {
FTPFS_OPT("codepage=%s", codepage, 0), FTPFS_OPT("codepage=%s", codepage, 0),
FTPFS_OPT("iocharset=%s", iocharset, 0), FTPFS_OPT("iocharset=%s", iocharset, 0),
FTPFS_OPT("nomulticonn", multiconn, 0), FTPFS_OPT("nomulticonn", multiconn, 0),
FTPFS_OPT("exit_after_connect", exit_after_connect, 1),
FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP),
@@ -208,7 +212,7 @@ static void cancel_previous_multi()
CURLMcode curlMCode = curl_multi_remove_handle(ftpfs.multi, ftpfs.connection); CURLMcode curlMCode = curl_multi_remove_handle(ftpfs.multi, ftpfs.connection);
if (curlMCode != CURLE_OK) if (curlMCode != CURLE_OK)
{ {
fprintf(stderr, "curl_multi_remove_handle problem: %d\n", curlMCode); dprintf(ftpfs.stderr_fd, "curl_multi_remove_handle problem: %d\n", curlMCode);
exit(1); exit(1);
} }
ftpfs.attached_to_multi = 0; ftpfs.attached_to_multi = 0;
@@ -221,11 +225,12 @@ static int op_return(int err, char * operation)
DEBUG(2, "%s successful\n", operation); DEBUG(2, "%s successful\n", operation);
return 0; return 0;
} }
fprintf(stderr, "ftpfs: operation %s failed because %s\n", operation, strerror(-err)); dprintf(ftpfs.stderr_fd, "ftpfs: operation %s failed because %s. inner: %s\n", operation, strerror(-err), error_buf);
if (strstr(error_buf, "timed out") != NULL if (strstr(error_buf, "timed out") != NULL
|| strstr(error_buf, "timeout") != NULL || strstr(error_buf, "timeout") != NULL
|| strstr(error_buf, "time-out") != NULL) { || strstr(error_buf, "time-out") != NULL) {
// try to match any curl error which would indiciate that the mount is offline. // try to match any curl error which would indiciate that the mount is offline.
dprintf(ftpfs.stderr_fd, "exiting due to timeout\n");
fuse_exit(fuse_get_context()->fuse); fuse_exit(fuse_get_context()->fuse);
} }
return err; return err;
@@ -261,18 +266,25 @@ static size_t read_data(void *ptr, size_t size, size_t nmemb, void *data) {
do {\ do {\
CURLcode res = curl_easy_setopt(handle, option, __VA_ARGS__);\ CURLcode res = curl_easy_setopt(handle, option, __VA_ARGS__);\
if (res != CURLE_OK) {\ if (res != CURLE_OK) {\
fprintf(stderr, "Error setting curl: %s\n", error_buf);\ dprintf(ftpfs.stderr_fd, "Error setting curl option %d: %s\n", option, error_buf);\
exit(1);\ exit(1);\
}\ }\
}while(0) }while(0)
static int ftpfs_getdir(const char* path, fuse_cache_dirh_t h, static int ftpfs_readdir(const char* path, void *dbuf, fuse_fill_dir_t filler,
fuse_cache_dirfil_t filler) { off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags) {
// N.B.: fuse.h tells us that we can ignore `offset` and pass 0 to the filler where it would expect an offset.
(void) offset;
// fuse_cache_dirfil_t handle = (struct dir_handle*) fi->fh; //< set by us in `open`
(void) fi;
(void) flags; //< TODO(colin): is this safe to ignore?
int err = 0; int err = 0;
CURLcode curl_res; CURLcode curl_res;
char* dir_path = get_fulldir_path(path); char* dir_path = get_fulldir_path(path);
DEBUG(1, "ftpfs_getdir: %s\n", dir_path); DEBUG(1, "ftpfs_readdir: %s\n", dir_path);
struct buffer buf; struct buffer buf;
buf_init(&buf); buf_init(&buf);
@@ -289,15 +301,17 @@ static int ftpfs_getdir(const char* path, fuse_cache_dirh_t h,
} else { } else {
buf_null_terminate(&buf); buf_null_terminate(&buf);
parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1, parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1,
NULL, NULL, NULL, 0, h, filler); NULL, NULL, NULL, 0, dbuf, filler);
} }
free(dir_path); free(dir_path);
buf_free(&buf); buf_free(&buf);
return op_return(err, "ftpfs_getdir"); return op_return(err, "ftpfs_readdir");
} }
static int ftpfs_getattr(const char* path, struct stat* sbuf) { static int ftpfs_getattr(const char* path, struct stat* sbuf, struct fuse_file_info* fi) {
(void) fi; //< TODO(colin): is this safe to ignore?
int err; int err;
CURLcode curl_res; CURLcode curl_res;
char* dir_path = get_dir_path(path); char* dir_path = get_dir_path(path);
@@ -315,13 +329,15 @@ static int ftpfs_getattr(const char* path, struct stat* sbuf) {
if (curl_res != 0) { if (curl_res != 0) {
DEBUG(1, "%s\n", error_buf); DEBUG(1, "%s\n", error_buf);
} err = -EIO;
} else {
buf_null_terminate(&buf); buf_null_terminate(&buf);
char* name = strrchr(path, '/'); char* name = strrchr(path, '/');
++name; ++name;
err = parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1, err = parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1,
name, sbuf, NULL, 0, NULL, NULL); name, sbuf, NULL, 0, NULL, NULL);
}
free(dir_path); free(dir_path);
buf_free(&buf); buf_free(&buf);
@@ -344,12 +360,12 @@ static size_t ftpfs_read_chunk(const char* full_path, char* rbuf,
int err = 0; int err = 0;
struct ftpfs_file* fh = get_ftpfs_file(fi); struct ftpfs_file* fh = get_ftpfs_file(fi);
DEBUG(2, "ftpfs_read_chunk: %s %p %zu %lld %p %p\n", DEBUG(2, "ftpfs_read_chunk: %s %p %zu %ld %p %p\n",
full_path, rbuf, size, offset, fi, fh); full_path, rbuf, size, offset, fi, fh);
pthread_mutex_lock(&ftpfs.lock); pthread_mutex_lock(&ftpfs.lock);
DEBUG(2, "buffer size: %zu %lld\n", fh->buf.len, fh->buf.begin_offset); DEBUG(2, "buffer size: %zu %ld\n", fh->buf.len, fh->buf.begin_offset);
if ((fh->buf.len < size + offset - fh->buf.begin_offset) || if ((fh->buf.len < size + offset - fh->buf.begin_offset) ||
offset < fh->buf.begin_offset || offset < fh->buf.begin_offset ||
@@ -361,7 +377,7 @@ static size_t ftpfs_read_chunk(const char* full_path, char* rbuf,
!check_running()) { !check_running()) {
DEBUG(1, "We need to restart the connection %p\n", ftpfs.connection); DEBUG(1, "We need to restart the connection %p\n", ftpfs.connection);
DEBUG(2, "current_fh=%p fh=%p\n", ftpfs.current_fh, fh); DEBUG(2, "current_fh=%p fh=%p\n", ftpfs.current_fh, fh);
DEBUG(2, "buf.begin_offset=%lld offset=%lld\n", fh->buf.begin_offset, offset); DEBUG(2, "buf.begin_offset=%ld offset=%ld\n", fh->buf.begin_offset, offset);
buf_clear(&fh->buf); buf_clear(&fh->buf);
fh->buf.begin_offset = offset; fh->buf.begin_offset = offset;
@@ -380,7 +396,7 @@ static size_t ftpfs_read_chunk(const char* full_path, char* rbuf,
CURLMcode curlMCode = curl_multi_add_handle(ftpfs.multi, ftpfs.connection); CURLMcode curlMCode = curl_multi_add_handle(ftpfs.multi, ftpfs.connection);
if (curlMCode != CURLE_OK) if (curlMCode != CURLE_OK)
{ {
fprintf(stderr, "curl_multi_add_handle problem: %d\n", curlMCode); dprintf(ftpfs.stderr_fd, "curl_multi_add_handle problem: %d\n", curlMCode);
exit(1); exit(1);
} }
ftpfs.attached_to_multi = 1; ftpfs.attached_to_multi = 1;
@@ -510,9 +526,9 @@ int write_thread_ctr = 0;
static void *ftpfs_write_thread(void *data) { static void *ftpfs_write_thread(void *data) {
struct ftpfs_file *fh = data; struct ftpfs_file *fh = data;
char range[15]; //char range[15];
DEBUG(2, "enter streaming write thread #%d path=%s pos=%lld\n", ++write_thread_ctr, fh->full_path, fh->pos); DEBUG(2, "enter streaming write thread #%d path=%s pos=%ld\n", ++write_thread_ctr, fh->full_path, fh->pos);
curl_easy_setopt_or_die(fh->write_conn, CURLOPT_URL, fh->full_path); curl_easy_setopt_or_die(fh->write_conn, CURLOPT_URL, fh->full_path);
@@ -563,7 +579,7 @@ static int start_write_thread(struct ftpfs_file *fh)
{ {
if (fh->write_conn != NULL) if (fh->write_conn != NULL)
{ {
fprintf(stderr, "assert fh->write_conn == NULL failed!\n"); dprintf(ftpfs.stderr_fd, "assert fh->write_conn == NULL failed!\n");
exit(1); exit(1);
} }
@@ -577,14 +593,14 @@ static int start_write_thread(struct ftpfs_file *fh)
fh->write_conn = curl_easy_init(); fh->write_conn = curl_easy_init();
if (fh->write_conn == NULL) { if (fh->write_conn == NULL) {
fprintf(stderr, "Error initializing libcurl\n"); dprintf(ftpfs.stderr_fd, "Error initializing libcurl\n");
return 0; return 0;
} else { } else {
int err; int err;
set_common_curl_stuff(fh->write_conn); set_common_curl_stuff(fh->write_conn);
err = pthread_create(&fh->thread_id, NULL, ftpfs_write_thread, fh); err = pthread_create(&fh->thread_id, NULL, ftpfs_write_thread, fh);
if (err) { if (err) {
fprintf(stderr, "failed to create thread: %s\n", strerror(err)); dprintf(ftpfs.stderr_fd, "failed to create thread: %s\n", strerror(err));
/* FIXME: destroy curl_easy */ /* FIXME: destroy curl_easy */
return 0; return 0;
} }
@@ -632,23 +648,23 @@ static void free_ftpfs_file(struct ftpfs_file *fh) {
free(fh); free(fh);
} }
static int buffer_file(struct ftpfs_file *fh) { // static int buffer_file(struct ftpfs_file *fh) {
// If we want to write to the file, we have to load it all at once, // // If we want to write to the file, we have to load it all at once,
// modify it in memory and then upload it as a whole as most FTP servers // // modify it in memory and then upload it as a whole as most FTP servers
// don't support resume for uploads. // // don't support resume for uploads.
pthread_mutex_lock(&ftpfs.lock); // pthread_mutex_lock(&ftpfs.lock);
cancel_previous_multi(); // cancel_previous_multi();
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, fh->full_path); // curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_URL, fh->full_path);
curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &fh->buf); // curl_easy_setopt_or_die(ftpfs.connection, CURLOPT_WRITEDATA, &fh->buf);
CURLcode curl_res = curl_easy_perform(ftpfs.connection); // CURLcode curl_res = curl_easy_perform(ftpfs.connection);
pthread_mutex_unlock(&ftpfs.lock); // pthread_mutex_unlock(&ftpfs.lock);
//
if (curl_res != 0) { // if (curl_res != 0) {
return -EACCES; // return -EACCES;
} // }
//
return 0; // return 0;
} // }
static int create_empty_file(const char * path) static int create_empty_file(const char * path)
{ {
@@ -674,7 +690,7 @@ static int create_empty_file(const char * path)
} }
static int ftpfs_mknod(const char* path, mode_t mode, dev_t rdev); static int ftpfs_mknod(const char* path, mode_t mode, dev_t rdev);
static int ftpfs_chmod(const char* path, mode_t mode); static int ftpfs_chmod(const char* path, mode_t mode, struct fuse_file_info* fi);
static char * flags_to_string(int flags) static char * flags_to_string(int flags)
{ {
@@ -698,13 +714,13 @@ static char * flags_to_string(int flags)
static int test_exists(const char* path) static int test_exists(const char* path)
{ {
struct stat sbuf; struct stat sbuf;
return ftpfs_getattr(path, &sbuf); return ftpfs_getattr(path, &sbuf, NULL);
} }
static __off_t test_size(const char* path) static __off_t test_size(const char* path)
{ {
struct stat sbuf; struct stat sbuf;
int err = ftpfs_getattr(path, &sbuf); int err = ftpfs_getattr(path, &sbuf, NULL);
if (err) if (err)
return err; return err;
return sbuf.st_size; return sbuf.st_size;
@@ -750,7 +766,7 @@ static int ftpfs_open_common(const char* path, mode_t mode,
size_t size = ftpfs_read_chunk(fh->full_path, NULL, 1, 0, fi, 0); size_t size = ftpfs_read_chunk(fh->full_path, NULL, 1, 0, fi, 0);
if (size == CURLFTPFS_BAD_READ) { if (size == CURLFTPFS_BAD_READ) {
DEBUG(1, "initial read failed size=%d\n", size); DEBUG(1, "initial read failed size=%zu\n", size);
err = -EACCES; err = -EACCES;
} }
} }
@@ -794,7 +810,7 @@ static int ftpfs_open_common(const char* path, mode_t mode,
{ {
sem_wait(&fh->ready); sem_wait(&fh->ready);
/* chmod makes only sense on O_CREAT */ /* chmod makes only sense on O_CREAT */
if (fi->flags & O_CREAT) ftpfs_chmod(path, mode); if (fi->flags & O_CREAT) ftpfs_chmod(path, mode, fi);
sem_post(&fh->data_need); sem_post(&fh->data_need);
} }
else else
@@ -827,23 +843,21 @@ static int ftpfs_open(const char* path, struct fuse_file_info* fi) {
return ftpfs_open_common(path, 0, fi); return ftpfs_open_common(path, 0, fi);
} }
#if FUSE_VERSION >= 25
static int ftpfs_create(const char* path, mode_t mode, static int ftpfs_create(const char* path, mode_t mode,
struct fuse_file_info* fi) { struct fuse_file_info* fi) {
return ftpfs_open_common(path, mode, fi); return ftpfs_open_common(path, mode, fi);
} }
#endif
static int ftpfs_read(const char* path, char* rbuf, size_t size, off_t offset, static int ftpfs_read(const char* path, char* rbuf, size_t size, off_t offset,
struct fuse_file_info* fi) { struct fuse_file_info* fi) {
int ret; int ret;
struct ftpfs_file *fh = get_ftpfs_file(fi); struct ftpfs_file *fh = get_ftpfs_file(fi);
DEBUG(1, "ftpfs_read: %s size=%zu offset=%lld has_write_conn=%d pos=%lld\n", path, size, (long long) offset, fh->write_conn!=0, fh->pos); DEBUG(1, "ftpfs_read: %s size=%zu offset=%ld has_write_conn=%d pos=%ld\n", path, size, offset, fh->write_conn!=0, fh->pos);
if (fh->pos>0 || fh->write_conn!=NULL) if (fh->pos>0 || fh->write_conn!=NULL)
{ {
fprintf(stderr, "in read/write mode we cannot read from a file that has already been written to\n"); dprintf(ftpfs.stderr_fd, "in read/write mode we cannot read from a file that has already been written to\n");
return op_return(-EIO, "ftpfs_read"); return op_return(-EIO, "ftpfs_read");
} }
@@ -873,12 +887,13 @@ static int ftpfs_mknod(const char* path, mode_t mode, dev_t rdev) {
err = create_empty_file(path); err = create_empty_file(path);
if (!err) if (!err)
ftpfs_chmod(path, mode); ftpfs_chmod(path, mode, NULL);
return op_return(err, "ftpfs_mknod"); return op_return(err, "ftpfs_mknod");
} }
static int ftpfs_chmod(const char* path, mode_t mode) { static int ftpfs_chmod(const char* path, mode_t mode, struct fuse_file_info* fi) {
(void) fi; //< TODO(colin): is this safe to ignore?
int err = 0; int err = 0;
// We can only process a subset of the mode - so strip // We can only process a subset of the mode - so strip
@@ -917,10 +932,10 @@ static int ftpfs_chmod(const char* path, mode_t mode) {
return op_return(err, "ftpfs_chmod"); return op_return(err, "ftpfs_chmod");
} }
static int ftpfs_chown(const char* path, uid_t uid, gid_t gid) { static int ftpfs_chown(const char* path, uid_t uid, gid_t gid, struct fuse_file_info* fi) {
int err = 0; int err = 0;
DEBUG(1, "ftpfs_chown: %d %d\n", (int)uid, (int)gid); DEBUG(1, "ftpfs_chown: %d %d %p\n", (int)uid, (int)gid, fi);
struct curl_slist* header = NULL; struct curl_slist* header = NULL;
char* full_path = get_dir_path(path); char* full_path = get_dir_path(path);
@@ -957,28 +972,9 @@ static int ftpfs_chown(const char* path, uid_t uid, gid_t gid) {
return op_return(err, "ftpfs_chown"); return op_return(err, "ftpfs_chown");
} }
static int ftpfs_truncate(const char* path, off_t offset) {
DEBUG(1, "ftpfs_truncate: %s len=%lld\n", path, offset);
/* we can't use ftpfs_mknod here, because we don't know the right permissions */
if (offset == 0) return op_return(create_empty_file(path), "ftpfs_truncate");
/* fix openoffice problem, truncating exactly to file length */
__off_t size = (long long int)test_size(path);
DEBUG(1, "ftpfs_truncate: %s check filesize=%lld\n", path, (long long int)size);
if (offset == size)
return op_return(0, "ftpfs_ftruncate");
DEBUG(1, "ftpfs_truncate problem: %s offset != 0 or filesize=%lld != offset\n", path, (long long int)size);
return op_return(-EPERM, "ftpfs_truncate");
}
static int ftpfs_ftruncate(const char * path , off_t offset, struct fuse_file_info * fi) static int ftpfs_ftruncate(const char * path , off_t offset, struct fuse_file_info * fi)
{ {
DEBUG(1, "ftpfs_ftruncate: %s len=%lld\n", path, offset); DEBUG(1, "ftpfs_ftruncate: %s len=%ld\n", path, offset);
struct ftpfs_file *fh = get_ftpfs_file(fi); struct ftpfs_file *fh = get_ftpfs_file(fi);
if (offset == 0) if (offset == 0)
@@ -1003,10 +999,33 @@ static int ftpfs_ftruncate(const char * path , off_t offset, struct fuse_file_in
return op_return(-EPERM, "ftpfs_ftruncate"); return op_return(-EPERM, "ftpfs_ftruncate");
} }
static int ftpfs_utime(const char* path, struct utimbuf* time) { static int ftpfs_truncate(const char* path, off_t offset, struct fuse_file_info* fi) {
if (fi)
return ftpfs_ftruncate(path, offset, fi);
DEBUG(1, "ftpfs_truncate: %s len=%ld\n", path, offset);
/* we can't use ftpfs_mknod here, because we don't know the right permissions */
if (offset == 0) return op_return(create_empty_file(path), "ftpfs_truncate");
/* fix openoffice problem, truncating exactly to file length */
__off_t size = (long long int)test_size(path);
DEBUG(1, "ftpfs_truncate: %s check filesize=%lld\n", path, (long long int)size);
if (offset == size)
return op_return(0, "ftpfs_ftruncate");
DEBUG(1, "ftpfs_truncate problem: %s offset != 0 or filesize=%lld != offset\n", path, (long long int)size);
return op_return(-EPERM, "ftpfs_truncate");
}
static int ftpfs_utimens(const char* path, const struct timespec* time, struct fuse_file_info* fi) {
(void) path; (void) path;
(void) time; (void) time;
return op_return(0, "ftpfs_utime"); (void) fi;
return op_return(0, "ftpfs_utimens");
} }
static int ftpfs_rmdir(const char* path) { static int ftpfs_rmdir(const char* path) {
@@ -1079,7 +1098,7 @@ static int ftpfs_mkdir(const char* path, mode_t mode) {
free(cmd); free(cmd);
if (!err) if (!err)
ftpfs_chmod(path, mode); ftpfs_chmod(path, mode, NULL);
return op_return(err, "ftpfs_mkdir"); return op_return(err, "ftpfs_mkdir");
} }
@@ -1123,7 +1142,7 @@ static int ftpfs_write(const char *path, const char *wbuf, size_t size,
(void) path; (void) path;
struct ftpfs_file *fh = get_ftpfs_file(fi); struct ftpfs_file *fh = get_ftpfs_file(fi);
DEBUG(1, "ftpfs_write: %s size=%zu offset=%lld has_write_conn=%d pos=%lld\n", path, size, (long long) offset, fh->write_conn!=0, fh->pos); DEBUG(1, "ftpfs_write: %s size=%zu offset=%ld has_write_conn=%d pos=%ld\n", path, size, offset, fh->write_conn!=0, fh->pos);
if (fh->write_fail_cause != CURLE_OK) if (fh->write_fail_cause != CURLE_OK)
{ {
@@ -1133,7 +1152,7 @@ static int ftpfs_write(const char *path, const char *wbuf, size_t size,
if (!fh->write_conn && fh->pos == 0 && offset == 0) if (!fh->write_conn && fh->pos == 0 && offset == 0)
{ {
DEBUG(1, "ftpfs_write: starting a streaming write at pos=%lld\n", fh->pos); DEBUG(1, "ftpfs_write: starting a streaming write at pos=%ld\n", fh->pos);
/* check if the file has been truncated to zero or has been newly created */ /* check if the file has been truncated to zero or has been newly created */
if (!fh->write_may_start) if (!fh->write_may_start)
@@ -1141,7 +1160,7 @@ static int ftpfs_write(const char *path, const char *wbuf, size_t size,
long long size = (long long int)test_size(path); long long size = (long long int)test_size(path);
if (size != 0) if (size != 0)
{ {
fprintf(stderr, "ftpfs_write: start writing with no previous truncate not allowed! size check rval=%lld\n", size); dprintf(ftpfs.stderr_fd, "ftpfs_write: start writing with no previous truncate not allowed! size check rval=%lld\n", size);
return op_return(-EIO, "ftpfs_write"); return op_return(-EIO, "ftpfs_write");
} }
} }
@@ -1158,7 +1177,7 @@ static int ftpfs_write(const char *path, const char *wbuf, size_t size,
if (!fh->write_conn && fh->pos >0 && offset == fh->pos) if (!fh->write_conn && fh->pos >0 && offset == fh->pos)
{ {
/* resume a streaming write */ /* resume a streaming write */
DEBUG(1, "ftpfs_write: resuming a streaming write at pos=%lld\n", fh->pos); DEBUG(1, "ftpfs_write: resuming a streaming write at pos=%ld\n", fh->pos);
int success = start_write_thread(fh); int success = start_write_thread(fh);
if (!success) if (!success)
@@ -1210,7 +1229,7 @@ static int ftpfs_flush(const char *path, struct fuse_file_info *fi) {
int err = 0; int err = 0;
struct ftpfs_file* fh = get_ftpfs_file(fi); struct ftpfs_file* fh = get_ftpfs_file(fi);
DEBUG(1, "ftpfs_flush: buf.len=%zu buf.pos=%lld write_conn=%d\n", fh->buf.len, fh->pos, fh->write_conn!=0); DEBUG(1, "ftpfs_flush: buf.len=%zu buf.pos=%ld write_conn=%d\n", fh->buf.len, fh->pos, fh->write_conn!=0);
if (fh->write_conn) { if (fh->write_conn) {
err = finish_write_thread(fh); err = finish_write_thread(fh);
@@ -1221,13 +1240,13 @@ static int ftpfs_flush(const char *path, struct fuse_file_info *fi) {
/* check if the resulting file has the correct size /* check if the resulting file has the correct size
this is important, because we use APPE for continuing this is important, because we use APPE for continuing
writing after a premature flush */ writing after a premature flush */
err = ftpfs_getattr(path, &sbuf); err = ftpfs_getattr(path, &sbuf, fi);
if (err) return op_return(err, "ftpfs_flush"); if (err) return op_return(err, "ftpfs_flush");
if (sbuf.st_size != fh->pos) if (sbuf.st_size != fh->pos)
{ {
fh->write_fail_cause = -999; fh->write_fail_cause = -999;
fprintf(stderr, "ftpfs_flush: check filesize problem: size=%lld expected=%lld\n", sbuf.st_size, fh->pos); dprintf(ftpfs.stderr_fd, "ftpfs_flush: check filesize problem: size=%ld expected=%ld\n", sbuf.st_size, fh->pos);
return op_return(-EIO, "ftpfs_flush"); return op_return(-EIO, "ftpfs_flush");
} }
@@ -1269,8 +1288,12 @@ static int ftpfs_release(const char* path, struct fuse_file_info* fi) {
} }
static int ftpfs_rename(const char* from, const char* to) { static int ftpfs_rename(const char* from, const char* to, unsigned int flags) {
DEBUG(1, "ftpfs_rename from %s to %s\n", from, to); DEBUG(1, "ftpfs_rename from %s to %s (%u)\n", from, to, flags);
if(flags != 0)
return -EINVAL;
int err = 0; int err = 0;
char* rnfr = g_strdup_printf("RNFR %s", from + 1); char* rnfr = g_strdup_printf("RNFR %s", from + 1);
char* rnto = g_strdup_printf("RNTO %s", to + 1); char* rnto = g_strdup_printf("RNTO %s", to + 1);
@@ -1327,13 +1350,15 @@ static int ftpfs_readlink(const char *path, char *linkbuf, size_t size) {
if (curl_res != 0) { if (curl_res != 0) {
DEBUG(1, "%s\n", error_buf); DEBUG(1, "%s\n", error_buf);
} err = -EIO;
} else {
buf_null_terminate(&buf); buf_null_terminate(&buf);
char* name = strrchr(path, '/'); char* name = strrchr(path, '/');
++name; ++name;
err = parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1, err = parse_dir((char*)buf.p, dir_path + strlen(ftpfs.host) - 1,
name, NULL, linkbuf, size, NULL, NULL); name, NULL, linkbuf, size, NULL, NULL);
}
free(dir_path); free(dir_path);
buf_free(&buf); buf_free(&buf);
@@ -1341,7 +1366,6 @@ static int ftpfs_readlink(const char *path, char *linkbuf, size_t size) {
return op_return(0, "ftpfs_readlink"); return op_return(0, "ftpfs_readlink");
} }
#if FUSE_VERSION >= 25
static int ftpfs_statfs(const char *path, struct statvfs *buf) static int ftpfs_statfs(const char *path, struct statvfs *buf)
{ {
(void) path; (void) path;
@@ -1356,21 +1380,15 @@ static int ftpfs_statfs(const char *path, struct statvfs *buf)
buf->f_ffree = 999999999; buf->f_ffree = 999999999;
return op_return(0, "ftpfs_statfs"); return op_return(0, "ftpfs_statfs");
} }
#else
static int ftpfs_statfs(const char *path, struct statfs *buf)
{
(void) path;
buf->f_namelen = 255; // static void *ftpfs_init(struct fuse_conn_info *conn,
buf->f_bsize = ftpfs.blksize; // struct fuse_config *cfg)
buf->f_blocks = 999999999 * 2; // {
buf->f_bfree = 999999999 * 2; // // alternatively, mount with `-o readdirplus=no`
buf->f_bavail = 999999999 * 2; // conn->want &= ~FUSE_CAP_READDIRPLUS;
buf->f_files = 999999999; // // alternatively, mount with `-o sync_read`
buf->f_ffree = 999999999; // conn->want &= ~FUSE_CAP_ASYNC_READ;
return op_return(0, "ftpfs_statfs"); // }
}
#endif
static struct fuse_cache_operations ftpfs_oper = { static struct fuse_cache_operations ftpfs_oper = {
.oper = { .oper = {
@@ -1386,7 +1404,7 @@ static struct fuse_cache_operations ftpfs_oper = {
.chmod = ftpfs_chmod, .chmod = ftpfs_chmod,
.chown = ftpfs_chown, .chown = ftpfs_chown,
.truncate = ftpfs_truncate, .truncate = ftpfs_truncate,
.utime = ftpfs_utime, .utimens = ftpfs_utimens,
.open = ftpfs_open, .open = ftpfs_open,
.flush = ftpfs_flush, .flush = ftpfs_flush,
.fsync = ftpfs_fsync, .fsync = ftpfs_fsync,
@@ -1394,22 +1412,14 @@ static struct fuse_cache_operations ftpfs_oper = {
.read = ftpfs_read, .read = ftpfs_read,
.write = ftpfs_write, .write = ftpfs_write,
.statfs = ftpfs_statfs, .statfs = ftpfs_statfs,
#if FUSE_VERSION >= 25
.create = ftpfs_create, .create = ftpfs_create,
.ftruncate = ftpfs_ftruncate, .readdir = ftpfs_readdir,
// .fgetattr = ftpfs_fgetattr,
#endif
}, },
.cache_getdir = ftpfs_getdir,
}; };
static int curlftpfs_fuse_main(struct fuse_args *args) static int curlftpfs_fuse_main(struct fuse_args *args)
{ {
#if FUSE_VERSION >= 26
return fuse_main(args->argc, args->argv, cache_init(&ftpfs_oper), NULL); return fuse_main(args->argc, args->argv, cache_init(&ftpfs_oper), NULL);
#else
return fuse_main(args->argc, args->argv, cache_init(&ftpfs_oper));
#endif
} }
static int ftpfs_opt_proc(void* data, const char* arg, int key, static int ftpfs_opt_proc(void* data, const char* arg, int key,
@@ -1441,7 +1451,7 @@ static int ftpfs_opt_proc(void* data, const char* arg, int key,
ftpfs.verbose = 1; ftpfs.verbose = 1;
return 0; return 0;
case KEY_VERSION: case KEY_VERSION:
fprintf(stderr, "curlftpfs %s libcurl/%s fuse/%u.%u\n", dprintf(ftpfs.stderr_fd, "curlftpfs %s libcurl/%s fuse/%u.%u\n",
VERSION, VERSION,
ftpfs.curl_version->version, ftpfs.curl_version->version,
FUSE_MAJOR_VERSION, FUSE_MAJOR_VERSION,
@@ -1464,6 +1474,8 @@ static void usage(const char* progname) {
"\n" "\n"
"FTP options:\n" "FTP options:\n"
" ftpfs_debug print some debugging information\n" " ftpfs_debug print some debugging information\n"
" stderr_path=STR log stderr to path\n"
" stderr_fd=N log stderr to file descriptor\n"
" transform_symlinks prepend mountpoint to absolute symlink targets\n" " transform_symlinks prepend mountpoint to absolute symlink targets\n"
" disable_epsv use PASV, without trying EPSV first (default)\n" " disable_epsv use PASV, without trying EPSV first (default)\n"
" enable_epsv try EPSV before reverting to PASV\n" " enable_epsv try EPSV before reverting to PASV\n"
@@ -1508,6 +1520,7 @@ static void usage(const char* progname) {
" utf8 try to transfer file list with utf-8 encoding\n" " utf8 try to transfer file list with utf-8 encoding\n"
" codepage=STR set the codepage the server uses\n" " codepage=STR set the codepage the server uses\n"
" iocharset=STR set the charset used by the client\n" " iocharset=STR set the charset used by the client\n"
" exit_after_connect after connecting to the FTP server, instead of passing control to FUSE, exit\n"
"\n" "\n"
"CurlFtpFS cache options: \n" "CurlFtpFS cache options: \n"
" cache=yes|no enable/disable cache (default: yes)\n" " cache=yes|no enable/disable cache (default: yes)\n"
@@ -1593,7 +1606,7 @@ static void set_common_curl_stuff(CURL* easy) {
* with version 7.15.4 */ * with version 7.15.4 */
if (ftpfs.use_ssl > CURLFTPSSL_TRY && if (ftpfs.use_ssl > CURLFTPSSL_TRY &&
ftpfs.curl_version->version_num <= CURLFTPFS_BAD_SSL) { ftpfs.curl_version->version_num <= CURLFTPFS_BAD_SSL) {
fprintf(stderr, dprintf(ftpfs.stderr_fd,
"WARNING: you are using libcurl %s.\n" "WARNING: you are using libcurl %s.\n"
"This version of libcurl does not respect the mandatory SSL flag.\n" "This version of libcurl does not respect the mandatory SSL flag.\n"
"It will try to send the user and password even if the server doesn't support\n" "It will try to send the user and password even if the server doesn't support\n"
@@ -1603,10 +1616,10 @@ static void set_common_curl_stuff(CURL* easy) {
int i; int i;
const int time_to_wait = 10; const int time_to_wait = 10;
for (i = 0; i < time_to_wait; i++) { for (i = 0; i < time_to_wait; i++) {
fprintf(stderr, "%d.. ", time_to_wait - i); dprintf(ftpfs.stderr_fd, "%d.. ", time_to_wait - i);
sleep(1); sleep(1);
} }
fprintf(stderr, "\n"); dprintf(ftpfs.stderr_fd, "\n");
} }
curl_easy_setopt_or_die(easy, CURLOPT_FTP_SSL, ftpfs.use_ssl); curl_easy_setopt_or_die(easy, CURLOPT_FTP_SSL, ftpfs.use_ssl);
@@ -1690,6 +1703,7 @@ static void checkpasswd(const char *kind, /* for what purpose */
if(!ptr) { if(!ptr) {
/* no password present, prompt for one */ /* no password present, prompt for one */
char *passwd; char *passwd;
char passwd_buf[256];
char prompt[256]; char prompt[256];
size_t passwdlen; size_t passwdlen;
size_t userlen = strlen(*userpwd); size_t userlen = strlen(*userpwd);
@@ -1701,7 +1715,7 @@ static void checkpasswd(const char *kind, /* for what purpose */
kind, *userpwd); kind, *userpwd);
/* get password */ /* get password */
passwd = getpass(prompt); passwd = readpassphrase(prompt, passwd_buf, 256, 0 /* flags */);
passwdlen = strlen(passwd); passwdlen = strlen(passwd);
/* extend the allocated memory area to fit the password too */ /* extend the allocated memory area to fit the password too */
@@ -1718,23 +1732,6 @@ static void checkpasswd(const char *kind, /* for what purpose */
} }
} }
#if FUSE_VERSION == 25
static int fuse_opt_insert_arg(struct fuse_args *args, int pos,
const char *arg)
{
assert(pos <= args->argc);
if (fuse_opt_add_arg(args, arg) == -1)
return -1;
if (pos != args->argc - 1) {
char *newarg = args->argv[args->argc - 1];
memmove(&args->argv[pos + 1], &args->argv[pos],
sizeof(char *) * (args->argc - pos - 1));
args->argv[pos] = newarg;
}
return 0;
}
#endif
int main(int argc, char** argv) { int main(int argc, char** argv) {
int res; int res;
@@ -1759,9 +1756,20 @@ int main(int argc, char** argv) {
if (fuse_opt_parse(&args, &ftpfs, ftpfs_opts, ftpfs_opt_proc) == -1) if (fuse_opt_parse(&args, &ftpfs, ftpfs_opts, ftpfs_opt_proc) == -1)
exit(1); exit(1);
if (ftpfs.stderr_path)
ftpfs.stderr_fd = open(ftpfs.stderr_path, O_RDWR|O_CREAT|O_APPEND, 0644);
if (ftpfs.stderr_fd == -1) {
fprintf(stderr, "failed to open stderr\n");
exit(1);
}
if (ftpfs.debug)
dprintf(ftpfs.stderr_fd, "logging configured\n");
if (!ftpfs.host) { if (!ftpfs.host) {
fprintf(stderr, "missing host\n"); dprintf(ftpfs.stderr_fd, "missing host\n");
fprintf(stderr, "see `%s -h' for usage\n", argv[0]); dprintf(ftpfs.stderr_fd, "see `%s -h' for usage\n", argv[0]);
exit(1); exit(1);
} }
@@ -1775,7 +1783,7 @@ int main(int argc, char** argv) {
easy = curl_easy_init(); easy = curl_easy_init();
if (easy == NULL) { if (easy == NULL) {
fprintf(stderr, "Error initializing libcurl\n"); dprintf(ftpfs.stderr_fd, "Error initializing libcurl\n");
exit(1); exit(1);
} }
@@ -1787,7 +1795,7 @@ int main(int argc, char** argv) {
checkpasswd("proxy", &ftpfs.proxy_user); checkpasswd("proxy", &ftpfs.proxy_user);
if (ftpfs.transform_symlinks && !ftpfs.mountpoint) { if (ftpfs.transform_symlinks && !ftpfs.mountpoint) {
fprintf(stderr, "cannot transform symlinks: no mountpoint given\n"); dprintf(ftpfs.stderr_fd, "cannot transform symlinks: no mountpoint given\n");
exit(1); exit(1);
} }
if (!ftpfs.transform_symlinks) if (!ftpfs.transform_symlinks)
@@ -1804,14 +1812,14 @@ int main(int argc, char** argv) {
curl_easy_setopt_or_die(easy, CURLOPT_NOBODY, ftpfs.safe_nobody); curl_easy_setopt_or_die(easy, CURLOPT_NOBODY, ftpfs.safe_nobody);
curl_res = curl_easy_perform(easy); curl_res = curl_easy_perform(easy);
if (curl_res != 0) { if (curl_res != 0) {
fprintf(stderr, "Error connecting to ftp: %s\n", error_buf); dprintf(ftpfs.stderr_fd, "Error connecting to ftp: %s\n", error_buf);
exit(1); exit(1);
} }
curl_easy_setopt_or_die(easy, CURLOPT_NOBODY, 0); curl_easy_setopt_or_die(easy, CURLOPT_NOBODY, 0);
ftpfs.multi = curl_multi_init(); ftpfs.multi = curl_multi_init();
if (ftpfs.multi == NULL) { if (ftpfs.multi == NULL) {
fprintf(stderr, "Error initializing libcurl multi\n"); dprintf(ftpfs.stderr_fd, "Error initializing libcurl multi\n");
exit(1); exit(1);
} }
@@ -1823,7 +1831,10 @@ int main(int argc, char** argv) {
fuse_opt_insert_arg(&args, 1, tmp); fuse_opt_insert_arg(&args, 1, tmp);
g_free(tmp); g_free(tmp);
if (!ftpfs.exit_after_connect) {
res = curlftpfs_fuse_main(&args); res = curlftpfs_fuse_main(&args);
}
cancel_previous_multi(); cancel_previous_multi();
curl_multi_cleanup(ftpfs.multi); curl_multi_cleanup(ftpfs.multi);

12
ftpfs.h
View File

@@ -13,6 +13,7 @@
#include <curl/easy.h> #include <curl/easy.h>
#include <pthread.h> #include <pthread.h>
#include <pthread.h> #include <pthread.h>
#include <stdio.h>
struct ftpfs { struct ftpfs {
char* host; char* host;
@@ -25,6 +26,8 @@ struct ftpfs {
unsigned blksize; unsigned blksize;
int verbose; int verbose;
int debug; int debug;
char *stderr_path;
int stderr_fd;
int transform_symlinks; int transform_symlinks;
int disable_epsv; int disable_epsv;
int skip_pasv_ip; int skip_pasv_ip;
@@ -67,6 +70,7 @@ struct ftpfs {
char *codepage; char *codepage;
char *iocharset; char *iocharset;
int multiconn; int multiconn;
int exit_after_connect;
}; };
extern struct ftpfs ftpfs; extern struct ftpfs ftpfs;
@@ -74,10 +78,10 @@ extern struct ftpfs ftpfs;
#define DEBUG(level, args...) \ #define DEBUG(level, args...) \
do { if (level <= ftpfs.debug) {\ do { if (level <= ftpfs.debug) {\
int i = 0; \ int i = 0; \
while (++i < level) fprintf(stderr, " "); \ while (++i < level) dprintf(ftpfs.stderr_fd, " "); \
fprintf(stderr, "%ld ", time(NULL));\ dprintf(ftpfs.stderr_fd, "%ld ", time(NULL));\
fprintf(stderr, __FILE__ ":%d ", __LINE__);\ dprintf(ftpfs.stderr_fd, __FILE__ ":%d ", __LINE__);\
fprintf(stderr, args);\ dprintf(ftpfs.stderr_fd, args);\
}\ }\
} while(0) } while(0)

View File

@@ -5038,7 +5038,7 @@ EOF
/* -DDEBUG is fairly common in CFLAGS. */ /* -DDEBUG is fairly common in CFLAGS. */
#undef DEBUG #undef DEBUG
#if defined DEBUGWRAPPER #if defined DEBUGWRAPPER
# define DEBUG(format, ...) fprintf(stderr, format, __VA_ARGS__) # define DEBUG(format, ...) dprintf(ftpfs.stderr_fd, format, __VA_ARGS__)
#else #else
# define DEBUG(format, ...) # define DEBUG(format, ...)
#endif #endif
@@ -5291,9 +5291,9 @@ static void
lt_error_core (int exit_status, const char * mode, lt_error_core (int exit_status, const char * mode,
const char * message, va_list ap) const char * message, va_list ap)
{ {
fprintf (stderr, "%s: %s: ", program_name, mode); dprintf (ftpfs.stderr_fd, "%s: %s: ", program_name, mode);
vfprintf (stderr, message, ap); vdprintf (ftpfs.stderr_fd, message, ap);
fprintf (stderr, ".\n"); dprintf (ftpfs.stderr_fd, ".\n");
if (exit_status >= 0) if (exit_status >= 0)
exit (exit_status); exit (exit_status);

View File

@@ -84,7 +84,7 @@ char* get_dir_path(const char* path) {
ret = g_strdup_printf("%s%.*s%s", ret = g_strdup_printf("%s%.*s%s",
ftpfs.host, ftpfs.host,
lastdir - path, (int)(lastdir - path),
path, path,
lastdir - path ? "/" : ""); lastdir - path ? "/" : "");