92fceb1487d96bf8ac23865ddaa4263d4459db82
1 /* $Id$ */
3 /***
4 This file is part of fusedav.
6 fusedav is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 fusedav is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
14 License for more details.
16 You should have received a copy of the GNU General Public License
17 along with fusedav; if not, write to the Free Software Foundation,
18 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 ***/
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
25 #include <signal.h>
26 #include <pthread.h>
27 #include <time.h>
28 #include <assert.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <dirent.h>
35 #include <errno.h>
36 #include <sys/statfs.h>
37 #include <getopt.h>
39 #include <ne_request.h>
40 #include <ne_basic.h>
41 #include <ne_props.h>
42 #include <ne_utils.h>
43 #include <ne_socket.h>
44 #include <ne_auth.h>
45 #include <ne_dates.h>
47 #include <fuse.h>
49 #include "statcache.h"
50 #include "filecache.h"
51 #include "session.h"
52 #include "openssl-thread.h"
54 const ne_propname query_properties[] = {
55 { "DAV:", "resourcetype" },
56 { "http://apache.org/dav/props/", "executable" },
57 { "DAV:", "getcontentlength" },
58 { "DAV:", "getlastmodified" },
59 { "DAV:", "creationdate" },
60 { NULL, NULL }
61 };
63 mode_t mask = 0;
64 int debug = 0;
65 struct fuse* fuse = NULL;
67 struct fill_info {
68 fuse_dirh_t h;
69 fuse_dirfil_t filler;
70 const char *root;
71 };
73 static int get_stat(const char *path, struct stat *stbuf);
75 static pthread_once_t path_cvt_once = PTHREAD_ONCE_INIT;
76 static pthread_key_t path_cvt_tsd_key;
78 static void path_cvt_tsd_key_init(void) {
79 pthread_key_create(&path_cvt_tsd_key, free);
80 }
82 static const char *path_cvt(const char *path) {
83 char *r, *t;
84 int l;
86 pthread_once(&path_cvt_once, path_cvt_tsd_key_init);
88 if ((r = pthread_getspecific(path_cvt_tsd_key)))
89 free(r);
91 t = malloc((l = strlen(base_directory)+strlen(path))+1);
92 assert(t);
93 sprintf(t, "%s%s", base_directory, path);
95 if (l > 1 && t[l-1] == '/')
96 t[l-1] = 0;
98 r = ne_path_escape(t);
99 free(t);
101 pthread_setspecific(path_cvt_tsd_key, r);
103 return r;
104 }
106 static void fill_stat(struct stat* st, const ne_prop_result_set *results, int is_dir) {
107 const char *rt, *e, *gcl, *glm, *cd;
108 const ne_propname resourcetype = { "DAV:", "resourcetype" };
109 const ne_propname executable = { "http://apache.org/dav/props/", "executable" };
110 const ne_propname getcontentlength = { "DAV:", "getcontentlength" };
111 const ne_propname getlastmodified = { "DAV:", "getlastmodified" };
112 const ne_propname creationdate = { "DAV:", "creationdate" };
114 assert(st && results);
116 rt = ne_propset_value(results, &resourcetype);
117 e = ne_propset_value(results, &executable);
118 gcl = ne_propset_value(results, &getcontentlength);
119 glm = ne_propset_value(results, &getlastmodified);
120 cd = ne_propset_value(results, &creationdate);
122 memset(st, 0, sizeof(struct stat));
124 if (is_dir) {
125 st->st_mode = S_IFDIR | 0777;
126 st->st_nlink = 3; /* find will ignore this directory if nlin <= and st_size == 0 */
127 st->st_size = 4096;
128 } else {
129 st->st_mode = S_IFREG | (e && (*e == 'T' || *e == 't') ? 0777 : 0666);
130 st->st_nlink = 1;
131 st->st_size = gcl ? atoll(gcl) : 0;
132 }
134 st->st_atime = time(NULL);
135 st->st_mtime = glm ? ne_rfc1123_parse(glm) : 0;
136 st->st_ctime = cd ? ne_iso8601_parse(cd) : 0;
139 st->st_blocks = (st->st_size+511)/512;
140 /*fprintf(stderr, "a: %u; m: %u; c: %u\n", st->st_atime, st->st_mtime, st->st_ctime);*/
142 st->st_mode &= ~mask;
144 st->st_uid = getuid();
145 st->st_gid = getgid();
146 }
148 static char *strip_trailing_slash(char *fn, int *is_dir) {
149 size_t l = strlen(fn);
150 assert(fn && is_dir);
152 if ((*is_dir = (fn[l-1] == '/')))
153 fn[l-1] = 0;
155 return fn;
156 }
158 static void getdir_propfind_callback(void *userdata, const char *href, const ne_prop_result_set *results) {
159 struct fill_info *f = userdata;
160 struct stat st;
161 char fn[PATH_MAX], *t;
162 int is_dir = 0;
164 assert(f);
166 strncpy(fn, href, sizeof(fn));
167 fn[sizeof(fn)-1] = 0;
168 strip_trailing_slash(fn, &is_dir);
170 if (strcmp(fn, f->root) && fn[0]) {
171 char *h;
173 if ((t = strrchr(fn, '/')))
174 t++;
175 else
176 t = fn;
178 dir_cache_add(f->root, t, is_dir);
179 f->filler(f->h, h = ne_path_unescape(t), is_dir ? DT_DIR : DT_REG);
180 free(h);
181 }
183 fill_stat(&st, results, is_dir);
184 stat_cache_set(fn, &st);
185 }
187 static void getdir_cache_callback(const char *root, const char *fn, int is_dir, void *user) {
188 struct fill_info *f = user;
189 assert(f);
190 char path[PATH_MAX];
191 struct stat st;
192 char *h;
194 snprintf(path, sizeof(path), "%s/%s", !strcmp(root, "/") ? "" : root, fn);
196 if (get_stat(path, &st) < 0)
197 return;
199 f->filler(f->h, h = ne_path_unescape(fn), is_dir ? DT_DIR : DT_REG);
200 free(h);
201 }
203 static int dav_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler) {
204 struct fill_info f;
205 ne_session *session;
207 path = path_cvt(path);
209 if (debug)
210 fprintf(stderr, "getdir(%s)\n", path);
212 f.h = h;
213 f.filler = filler;
214 f.root = path;
216 if (dir_cache_enumerate(path, getdir_cache_callback, &f) < 0) {
218 if (debug)
219 fprintf(stderr, "DIR-CACHE-MISS\n");
221 if (!(session = session_get()))
222 return -EIO;
224 dir_cache_begin(path);
226 if (ne_simple_propfind(session, path, NE_DEPTH_ONE, query_properties, getdir_propfind_callback, &f) != NE_OK) {
227 dir_cache_finish(path, 2);
228 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
229 return -ENOENT;
230 }
232 dir_cache_finish(path, 1);
233 }
235 filler(h, ".", DT_DIR);
236 filler(h, "..", DT_DIR);
238 return 0;
239 }
241 static void getattr_propfind_callback(void *userdata, const char *href, const ne_prop_result_set *results) {
242 struct stat *st = (struct stat*) userdata;
243 char fn[PATH_MAX];
244 int is_dir;
246 assert(st);
248 strncpy(fn, href, sizeof(fn));
249 fn[sizeof(fn)-1] = 0;
250 strip_trailing_slash(fn, &is_dir);
252 fill_stat(st, results, is_dir);
253 stat_cache_set(fn, st);
254 }
256 static int get_stat(const char *path, struct stat *stbuf) {
257 ne_session *session;
259 if (!(session = session_get()))
260 return -EIO;
262 if (stat_cache_get(path, stbuf) == 0) {
263 return stbuf->st_mode == 0 ? -ENOENT : 0;
264 } else {
265 if (debug)
266 fprintf(stderr, "STAT-CACHE-MISS\n");
268 if (ne_simple_propfind(session, path, NE_DEPTH_ZERO, query_properties, getattr_propfind_callback, stbuf) != NE_OK) {
269 stat_cache_invalidate(path);
270 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
271 return -ENOENT;
272 }
274 return 0;
275 }
276 }
278 static int dav_getattr(const char *path, struct stat *stbuf) {
279 path = path_cvt(path);
280 if (debug)
281 fprintf(stderr, "getattr(%s)\n", path);
282 return get_stat(path, stbuf);
283 }
285 static int dav_unlink(const char *path) {
286 int r;
287 struct stat st;
288 ne_session *session;
290 path = path_cvt(path);
292 if (debug)
293 fprintf(stderr, "unlink(%s)\n", path);
295 if (!(session = session_get()))
296 return -EIO;
298 if ((r = get_stat(path, &st)) < 0)
299 return r;
301 if (!S_ISREG(st.st_mode))
302 return -EISDIR;
304 if (ne_delete(session, path)) {
305 fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
306 return -ENOENT;
307 }
309 stat_cache_invalidate(path);
310 dir_cache_invalidate_parent(path);
312 return 0;
313 }
315 static int dav_rmdir(const char *path) {
316 int r;
317 struct stat st;
318 ne_session *session;
320 path = path_cvt(path);
322 if (debug)
323 fprintf(stderr, "rmdir(%s)\n", path);
325 if (!(session = session_get()))
326 return -EIO;
328 if ((r = get_stat(path, &st)) < 0)
329 return r;
331 if (!S_ISDIR(st.st_mode))
332 return -ENOTDIR;
334 if (ne_delete(session, path)) {
335 fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
336 return -ENOENT;
337 }
339 stat_cache_invalidate(path);
340 dir_cache_invalidate_parent(path);
342 return 0;
343 }
345 static int dav_mkdir(const char *path, mode_t mode) {
346 char fn[PATH_MAX];
347 ne_session *session;
349 path = path_cvt(path);
351 if (debug)
352 fprintf(stderr, "mkdir(%s)\n", path);
354 if (!(session = session_get()))
355 return -EIO;
357 snprintf(fn, sizeof(fn), "%s/", path);
359 if (ne_mkcol(session, fn)) {
360 fprintf(stderr, "MKCOL failed: %s\n", ne_get_error(session));
361 return -ENOENT;
362 }
364 stat_cache_invalidate(path);
365 dir_cache_invalidate_parent(path);
367 return 0;
368 }
370 static int dav_rename(const char *from, const char *to) {
371 ne_session *session;
372 int r = 0;
374 from = strdup(path_cvt(from));
375 to = path_cvt(to);
377 if (debug)
378 fprintf(stderr, "rename(%s, %s)\n", from, to);
380 if (!(session = session_get())) {
381 r = -EIO;
382 goto finish;
383 }
385 if (ne_move(session, 1, from, to)) {
386 fprintf(stderr, "MOVE failed: %s\n", ne_get_error(session));
387 r = -ENOENT;
388 goto finish;
389 }
391 stat_cache_invalidate(from);
392 stat_cache_invalidate(to);
394 dir_cache_invalidate_parent(from);
395 dir_cache_invalidate_parent(to);
397 finish:
399 free((char*) from);
401 return r;
402 }
404 static int dav_release(const char *path, int flags) {
405 void *f = NULL;
406 int r = 0;
407 ne_session *session;
409 path = path_cvt(path);
411 if (debug)
412 fprintf(stderr, "release(%s)\n", path);
414 if (!(session = session_get())) {
415 r = -EIO;
416 goto finish;
417 }
419 if (!(f = file_cache_get(path))) {
420 fprintf(stderr, "release() called for closed file\n");
421 r = -EFAULT;
422 goto finish;
423 }
425 if (file_cache_close(f) < 0) {
426 r = -errno;
427 goto finish;
428 }
430 finish:
431 if (f)
432 file_cache_unref(f);
434 return r;
435 }
437 static int dav_fsync(const char *path, int isdatasync) {
438 void *f = NULL;
439 int r = 0;
440 ne_session *session;
442 path = path_cvt(path);
443 if (debug)
444 fprintf(stderr, "fsync(%s)\n", path);
446 if (!(session = session_get())) {
447 r = -EIO;
448 goto finish;
449 }
451 if (!(f = file_cache_get(path))) {
452 fprintf(stderr, "fsync() called for closed file\n");
453 r = -EFAULT;
454 goto finish;
455 }
457 if (file_cache_sync(f) < 0) {
458 r = -errno;
459 goto finish;
460 }
462 finish:
464 if (f)
465 file_cache_unref(f);
467 return r;
468 }
470 static int dav_mknod(const char *path, mode_t mode, dev_t rdev) {
471 char tempfile[PATH_MAX];
472 int fd;
473 ne_session *session;
475 path = path_cvt(path);
476 if (debug)
477 fprintf(stderr, "mknod(%s)\n", path);
479 if (!(session = session_get()))
480 return -EIO;
482 if (!S_ISREG(mode))
483 return -ENOTSUP;
485 snprintf(tempfile, sizeof(tempfile), "%s/fusedav-empty-XXXXXX", "/tmp");
486 if ((fd = mkstemp(tempfile)) < 0)
487 return -errno;
489 unlink(tempfile);
491 if (ne_put(session, path, fd)) {
492 fprintf(stderr, "mknod:PUT failed: %s\n", ne_get_error(session));
493 close(fd);
494 return -EACCES;
495 }
497 close(fd);
499 stat_cache_invalidate(path);
500 dir_cache_invalidate_parent(path);
502 return 0;
503 }
505 static int dav_open(const char *path, int flags) {
506 void *f;
508 if (debug)
509 fprintf(stderr, "open(%s)\n", path);
511 path = path_cvt(path);
512 if (!(f = file_cache_open(path, flags)))
513 return -errno;
515 file_cache_unref(f);
517 return 0;
518 }
520 static int dav_read(const char *path, 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, "read(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
528 if (!(f = file_cache_get(path))) {
529 fprintf(stderr, "read() called for closed file\n");
530 r = -EFAULT;
531 goto finish;
532 }
534 if ((r = file_cache_read(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 }
546 static int dav_write(const char *path, const char *buf, size_t size, off_t offset) {
547 void *f = NULL;
548 ssize_t r;
550 path = path_cvt(path);
551 if (debug)
552 fprintf(stderr, "write(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
554 if (!(f = file_cache_get(path))) {
555 fprintf(stderr, "write() called for closed file\n");
556 r = -EFAULT;
557 goto finish;
558 }
560 if ((r = file_cache_write(f, buf, size, offset)) < 0) {
561 r = -errno;
562 goto finish;
563 }
565 finish:
566 if (f)
567 file_cache_unref(f);
569 return r;
570 }
573 static int dav_truncate(const char *path, off_t size) {
574 void *f = NULL;
575 int r = 0;
576 ne_session *session;
578 path = path_cvt(path);
579 if (debug)
580 fprintf(stderr, "truncate(%s, %lu)\n", path, (unsigned long) size);
582 if (!(session = session_get()))
583 r = -EIO;
584 goto finish;
586 if (!(f = file_cache_get(path))) {
587 fprintf(stderr, "truncate() called for closed file\n");
588 r = -EFAULT;
589 goto finish;
590 }
592 if (file_cache_truncate(f, size) < 0) {
593 r = -errno;
594 goto finish;
595 }
597 finish:
598 if (f)
599 file_cache_unref(f);
601 return r;
602 }
605 static struct fuse_operations dav_oper = {
606 .getattr = dav_getattr,
607 .getdir = dav_getdir,
608 .mknod = dav_mknod,
609 .mkdir = dav_mkdir,
610 .unlink = dav_unlink,
611 .rmdir = dav_rmdir,
612 .rename = dav_rename,
613 /* .chmod = dav_chmod,*/
614 .truncate = dav_truncate,
615 /* .utime = dav_utime,*/
616 .open = dav_open,
617 .read = dav_read,
618 .write = dav_write,
619 .release = dav_release,
620 .fsync = dav_fsync
621 };
623 static void usage(char *argv0) {
624 char *e;
626 if ((e = strrchr(argv0, '/')))
627 e++;
628 else
629 e = argv0;
631 fprintf(stderr,
632 "%s [-h] [-D] [-u USERNAME] [-p PASSWORD] URL MOUNTPOINT\n"
633 "\t-h Show this help\n"
634 "\t-D Enable debug mode\n"
635 "\t-u Username if required\n"
636 "\t-p Password if required\n",
637 e);
638 }
640 static void exit_handler(int s) {
641 static const char m[] = "*** Caught signal ***\n";
642 write(2, m, strlen(m));
643 if(fuse != NULL)
644 fuse_exit(fuse);
645 }
647 static int setup_signal_handlers(void) {
648 struct sigaction sa;
650 sa.sa_handler = exit_handler;
651 sigemptyset(&(sa.sa_mask));
652 sa.sa_flags = 0;
654 if (sigaction(SIGHUP, &sa, NULL) == -1 ||
655 sigaction(SIGINT, &sa, NULL) == -1 ||
656 sigaction(SIGTERM, &sa, NULL) == -1) {
658 fprintf(stderr, "Cannot set exit signal handlers: %s\n", strerror(errno));
659 return -1;
660 }
662 sa.sa_handler = SIG_IGN;
664 if (sigaction(SIGPIPE, &sa, NULL) == -1) {
665 fprintf(stderr, "Cannot set ignored signals: %s\n", strerror(errno));
666 return -1;
667 }
669 return 0;
670 }
672 int main(int argc, char *argv[]) {
673 int c;
674 char *u=NULL, *p = NULL;
675 int fuse_fd = -1;
676 int ret = 1;
677 char mountpoint[PATH_MAX];
678 static const char *mount_args[] = { "-n", NULL, "-l", "-c", NULL };
680 if (ne_sock_init()) {
681 fprintf(stderr, "Failed to initialize libneon.\n");
682 goto finish;
683 }
685 openssl_thread_setup();
687 mask = umask(0);
688 umask(mask);
690 cache_alloc();
692 if (setup_signal_handlers() < 0)
693 goto finish;
695 while ((c = getopt(argc, argv, "hu:p:D")) != -1) {
697 switch(c) {
698 case 'u':
699 u = optarg;
700 break;
702 case 'p':
703 p = optarg;
704 break;
706 case 'D':
707 debug = !debug;
708 break;
710 case 'h':
711 default:
712 usage(argv[0]);
713 goto finish;
714 }
715 }
717 if (optind != argc-2) {
718 usage(argv[0]);
719 goto finish;
720 }
722 if (session_set_uri(argv[optind], u, p) < 0) {
723 usage(argv[0]);
724 goto finish;
725 }
727 if (argv[optind+1][0] == '/')
728 snprintf(mountpoint, sizeof(mountpoint), "%s", argv[optind+1]);
729 else {
730 char *pwd = get_current_dir_name();
731 snprintf(mountpoint, sizeof(mountpoint), "%s/%s", pwd, argv[optind+1]);
732 free(pwd);
733 }
735 mount_args[1] = argv[optind];
737 if ((fuse_fd = fuse_mount(mountpoint, mount_args)) < 0) {
738 fprintf(stderr, "Failed to mount FUSE file system.\n");
739 goto finish;
740 }
742 if (!(fuse = fuse_new(fuse_fd, 0, &dav_oper))) {
743 fprintf(stderr, "Failed to create FUSE object.\n");
744 goto finish;
745 }
747 fuse_loop_mt(fuse);
749 ret = 0;
751 finish:
753 if (fuse)
754 fuse_destroy(fuse);
756 if (fuse_fd >= 0)
757 fuse_unmount(mountpoint);
759 file_cache_close_all();
760 cache_free();
761 session_free();
762 openssl_thread_cleanup();
764 return ret;
765 }