From 868905c5d44902e293d9837bdcca3751e2b92dc8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 29 Mar 2004 01:40:30 +0000 Subject: Inital commit git-svn-id: file:///home/lennart/svn/public/fusedav/trunk@3 e35a362c-bbd6-0310-a59f-a4efcb1729c4 --- src/Makefile | 16 ++ src/filecache.c | 398 ++++++++++++++++++++++++++++ src/filecache.h | 19 ++ src/fusedav.c | 727 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/fusedav.h | 6 + src/openssl-thread.c | 40 +++ src/openssl-thread.h | 7 + src/session.c | 197 ++++++++++++++ src/session.h | 12 + src/statcache.c | 361 +++++++++++++++++++++++++ src/statcache.h | 20 ++ 11 files changed, 1803 insertions(+) create mode 100644 src/Makefile create mode 100644 src/filecache.c create mode 100644 src/filecache.h create mode 100644 src/fusedav.c create mode 100644 src/fusedav.h create mode 100644 src/openssl-thread.c create mode 100644 src/openssl-thread.h create mode 100644 src/session.c create mode 100644 src/session.h create mode 100644 src/statcache.c create mode 100644 src/statcache.h diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..bea2a08 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,16 @@ +CC=gcc +CFLAGS=-g -Wall -pipe -O0 -I/usr/include/neon +LIBS=/usr/lib/libfuse.a -lpthread -lneon + +all: fusexmp fusedav + +fusexmp: fusexmp.o + $(CC) -o $@ $^ $(LIBS) + +fusedav: fusedav.o statcache.o filecache.o session.o openssl-thread.o + $(CC) -o $@ $^ $(LIBS) + +clean: + rm -f *.o fusexmp fusedav + +.PHONY: clean all diff --git a/src/filecache.c b/src/filecache.c new file mode 100644 index 0000000..429385d --- /dev/null +++ b/src/filecache.c @@ -0,0 +1,398 @@ +#define _XOPEN_SOURCE 500 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "filecache.h" +#include "fusedav.h" +#include "session.h" +#include "statcache.h" + +struct file_info { + char *filename; + int fd; + off_t server_length, length, present; + + int readable; + int writable; + + int modified; + + int ref, dead; + + pthread_mutex_t mutex; + + /* This field is locked by files_mutex, not by file_info->mutex */ + struct file_info *next; +}; + +static struct file_info *files = NULL; +static pthread_mutex_t files_mutex = PTHREAD_MUTEX_INITIALIZER; + +int file_cache_sync_unlocked(struct file_info *fi); + +void* file_cache_get(const char *path) { + struct file_info *f, *r = NULL; + + pthread_mutex_lock(&files_mutex); + + for (f = files; f; f = f->next) { + + pthread_mutex_lock(&f->mutex); + if (!f->dead && f->filename && !strcmp(path, f->filename)) { + f->ref++; + r = f; + } + pthread_mutex_unlock(&f->mutex); + + if (r) + break; + } + + pthread_mutex_unlock(&files_mutex); + return f; +} + +static void file_cache_free_unlocked(struct file_info *fi) { + assert(fi && fi->dead && fi->ref == 0); + + free(fi->filename); + + if (fi->fd >= 0) + close(fi->fd); + + pthread_mutex_destroy(&fi->mutex); + free(fi); +} + +void file_cache_unref(void *f) { + struct file_info *fi = f; + assert(fi); + + pthread_mutex_lock(&fi->mutex); + + assert(fi->ref >= 1); + fi->ref--; + + if (!fi->ref && fi->dead) { + file_cache_sync_unlocked(fi); + file_cache_free_unlocked(fi); + } + + pthread_mutex_unlock(&fi->mutex); +} + +static void file_cache_unlink(struct file_info *fi) { + struct file_info *s, *prev; + assert(fi); + + pthread_mutex_lock(&files_mutex); + + for (s = files, prev = NULL; s; s = s->next) { + if (s == fi) { + if (prev) + prev->next = s->next; + else + files = s->next; + + break; + } + + prev = s; + } + + pthread_mutex_unlock(&files_mutex); +} + +int file_cache_close(void *f) { + struct file_info *fi = f; + int r = 0; + assert(fi); + + file_cache_unlink(f); + + pthread_mutex_lock(&fi->mutex); + fi->dead = 1; + pthread_mutex_unlock(&fi->mutex); + + return r; +} + +void* file_cache_open(const char *path, int flags) { + struct file_info *fi; + char tempfile[PATH_MAX]; + char *length = NULL; + ne_request *req; + ne_session *session; + + if ((fi = file_cache_get(path))) { + if (flags & O_RDONLY || flags & O_RDWR) fi->readable = 1; + if (flags & O_WRONLY || flags & O_RDWR) fi->writable = 1; + return fi; + } + + if (!(session = session_get())) { + errno = -EIO; + return NULL; + } + + fi = malloc(sizeof(struct file_info)); + memset(fi, 0, sizeof(struct file_info)); + fi->fd = -1; + + fi->filename = strdup(path); + + snprintf(tempfile, sizeof(tempfile), "%s/fusedav-cache-XXXXXX", "/tmp"); + if ((fi->fd = mkstemp(tempfile)) < 0) + goto fail; + unlink(tempfile); + + req = ne_request_create(session, "HEAD", path); + assert(req); + + ne_add_response_header_handler(req, "Content-Length", ne_duplicate_header, &length); + + if (ne_request_dispatch(req) != NE_OK) { + fprintf(stderr, "HEAD failed: %s\n", ne_get_error(session)); + errno = ENOENT; + goto fail; + } + + if (!length) { + fprintf(stderr, "HEAD did not return content length.\n"); + errno = EPROTO; + goto fail; + } + + fi->server_length = fi->length = atoi(length); + + ne_request_destroy(req); + free(length); + + if (flags & O_RDONLY || flags & O_RDWR) fi->readable = 1; + if (flags & O_WRONLY || flags & O_RDWR) fi->writable = 1; + + pthread_mutex_init(&fi->mutex, NULL); + + pthread_mutex_lock(&files_mutex); + fi->next = files; + files = fi; + pthread_mutex_unlock(&files_mutex); + + fi->ref = 1; + + return fi; + +fail: + + if (req) + ne_request_destroy(req); + + if (length) + free(length); + + if (fi) { + if (fi->fd >= 0) + close(fi->fd); + free(fi->filename); + free(fi); + } + + return NULL; +} + +static int load_up_to_unlocked(struct file_info *fi, off_t l) { + ne_content_range range; + assert(fi); + ne_session *session; + + if (!(session = session_get())) { + errno = EIO; + return -1; + } + + if (l > fi->server_length) + l = fi->server_length; + + if (l <= fi->present) + return 0; + + if (lseek(fi->fd, fi->present, SEEK_SET) != fi->present) + return -1; + + range.start = fi->present; + range.end = l-1; + + if (ne_get_range(session, fi->filename, &range, fi->fd)) { + fprintf(stderr, "GET failed: %s\n", ne_get_error(session)); + errno = ENOENT; + return -1; + } + + fi->present = l; + return 0; +} + +int file_cache_read(void *f, char *buf, size_t size, off_t offset) { + struct file_info *fi = f; + ssize_t r = -1; + + assert(fi && buf && size); + + pthread_mutex_lock(&fi->mutex); + + if (load_up_to_unlocked(fi, offset+size) < 0) + goto finish; + + if ((r = pread(fi->fd, buf, size, offset)) < 0) + goto finish; + +finish: + + pthread_mutex_unlock(&fi->mutex); + + return r; +} + +int file_cache_write(void *f, const char *buf, size_t size, off_t offset) { + struct file_info *fi = f; + ssize_t r = -1; + + assert (fi); + + pthread_mutex_lock(&fi->mutex); + + if (!fi->writable) { + errno = EBADF; + goto finish; + } + + if (load_up_to_unlocked(fi, offset) < 0) + goto finish; + + if ((r = pwrite(fi->fd, buf, size, offset)) < 0) + goto finish; + + if (offset+size > fi->present) + fi->present = offset+size; + + if (offset+size > fi->length) + fi->length = offset+size; + + fi->modified = 1; + + r = 0; + +finish: + pthread_mutex_unlock(&fi->mutex); + + return r; +} + +int file_cache_truncate(void *f, off_t s) { + struct file_info *fi = f; + assert(fi); + int r; + + pthread_mutex_lock(&fi->mutex); + + fi->length = s; + r = ftruncate(fi->fd, fi->length); + + pthread_mutex_unlock(&fi->mutex); + + return r; +} + +int file_cache_sync_unlocked(struct file_info *fi) { + int r = -1; + ne_session *session; + assert(fi); + + if (!(session = session_get())) { + errno = EIO; + goto finish; + } + + if (!fi->writable) { + errno = EBADF; + goto finish; + } + + if (!fi->modified) { + r = 0; + goto finish; + } + + if (load_up_to_unlocked(fi, (off_t) -1) < 0) + goto finish; + + if (lseek(fi->fd, 0, SEEK_SET) == (off_t)-1) + goto finish; + + + if (ne_put(session, fi->filename, fi->fd)) { + fprintf(stderr, "PUT failed: %s\n", ne_get_error(session)); + errno = ENOENT; + goto finish; + } + + stat_cache_invalidate(fi->filename); + dir_cache_invalidate_parent(fi->filename); + + r = 0; + +finish: + + return r; +} + +int file_cache_sync(void *f) { + struct file_info *fi = f; + int r = -1; + assert(fi); + + pthread_mutex_lock(&fi->mutex); + r = file_cache_sync_unlocked(fi); + pthread_mutex_unlock(&fi->mutex); + + return r; +} + +int file_cache_close_all(void) { + int r = 0; + + pthread_mutex_lock(&files_mutex); + + while (files) { + struct file_info *fi = files; + + pthread_mutex_lock(&fi->mutex); + fi->ref++; + pthread_mutex_unlock(&fi->mutex); + + pthread_mutex_unlock(&files_mutex); + file_cache_close(fi); + file_cache_unref(fi); + pthread_mutex_lock(&files_mutex); + } + + pthread_mutex_unlock(&files_mutex); + + return r; +} diff --git a/src/filecache.h b/src/filecache.h new file mode 100644 index 0000000..90972b0 --- /dev/null +++ b/src/filecache.h @@ -0,0 +1,19 @@ +#ifndef foofilecachehfoo +#define foofilecachehfoo + +#include + +void* file_cache_open(const char *path, int flags); +void* file_cache_get(const char *path); +void file_cache_unref(void *f); + +int file_cache_close(void *f); + +int file_cache_read(void *f, char *buf, size_t size, off_t offset); +int file_cache_write(void *f, const char *buf, size_t size, off_t offset); +int file_cache_truncate(void *f, off_t s); +int file_cache_sync(void *f); +int file_cache_close_all(void); + + +#endif 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; +} diff --git a/src/fusedav.h b/src/fusedav.h new file mode 100644 index 0000000..f233066 --- /dev/null +++ b/src/fusedav.h @@ -0,0 +1,6 @@ +#ifndef foofusedevhfoo +#define foofusedevhfoo + +extern int debug; + +#endif diff --git a/src/openssl-thread.c b/src/openssl-thread.c new file mode 100644 index 0000000..8dd0506 --- /dev/null +++ b/src/openssl-thread.c @@ -0,0 +1,40 @@ +#include +#include + +static pthread_mutex_t *mutexes; + +static void pthreads_locking_callback(int mode, int n, const char *file, int line) { + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(mutexes+n); + else + pthread_mutex_unlock(mutexes+n); +} + +static unsigned long pthreads_thread_id(void) { + return (unsigned long)pthread_self(); +} + +void openssl_thread_setup(void) { + int i, l; + + mutexes = OPENSSL_malloc((l = CRYPTO_num_locks()) * sizeof(pthread_mutex_t)); + + for (i = 0; i < l; i++) + pthread_mutex_init(mutexes+i, NULL); + + CRYPTO_set_id_callback(pthreads_thread_id); + CRYPTO_set_locking_callback(pthreads_locking_callback); +} + +void openssl_thread_cleanup(void) { + int i, l; + + CRYPTO_set_locking_callback(NULL); + + l = CRYPTO_num_locks(); + for (i = 0; i < l; i++) + pthread_mutex_destroy(mutexes+i); + + OPENSSL_free(mutexes); +} + diff --git a/src/openssl-thread.h b/src/openssl-thread.h new file mode 100644 index 0000000..f311760 --- /dev/null +++ b/src/openssl-thread.h @@ -0,0 +1,7 @@ +#ifndef fooopensslhfoo +#define fooopensslhfoo + +void openssl_thread_setup(void); +void openssl_thread_cleanup(void); + +#endif diff --git a/src/session.c b/src/session.c new file mode 100644 index 0000000..e4780e9 --- /dev/null +++ b/src/session.c @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "session.h" + + +static pthread_once_t session_once = PTHREAD_ONCE_INIT; +static pthread_key_t session_tsd_key; + +static ne_uri uri; +static int b_uri = 0; + +static const char *username = NULL, *password = NULL; +const char *base_directory = NULL; + +static pthread_mutex_t credential_mutex = PTHREAD_MUTEX_INITIALIZER; + + +static char* ask_user(char *p, int hidden) { + char q[256], *r; + struct termios t; + int c, l; + + + if (hidden) { + if (!isatty(fileno(stdin))) + hidden = 0; + else { + if (tcgetattr(fileno(stdin), &t) < 0) + hidden = 0; + else { + c = t.c_lflag; + t.c_lflag &= ~ECHO; + if (tcsetattr(fileno(stdin), TCSANOW, &t) < 0) + hidden = 0; + } + } + } + + fprintf(stderr, "%s: ", p); + r = fgets(q, sizeof(q), stdin); + l = strlen(q); + if (l && q[l-1] == '\n') + q[l-1] = 0; + + if (hidden) { + t.c_lflag = c; + tcsetattr(fileno(stdin), TCSANOW, &t); + fprintf(stderr, "\n"); + } + + return r ? strdup(r) : NULL; +} + +static int ssl_verify_cb(void *userdata, int failures, const ne_ssl_certificate *cert) { + return 0; +} + +static int ne_auth_creds_cb(void *userdata, const char *realm, int attempt, char *u, char *p) { + int r = -1; + + + pthread_mutex_lock(&credential_mutex); + + if (attempt) { + fprintf(stderr, "Authenication failure!\n"); + free((void*) username); + free((void*) password); + username = password = NULL; + } + + if (!username) + username = ask_user("Username", 0); + + if (username && !password) + password = ask_user("Password", 1); + + if (username && password) { + snprintf(u, NE_ABUFSIZ, "%s", username); + snprintf(p, NE_ABUFSIZ, "%s", password); + r = 0; + } + + pthread_mutex_unlock(&credential_mutex); + return r; +} + +static ne_session *session_open(void) { + char *scheme = NULL; + ne_session *session; + + if (!b_uri) + return NULL; + + scheme = uri.scheme ? uri.scheme : "http"; + + if (!(session = ne_session_create(scheme, uri.host, uri.port ? uri.port : ne_uri_defaultport(scheme)))) { + fprintf(stderr, "Failed to create session\n"); + return NULL; + } + + ne_ssl_set_verify(session, ssl_verify_cb, NULL); + ne_set_server_auth(session, ne_auth_creds_cb, NULL); + return session; +} + +static void session_destroy(void *s) { + ne_session *session = s; + assert(s); + ne_session_destroy(session); +} + +static void session_tsd_key_init(void) { + pthread_key_create(&session_tsd_key, session_destroy); +} + +ne_session *session_get(void) { + ne_session *session; + + pthread_once(&session_once, session_tsd_key_init); + + if ((session = pthread_getspecific(session_tsd_key))) + return session; + + session = session_open(); + pthread_setspecific(session_tsd_key, session); + + return session; +} + +int session_set_uri(const char *s, const char *u, const char *p) { + assert(!b_uri && !username && !password); + int l; + + if (ne_uri_parse(s, &uri)) { + fprintf(stderr, "Invalid URI <%s>\n", s); + goto finish; + } + + b_uri = 1; + + if (!uri.host) { + fprintf(stderr, "Missing host part in URI <%s>\n", s); + goto finish; + } + + base_directory = strdup(uri.path); + l = strlen(base_directory); + if (base_directory[l-1] == '/') + ((char*) base_directory)[l-1] = 0; + + if (u) + username = strdup(u); + + if (p) + password = strdup(p); + + return 0; + +finish: + + if (b_uri) { + ne_uri_free(&uri); + b_uri = 0; + } + + return -1; +} + + +void session_free(void) { + if (b_uri) { + ne_uri_free(&uri); + b_uri = 0; + } + + free((char*) username); + free((char*) password); + free((char*) base_directory); + + username = password = base_directory = NULL; +} + diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..5ac0114 --- /dev/null +++ b/src/session.h @@ -0,0 +1,12 @@ +#ifndef foosessionhfoo +#define foosessionhfoo + +#include + +ne_session *session_get(void); +int session_set_uri(const char *s, const char*u, const char*p); +void session_free(void); + +extern const char *base_directory; + +#endif diff --git a/src/statcache.c b/src/statcache.c new file mode 100644 index 0000000..3622a73 --- /dev/null +++ b/src/statcache.c @@ -0,0 +1,361 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "statcache.h" +#include "fusedav.h" + +#include + +#define CACHE_SIZE 2049 +#define CACHE_TIMEOUT 60 + +struct dir_entry { + struct dir_entry *next; + int is_dir; + char filename[]; +}; + +struct cache_entry { + struct { + int valid; + uint32_t hash; + char *filename; + time_t dead; + struct stat st; + } stat_info; + + struct { + int valid, filling, in_use, valid2; + uint32_t hash; + char *filename; + struct dir_entry *entries, *entries2; + time_t dead, dead2; + } dir_info; +}; + +static struct cache_entry *cache = NULL; +static pthread_mutex_t stat_cache_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t dir_cache_mutex = PTHREAD_MUTEX_INITIALIZER; + +static uint32_t calc_hash(const char *s) { + uint32_t h = 0; + + for (; *s; s++) { + h ^= * (uint8_t*) s; + h = (h << 8) | (h >> 24); + } + + return h; +} + +int stat_cache_get(const char *fn, struct stat *st) { + uint32_t h; + struct cache_entry *ce; + int r = -1; + + if (debug) + fprintf(stderr, "CGET: %s\n", fn); + + assert(cache); + + h = calc_hash(fn); + ce = cache + (h % CACHE_SIZE); + + pthread_mutex_lock(&stat_cache_mutex); + + if (ce->stat_info.valid && + ce->stat_info.filename && + ce->stat_info.hash == h && + !strcmp(ce->stat_info.filename, fn) && + time(NULL) <= ce->stat_info.dead) { + + *st = ce->stat_info.st; + r = 0; + } + + pthread_mutex_unlock(&stat_cache_mutex); + + return r; +} + +void stat_cache_set(const char *fn, const struct stat*st) { + uint32_t h; + struct cache_entry *ce; + + if (debug) + fprintf(stderr, "CSET: %s\n", fn); + assert(cache); + + h = calc_hash(fn); + ce = cache + (h % CACHE_SIZE); + + pthread_mutex_lock(&stat_cache_mutex); + + if (!ce->stat_info.filename || ce->stat_info.hash != h || strcmp(ce->stat_info.filename, fn)) { + free(ce->stat_info.filename); + ce->stat_info.filename = strdup(fn); + ce->stat_info.hash = h; + } + + ce->stat_info.st = *st; + ce->stat_info.dead = time(NULL)+CACHE_TIMEOUT; + ce->stat_info.valid = 1; + + pthread_mutex_unlock(&stat_cache_mutex); +} + +void stat_cache_invalidate(const char*fn) { + uint32_t h; + struct cache_entry *ce; + + assert(cache); + + h = calc_hash(fn); + ce = cache + (h % CACHE_SIZE); + + pthread_mutex_lock(&stat_cache_mutex); + + ce->stat_info.valid = 0; + free(ce->stat_info.filename); + ce->stat_info.filename = NULL; + + pthread_mutex_unlock(&stat_cache_mutex); +} + +static void free_dir_entries(struct dir_entry *de) { + + while (de) { + struct dir_entry *next = de->next; + free(de); + de = next; + } +} + + +void dir_cache_begin(const char *fn) { + uint32_t h; + struct cache_entry *ce; + struct dir_entry *de = NULL, *de2 = NULL; + assert(cache); + + h = calc_hash(fn); + ce = cache + (h % CACHE_SIZE); + + pthread_mutex_lock(&dir_cache_mutex); + + if (!ce->dir_info.filling) { + + if (!ce->dir_info.filename || ce->dir_info.hash != h || strcmp(ce->dir_info.filename, fn)) { + free(ce->dir_info.filename); + ce->dir_info.filename = strdup(fn); + ce->dir_info.hash = h; + + de = ce->dir_info.entries; + ce->dir_info.entries = NULL; + ce->dir_info.valid = 0; + } + + de2 = ce->dir_info.entries2; + ce->dir_info.entries2 = NULL; + ce->dir_info.valid2 = 0; + ce->dir_info.filling = 1; + } + + pthread_mutex_unlock(&dir_cache_mutex); + free_dir_entries(de); + free_dir_entries(de2); +} + +void dir_cache_finish(const char *fn, int success) { + uint32_t h; + struct cache_entry *ce; + struct dir_entry *de = NULL; + assert(cache); + + h = calc_hash(fn); + ce = cache + (h % CACHE_SIZE); + + pthread_mutex_lock(&dir_cache_mutex); + + if (ce->dir_info.filling && + ce->dir_info.filename && + ce->dir_info.hash == h && + !strcmp(ce->dir_info.filename, fn)) { + + assert(!ce->dir_info.valid2); + + if (success) { + + ce->dir_info.valid2 = 1; + ce->dir_info.filling = 0; + ce->dir_info.dead2 = time(NULL)+CACHE_TIMEOUT; + + if (!ce->dir_info.in_use) { + de = ce->dir_info.entries; + ce->dir_info.entries = ce->dir_info.entries2; + ce->dir_info.entries2 = NULL; + ce->dir_info.dead = ce->dir_info.dead2; + ce->dir_info.valid2 = 0; + ce->dir_info.valid = 1; + } + + } else { + ce->dir_info.filling = 0; + de = ce->dir_info.entries2; + ce->dir_info.entries2 = NULL; + } + } + + pthread_mutex_unlock(&dir_cache_mutex); + free_dir_entries(de); +} + +void dir_cache_add(const char *fn, const char *subdir, int is_dir) { + uint32_t h; + struct cache_entry *ce; + assert(cache); + + h = calc_hash(fn); + ce = cache + (h % CACHE_SIZE); + + pthread_mutex_lock(&dir_cache_mutex); + + if (ce->dir_info.filling && + ce->dir_info.filename && + ce->dir_info.hash == h && + !strcmp(ce->dir_info.filename, fn)) { + + struct dir_entry *n; + + assert(!ce->dir_info.valid2); + + n = malloc(sizeof(struct dir_entry) + strlen(subdir) + 1); + assert(n); + + strcpy(n->filename, subdir); + n->is_dir = is_dir; + + n->next = ce->dir_info.entries2; + ce->dir_info.entries2 = n; + } + + pthread_mutex_unlock(&dir_cache_mutex); +} + +int dir_cache_enumerate(const char *fn, void (*f) (const char*fn, const char *subdir, int is_dir, void *user), void *user) { + uint32_t h; + struct cache_entry *ce; + struct dir_entry *de = NULL; + assert(cache && f); + int r = -1; + + h = calc_hash(fn); + ce = cache + (h % CACHE_SIZE); + + pthread_mutex_lock(&dir_cache_mutex); + + if (ce->dir_info.valid && + ce->dir_info.filename && + ce->dir_info.hash == h && + !strcmp(ce->dir_info.filename, fn) && + time(NULL) <= ce->dir_info.dead) { + + ce->dir_info.in_use = 1; + pthread_mutex_unlock(&dir_cache_mutex); + + for (de = ce->dir_info.entries; de; de = de->next) + f(fn, de->filename, de->is_dir, user); + + pthread_mutex_lock(&dir_cache_mutex); + ce->dir_info.in_use = 0; + + if (ce->dir_info.valid2) { + de = ce->dir_info.entries; + ce->dir_info.entries = ce->dir_info.entries2; + ce->dir_info.entries2 = NULL; + ce->dir_info.dead = ce->dir_info.dead2; + ce->dir_info.valid2 = 0; + ce->dir_info.valid = 1; + } + + r = 0; + } + + pthread_mutex_unlock(&dir_cache_mutex); + free_dir_entries(de); + + return r; +} + +void dir_cache_invalidate(const char*fn) { + uint32_t h; + struct cache_entry *ce; + struct dir_entry *de = NULL; + assert(cache && fn); + + h = calc_hash(fn); + ce = cache + (h % CACHE_SIZE); + pthread_mutex_lock(&dir_cache_mutex); + + if (ce->dir_info.valid && + ce->dir_info.filename && + ce->dir_info.hash == h && + !strcmp(ce->dir_info.filename, fn)) { + + ce->dir_info.valid = 0; + de = ce->dir_info.entries; + ce->dir_info.entries = NULL; + } + + pthread_mutex_unlock(&dir_cache_mutex); + free_dir_entries(de); +} + +void dir_cache_invalidate_parent(const char *fn) { + char *p; + + if ((p = ne_path_parent(fn))) { + int l = strlen(p); + + if (strcmp(p, "/") && l) { + if (p[l-1] == '/') + p[l-1] = 0; + } + + dir_cache_invalidate(p); + free(p); + } else + dir_cache_invalidate(fn); +} + +void cache_free(void) { + uint32_t h; + struct cache_entry *ce; + + if (!cache) + return; + + for (h = 0, ce = cache; h < CACHE_SIZE; h++, ce++) { + free(ce->stat_info.filename); + free(ce->dir_info.filename); + free_dir_entries(ce->dir_info.entries); + free_dir_entries(ce->dir_info.entries2); + } + + memset(cache, 0, sizeof(struct cache_entry)*CACHE_SIZE); +} + +void cache_alloc(void) { + + if (cache) + return; + + cache = malloc(sizeof(struct cache_entry)*CACHE_SIZE); + memset(cache, 0, sizeof(struct cache_entry)*CACHE_SIZE); +} + diff --git a/src/statcache.h b/src/statcache.h new file mode 100644 index 0000000..5af9bd9 --- /dev/null +++ b/src/statcache.h @@ -0,0 +1,20 @@ +#ifndef foostatcachehfoo +#define foostatcachehfoo + +#include + +int stat_cache_get(const char *fn, struct stat *st); +void stat_cache_set(const char *fn, const struct stat *st); +void stat_cache_invalidate(const char*fn); + +void dir_cache_invalidate(const char*fn); +void dir_cache_invalidate_parent(const char *fn); +void dir_cache_begin(const char *fn); +void dir_cache_finish(const char *fn, int success); +void dir_cache_add(const char *fn, const char *subdir, int is_dir); +int dir_cache_enumerate(const char *fn, void (*f) (const char*fn, const char *subdir, int is_dir, void *user), void *user); + +void cache_free(void); +void cache_alloc(void); + +#endif -- cgit