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>
38 #include <attr/xattr.h>
40 #include <ne_request.h>
41 #include <ne_basic.h>
42 #include <ne_props.h>
43 #include <ne_utils.h>
44 #include <ne_socket.h>
45 #include <ne_auth.h>
46 #include <ne_dates.h>
47 #include <ne_redirect.h>
48 #include <ne_uri.h>
50 #include <fuse.h>
52 #include "statcache.h"
53 #include "filecache.h"
54 #include "session.h"
55 #include "fusedav.h"
57 const ne_propname query_properties[] = {
58 { "DAV:", "resourcetype" },
59 { "http://apache.org/dav/props/", "executable" },
60 { "DAV:", "getcontentlength" },
61 { "DAV:", "getlastmodified" },
62 { "DAV:", "creationdate" },
63 { NULL, NULL }
64 };
66 mode_t mask = 0;
67 int debug = 0;
68 struct fuse* fuse = NULL;
69 ne_lock_store *lock_store = NULL;
70 struct ne_lock *lock = NULL;
71 int lock_thread_exit = 0;
72 int lock_timeout = 60;
74 #define MIME_XATTR "user.mime_type"
76 #define MAX_REDIRECTS 10
78 struct fill_info {
79 void *buf;
80 fuse_fill_dir_t filler;
81 const char *root;
82 };
84 static int get_stat(const char *path, struct stat *stbuf);
86 static pthread_once_t path_cvt_once = PTHREAD_ONCE_INIT;
87 static pthread_key_t path_cvt_tsd_key;
89 static void path_cvt_tsd_key_init(void) {
90 pthread_key_create(&path_cvt_tsd_key, free);
91 }
93 static const char *path_cvt(const char *path) {
94 char *r, *t;
95 int l;
97 pthread_once(&path_cvt_once, path_cvt_tsd_key_init);
99 if ((r = pthread_getspecific(path_cvt_tsd_key)))
100 free(r);
102 t = malloc((l = strlen(base_directory)+strlen(path))+1);
103 assert(t);
104 sprintf(t, "%s%s", base_directory, path);
106 if (l > 1 && t[l-1] == '/')
107 t[l-1] = 0;
109 r = ne_path_escape(t);
110 free(t);
112 pthread_setspecific(path_cvt_tsd_key, r);
114 return r;
115 }
117 static int simple_propfind_with_redirect(
118 ne_session *session,
119 const char *path,
120 int depth,
121 const ne_propname *props,
122 ne_props_result results,
123 void *userdata) {
125 int i, ret;
127 for (i = 0; i < MAX_REDIRECTS; i++) {
128 const ne_uri *u;
130 if ((ret = ne_simple_propfind(session, path, depth, props, results, userdata)) != NE_REDIRECT)
131 return ret;
133 if (!(u = ne_redirect_location(session)))
134 break;
136 if (!session_is_local(u))
137 break;
139 if (debug)
140 fprintf(stderr, "REDIRECT FROM '%s' to '%s'\n", path, u->path);
142 path = u->path;
143 }
145 return ret;
146 }
148 static int proppatch_with_redirect(
149 ne_session *session,
150 const char *path,
151 const ne_proppatch_operation *ops) {
153 int i, ret;
155 for (i = 0; i < MAX_REDIRECTS; i++) {
156 const ne_uri *u;
158 if ((ret = ne_proppatch(session, path, ops)) != NE_REDIRECT)
159 return ret;
161 if (!(u = ne_redirect_location(session)))
162 break;
164 if (!session_is_local(u))
165 break;
167 if (debug)
168 fprintf(stderr, "REDIRECT FROM '%s' to '%s'\n", path, u->path);
170 path = u->path;
171 }
173 return ret;
174 }
177 static void fill_stat(struct stat* st, const ne_prop_result_set *results, int is_dir) {
178 const char *rt, *e, *gcl, *glm, *cd;
179 const ne_propname resourcetype = { "DAV:", "resourcetype" };
180 const ne_propname executable = { "http://apache.org/dav/props/", "executable" };
181 const ne_propname getcontentlength = { "DAV:", "getcontentlength" };
182 const ne_propname getlastmodified = { "DAV:", "getlastmodified" };
183 const ne_propname creationdate = { "DAV:", "creationdate" };
185 assert(st && results);
187 rt = ne_propset_value(results, &resourcetype);
188 e = ne_propset_value(results, &executable);
189 gcl = ne_propset_value(results, &getcontentlength);
190 glm = ne_propset_value(results, &getlastmodified);
191 cd = ne_propset_value(results, &creationdate);
193 memset(st, 0, sizeof(struct stat));
195 if (is_dir) {
196 st->st_mode = S_IFDIR | 0777;
197 st->st_nlink = 3; /* find will ignore this directory if nlin <= and st_size == 0 */
198 st->st_size = 4096;
199 } else {
200 st->st_mode = S_IFREG | (e && (*e == 'T' || *e == 't') ? 0777 : 0666);
201 st->st_nlink = 1;
202 st->st_size = gcl ? atoll(gcl) : 0;
203 }
205 st->st_atime = time(NULL);
206 st->st_mtime = glm ? ne_rfc1123_parse(glm) : 0;
207 st->st_ctime = cd ? ne_iso8601_parse(cd) : 0;
209 st->st_blocks = (st->st_size+511)/512;
210 /*fprintf(stderr, "a: %u; m: %u; c: %u\n", st->st_atime, st->st_mtime, st->st_ctime);*/
212 st->st_mode &= ~mask;
214 st->st_uid = getuid();
215 st->st_gid = getgid();
216 }
218 static char *strip_trailing_slash(char *fn, int *is_dir) {
219 size_t l = strlen(fn);
220 assert(fn);
221 assert(is_dir);
222 assert(l > 0);
224 if ((*is_dir = (fn[l-1] == '/')))
225 fn[l-1] = 0;
227 return fn;
228 }
230 static void getdir_propfind_callback(void *userdata, const ne_uri *u, const ne_prop_result_set *results) {
231 struct fill_info *f = userdata;
232 struct stat st;
233 char fn[PATH_MAX], *t;
234 int is_dir = 0;
236 assert(f);
238 strncpy(fn, u->path, sizeof(fn));
239 fn[sizeof(fn)-1] = 0;
240 strip_trailing_slash(fn, &is_dir);
242 if (strcmp(fn, f->root) && fn[0]) {
243 char *h;
245 if ((t = strrchr(fn, '/')))
246 t++;
247 else
248 t = fn;
250 dir_cache_add(f->root, t);
251 f->filler(f->buf, h = ne_path_unescape(t), NULL, 0);
252 free(h);
253 }
255 fill_stat(&st, results, is_dir);
256 stat_cache_set(fn, &st);
257 }
259 static void getdir_cache_callback(
260 const char *root,
261 const char *fn,
262 void *user) {
264 struct fill_info *f = user;
265 char path[PATH_MAX];
266 char *h;
268 assert(f);
270 snprintf(path, sizeof(path), "%s/%s", !strcmp(root, "/") ? "" : root, fn);
272 f->filler(f->buf, h = ne_path_unescape(fn), NULL, 0);
273 free(h);
274 }
276 static int dav_readdir(
277 const char *path,
278 void *buf,
279 fuse_fill_dir_t filler,
280 __unused off_t offset,
281 __unused struct fuse_file_info *fi) {
283 struct fill_info f;
284 ne_session *session;
286 path = path_cvt(path);
288 if (debug)
289 fprintf(stderr, "getdir(%s)\n", path);
291 f.buf = buf;
292 f.filler = filler;
293 f.root = path;
295 filler(buf, ".", NULL, 0);
296 filler(buf, "..", NULL, 0);
298 if (dir_cache_enumerate(path, getdir_cache_callback, &f) < 0) {
300 if (debug)
301 fprintf(stderr, "DIR-CACHE-MISS\n");
303 if (!(session = session_get(1)))
304 return -EIO;
306 dir_cache_begin(path);
308 if (simple_propfind_with_redirect(session, path, NE_DEPTH_ONE, query_properties, getdir_propfind_callback, &f) != NE_OK) {
309 dir_cache_finish(path, 2);
310 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
311 return -ENOENT;
312 }
314 dir_cache_finish(path, 1);
315 }
317 return 0;
318 }
320 static void getattr_propfind_callback(void *userdata, const ne_uri *u, const ne_prop_result_set *results) {
321 struct stat *st = (struct stat*) userdata;
322 char fn[PATH_MAX];
323 int is_dir;
325 assert(st);
327 strncpy(fn, u->path, sizeof(fn));
328 fn[sizeof(fn)-1] = 0;
329 strip_trailing_slash(fn, &is_dir);
331 fill_stat(st, results, is_dir);
332 stat_cache_set(fn, st);
333 }
335 static int get_stat(const char *path, struct stat *stbuf) {
336 ne_session *session;
338 if (!(session = session_get(1)))
339 return -EIO;
341 if (stat_cache_get(path, stbuf) == 0) {
342 return stbuf->st_mode == 0 ? -ENOENT : 0;
343 } else {
344 if (debug)
345 fprintf(stderr, "STAT-CACHE-MISS\n");
347 if (simple_propfind_with_redirect(session, path, NE_DEPTH_ZERO, query_properties, getattr_propfind_callback, stbuf) != NE_OK) {
348 stat_cache_invalidate(path);
349 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
350 return -ENOENT;
351 }
353 return 0;
354 }
355 }
357 static int dav_getattr(const char *path, struct stat *stbuf) {
358 path = path_cvt(path);
359 if (debug)
360 fprintf(stderr, "getattr(%s)\n", path);
361 return get_stat(path, stbuf);
362 }
364 static int dav_unlink(const char *path) {
365 int r;
366 struct stat st;
367 ne_session *session;
369 path = path_cvt(path);
371 if (debug)
372 fprintf(stderr, "unlink(%s)\n", path);
374 if (!(session = session_get(1)))
375 return -EIO;
377 if ((r = get_stat(path, &st)) < 0)
378 return r;
380 if (!S_ISREG(st.st_mode))
381 return -EISDIR;
383 if (ne_delete(session, path)) {
384 fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
385 return -ENOENT;
386 }
388 stat_cache_invalidate(path);
389 dir_cache_invalidate_parent(path);
391 return 0;
392 }
394 static int dav_rmdir(const char *path) {
395 char fn[PATH_MAX];
396 int r;
397 struct stat st;
398 ne_session *session;
400 path = path_cvt(path);
402 if (debug)
403 fprintf(stderr, "rmdir(%s)\n", path);
405 if (!(session = session_get(1)))
406 return -EIO;
408 if ((r = get_stat(path, &st)) < 0)
409 return r;
411 if (!S_ISDIR(st.st_mode))
412 return -ENOTDIR;
414 snprintf(fn, sizeof(fn), "%s/", path);
416 if (ne_delete(session, fn)) {
417 fprintf(stderr, "DELETE failed: %s\n", ne_get_error(session));
418 return -ENOENT;
419 }
421 stat_cache_invalidate(path);
422 dir_cache_invalidate_parent(path);
424 return 0;
425 }
427 static int dav_mkdir(const char *path, __unused mode_t mode) {
428 char fn[PATH_MAX];
429 ne_session *session;
431 path = path_cvt(path);
433 if (debug)
434 fprintf(stderr, "mkdir(%s)\n", path);
436 if (!(session = session_get(1)))
437 return -EIO;
439 snprintf(fn, sizeof(fn), "%s/", path);
441 if (ne_mkcol(session, fn)) {
442 fprintf(stderr, "MKCOL failed: %s\n", ne_get_error(session));
443 return -ENOENT;
444 }
446 stat_cache_invalidate(path);
447 dir_cache_invalidate_parent(path);
449 return 0;
450 }
452 static int dav_rename(const char *from, const char *to) {
453 ne_session *session;
454 int r = 0;
455 struct stat st;
456 char fn[PATH_MAX], *_from;
458 from = _from = strdup(path_cvt(from));
459 assert(from);
460 to = path_cvt(to);
462 if (debug)
463 fprintf(stderr, "rename(%s, %s)\n", from, to);
465 if (!(session = session_get(1))) {
466 r = -EIO;
467 goto finish;
468 }
470 if ((r = get_stat(from, &st)) < 0)
471 goto finish;
473 if (S_ISDIR(st.st_mode)) {
474 snprintf(fn, sizeof(fn), "%s/", from);
475 from = fn;
476 }
478 if (ne_move(session, 1, from, to)) {
479 fprintf(stderr, "MOVE failed: %s\n", ne_get_error(session));
480 r = -ENOENT;
481 goto finish;
482 }
484 stat_cache_invalidate(from);
485 stat_cache_invalidate(to);
487 dir_cache_invalidate_parent(from);
488 dir_cache_invalidate_parent(to);
490 finish:
492 free(_from);
494 return r;
495 }
497 static int dav_release(const char *path, __unused struct fuse_file_info *info) {
498 void *f = NULL;
499 int r = 0;
500 ne_session *session;
502 path = path_cvt(path);
504 if (debug)
505 fprintf(stderr, "release(%s)\n", path);
507 if (!(session = session_get(1))) {
508 r = -EIO;
509 goto finish;
510 }
512 if (!(f = file_cache_get(path))) {
513 fprintf(stderr, "release() called for closed file\n");
514 r = -EFAULT;
515 goto finish;
516 }
518 if (file_cache_close(f) < 0) {
519 r = -errno;
520 goto finish;
521 }
523 finish:
524 if (f)
525 file_cache_unref(f);
527 return r;
528 }
530 static int dav_fsync(const char *path, __unused int isdatasync, __unused struct fuse_file_info *info) {
531 void *f = NULL;
532 int r = 0;
533 ne_session *session;
535 path = path_cvt(path);
536 if (debug)
537 fprintf(stderr, "fsync(%s)\n", path);
539 if (!(session = session_get(1))) {
540 r = -EIO;
541 goto finish;
542 }
544 if (!(f = file_cache_get(path))) {
545 fprintf(stderr, "fsync() called for closed file\n");
546 r = -EFAULT;
547 goto finish;
548 }
550 if (file_cache_sync(f) < 0) {
551 r = -errno;
552 goto finish;
553 }
555 finish:
557 if (f)
558 file_cache_unref(f);
560 return r;
561 }
563 static int dav_mknod(const char *path, mode_t mode, __unused dev_t rdev) {
564 char tempfile[PATH_MAX];
565 int fd;
566 ne_session *session;
568 path = path_cvt(path);
569 if (debug)
570 fprintf(stderr, "mknod(%s)\n", path);
572 if (!(session = session_get(1)))
573 return -EIO;
575 if (!S_ISREG(mode))
576 return -ENOTSUP;
578 snprintf(tempfile, sizeof(tempfile), "%s/fusedav-empty-XXXXXX", "/tmp");
579 if ((fd = mkstemp(tempfile)) < 0)
580 return -errno;
582 unlink(tempfile);
584 if (ne_put(session, path, fd)) {
585 fprintf(stderr, "mknod:PUT failed: %s\n", ne_get_error(session));
586 close(fd);
587 return -EACCES;
588 }
590 close(fd);
592 stat_cache_invalidate(path);
593 dir_cache_invalidate_parent(path);
595 return 0;
596 }
598 static int dav_open(const char *path, struct fuse_file_info *info) {
599 void *f;
601 if (debug)
602 fprintf(stderr, "open(%s)\n", path);
604 path = path_cvt(path);
606 if (!(f = file_cache_open(path, info->flags)))
607 return -errno;
609 file_cache_unref(f);
611 return 0;
612 }
614 static int dav_read(const char *path, char *buf, size_t size, off_t offset, __unused struct fuse_file_info *info) {
615 void *f = NULL;
616 ssize_t r;
618 path = path_cvt(path);
620 if (debug)
621 fprintf(stderr, "read(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
623 if (!(f = file_cache_get(path))) {
624 fprintf(stderr, "read() called for closed file\n");
625 r = -EFAULT;
626 goto finish;
627 }
629 if ((r = file_cache_read(f, buf, size, offset)) < 0) {
630 r = -errno;
631 goto finish;
632 }
634 finish:
635 if (f)
636 file_cache_unref(f);
638 return r;
639 }
641 static int dav_write(const char *path, const char *buf, size_t size, off_t offset, __unused struct fuse_file_info *info) {
642 void *f = NULL;
643 ssize_t r;
645 path = path_cvt(path);
647 if (debug)
648 fprintf(stderr, "write(%s, %lu+%lu)\n", path, (unsigned long) offset, (unsigned long) size);
650 if (!(f = file_cache_get(path))) {
651 fprintf(stderr, "write() called for closed file\n");
652 r = -EFAULT;
653 goto finish;
654 }
656 if ((r = file_cache_write(f, buf, size, offset)) < 0) {
657 r = -errno;
658 goto finish;
659 }
661 finish:
662 if (f)
663 file_cache_unref(f);
665 return r;
666 }
669 static int dav_truncate(const char *path, off_t size) {
670 void *f = NULL;
671 int r = 0;
672 ne_session *session;
674 path = path_cvt(path);
676 if (debug)
677 fprintf(stderr, "truncate(%s, %lu)\n", path, (unsigned long) size);
679 if (!(session = session_get(1)))
680 r = -EIO;
681 goto finish;
683 if (!(f = file_cache_get(path))) {
684 fprintf(stderr, "truncate() called for closed file\n");
685 r = -EFAULT;
686 goto finish;
687 }
689 if (file_cache_truncate(f, size) < 0) {
690 r = -errno;
691 goto finish;
692 }
694 finish:
695 if (f)
696 file_cache_unref(f);
698 return r;
699 }
701 static int dav_utime(const char *path, struct utimbuf *buf) {
702 ne_session *session;
703 const ne_propname getlastmodified = { "DAV:", "getlastmodified" };
704 ne_proppatch_operation ops[2];
705 int r = 0;
706 char *date;
708 assert(path);
709 assert(buf);
711 path = path_cvt(path);
713 if (debug)
714 fprintf(stderr, "utime(%s, %lu, %lu)\n", path, (unsigned long) buf->actime, (unsigned long) buf->modtime);
716 ops[0].name = &getlastmodified;
717 ops[0].type = ne_propset;
718 ops[0].value = date = ne_rfc1123_date(buf->modtime);
719 ops[1].name = NULL;
721 if (!(session = session_get(1))) {
722 r = -EIO;
723 goto finish;
724 }
726 if (proppatch_with_redirect(session, path, ops)) {
727 fprintf(stderr, "PROPPATCH failed: %s\n", ne_get_error(session));
728 r = -ENOTSUP;
729 goto finish;
730 }
732 stat_cache_invalidate(path);
734 finish:
735 free(date);
737 return r;
738 }
740 static const char *fix_xattr(const char *name) {
741 assert(name);
743 if (!strcmp(name, MIME_XATTR))
744 return "user.webdav(DAV:;getcontenttype)";
746 return name;
747 }
749 struct listxattr_info {
750 char *list;
751 size_t space, size;
752 };
754 static int listxattr_iterator(
755 void *userdata,
756 const ne_propname *pname,
757 const char *value,
758 __unused const ne_status *status) {
760 struct listxattr_info *l = userdata;
761 int n;
763 assert(l);
765 if (!value || !pname)
766 return -1;
768 if (l->list) {
769 n = snprintf(l->list, l->space, "user.webdav(%s;%s)", pname->nspace, pname->name) + 1;
771 if (n >= (int) l->space) {
772 l->size += l->space;
773 l->space = 0;
774 return 1;
776 } else {
777 l->size += n;
778 l->space -= n;
780 if (l->list)
781 l->list += n;
783 return 0;
784 }
785 } else {
786 /* Calculate space */
788 l->size += strlen(pname->nspace) + strlen(pname->name) + 15;
789 return 0;
790 }
791 }
793 static void listxattr_propfind_callback(void *userdata, __unused const ne_uri *u, const ne_prop_result_set *results) {
794 struct listxattr_info *l = userdata;
795 ne_propset_iterate(results, listxattr_iterator, l);
796 }
798 static int dav_listxattr(
799 const char *path,
800 char *list,
801 size_t size) {
803 ne_session *session;
804 struct listxattr_info l;
807 assert(path);
809 path = path_cvt(path);
811 if (debug)
812 fprintf(stderr, "listxattr(%s, .., %lu)\n", path, (unsigned long) size);
814 if (list) {
815 l.list = list;
816 l.space = size-1;
817 l.size = 0;
819 if (l.space >= sizeof(MIME_XATTR)) {
820 memcpy(l.list, MIME_XATTR, sizeof(MIME_XATTR));
821 l.list += sizeof(MIME_XATTR);
822 l.space -= sizeof(MIME_XATTR);
823 l.size += sizeof(MIME_XATTR);
824 }
826 } else {
827 l.list = NULL;
828 l.space = 0;
829 l.size = sizeof(MIME_XATTR);
830 }
832 if (!(session = session_get(1)))
833 return -EIO;
835 if (simple_propfind_with_redirect(session, path, NE_DEPTH_ZERO, NULL, listxattr_propfind_callback, &l) != NE_OK) {
836 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
837 return -EIO;
838 }
840 if (l.list) {
841 assert(l.space > 0);
842 *l.list = 0;
843 }
845 return l.size+1;
846 }
848 struct getxattr_info {
849 ne_propname propname;
850 char *value;
851 size_t space, size;
852 };
854 static int getxattr_iterator(
855 void *userdata,
856 const ne_propname *pname,
857 const char *value,
858 __unused const ne_status *status) {
860 struct getxattr_info *g = userdata;
862 assert(g);
864 if (!value || !pname)
865 return -1;
867 if (strcmp(pname->nspace, g->propname.nspace) ||
868 strcmp(pname->name, g->propname.name))
869 return 0;
871 if (g->value) {
872 size_t l;
874 l = strlen(value);
876 if (l > g->space)
877 l = g->space;
879 memcpy(g->value, value, l);
880 g->size = l;
881 } else {
882 /* Calculate space */
884 g->size = strlen(value);
885 return 0;
886 }
888 return 0;
889 }
891 static void getxattr_propfind_callback(void *userdata, __unused const ne_uri *u, const ne_prop_result_set *results) {
892 struct getxattr_info *g = userdata;
893 ne_propset_iterate(results, getxattr_iterator, g);
894 }
896 static int parse_xattr(const char *name, char *dnspace, size_t dnspace_length, char *dname, size_t dname_length) {
897 char *e;
898 size_t k;
900 assert(name);
901 assert(dnspace);
902 assert(dnspace_length);
903 assert(dname);
904 assert(dname_length);
906 if (strncmp(name, "user.webdav(", 12) ||
907 name[strlen(name)-1] != ')' ||
908 !(e = strchr(name+12, ';')))
909 return -1;
911 if ((k = strcspn(name+12, ";")) > dnspace_length-1)
912 return -1;
914 memcpy(dnspace, name+12, k);
915 dnspace[k] = 0;
917 e++;
919 if ((k = strlen(e)) > dname_length-1)
920 return -1;
922 assert(k > 0);
923 k--;
925 memcpy(dname, e, k);
926 dname[k] = 0;
928 return 0;
929 }
931 static int dav_getxattr(
932 const char *path,
933 const char *name,
934 char *value,
935 size_t size) {
937 ne_session *session;
938 struct getxattr_info g;
939 ne_propname props[2];
940 char dnspace[128], dname[128];
942 assert(path);
944 path = path_cvt(path);
945 name = fix_xattr(name);
947 if (debug)
948 fprintf(stderr, "getxattr(%s, %s, .., %lu)\n", path, name, (unsigned long) size);
950 if (parse_xattr(name, dnspace, sizeof(dnspace), dname, sizeof(dname)) < 0)
951 return -ENOATTR;
953 props[0].nspace = dnspace;
954 props[0].name = dname;
955 props[1].nspace = NULL;
956 props[1].name = NULL;
958 if (value) {
959 g.value = value;
960 g.space = size;
961 g.size = (size_t) -1;
962 } else {
963 g.value = NULL;
964 g.space = 0;
965 g.size = (size_t) -1;
966 }
968 g.propname = props[0];
970 if (!(session = session_get(1)))
971 return -EIO;
973 if (simple_propfind_with_redirect(session, path, NE_DEPTH_ZERO, props, getxattr_propfind_callback, &g) != NE_OK) {
974 fprintf(stderr, "PROPFIND failed: %s\n", ne_get_error(session));
975 return -EIO;
976 }
978 if (g.size == (size_t) -1)
979 return -ENOATTR;
981 return g.size;
982 }
984 static int dav_setxattr(
985 const char *path,
986 const char *name,
987 const char *value,
988 size_t size,
989 int flags) {
991 ne_session *session;
992 ne_propname propname;
993 ne_proppatch_operation ops[2];
994 int r = 0;
995 char dnspace[128], dname[128];
996 char *value_fixed = NULL;
998 assert(path);
999 assert(name);
1000 assert(value);
1002 path = path_cvt(path);
1003 name = fix_xattr(name);
1005 if (debug)
1006 fprintf(stderr, "setxattr(%s, %s)\n", path, name);
1008 if (flags) {
1009 r = ENOTSUP;
1010 goto finish;
1011 }
1013 if (parse_xattr(name, dnspace, sizeof(dnspace), dname, sizeof(dname)) < 0) {
1014 r = -ENOATTR;
1015 goto finish;
1016 }
1018 propname.nspace = dnspace;
1019 propname.name = dname;
1021 /* Add trailing NUL byte if required */
1022 if (!memchr(value, 0, size)) {
1023 value_fixed = malloc(size+1);
1024 assert(value_fixed);
1026 memcpy(value_fixed, value, size);
1027 value_fixed[size] = 0;
1029 value = value_fixed;
1030 }
1032 ops[0].name = &propname;
1033 ops[0].type = ne_propset;
1034 ops[0].value = value;
1036 ops[1].name = NULL;
1038 if (!(session = session_get(1))) {
1039 r = -EIO;
1040 goto finish;
1041 }
1043 if (proppatch_with_redirect(session, path, ops)) {
1044 fprintf(stderr, "PROPPATCH failed: %s\n", ne_get_error(session));
1045 r = -ENOTSUP;
1046 goto finish;
1047 }
1049 stat_cache_invalidate(path);
1051 finish:
1052 free(value_fixed);
1054 return r;
1055 }
1057 static int dav_removexattr(const char *path, const char *name) {
1058 ne_session *session;
1059 ne_propname propname;
1060 ne_proppatch_operation ops[2];
1061 int r = 0;
1062 char dnspace[128], dname[128];
1064 assert(path);
1065 assert(name);
1067 path = path_cvt(path);
1068 name = fix_xattr(name);
1070 if (debug)
1071 fprintf(stderr, "removexattr(%s, %s)\n", path, name);
1073 if (parse_xattr(name, dnspace, sizeof(dnspace), dname, sizeof(dname)) < 0) {
1074 r = -ENOATTR;
1075 goto finish;
1076 }
1078 propname.nspace = dnspace;
1079 propname.name = dname;
1081 ops[0].name = &propname;
1082 ops[0].type = ne_propremove;
1083 ops[0].value = NULL;
1085 ops[1].name = NULL;
1087 if (!(session = session_get(1))) {
1088 r = -EIO;
1089 goto finish;
1090 }
1092 if (proppatch_with_redirect(session, path, ops)) {
1093 fprintf(stderr, "PROPPATCH failed: %s\n", ne_get_error(session));
1094 r = -ENOTSUP;
1095 goto finish;
1096 }
1098 stat_cache_invalidate(path);
1100 finish:
1102 return r;
1103 }
1105 static int dav_chmod(const char *path, mode_t mode) {
1106 ne_session *session;
1107 const ne_propname executable = { "http://apache.org/dav/props/", "executable" };
1108 ne_proppatch_operation ops[2];
1109 int r = 0;
1111 assert(path);
1113 path = path_cvt(path);
1115 if (debug)
1116 fprintf(stderr, "chmod(%s, %04o)\n", path, mode);
1118 ops[0].name = &executable;
1119 ops[0].type = ne_propset;
1120 ops[0].value = mode & 0111 ? "T" : "F";
1121 ops[1].name = NULL;
1123 if (!(session = session_get(1))) {
1124 r = -EIO;
1125 goto finish;
1126 }
1128 if (proppatch_with_redirect(session, path, ops)) {
1129 fprintf(stderr, "PROPPATCH failed: %s\n", ne_get_error(session));
1130 r = -ENOTSUP;
1131 goto finish;
1132 }
1134 stat_cache_invalidate(path);
1136 finish:
1138 return r;
1139 }
1141 static struct fuse_operations dav_oper = {
1142 .getattr = dav_getattr,
1143 .readdir = dav_readdir,
1144 .mknod = dav_mknod,
1145 .mkdir = dav_mkdir,
1146 .unlink = dav_unlink,
1147 .rmdir = dav_rmdir,
1148 .rename = dav_rename,
1149 .chmod = dav_chmod,
1150 .truncate = dav_truncate,
1151 .utime = dav_utime,
1152 .open = dav_open,
1153 .read = dav_read,
1154 .write = dav_write,
1155 .release = dav_release,
1156 .fsync = dav_fsync,
1157 .setxattr = dav_setxattr,
1158 .getxattr = dav_getxattr,
1159 .listxattr = dav_listxattr,
1160 .removexattr = dav_removexattr,
1161 };
1163 static void usage(char *argv0) {
1164 char *e;
1166 if ((e = strrchr(argv0, '/')))
1167 e++;
1168 else
1169 e = argv0;
1171 fprintf(stderr,
1172 "%s [-hDL] [-t SECS] [-u USERNAME] [-p PASSWORD] [-o OPTIONS] URL MOUNTPOINT\n"
1173 "\t-h Show this help\n"
1174 "\t-D Enable debug mode\n"
1175 "\t-u Username if required\n"
1176 "\t-p Password if required\n"
1177 "\t-o Additional FUSE mount options\n"
1178 "\t-L Locking the repository during mount\n"
1179 "\t-t Set lock timeout\n",
1180 e);
1181 }
1183 static void exit_handler(__unused int sig) {
1184 static const char m[] = "*** Caught signal ***\n";
1185 if(fuse != NULL)
1186 fuse_exit(fuse);
1187 write(2, m, strlen(m));
1188 }
1190 static void empty_handler(__unused int sig) {}
1192 static int setup_signal_handlers(void) {
1193 struct sigaction sa;
1194 sigset_t m;
1196 sa.sa_handler = exit_handler;
1197 sigemptyset(&(sa.sa_mask));
1198 sa.sa_flags = 0;
1200 if (sigaction(SIGHUP, &sa, NULL) == -1 ||
1201 sigaction(SIGINT, &sa, NULL) == -1 ||
1202 sigaction(SIGTERM, &sa, NULL) == -1) {
1204 fprintf(stderr, "Cannot set exit signal handlers: %s\n", strerror(errno));
1205 return -1;
1206 }
1208 sa.sa_handler = SIG_IGN;
1210 if (sigaction(SIGPIPE, &sa, NULL) == -1) {
1211 fprintf(stderr, "Cannot set ignored signals: %s\n", strerror(errno));
1212 return -1;
1213 }
1215 /* Used to shut down the locking thread */
1216 sa.sa_handler = empty_handler;
1218 if (sigaction(SIGUSR1, &sa, NULL) == -1) {
1219 fprintf(stderr, "Cannot set user signals: %s\n", strerror(errno));
1220 return -1;
1221 }
1223 sigemptyset(&m);
1224 pthread_sigmask(SIG_BLOCK, &m, &m);
1225 sigdelset(&m, SIGHUP);
1226 sigdelset(&m, SIGINT);
1227 sigdelset(&m, SIGTERM);
1228 sigaddset(&m, SIGPIPE);
1229 sigaddset(&m, SIGUSR1);
1230 pthread_sigmask(SIG_SETMASK, &m, NULL);
1232 return 0;
1233 }
1235 static int create_lock(void) {
1236 ne_session *session;
1237 char _owner[64], *owner;
1238 int i;
1239 int ret;
1241 lock = ne_lock_create();
1242 assert(lock);
1244 if (!(session = session_get(0)))
1245 return -1;
1247 if (!(owner = username))
1248 if (!(owner = getenv("USER")))
1249 if (!(owner = getenv("LOGNAME"))) {
1250 snprintf(_owner, sizeof(_owner), "%lu", (unsigned long) getuid());
1251 owner = owner;
1252 }
1254 ne_fill_server_uri(session, &lock->uri);
1256 lock->uri.path = strdup(base_directory);
1257 lock->depth = NE_DEPTH_INFINITE;
1258 lock->timeout = lock_timeout;
1259 lock->owner = strdup(owner);
1261 if (debug)
1262 fprintf(stderr, "Acquiring lock...\n");
1264 for (i = 0; i < MAX_REDIRECTS; i++) {
1265 const ne_uri *u;
1267 if ((ret = ne_lock(session, lock)) != NE_REDIRECT)
1268 break;
1270 if (!(u = ne_redirect_location(session)))
1271 break;
1273 if (!session_is_local(u))
1274 break;
1276 if (debug)
1277 fprintf(stderr, "REDIRECT FROM '%s' to '%s'\n", lock->uri.path, u->path);
1279 free(lock->uri.path);
1280 lock->uri.path = strdup(u->path);
1281 }
1283 if (ret) {
1284 fprintf(stderr, "LOCK failed: %s\n", ne_get_error(session));
1285 ne_lock_destroy(lock);
1286 lock = NULL;
1287 return -1;
1288 }
1290 lock_store = ne_lockstore_create();
1291 assert(lock_store);
1293 ne_lockstore_add(lock_store, lock);
1295 return 0;
1296 }
1298 static int remove_lock(void) {
1299 ne_session *session;
1301 assert(lock);
1303 if (!(session = session_get(0)))
1304 return -1;
1306 if (debug)
1307 fprintf(stderr, "Removing lock...\n");
1309 if (ne_unlock(session, lock)) {
1310 fprintf(stderr, "UNLOCK failed: %s\n", ne_get_error(session));
1311 return -1;
1312 }
1314 return 0;
1315 }
1317 static void *lock_thread_func(__unused void *p) {
1318 ne_session *session;
1319 sigset_t block;
1321 if (debug)
1322 fprintf(stderr, "lock_thread entering\n");
1324 if (!(session = session_get(1)))
1325 return NULL;
1327 sigemptyset(&block);
1328 sigaddset(&block, SIGUSR1);
1330 assert(lock);
1332 while (!lock_thread_exit) {
1333 int r, t;
1335 lock->timeout = lock_timeout;
1337 pthread_sigmask(SIG_BLOCK, &block, NULL);
1338 r = ne_lock_refresh(session, lock);
1339 pthread_sigmask(SIG_UNBLOCK, &block, NULL);
1341 if (r) {
1342 fprintf(stderr, "LOCK refresh failed: %s\n", ne_get_error(session));
1343 break;
1344 }
1346 if (lock_thread_exit)
1347 break;
1349 t = lock_timeout/2;
1350 if (t <= 0)
1351 t = 1;
1352 sleep(t);
1353 }
1355 if (debug)
1356 fprintf(stderr, "lock_thread exiting\n");
1358 return NULL;
1359 }
1361 int main(int argc, char *argv[]) {
1362 int c;
1363 char *u = NULL, *p = NULL, *o = NULL;
1364 int fuse_fd = -1;
1365 int ret = 1;
1366 char mountpoint[PATH_MAX];
1367 pthread_t lock_thread;
1368 int lock_thread_running = 0;
1369 int enable_locking = 0;
1371 static char *mount_args_strings[] = {
1372 NULL, /* path*/
1373 NULL, /* -o */
1374 NULL,
1375 NULL};
1377 struct fuse_args mount_args = {
1378 .argc = 1,
1379 .argv = mount_args_strings,
1380 .allocated = 0
1381 };
1383 if (ne_sock_init()) {
1384 fprintf(stderr, "Failed to initialize libneon.\n");
1385 goto finish;
1386 }
1388 if (!ne_has_support(NE_FEATURE_SSL) ||
1389 !ne_has_support(NE_FEATURE_TS_SSL) ||
1390 !ne_has_support(NE_FEATURE_LFS)) {
1391 fprintf(stderr, "fusedav requires libneon built with SSL, SSL thread safety and LFS enabled.\n");
1392 goto finish;
1393 }
1395 mask = umask(0);
1396 umask(mask);
1398 cache_alloc();
1400 if (setup_signal_handlers() < 0)
1401 goto finish;
1403 while ((c = getopt(argc, argv, "hu:p:Do:Lt:")) != -1) {
1405 switch(c) {
1406 case 'u':
1407 u = optarg;
1408 break;
1410 case 'p':
1411 p = optarg;
1412 break;
1414 case 'D':
1415 debug = !debug;
1416 break;
1418 case 'o':
1419 o = optarg;
1420 break;
1422 case 'L':
1423 enable_locking = 1;
1424 break;
1426 case 't':
1427 if ((lock_timeout = atoi(optarg)) < 0) {
1428 fprintf(stderr, "Invalid lock timeout '%s'\n", optarg);
1429 goto finish;
1430 }
1431 break;
1433 case 'h':
1434 ret = 0;
1436 /* fall through */
1437 default:
1438 usage(argv[0]);
1439 goto finish;
1440 }
1441 }
1443 if (optind != argc-2) {
1444 usage(argv[0]);
1445 goto finish;
1446 }
1448 if (session_set_uri(argv[optind], u, p) < 0) {
1449 usage(argv[0]);
1450 goto finish;
1451 }
1453 if (argv[optind+1][0] == '/')
1454 snprintf(mountpoint, sizeof(mountpoint), "%s", argv[optind+1]);
1455 else {
1456 char *pwd = get_current_dir_name();
1457 snprintf(mountpoint, sizeof(mountpoint), "%s/%s", pwd, argv[optind+1]);
1458 free(pwd);
1459 }
1461 mount_args_strings[0] = argv[optind];
1463 if (o) {
1464 mount_args_strings[1] = (char*) "-o";
1465 mount_args_strings[2] = o;
1466 mount_args.argc += 2;
1467 }
1469 if ((fuse_fd = fuse_mount(mountpoint, &mount_args)) < 0) {
1470 fprintf(stderr, "Failed to mount FUSE file system.\n");
1471 goto finish;
1472 }
1474 if (!(fuse = fuse_new(fuse_fd, &mount_args, &dav_oper, sizeof(dav_oper)))) {
1475 fprintf(stderr, "Failed to create FUSE object.\n");
1476 goto finish;
1477 }
1479 if (enable_locking && create_lock() >= 0) {
1480 int r;
1481 if ((r = pthread_create(&lock_thread, NULL, lock_thread_func, NULL)) < 0) {
1482 fprintf(stderr, "pthread_create(): %s\n", strerror(r));
1483 goto finish;
1484 }
1486 lock_thread_running = 1;
1487 }
1489 fuse_loop_mt(fuse);
1491 if (debug)
1492 fprintf(stderr, "Exiting cleanly.\n");
1494 ret = 0;
1496 finish:
1498 if (lock_thread_running) {
1499 lock_thread_exit = 1;
1500 pthread_kill(lock_thread, SIGUSR1);
1501 pthread_join(lock_thread, NULL);
1502 remove_lock();
1503 ne_lockstore_destroy(lock_store);
1504 }
1506 if (fuse)
1507 fuse_destroy(fuse);
1509 if (fuse_fd >= 0)
1510 fuse_unmount(mountpoint);
1512 file_cache_close_all();
1513 cache_free();
1514 session_free();
1516 return ret;
1517 }