agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs

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 }