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);
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);
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" };
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));
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;
118 st->st_uid = getuid();
119 st->st_gid = getgid();
120 }
122 static char *strip_trailing_slash(char *fn, int *is_dir) {
123 size_t l = strlen(fn);
124 assert(fn && is_dir);
126 if ((*is_dir = (fn[l-1] == '/')))
127 fn[l-1] = 0;
129 return fn;
130 }
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;
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);
159 }
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);
170 if (get_stat(path, &st) < 0)
171 return;
173 f->filler(f->h, h = ne_path_unescape(fn), is_dir ? DT_DIR : DT_REG);
174 free(h);
175 }
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");
195 if (!(session = session_get()))
196 return -EIO;
198 dir_cache_begin(path);
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;
213 }
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);
226 fill_stat(st, results, is_dir);
227 stat_cache_set(fn, st);
228 }
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");
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 }
250 }
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);
257 }
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;
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);
286 return 0;
287 }
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;
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;
317 }
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);
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);
341 return 0;
342 }
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 }
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);
375 return r;
376 }
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 }
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);
408 return r;
409 }
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:
438 if (f)
439 file_cache_unref(f);
441 return r;
442 }
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;
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;
463 unlink(tempfile);
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;
477 }
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;
492 }
494 static int dav_read(const char *path, char *buf, size_t size, off_t offset) {
495 void *f = NULL;
496 ssize_t r;
498 path = path_cvt(path);
499 if (debug)
500 fprintf(stderr, "read(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
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);
517 return r;
518 }
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 }
539 finish:
540 if (f)
541 file_cache_unref(f);
543 return r;
544 }
547 static int dav_truncate(const char *path, off_t size) {
548 void *f = NULL;
549 int r = 0;
550 ne_session *session;
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;
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);
575 return r;
576 }
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;
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);
612 }
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);
619 }
621 static int setup_signal_handlers(void) {
622 struct sigaction sa;
624 sa.sa_handler = exit_handler;
625 sigemptyset(&(sa.sa_mask));
626 sa.sa_flags = 0;
628 if (sigaction(SIGHUP, &sa, NULL) == -1 ||
629 sigaction(SIGINT, &sa, NULL) == -1 ||
630 sigaction(SIGTERM, &sa, NULL) == -1) {
632 fprintf(stderr, "Cannot set exit signal handlers: %s\n", strerror(errno));
633 return -1;
634 }
636 sa.sa_handler = SIG_IGN;
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;
644 }
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;
667 while ((c = getopt(argc, argv, "hu:p:D")) != -1) {
669 switch(c) {
670 case 'u':
671 u = optarg;
672 break;
674 case 'p':
675 p = optarg;
676 break;
678 case 'D':
679 debug = !debug;
680 break;
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 }
709 fuse_loop_mt(fuse);
711 ret = 0;
713 finish:
715 if (fuse)
716 fuse_destroy(fuse);
718 if (fuse_fd >= 0)
719 fuse_unmount(argv[optind]);
721 file_cache_close_all();
722 cache_free();
723 session_free();
724 openssl_thread_cleanup();
726 return ret;
727 }