diff options
Diffstat (limited to 'src/fusedav.c')
-rw-r--r-- | src/fusedav.c | 727 |
1 files changed, 727 insertions, 0 deletions
diff --git a/src/fusedav.c b/src/fusedav.c new file mode 100644 index 0000000..17f1001 --- /dev/null +++ b/src/fusedav.c @@ -0,0 +1,727 @@ +#include <signal.h> +#include <pthread.h> +#include <time.h> +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include <sys/statfs.h> +#include <getopt.h> + +#include <ne_request.h> +#include <ne_basic.h> +#include <ne_props.h> +#include <ne_utils.h> +#include <ne_socket.h> +#include <ne_auth.h> +#include <ne_dates.h> + +#include <fuse.h> + +#include "statcache.h" +#include "filecache.h" +#include "session.h" +#include "openssl-thread.h" + +const ne_propname query_properties[] = { + { "DAV:", "resourcetype" }, + { "http://apache.org/dav/props/", "executable" }, + { "DAV:", "getcontentlength" }, + { "DAV:", "getlastmodified" }, + { "DAV:", "creationdate" }, + { NULL, NULL } +}; + +mode_t mask = 0; +int debug = 0; +struct fuse* fuse = NULL; + +struct fill_info { + fuse_dirh_t h; + fuse_dirfil_t filler; + const char *root; +}; + +static int get_stat(const char *path, struct stat *stbuf); + +static pthread_once_t path_cvt_once = PTHREAD_ONCE_INIT; +static pthread_key_t path_cvt_tsd_key; + +static void path_cvt_tsd_key_init(void) { + pthread_key_create(&path_cvt_tsd_key, free); +} + +static const char *path_cvt(const char *path) { + char *r, *t; + int l; + + pthread_once(&path_cvt_once, path_cvt_tsd_key_init); + + if ((r = pthread_getspecific(path_cvt_tsd_key))) + free(r); + + t = malloc((l = strlen(base_directory)+strlen(path))+1); + assert(t); + sprintf(t, "%s%s", base_directory, path); + + if (l > 1 && t[l-1] == '/') + t[l-1] = 0; + + r = ne_path_escape(t); + free(t); + + pthread_setspecific(path_cvt_tsd_key, r); + + return r; +} + +static void fill_stat(struct stat* st, const ne_prop_result_set *results, int is_dir) { + const char *rt, *e, *gcl, *glm, *cd; + const ne_propname resourcetype = { "DAV:", "resourcetype" }; + const ne_propname executable = { "http://apache.org/dav/props/", "executable" }; + const ne_propname getcontentlength = { "DAV:", "getcontentlength" }; + const ne_propname getlastmodified = { "DAV:", "getlastmodified" }; + const ne_propname creationdate = { "DAV:", "creationdate" }; + + assert(st && results); + + rt = ne_propset_value(results, &resourcetype); + e = ne_propset_value(results, &executable); + gcl = ne_propset_value(results, &getcontentlength); + glm = ne_propset_value(results, &getlastmodified); + cd = ne_propset_value(results, &creationdate); + + memset(st, 0, sizeof(struct stat)); + + if (is_dir) { + st->st_mode = S_IFDIR | 0777; + st->st_nlink = 3; /* find will ignore this directory if nlin <= and st_size == 0 */ + st->st_size = 4096; + } else { + st->st_mode = S_IFREG | (e && (*e == 'T' || *e == 't') ? 0777 : 0666); + st->st_nlink = 1; + st->st_size = gcl ? atoll(gcl) : 0; + } + + st->st_atime = time(NULL); + st->st_mtime = glm ? ne_rfc1123_parse(glm) : 0; + st->st_ctime = cd ? ne_iso8601_parse(cd) : 0; + + //fprintf(stderr, "a: %u; m: %u; c: %u\n", st->st_atime, st->st_mtime, st->st_ctime); + + st->st_mode &= ~mask; + + st->st_uid = getuid(); + st->st_gid = getgid(); +} + +static char *strip_trailing_slash(char *fn, int *is_dir) { + size_t l = strlen(fn); + assert(fn && is_dir); + + if ((*is_dir = (fn[l-1] == '/'))) + fn[l-1] = 0; + + return fn; +} + +static void getdir_propfind_callback(void *userdata, const char *href, const ne_prop_result_set *results) { + struct fill_info *f = userdata; + struct stat st; + char fn[PATH_MAX], *t; + int is_dir = 0; + + assert(f); + + strncpy(fn, href, sizeof(fn)); + fn[sizeof(fn)-1] = 0; + strip_trailing_slash(fn, &is_dir); + + if (strcmp(fn, f->root) && fn[0]) { + char *h; + + if ((t = strrchr(fn, '/'))) + t++; + else + t = fn; + + dir_cache_add(f->root, t, is_dir); + f->filler(f->h, h = ne_path_unescape(t), is_dir ? DT_DIR : DT_REG); + free(h); + } + + fill_stat(&st, results, is_dir); + stat_cache_set(fn, &st); +} + +static void getdir_cache_callback(const char *root, const char *fn, int is_dir, void *user) { + struct fill_info *f = user; + assert(f); + char path[PATH_MAX]; + struct stat st; + char *h; + + snprintf(path, sizeof(path), "%s/%s", !strcmp(root, "/") ? "" : root, fn); + + if (get_stat(path, &st) < 0) + return; + + f->filler(f->h, h = ne_path_unescape(fn), is_dir ? DT_DIR : DT_REG); + free(h); +} + +static int dav_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) { + struct fill_info f; + ne_session *session; + + path = path_cvt(path); + + if (debug) + fprintf(stderr, "getdir(%s)\n", path); + + f.h = h; + f.filler = filler; + f.root = path; + + if (dir_cache_enumerate(path, getdir_cache_callback, &f) < 0) { + + if (debug) + fprintf(stderr, "DIR-CACHE-MISS\n"); + + if (!(session = session_get())) + return -EIO; + + dir_cache_begin(path); + + if (ne_simple_propfind(session, path, NE_DEPTH_ONE, query_properties, getdir_propfind_callback, &f) != NE_OK) { + dir_cache_finish(path, 2); + fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session)); + return -ENOENT; + } + + dir_cache_finish(path, 1); + } + + filler(h, ".", DT_DIR); + filler(h, "..", DT_DIR); + + return 0; +} + +static void getattr_propfind_callback(void *userdata, const char *href, const ne_prop_result_set *results) { + struct stat *st = (struct stat*) userdata; + char fn[PATH_MAX]; + int is_dir; + + assert(st); + + strncpy(fn, href, sizeof(fn)); + fn[sizeof(fn)-1] = 0; + strip_trailing_slash(fn, &is_dir); + + fill_stat(st, results, is_dir); + stat_cache_set(fn, st); +} + +static int get_stat(const char *path, struct stat *stbuf) { + ne_session *session; + + if (!(session = session_get())) + return -EIO; + + if (stat_cache_get(path, stbuf) == 0) { + return stbuf->st_mode == 0 ? -ENOENT : 0; + } else { + if (debug) + fprintf(stderr, "STAT-CACHE-MISS\n"); + + if (ne_simple_propfind(session, path, NE_DEPTH_ZERO, query_properties, getattr_propfind_callback, stbuf) != NE_OK) { + stat_cache_invalidate(path); + fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session)); + return -ENOENT; + } + + return 0; + } +} + +static int dav_getattr(const char *path, struct stat *stbuf) { + path = path_cvt(path); + if (debug) + fprintf(stderr, "getattr(%s)\n", path); + return get_stat(path, stbuf); +} + +static int dav_unlink(const char *path) { + int r; + struct stat st; + ne_session *session; + + path = path_cvt(path); + + if (debug) + fprintf(stderr, "unlink(%s)\n", path); + + if (!(session = session_get())) + return -EIO; + + if ((r = get_stat(path, &st)) < 0) + return r; + + if (!S_ISREG(st.st_mode)) + return -EISDIR; + + if (ne_delete(session, path)) { + fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session)); + return -ENOENT; + } + + stat_cache_invalidate(path); + dir_cache_invalidate_parent(path); + + return 0; +} + +static int dav_rmdir(const char *path) { + int r; + struct stat st; + ne_session *session; + + path = path_cvt(path); + + if (debug) + fprintf(stderr, "rmdir(%s)\n", path); + + if (!(session = session_get())) + return -EIO; + + if ((r = get_stat(path, &st)) < 0) + return r; + + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + if (ne_delete(session, path)) { + fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session)); + return -ENOENT; + } + + stat_cache_invalidate(path); + dir_cache_invalidate_parent(path); + + return 0; +} + +static int dav_mkdir(const char *path, mode_t mode) { + char fn[PATH_MAX]; + ne_session *session; + + path = path_cvt(path); + + if (debug) + fprintf(stderr, "mkdir(%s)\n", path); + + if (!(session = session_get())) + return -EIO; + + snprintf(fn, sizeof(fn), "%s/", path); + + if (ne_mkcol(session, fn)) { + fprintf(stderr, "MKCOL failed: %s\n", ne_get_error(session)); + return -ENOENT; + } + + stat_cache_invalidate(path); + dir_cache_invalidate_parent(path); + + return 0; +} + +static int dav_rename(const char *from, const char *to) { + ne_session *session; + int r = 0; + + from = strdup(path_cvt(from)); + to = path_cvt(to); + + if (debug) + fprintf(stderr, "rename(%s, %s)\n", from, to); + + if (!(session = session_get())) { + r = -EIO; + goto finish; + } + + if (ne_move(session, 1, from, to)) { + fprintf(stderr, "MOVE failed: %s\n", ne_get_error(session)); + r = -ENOENT; + goto finish; + } + + stat_cache_invalidate(from); + stat_cache_invalidate(to); + + dir_cache_invalidate_parent(from); + dir_cache_invalidate_parent(to); + +finish: + + free((char*) from); + + return r; +} + +static int dav_release(const char *path, int flags) { + void *f = NULL; + int r = 0; + ne_session *session; + + path = path_cvt(path); + + if (debug) + fprintf(stderr, "release(%s)\n", path); + + if (!(session = session_get())) { + r = -EIO; + goto finish; + } + + if (!(f = file_cache_get(path))) { + fprintf(stderr, "release() called for closed file\n"); + r = -EFAULT; + goto finish; + } + + if (file_cache_close(f) < 0) { + r = -errno; + goto finish; + } + +finish: + if (f) + file_cache_unref(f); + + return r; +} + +static int dav_fsync(const char *path, int isdatasync) { + void *f = NULL; + int r = 0; + ne_session *session; + + path = path_cvt(path); + if (debug) + fprintf(stderr, "fsync(%s)\n", path); + + if (!(session = session_get())) { + r = -EIO; + goto finish; + } + + if (!(f = file_cache_get(path))) { + fprintf(stderr, "fsync() called for closed file\n"); + r = -EFAULT; + goto finish; + } + + if (file_cache_sync(f) < 0) { + r = -errno; + goto finish; + } + +finish: + + if (f) + file_cache_unref(f); + + return r; +} + +static int dav_mknod(const char *path, mode_t mode, dev_t rdev) { + char tempfile[PATH_MAX]; + int fd; + ne_session *session; + + path = path_cvt(path); + if (debug) + fprintf(stderr, "mknod(%s)\n", path); + + if (!(session = session_get())) + return -EIO; + + if (!S_ISREG(mode)) + return -ENOTSUP; + + snprintf(tempfile, sizeof(tempfile), "%s/fusedav-empty-XXXXXX", "/tmp"); + if ((fd = mkstemp(tempfile)) < 0) + return -errno; + + unlink(tempfile); + + if (ne_put(session, path, fd)) { + fprintf(stderr, "mknod:PUT failed: %s\n", ne_get_error(session)); + close(fd); + return -EACCES; + } + + close(fd); + + stat_cache_invalidate(path); + dir_cache_invalidate_parent(path); + + return 0; +} + +static int dav_open(const char *path, int flags) { + void *f; + + if (debug) + fprintf(stderr, "open(%s)\n", path); + + path = path_cvt(path); + if (!(f = file_cache_open(path, flags))) + return -errno; + + file_cache_unref(f); + + return 0; +} + +static int dav_read(const char *path, char *buf, size_t size, off_t offset) { + void *f = NULL; + ssize_t r; + + path = path_cvt(path); + if (debug) + fprintf(stderr, "read(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size); + + if (!(f = file_cache_get(path))) { + fprintf(stderr, "read() called for closed file\n"); + r = -EFAULT; + goto finish; + } + + if ((r = file_cache_read(f, buf, size, offset)) < 0) { + r = -errno; + goto finish; + } + +finish: + if (f) + file_cache_unref(f); + + return r; +} + +static int dav_write(const char *path, const char *buf, size_t size, off_t offset) { + void *f = NULL; + ssize_t r; + + path = path_cvt(path); + if (debug) + fprintf(stderr, "write(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size); + + if (!(f = file_cache_get(path))) { + fprintf(stderr, "write() called for closed file\n"); + r = -EFAULT; + goto finish; + } + + if ((r = file_cache_write(f, buf, size, offset)) < 0) { + r = -errno; + goto finish; + } + +finish: + if (f) + file_cache_unref(f); + + return r; +} + + +static int dav_truncate(const char *path, off_t size) { + void *f = NULL; + int r = 0; + ne_session *session; + + path = path_cvt(path); + if (debug) + fprintf(stderr, "truncate(%s, %lu)\n", path, (unsigned long) size); + + if (!(session = session_get())) + r = -EIO; + goto finish; + + if (!(f = file_cache_get(path))) { + fprintf(stderr, "truncate() called for closed file\n"); + r = -EFAULT; + goto finish; + } + + if (file_cache_truncate(f, size) < 0) { + r = -errno; + goto finish; + } + +finish: + if (f) + file_cache_unref(f); + + return r; +} + + +static struct fuse_operations dav_oper = { + .getattr = dav_getattr, + .getdir = dav_getdir, + .mknod = dav_mknod, + .mkdir = dav_mkdir, + .unlink = dav_unlink, + .rmdir = dav_rmdir, + .rename = dav_rename, +/* .chmod = dav_chmod,*/ + .truncate = dav_truncate, +/* .utime = dav_utime,*/ + .open = dav_open, + .read = dav_read, + .write = dav_write, + .release = dav_release, + .fsync = dav_fsync +}; + +static void usage(char *argv0) { + char *e; + + if ((e = strrchr(argv0, '/'))) + e++; + else + e = argv0; + + fprintf(stderr, + "%s [-h] [-D] [-u USERNAME] [-p PASSWORD] URL MOUNTPOINT\n" + "\t-h Show this help\n" + "\t-D Enable debug mode\n" + "\t-u Username if required\n" + "\t-p Password if required\n", + e); +} + +static void exit_handler(int s) { + static const char m[] = "Signal caught\n"; + write(2, m, strlen(m)); + if(fuse != NULL) + fuse_exit(fuse); +} + +static int setup_signal_handlers(void) { + struct sigaction sa; + + sa.sa_handler = exit_handler; + sigemptyset(&(sa.sa_mask)); + sa.sa_flags = 0; + + if (sigaction(SIGHUP, &sa, NULL) == -1 || + sigaction(SIGINT, &sa, NULL) == -1 || + sigaction(SIGTERM, &sa, NULL) == -1) { + + fprintf(stderr, "Cannot set exit signal handlers: %s\n", strerror(errno)); + return -1; + } + + sa.sa_handler = SIG_IGN; + + if (sigaction(SIGPIPE, &sa, NULL) == -1) { + fprintf(stderr, "Cannot set ignored signals: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) { + int c; + char *u=NULL, *p = NULL; + int fuse_fd = -1; + int ret = 1; + + if (ne_sock_init()) { + fprintf(stderr, "Failed to initialize libneon.\n"); + goto finish; + } + + openssl_thread_setup(); + + mask = umask(0); + umask(mask); + + cache_alloc(); + + if (setup_signal_handlers() < 0) + goto finish; + + while ((c = getopt(argc, argv, "hu:p:D")) != -1) { + + switch(c) { + case 'u': + u = optarg; + break; + + case 'p': + p = optarg; + break; + + case 'D': + debug = !debug; + break; + + case 'h': + default: + usage(argv[0]); + goto finish; + } + } + + if (optind != argc-2) { + usage(argv[0]); + goto finish; + } + + if (session_set_uri(argv[optind], u, p) < 0) { + usage(argv[0]); + goto finish; + } + + if ((fuse_fd = fuse_mount(argv[optind+1], NULL)) < 0) { + fprintf(stderr, "Failed to mount FUSE file system.\n"); + goto finish; + } + + if (!(fuse = fuse_new(fuse_fd, 0, &dav_oper))) { + fprintf(stderr, "Failed to create FUSE object.\n"); + goto finish; + } + + fuse_loop_mt(fuse); + + ret = 0; + +finish: + + if (fuse) + fuse_destroy(fuse); + + if (fuse_fd >= 0) + fuse_unmount(argv[optind]); + + file_cache_close_all(); + cache_free(); + session_free(); + openssl_thread_cleanup(); + + return ret; +} |