Code

Inital commit
authorLennart Poettering <lennart@poettering.net>
Mon, 29 Mar 2004 01:40:30 +0000 (01:40 +0000)
committerLennart Poettering <lennart@poettering.net>
Mon, 29 Mar 2004 01:40:30 +0000 (01:40 +0000)
git-svn-id: file:///home/lennart/svn/public/fusedav/trunk@3 e35a362c-bbd6-0310-a59f-a4efcb1729c4

src/Makefile [new file with mode: 0644]
src/filecache.c [new file with mode: 0644]
src/filecache.h [new file with mode: 0644]
src/fusedav.c [new file with mode: 0644]
src/fusedav.h [new file with mode: 0644]
src/openssl-thread.c [new file with mode: 0644]
src/openssl-thread.h [new file with mode: 0644]
src/session.c [new file with mode: 0644]
src/session.h [new file with mode: 0644]
src/statcache.c [new file with mode: 0644]
src/statcache.h [new file with mode: 0644]

diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..bea2a08
--- /dev/null
@@ -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 (file)
index 0000000..429385d
--- /dev/null
@@ -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 (file)
index 0000000..90972b0
--- /dev/null
@@ -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 (file)
index 0000000..17f1001
--- /dev/null
@@ -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 (file)
index 0000000..f233066
--- /dev/null
@@ -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 (file)
index 0000000..8dd0506
--- /dev/null
@@ -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 (file)
index 0000000..f311760
--- /dev/null
@@ -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 (file)
index 0000000..e4780e9
--- /dev/null
@@ -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 (file)
index 0000000..5ac0114
--- /dev/null
@@ -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 (file)
index 0000000..3622a73
--- /dev/null
@@ -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 (file)
index 0000000..5af9bd9
--- /dev/null
@@ -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