1 /***
2 Copyright (c) 2004-2006 Lennart Poettering
4 Permission is hereby granted, free of charge, to any person
5 obtaining a copy of this software and associated documentation files
6 (the "Software"), to deal in the Software without restriction,
7 including without limitation the rights to use, copy, modify, merge,
8 publish, distribute, sublicense, and/or sell copies of the Software,
9 and to permit persons to whom the Software is furnished to do so,
10 subject to the following conditions:
12 The above copyright notice and this permission notice shall be
13 included in all copies or substantial portions of the Software.
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 ***/
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include <signal.h>
30 #include <pthread.h>
31 #include <time.h>
32 #include <assert.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <dirent.h>
39 #include <errno.h>
40 #include <sys/statfs.h>
41 #include <getopt.h>
42 #include <attr/xattr.h>
44 #include <ne_request.h>
45 #include <ne_basic.h>
46 #include <ne_props.h>
47 #include <ne_utils.h>
48 #include <ne_socket.h>
49 #include <ne_auth.h>
50 #include <ne_dates.h>
51 #include <ne_redirect.h>
52 #include <ne_uri.h>
54 #include <fuse.h>
56 #include "statcache.h"
57 #include "filecache.h"
58 #include "session.h"
59 #include "fusedav.h"
61 const ne_propname query_properties[] = {
62 { "DAV:", "resourcetype" },
63 { "http://apache.org/dav/props/", "executable" },
64 { "DAV:", "getcontentlength" },
65 { "DAV:", "getlastmodified" },
66 { "DAV:", "creationdate" },
67 { NULL, NULL }
68 };
70 mode_t mask = 0;
71 int debug = 0;
72 struct fuse* fuse = NULL;
73 ne_lock_store *lock_store = NULL;
74 struct ne_lock *lock = NULL;
75 int lock_thread_exit = 0;
76 int lock_timeout = 60;
78 #define MIME_XATTR "user.mime_type"
80 #define MAX_REDIRECTS 10
82 struct fill_info {
83 void *buf;
84 fuse_fill_dir_t filler;
85 const char *root;
86 };
88 static int get_stat(const char *path, struct stat *stbuf);
90 static pthread_once_t path_cvt_once = PTHREAD_ONCE_INIT;
91 static pthread_key_t path_cvt_tsd_key;
93 static void path_cvt_tsd_key_init(void) {
94 pthread_key_create(&path_cvt_tsd_key, free);
95 }
97 static const char *path_cvt(const char *path) {
98 char *r, *t;
99 int l;
101 pthread_once(&path_cvt_once, path_cvt_tsd_key_init);
103 if ((r = pthread_getspecific(path_cvt_tsd_key)))
104 free(r);
106 t = malloc((l = strlen(base_directory)+strlen(path))+1);
107 assert(t);
108 sprintf(t, "%s%s", base_directory, path);
110 if (l > 1 && t[l-1] == '/')
111 t[l-1] = 0;
113 r = ne_path_escape(t);
114 free(t);
116 pthread_setspecific(path_cvt_tsd_key, r);
118 return r;
119 }
121 static int simple_propfind_with_redirect(
122 ne_session *session,
123 const char *path,
124 int depth,
125 const ne_propname *props,
126 ne_props_result results,
127 void *userdata) {
129 int i, ret;
131 for (i = 0; i < MAX_REDIRECTS; i++) {
132 const ne_uri *u;
134 if ((ret = ne_simple_propfind(session, path, depth, props, results, userdata)) != NE_REDIRECT)
135 return ret;
137 if (!(u = ne_redirect_location(session)))
138 break;
140 if (!session_is_local(u))
141 break;
143 if (debug)
144 fprintf(stderr, "REDIRECT FROM '%s' to '%s'\n", path, u->path);
146 path = u->path;
147 }
149 return ret;
150 }
152 static int proppatch_with_redirect(
153 ne_session *session,
154 const char *path,
155 const ne_proppatch_operation *ops) {
157 int i, ret;
159 for (i = 0; i < MAX_REDIRECTS; i++) {
160 const ne_uri *u;
162 if ((ret = ne_proppatch(session, path, ops)) != NE_REDIRECT)
163 return ret;
165 if (!(u = ne_redirect_location(session)))
166 break;
168 if (!session_is_local(u))
169 break;
171 if (debug)
172 fprintf(stderr, "REDIRECT FROM '%s' to '%s'\n", path, u->path);
174 path = u->path;
175 }
177 return ret;
178 }
181 static void fill_stat(struct stat* st, const ne_prop_result_set *results, int is_dir) {
182 const char *rt, *e, *gcl, *glm, *cd;
183 const ne_propname resourcetype = { "DAV:", "resourcetype" };
184 const ne_propname executable = { "http://apache.org/dav/props/", "executable" };
185 const ne_propname getcontentlength = { "DAV:", "getcontentlength" };
186 const ne_propname getlastmodified = { "DAV:", "getlastmodified" };
187 const ne_propname creationdate = { "DAV:", "creationdate" };
189 assert(st && results);
191 rt = ne_propset_value(results, &resourcetype);
192 e = ne_propset_value(results, &executable);
193 gcl = ne_propset_value(results, &getcontentlength);
194 glm = ne_propset_value(results, &getlastmodified);
195 cd = ne_propset_value(results, &creationdate);
197 memset(st, 0, sizeof(struct stat));
199 if (is_dir) {
200 st->st_mode = S_IFDIR | 0777;
201 st->st_nlink = 3; /* find will ignore this directory if nlin <= and st_size == 0 */
202 st->st_size = 4096;
203 } else {
204 st->st_mode = S_IFREG | (e && (*e == 'T' || *e == 't') ? 0777 : 0666);
205 st->st_nlink = 1;
206 st->st_size = gcl ? atoll(gcl) : 0;
207 }
209 st->st_atime = time(NULL);
210 st->st_mtime = glm ? ne_rfc1123_parse(glm) : 0;
211 st->st_ctime = cd ? ne_iso8601_parse(cd) : 0;
213 st->st_blocks = (st->st_size+511)/512;
214 /*fprintf(stderr, "a: %u; m: %u; c: %u\n", st->st_atime, st->st_mtime, st->st_ctime);*/
216 st->st_mode &= ~mask;
218 st->st_uid = getuid();
219 st->st_gid = getgid();
220 }
222 static char *strip_trailing_slash(char *fn, int *is_dir) {
223 size_t l = strlen(fn);
224 assert(fn);
225 assert(is_dir);
226 assert(l > 0);
228 if ((*is_dir = (fn[l-1] == '/')))
229 fn[l-1] = 0;
231 return fn;
232 }
234 static void getdir_propfind_callback(void *userdata, const ne_uri *u, const ne_prop_result_set *results) {
235 struct fill_info *f = userdata;
236 struct stat st;
237 char fn[PATH_MAX], *t;
238 int is_dir = 0;
240 assert(f);
242 strncpy(fn, u->path, sizeof(fn));
243 fn[sizeof(fn)-1] = 0;
244 strip_trailing_slash(fn, &is_dir);
246 if (strcmp(fn, f->root) && fn[0]) {
247 char *h;
249 if ((t = strrchr(fn, '/')))
250 t++;
251 else
252 t = fn;
254 dir_cache_add(f->root, t);
255 f->filler(f->buf, h = ne_path_unescape(t), NULL, 0);
256 free(h);
257 }
259 fill_stat(&st, results, is_dir);
260 stat_cache_set(fn, &st);
261 }
263 static void getdir_cache_callback(
264 const char *root,
265 const char *fn,
266 void *user) {
268 struct fill_info *f = user;
269 char path[PATH_MAX];
270 char *h;
272 assert(f);
274 snprintf(path, sizeof(path), "%s/%s", !strcmp(root, "/") ? "" : root, fn);
276 f->filler(f->buf, h = ne_path_unescape(fn), NULL, 0);
277 free(h);
278 }
280 static int dav_readdir(
281 const char *path,
282 void *buf,
283 fuse_fill_dir_t filler,
284 __unused off_t offset,
285 __unused struct fuse_file_info *fi) {
287 struct fill_info f;
288 ne_session *session;
290 path = path_cvt(path);
292 if (debug)
293 fprintf(stderr, "getdir(%s)\n", path);
295 f.buf = buf;
296 f.filler = filler;
297 f.root = path;
299 filler(buf, ".", NULL, 0);
300 filler(buf, "..", NULL, 0);
302 if (dir_cache_enumerate(path, getdir_cache_callback, &f) < 0) {
304 if (debug)
305 fprintf(stderr, "DIR-CACHE-MISS\n");
307 if (!(session = session_get(1)))
308 return -EIO;
310 dir_cache_begin(path);
312 if (simple_propfind_with_redirect(session, path, NE_DEPTH_ONE, query_properties, getdir_propfind_callback, &f) != NE_OK) {
313 dir_cache_finish(path, 2);
314 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
315 return -ENOENT;
316 }
318 dir_cache_finish(path, 1);
319 }
321 return 0;
322 }
324 static void getattr_propfind_callback(void *userdata, const ne_uri *u, const ne_prop_result_set *results) {
325 struct stat *st = (struct stat*) userdata;
326 char fn[PATH_MAX];
327 int is_dir;
329 assert(st);
331 strncpy(fn, u->path, sizeof(fn));
332 fn[sizeof(fn)-1] = 0;
333 strip_trailing_slash(fn, &is_dir);
335 fill_stat(st, results, is_dir);
336 stat_cache_set(fn, st);
337 }
339 static int get_stat(const char *path, struct stat *stbuf) {
340 ne_session *session;
342 if (!(session = session_get(1)))
343 return -EIO;
345 if (stat_cache_get(path, stbuf) == 0) {
346 return stbuf->st_mode == 0 ? -ENOENT : 0;
347 } else {
348 if (debug)
349 fprintf(stderr, "STAT-CACHE-MISS\n");
351 if (simple_propfind_with_redirect(session, path, NE_DEPTH_ZERO, query_properties, getattr_propfind_callback, stbuf) != NE_OK) {
352 stat_cache_invalidate(path);
353 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
354 return -ENOENT;
355 }
357 return 0;
358 }
359 }
361 static int dav_getattr(const char *path, struct stat *stbuf) {
362 path = path_cvt(path);
363 if (debug)
364 fprintf(stderr, "getattr(%s)\n", path);
365 return get_stat(path, stbuf);
366 }
368 static int dav_unlink(const char *path) {
369 int r;
370 struct stat st;
371 ne_session *session;
373 path = path_cvt(path);
375 if (debug)
376 fprintf(stderr, "unlink(%s)\n", path);
378 if (!(session = session_get(1)))
379 return -EIO;
381 if ((r = get_stat(path, &st)) < 0)
382 return r;
384 if (!S_ISREG(st.st_mode))
385 return -EISDIR;
387 if (ne_delete(session, path)) {
388 fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
389 return -ENOENT;
390 }
392 stat_cache_invalidate(path);
393 dir_cache_invalidate_parent(path);
395 return 0;
396 }
398 static int dav_rmdir(const char *path) {
399 char fn[PATH_MAX];
400 int r;
401 struct stat st;
402 ne_session *session;
404 path = path_cvt(path);
406 if (debug)
407 fprintf(stderr, "rmdir(%s)\n", path);
409 if (!(session = session_get(1)))
410 return -EIO;
412 if ((r = get_stat(path, &st)) < 0)
413 return r;
415 if (!S_ISDIR(st.st_mode))
416 return -ENOTDIR;
418 snprintf(fn, sizeof(fn), "%s/", path);
420 if (ne_delete(session, fn)) {
421 fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
422 return -ENOENT;
423 }
425 stat_cache_invalidate(path);
426 dir_cache_invalidate_parent(path);
428 return 0;
429 }
431 static int dav_mkdir(const char *path, __unused mode_t mode) {
432 char fn[PATH_MAX];
433 ne_session *session;
435 path = path_cvt(path);
437 if (debug)
438 fprintf(stderr, "mkdir(%s)\n", path);
440 if (!(session = session_get(1)))
441 return -EIO;
443 snprintf(fn, sizeof(fn), "%s/", path);
445 if (ne_mkcol(session, fn)) {
446 fprintf(stderr, "MKCOL failed: %s\n", ne_get_error(session));
447 return -ENOENT;
448 }
450 stat_cache_invalidate(path);
451 dir_cache_invalidate_parent(path);
453 return 0;
454 }
456 static int dav_rename(const char *from, const char *to) {
457 ne_session *session;
458 int r = 0;
459 struct stat st;
460 char fn[PATH_MAX], *_from;
462 from = _from = strdup(path_cvt(from));
463 assert(from);
464 to = path_cvt(to);
466 if (debug)
467 fprintf(stderr, "rename(%s, %s)\n", from, to);
469 if (!(session = session_get(1))) {
470 r = -EIO;
471 goto finish;
472 }
474 if ((r = get_stat(from, &st)) < 0)
475 goto finish;
477 if (S_ISDIR(st.st_mode)) {
478 snprintf(fn, sizeof(fn), "%s/", from);
479 from = fn;
480 }
482 if (ne_move(session, 1, from, to)) {
483 fprintf(stderr, "MOVE failed: %s\n", ne_get_error(session));
484 r = -ENOENT;
485 goto finish;
486 }
488 stat_cache_invalidate(from);
489 stat_cache_invalidate(to);
491 dir_cache_invalidate_parent(from);
492 dir_cache_invalidate_parent(to);
494 finish:
496 free(_from);
498 return r;
499 }
501 static int dav_release(const char *path, __unused struct fuse_file_info *info) {
502 void *f = NULL;
503 int r = 0;
504 ne_session *session;
506 path = path_cvt(path);
508 if (debug)
509 fprintf(stderr, "release(%s)\n", path);
511 if (!(session = session_get(1))) {
512 r = -EIO;
513 goto finish;
514 }
516 if (!(f = file_cache_get(path))) {
517 fprintf(stderr, "release() called for closed file\n");
518 r = -EFAULT;
519 goto finish;
520 }
522 if (file_cache_close(f) < 0) {
523 r = -errno;
524 goto finish;
525 }
527 finish:
528 if (f)
529 file_cache_unref(f);
531 return r;
532 }
534 static int dav_fsync(const char *path, __unused int isdatasync, __unused struct fuse_file_info *info) {
535 void *f = NULL;
536 int r = 0;
537 ne_session *session;
539 path = path_cvt(path);
540 if (debug)
541 fprintf(stderr, "fsync(%s)\n", path);
543 if (!(session = session_get(1))) {
544 r = -EIO;
545 goto finish;
546 }
548 if (!(f = file_cache_get(path))) {
549 fprintf(stderr, "fsync() called for closed file\n");
550 r = -EFAULT;
551 goto finish;
552 }
554 if (file_cache_sync(f) < 0) {
555 r = -errno;
556 goto finish;
557 }
559 finish:
561 if (f)
562 file_cache_unref(f);
564 return r;
565 }
567 static int dav_mknod(const char *path, mode_t mode, __unused dev_t rdev) {
568 char tempfile[PATH_MAX];
569 int fd;
570 ne_session *session;
572 path = path_cvt(path);
573 if (debug)
574 fprintf(stderr, "mknod(%s)\n", path);
576 if (!(session = session_get(1)))
577 return -EIO;
579 if (!S_ISREG(mode))
580 return -ENOTSUP;
582 snprintf(tempfile, sizeof(tempfile), "%s/fusedav-empty-XXXXXX", "/tmp");
583 if ((fd = mkstemp(tempfile)) < 0)
584 return -errno;
586 unlink(tempfile);
588 if (ne_put(session, path, fd)) {
589 fprintf(stderr, "mknod:PUT failed: %s\n", ne_get_error(session));
590 close(fd);
591 return -EACCES;
592 }
594 close(fd);
596 stat_cache_invalidate(path);
597 dir_cache_invalidate_parent(path);
599 return 0;
600 }
602 static int dav_open(const char *path, struct fuse_file_info *info) {
603 void *f;
605 if (debug)
606 fprintf(stderr, "open(%s)\n", path);
608 path = path_cvt(path);
610 if (!(f = file_cache_open(path, info->flags)))
611 return -errno;
613 file_cache_unref(f);
615 return 0;
616 }
618 static int dav_read(const char *path, char *buf, size_t size, off_t offset, __unused struct fuse_file_info *info) {
619 void *f = NULL;
620 ssize_t r;
622 path = path_cvt(path);
624 if (debug)
625 fprintf(stderr, "read(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
627 if (!(f = file_cache_get(path))) {
628 fprintf(stderr, "read() called for closed file\n");
629 r = -EFAULT;
630 goto finish;
631 }
633 if ((r = file_cache_read(f, buf, size, offset)) < 0) {
634 r = -errno;
635 goto finish;
636 }
638 finish:
639 if (f)
640 file_cache_unref(f);
642 return r;
643 }
645 static int dav_write(const char *path, const char *buf, size_t size, off_t offset, __unused struct fuse_file_info *info) {
646 void *f = NULL;
647 ssize_t r;
649 path = path_cvt(path);
651 if (debug)
652 fprintf(stderr, "write(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
654 if (!(f = file_cache_get(path))) {
655 fprintf(stderr, "write() called for closed file\n");
656 r = -EFAULT;
657 goto finish;
658 }
660 if ((r = file_cache_write(f, buf, size, offset)) < 0) {
661 r = -errno;
662 goto finish;
663 }
665 finish:
666 if (f)
667 file_cache_unref(f);
669 return r;
670 }
673 static int dav_truncate(const char *path, off_t size) {
674 void *f = NULL;
675 int r = 0;
676 ne_session *session;
678 path = path_cvt(path);
680 if (debug)
681 fprintf(stderr, "truncate(%s, %lu)\n", path, (unsigned long) size);
683 if (!(session = session_get(1)))
684 r = -EIO;
685 goto finish;
687 if (!(f = file_cache_get(path))) {
688 fprintf(stderr, "truncate() called for closed file\n");
689 r = -EFAULT;
690 goto finish;
691 }
693 if (file_cache_truncate(f, size) < 0) {
694 r = -errno;
695 goto finish;
696 }
698 finish:
699 if (f)
700 file_cache_unref(f);
702 return r;
703 }
705 static int dav_utime(const char *path, struct utimbuf *buf) {
706 ne_session *session;
707 const ne_propname getlastmodified = { "DAV:", "getlastmodified" };
708 ne_proppatch_operation ops[2];
709 int r = 0;
710 char *date;
712 assert(path);
713 assert(buf);
715 path = path_cvt(path);
717 if (debug)
718 fprintf(stderr, "utime(%s, %lu, %lu)\n", path, (unsigned long) buf->actime, (unsigned long) buf->modtime);
720 ops[0].name = &getlastmodified;
721 ops[0].type = ne_propset;
722 ops[0].value = date = ne_rfc1123_date(buf->modtime);
723 ops[1].name = NULL;
725 if (!(session = session_get(1))) {
726 r = -EIO;
727 goto finish;
728 }
730 if (proppatch_with_redirect(session, path, ops)) {
731 fprintf(stderr, "PROPPATCH failed: %s\n", ne_get_error(session));
732 r = -ENOTSUP;
733 goto finish;
734 }
736 stat_cache_invalidate(path);
738 finish:
739 free(date);
741 return r;
742 }
744 static const char *fix_xattr(const char *name) {
745 assert(name);
747 if (!strcmp(name, MIME_XATTR))
748 return "user.webdav(DAV:;getcontenttype)";
750 return name;
751 }
753 struct listxattr_info {
754 char *list;
755 size_t space, size;
756 };
758 static int listxattr_iterator(
759 void *userdata,
760 const ne_propname *pname,
761 const char *value,
762 __unused const ne_status *status) {
764 struct listxattr_info *l = userdata;
765 int n;
767 assert(l);
769 if (!value || !pname)
770 return -1;
772 if (l->list) {
773 n = snprintf(l->list, l->space, "user.webdav(%s;%s)", pname->nspace, pname->name) + 1;
775 if (n >= (int) l->space) {
776 l->size += l->space;
777 l->space = 0;
778 return 1;
780 } else {
781 l->size += n;
782 l->space -= n;
784 if (l->list)
785 l->list += n;
787 return 0;
788 }
789 } else {
790 /* Calculate space */
792 l->size += strlen(pname->nspace) + strlen(pname->name) + 15;
793 return 0;
794 }
795 }
797 static void listxattr_propfind_callback(void *userdata, __unused const ne_uri *u, const ne_prop_result_set *results) {
798 struct listxattr_info *l = userdata;
799 ne_propset_iterate(results, listxattr_iterator, l);
800 }
802 static int dav_listxattr(
803 const char *path,
804 char *list,
805 size_t size) {
807 ne_session *session;
808 struct listxattr_info l;
811 assert(path);
813 path = path_cvt(path);
815 if (debug)
816 fprintf(stderr, "listxattr(%s, .., %lu)\n", path, (unsigned long) size);
818 if (list) {
819 l.list = list;
820 l.space = size-1;
821 l.size = 0;
823 if (l.space >= sizeof(MIME_XATTR)) {
824 memcpy(l.list, MIME_XATTR, sizeof(MIME_XATTR));
825 l.list += sizeof(MIME_XATTR);
826 l.space -= sizeof(MIME_XATTR);
827 l.size += sizeof(MIME_XATTR);
828 }
830 } else {
831 l.list = NULL;
832 l.space = 0;
833 l.size = sizeof(MIME_XATTR);
834 }
836 if (!(session = session_get(1)))
837 return -EIO;
839 if (simple_propfind_with_redirect(session, path, NE_DEPTH_ZERO, NULL, listxattr_propfind_callback, &l) != NE_OK) {
840 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
841 return -EIO;
842 }
844 if (l.list) {
845 assert(l.space > 0);
846 *l.list = 0;
847 }
849 return l.size+1;
850 }
852 struct getxattr_info {
853 ne_propname propname;
854 char *value;
855 size_t space, size;
856 };
858 static int getxattr_iterator(
859 void *userdata,
860 const ne_propname *pname,
861 const char *value,
862 __unused const ne_status *status) {
864 struct getxattr_info *g = userdata;
866 assert(g);
868 if (!value || !pname)
869 return -1;
871 if (strcmp(pname->nspace, g->propname.nspace) ||
872 strcmp(pname->name, g->propname.name))
873 return 0;
875 if (g->value) {
876 size_t l;
878 l = strlen(value);
880 if (l > g->space)
881 l = g->space;
883 memcpy(g->value, value, l);
884 g->size = l;
885 } else {
886 /* Calculate space */
888 g->size = strlen(value);
889 return 0;
890 }
892 return 0;
893 }
895 static void getxattr_propfind_callback(void *userdata, __unused const ne_uri *u, const ne_prop_result_set *results) {
896 struct getxattr_info *g = userdata;
897 ne_propset_iterate(results, getxattr_iterator, g);
898 }
900 static int parse_xattr(const char *name, char *dnspace, size_t dnspace_length, char *dname, size_t dname_length) {
901 char *e;
902 size_t k;
904 assert(name);
905 assert(dnspace);
906 assert(dnspace_length);
907 assert(dname);
908 assert(dname_length);
910 if (strncmp(name, "user.webdav(", 12) ||
911 name[strlen(name)-1] != ')' ||
912 !(e = strchr(name+12, ';')))
913 return -1;
915 if ((k = strcspn(name+12, ";")) > dnspace_length-1)
916 return -1;
918 memcpy(dnspace, name+12, k);
919 dnspace[k] = 0;
921 e++;
923 if ((k = strlen(e)) > dname_length-1)
924 return -1;
926 assert(k > 0);
927 k--;
929 memcpy(dname, e, k);
930 dname[k] = 0;
932 return 0;
933 }
935 static int dav_getxattr(
936 const char *path,
937 const char *name,
938 char *value,
939 size_t size) {
941 ne_session *session;
942 struct getxattr_info g;
943 ne_propname props[2];
944 char dnspace[128], dname[128];
946 assert(path);
948 path = path_cvt(path);
949 name = fix_xattr(name);
951 if (debug)
952 fprintf(stderr, "getxattr(%s, %s, .., %lu)\n", path, name, (unsigned long) size);
954 if (parse_xattr(name, dnspace, sizeof(dnspace), dname, sizeof(dname)) < 0)
955 return -ENOATTR;
957 props[0].nspace = dnspace;
958 props[0].name = dname;
959 props[1].nspace = NULL;
960 props[1].name = NULL;
962 if (value) {
963 g.value = value;
964 g.space = size;
965 g.size = (size_t) -1;
966 } else {
967 g.value = NULL;
968 g.space = 0;
969 g.size = (size_t) -1;
970 }
972 g.propname = props[0];
974 if (!(session = session_get(1)))
975 return -EIO;
977 if (simple_propfind_with_redirect(session, path, NE_DEPTH_ZERO, props, getxattr_propfind_callback, &g) != NE_OK) {
978 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
979 return -EIO;
980 }
982 if (g.size == (size_t) -1)
983 return -ENOATTR;
985 return g.size;
986 }
988 static int dav_setxattr(
989 const char *path,
990 const char *name,
991 const char *value,
992 size_t size,
993 int flags) {
995 ne_session *session;
996 ne_propname propname;
997 ne_proppatch_operation ops[2];
998 int r = 0;
999 char dnspace[128], dname[128];
1000 char *value_fixed = NULL;
1002 assert(path);
1003 assert(name);
1004 assert(value);
1006 path = path_cvt(path);
1007 name = fix_xattr(name);
1009 if (debug)
1010 fprintf(stderr, "setxattr(%s, %s)\n", path, name);
1012 if (flags) {
1013 r = ENOTSUP;
1014 goto finish;
1015 }
1017 if (parse_xattr(name, dnspace, sizeof(dnspace), dname, sizeof(dname)) < 0) {
1018 r = -ENOATTR;
1019 goto finish;
1020 }
1022 propname.nspace = dnspace;
1023 propname.name = dname;
1025 /* Add trailing NUL byte if required */
1026 if (!memchr(value, 0, size)) {
1027 value_fixed = malloc(size+1);
1028 assert(value_fixed);
1030 memcpy(value_fixed, value, size);
1031 value_fixed[size] = 0;
1033 value = value_fixed;
1034 }
1036 ops[0].name = &propname;
1037 ops[0].type = ne_propset;
1038 ops[0].value = value;
1040 ops[1].name = NULL;
1042 if (!(session = session_get(1))) {
1043 r = -EIO;
1044 goto finish;
1045 }
1047 if (proppatch_with_redirect(session, path, ops)) {
1048 fprintf(stderr, "PROPPATCH failed: %s\n", ne_get_error(session));
1049 r = -ENOTSUP;
1050 goto finish;
1051 }
1053 stat_cache_invalidate(path);
1055 finish:
1056 free(value_fixed);
1058 return r;
1059 }
1061 static int dav_removexattr(const char *path, const char *name) {
1062 ne_session *session;
1063 ne_propname propname;
1064 ne_proppatch_operation ops[2];
1065 int r = 0;
1066 char dnspace[128], dname[128];
1068 assert(path);
1069 assert(name);
1071 path = path_cvt(path);
1072 name = fix_xattr(name);
1074 if (debug)
1075 fprintf(stderr, "removexattr(%s, %s)\n", path, name);
1077 if (parse_xattr(name, dnspace, sizeof(dnspace), dname, sizeof(dname)) < 0) {
1078 r = -ENOATTR;
1079 goto finish;
1080 }
1082 propname.nspace = dnspace;
1083 propname.name = dname;
1085 ops[0].name = &propname;
1086 ops[0].type = ne_propremove;
1087 ops[0].value = NULL;
1089 ops[1].name = NULL;
1091 if (!(session = session_get(1))) {
1092 r = -EIO;
1093 goto finish;
1094 }
1096 if (proppatch_with_redirect(session, path, ops)) {
1097 fprintf(stderr, "PROPPATCH failed: %s\n", ne_get_error(session));
1098 r = -ENOTSUP;
1099 goto finish;
1100 }
1102 stat_cache_invalidate(path);
1104 finish:
1106 return r;
1107 }
1109 static int dav_chmod(const char *path, mode_t mode) {
1110 ne_session *session;
1111 const ne_propname executable = { "http://apache.org/dav/props/", "executable" };
1112 ne_proppatch_operation ops[2];
1113 int r = 0;
1115 assert(path);
1117 path = path_cvt(path);
1119 if (debug)
1120 fprintf(stderr, "chmod(%s, %04o)\n", path, mode);
1122 ops[0].name = &executable;
1123 ops[0].type = ne_propset;
1124 ops[0].value = mode & 0111 ? "T" : "F";
1125 ops[1].name = NULL;
1127 if (!(session = session_get(1))) {
1128 r = -EIO;
1129 goto finish;
1130 }
1132 if (proppatch_with_redirect(session, path, ops)) {
1133 fprintf(stderr, "PROPPATCH failed: %s\n", ne_get_error(session));
1134 r = -ENOTSUP;
1135 goto finish;
1136 }
1138 stat_cache_invalidate(path);
1140 finish:
1142 return r;
1143 }
1145 static struct fuse_operations dav_oper = {
1146 .getattr = dav_getattr,
1147 .readdir = dav_readdir,
1148 .mknod = dav_mknod,
1149 .mkdir = dav_mkdir,
1150 .unlink = dav_unlink,
1151 .rmdir = dav_rmdir,
1152 .rename = dav_rename,
1153 .chmod = dav_chmod,
1154 .truncate = dav_truncate,
1155 .utime = dav_utime,
1156 .open = dav_open,
1157 .read = dav_read,
1158 .write = dav_write,
1159 .release = dav_release,
1160 .fsync = dav_fsync,
1161 .setxattr = dav_setxattr,
1162 .getxattr = dav_getxattr,
1163 .listxattr = dav_listxattr,
1164 .removexattr = dav_removexattr,
1165 };
1167 static void usage(char *argv0) {
1168 char *e;
1170 if ((e = strrchr(argv0, '/')))
1171 e++;
1172 else
1173 e = argv0;
1175 fprintf(stderr,
1176 "%s [-hDL] [-t SECS] [-u USERNAME] [-p PASSWORD] [-o OPTIONS] URL MOUNTPOINT\n"
1177 "\t-h Show this help\n"
1178 "\t-D Enable debug mode\n"
1179 "\t-u Username if required\n"
1180 "\t-p Password if required\n"
1181 "\t-o Additional FUSE mount options\n"
1182 "\t-L Locking the repository during mount\n"
1183 "\t-t Set lock timeout\n",
1184 e);
1185 }
1187 static void exit_handler(__unused int sig) {
1188 static const char m[] = "*** Caught signal ***\n";
1189 if(fuse != NULL)
1190 fuse_exit(fuse);
1191 write(2, m, strlen(m));
1192 }
1194 static void empty_handler(__unused int sig) {}
1196 static int setup_signal_handlers(void) {
1197 struct sigaction sa;
1198 sigset_t m;
1200 sa.sa_handler = exit_handler;
1201 sigemptyset(&(sa.sa_mask));
1202 sa.sa_flags = 0;
1204 if (sigaction(SIGHUP, &sa, NULL) == -1 ||
1205 sigaction(SIGINT, &sa, NULL) == -1 ||
1206 sigaction(SIGTERM, &sa, NULL) == -1) {
1208 fprintf(stderr, "Cannot set exit signal handlers: %s\n", strerror(errno));
1209 return -1;
1210 }
1212 sa.sa_handler = SIG_IGN;
1214 if (sigaction(SIGPIPE, &sa, NULL) == -1) {
1215 fprintf(stderr, "Cannot set ignored signals: %s\n", strerror(errno));
1216 return -1;
1217 }
1219 /* Used to shut down the locking thread */
1220 sa.sa_handler = empty_handler;
1222 if (sigaction(SIGUSR1, &sa, NULL) == -1) {
1223 fprintf(stderr, "Cannot set user signals: %s\n", strerror(errno));
1224 return -1;
1225 }
1227 sigemptyset(&m);
1228 pthread_sigmask(SIG_BLOCK, &m, &m);
1229 sigdelset(&m, SIGHUP);
1230 sigdelset(&m, SIGINT);
1231 sigdelset(&m, SIGTERM);
1232 sigaddset(&m, SIGPIPE);
1233 sigaddset(&m, SIGUSR1);
1234 pthread_sigmask(SIG_SETMASK, &m, NULL);
1236 return 0;
1237 }
1239 static int create_lock(void) {
1240 ne_session *session;
1241 char _owner[64], *owner;
1242 int i;
1243 int ret;
1245 lock = ne_lock_create();
1246 assert(lock);
1248 if (!(session = session_get(0)))
1249 return -1;
1251 if (!(owner = username))
1252 if (!(owner = getenv("USER")))
1253 if (!(owner = getenv("LOGNAME"))) {
1254 snprintf(_owner, sizeof(_owner), "%lu", (unsigned long) getuid());
1255 owner = owner;
1256 }
1258 ne_fill_server_uri(session, &lock->uri);
1260 lock->uri.path = strdup(base_directory);
1261 lock->depth = NE_DEPTH_INFINITE;
1262 lock->timeout = lock_timeout;
1263 lock->owner = strdup(owner);
1265 if (debug)
1266 fprintf(stderr, "Acquiring lock...\n");
1268 for (i = 0; i < MAX_REDIRECTS; i++) {
1269 const ne_uri *u;
1271 if ((ret = ne_lock(session, lock)) != NE_REDIRECT)
1272 break;
1274 if (!(u = ne_redirect_location(session)))
1275 break;
1277 if (!session_is_local(u))
1278 break;
1280 if (debug)
1281 fprintf(stderr, "REDIRECT FROM '%s' to '%s'\n", lock->uri.path, u->path);
1283 free(lock->uri.path);
1284 lock->uri.path = strdup(u->path);
1285 }
1287 if (ret) {
1288 fprintf(stderr, "LOCK failed: %s\n", ne_get_error(session));
1289 ne_lock_destroy(lock);
1290 lock = NULL;
1291 return -1;
1292 }
1294 lock_store = ne_lockstore_create();
1295 assert(lock_store);
1297 ne_lockstore_add(lock_store, lock);
1299 return 0;
1300 }
1302 static int remove_lock(void) {
1303 ne_session *session;
1305 assert(lock);
1307 if (!(session = session_get(0)))
1308 return -1;
1310 if (debug)
1311 fprintf(stderr, "Removing lock...\n");
1313 if (ne_unlock(session, lock)) {
1314 fprintf(stderr, "UNLOCK failed: %s\n", ne_get_error(session));
1315 return -1;
1316 }
1318 return 0;
1319 }
1321 static void *lock_thread_func(__unused void *p) {
1322 ne_session *session;
1323 sigset_t block;
1325 if (debug)
1326 fprintf(stderr, "lock_thread entering\n");
1328 if (!(session = session_get(1)))
1329 return NULL;
1331 sigemptyset(&block);
1332 sigaddset(&block, SIGUSR1);
1334 assert(lock);
1336 while (!lock_thread_exit) {
1337 int r, t;
1339 lock->timeout = lock_timeout;
1341 pthread_sigmask(SIG_BLOCK, &block, NULL);
1342 r = ne_lock_refresh(session, lock);
1343 pthread_sigmask(SIG_UNBLOCK, &block, NULL);
1345 if (r) {
1346 fprintf(stderr, "LOCK refresh failed: %s\n", ne_get_error(session));
1347 break;
1348 }
1350 if (lock_thread_exit)
1351 break;
1353 t = lock_timeout/2;
1354 if (t <= 0)
1355 t = 1;
1356 sleep(t);
1357 }
1359 if (debug)
1360 fprintf(stderr, "lock_thread exiting\n");
1362 return NULL;
1363 }
1365 int main(int argc, char *argv[]) {
1366 int c;
1367 char *u = NULL, *p = NULL, *o = NULL;
1368 int fuse_fd = -1;
1369 int ret = 1;
1370 char mountpoint[PATH_MAX];
1371 pthread_t lock_thread;
1372 int lock_thread_running = 0;
1373 int enable_locking = 0;
1375 static char *mount_args_strings[] = {
1376 NULL, /* path*/
1377 NULL, /* -o */
1378 NULL,
1379 NULL};
1381 struct fuse_args mount_args = {
1382 .argc = 1,
1383 .argv = mount_args_strings,
1384 .allocated = 0
1385 };
1387 if (ne_sock_init()) {
1388 fprintf(stderr, "Failed to initialize libneon.\n");
1389 goto finish;
1390 }
1392 if (!ne_has_support(NE_FEATURE_SSL) ||
1393 !ne_has_support(NE_FEATURE_TS_SSL) ||
1394 !ne_has_support(NE_FEATURE_LFS)) {
1395 fprintf(stderr, "fusedav requires libneon built with SSL, SSL thread safety and LFS enabled.\n");
1396 goto finish;
1397 }
1399 mask = umask(0);
1400 umask(mask);
1402 cache_alloc();
1404 if (setup_signal_handlers() < 0)
1405 goto finish;
1407 while ((c = getopt(argc, argv, "hu:p:Do:Lt:")) != -1) {
1409 switch(c) {
1410 case 'u':
1411 u = optarg;
1412 break;
1414 case 'p':
1415 p = optarg;
1416 break;
1418 case 'D':
1419 debug = !debug;
1420 break;
1422 case 'o':
1423 o = optarg;
1424 break;
1426 case 'L':
1427 enable_locking = 1;
1428 break;
1430 case 't':
1431 if ((lock_timeout = atoi(optarg)) < 0) {
1432 fprintf(stderr, "Invalid lock timeout '%s'\n", optarg);
1433 goto finish;
1434 }
1435 break;
1437 case 'h':
1438 ret = 0;
1440 /* fall through */
1441 default:
1442 usage(argv[0]);
1443 goto finish;
1444 }
1445 }
1447 if (optind != argc-2) {
1448 usage(argv[0]);
1449 goto finish;
1450 }
1452 if (session_set_uri(argv[optind], u, p) < 0) {
1453 usage(argv[0]);
1454 goto finish;
1455 }
1457 if (argv[optind+1][0] == '/')
1458 snprintf(mountpoint, sizeof(mountpoint), "%s", argv[optind+1]);
1459 else {
1460 char *pwd = get_current_dir_name();
1461 snprintf(mountpoint, sizeof(mountpoint), "%s/%s", pwd, argv[optind+1]);
1462 free(pwd);
1463 }
1465 mount_args_strings[0] = argv[optind];
1467 if (o) {
1468 mount_args_strings[1] = (char*) "-o";
1469 mount_args_strings[2] = o;
1470 mount_args.argc += 2;
1471 }
1473 if ((fuse_fd = fuse_mount(mountpoint, &mount_args)) < 0) {
1474 fprintf(stderr, "Failed to mount FUSE file system.\n");
1475 goto finish;
1476 }
1478 if (!(fuse = fuse_new(fuse_fd, &mount_args, &dav_oper, sizeof(dav_oper)))) {
1479 fprintf(stderr, "Failed to create FUSE object.\n");
1480 goto finish;
1481 }
1483 if (enable_locking && create_lock() >= 0) {
1484 int r;
1485 if ((r = pthread_create(&lock_thread, NULL, lock_thread_func, NULL)) < 0) {
1486 fprintf(stderr, "pthread_create(): %s\n", strerror(r));
1487 goto finish;
1488 }
1490 lock_thread_running = 1;
1491 }
1493 fuse_loop_mt(fuse);
1495 if (debug)
1496 fprintf(stderr, "Exiting cleanly.\n");
1498 ret = 0;
1500 finish:
1502 if (lock_thread_running) {
1503 lock_thread_exit = 1;
1504 pthread_kill(lock_thread, SIGUSR1);
1505 pthread_join(lock_thread, NULL);
1506 remove_lock();
1507 ne_lockstore_destroy(lock_store);
1508 }
1510 if (fuse)
1511 fuse_destroy(fuse);
1513 if (fuse_fd >= 0)
1514 fuse_unmount(mountpoint);
1516 file_cache_close_all();
1517 cache_free();
1518 session_free();
1520 return ret;
1521 }