Code

some cleanups
[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;
115     st->st_blocks = (st->st_size+511)/512;
116     /*fprintf(stderr, "a: %u; m: %u; c: %u\n", st->st_atime, st->st_mtime, st->st_ctime);*/
118     st->st_mode &= ~mask;
119     
120     st->st_uid = getuid();
121     st->st_gid = getgid();
124 static char *strip_trailing_slash(char *fn, int *is_dir) {
125     size_t l = strlen(fn);
126     assert(fn && is_dir);
127     
128     if ((*is_dir = (fn[l-1] == '/')))
129         fn[l-1] = 0;
131     return fn;
134 static void getdir_propfind_callback(void *userdata, const char *href, const ne_prop_result_set *results) {
135     struct fill_info *f = userdata;
136     struct stat st;
137     char fn[PATH_MAX], *t;
138     int is_dir = 0;
140     assert(f);
142     strncpy(fn, href, sizeof(fn));
143     fn[sizeof(fn)-1] = 0;
144     strip_trailing_slash(fn, &is_dir);
146     if (strcmp(fn, f->root) && fn[0]) {
147         char *h;
148         
149         if ((t = strrchr(fn, '/')))
150             t++;
151         else
152             t = fn;
154         dir_cache_add(f->root, t, is_dir);
155         f->filler(f->h, h = ne_path_unescape(t), is_dir ? DT_DIR : DT_REG);
156         free(h);
157     }
159     fill_stat(&st, results, is_dir);
160     stat_cache_set(fn, &st);
163 static void getdir_cache_callback(const char *root, const char *fn, int is_dir, void *user) {
164     struct fill_info *f = user;
165     assert(f);
166     char path[PATH_MAX];
167     struct stat st;
168     char *h;
170     snprintf(path, sizeof(path), "%s/%s", !strcmp(root, "/") ? "" : root, fn);
171     
172     if (get_stat(path, &st) < 0)
173         return;
174     
175     f->filler(f->h, h = ne_path_unescape(fn), is_dir ? DT_DIR : DT_REG);
176     free(h);
179 static int dav_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) {
180     struct fill_info f;
181     ne_session *session;
183     path = path_cvt(path);
185     if (debug)
186         fprintf(stderr, "getdir(%s)\n", path);
188     f.h = h;
189     f.filler = filler;
190     f.root = path;
192     if (dir_cache_enumerate(path, getdir_cache_callback, &f) < 0) {
194         if (debug)
195             fprintf(stderr, "DIR-CACHE-MISS\n");
196         
197         if (!(session = session_get())) 
198             return -EIO;
200         dir_cache_begin(path);
201         
202         if (ne_simple_propfind(session, path, NE_DEPTH_ONE, query_properties, getdir_propfind_callback, &f) != NE_OK) {
203             dir_cache_finish(path, 2);
204             fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
205             return -ENOENT;
206         }
208         dir_cache_finish(path, 1);
209     }
211     filler(h, ".", DT_DIR);
212     filler(h, "..", DT_DIR);
214     return 0;
217 static void getattr_propfind_callback(void *userdata, const char *href, const ne_prop_result_set *results) {
218     struct stat *st = (struct stat*) userdata;
219     char fn[PATH_MAX];
220     int is_dir;
222     assert(st);
224     strncpy(fn, href, sizeof(fn));
225     fn[sizeof(fn)-1] = 0;
226     strip_trailing_slash(fn, &is_dir);
227     
228     fill_stat(st, results, is_dir);
229     stat_cache_set(fn, st);
232 static int get_stat(const char *path, struct stat *stbuf) {
233     ne_session *session;
235     if (!(session = session_get())) 
236         return -EIO;
238     if (stat_cache_get(path, stbuf) == 0) {
239         return stbuf->st_mode == 0 ? -ENOENT : 0;
240     } else {
241         if (debug)
242             fprintf(stderr, "STAT-CACHE-MISS\n");
243         
244         if (ne_simple_propfind(session, path, NE_DEPTH_ZERO, query_properties, getattr_propfind_callback, stbuf) != NE_OK) {
245             stat_cache_invalidate(path);
246             fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
247             return -ENOENT;
248         }
250         return 0;
251     }
254 static int dav_getattr(const char *path, struct stat *stbuf) {
255     path = path_cvt(path);
256     if (debug)
257         fprintf(stderr, "getattr(%s)\n", path);
258     return get_stat(path, stbuf);
261 static int dav_unlink(const char *path) {
262     int r;
263     struct stat st;
264     ne_session *session;
266     path = path_cvt(path);
268     if (debug)
269         fprintf(stderr, "unlink(%s)\n", path);
271     if (!(session = session_get())) 
272         return -EIO;
274     if ((r = get_stat(path, &st)) < 0)
275         return r;
277     if (!S_ISREG(st.st_mode))
278         return -EISDIR;
279     
280     if (ne_delete(session, path)) {
281         fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
282         return -ENOENT;
283     }
285     stat_cache_invalidate(path);
286     dir_cache_invalidate_parent(path);
287     
288     return 0;
291 static int dav_rmdir(const char *path) {
292     int r;
293     struct stat st;
294     ne_session *session;
296     path = path_cvt(path);
298     if (debug)
299         fprintf(stderr, "rmdir(%s)\n", path);
301     if (!(session = session_get())) 
302         return -EIO;
304     if ((r = get_stat(path, &st)) < 0)
305         return r;
307     if (!S_ISDIR(st.st_mode))
308         return -ENOTDIR;
309     
310     if (ne_delete(session, path)) {
311         fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
312         return -ENOENT;
313     }
315     stat_cache_invalidate(path);
316     dir_cache_invalidate_parent(path);
318     return 0;
321 static int dav_mkdir(const char *path, mode_t mode) {
322     char fn[PATH_MAX];
323     ne_session *session;
325     path = path_cvt(path);
327     if (debug)
328         fprintf(stderr, "mkdir(%s)\n", path);
330     if (!(session = session_get())) 
331         return -EIO;
333     snprintf(fn, sizeof(fn), "%s/", path);
334     
335     if (ne_mkcol(session, fn)) {
336         fprintf(stderr, "MKCOL failed: %s\n", ne_get_error(session));
337         return -ENOENT;
338     }
340     stat_cache_invalidate(path);
341     dir_cache_invalidate_parent(path);
342     
343     return 0;
346 static int dav_rename(const char *from, const char *to) {
347     ne_session *session;
348     int r = 0;
350     from = strdup(path_cvt(from));
351     to = path_cvt(to);
353     if (debug)
354         fprintf(stderr, "rename(%s, %s)\n", from, to);
356     if (!(session = session_get())) {
357         r = -EIO;
358         goto finish;
359     }
361     if (ne_move(session, 1, from, to)) {
362         fprintf(stderr, "MOVE failed: %s\n", ne_get_error(session));
363         r = -ENOENT;
364         goto finish;
365     }
366     
367     stat_cache_invalidate(from);
368     stat_cache_invalidate(to);
370     dir_cache_invalidate_parent(from);
371     dir_cache_invalidate_parent(to);
373 finish:
375     free((char*) from);
376     
377     return r;
380 static int dav_release(const char *path, int flags) {
381     void *f = NULL;
382     int r = 0;
383     ne_session *session;
385     path = path_cvt(path);
387     if (debug)
388         fprintf(stderr, "release(%s)\n", path);
390     if (!(session = session_get())) {
391         r = -EIO;
392         goto finish;
393     }
394     
395     if (!(f = file_cache_get(path))) {
396         fprintf(stderr, "release() called for closed file\n");
397         r = -EFAULT;
398         goto finish;
399     }
401     if (file_cache_close(f) < 0) {
402         r = -errno;
403         goto finish;
404     }
406 finish:
407     if (f)
408         file_cache_unref(f);
409     
410     return r;
413 static int dav_fsync(const char *path, int isdatasync) {
414     void *f = NULL;
415     int r = 0;
416     ne_session *session;
418     path = path_cvt(path);
419     if (debug)
420         fprintf(stderr, "fsync(%s)\n", path);
422     if (!(session = session_get())) {
423         r = -EIO;
424         goto finish;
425     }
427     if (!(f = file_cache_get(path))) {
428         fprintf(stderr, "fsync() called for closed file\n");
429         r = -EFAULT;
430         goto finish;
431     }
433     if (file_cache_sync(f) < 0) {
434         r = -errno;
435         goto finish;
436     }
438 finish:
439     
440     if (f)
441         file_cache_unref(f);
443     return r;
446 static int dav_mknod(const char *path, mode_t mode, dev_t rdev) {
447     char tempfile[PATH_MAX];
448     int fd;
449     ne_session *session;
450     
451     path = path_cvt(path);
452     if (debug)
453         fprintf(stderr, "mknod(%s)\n", path);
455     if (!(session = session_get())) 
456         return -EIO;
458     if (!S_ISREG(mode))
459         return -ENOTSUP;
461     snprintf(tempfile, sizeof(tempfile), "%s/fusedav-empty-XXXXXX", "/tmp");
462     if ((fd = mkstemp(tempfile)) < 0)
463         return -errno;
464     
465     unlink(tempfile);
466     
467     if (ne_put(session, path, fd)) {
468         fprintf(stderr, "mknod:PUT failed: %s\n", ne_get_error(session));
469         close(fd);
470         return -EACCES;
471     }
473     close(fd);
475     stat_cache_invalidate(path);
476     dir_cache_invalidate_parent(path);
478     return 0;
481 static int dav_open(const char *path, int flags) {
482     void *f;
484     if (debug)
485         fprintf(stderr, "open(%s)\n", path);
487     path = path_cvt(path);
488     if (!(f = file_cache_open(path, flags)))
489         return -errno;
491     file_cache_unref(f);
493     return 0;
496 static int dav_read(const char *path, char *buf, size_t size, off_t offset) {
497     void *f = NULL;
498     ssize_t r;
499  
500     path = path_cvt(path);
501     if (debug)
502         fprintf(stderr, "read(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
503     
504     if (!(f = file_cache_get(path))) {
505         fprintf(stderr, "read() called for closed file\n");
506         r = -EFAULT;
507         goto finish;
508     }
510     if ((r = file_cache_read(f, buf, size, offset)) < 0) {
511         r = -errno;
512         goto finish;
513     }
515 finish:
516     if (f)
517         file_cache_unref(f);
518     
519     return r;
522 static int dav_write(const char *path, const char *buf, size_t size, off_t offset) {
523     void *f = NULL;
524     ssize_t r;
526     path = path_cvt(path);
527     if (debug)
528         fprintf(stderr, "write(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
530     if (!(f = file_cache_get(path))) {
531         fprintf(stderr, "write() called for closed file\n");
532         r = -EFAULT;
533         goto finish;
534     }
536     if ((r = file_cache_write(f, buf, size, offset)) < 0) {
537         r = -errno;
538         goto finish;
539     }
540     
541 finish:
542     if (f)
543         file_cache_unref(f);
544     
545     return r;
549 static int dav_truncate(const char *path, off_t size) {
550     void *f = NULL;
551     int r = 0;
552     ne_session *session;
553     
554     path = path_cvt(path);
555     if (debug)
556         fprintf(stderr, "truncate(%s, %lu)\n", path, (unsigned long) size);
558     if (!(session = session_get()))
559         r = -EIO;
560         goto finish;
561     
562     if (!(f = file_cache_get(path))) {
563         fprintf(stderr, "truncate() called for closed file\n");
564         r = -EFAULT;
565         goto finish;
566     }
568     if (file_cache_truncate(f, size) < 0) {
569         r = -errno;
570         goto finish;
571     }
573 finish:
574     if (f)
575         file_cache_unref(f);
576     
577     return r;
581 static struct fuse_operations dav_oper = {
582     .getattr    = dav_getattr,
583     .getdir     = dav_getdir,
584     .mknod      = dav_mknod,
585     .mkdir      = dav_mkdir,
586     .unlink     = dav_unlink,
587     .rmdir      = dav_rmdir,
588     .rename     = dav_rename,
589 /*    .chmod    = dav_chmod,*/
590     .truncate   = dav_truncate,
591 /*    .utime    = dav_utime,*/
592     .open       = dav_open,
593     .read       = dav_read,
594     .write      = dav_write,
595     .release    = dav_release,
596     .fsync      = dav_fsync
597 };
599 static void usage(char *argv0) {
600     char *e;
602     if ((e = strrchr(argv0, '/')))
603         e++;
604     else
605         e = argv0;
606     
607     fprintf(stderr,
608             "%s [-h] [-D] [-u USERNAME] [-p PASSWORD] URL MOUNTPOINT\n"
609             "\t-h Show this help\n"
610             "\t-D Enable debug mode\n"
611             "\t-u Username if required\n"
612             "\t-p Password if required\n",
613             e);
616 static void exit_handler(int s) {
617     static const char m[] = "*** Caught signal ***\n";
618     write(2, m, strlen(m));
619     if(fuse != NULL)
620         fuse_exit(fuse);
623 static int setup_signal_handlers(void) {
624     struct sigaction sa;
625                                                                                                         
626     sa.sa_handler = exit_handler;
627     sigemptyset(&(sa.sa_mask));
628     sa.sa_flags = 0;
629     
630     if (sigaction(SIGHUP, &sa, NULL) == -1 ||
631         sigaction(SIGINT, &sa, NULL) == -1 ||
632         sigaction(SIGTERM, &sa, NULL) == -1) {
633                                                                                                         
634         fprintf(stderr, "Cannot set exit signal handlers: %s\n", strerror(errno));
635         return -1;
636     }
637                                                                                                         
638     sa.sa_handler = SIG_IGN;
639                                                                                                         
640     if (sigaction(SIGPIPE, &sa, NULL) == -1) {
641         fprintf(stderr, "Cannot set ignored signals: %s\n", strerror(errno));
642         return -1;
643     }
645     return 0;
648 int main(int argc, char *argv[]) {
649     int c;
650     char *u=NULL, *p = NULL;
651     int fuse_fd = -1;
652     int ret = 1;
653     char mountpoint[PATH_MAX];
654     static const char *mount_args[] = { "-n",  NULL, "-l", "-c", NULL };
656     if (ne_sock_init()) {
657         fprintf(stderr, "Failed to initialize libneon.\n");
658         goto finish;
659     }
661     openssl_thread_setup();
663     mask = umask(0);
664     umask(mask);
666     cache_alloc();
668     if (setup_signal_handlers() < 0)
669         goto finish;
670     
671     while ((c = getopt(argc, argv, "hu:p:D")) != -1) {
673         switch(c) {
674             case 'u':
675                 u = optarg;
676                 break;
677                 
678             case 'p':
679                 p = optarg;
680                 break;
681                 
682             case 'D':
683                 debug = !debug;
684                 break;
685                     
686             case 'h':
687             default:
688                 usage(argv[0]);
689                 goto finish;
690         }
691     }
693     if (optind != argc-2) {
694         usage(argv[0]);
695         goto finish;
696     }
698     if (session_set_uri(argv[optind], u, p) < 0) {
699         usage(argv[0]);
700         goto finish;
701     }
703     if (argv[optind+1][0] == '/')
704         snprintf(mountpoint, sizeof(mountpoint), "%s", argv[optind+1]);
705     else {
706         char *pwd = get_current_dir_name();
707         snprintf(mountpoint, sizeof(mountpoint), "%s/%s", pwd, argv[optind+1]);
708         free(pwd);
709     }
711     mount_args[1] = argv[optind];
712     
713     if ((fuse_fd = fuse_mount(mountpoint, mount_args)) < 0) {
714         fprintf(stderr, "Failed to mount FUSE file system.\n");
715         goto finish;
716     }
718     if (!(fuse = fuse_new(fuse_fd, 0, &dav_oper))) {
719         fprintf(stderr, "Failed to create FUSE object.\n");
720         goto finish;
721     }
722     
723     fuse_loop_mt(fuse);
724     
725     ret = 0;
726     
727 finish:
729     if (fuse)
730         fuse_destroy(fuse);
731     
732     if (fuse_fd >= 0)
733         fuse_unmount(mountpoint);
734     
735     file_cache_close_all();
736     cache_free();
737     session_free();
738     openssl_thread_cleanup();
739     
740     return ret;