agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafs| Log | Files | Refs | README | LICENSE | Mail | Website |
main.c (21139B)
1 #define FUSE_USE_VERSION 31
2
3 #include "fuse_node.h"
4 #include "fuse_node_store.h"
5 #include "ical_extra.h"
6 #include "arena.h"
7 #include "tree.h"
8 #include "util.h"
9 #include <dirent.h>
10 #include <errno.h>
11 #include <fuse3/fuse.h>
12 #include <fuse3/fuse_lowlevel.h>
13 #include <fuse3/fuse_opt.h>
14 #include <libical/ical.h>
15 #include <pthread.h>
16 #include <regex.h>
17 #include <stdbool.h>
18 #include <stddef.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/inotify.h>
23 #include <sys/stat.h>
24 #include <time.h>
25 #include <unistd.h>
26 #include <uuid/uuid.h>
27 #include <wordexp.h>
28
29 // Listening to file changes of the original content
30 #define EVENT_BUF_LEN (1024 * (sizeof(struct inotify_event) + 16))
31
32 // Between the inotify event listener and the fuse filesystem
33 static pthread_rwlock_t entries_lock = PTHREAD_RWLOCK_INITIALIZER;
34
35 #define FUSE_WRITE_BEGIN \
36 LOG("%s", __func__); \
37 pthread_rwlock_wrlock(&entries_lock); \
38 arena *ar = create_arena(); \
39 int status = 0; \
40 if (!ar) { \
41 pthread_rwlock_unlock(&entries_lock); \
42 return -ENOMEM; \
43 }
44
45 #define FUSE_READ_BEGIN \
46 LOG("%s", __func__); \
47 pthread_rwlock_rdlock(&entries_lock); \
48 arena *ar = create_arena(); \
49 int status = 0; \
50 if (!ar) { \
51 pthread_rwlock_unlock(&entries_lock); \
52 return -ENOMEM; \
53 }
54
55 #define FUSE_CLEANUP \
56 pthread_rwlock_unlock(&entries_lock); \
57 free_all(ar);
58
59 static int
60 fuse_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
61 {
62 FUSE_READ_BEGIN;
63 LOG("%s", path);
64
65 memset(stbuf, 0, sizeof(struct stat));
66
67 if (strcmp(path, "/") == 0) {
68 stbuf->st_mode = S_IFDIR | 0444;
69 stbuf->st_nlink = 2;
70
71 // TODO: Probably should be set with an option?
72 stbuf->st_uid = getuid();
73 stbuf->st_gid = getgid();
74 stbuf->st_ino = 0;
75 goto cleanup_return;
76 }
77
78 const struct tree_node *node = get_node_by_path(ar, path);
79 if (!node) {
80 LOG("Entry not found");
81 status = -ENOENT;
82 goto cleanup_return;
83 }
84
85 icalcomponent *ic = get_icalcomponent_from_node(ar, node);
86
87 struct stat st = get_node_stat(ar, node, ic);
88
89 *stbuf = st;
90
91 stbuf->st_atime = time(NULL);
92
93 cleanup_return:
94 FUSE_CLEANUP;
95 return status;
96 }
97
98 static int
99 fuse_readlink(const char *path, char *buf, size_t size)
100 {
101 // TODO: Implement
102 LOG("READLINK path: %s", path);
103 return -ENOENT;
104 }
105
106 static int
107 fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
108 struct fuse_file_info *fi, enum fuse_readdir_flags flags)
109 {
110
111 FUSE_READ_BEGIN;
112 LOG("%s", path);
113
114 filler(buf, ".", NULL, 0, 0);
115 filler(buf, "..", NULL, 0, 0);
116
117 const struct tree_node *node = get_node_by_path(ar, path);
118 if (!node) {
119 status = -ENOENT;
120 goto cleanup_return;
121 }
122
123 for (size_t i = 0; i < node->child_count; i++) {
124 const struct tree_node *child = node->children[i];
125 icalcomponent *ic = get_icalcomponent_from_node(ar, child);
126 assert(ic);
127
128 struct stat st = get_node_stat(ar, child, ic);
129
130 if (filler(buf, get_node_filename(child), &st, 0, 0) != 0) {
131 status = -ENOMEM;
132 goto cleanup_return;
133 }
134 }
135
136 cleanup_return:
137 FUSE_CLEANUP;
138 return status;
139 }
140
141 static int
142 fuse_open(const char *path, struct fuse_file_info *fi)
143 {
144 FUSE_READ_BEGIN;
145 LOG("%s", path);
146 const struct tree_node *node = get_node_by_path(ar, path);
147 if (!node) {
148 status = -ENOENT;
149 }
150
151 FUSE_CLEANUP;
152 return status;
153 }
154
155 static int
156 fuse_mkdir(const char *filepath, mode_t mode)
157 {
158 FUSE_WRITE_BEGIN;
159
160 // Reserved
161 if (pathIsHidden(filepath)) {
162 status = -EPERM;
163 goto cleanup_return;
164 }
165
166 struct tree_node *node = get_node_by_path(ar, filepath);
167 if (node) {
168 status = -EEXIST;
169 goto cleanup_return;
170 }
171
172 status = create_entry_from_fuse(ar, filepath, ENTRY_DIRECTORY);
173
174 cleanup_return:
175 FUSE_CLEANUP;
176 return status;
177 }
178
179 static int
180 fuse_read(const char *path, char *buf, size_t size, off_t offset,
181 struct fuse_file_info *fi)
182 {
183 FUSE_READ_BEGIN;
184
185 LOG("READ: offset=%ld, size=%zu", offset, size);
186
187 const struct tree_node *n = get_node_by_path(ar, path);
188 if (!n) {
189 status = -ENOENT;
190 goto cleanup_return;
191 }
192 icalcomponent *ic = get_icalcomponent_from_node(ar, n);
193 if (!ic) {
194 status = -EIO;
195 goto cleanup_return;
196 }
197
198 const char *description = icalcomponent_get_description(ic);
199 if (!description) {
200 goto cleanup_return;
201 }
202
203 size_t content_len = strlen(description);
204
205 if (offset >= content_len) {
206 goto cleanup_return;
207 }
208
209 size_t bytes_to_read = content_len - offset;
210 if (bytes_to_read > size) {
211 bytes_to_read = size;
212 }
213 if (bytes_to_read > 0) {
214 memcpy(buf, description + offset, bytes_to_read);
215 }
216
217 status = bytes_to_read;
218
219 cleanup_return:
220 FUSE_CLEANUP;
221 return status;
222 }
223
224 static int
225 fuse_write(const char *path, const char *buf, size_t size, off_t offset,
226 struct fuse_file_info *fi)
227 {
228
229 FUSE_WRITE_BEGIN;
230 LOG("%s, %zu, %zu", buf, size, offset);
231
232 if (pathIsHidden(path)) {
233 status = -EPERM;
234 goto cleanup_return;
235 }
236
237 const struct tree_node *node = get_node_by_path(ar, path);
238 if (!node) {
239 status = -ENONET;
240 goto cleanup_return;
241 }
242
243 icalcomponent *ic = get_icalcomponent_from_node(ar, node);
244
245 // It possible to write to "/". The rest have an icalcomponent
246 assert(ic);
247
248 icalcomponent_insert_description(ar, ic, buf, size, offset);
249
250 status = write_ical_file(ar, node, ic) >= 0 ? size : status;
251
252 cleanup_return:
253 FUSE_CLEANUP;
254 return status;
255 }
256
257 // Aka remove or delete
258 static int
259 fuse_unlink(const char *file)
260 {
261 FUSE_WRITE_BEGIN;
262 LOG("%s", file);
263
264 struct tree_node *node = get_node_by_path(ar, file);
265 if (!node) {
266 status = -ENOENT;
267 goto cleanup_return;
268 }
269
270 icalcomponent *node_ics = get_icalcomponent_from_node(ar, node);
271 if (node_is_directory(ar, node, node_ics)) {
272 status = -EISDIR;
273 goto cleanup_return;
274 }
275
276 status = delete_vdir_entry(ar, node);
277
278 cleanup_return:
279 FUSE_CLEANUP;
280 return status;
281 }
282
283 static int
284 fuse_rmdir(const char *filepath)
285 {
286 FUSE_WRITE_BEGIN;
287 LOG("%s", filepath);
288
289 struct tree_node *node = get_node_by_path(ar, filepath);
290 if (!node) {
291 status = -ENOENT;
292 goto cleanup_return;
293 }
294
295 icalcomponent *node_ics = get_icalcomponent_from_node(ar, node);
296 if (!node_is_directory(ar, node, node_ics)) {
297 status = -ENOTDIR;
298 goto cleanup_return;
299 }
300
301 if (node->child_count > 0) {
302 status = -ENOTEMPTY;
303 goto cleanup_return;
304 }
305
306 status = delete_vdir_entry(ar, node);
307
308 cleanup_return:
309 FUSE_CLEANUP;
310 return status;
311 }
312
313 static int
314 fuse_rename(const char *old, const char *new, unsigned int flags)
315 {
316
317 FUSE_WRITE_BEGIN;
318 LOG("%s -> %s", old, new);
319
320 struct tree_node *existing_node = get_node_by_path(ar, new);
321 if (existing_node) {
322 if (node_has_children(existing_node)) {
323 status = -ENOTEMPTY;
324 goto cleanup_return;
325 }
326 if (delete_vdir_entry(ar, existing_node) != 0) {
327 status = -EIO;
328 goto cleanup_return;
329 }
330 }
331
332 status = do_agenda_rename(ar, old, new);
333
334 cleanup_return:
335 FUSE_CLEANUP;
336 return status;
337 }
338
339 static int
340 fuse_create(const char *filepath, mode_t mode, struct fuse_file_info *info)
341 {
342
343 FUSE_WRITE_BEGIN;
344 LOG("%s", filepath);
345
346 // Reserved
347 if (pathIsHidden(filepath)) {
348 status = -EPERM;
349 goto cleanup_return;
350 }
351
352 struct tree_node *node = get_node_by_path(ar, filepath);
353 if (node) {
354 status = -EEXIST;
355 goto cleanup_return;
356 }
357
358 // TODO: Handle mode and fuse_file_info
359 status = create_entry_from_fuse(ar, filepath, ENTRY_FILE);
360
361 cleanup_return:
362 FUSE_CLEANUP;
363 return status;
364 }
365
366 static int
367 fuse_removexattr(const char *path, const char *attribute)
368 {
369 FUSE_WRITE_BEGIN;
370 LOG("'%s' '%s'", path, attribute);
371
372 struct tree_node *node = get_node_by_path(ar, path);
373 if (!node) {
374 status = -ENONET;
375 goto cleanup_return;
376 }
377
378 if (strcmp(attribute, "user.categories") == 0) {
379 status = set_node_categories(ar, node, "", 0);
380 goto cleanup_return;
381 }
382 else if (strcmp(attribute, "user.class") == 0) {
383 status = delete_node_class(ar, node);
384 goto cleanup_return;
385 }
386
387 // It should probably always be set
388 else if (strcmp(attribute, "user.status") == 0) {
389 status = -EPERM;
390 goto cleanup_return;
391 }
392 if (strcmp(attribute, "user.dtstart") == 0) {
393 status = clear_dtstart(ar, node);
394 goto cleanup_return;
395 }
396 else if (starts_with_str(attribute, "user.")) {
397 icalcomponent *ic = get_icalcomponent_from_node(ar, node);
398
399 const char *key = attribute + 5;
400 icalcomponent_remove_custom_prop(ar, ic, key);
401 status = write_ical_file(ar, node, ic);
402 goto cleanup_return;
403 }
404 else {
405 status = -EINVAL;
406 goto cleanup_return;
407 }
408
409 cleanup_return:
410 FUSE_CLEANUP;
411 return status;
412 }
413
414 static int
415 fuse_setxattr(const char *path, const char *attribute, const char *value,
416 size_t s, int flags)
417 {
418
419 FUSE_WRITE_BEGIN;
420 LOG("'%s' '%s' '%zu' '%s' '%d'", path, attribute, s, value, flags);
421
422 // Limit documented in xattr(7)
423 if (strlen(attribute) >= 256) {
424 status = -EMSGSIZE;
425 goto cleanup_return;
426 }
427
428 // Limit documented in xattr(7)
429 if (strlen(value) >= 64 * 1024) {
430 status = -EMSGSIZE;
431 goto cleanup_return;
432 }
433
434 struct tree_node *node = get_node_by_path(ar, path);
435 if (!node) {
436 status = -ENONET;
437 goto cleanup_return;
438 }
439
440 // uid is set by the ics file and is immutable
441 if (strcmp(attribute, "user.uid") == 0) {
442 status = -EPERM;
443 goto cleanup_return;
444 }
445
446 // user.sibling is reserved but not implemented
447 else if (strcmp(attribute, "user.sibling") == 0) {
448 status = -EPERM;
449 goto cleanup_return;
450 }
451
452 else if (strcmp(attribute, "user.dtstart") == 0) {
453 if (strcmp(value, "") == 0) {
454 status = clear_dtstart(ar, node);
455 }
456 else {
457 status = set_dtstart(ar, value, node);
458 }
459 goto cleanup_return;
460 }
461
462 else if (strcmp(attribute, "user.categories") == 0) {
463 LOG("Updating categories");
464 status = set_node_categories(ar, node, value, s);
465 goto cleanup_return;
466 }
467
468 else if (strcmp(attribute, "user.class") == 0) {
469 LOG("Updating class");
470 enum icalproperty_class iclass = parse_ical_class(value);
471
472 if (iclass == ICAL_CLASS_NONE) {
473 LOG("INVALID CLASS %s", value);
474 status = -EINVAL;
475 goto cleanup_return;
476 }
477
478 status = set_node_class(ar, node, iclass);
479 goto cleanup_return;
480 }
481 else if (strcmp(attribute, "user.status") == 0) {
482 LOG("Updating status");
483 enum icalproperty_status istatus = parse_ical_status(value);
484
485 if (istatus == ICAL_STATUS_NONE) {
486 LOG("INVALID STATUS");
487 status = -EINVAL;
488 goto cleanup_return;
489 }
490
491 status = set_node_status(ar, node, istatus);
492 goto cleanup_return;
493 }
494 else if (starts_with_str(attribute, "user.")) {
495 icalcomponent *ic = get_icalcomponent_from_node(ar, node);
496
497 const char *rkey = attribute + 5;
498 LOG("Updating %s", rkey);
499
500 icalcomponent_set_custom_x_value(ar, ic, rkey, value);
501 status = write_ical_file(ar, node, ic);
502 goto cleanup_return;
503 }
504 else {
505 status = -EINVAL;
506 goto cleanup_return;
507 }
508
509 cleanup_return:
510 FUSE_CLEANUP;
511 return status;
512 }
513
514 static int
515 fuse_listxattr(const char *path, char *list, size_t size)
516 {
517 FUSE_READ_BEGIN;
518 LOG("%s %zu", list, size);
519
520 FILE *stream;
521 char *xattribute_list = NULL;
522 size_t xattr_list_len = 0;
523
524 struct tree_node *node = get_node_by_path(ar, path);
525 if (!node) {
526 status = -ENONET;
527 goto cleanup_return_2;
528 }
529
530 if (is_root_node(node)) {
531 status = -EPERM;
532 goto cleanup_return_2;
533 }
534
535 icalcomponent *comp = get_icalcomponent_from_node(ar, node);
536 assert(comp);
537
538 stream = open_memstream(&xattribute_list, &xattr_list_len);
539 if (stream == NULL) {
540 status = -EIO;
541 goto cleanup_return_2;
542 }
543
544 char *cats = get_node_categories(ar, node);
545 if (cats != NULL && strcmp("", cats) != 0) {
546 fprintf(stream, "user.categories");
547 fputc('\0', stream);
548 }
549
550 const char *dtstart = get_dtstart(ar, node);
551 if (dtstart != NULL) {
552 fprintf(stream, "user.dtstart");
553 fputc('\0', stream);
554 }
555
556 // All notes have a uid
557 fprintf(stream, "user.uid");
558 fputc('\0', stream);
559
560 LOG("Checking node class");
561 const char *iclass = get_node_class(ar, node);
562 if (iclass != NULL && strcmp("", iclass) != 0) {
563 fprintf(stream, "user.class");
564 fputc('\0', stream);
565 }
566
567 LOG("Checking node status");
568 const char *istatus = get_node_status(ar, node);
569 if (istatus != NULL && strcmp("", istatus) != 0) {
570 fprintf(stream, "user.status");
571 fputc('\0', stream);
572 }
573
574 icalcomponent_print_x_props(stream, comp);
575
576 if (fclose(stream) != 0) {
577 status = -EIO;
578 goto cleanup_return;
579 }
580
581 if (size > 0) {
582 if (xattr_list_len > size) {
583 memcpy(list, xattribute_list, xattr_list_len);
584 }
585 else {
586 memcpy(list, xattribute_list, size);
587 }
588 }
589 status = xattr_list_len;
590
591 cleanup_return:
592 free(xattribute_list);
593 cleanup_return_2:
594 FUSE_CLEANUP;
595 return status;
596 }
597
598 static int
599 fuse_getxattr(const char *path, const char *attribute, char *buf, size_t s)
600 {
601 FUSE_READ_BEGIN;
602 LOG("'%s' '%s' '%zu'\n", path, attribute, s);
603
604 struct tree_node *node = get_node_by_path(ar, path);
605 if (!node) {
606 status = -ENONET;
607 goto cleanup_return;
608 }
609
610 if (strcmp(attribute, "user.categories") == 0) {
611
612 const char *cats = get_node_categories(ar, node);
613
614 if (!cats || *cats == '\0') {
615 status = -ENODATA;
616 goto cleanup_return;
617 }
618
619 int string_len = strlen(cats);
620
621 // If s == 0 we don't need to write, just return size to
622 // allocate
623 if (s > 0) {
624 snprintf(buf, s, "%s", cats);
625 }
626 status = string_len;
627 goto cleanup_return;
628 }
629 if (strcmp(attribute, "user.uid") == 0) {
630 icalcomponent *ic = get_icalcomponent_from_node(ar, node);
631 const char *uid = icalcomponent_get_uid(ic);
632 assert(uid);
633
634 int string_len = strlen(uid);
635
636 // If s == 0 we don't need to write, just return size to
637 // allocate
638 if (s > 0) {
639 snprintf(buf, s, "%s", uid);
640 }
641
642 status = string_len;
643 goto cleanup_return;
644 }
645 else if (strcmp(attribute, "user.class") == 0) {
646 const char *c = get_node_class(ar, node);
647 if (!c || *c == '\0') {
648 status = -ENODATA;
649 goto cleanup_return;
650 }
651
652 int string_len = strlen(c);
653
654 if (s > 0) {
655 snprintf(buf, s, "%s", c);
656 }
657
658 status = string_len;
659 goto cleanup_return;
660 }
661 else if (strcmp(attribute, "user.status") == 0) {
662 const char *c = get_node_status(ar, node);
663 if (!c || *c == '\0') {
664 status = -ENODATA;
665 goto cleanup_return;
666 }
667
668 int string_len = strlen(c);
669
670 if (s > 0) {
671 snprintf(buf, s, "%s", c);
672 }
673
674 status = string_len;
675 goto cleanup_return;
676 }
677 else if (strcmp(attribute, "user.dtstart") == 0) {
678 const char *res = get_dtstart(ar, node);
679 if (!res) {
680 status = -ENODATA;
681 goto cleanup_return;
682 }
683
684 int string_len = strlen(res);
685
686 // If s == 0 we don't need to write, just return size to
687 // allocate
688 if (s > 0) {
689 snprintf(buf, s, "%s", res);
690 }
691
692 status = string_len;
693 goto cleanup_return;
694 }
695 else if (starts_with_str(attribute, "user.")) {
696 icalcomponent *ic = get_icalcomponent_from_node(ar, node);
697
698 const char *key = attribute + 5;
699 const char *value =
700 icalcomponent_get_custom_x_value(ar, ic, key);
701 if (!value) {
702 status = -ENODATA;
703 goto cleanup_return;
704 }
705
706 int string_len = strlen(value);
707
708 // If s == 0 we don't need to write, just return size to
709 // allocate
710 if (s > 0) {
711 snprintf(buf, s, "%s", value);
712 }
713
714 status = string_len;
715 goto cleanup_return;
716 }
717 else {
718 status = -ENODATA;
719 goto cleanup_return;
720 }
721
722 cleanup_return:
723 FUSE_CLEANUP;
724 return status;
725 }
726
727 static void
728 handle_vdir_event(struct inotify_event *event)
729 {
730 if (!event->len || !strstr(event->name, ".ics"))
731 return;
732
733 arena *ar = create_arena();
734 pthread_rwlock_wrlock(&entries_lock);
735
736 LOG("Detected change in ICS file: %s\n", event->name);
737
738 char *full_path = NULL;
739 rasprintf(ar, &full_path, "%s/%s", VDIR, event->name);
740
741 if (event->mask & IN_DELETE) {
742 LOG("IN_DELETE HOOK");
743 delete_from_vdir_path(ar, full_path);
744 }
745 else if (event->mask & IN_MODIFY) {
746 LOG("IN_MODIFY HOOK");
747 update_or_create_fuse_entry_from_vdir(ar, full_path);
748 }
749 else if (event->mask & IN_CREATE) {
750 LOG("IN_CREATE HOOK");
751 update_or_create_fuse_entry_from_vdir(ar, full_path);
752 }
753
754 pthread_rwlock_unlock(&entries_lock);
755 free_all(ar);
756 }
757
758 static void *
759 watch_vdir_changes(void *arg)
760 {
761 int fd = inotify_init1(IN_NONBLOCK);
762 assert(fd >= 0);
763
764 int wd = inotify_add_watch(fd, VDIR, IN_CREATE | IN_MODIFY | IN_DELETE);
765 assert(wd >= 0);
766
767 char buffer[EVENT_BUF_LEN];
768
769 while (1) {
770 ssize_t length = read(fd, buffer, EVENT_BUF_LEN);
771
772 if (length < 0) {
773 // No event, sleep and check again
774 assert(errno == EAGAIN || errno == EINTR);
775 usleep(100000);
776 continue;
777 }
778
779 size_t i = 0;
780 while (i < length) {
781 struct inotify_event *event =
782 (struct inotify_event *)&buffer[i];
783 handle_vdir_event(event);
784 size_t event_size =
785 sizeof(struct inotify_event) + event->len;
786
787 i += event_size;
788 }
789 }
790
791 inotify_rm_watch(fd, wd);
792 close(fd);
793 return NULL;
794 }
795
796 // Ignore for now
797 static int
798 fuse_utimens(const char *path, const struct timespec tv[2],
799 struct fuse_file_info *info)
800 {
801 return 0;
802 }
803
804 struct agendafs_config {
805 char *ics_directory;
806 char *default_file_extension;
807 };
808 enum {
809 KEY_HELP,
810 KEY_VERSION,
811 };
812
813 #define CUSTOMFS_OPT(t, p, v) {t, offsetof(struct agendafs_config, p), v}
814
815 static struct fuse_opt agendafs_opts[] = {
816 CUSTOMFS_OPT("ext=%s", default_file_extension, 0),
817 CUSTOMFS_OPT("vdir=%s", ics_directory, 0),
818 FUSE_OPT_KEY("-V", KEY_VERSION),
819 FUSE_OPT_KEY("--version", KEY_VERSION),
820 FUSE_OPT_KEY("-h", KEY_HELP),
821 FUSE_OPT_KEY("--help", KEY_HELP),
822 FUSE_OPT_END};
823
824 size_t
825 load_agendafs_environment(char *vdir_env)
826 {
827 wordexp_t expanded;
828 if (wordexp(vdir_env, &expanded, 0) != 0 || expanded.we_wordc == 0) {
829 fprintf(stderr, "Failed to expand CALDAVFS_ICS_DIR\n");
830 wordfree(&expanded);
831 return -1;
832 }
833
834 char *expanded_path = expanded.we_wordv[0];
835
836 struct stat s;
837 if (stat(expanded_path, &s) == 0 && S_ISDIR(s.st_mode)) {
838 assert(strlen(expanded_path) < 256);
839 set_vdir(expanded_path);
840 wordfree(&expanded);
841 return 0;
842 }
843
844 fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
845 expanded_path);
846 return -1;
847 }
848
849 struct fuse_operations fuse_oper = {.getattr = fuse_getattr,
850 .readdir = fuse_readdir,
851 .open = fuse_open,
852 .read = fuse_read,
853 .utimens = fuse_utimens,
854 .write = fuse_write,
855 .create = fuse_create,
856 .mkdir = fuse_mkdir,
857 .unlink = fuse_unlink,
858 .getxattr = fuse_getxattr,
859 .setxattr = fuse_setxattr,
860 .listxattr = fuse_listxattr,
861 .removexattr = fuse_removexattr,
862 .rmdir = fuse_rmdir,
863 .readlink = fuse_readlink,
864 .rename = fuse_rename};
865
866 static int
867 agendafs_opt_proc(void *data, const char *arg, int key,
868 struct fuse_args *outargs)
869 {
870 switch (key) {
871 case KEY_HELP:
872 fprintf(stderr,
873 "usage: %s mountpoint [options]\n"
874 "\n"
875 "agendafs options:\n"
876 " -o vdir=STRING\n",
877 outargs->argv[0]);
878 fuse_opt_add_arg(outargs, "-h");
879 fuse_main(outargs->argc, outargs->argv, &fuse_oper, NULL);
880 exit(1);
881
882 case KEY_VERSION:
883 fprintf(stderr, "agendafs version %s\n", "v0.01 ALPHA");
884 fuse_opt_add_arg(outargs, "--version");
885 fuse_main(outargs->argc, outargs->argv, &fuse_oper, NULL);
886 exit(0);
887 }
888 return 1;
889 }
890
891 int
892 main(int argc, char *argv[])
893 {
894 pthread_t watcher_thread;
895
896 tzset();
897
898 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
899 struct agendafs_config conf = {0};
900
901 fuse_opt_parse(&args, &conf, agendafs_opts, agendafs_opt_proc);
902
903 if (!conf.ics_directory) {
904 fprintf(stderr,
905 "usage: %s [options] mountpoint\n"
906 "\n"
907 "agendafs options:\n"
908 " -o vdir=STRING\n\n",
909 argv[0]);
910 fuse_opt_add_arg(&args, "-h");
911 fuse_main(args.argc, args.argv, &fuse_oper, NULL);
912 exit(1);
913 }
914
915 if (conf.default_file_extension) {
916 LOG("DEFAULT FILE EXTENSION SET");
917 int res = set_file_extension(conf.default_file_extension);
918 if (res == -1) {
919 fprintf(stderr,
920 "File extension should be without dot\n");
921 exit(1);
922 }
923
924 if (res == -2) {
925 fprintf(stderr, "File extension can not be more than "
926 "256 characters\n");
927 exit(1);
928 }
929 }
930 else {
931 LOG("DEFAULT FILE EXTENSION NOT SET. DEFAULTING TO .txt");
932 }
933
934 #ifdef DEBUG
935 fuse_opt_add_arg(&args, "-f");
936 #endif
937
938 if (load_agendafs_environment(conf.ics_directory) != 0) {
939 fprintf(stderr, "Failed to load ICS directory\n");
940 exit(1);
941 }
942
943 LOG("LOADED ICS DIR");
944
945 load_root_node_tree();
946
947 if (pthread_create(&watcher_thread, NULL, watch_vdir_changes, NULL) !=
948 0) {
949 perror("Failed to create inotify watcher thread");
950 return 1;
951 }
952
953 int ret = fuse_main(args.argc, args.argv, &fuse_oper, NULL);
954 LOG("Cleaning up");
955
956 pthread_cancel(watcher_thread);
957 pthread_join(watcher_thread, NULL);
958 LOG("Pthread freed");
959
960 hashmap_free(entries_vdir);
961 free_tree(fuse_root);
962 LOG("Hashmap and tree freed");
963
964 exit(ret);
965 }