summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2004-03-29 01:40:30 +0000
committerLennart Poettering <lennart@poettering.net>2004-03-29 01:40:30 +0000
commit868905c5d44902e293d9837bdcca3751e2b92dc8 (patch)
tree7cda49b2af3ec3221bfb30d1781216a9f364973c
parentbca0482f2664901b9763f56d7113a85f84cb8a49 (diff)
Inital commit
git-svn-id: file:///home/lennart/svn/public/fusedav/trunk@3 e35a362c-bbd6-0310-a59f-a4efcb1729c4
-rw-r--r--src/Makefile16
-rw-r--r--src/filecache.c398
-rw-r--r--src/filecache.h19
-rw-r--r--src/fusedav.c727
-rw-r--r--src/fusedav.h6
-rw-r--r--src/openssl-thread.c40
-rw-r--r--src/openssl-thread.h7
-rw-r--r--src/session.c197
-rw-r--r--src/session.h12
-rw-r--r--src/statcache.c361
-rw-r--r--src/statcache.h20
11 files changed, 1803 insertions, 0 deletions
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 <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include <ne_props.h>
+#include <ne_uri.h>
+#include <ne_session.h>
+#include <ne_utils.h>
+#include <ne_socket.h>
+#include <ne_auth.h>
+#include <ne_dates.h>
+#include <ne_basic.h>
+
+#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 <sys/types.h>
+
+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 <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;
+}
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 <pthread.h>
+#include <openssl/crypto.h>
+
+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 <stdio.h>
+#include <assert.h>
+#include <pthread.h>
+#include <string.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <ne_uri.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 "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.h>
+
+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 <stdio.h>
+#include <inttypes.h>
+#include <time.h>
+#include <string.h>
+#include <malloc.h>
+#include <pthread.h>
+#include <assert.h>
+
+#include "statcache.h"
+#include "fusedav.h"
+
+#include <ne_uri.h>
+
+#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 <sys/stat.h>
+
+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