Code

Inital commit
[fusedav.git] / src / fusedav.c
1 #include <signal.h>
2 #include <pthread.h>
3 #include <time.h>
4 #include <assert.h>
5 #include <stdio.h>
6 #include <string.h>
7 #include <stdlib.h>
8 #include <unistd.h>
9 #include <fcntl.h>
10 #include <dirent.h>
11 #include <errno.h>
12 #include <sys/statfs.h>
13 #include <getopt.h>
15 #include <ne_request.h>
16 #include <ne_basic.h>
17 #include <ne_props.h>
18 #include <ne_utils.h>
19 #include <ne_socket.h>
20 #include <ne_auth.h>
21 #include <ne_dates.h>
23 #include <fuse.h>
25 #include "statcache.h"
26 #include "filecache.h"
27 #include "session.h"
28 #include "openssl-thread.h"
30 const ne_propname query_properties[] = {
31     { "DAV:", "resourcetype" },
32     { "http://apache.org/dav/props/", "executable" },
33     { "DAV:", "getcontentlength" },
34     { "DAV:", "getlastmodified" },
35     { "DAV:", "creationdate" },
36     { NULL, NULL }
37 };
39 mode_t mask = 0;
40 int debug = 0;
41 struct fuse* fuse = NULL;
43 struct fill_info {
44     fuse_dirh_t h;
45     fuse_dirfil_t filler;
46     const char *root;
47 };
49 static int get_stat(const char *path, struct stat *stbuf);
51 static pthread_once_t path_cvt_once = PTHREAD_ONCE_INIT;
52 static pthread_key_t path_cvt_tsd_key;
54 static void path_cvt_tsd_key_init(void) {
55     pthread_key_create(&path_cvt_tsd_key, free);
56 }
58 static const char *path_cvt(const char *path) {
59     char *r, *t;
60     int l;
62     pthread_once(&path_cvt_once, path_cvt_tsd_key_init);
63     
64     if ((r = pthread_getspecific(path_cvt_tsd_key)))
65         free(r);
67     t = malloc((l = strlen(base_directory)+strlen(path))+1);
68     assert(t);
69     sprintf(t, "%s%s", base_directory, path);
71     if (l > 1 && t[l-1] == '/')
72         t[l-1] = 0;
74     r = ne_path_escape(t);
75     free(t);
77     pthread_setspecific(path_cvt_tsd_key, r);
78     
79     return r;
80 }
82 static void fill_stat(struct stat* st, const ne_prop_result_set *results, int is_dir) {
83     const char *rt, *e, *gcl, *glm, *cd;
84     const ne_propname resourcetype = { "DAV:", "resourcetype" };
85     const ne_propname executable = { "http://apache.org/dav/props/", "executable" };
86     const ne_propname getcontentlength = { "DAV:", "getcontentlength" };
87     const ne_propname getlastmodified = { "DAV:", "getlastmodified" };
88     const ne_propname creationdate = { "DAV:", "creationdate" };
89         
90     assert(st && results);
92     rt = ne_propset_value(results, &resourcetype);
93     e = ne_propset_value(results, &executable);
94     gcl = ne_propset_value(results, &getcontentlength);
95     glm = ne_propset_value(results, &getlastmodified);
96     cd = ne_propset_value(results, &creationdate);
98     memset(st, 0, sizeof(struct stat));
99     
100     if (is_dir) {
101         st->st_mode = S_IFDIR | 0777;
102         st->st_nlink = 3;            /* find will ignore this directory if nlin <= and st_size == 0 */
103         st->st_size = 4096;
104     } else {
105         st->st_mode = S_IFREG | (e && (*e == 'T' || *e == 't') ? 0777 : 0666);
106         st->st_nlink = 1;
107         st->st_size = gcl ? atoll(gcl) : 0;
108     }
110     st->st_atime = time(NULL);
111     st->st_mtime = glm ? ne_rfc1123_parse(glm) : 0;
112     st->st_ctime = cd ? ne_iso8601_parse(cd) : 0;
114     //fprintf(stderr, "a: %u; m: %u; c: %u\n", st->st_atime, st->st_mtime, st->st_ctime);
116     st->st_mode &= ~mask;
117     
118     st->st_uid = getuid();
119     st->st_gid = getgid();
122 static char *strip_trailing_slash(char *fn, int *is_dir) {
123     size_t l = strlen(fn);
124     assert(fn && is_dir);
125     
126     if ((*is_dir = (fn[l-1] == '/')))
127         fn[l-1] = 0;
129     return fn;
132 static void getdir_propfind_callback(void *userdata, const char *href, const ne_prop_result_set *results) {
133     struct fill_info *f = userdata;
134     struct stat st;
135     char fn[PATH_MAX], *t;
136     int is_dir = 0;
138     assert(f);
140     strncpy(fn, href, sizeof(fn));
141     fn[sizeof(fn)-1] = 0;
142     strip_trailing_slash(fn, &is_dir);
144     if (strcmp(fn, f->root) && fn[0]) {
145         char *h;
146         
147         if ((t = strrchr(fn, '/')))
148             t++;
149         else
150             t = fn;
152         dir_cache_add(f->root, t, is_dir);
153         f->filler(f->h, h = ne_path_unescape(t), is_dir ? DT_DIR : DT_REG);
154         free(h);
155     }
157     fill_stat(&st, results, is_dir);
158     stat_cache_set(fn, &st);
161 static void getdir_cache_callback(const char *root, const char *fn, int is_dir, void *user) {
162     struct fill_info *f = user;
163     assert(f);
164     char path[PATH_MAX];
165     struct stat st;
166     char *h;
168     snprintf(path, sizeof(path), "%s/%s", !strcmp(root, "/") ? "" : root, fn);
169     
170     if (get_stat(path, &st) < 0)
171         return;
172     
173     f->filler(f->h, h = ne_path_unescape(fn), is_dir ? DT_DIR : DT_REG);
174     free(h);
177 static int dav_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) {
178     struct fill_info f;
179     ne_session *session;
181     path = path_cvt(path);
183     if (debug)
184         fprintf(stderr, "getdir(%s)\n", path);
186     f.h = h;
187     f.filler = filler;
188     f.root = path;
190     if (dir_cache_enumerate(path, getdir_cache_callback, &f) < 0) {
192         if (debug)
193             fprintf(stderr, "DIR-CACHE-MISS\n");
194         
195         if (!(session = session_get())) 
196             return -EIO;
198         dir_cache_begin(path);
199         
200         if (ne_simple_propfind(session, path, NE_DEPTH_ONE, query_properties, getdir_propfind_callback, &f) != NE_OK) {
201             dir_cache_finish(path, 2);
202             fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
203             return -ENOENT;
204         }
206         dir_cache_finish(path, 1);
207     }
209     filler(h, ".", DT_DIR);
210     filler(h, "..", DT_DIR);
212     return 0;
215 static void getattr_propfind_callback(void *userdata, const char *href, const ne_prop_result_set *results) {
216     struct stat *st = (struct stat*) userdata;
217     char fn[PATH_MAX];
218     int is_dir;
220     assert(st);
222     strncpy(fn, href, sizeof(fn));
223     fn[sizeof(fn)-1] = 0;
224     strip_trailing_slash(fn, &is_dir);
225     
226     fill_stat(st, results, is_dir);
227     stat_cache_set(fn, st);
230 static int get_stat(const char *path, struct stat *stbuf) {
231     ne_session *session;
233     if (!(session = session_get())) 
234         return -EIO;
236     if (stat_cache_get(path, stbuf) == 0) {
237         return stbuf->st_mode == 0 ? -ENOENT : 0;
238     } else {
239         if (debug)
240             fprintf(stderr, "STAT-CACHE-MISS\n");
241         
242         if (ne_simple_propfind(session, path, NE_DEPTH_ZERO, query_properties, getattr_propfind_callback, stbuf) != NE_OK) {
243             stat_cache_invalidate(path);
244             fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
245             return -ENOENT;
246         }
248         return 0;
249     }
252 static int dav_getattr(const char *path, struct stat *stbuf) {
253     path = path_cvt(path);
254     if (debug)
255         fprintf(stderr, "getattr(%s)\n", path);
256     return get_stat(path, stbuf);
259 static int dav_unlink(const char *path) {
260     int r;
261     struct stat st;
262     ne_session *session;
264     path = path_cvt(path);
266     if (debug)
267         fprintf(stderr, "unlink(%s)\n", path);
269     if (!(session = session_get())) 
270         return -EIO;
272     if ((r = get_stat(path, &st)) < 0)
273         return r;
275     if (!S_ISREG(st.st_mode))
276         return -EISDIR;
277     
278     if (ne_delete(session, path)) {
279         fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
280         return -ENOENT;
281     }
283     stat_cache_invalidate(path);
284     dir_cache_invalidate_parent(path);
285     
286     return 0;
289 static int dav_rmdir(const char *path) {
290     int r;
291     struct stat st;
292     ne_session *session;
294     path = path_cvt(path);
296     if (debug)
297         fprintf(stderr, "rmdir(%s)\n", path);
299     if (!(session = session_get())) 
300         return -EIO;
302     if ((r = get_stat(path, &st)) < 0)
303         return r;
305     if (!S_ISDIR(st.st_mode))
306         return -ENOTDIR;
307     
308     if (ne_delete(session, path)) {
309         fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
310         return -ENOENT;
311     }
313     stat_cache_invalidate(path);
314     dir_cache_invalidate_parent(path);
316     return 0;
319 static int dav_mkdir(const char *path, mode_t mode) {
320     char fn[PATH_MAX];
321     ne_session *session;
323     path = path_cvt(path);
325     if (debug)
326         fprintf(stderr, "mkdir(%s)\n", path);
328     if (!(session = session_get())) 
329         return -EIO;
331     snprintf(fn, sizeof(fn), "%s/", path);
332     
333     if (ne_mkcol(session, fn)) {
334         fprintf(stderr, "MKCOL failed: %s\n", ne_get_error(session));
335         return -ENOENT;
336     }
338     stat_cache_invalidate(path);
339     dir_cache_invalidate_parent(path);
340     
341     return 0;
344 static int dav_rename(const char *from, const char *to) {
345     ne_session *session;
346     int r = 0;
348     from = strdup(path_cvt(from));
349     to = path_cvt(to);
351     if (debug)
352         fprintf(stderr, "rename(%s, %s)\n", from, to);
354     if (!(session = session_get())) {
355         r = -EIO;
356         goto finish;
357     }
359     if (ne_move(session, 1, from, to)) {
360         fprintf(stderr, "MOVE failed: %s\n", ne_get_error(session));
361         r = -ENOENT;
362         goto finish;
363     }
364     
365     stat_cache_invalidate(from);
366     stat_cache_invalidate(to);
368     dir_cache_invalidate_parent(from);
369     dir_cache_invalidate_parent(to);
371 finish:
373     free((char*) from);
374     
375     return r;
378 static int dav_release(const char *path, int flags) {
379     void *f = NULL;
380     int r = 0;
381     ne_session *session;
383     path = path_cvt(path);
385     if (debug)
386         fprintf(stderr, "release(%s)\n", path);
388     if (!(session = session_get())) {
389         r = -EIO;
390         goto finish;
391     }
392     
393     if (!(f = file_cache_get(path))) {
394         fprintf(stderr, "release() called for closed file\n");
395         r = -EFAULT;
396         goto finish;
397     }
399     if (file_cache_close(f) < 0) {
400         r = -errno;
401         goto finish;
402     }
404 finish:
405     if (f)
406         file_cache_unref(f);
407     
408     return r;
411 static int dav_fsync(const char *path, int isdatasync) {
412     void *f = NULL;
413     int r = 0;
414     ne_session *session;
416     path = path_cvt(path);
417     if (debug)
418         fprintf(stderr, "fsync(%s)\n", path);
420     if (!(session = session_get())) {
421         r = -EIO;
422         goto finish;
423     }
425     if (!(f = file_cache_get(path))) {
426         fprintf(stderr, "fsync() called for closed file\n");
427         r = -EFAULT;
428         goto finish;
429     }
431     if (file_cache_sync(f) < 0) {
432         r = -errno;
433         goto finish;
434     }
436 finish:
437     
438     if (f)
439         file_cache_unref(f);
441     return r;
444 static int dav_mknod(const char *path, mode_t mode, dev_t rdev) {
445     char tempfile[PATH_MAX];
446     int fd;
447     ne_session *session;
448     
449     path = path_cvt(path);
450     if (debug)
451         fprintf(stderr, "mknod(%s)\n", path);
453     if (!(session = session_get())) 
454         return -EIO;
456     if (!S_ISREG(mode))
457         return -ENOTSUP;
459     snprintf(tempfile, sizeof(tempfile), "%s/fusedav-empty-XXXXXX", "/tmp");
460     if ((fd = mkstemp(tempfile)) < 0)
461         return -errno;
462     
463     unlink(tempfile);
464     
465     if (ne_put(session, path, fd)) {
466         fprintf(stderr, "mknod:PUT failed: %s\n", ne_get_error(session));
467         close(fd);
468         return -EACCES;
469     }
471     close(fd);
473     stat_cache_invalidate(path);
474     dir_cache_invalidate_parent(path);
476     return 0;
479 static int dav_open(const char *path, int flags) {
480     void *f;
482     if (debug)
483         fprintf(stderr, "open(%s)\n", path);
485     path = path_cvt(path);
486     if (!(f = file_cache_open(path, flags)))
487         return -errno;
489     file_cache_unref(f);
491     return 0;
494 static int dav_read(const char *path, char *buf, size_t size, off_t offset) {
495     void *f = NULL;
496     ssize_t r;
497  
498     path = path_cvt(path);
499     if (debug)
500         fprintf(stderr, "read(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
501     
502     if (!(f = file_cache_get(path))) {
503         fprintf(stderr, "read() called for closed file\n");
504         r = -EFAULT;
505         goto finish;
506     }
508     if ((r = file_cache_read(f, buf, size, offset)) < 0) {
509         r = -errno;
510         goto finish;
511     }
513 finish:
514     if (f)
515         file_cache_unref(f);
516     
517     return r;
520 static int dav_write(const char *path, const char *buf, size_t size, off_t offset) {
521     void *f = NULL;
522     ssize_t r;
524     path = path_cvt(path);
525     if (debug)
526         fprintf(stderr, "write(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
528     if (!(f = file_cache_get(path))) {
529         fprintf(stderr, "write() called for closed file\n");
530         r = -EFAULT;
531         goto finish;
532     }
534     if ((r = file_cache_write(f, buf, size, offset)) < 0) {
535         r = -errno;
536         goto finish;
537     }
538     
539 finish:
540     if (f)
541         file_cache_unref(f);
542     
543     return r;
547 static int dav_truncate(const char *path, off_t size) {
548     void *f = NULL;
549     int r = 0;
550     ne_session *session;
551     
552     path = path_cvt(path);
553     if (debug)
554         fprintf(stderr, "truncate(%s, %lu)\n", path, (unsigned long) size);
556     if (!(session = session_get()))
557         r = -EIO;
558         goto finish;
559     
560     if (!(f = file_cache_get(path))) {
561         fprintf(stderr, "truncate() called for closed file\n");
562         r = -EFAULT;
563         goto finish;
564     }
566     if (file_cache_truncate(f, size) < 0) {
567         r = -errno;
568         goto finish;
569     }
571 finish:
572     if (f)
573         file_cache_unref(f);
574     
575     return r;
579 static struct fuse_operations dav_oper = {
580     .getattr    = dav_getattr,
581     .getdir     = dav_getdir,
582     .mknod      = dav_mknod,
583     .mkdir      = dav_mkdir,
584     .unlink     = dav_unlink,
585     .rmdir      = dav_rmdir,
586     .rename     = dav_rename,
587 /*    .chmod    = dav_chmod,*/
588     .truncate   = dav_truncate,
589 /*    .utime    = dav_utime,*/
590     .open       = dav_open,
591     .read       = dav_read,
592     .write      = dav_write,
593     .release    = dav_release,
594     .fsync      = dav_fsync
595 };
597 static void usage(char *argv0) {
598     char *e;
600     if ((e = strrchr(argv0, '/')))
601         e++;
602     else
603         e = argv0;
604     
605     fprintf(stderr,
606             "%s [-h] [-D] [-u USERNAME] [-p PASSWORD] URL MOUNTPOINT\n"
607             "\t-h Show this help\n"
608             "\t-D Enable debug mode\n"
609             "\t-u Username if required\n"
610             "\t-p Password if required\n",
611             e);
614 static void exit_handler(int s) {
615     static const char m[] = "Signal caught\n";
616     write(2, m, strlen(m));
617     if(fuse != NULL)
618         fuse_exit(fuse);
621 static int setup_signal_handlers(void) {
622     struct sigaction sa;
623                                                                                                         
624     sa.sa_handler = exit_handler;
625     sigemptyset(&(sa.sa_mask));
626     sa.sa_flags = 0;
627     
628     if (sigaction(SIGHUP, &sa, NULL) == -1 ||
629         sigaction(SIGINT, &sa, NULL) == -1 ||
630         sigaction(SIGTERM, &sa, NULL) == -1) {
631                                                                                                         
632         fprintf(stderr, "Cannot set exit signal handlers: %s\n", strerror(errno));
633         return -1;
634     }
635                                                                                                         
636     sa.sa_handler = SIG_IGN;
637                                                                                                         
638     if (sigaction(SIGPIPE, &sa, NULL) == -1) {
639         fprintf(stderr, "Cannot set ignored signals: %s\n", strerror(errno));
640         return -1;
641     }
643     return 0;
646 int main(int argc, char *argv[]) {
647     int c;
648     char *u=NULL, *p = NULL;
649     int fuse_fd = -1;
650     int ret = 1;
652     if (ne_sock_init()) {
653         fprintf(stderr, "Failed to initialize libneon.\n");
654         goto finish;
655     }
657     openssl_thread_setup();
659     mask = umask(0);
660     umask(mask);
662     cache_alloc();
664     if (setup_signal_handlers() < 0)
665         goto finish;
666     
667     while ((c = getopt(argc, argv, "hu:p:D")) != -1) {
669         switch(c) {
670             case 'u':
671                 u = optarg;
672                 break;
673                 
674             case 'p':
675                 p = optarg;
676                 break;
677                 
678             case 'D':
679                 debug = !debug;
680                 break;
681                     
682             case 'h':
683             default:
684                 usage(argv[0]);
685                 goto finish;
686         }
687     }
689     if (optind != argc-2) {
690         usage(argv[0]);
691         goto finish;
692     }
694     if (session_set_uri(argv[optind], u, p) < 0) {
695         usage(argv[0]);
696         goto finish;
697     }
699     if ((fuse_fd = fuse_mount(argv[optind+1], NULL)) < 0) {
700         fprintf(stderr, "Failed to mount FUSE file system.\n");
701         goto finish;
702     }
704     if (!(fuse = fuse_new(fuse_fd, 0, &dav_oper))) {
705         fprintf(stderr, "Failed to create FUSE object.\n");
706         goto finish;
707     }
708     
709     fuse_loop_mt(fuse);
710     
711     ret = 0;
712     
713 finish:
715     if (fuse)
716         fuse_destroy(fuse);
717     
718     if (fuse_fd >= 0)
719         fuse_unmount(argv[optind]);
720     
721     file_cache_close_all();
722     cache_free();
723     session_free();
724     openssl_thread_cleanup();
725     
726     return ret;