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;
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;
120 st->st_uid = getuid();
121 st->st_gid = getgid();
122 }
124 static char *strip_trailing_slash(char *fn, int *is_dir) {
125 size_t l = strlen(fn);
126 assert(fn && is_dir);
128 if ((*is_dir = (fn[l-1] == '/')))
129 fn[l-1] = 0;
131 return fn;
132 }
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;
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);
161 }
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);
172 if (get_stat(path, &st) < 0)
173 return;
175 f->filler(f->h, h = ne_path_unescape(fn), is_dir ? DT_DIR : DT_REG);
176 free(h);
177 }
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");
197 if (!(session = session_get()))
198 return -EIO;
200 dir_cache_begin(path);
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;
215 }
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);
228 fill_stat(st, results, is_dir);
229 stat_cache_set(fn, st);
230 }
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");
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 }
252 }
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);
259 }
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;
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);
288 return 0;
289 }
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;
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;
319 }
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);
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);
343 return 0;
344 }
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 }
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);
377 return r;
378 }
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 }
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);
410 return r;
411 }
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:
440 if (f)
441 file_cache_unref(f);
443 return r;
444 }
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;
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;
465 unlink(tempfile);
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;
479 }
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;
494 }
496 static int dav_read(const char *path, char *buf, size_t size, off_t offset) {
497 void *f = NULL;
498 ssize_t r;
500 path = path_cvt(path);
501 if (debug)
502 fprintf(stderr, "read(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
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);
519 return r;
520 }
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 }
541 finish:
542 if (f)
543 file_cache_unref(f);
545 return r;
546 }
549 static int dav_truncate(const char *path, off_t size) {
550 void *f = NULL;
551 int r = 0;
552 ne_session *session;
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;
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);
577 return r;
578 }
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;
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);
614 }
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);
621 }
623 static int setup_signal_handlers(void) {
624 struct sigaction sa;
626 sa.sa_handler = exit_handler;
627 sigemptyset(&(sa.sa_mask));
628 sa.sa_flags = 0;
630 if (sigaction(SIGHUP, &sa, NULL) == -1 ||
631 sigaction(SIGINT, &sa, NULL) == -1 ||
632 sigaction(SIGTERM, &sa, NULL) == -1) {
634 fprintf(stderr, "Cannot set exit signal handlers: %s\n", strerror(errno));
635 return -1;
636 }
638 sa.sa_handler = SIG_IGN;
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;
646 }
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;
671 while ((c = getopt(argc, argv, "hu:p:D")) != -1) {
673 switch(c) {
674 case 'u':
675 u = optarg;
676 break;
678 case 'p':
679 p = optarg;
680 break;
682 case 'D':
683 debug = !debug;
684 break;
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];
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 }
723 fuse_loop_mt(fuse);
725 ret = 0;
727 finish:
729 if (fuse)
730 fuse_destroy(fuse);
732 if (fuse_fd >= 0)
733 fuse_unmount(mountpoint);
735 file_cache_close_all();
736 cache_free();
737 session_free();
738 openssl_thread_cleanup();
740 return ret;
741 }