agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit e84a32c91a12240201572648bcdef3f7a3da189e
parent ca205e64d4091ba65db76fdc7200ed25ba9f8399
Author: Marc Coquand <marc@coquand.email>
Date:   Wed, 16 Jul 2025 11:49:23 +0200

Parse files on demand instead of keeping everything in memory

This change refactors agendafs to use a memory_region structure
for everything that is temporary, and copy over data that is written
to global state.

Implements: https://todo.sr.ht/~marcc/agendafs/6

Diffstat:
MMakefile | 4++--
Aagenda_entry.c | 1228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aagenda_entry.h | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhashmap.c | 12++++++++++++
Mhashmap.h | 30+++++++++++++++++++++---------
Djournal_entry.c | 1333-------------------------------------------------------------------------------
Djournal_entry.h | 190-------------------------------------------------------------------------------
Mmain.c | 204+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Amregion.c | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amregion.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpath.c | 47++++++++++++++++-------------------------------
Mpath.h | 23+++++++++++++----------
Mtree.c | 19+++++++++++++++++--
Mtree.h | 4++++
Mutil.c | 5++++-
15 files changed, 1922 insertions(+), 1656 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,10 +5,10 @@ LIBS = `pkg-config uuid fuse3 libical --cflags --libs`
 
 all: agendafs
 
-agendafs: main.c path.c util.c hashmap.c journal_entry.c tree.c
+agendafs: main.c path.c util.c hashmap.c agenda_entry.c tree.c
 	$(CC) $(CFLAGS) *.c -o mount_agendafs $(LIBS)
 
-debug: main.c path.c util.c hashmap.c journal_entry.c tree.c
+debug: main.c path.c util.c hashmap.c agenda_entry.c tree.c
 	$(CC) $(CFLAGS_DEBUG) $(DEBUG_FLAGS) *.c -o mount_agendafs-debug $(LIBS)
 
 clean:
diff --git a/agenda_entry.c b/agenda_entry.c
@@ -0,0 +1,1228 @@
+#include "agenda_entry.h"
+#include "hashmap.h"
+#include "mregion.h"
+#include "path.h"
+#include "tree.h"
+#include "util.h"
+#include <dirent.h>
+#include <libical/ical.h>
+#include <pthread.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+#include <wordexp.h>
+
+const char *IS_DIRECTORY_PROPERTY = "X-CALDAVFS-ISDIRECTORY";
+char ICS_DIR[256];
+
+// A structure of the current root
+struct tree_node *fuse_root = NULL;
+// ics entries, keys are the filename as stored in ICS_DIR
+// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
+struct hashmap *entries_original_ics = NULL;
+
+struct agenda_entry *
+copy_agenda_entry(const struct agenda_entry *src)
+{
+	// Root node
+	if (!src) {
+		return NULL;
+	}
+	struct agenda_entry *copy = xmalloc(sizeof(struct agenda_entry));
+	copy->filename = xstrdup(src->filename);
+	copy->filename_original = xstrdup(src->filename_original);
+	copy->gid = src->gid;
+	copy->uid = src->uid;
+	return copy;
+}
+
+void
+free_agenda_entry(struct agenda_entry *entry)
+{
+	if (!entry)
+		return;
+	if (entry->filename)
+		free(entry->filename);
+	if (entry->filename_original)
+		free(entry->filename_original);
+	free(entry);
+	return;
+}
+
+struct tree_node *
+get_node_by_uuid(memory_region *mreg, const char *target_uuid)
+{
+	char *filename_original = NULL;
+
+	// Filenames are uid.ics, so we levarage that
+	size_t wRes =
+	    rasprintf(mreg, &filename_original, "%s.ics", target_uuid);
+	assert(wRes != -1);
+
+	LOG("Looking for UID: '%s'", target_uuid);
+	LOG("Full filename: '%s'", filename_original);
+
+	struct tree_node *node =
+	    hashmap_get(entries_original_ics, filename_original);
+	LOG("Found it: %b", node != NULL);
+
+	return node;
+}
+
+char *
+get_node_filename(memory_region *mreg, const struct tree_node *node)
+{
+	struct agenda_entry *entry = node->data;
+	if (entry == NULL)
+		return "";
+	return rstrdup(mreg, entry->filename);
+}
+
+struct tree_node *
+get_node_by_path(memory_region *mreg, const char *path)
+{
+	if (strcmp(path, "/") == 0) {
+		LOG("Is ROOT %s", path);
+		return fuse_root;
+	}
+
+	char *segments[255];
+	size_t count = split_path(mreg, path, segments);
+
+	LOG("Segments are defined");
+
+	struct tree_node *current = fuse_root;
+
+	for (size_t i = 0; i < count && current; ++i) {
+		const char *segment = segments[i];
+		struct tree_node *next = NULL;
+		LOG("Segment: %s", segment);
+
+		for (size_t j = 0; j < current->child_count; ++j) {
+
+			const char *child_name =
+			    get_node_filename(mreg, current->children[j]);
+			if (strcmp(child_name, segment) == 0) {
+
+				next = current->children[j];
+				break;
+			}
+		}
+		current = next;
+	}
+	LOG("Done");
+	return current;
+}
+
+bool
+node_is_directory(memory_region *mreg, const struct tree_node *node,
+		  icalcomponent *node_ics)
+{
+	if (is_root_node(node) || node_has_children(node)) {
+		return true;
+	}
+	else {
+		// When user creates a directory, a directory component is
+		// stored in the ics file. So even if a node has no children
+		// it can still be that it should be shown as a directory.
+		return is_directory_component(node_ics);
+	}
+}
+
+bool
+is_directory_component(icalcomponent *component)
+{
+	icalcomponent *journal = icalcomponent_get_first_component(
+	    component, ICAL_VJOURNAL_COMPONENT);
+
+	for (icalproperty *prop = icalcomponent_get_first_property(
+		 journal, ICAL_RELATEDTO_PROPERTY);
+	     prop != NULL; prop = icalcomponent_get_next_property(
+			       journal, ICAL_RELATEDTO_PROPERTY)) {
+
+		const char *reltype =
+		    icalproperty_get_parameter_as_string(prop, "RELTYPE");
+		if (reltype && strcasecmp(reltype, "CHILD") == 0) {
+			return true;
+		}
+	}
+	for (icalproperty *prop =
+		 icalcomponent_get_first_property(journal, ICAL_X_PROPERTY);
+	     prop != NULL;
+	     prop = icalcomponent_get_next_property(journal, ICAL_X_PROPERTY)) {
+		LOG("FOUND X PROPERTY");
+
+		const char *prop_name = icalproperty_get_x_name(prop);
+		if (prop_name &&
+		    strcmp(prop_name, IS_DIRECTORY_PROPERTY) == 0) {
+			const char *value =
+			    icalproperty_get_value_as_string(prop);
+			LOG("FOUND DIRECTORY");
+			return value && strcmp(value, "YES") == 0;
+		}
+	}
+	return false;
+}
+
+bool
+is_root_node(const struct tree_node *node)
+{
+	return !node->data;
+}
+
+struct agenda_entry *
+get_entry(const struct tree_node *node)
+{
+	assert(node->data);
+	return node->data;
+}
+
+char *
+get_original_filepath(memory_region *mreg, const struct tree_node *node)
+{
+	struct agenda_entry *entry = get_entry(node);
+	return append_path(mreg, ICS_DIR, entry->filename_original);
+}
+
+// Owner: ctx
+icalcomponent *
+parse_ics_file(memory_region *mreg, const char *filename)
+{
+
+	FILE *file = fopen(filename, "r");
+	LOG("filename is %s", filename);
+
+	struct stat st;
+
+	LOG("Stating...");
+	if (stat(filename, &st) != 0) {
+		LOG("Failed to stat file: %s", filename);
+		exit(1);
+	}
+
+	size_t size = st.st_size;
+	LOG("rmallocing...");
+	char *buffer = rmalloc(mreg, size + 1);
+
+	LOG("freading...");
+	size_t bytes_read = fread(buffer, 1, size, file);
+	buffer[bytes_read] = '\0';
+	fclose(file);
+
+	icalcomponent *component = ricalcomponent_new_from_string(mreg, buffer);
+
+	return component;
+}
+
+icaltimetype
+get_ical_now()
+{
+	return icaltime_from_timet_with_zone(time(NULL), 0,
+					     icaltimezone_get_utc_timezone());
+}
+
+size_t
+write_ical_file(memory_region *mreg, const struct tree_node *node,
+		icalcomponent *ic)
+{
+
+	// Always set last modified to the time we write
+	icalproperty *prop =
+	    icalcomponent_get_first_property(ic, ICAL_LASTMODIFIED_PROPERTY);
+	if (!prop) {
+		prop = icalproperty_new_lastmodified(get_ical_now());
+		icalcomponent_add_property(ic, prop);
+	}
+	else {
+		icalproperty_set_lastmodified(prop, get_ical_now());
+	}
+
+	icalcomponent_set_status(ic, ICAL_STATUS_FINAL);
+
+	const char *descr_prop = icalcomponent_get_description(ic);
+	if (descr_prop != NULL && strcmp("", descr_prop) == 0) {
+		icalproperty *ical_descr_prop =
+		    icalcomponent_get_first_property(
+			icalcomponent_get_inner(ic), ICAL_DESCRIPTION_PROPERTY);
+		icalcomponent_remove_property(icalcomponent_get_inner(ic),
+					      ical_descr_prop);
+	}
+
+	LOG("Set last modified");
+
+	char *ical_str = ricalcomponent_as_ical_string_r(mreg, ic);
+
+	LOG("Got ical_str %s", ical_str);
+
+	int res = write_to_file(get_original_filepath(mreg, node), ical_str);
+
+	return res;
+}
+
+// Owner: ctx
+icalcomponent *
+get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n)
+{
+	if (is_root_node(n)) {
+		return NULL;
+	}
+
+	char *orig_path = get_original_filepath(mreg, n);
+	// TODO: Cache intermediate results within context
+	icalcomponent *ic = parse_ics_file(mreg, orig_path);
+	return ic;
+}
+
+const char *
+get_parent_uid(icalcomponent *component)
+{
+	icalcomponent *inner =
+	    icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
+
+	for (icalproperty *prop = icalcomponent_get_first_property(
+		 inner, ICAL_RELATEDTO_PROPERTY);
+	     prop != NULL; prop = icalcomponent_get_next_property(
+			       inner, ICAL_RELATEDTO_PROPERTY)) {
+		const char *reltype =
+		    icalproperty_get_parameter_as_string(prop, "RELTYPE");
+		if (reltype && strcasecmp(reltype, "PARENT") == 0) {
+			return icalproperty_get_value_as_string(prop);
+		}
+	}
+
+	return NULL;
+}
+
+void
+remove_parent_child_relationship_from_component(icalcomponent *parent,
+						icalcomponent *child)
+{
+
+	const char *parent_uid = icalcomponent_get_uid(parent);
+
+	icalcomponent *inner =
+	    icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
+
+	icalproperty *prop =
+	    icalcomponent_get_first_property(inner, ICAL_RELATEDTO_PROPERTY);
+	while (prop != NULL) {
+		icalproperty *next = icalcomponent_get_next_property(
+		    inner, ICAL_RELATEDTO_PROPERTY);
+		const char *value = icalproperty_get_relatedto(prop);
+		icalparameter *reltype = icalproperty_get_first_parameter(
+		    prop, ICAL_RELTYPE_PARAMETER);
+
+		if (value && strcmp(value, parent_uid) == 0 && reltype &&
+		    icalparameter_get_reltype(reltype) == ICAL_RELTYPE_PARENT) {
+			icalcomponent_remove_property(inner, prop);
+			icalproperty_free(prop);
+		}
+
+		prop = next;
+	}
+}
+
+void
+set_parent_child_relationship_to_component(icalcomponent *parent,
+					   icalcomponent *child)
+{
+	icalproperty *child_related_to_parent =
+	    icalproperty_new_relatedto(icalcomponent_get_uid(parent));
+	icalparameter *reltype_child =
+	    icalparameter_new_reltype(ICAL_RELTYPE_PARENT);
+	icalproperty_add_parameter(child_related_to_parent, reltype_child);
+
+	icalcomponent *inner =
+	    icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
+	if (inner) {
+		icalcomponent_add_property(inner, child_related_to_parent);
+	}
+}
+
+int
+write_parent_child_components(memory_region *mreg,
+			      const struct tree_node *parent,
+			      icalcomponent *iparent,
+			      const struct tree_node *child,
+			      icalcomponent *ichild)
+{
+	LOG("Adding parent and child relation");
+	set_parent_child_relationship_to_component(iparent, ichild);
+	return write_ical_file(mreg, child, ichild);
+}
+
+// You also will need to update the ics components
+int
+move_node(struct tree_node *new_parent, struct tree_node *child)
+{
+	detach_tree_node(child);
+	return add_child(new_parent, child);
+}
+
+int
+update_node_filename(struct tree_node *node, const char *filename)
+{
+	struct agenda_entry *entry = node->data;
+	free(entry->filename);
+	entry->filename = xstrdup(filename);
+	return 0;
+}
+
+// Writes ics component summary to the filename of the tree_node
+int
+update_summary(memory_region *mreg, icalcomponent *ics,
+	       const struct tree_node *node)
+{
+	struct agenda_entry *e = get_entry(node);
+	icalcomponent_set_summary(ics, e->filename);
+	return write_ical_file(mreg, node, ics);
+}
+
+// Agenda entries are NOT stored in memory_region, and need to be freed
+// manually
+struct agenda_entry *
+parse_ics_to_agenda_entry(memory_region *mreg, const char *filepath)
+{
+	icalcomponent *component = parse_ics_file(mreg, filepath);
+	if (component == NULL) {
+		LOG("No component");
+		return NULL;
+	}
+	icalcomponent *journal =
+	    icalcomponent_get_first_real_component(component);
+
+	if (journal == NULL ||
+	    icalcomponent_isa(journal) != ICAL_VJOURNAL_COMPONENT) {
+		LOG("Not journal component");
+		return NULL;
+	}
+	if (icalcomponent_get_summary(component) == NULL) {
+		LOG("No summary found");
+		return NULL;
+	}
+	struct agenda_entry *e = xmalloc(sizeof(struct agenda_entry));
+	region_register(mreg, e, (void *)free_agenda_entry);
+
+	e->filename = xstrdup(icalcomponent_get_summary(component));
+
+	e->filename_original = xstrdup(get_filename(filepath));
+
+	return e;
+}
+
+// agenda_entry is automatically cleaned up by mreg, use copy_agenda_entry
+// to take ownership!
+struct agenda_entry *
+load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename)
+{
+	path *filepath = append_path(mreg, ICS_DIR, filename);
+	LOG("Filepath is %s", filepath);
+
+	struct stat fileStat;
+	if (stat(filepath, &fileStat) == -1) {
+		LOG("Can not stat %s. skipping", filepath);
+		return NULL;
+	}
+
+	struct agenda_entry *new_entry =
+	    parse_ics_to_agenda_entry(mreg, filepath);
+	if (!new_entry) {
+		LOG("Could not parse entry");
+		return NULL;
+	}
+
+	// TODO: Load these from the original file dynamically instead.
+	new_entry->uid = fileStat.st_uid;
+	new_entry->gid = fileStat.st_gid;
+
+	LOG("Parsed %s to file %s", new_entry->filename_original,
+	    new_entry->filename);
+	return new_entry;
+}
+
+void
+load_root_node_tree()
+{
+	LOG("Allocating journal entries");
+	entries_original_ics = hashmap_new(NULL);
+	fuse_root = create_tree_node(NULL, NULL);
+	memory_region *mreg = create_region();
+
+	LOG("Loading journal entries from: %s\n", ICS_DIR);
+
+	DIR *dir = opendir(ICS_DIR);
+	if (!dir) {
+		perror("opendir");
+		return;
+	}
+
+	struct dirent *entry;
+	LOG("Load files to root directory");
+	while ((entry = readdir(dir))) {
+		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
+			LOG("Loading %s", entry->d_name);
+			struct agenda_entry *new_entry =
+			    load_agenda_entry_from_ics_file(mreg,
+							    entry->d_name);
+
+			struct agenda_entry *owned_entry =
+			    copy_agenda_entry(new_entry);
+
+			if (!new_entry) {
+				LOG("Skipping entry %s", entry->d_name);
+				continue;
+			}
+
+			struct tree_node *new_node = create_tree_node(
+			    owned_entry, (void *)free_agenda_entry);
+
+			hashmap_insert(entries_original_ics,
+				       new_entry->filename_original, new_node);
+
+			LOG("Inserted filename_original: %s, %s",
+			    owned_entry->filename,
+			    owned_entry->filename_original);
+		}
+	}
+	closedir(dir);
+	LOG("Set up directories according to parent-child");
+
+	size_t n_keys = 0;
+	char **keys = hashmap_get_keys(entries_original_ics, &n_keys);
+	for (size_t i = 0; i < n_keys; i++) {
+		const char *filename = keys[i];
+		struct tree_node *child =
+		    hashmap_get(entries_original_ics, filename);
+
+		LOG("Parsing %s", filename);
+		icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
+		LOG("Success");
+
+		struct agenda_entry *entry = get_entry(child);
+
+		const char *parent_uid = get_parent_uid(ic);
+		LOG("Looking up entry %s, %s", entry->filename_original,
+		    entry->filename);
+		if (parent_uid) {
+			LOG("Has parent");
+
+			struct tree_node *parent =
+			    get_node_by_uuid(mreg, parent_uid);
+			if (parent) {
+				struct agenda_entry *p_entry = parent->data;
+				LOG("Adding to parent %s", p_entry->filename);
+				detach_tree_node(child);
+				add_child(parent, child);
+			}
+			else {
+				LOG("COULD NOT FIND PARENT NODE: %s",
+				    parent_uid);
+			}
+		}
+		else {
+			add_child(fuse_root, child);
+		}
+	}
+	LOG("Inserted %zu entries", n_keys);
+
+	for (int i = 0; i < n_keys; i++) {
+		free(keys[i]);
+	}
+	free(keys);
+	rfree_all(mreg);
+
+	LOG("Done");
+}
+
+size_t
+load_agendafs_environment(char *ics_dir_env)
+{
+	wordexp_t expanded;
+	if (wordexp(ics_dir_env, &expanded, 0) != 0 || expanded.we_wordc == 0) {
+		fprintf(stderr, "Failed to expand CALDAVFS_ICS_DIR\n");
+		wordfree(&expanded);
+		return -1;
+	}
+
+	char *expanded_path = expanded.we_wordv[0];
+
+	struct stat s;
+	if (stat(expanded_path, &s) == 0 && S_ISDIR(s.st_mode)) {
+		strlcpy(ICS_DIR, expanded_path, sizeof(ICS_DIR));
+		wordfree(&expanded);
+		return 0;
+	}
+
+	fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
+		expanded_path);
+	return -1;
+}
+
+static int
+append_category_to_memstream(FILE *memstream, const char *category,
+			     bool is_first_category)
+{
+	int res = 0;
+	if (!is_first_category) {
+		res = fputs(",", memstream);
+		assert(res != EOF);
+	}
+
+	res = fputs(category, memstream);
+	assert(res != EOF);
+	return 0; // Success
+}
+
+// Returns comma separated list of categories for a file
+char *
+get_node_categories(memory_region *mreg, const struct tree_node *node)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return NULL;
+	}
+
+	FILE *memstream = NULL;
+	char *result_buffer = NULL;
+	size_t buffer_size = 0;
+
+	memstream = open_memstream(&result_buffer, &buffer_size);
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	icalproperty *catp =
+	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+	bool first_category = true;
+	while (catp) {
+		const char *category = icalproperty_get_categories(catp);
+		append_category_to_memstream(memstream, category,
+					     first_category);
+		catp = icalcomponent_get_next_property(
+		    inner, ICAL_CATEGORIES_PROPERTY);
+
+		first_category = false;
+	}
+
+	size_t res = fclose(memstream);
+	assert(res != EOF);
+
+	return result_buffer;
+}
+
+int
+delete_node_categories(memory_region *mreg, const struct tree_node *node)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	icalproperty *catp =
+	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+	// Delete old categories
+	while (catp) {
+		icalcomponent_remove_property(inner, catp);
+
+		catp = icalcomponent_get_next_property(
+		    inner, ICAL_CATEGORIES_PROPERTY);
+	}
+
+	return write_ical_file(mreg, node, component);
+}
+
+int
+set_node_categories(memory_region *mreg, const struct tree_node *node,
+		    const char *new_categories, size_t s)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	icalproperty *catp =
+	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+	// Delete old categories
+	while (catp) {
+		icalcomponent_remove_property(inner, catp);
+
+		catp = icalcomponent_get_next_property(
+		    inner, ICAL_CATEGORIES_PROPERTY);
+	}
+
+	if (s == 0) {
+		return write_ical_file(mreg, node, component);
+	}
+
+	char *null_terminated_categories = rmalloc(mreg, s + 1);
+	strncpy(null_terminated_categories, new_categories, s);
+	null_terminated_categories[s] = '\0';
+
+	icalproperty *new_cat_prop =
+	    icalproperty_new_categories(null_terminated_categories);
+	icalcomponent_add_property(inner, new_cat_prop);
+
+	return write_ical_file(mreg, node, component);
+}
+
+const char *
+format_ical_class(const enum icalproperty_class iclass)
+{
+	if (iclass == ICAL_CLASS_PRIVATE) {
+		return "private";
+	}
+	else if (iclass == ICAL_CLASS_PUBLIC) {
+		return "public";
+	}
+	else if (iclass == ICAL_CLASS_CONFIDENTIAL) {
+		return "confidential";
+	}
+	return NULL;
+}
+
+const icalproperty_class
+parse_ical_class(const char *input)
+{
+	if (strcmp(input, "private") == 0) {
+		return ICAL_CLASS_PRIVATE;
+	}
+	else if (strcmp(input, "public") == 0) {
+		return ICAL_CLASS_PUBLIC;
+	}
+	else if (strcmp(input, "confidential") == 0) {
+		return ICAL_CLASS_CONFIDENTIAL;
+	}
+	return ICAL_CLASS_X;
+}
+
+const char *
+get_node_class(memory_region *mreg, const struct tree_node *node)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	const icalproperty *classp =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	const enum icalproperty_class classv = icalproperty_get_class(classp);
+
+	if (classp) {
+		return format_ical_class(classv);
+	}
+
+	return "";
+}
+
+int
+delete_node_class(memory_region *mreg, const struct tree_node *node)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+	icalcomponent *inner = icalcomponent_get_inner(component);
+	icalproperty *prop =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	icalcomponent_remove_property(inner, prop);
+
+	return write_ical_file(mreg, node, component);
+}
+
+int
+set_node_class(memory_region *mreg, const struct tree_node *node,
+	       const icalproperty_class new_class)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	icalproperty *classp =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	icalcomponent_remove_property(inner, classp);
+
+	icalproperty *new_class_prop = icalproperty_new_class(new_class);
+	icalcomponent_add_property(inner, new_class_prop);
+
+	return write_ical_file(mreg, node, component);
+}
+
+char *
+create_new_unique_ics_uid(memory_region *mreg)
+{
+	uuid_t uuid;
+	char uuid_str[37]; // UUIDs are 36 characters + null terminator
+	uuid_generate(uuid);
+	uuid_unparse(uuid, uuid_str);
+	char *res = NULL;
+	size_t wRes = rasprintf(mreg, &res, "%s-caldavfs", uuid_str);
+	assert(wRes != -1);
+
+	return res;
+}
+
+icalcomponent *
+create_vjournal_entry(memory_region *mreg, char *summary)
+{
+
+	icalcomponent *calendar = ricalcomponent_new_vcalendar(mreg);
+
+	icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
+	icalcomponent_add_property(calendar,
+				   icalproperty_new_prodid("-//caldavfs//EN"));
+
+	// Step 2: Create a VJOURNAL entry
+	icalcomponent *journal = icalcomponent_new_vjournal();
+	char *id = create_new_unique_ics_uid(mreg);
+
+	icalcomponent_add_property(journal, icalproperty_new_uid(id));
+	icalcomponent_add_property(journal,
+				   icalproperty_new_class(ICAL_CLASS_PRIVATE));
+
+	icalcomponent_add_property(
+	    journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
+			 icaltimezone_get_utc_timezone())));
+	icalcomponent_add_property(journal, icalproperty_new_summary(summary));
+	icalcomponent_add_property(journal, icalproperty_new_description(""));
+
+	icalcomponent_add_component(calendar, journal);
+
+	return calendar;
+}
+
+uid_t
+get_node_uid(const struct tree_node *node)
+{
+	if (is_root_node(node)) {
+		return getuid();
+	}
+	return get_entry(node)->uid;
+}
+
+gid_t
+get_node_gid(const struct tree_node *node)
+{
+	if (is_root_node(node)) {
+		return getgid();
+	}
+	return get_entry(node)->gid;
+}
+
+icaltimetype
+get_last_modified(icalcomponent *component)
+{
+	icalproperty *last_modified_prop = icalcomponent_get_next_property(
+	    component, ICAL_LASTMODIFIED_PROPERTY);
+
+	if (last_modified_prop) {
+		icaltimetype ical_lastmodified =
+		    icalproperty_get_lastmodified(last_modified_prop);
+		return ical_lastmodified;
+	}
+	else {
+		return icalcomponent_get_dtstamp(component);
+	}
+}
+
+size_t
+get_entry_content_size(icalcomponent *component)
+{
+	size_t size =
+	    snprintf(NULL, 0, "%s", icalcomponent_get_description(component));
+
+	assert(size != -1);
+	return size;
+}
+
+icalcomponent *
+create_vjournal_directory(memory_region *mreg, const char *summary)
+{
+
+	icalcomponent *calendar = icalcomponent_new_vcalendar();
+
+	icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
+	icalcomponent_add_property(calendar,
+				   icalproperty_new_prodid("-//caldavfs//EN"));
+
+	icalcomponent *journal = icalcomponent_new_vjournal();
+	char *id = create_new_unique_ics_uid(mreg);
+
+	icalcomponent_add_property(journal, icalproperty_new_uid(id));
+
+	icalcomponent_add_property(
+	    journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
+			 icaltimezone_get_utc_timezone())));
+	icalcomponent_add_property(journal, icalproperty_new_summary(summary));
+
+	// Empty description; unused
+	icalcomponent_add_property(journal, icalproperty_new_description(""));
+
+	icalproperty *is_directory = icalproperty_new_x(IS_DIRECTORY_PROPERTY);
+	icalproperty_set_x_name(is_directory, IS_DIRECTORY_PROPERTY);
+	icalproperty_set_x(is_directory, "YES");
+	icalcomponent_add_property(journal, is_directory);
+
+	icalcomponent_add_component(calendar, journal);
+	return calendar;
+}
+
+int
+create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
+{
+	// TODO: Move to path.c
+	const char *entry_prefix = "/";
+	if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
+		return -EINVAL;
+	}
+	char *new_basename = strrchr(fuse_path, '/');
+	new_basename++;
+
+	struct agenda_entry *new_entry =
+	    xcalloc(1, sizeof(struct agenda_entry));
+	icalcomponent *new_component =
+	    create_vjournal_entry(mreg, new_basename);
+
+	new_entry->filename = xstrdup(new_basename);
+	if (asprintf(&new_entry->filename_original, "%s.ics",
+		     icalcomponent_get_uid(new_component)) == -1) {
+		LOG("Failed to create journal entry");
+		free_agenda_entry(new_entry);
+		return -ENOMEM;
+	}
+	LOG("Inserting vjournal directory");
+
+	struct tree_node *new_node =
+	    create_tree_node(new_entry, (void *)free_agenda_entry);
+
+	char *parent_path = get_parent_path(mreg, fuse_path);
+	LOG("Parent path is %s", parent_path);
+	struct tree_node *parent = get_node_by_path(mreg, parent_path);
+	icalcomponent *parent_ics = get_icalcomponent_from_node(mreg, parent);
+
+	if (!parent) {
+		LOG("Parent does not exist");
+		return -ENOENT;
+	}
+	if (!is_root_node(parent) ||
+	    (parent_ics && !node_is_directory(mreg, parent, parent_ics))) {
+		LOG("Parent is not directory ");
+		return -ENOTDIR;
+	}
+
+	if (parent_ics) {
+		write_parent_child_components(mreg, parent, parent_ics,
+					      new_node, new_component);
+
+		add_child(parent, new_node);
+	}
+
+	hashmap_insert(entries_original_ics, new_entry->filename_original,
+		       new_node);
+
+	if (write_ical_file(mreg, new_node, new_component) != 0) {
+		LOG("Write to entry failed");
+		hashmap_remove(entries_original_ics,
+			       new_entry->filename_original);
+
+		free_tree(new_node);
+		return -EIO;
+	}
+
+	LOG("DIRECTORY CREATED");
+	return 0;
+}
+
+int
+create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path)
+{
+	const char *entry_prefix = "/";
+	if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
+		return -EINVAL;
+	}
+	char *new_basename = strrchr(fuse_path, '/');
+	new_basename++;
+
+	struct agenda_entry *new_entry =
+	    xcalloc(1, sizeof(struct agenda_entry));
+	icalcomponent *new_component =
+	    create_vjournal_directory(mreg, new_basename);
+
+	new_entry->filename = xstrdup(new_basename);
+	if (asprintf(&new_entry->filename_original, "%s.ics",
+		     icalcomponent_get_uid(new_component)) == -1) {
+		LOG("Failed to create journal entry");
+		free_agenda_entry(new_entry);
+		return -ENOMEM;
+	}
+	LOG("Inserting vjournal directory");
+
+	struct tree_node *new_node =
+	    create_tree_node(new_entry, (void *)free_agenda_entry);
+
+	char *parent_path = get_parent_path(mreg, fuse_path);
+	LOG("Parent path is %s", parent_path);
+	struct tree_node *parent = get_node_by_path(mreg, parent_path);
+	icalcomponent *parent_ics = get_icalcomponent_from_node(mreg, parent);
+
+	if (!parent) {
+		LOG("Parent does not exist");
+		return -ENOENT;
+	}
+	if (!is_root_node(parent) ||
+	    (parent_ics && !node_is_directory(mreg, parent, parent_ics))) {
+		LOG("Parent is not directory ");
+		return -ENOTDIR;
+	}
+
+	if (parent_ics) {
+		write_parent_child_components(mreg, parent, parent_ics,
+					      new_node, new_component);
+
+		add_child(parent, new_node);
+	}
+
+	hashmap_insert(entries_original_ics, new_entry->filename_original,
+		       new_node);
+
+	if (write_ical_file(mreg, new_node, new_component) != 0) {
+		LOG("Write to entry failed");
+		hashmap_remove(entries_original_ics,
+			       new_entry->filename_original);
+
+		free_tree(new_node);
+		return -EIO;
+	}
+
+	LOG("DIRECTORY CREATED");
+	return 0;
+}
+
+void
+update_or_create_fuse_entry_from_original(memory_region *mreg,
+					  const char *filepath_original)
+{
+	const char *filename_original = get_filename(filepath_original);
+	LOG("Filename original is %s", filename_original);
+
+	struct agenda_entry *updated_entry =
+	    load_agenda_entry_from_ics_file(mreg, filename_original);
+
+	struct agenda_entry *updated_entry_owned =
+	    copy_agenda_entry(updated_entry);
+
+	if (!updated_entry_owned) {
+		LOG("PARSING FAILED");
+		// TODO: Error code
+		return;
+	}
+
+	struct tree_node *new_or_updated_node =
+	    hashmap_get(entries_original_ics, filename_original);
+
+	if (new_or_updated_node) {
+		LOG("Updating existing entry");
+		update_node_data(new_or_updated_node, updated_entry_owned);
+	}
+	else {
+		LOG("Creating new entry");
+		new_or_updated_node = create_tree_node(
+		    updated_entry_owned, (void *)free_agenda_entry);
+		hashmap_insert(entries_original_ics,
+			       updated_entry_owned->filename_original,
+			       new_or_updated_node);
+	}
+
+	icalcomponent *entry_ics =
+	    get_icalcomponent_from_node(mreg, new_or_updated_node);
+
+	const char *new_parent_uid = get_parent_uid(entry_ics);
+
+	if (new_parent_uid) {
+		LOG("Has parent");
+		struct tree_node *new_parent =
+		    get_node_by_uuid(mreg, new_parent_uid);
+		move_node(new_parent, new_or_updated_node);
+	}
+	else {
+		LOG("Does not have parent, adding to root");
+		detach_tree_node(new_or_updated_node);
+		add_child(fuse_root, new_or_updated_node);
+	}
+
+	LOG("Tree updated");
+}
+
+int
+delete_dir_from_fuse_path(memory_region *mreg, const char *filepath)
+{
+	int res = 0;
+	struct tree_node *node = get_node_by_path(mreg, filepath);
+	if (!node) {
+		return -EIO;
+	}
+
+	icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+	if (!node_is_directory(mreg, node, node_ics)) {
+		return -ENOTDIR;
+	}
+	if (node->child_count > 0) {
+		return -ENOTEMPTY;
+	}
+
+	char *filepath_original = get_original_filepath(mreg, node);
+	if (!filepath_original) {
+		LOG("Entry not found");
+		return -EIO;
+	}
+	LOG("Deleting file %s, located at %s", filepath, filepath_original);
+
+	res = remove(filepath_original);
+	if (res != 0) {
+		LOG("Failed to delete file");
+		return -res;
+	}
+
+	hashmap_remove(entries_original_ics, filepath_original);
+
+	detach_tree_node(node);
+	free_tree(node);
+	return res;
+}
+
+int
+do_agenda_rename(memory_region *mreg, const char *old, const char *new)
+{
+	char *new_copy = rstrdup(mreg, new);
+	const char *new_filename = get_filename(new_copy);
+
+	const char *old_filename = get_filename(old);
+	if (!old_filename)
+		return -EINVAL;
+
+	struct tree_node *new_parent_node =
+	    get_node_by_path(mreg, get_parent_path(mreg, new));
+
+	if (new_parent_node == NULL) {
+		return -EINVAL;
+	}
+
+	char *new_summary = rstrdup(mreg, new_filename);
+
+	// Remove everything after extension
+	// TODO: Store the extension as a separate field in the ics file
+	char *dot = strrchr(new_summary, '.');
+	if (dot) {
+		*dot = '\0';
+	}
+
+	LOG("Summary is now %s", new_summary);
+	struct tree_node *child_node = get_node_by_path(mreg, old);
+	assert(child_node);
+	icalcomponent *child_ics =
+	    get_icalcomponent_from_node(mreg, child_node);
+	assert(child_ics);
+
+	icalcomponent *new_parent_ics =
+	    get_icalcomponent_from_node(mreg, new_parent_node);
+
+	struct tree_node *old_parent_node = child_node->parent;
+	icalcomponent *old_parent_ics =
+	    get_icalcomponent_from_node(mreg, old_parent_node);
+
+	update_node_filename(child_node, new_filename);
+	update_summary(mreg, child_ics, child_node);
+
+	if (old_parent_node && old_parent_ics) {
+		remove_parent_child_relationship_from_component(old_parent_ics,
+								child_ics);
+	}
+
+	detach_tree_node(child_node);
+
+	if (new_parent_ics) {
+		move_node(new_parent_node, child_node);
+		write_parent_child_components(mreg, new_parent_node,
+					      new_parent_ics, child_node,
+					      child_ics);
+	}
+	else {
+		add_child(fuse_root, child_node);
+	}
+	return 0;
+}
+
+int
+delete_from_fuse_path(memory_region *mreg, const char *filepath)
+{
+	int res = 0;
+	struct tree_node *node = get_node_by_path(mreg, filepath);
+	if (!node) {
+		return -EIO;
+	}
+
+	icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+	if (node_is_directory(mreg, node, node_ics)) {
+		return -EISDIR;
+	}
+	char *filepath_original = get_original_filepath(mreg, node);
+	if (!filepath_original) {
+		LOG("Entry not found");
+		return -EIO;
+	}
+	LOG("Deleting file %s, located at %s", filepath, filepath_original);
+
+	res = remove(filepath_original);
+	if (res == -1) {
+		return -EIO;
+	}
+
+	hashmap_remove(entries_original_ics, filepath_original);
+	detach_tree_node(node);
+	free_tree(node);
+	return res;
+}
+
+int
+delete_from_original_path(memory_region *mreg, const char *filepath)
+{
+	// Strip path, validate just the new basename
+	const char *keyname = strrchr(filepath, '/');
+	keyname++;
+	struct agenda_entry *entry = hashmap_get(entries_original_ics, keyname);
+	if (!entry) {
+		return -EIO;
+	}
+
+	// TODO: Fix so it finds by actual UUID, not filepath
+	struct tree_node *node = get_node_by_uuid(mreg, filepath);
+
+	// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
+	// Instead, update the children so they have a new parent
+	// which is the parent node.
+	// Requires a change_parent function.
+	detach_tree_node(node);
+	free_tree(node);
+	hashmap_remove(entries_original_ics, keyname);
+	return 0;
+}
diff --git a/agenda_entry.h b/agenda_entry.h
@@ -0,0 +1,202 @@
+#ifndef agenda_entry_h_INCLUDED
+#define agenda_entry_h_INCLUDED
+
+#include "hashmap.h"
+#include "mregion.h"
+#include "path.h"
+#include "tree.h"
+#include "util.h"
+#include <dirent.h>
+#include <libical/ical.h>
+#include <pthread.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+#include <wordexp.h>
+
+struct agenda_entry {
+	// filename is full path relative to fuse directory
+	// I.E. hello world
+	char *filename;
+	// filename_original is the relative path to ICS_DIR
+	// I.E. 910319208nrao19p.ics
+	char *filename_original;
+	uid_t uid;
+	gid_t gid;
+};
+
+extern char ICS_DIR[256];
+
+extern const char *IS_DIRECTORY_PROPERTY;
+
+// A structure of the current root
+extern struct tree_node *fuse_root;
+// ics entries, keys are the filename as stored in ICS_DIR
+// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
+extern struct hashmap *entries_original_ics;
+
+struct agenda_entry *
+copy_agenda_entry(const struct agenda_entry *src);
+
+void
+free_agenda_entry(struct agenda_entry *entry);
+
+struct tree_node *
+get_node_by_uuid(memory_region *mreg, const char *target_uuid);
+
+char *
+get_node_filename(memory_region *mreg, const struct tree_node *node);
+
+struct tree_node *
+get_node_by_path(memory_region *mreg, const char *path);
+
+bool
+node_is_directory(memory_region *mreg, const struct tree_node *node,
+		  icalcomponent *node_ics);
+
+bool
+is_directory_component(icalcomponent *component);
+
+bool
+is_root_node(const struct tree_node *node);
+
+struct agenda_entry *
+get_entry(const struct tree_node *node);
+
+char *
+get_original_filepath(memory_region *mreg, const struct tree_node *node);
+
+// Owner: ctx
+icalcomponent *
+parse_ics_file(memory_region *mreg, const char *filename);
+
+icaltimetype
+get_ical_now();
+
+size_t
+write_ical_file(memory_region *mreg, const struct tree_node *node,
+		icalcomponent *ic);
+
+// Owner: ctx
+icalcomponent *
+get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n);
+
+const char *
+get_parent_uid(icalcomponent *component);
+
+void
+remove_parent_child_relationship_from_component(icalcomponent *parent,
+						icalcomponent *child);
+
+void
+set_parent_child_relationship_to_component(icalcomponent *parent,
+					   icalcomponent *child);
+
+int
+write_parent_child_components(memory_region *mreg,
+			      const struct tree_node *parent,
+			      icalcomponent *iparent,
+			      const struct tree_node *child,
+			      icalcomponent *ichild);
+
+// You also will need to update the ics components
+int
+move_node(struct tree_node *new_parent, struct tree_node *child);
+
+int
+update_node_filename(struct tree_node *node, const char *filename);
+
+// Writes ics component summary to the filename of the tree_node
+int
+update_summary(memory_region *mreg, icalcomponent *ics,
+	       const struct tree_node *node);
+
+struct agenda_entry *
+parse_ics_to_agenda_entry(memory_region *mreg, const char *filename);
+
+// Will be cleaned up by mreg
+struct agenda_entry *
+load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename);
+
+void
+load_root_node_tree();
+
+size_t
+load_agendafs_environment(char *ics_dir_env);
+
+// Returns comma separated list of categories for a file
+char *
+get_node_categories(memory_region *mreg, const struct tree_node *node);
+
+int
+delete_node_categories(memory_region *mreg, const struct tree_node *node);
+
+int
+set_node_categories(memory_region *mreg, const struct tree_node *node,
+		    const char *new_categories, size_t s);
+
+const char *
+format_ical_class(const enum icalproperty_class iclass);
+
+const icalproperty_class
+parse_ical_class(const char *input);
+
+const char *
+get_node_class(memory_region *mreg, const struct tree_node *node);
+
+int
+delete_node_class(memory_region *mreg, const struct tree_node *node);
+
+int
+set_node_class(memory_region *mreg, const struct tree_node *node,
+	       const icalproperty_class new_class);
+
+char *
+create_new_unique_ics_uid(memory_region *mreg);
+
+icalcomponent *
+create_vjournal_entry(memory_region *mreg, char *summary);
+uid_t
+get_node_uid(const struct tree_node *node);
+
+gid_t
+get_node_gid(const struct tree_node *node);
+
+icaltimetype
+get_last_modified(icalcomponent *component);
+
+size_t
+get_entry_content_size(icalcomponent *component);
+
+icalcomponent *
+create_vjournal_directory(memory_region *mreg, const char *summary);
+
+int
+create_entry_from_fuse(memory_region *mreg, const char *fuse_path);
+
+int
+create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path);
+
+void
+update_or_create_fuse_entry_from_original(memory_region *mreg,
+					  const char *filepath_original);
+
+int
+delete_dir_from_fuse_path(memory_region *mreg, const char *filepath);
+
+int
+do_agenda_rename(memory_region *, const char *, const char *);
+
+int
+delete_from_fuse_path(memory_region *mreg, const char *filepath);
+
+int
+delete_from_original_path(memory_region *mreg, const char *filepath);
+#endif // agenda_entry_h_INCLUDED
diff --git a/hashmap.c b/hashmap.c
@@ -1,6 +1,8 @@
 // Taken from https://github.com/prismz/hashmap
 #include "hashmap.h"
 
+#include "mregion.h"
+#include <assert.h>
 #include <inttypes.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -431,3 +433,13 @@ hashmap_free_keys(char **keys, size_t n_keys)
 		free(keys[i]);
 	free(keys);
 }
+
+struct hashmap *
+rhashmap_new(memory_region *region, void (*val_free_func)(void *))
+{
+	struct hashmap *h = hashmap_new(val_free_func);
+	assert(h);
+
+	region_register(region, h, (void *)hashmap_free);
+	return h;
+}
diff --git a/hashmap.h b/hashmap.h
@@ -3,6 +3,7 @@
 #ifndef HASHMAP_H
 #define HASHMAP_H
 
+#include "mregion.h"
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -54,13 +55,24 @@ struct hashmap {
 	void (*val_free_func)(void *);
 };
 
-struct hashmap *hashmap_new(void (*val_free_func)(void *));
-void hashmap_free(struct hashmap *hm);
-int hashmap_resize(struct hashmap *hm);
-int hashmap_insert(struct hashmap *hm, const char *key, void *val);
-void *hashmap_get(struct hashmap *hm, const char *key);
-int hashmap_remove(struct hashmap *hm, const char *key);
-void hashmap_print(struct hashmap *hm);
-char **hashmap_get_keys(struct hashmap *hm, size_t *n_keys);
-void hashmap_free_keys(char **keys, size_t n_keys);
+struct hashmap *
+hashmap_new(void (*val_free_func)(void *));
+void
+hashmap_free(struct hashmap *hm);
+int
+hashmap_resize(struct hashmap *hm);
+int
+hashmap_insert(struct hashmap *hm, const char *key, void *val);
+void *
+hashmap_get(struct hashmap *hm, const char *key);
+int
+hashmap_remove(struct hashmap *hm, const char *key);
+void
+hashmap_print(struct hashmap *hm);
+char **
+hashmap_get_keys(struct hashmap *hm, size_t *n_keys);
+void
+hashmap_free_keys(char **keys, size_t n_keys);
+struct hashmap *
+rhashmap_new(memory_region *region, void (*val_free_func)(void *));
 #endif /* HASHMAP_H */
diff --git a/journal_entry.c b/journal_entry.c
@@ -1,1333 +0,0 @@
-#include "journal_entry.h"
-
-#include "hashmap.h"
-#include "path.h"
-#include "tree.h"
-#include "util.h"
-#include <assert.h>
-#include <dirent.h>
-#include <errno.h>
-#include <libical/ical.h>
-#include <pthread.h>
-#include <regex.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/inotify.h>
-#include <sys/stat.h>
-#include <time.h>
-#include <unistd.h>
-#include <uuid/uuid.h>
-#include <wordexp.h>
-
-char ICS_DIR[256];
-
-static const char *IS_DIRECTORY_PROPERTY = "X-CALDAVFS-ISDIRECTORY";
-
-// tree of journal entries
-struct tree_node *fuse_tree_root = NULL;
-
-// journal_entry, keys are the filename as stored in ICS_DIR
-// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
-struct hashmap *entries_original_ics = NULL;
-
-struct tree_node *
-create_file_node(struct journal_entry *entry)
-{
-	struct tree_node *file_node =
-	    create_tree_node(entry, free_journal_entry);
-	return file_node;
-}
-
-struct journal_entry *
-get_entry(const struct tree_node *node)
-{
-	struct journal_entry *entry = node->data;
-	return entry;
-}
-
-char *
-get_node_filename(const struct tree_node *node)
-{
-	struct journal_entry *entry = node->data;
-	if (entry == NULL)
-		return "";
-	return entry->filename;
-}
-
-icalcomponent *
-get_node_component(const struct tree_node *node)
-{
-	struct journal_entry *entry = node->data;
-	if (entry == NULL)
-		return NULL;
-	return entry->component;
-}
-
-struct tree_node **
-get_node_children(const struct tree_node *node)
-{
-	struct tree_node **entries = node->children;
-	return entries;
-}
-
-void
-detach_node_and_entry(struct tree_node *node)
-{
-	struct tree_node *parent = node->parent;
-	if (!parent || !parent->data) {
-		return;
-	}
-	// TODO: Remove parent child relationship
-	detach_tree_node(node);
-}
-
-struct tree_node *
-get_node_by_uuid(const char *target_uuid)
-{
-	char *filename_original = NULL;
-
-	// Filenames are uid.ics, so we levarage that
-	size_t wRes = asprintf(&filename_original, "%s.ics", target_uuid);
-	assert(wRes != -1);
-
-	LOG("Looking for UID: '%s'", target_uuid);
-	LOG("Full filename: '%s'", filename_original);
-
-	struct tree_node *node =
-	    hashmap_get(entries_original_ics, filename_original);
-	free(filename_original);
-	LOG("Found it: %b", node != NULL);
-	return node;
-}
-
-const char *
-get_node_ical_uuid(const struct tree_node *node)
-{
-	struct journal_entry *entry = node->data;
-	if (entry) {
-		return icalcomponent_get_uid(entry->component);
-	}
-	return NULL;
-}
-bool
-is_root_node(const struct tree_node *node)
-{
-	if (!node->data) {
-		return true;
-	}
-	return false;
-}
-
-void
-set_parent_child_relationship_to_component(icalcomponent *parent,
-					   icalcomponent *child)
-{
-	icalproperty *child_related_to_parent =
-	    icalproperty_new_relatedto(icalcomponent_get_uid(parent));
-	icalparameter *reltype_child =
-	    icalparameter_new_reltype(ICAL_RELTYPE_PARENT);
-	icalproperty_add_parameter(child_related_to_parent, reltype_child);
-
-	icalcomponent *inner =
-	    icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
-	if (inner) {
-		icalcomponent_add_property(inner, child_related_to_parent);
-	}
-}
-void
-remove_parent_child_relationship_from_component(icalcomponent *parent,
-						icalcomponent *child)
-{
-
-	const char *parent_uid = icalcomponent_get_uid(parent);
-
-	icalcomponent *inner =
-	    icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
-
-	icalproperty *prop =
-	    icalcomponent_get_first_property(inner, ICAL_RELATEDTO_PROPERTY);
-	while (prop != NULL) {
-		icalproperty *next = icalcomponent_get_next_property(
-		    inner, ICAL_RELATEDTO_PROPERTY);
-		const char *value = icalproperty_get_relatedto(prop);
-		icalparameter *reltype = icalproperty_get_first_parameter(
-		    prop, ICAL_RELTYPE_PARAMETER);
-
-		if (value && strcmp(value, parent_uid) == 0 && reltype &&
-		    icalparameter_get_reltype(reltype) == ICAL_RELTYPE_PARENT) {
-			icalcomponent_remove_property(inner, prop);
-			icalproperty_free(prop);
-		}
-
-		prop = next;
-	}
-}
-
-int
-add_child_to_node(struct tree_node *parent, struct tree_node *child)
-{
-	// Root node, don't add relationship to component
-	if (is_root_node(parent)) {
-		LOG("Is root node, not adding relationship");
-		add_child(parent, child);
-		return 0;
-	}
-	else {
-		LOG("Adding parent and child relation");
-		struct journal_entry *p_entry = parent->data;
-		struct journal_entry *c_entry = child->data;
-		set_parent_child_relationship_to_component(p_entry->component,
-							   c_entry->component);
-		if (write_entry_to_ical_file(p_entry) != 0) {
-			return -EIO;
-		}
-		if (write_entry_to_ical_file(c_entry) != 0) {
-			return -EIO;
-		}
-
-		return add_child(parent, child);
-	}
-}
-
-struct tree_node *
-get_node_by_path(const char *path)
-{
-	if (strcmp(path, "/") == 0) {
-		LOG("Is ROOT %s", path);
-		return fuse_tree_root;
-	}
-
-	char *segments[255];
-	size_t count = split_path(path, segments);
-
-	LOG("Segments are defined");
-
-	struct tree_node *current = fuse_tree_root;
-
-	for (size_t i = 0; i < count && current; ++i) {
-		const char *segment = segments[i];
-		struct tree_node *next = NULL;
-		LOG("Segment: %s", segment);
-
-		for (size_t j = 0; j < current->child_count; ++j) {
-			const char *child_name =
-			    get_node_filename(current->children[j]);
-			if (strcmp(child_name, segment) == 0) {
-				next = current->children[j];
-				break;
-			}
-		}
-		current = next;
-	}
-
-	free_segments(segments, count);
-	return current;
-}
-
-// Full filepath of original ics file
-char *
-get_original_filepath(const struct journal_entry *entry)
-{
-	return append_path(ICS_DIR, entry->filename_original);
-}
-
-icaltimetype
-get_ical_now()
-{
-	return icaltime_from_timet_with_zone(time(NULL), 0,
-					     icaltimezone_get_utc_timezone());
-}
-
-void
-free_journal_entry(void *val)
-{
-	struct journal_entry *entry = (struct journal_entry *)val;
-	if (!entry)
-		return;
-	if (entry->component)
-		icalcomponent_free(entry->component);
-	if (entry->filename)
-		free(entry->filename);
-	if (entry->filename_original)
-		free(entry->filename_original);
-	free(entry);
-}
-
-struct journal_entry *
-get_entry_from_fuse_path(const char *path)
-{
-	if (!path || path[0] != '/') {
-		LOG("Invalid path: '%s'\n", path ? path : "NULL");
-		return NULL;
-	}
-	const struct tree_node *node = get_node_by_path(path);
-
-	if (!node || !node->data) {
-		LOG("No entry found for key: '%s'\n", path);
-		return NULL;
-	}
-
-	struct journal_entry *entry = node->data;
-	char *ical_str = icalcomponent_as_ical_string_r(entry->component);
-	LOG("Entry found: filename = '%s', original = '%s', path = '%s', has "
-	    "nonroot parent= '%b'\n%s",
-	    entry->filename, entry->filename_original, path,
-	    !is_root_node(node->parent), ical_str);
-	free(ical_str);
-	return entry;
-}
-
-// If it has a relationship type
-bool
-is_directory_component(icalcomponent *component)
-{
-	icalcomponent *journal = icalcomponent_get_first_component(
-	    component, ICAL_VJOURNAL_COMPONENT);
-
-	for (icalproperty *prop = icalcomponent_get_first_property(
-		 journal, ICAL_RELATEDTO_PROPERTY);
-	     prop != NULL; prop = icalcomponent_get_next_property(
-			       journal, ICAL_RELATEDTO_PROPERTY)) {
-
-		const char *reltype =
-		    icalproperty_get_parameter_as_string(prop, "RELTYPE");
-		if (reltype && strcasecmp(reltype, "CHILD") == 0) {
-			return true;
-		}
-	}
-	for (icalproperty *prop =
-		 icalcomponent_get_first_property(journal, ICAL_X_PROPERTY);
-	     prop != NULL;
-	     prop = icalcomponent_get_next_property(journal, ICAL_X_PROPERTY)) {
-		LOG("FOUND X PROPERTY");
-
-		const char *prop_name = icalproperty_get_x_name(prop);
-		if (prop_name &&
-		    strcmp(prop_name, IS_DIRECTORY_PROPERTY) == 0) {
-			const char *value =
-			    icalproperty_get_value_as_string(prop);
-			LOG("FOUND DIRECTORY");
-			return value && strcmp(value, "YES") == 0;
-		}
-	}
-	return false;
-}
-
-// We assume it to be a directory if it has a property directory property
-// so or is a parent node
-bool
-node_is_directory(const struct tree_node *node)
-{
-	if (is_root_node(node) || node_has_children(node)) {
-		return true;
-	}
-	else {
-		// When user creates a directory, a directory component is
-		// stored in the ics file. So even if a node has no children
-		// it can still be that it should be shown as a directory.
-		struct journal_entry *entry = node->data;
-		return is_directory_component(entry->component);
-	}
-}
-
-char *
-read_stream(char *s, size_t size, void *d)
-{
-	return fgets(s, (int)size, (FILE *)d);
-}
-
-size_t
-parse_entry_content(struct journal_entry *entry, const char *buf, size_t size)
-{
-
-	char *description = NULL;
-	asprintf(&description, "%s", buf);
-
-	icalcomponent_set_description(entry->component, description);
-	return 0;
-}
-
-icalcomponent *
-parse_ics_file(const char *filename)
-{
-
-	FILE *file = fopen(filename, "r");
-	assert(file != NULL);
-
-	struct stat st;
-	stat(filename, &st);
-	size_t size = st.st_size;
-
-	char *buffer = xmalloc(size + 1);
-	size_t bytes_read = fread(buffer, 1, size, file);
-	buffer[bytes_read] = '\0';
-	fclose(file);
-
-	icalcomponent *component = icalcomponent_new_from_string(buffer);
-	free(buffer);
-
-	return component;
-}
-
-// We assume that there is only one inner component in the file
-int
-parse_ics_to_journal_entry_component(const char *filename,
-				     struct journal_entry *entry)
-{
-	icalcomponent *component = parse_ics_file(filename);
-	if (component == NULL) {
-		LOG("No component");
-		return -1;
-	}
-	// Verify if necessary?
-	icalcomponent *journal =
-	    icalcomponent_get_first_real_component(component);
-
-	// TODO: Support VTODOs, VEVENTs etc.
-	if (journal == NULL ||
-	    icalcomponent_isa(journal) != ICAL_VJOURNAL_COMPONENT) {
-		LOG("Not journal component");
-		return -2;
-	}
-	if (icalcomponent_get_summary(component) == NULL) {
-		LOG("No summary found");
-		return -2;
-	}
-
-	if (icalcomponent_get_description(component) == NULL) {
-		icalcomponent_set_description(component, "");
-	}
-
-	entry->component = component;
-	return 0;
-}
-
-int
-move_node(struct tree_node *old_parent, struct tree_node *new_parent,
-	  struct tree_node *child)
-{
-	struct journal_entry *child_entry = child->data;
-
-	// Not root node
-	if (!is_root_node(old_parent)) {
-		LOG("Removing old node");
-		struct journal_entry *old_parent_entry = old_parent->data;
-		remove_parent_child_relationship_from_component(
-		    old_parent_entry->component, child_entry->component);
-		write_entry_to_ical_file(old_parent_entry);
-	}
-	detach_tree_node(child);
-	return add_child_to_node(new_parent, child);
-}
-
-// Returns -1 on failure, 0 on success
-int
-load_journal_entry_from_ics_file(const char *filename,
-				 struct journal_entry *entry)
-{
-	path *filepath = append_path(ICS_DIR, filename);
-	LOG("Filepath is %s", filepath);
-
-	struct stat fileStat;
-	if (stat(filepath, &fileStat) == -1) {
-		LOG("Can not stat %s. skipping", filepath);
-		free(filepath);
-		return -1;
-	}
-
-	entry->filename_original = xstrdup(filename);
-
-	if (parse_ics_to_journal_entry_component(filepath, entry) != 0) {
-		LOG("Failed to parse entry: %s", filename);
-		free(filepath);
-		return -1;
-	}
-
-	entry->uid = fileStat.st_uid;
-	entry->gid = fileStat.st_gid;
-
-	// Make sure filename, which happens when
-	// Two entries are created on the same minute
-	// TODO: Check for duplicates
-	if (is_directory_component(entry->component)) {
-		if (asprintf(&entry->filename, "%s",
-			     icalcomponent_get_summary(entry->component)) ==
-		    -1) {
-			LOG("Failed to write filename");
-			return -1;
-		};
-	}
-
-	// TODO: Enable custom fileformats by storing them in a
-	// X-CALDAVFS-EXTENSION format
-	else if (asprintf(&entry->filename, "%s",
-			  icalcomponent_get_summary(entry->component)) == -1) {
-		LOG("Failed to write filename");
-		return -1;
-	};
-	LOG("Parsed %s to file %s", entry->filename_original, entry->filename);
-
-	free(filepath);
-	return 0;
-}
-
-const char *
-get_parent_uid(icalcomponent *component)
-{
-	icalcomponent *inner =
-	    icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
-
-	for (icalproperty *prop = icalcomponent_get_first_property(
-		 inner, ICAL_RELATEDTO_PROPERTY);
-	     prop != NULL; prop = icalcomponent_get_next_property(
-			       inner, ICAL_RELATEDTO_PROPERTY)) {
-		const char *reltype =
-		    icalproperty_get_parameter_as_string(prop, "RELTYPE");
-		if (reltype && strcasecmp(reltype, "PARENT") == 0) {
-			return icalproperty_get_value_as_string(prop);
-		}
-	}
-
-	return NULL;
-}
-
-void
-load_journal_entries()
-{
-	LOG("Allocating journal entries");
-	if (entries_original_ics) {
-		hashmap_free(entries_original_ics);
-		entries_original_ics = NULL;
-	}
-	if (fuse_tree_root) {
-		free_tree(fuse_tree_root);
-		fuse_tree_root = NULL;
-	}
-	entries_original_ics = hashmap_new(NULL);
-	fuse_tree_root = create_tree_node(NULL, NULL);
-
-	LOG("Loading journal entries from: %s\n", ICS_DIR);
-
-	DIR *dir = opendir(ICS_DIR);
-	if (!dir) {
-		perror("opendir");
-		return;
-	}
-
-	struct dirent *entry;
-	LOG("Load files to root directory");
-	while ((entry = readdir(dir))) {
-		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
-
-			struct journal_entry *new_entry =
-			    xcalloc(1, sizeof(struct journal_entry));
-
-			if (load_journal_entry_from_ics_file(entry->d_name,
-							     new_entry) != 0) {
-				LOG("FAILED TO LOAD JOURNAL ENTRY");
-				free_journal_entry(new_entry);
-				continue;
-			}
-
-			struct tree_node *new_node =
-			    create_file_node(new_entry);
-
-			hashmap_insert(entries_original_ics,
-				       new_entry->filename_original, new_node);
-
-			LOG("Inserted filename_original: %s",
-			    new_entry->filename_original);
-		}
-	}
-	closedir(dir);
-	LOG("Set up directories according to parent-child");
-
-	size_t n_keys = 0;
-	char **keys = hashmap_get_keys(entries_original_ics, &n_keys);
-	for (size_t i = 0; i < n_keys; i++) {
-		const char *filename = keys[i];
-		struct tree_node *child =
-		    hashmap_get(entries_original_ics, filename);
-		struct journal_entry *entry = child->data;
-
-		const char *parent_uid = get_parent_uid(entry->component);
-		LOG("Looking up entry %s, %s", entry->filename_original,
-		    entry->filename);
-		if (parent_uid) {
-			LOG("Has parent");
-
-			struct tree_node *parent = get_node_by_uuid(parent_uid);
-			if (parent) {
-				struct journal_entry *p_entry = parent->data;
-				LOG("Adding to parent %s", p_entry->filename);
-				detach_tree_node(child);
-				add_child(parent, child);
-			}
-			else {
-				LOG("COULD NOT FIND PARENT NODE: %s",
-				    parent_uid);
-			}
-		}
-		else {
-			add_child(fuse_tree_root, child);
-		}
-	}
-	LOG("Inserted %zu entries", n_keys);
-
-	for (int i = 0; i < n_keys; i++) {
-		free(keys[i]);
-	}
-	free(keys);
-
-	LOG("Done");
-}
-
-size_t
-write_entry_content(char *buf, struct journal_entry *entry)
-{
-	// Allocate memory for the content
-	size_t size = asprintf(&buf, "%s",
-			       icalcomponent_get_description(entry->component));
-	assert(size != -1);
-	return size;
-}
-
-size_t
-get_entry_content_size(struct journal_entry *entry)
-{
-	size_t size = snprintf(NULL, 0, "%s",
-			       icalcomponent_get_description(entry->component));
-
-	assert(size != -1);
-	return size;
-}
-
-// Gets last modified, and if not exists, falls back to dtstamp
-icaltimetype
-get_last_modified(struct journal_entry *entry)
-{
-	icalproperty *last_modified_prop = icalcomponent_get_next_property(
-	    entry->component, ICAL_LASTMODIFIED_PROPERTY);
-
-	if (last_modified_prop) {
-		icaltimetype ical_lastmodified =
-		    icalproperty_get_lastmodified(last_modified_prop);
-		return ical_lastmodified;
-	}
-	else {
-		return icalcomponent_get_dtstamp(entry->component);
-	}
-}
-
-uid_t
-get_node_uid(const struct tree_node *node)
-{
-	if (is_root_node(node)) {
-		return getuid();
-	}
-	return get_entry(node)->uid;
-}
-
-gid_t
-get_node_gid(const struct tree_node *node)
-{
-	if (is_root_node(node)) {
-		return getgid();
-	}
-	return get_entry(node)->gid;
-}
-
-struct journal_entry *
-copy_journal_entry(const struct journal_entry *src)
-{
-	struct journal_entry *copy = xcalloc(1, sizeof(struct journal_entry));
-
-	copy->filename = xstrdup(src->filename);
-	copy->filename_original = xstrdup(src->filename_original);
-	copy->component = icalcomponent_new_clone(src->component);
-	assert(copy->component);
-	copy->gid = src->gid;
-	copy->uid = src->uid;
-
-	return copy;
-}
-
-static int
-append_category_to_memstream(FILE *memstream, const char *category,
-			     bool is_first_category)
-{
-	int res = 0;
-	if (!is_first_category) {
-		res = fputs(",", memstream);
-		assert(res != EOF);
-	}
-
-	res = fputs(category, memstream);
-	assert(res != EOF);
-	return 0; // Success
-}
-
-// Returns comma separated list of categories for a file
-char *
-get_node_categories(const struct tree_node *node)
-{
-	const struct journal_entry *entry = node->data;
-	if (!entry) {
-		return NULL;
-	}
-
-	FILE *memstream = NULL;
-	char *result_buffer = NULL;
-	size_t buffer_size = 0;
-
-	memstream = open_memstream(&result_buffer, &buffer_size);
-
-	icalcomponent *inner = icalcomponent_get_inner(entry->component);
-
-	icalproperty *catp =
-	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
-	bool first_category = true;
-	while (catp) {
-		const char *category = icalproperty_get_categories(catp);
-		append_category_to_memstream(memstream, category,
-					     first_category);
-		catp = icalcomponent_get_next_property(
-		    inner, ICAL_CATEGORIES_PROPERTY);
-
-		first_category = false;
-	}
-
-	size_t res = fclose(memstream);
-	assert(res != EOF);
-
-	return result_buffer;
-}
-
-int
-delete_node_categories(const struct tree_node *node)
-{
-	const struct journal_entry *entry = node->data;
-	if (!entry) {
-		return -ENOENT;
-	}
-
-	icalcomponent *inner = icalcomponent_get_inner(entry->component);
-
-	icalproperty *catp =
-	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
-	// Delete old categories
-	while (catp) {
-		icalcomponent_remove_property(inner, catp);
-
-		catp = icalcomponent_get_next_property(
-		    inner, ICAL_CATEGORIES_PROPERTY);
-	}
-
-	return write_entry_to_ical_file(entry);
-}
-
-int
-set_node_categories(const struct tree_node *node, const char *new_categories,
-		    size_t s)
-{
-	const struct journal_entry *entry = node->data;
-	if (!entry) {
-		return -ENOENT;
-	}
-
-	icalcomponent *inner = icalcomponent_get_inner(entry->component);
-
-	icalproperty *catp =
-	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
-	// Delete old categories
-	while (catp) {
-		icalcomponent_remove_property(inner, catp);
-
-		catp = icalcomponent_get_next_property(
-		    inner, ICAL_CATEGORIES_PROPERTY);
-	}
-
-	if (s == 0) {
-		return write_entry_to_ical_file(entry);
-	}
-
-	char *null_terminated_categories = xmalloc(s + 1);
-	strncpy(null_terminated_categories, new_categories, s);
-	null_terminated_categories[s] = '\0';
-
-	icalproperty *new_cat_prop =
-	    icalproperty_new_categories(null_terminated_categories);
-	icalcomponent_add_property(inner, new_cat_prop);
-
-	return write_entry_to_ical_file(entry);
-}
-
-const char *
-format_ical_class(const enum icalproperty_class iclass)
-{
-	if (iclass == ICAL_CLASS_PRIVATE) {
-		return "private";
-	}
-	else if (iclass == ICAL_CLASS_PUBLIC) {
-		return "public";
-	}
-	else if (iclass == ICAL_CLASS_CONFIDENTIAL) {
-		return "confidential";
-	}
-	return NULL;
-}
-
-const icalproperty_class
-parse_ical_class(const char *input)
-{
-	if (strcmp(input, "private") == 0) {
-		return ICAL_CLASS_PRIVATE;
-	}
-	else if (strcmp(input, "public") == 0) {
-		return ICAL_CLASS_PUBLIC;
-	}
-	else if (strcmp(input, "confidential") == 0) {
-		return ICAL_CLASS_CONFIDENTIAL;
-	}
-	return ICAL_CLASS_X;
-}
-
-const char *
-get_node_class(const struct tree_node *node)
-{
-	const struct journal_entry *entry = node->data;
-	if (!entry) {
-		return "";
-	}
-
-	icalcomponent *inner = icalcomponent_get_inner(entry->component);
-
-	const icalproperty *classp =
-	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
-	const enum icalproperty_class classv = icalproperty_get_class(classp);
-
-	if (classp) {
-		return format_ical_class(classv);
-	}
-
-	return "";
-}
-
-int
-delete_node_class(const struct tree_node *node)
-{
-	const struct journal_entry *entry = node->data;
-	if (!entry) {
-		return -ENOENT;
-	}
-	icalcomponent *inner = icalcomponent_get_inner(entry->component);
-	icalproperty *prop =
-	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
-	icalcomponent_remove_property(inner, prop);
-
-	return write_entry_to_ical_file(entry);
-}
-
-int
-set_node_class(const struct tree_node *node, const icalproperty_class new_class)
-{
-	const struct journal_entry *entry = node->data;
-	if (!entry) {
-		return -ENOENT;
-	}
-
-	icalcomponent *inner = icalcomponent_get_inner(entry->component);
-
-	icalproperty *classp =
-	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
-	icalcomponent_remove_property(inner, classp);
-
-	icalproperty *new_class_prop = icalproperty_new_class(new_class);
-	icalcomponent_add_property(inner, new_class_prop);
-
-	return write_entry_to_ical_file(entry);
-}
-
-size_t
-write_entry_to_ical_file(const struct journal_entry *entry)
-{
-
-	char *filepath = get_original_filepath(entry);
-
-	// Always set last modified to the time we write
-	icalproperty *prop = icalcomponent_get_first_property(
-	    entry->component, ICAL_LASTMODIFIED_PROPERTY);
-	if (!prop) {
-		prop = icalproperty_new_lastmodified(get_ical_now());
-		icalcomponent_add_property(entry->component, prop);
-	}
-	else {
-		icalproperty_set_lastmodified(prop, get_ical_now());
-	}
-
-	icalcomponent_set_status(entry->component, ICAL_STATUS_FINAL);
-
-	const char *descr_prop =
-	    icalcomponent_get_description(entry->component);
-	if (descr_prop != NULL && strcmp("", descr_prop) == 0) {
-		icalproperty *ical_descr_prop =
-		    icalcomponent_get_first_property(
-			icalcomponent_get_inner(entry->component),
-			ICAL_DESCRIPTION_PROPERTY);
-		icalcomponent_remove_property(
-		    icalcomponent_get_inner(entry->component), ical_descr_prop);
-	}
-
-	LOG("Set last modified");
-
-	char *ical_str = icalcomponent_as_ical_string_r(entry->component);
-
-	LOG("Got ical_str %s", ical_str);
-
-	int res = write_to_file(filepath, ical_str);
-
-	free(ical_str);
-	free(filepath);
-
-	return res;
-}
-
-size_t
-load_agendafs_environment(char *ics_dir_env)
-{
-	wordexp_t expanded;
-	if (wordexp(ics_dir_env, &expanded, 0) != 0 || expanded.we_wordc == 0) {
-		fprintf(stderr, "Failed to expand CALDAVFS_ICS_DIR\n");
-		wordfree(&expanded);
-		return -1;
-	}
-
-	char *expanded_path = expanded.we_wordv[0];
-
-	struct stat s;
-	if (stat(expanded_path, &s) == 0 && S_ISDIR(s.st_mode)) {
-		strlcpy(ICS_DIR, expanded_path, sizeof(ICS_DIR));
-		wordfree(&expanded);
-		return 0;
-	}
-
-	fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
-		expanded_path);
-	return -1;
-}
-
-int
-do_journal_entry_rename(const char *old, const char *new)
-{
-	char *new_copy = xstrdup(new);
-	const char *new_filename = get_filename(new_copy);
-
-	const char *old_filename = get_filename(old);
-	if (!old_filename)
-		return -EINVAL;
-
-	char *parent_path = NULL;
-	get_parent_path(new, &parent_path);
-	LOG("PARENT PATH: %s", parent_path);
-	struct tree_node *new_parent_node = get_node_by_path(parent_path);
-	free(parent_path);
-
-	if (new_parent_node == NULL) {
-		return -EINVAL;
-	}
-
-	char *new_summary = xstrdup(new_filename);
-
-	// Remove everything after extension
-	// TODO: Store the extension as a separate field in the ics file
-	char *dot = strrchr(new_summary, '.');
-	if (dot) {
-		*dot = '\0';
-	}
-
-	LOG("Summary is now %s", new_summary);
-	struct tree_node *entry_node = get_node_by_path(old);
-	assert(entry_node);
-
-	struct tree_node *old_parent_node = entry_node->parent;
-
-	struct journal_entry *old_entry = entry_node->data;
-	assert(old_entry);
-
-	struct journal_entry *new_entry = copy_journal_entry(old_entry);
-	free(parent_path);
-
-	// TODO: Filename must be unique
-	size_t wRes = asprintf(&new_entry->filename, "%s", new_filename);
-	assert(wRes != -1);
-
-	LOG("New filename is now %s", new_filename);
-	LOG("Removed original entry from fuse");
-	LOG("Inserting %s", new_entry->filename_original);
-	hashmap_insert(entries_original_ics, new_entry->filename_original,
-		       entry_node);
-
-	icalcomponent_set_summary(new_entry->component, new_summary);
-	entry_node->data = new_entry;
-	move_node(old_parent_node, new_parent_node, entry_node);
-
-	free_journal_entry(old_entry);
-	free(new_copy);
-
-	return 0;
-}
-
-//  uuid-caldavfs
-char *
-create_new_unique_ics_uid()
-{
-	uuid_t uuid;
-	char uuid_str[37]; // UUIDs are 36 characters + null terminator
-	uuid_generate(uuid);
-	uuid_unparse(uuid, uuid_str);
-	char *res = NULL;
-	size_t wRes = asprintf(&res, "%s-caldavfs", uuid_str);
-	assert(wRes != -1);
-
-	return res;
-}
-
-icalcomponent *
-create_vjournal_entry(char *summary)
-{
-
-	icalcomponent *calendar = icalcomponent_new_vcalendar();
-
-	icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
-	icalcomponent_add_property(calendar,
-				   icalproperty_new_prodid("-//caldavfs//EN"));
-
-	// Step 2: Create a VJOURNAL entry
-	icalcomponent *journal = icalcomponent_new_vjournal();
-	char *id = create_new_unique_ics_uid();
-
-	icalcomponent_add_property(journal, icalproperty_new_uid(id));
-	icalcomponent_add_property(journal,
-				   icalproperty_new_class(ICAL_CLASS_PRIVATE));
-
-	icalcomponent_add_property(
-	    journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
-			 icaltimezone_get_utc_timezone())));
-	icalcomponent_add_property(journal, icalproperty_new_summary(summary));
-	icalcomponent_add_property(journal, icalproperty_new_description(""));
-
-	icalcomponent_add_component(calendar, journal);
-
-	return calendar;
-}
-
-icalcomponent *
-create_vjournal_directory(char *summary)
-{
-
-	icalcomponent *calendar = icalcomponent_new_vcalendar();
-
-	icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
-	icalcomponent_add_property(calendar,
-				   icalproperty_new_prodid("-//caldavfs//EN"));
-
-	icalcomponent *journal = icalcomponent_new_vjournal();
-	char *id = create_new_unique_ics_uid();
-
-	icalcomponent_add_property(journal, icalproperty_new_uid(id));
-
-	icalcomponent_add_property(
-	    journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
-			 icaltimezone_get_utc_timezone())));
-	icalcomponent_add_property(journal, icalproperty_new_summary(summary));
-
-	// Empty description; unused
-	icalcomponent_add_property(journal, icalproperty_new_description(""));
-
-	icalproperty *is_directory = icalproperty_new_x(IS_DIRECTORY_PROPERTY);
-	icalproperty_set_x_name(is_directory, IS_DIRECTORY_PROPERTY);
-	icalproperty_set_x(is_directory, "YES");
-	icalcomponent_add_property(journal, is_directory);
-
-	icalcomponent_add_component(calendar, journal);
-	return calendar;
-}
-
-bool
-match_by_uid(icalcomponent *component, const char *target_uuid)
-{
-	const char *uid = icalcomponent_get_uid(component);
-	return uid && strcmp(uid, target_uuid) == 0;
-}
-
-// TODO: Handle duplicates in filename
-int
-create_entry_from_fuse(const char *fuse_path)
-{
-	// TODO: Move to path.c
-	const char *new_basename = get_filename(fuse_path);
-
-	path *without_extension = without_file_extension(new_basename);
-
-	struct journal_entry *new_entry =
-	    xcalloc(1, sizeof(struct journal_entry));
-	icalcomponent *vjournal_component =
-	    create_vjournal_entry(without_extension);
-	free(without_extension);
-
-	new_entry->component = vjournal_component;
-	new_entry->filename = strdup(new_basename);
-	if (asprintf(&new_entry->filename_original, "%s.ics",
-		     icalcomponent_get_uid(new_entry->component)) == -1) {
-		LOG("Failed to create journal entry");
-		free_journal_entry(new_entry);
-
-		return -ENOMEM;
-	}
-	LOG("Inserting vjournal entry");
-
-	struct tree_node *new_node = create_file_node(new_entry);
-	char *parent_path = NULL;
-	get_parent_path(fuse_path, &parent_path);
-	LOG("Parent path is %s", parent_path);
-	struct tree_node *parent = get_node_by_path(parent_path);
-	free(parent_path);
-
-	if (!parent) {
-		LOG("Parent is not node");
-		return -ENOENT;
-	}
-	if (!node_is_directory(parent)) {
-		LOG("Parent is not directory ");
-		return -ENOTDIR;
-	}
-
-	if (add_child_to_node(parent, new_node) != 0) {
-		return -EIO;
-	}
-
-	hashmap_insert(entries_original_ics, new_entry->filename_original,
-		       new_node);
-
-	return write_entry_to_ical_file(new_entry);
-}
-
-int
-create_directory_from_fuse_path(const char *fuse_path)
-{
-	const char *entry_prefix = "/";
-	if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
-		return -EINVAL;
-	}
-	char *new_basename = strrchr(fuse_path, '/');
-	new_basename++;
-
-	struct journal_entry *new_entry =
-	    xcalloc(1, sizeof(struct journal_entry));
-	icalcomponent *vjournal_component =
-	    create_vjournal_directory(new_basename);
-
-	new_entry->component = vjournal_component;
-	new_entry->filename = strdup(new_basename);
-	if (asprintf(&new_entry->filename_original, "%s.ics",
-		     icalcomponent_get_uid(new_entry->component)) == -1) {
-		LOG("Failed to create journal entry");
-		free_journal_entry(new_entry);
-
-		return -ENOMEM;
-	}
-	LOG("Inserting vjournal directory");
-
-	struct tree_node *new_node = create_file_node(new_entry);
-
-	char *parent_path = NULL;
-	get_parent_path(fuse_path, &parent_path);
-	LOG("Parent path is %s", parent_path);
-	struct tree_node *parent = get_node_by_path(parent_path);
-	free(parent_path);
-
-	if (!parent) {
-		LOG("Parent is not node");
-		return -ENOENT;
-	}
-	if (!node_is_directory(parent)) {
-		LOG("Parent is not directory ");
-		return -ENOTDIR;
-	}
-
-	add_child_to_node(parent, new_node);
-	hashmap_insert(entries_original_ics, new_entry->filename_original,
-		       new_node);
-
-	if (write_entry_to_ical_file(new_entry) != 0) {
-		LOG("Write to entry failed");
-		return -EIO;
-	}
-
-	LOG("DIRECTORY CREATED");
-	return 0;
-}
-
-// TODO: Add return status
-void
-update_or_create_fuse_entry_from_original(const char *filepath_original)
-{
-	const char *filename_original = get_filename(filepath_original);
-	LOG("Filename original is %s", filename_original);
-
-	struct journal_entry *updated_entry =
-	    xcalloc(1, sizeof(struct journal_entry));
-
-	if (load_journal_entry_from_ics_file(filename_original,
-					     updated_entry) != 0) {
-		LOG("PARSING FAILED");
-		return;
-	}
-
-	struct tree_node *new_or_updated_node =
-	    hashmap_get(entries_original_ics, filename_original);
-
-	if (new_or_updated_node) {
-		LOG("Updating existing entry");
-		update_node_data(new_or_updated_node, updated_entry);
-	}
-	else {
-		LOG("Creating new entry");
-		new_or_updated_node = create_file_node(updated_entry);
-		hashmap_insert(entries_original_ics,
-			       updated_entry->filename_original,
-			       new_or_updated_node);
-	}
-
-	const char *parent_uid = get_parent_uid(updated_entry->component);
-	LOG("Looking up updated_entry %s, %s", updated_entry->filename_original,
-	    updated_entry->filename);
-	if (parent_uid) {
-		LOG("Has parent");
-		struct tree_node *parent = get_node_by_uuid(parent_uid);
-		if (parent) {
-			struct tree_node *current_parent =
-			    new_or_updated_node->parent;
-
-			if (current_parent != NULL &&
-			    strcmp(get_node_ical_uuid(current_parent),
-				   get_node_ical_uuid(parent)) == 0) {
-				LOG("Already set, not moving");
-			}
-			else {
-				move_node(new_or_updated_node->parent, parent,
-					  new_or_updated_node);
-			}
-		}
-		else {
-			LOG("Parent not found, adding to root");
-			add_child(fuse_tree_root, new_or_updated_node);
-		}
-	}
-	else {
-		LOG("Does not have parent");
-		detach_tree_node(new_or_updated_node);
-		add_child(fuse_tree_root, new_or_updated_node);
-	}
-
-	LOG("File updated");
-}
-
-int
-delete_dir_from_fuse_path(const char *filepath)
-{
-	int res = 0;
-	struct tree_node *node = get_node_by_path(filepath);
-	if (!node) {
-		LOG("Node not found");
-		return -EIO;
-	}
-	if (!node_is_directory(node)) {
-		return -ENOTDIR;
-	}
-	if (node->child_count > 0) {
-		return -ENOTEMPTY;
-	}
-
-	struct journal_entry *entry = get_entry(node);
-	char *filepath_original = get_original_filepath(entry);
-	if (!filepath_original) {
-		LOG("Entry not found");
-		return -EIO;
-	}
-	LOG("Deleting file %s, located at %s", filepath, filepath_original);
-
-	res = remove(filepath_original);
-	if (res != 0) {
-		LOG("Failed to delete file");
-		return -res;
-	}
-
-	hashmap_remove(entries_original_ics, filepath_original);
-
-	detach_tree_node(node);
-	free_tree(node);
-	return res;
-}
-
-// Deletes original file too!
-int
-delete_from_fuse_path(const char *filepath)
-{
-	int res = 0;
-	struct tree_node *node = get_node_by_path(filepath);
-	if (!node) {
-		return -EIO;
-	}
-	if (node_is_directory(node)) {
-		return -EISDIR;
-	}
-	struct journal_entry *entry = get_entry(node);
-	char *filepath_original = get_original_filepath(entry);
-	if (!filepath_original) {
-		LOG("Entry not found");
-		return -EIO;
-	}
-	LOG("Deleting file %s, located at %s", filepath, filepath_original);
-
-	res = remove(filepath_original);
-
-	hashmap_remove(entries_original_ics, filepath_original);
-
-	detach_tree_node(node);
-	free_tree(node);
-	return res;
-}
-
-int
-delete_from_original_path(const char *filepath)
-{
-	// Strip path, validate just the new basename
-	const char *keyname = strrchr(filepath, '/');
-	keyname++;
-	struct journal_entry *entry =
-	    hashmap_get(entries_original_ics, keyname);
-	if (!entry) {
-		return -EIO;
-	}
-
-	// TODO: Fix so it finds by actual UUID, not filepath
-	struct tree_node *node = get_node_by_uuid(filepath);
-
-	// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
-	// Instead, update the children so they have a new parent
-	// which is the parent node.
-	// Requires a change_parent function.
-	detach_tree_node(node);
-	free_tree(node);
-	hashmap_remove(entries_original_ics, keyname);
-	return 0;
-}
diff --git a/journal_entry.h b/journal_entry.h
@@ -1,190 +0,0 @@
-#ifndef JOURNAL_ENTRY_H
-#define JOURNAL_ENTRY_H
-#include "hashmap.h"
-#include "util.h"
-#include <dirent.h>
-#include <libical/ical.h>
-#include <pthread.h>
-#include <regex.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/inotify.h>
-#include <sys/stat.h>
-#include <time.h>
-#include <unistd.h>
-#include <uuid/uuid.h>
-#include <wordexp.h>
-
-// TODO: Move this to a file structure to a separate module, refactor all
-// operations to use the module exposed files
-// Such as
-// - rename
-// - remove
-// - get filename
-// et.c.
-struct journal_entry {
-	// filename is full path relative to fuse directory
-	// I.E. hello world
-	char *filename;
-	// filename_original is the relative path to ICS_DIR
-	// I.E. 910319208nrao19p.ics
-	char *filename_original;
-	uid_t uid;
-	gid_t gid;
-
-	icalcomponent *component;
-};
-
-extern char ICS_DIR[256];
-
-extern struct tree_node *fuse_tree_root;
-
-// journal_entry
-extern struct hashmap *entries_original_ics;
-
-// Full filepath of original ics file
-char *
-get_original_filepath(const struct journal_entry *entry);
-
-void
-free_journal_entry(void *val);
-
-const char *
-get_filename(const char *full_path);
-
-struct journal_entry *
-get_entry_from_fuse_path(const char *path);
-
-icalcomponent *
-parse_ics_file(const char *filename);
-
-// We assume that there is only one entry in the file
-int
-parse_ics_to_journal_entry_component(const char *filename,
-				     struct journal_entry *entry);
-
-int
-do_journal_entry_rename(const char *old, const char *new_file);
-
-size_t
-parse_entry_content(struct journal_entry *entry, const char *buf, size_t size);
-
-size_t
-write_entry_to_ical_file(const struct journal_entry *entry);
-
-// Returns -1 on failure, 0 on success
-int
-load_journal_entry_from_ics_file(const char *filename,
-				 struct journal_entry *entry);
-
-// We assume that there is only one entry in the file
-int
-parse_ics_to_journal_entry_component(const char *filename,
-				     struct journal_entry *entry);
-struct tree_node *
-find_node_by_path(struct tree_node *root, const char *path);
-
-struct journal_entry *
-get_entry(const struct tree_node *node);
-
-int
-delete_dir_from_fuse_path(const char *filepath);
-
-bool
-node_is_directory(const struct tree_node *node);
-
-int
-delete_from_fuse_path(const char *filepath);
-
-int
-create_entry_from_fuse(const char *filename);
-int
-delete_from_original_path(const char *filepath);
-
-// Returns -1 on failure, 0 on success
-int
-load_journal_entry_from_ics_file(const char *filename,
-				 struct journal_entry *entry);
-
-icalcomponent *
-create_vjournal_directory(char *summary);
-
-// Initializes
-// entries_original_key
-// entries_fuse_key
-// Should only be run once
-void
-load_journal_entries();
-
-icaltimetype
-get_ical_now();
-
-size_t
-write_entry_content(char *buf, struct journal_entry *entry);
-
-size_t
-get_entry_content_size(struct journal_entry *entry);
-
-// Gets last modified, and if not exists, falls back to dtstamp
-icaltimetype
-get_last_modified(struct journal_entry *entry);
-
-// TODO: Probably better to calculate this on demand
-int
-build_today_hashmap();
-
-struct tree_node *
-get_node_by_path(const char *path);
-
-const icalproperty_class
-parse_ical_class(const char *);
-
-const char *
-get_node_class(const struct tree_node *node);
-
-int
-delete_node_class(const struct tree_node *node);
-
-int
-set_node_class(const struct tree_node *node,
-	       const icalproperty_class new_class);
-
-char *
-get_node_categories(const struct tree_node *);
-
-int
-delete_node_categories(const struct tree_node *);
-
-int
-set_node_categories(const struct tree_node *, const char *, size_t s);
-
-int
-build_notes_hashmap();
-
-size_t
-load_agendafs_environment(char *dir);
-char *
-create_new_unique_ics_uid();
-
-icalcomponent *
-create_vjournal_entry(char *summary);
-
-int
-create_directory_from_fuse_path(const char *fuse_path);
-
-uid_t
-get_node_uid(const struct tree_node *node);
-
-gid_t
-get_node_gid(const struct tree_node *node);
-
-void
-update_or_create_fuse_entry_from_original(const char *filename);
-
-int
-update_fuse_entry_delete(const char *file);
-
-#endif // JOURNAL_ENTRY_H
-
diff --git a/main.c b/main.c
@@ -1,7 +1,8 @@
+#include "mregion.h"
 #define FUSE_USE_VERSION 31
 
+#include "agenda_entry.h"
 #include "hashmap.h"
-#include "journal_entry.h"
 #include "tree.h"
 #include "util.h"
 #include <dirent.h>
@@ -31,12 +32,15 @@ static int
 journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 {
 	pthread_mutex_lock(&entries_mutex);
+	memory_region *mreg = create_region();
 	LOG("STAT %s", path);
 
+	LOG("memset");
 	memset(stbuf, 0, sizeof(struct stat));
 
 	int ret_code = 0;
 
+	LOG("strcmp");
 	if (strcmp(path, "/") == 0) {
 		stbuf->st_mode = S_IFDIR | 0444;
 		stbuf->st_nlink = 2;
@@ -47,15 +51,17 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 		goto unlock_and_return;
 	}
 
-	const struct tree_node *node = get_node_by_path(path);
+	LOG("Getting node");
+	const struct tree_node *node = get_node_by_path(mreg, path);
 	if (!node) {
 		LOG("Entry not found");
 		ret_code = -ENOENT;
 		goto unlock_and_return;
 	}
 
-	struct journal_entry *entry = get_entry(node);
-	if (node_is_directory(node)) {
+	LOG("icalcomponent from node");
+	icalcomponent *ic = get_icalcomponent_from_node(mreg, node);
+	if (node_is_directory(mreg, node, ic)) {
 		stbuf->st_mode = S_IFDIR | 0444;
 		stbuf->st_uid = get_node_uid(node);
 		stbuf->st_gid = get_node_gid(node);
@@ -70,12 +76,11 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 		stbuf->st_nlink = 1;
 	}
 
-	int size = get_entry_content_size(entry);
+	int size = get_entry_content_size(ic);
 
 	stbuf->st_size = (off_t)size;
 
-	LOG("Entry found: %s", entry->filename);
-	icaltimetype ical_last_modified = get_last_modified(entry);
+	icaltimetype ical_last_modified = get_last_modified(ic);
 
 	time_t last_modified = icaltime_as_timet(ical_last_modified);
 
@@ -87,6 +92,7 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 
 unlock_and_return:
 	pthread_mutex_unlock(&entries_mutex);
+	rfree_all(mreg);
 	return ret_code;
 }
 
@@ -104,6 +110,7 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
 {
 
 	pthread_mutex_lock(&entries_mutex);
+	memory_region *mreg = create_region();
 	LOG("READDIR %s", path);
 	filler(buf, ".", NULL, 0, 0);
 	filler(buf, "..", NULL, 0, 0);
@@ -112,11 +119,11 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
 	if (strcmp(path, "/") == 0) {
 		LOG("IS ROOT");
 
-		for (size_t i = 0; i < fuse_tree_root->child_count; i++) {
+		for (size_t i = 0; i < fuse_root->child_count; i++) {
 
-			const struct tree_node *node =
-			    fuse_tree_root->children[i];
-			struct journal_entry *entry = node->data;
+			const struct tree_node *node = fuse_root->children[i];
+			icalcomponent *ic =
+			    get_icalcomponent_from_node(mreg, node);
 
 			struct stat st = {0};
 			// TODO: Fix
@@ -126,21 +133,21 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
 			st.st_uid = get_node_uid(node);
 			st.st_gid = get_node_gid(node);
 
-			if (node_is_directory(node)) {
+			if (node_is_directory(mreg, node, ic)) {
 				st.st_mode = S_IFDIR | 0755;
 			}
 
-			filler(buf, entry->filename, &st, 0, 0);
+			filler(buf, get_node_filename(mreg, node), &st, 0, 0);
 		}
 	}
 	else {
-		const struct tree_node *node = get_node_by_path(path);
+		const struct tree_node *node = get_node_by_path(mreg, path);
 		if (node) {
 			for (size_t i = 0; i < node->child_count; i++) {
-				LOG("FOUND CHILD");
 				const struct tree_node *child =
 				    node->children[i];
-				struct journal_entry *entry = child->data;
+				icalcomponent *ic =
+				    get_icalcomponent_from_node(mreg, child);
 
 				struct stat st = {0};
 				// TODO: Fix
@@ -150,42 +157,50 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
 				st.st_uid = get_node_uid(child);
 				st.st_gid = get_node_gid(child);
 
-				if (node_is_directory(child)) {
+				if (node_is_directory(mreg, child, ic)) {
 					st.st_mode = S_IFDIR | 0755;
 				}
 
-				filler(buf, entry->filename, &st, 0, 0);
+				filler(buf, get_node_filename(mreg, child), &st,
+				       0, 0);
 			}
 		}
 		else {
 			pthread_mutex_unlock(&entries_mutex);
+			rfree_all(mreg);
 			return -ENOENT;
 		}
 	}
 	pthread_mutex_unlock(&entries_mutex);
+	rfree_all(mreg);
 	return 0;
 }
 
 static int
 journal_open(const char *path, struct fuse_file_info *fi)
 {
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
 	LOG("Opening journal for %s", path);
-	struct journal_entry *entry = get_entry_from_fuse_path(path);
+	const struct tree_node *node = get_node_by_path(mreg, path);
 	pthread_mutex_unlock(&entries_mutex);
-	return entry ? 0 : -ENOENT;
+	rfree_all(mreg);
+	return node ? 0 : -ENOENT;
 }
 
 static int
 journal_mkdir(const char *path, mode_t mode)
 {
+
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
 	LOG("MKDIR %s", path);
-	if (create_directory_from_fuse_path(path) != 0) {
+	if (create_directory_from_fuse_path(mreg, path) != 0) {
 		pthread_mutex_unlock(&entries_mutex);
 		return -EIO;
 	};
 	pthread_mutex_unlock(&entries_mutex);
+	rfree_all(mreg);
 	return 0;
 }
 
@@ -194,18 +209,19 @@ journal_read(const char *path, char *buf, size_t size, off_t offset,
 	     struct fuse_file_info *fi)
 {
 	int ret_code = 0;
+
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
 	LOG("READ on path %s", path);
 
-	struct journal_entry *entry = get_entry_from_fuse_path(path);
-	if (!entry) {
+	const struct tree_node *n = get_node_by_path(mreg, path);
+	if (!n) {
 		ret_code = -ENOENT;
 		goto unlock_and_return;
 	}
-	LOG("entry = %p", entry);
-	LOG("entry->component = %p", entry->component);
-	const char *description =
-	    icalcomponent_get_description(entry->component);
+	LOG("entry = %p", n);
+	icalcomponent *ic = get_icalcomponent_from_node(mreg, n);
+	const char *description = icalcomponent_get_description(ic);
 	if (!description) {
 		description = "";
 	}
@@ -236,6 +252,7 @@ journal_read(const char *path, char *buf, size_t size, off_t offset,
 
 unlock_and_return:
 	pthread_mutex_unlock(&entries_mutex);
+	rfree_all(mreg);
 	return ret_code;
 }
 
@@ -243,29 +260,34 @@ static int
 journal_write(const char *path, const char *buf, size_t size, off_t offset,
 	      struct fuse_file_info *fi)
 {
-	if (size > 60 * 1024) {
-		return -EFBIG;
-	}
 
+	LOG("WRITE %s, %zu, %zu", buf, size, offset);
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
-	struct journal_entry *entry = get_entry_from_fuse_path(path);
-	if (!entry) {
+	const struct tree_node *node = get_node_by_path(mreg, path);
+	if (!node) {
 		pthread_mutex_unlock(&entries_mutex);
+		rfree_all(mreg);
 		return -ENOENT;
 	}
 
-	LOG("Parsing entry content");
-	int ret = parse_entry_content(entry, buf, size);
-	if (ret != 0) {
-		pthread_mutex_unlock(&entries_mutex);
-		return ret;
-	}
+	icalcomponent *ic = get_icalcomponent_from_node(mreg, node);
+	const char *old_desc = icalcomponent_get_description(ic);
+	if (!old_desc)
+		old_desc = "";
+
+	char *new_desc = rstrins(mreg, old_desc, offset, buf, size);
 
-	if (write_entry_to_ical_file(entry) != 0) {
+	icalcomponent_set_description(ic, new_desc);
+
+	if (write_ical_file(mreg, node, ic) != 0) {
 		pthread_mutex_unlock(&entries_mutex);
+		rfree_all(mreg);
 		return -EIO;
 	}
 	pthread_mutex_unlock(&entries_mutex);
+	rfree_all(mreg);
+
 	return size;
 }
 
@@ -273,60 +295,60 @@ journal_write(const char *path, const char *buf, size_t size, off_t offset,
 static int
 journal_unlink(const char *file)
 {
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
 	LOG("DELETE %s", file);
 	int res = 0;
 
-	res = delete_from_fuse_path(file);
-	if (res != 0) {
-		LOG("Failed to delete file");
-		pthread_mutex_unlock(&entries_mutex);
-		return -EIO;
-	}
-
-	LOG("Delete successful");
+	res = delete_from_fuse_path(mreg, file);
+	LOG("Delete");
 	pthread_mutex_unlock(&entries_mutex);
-	return 0;
+	rfree_all(mreg);
+	return res;
 }
 
 static int
 journal_rmdir(const char *file)
 {
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
 	LOG("DELETE %s", file);
 	int res = 0;
 
-	res = delete_dir_from_fuse_path(file);
-	if (res != 0) {
-		LOG("Failed to delete file");
-		pthread_mutex_unlock(&entries_mutex);
-		return res;
-	}
+	res = delete_dir_from_fuse_path(mreg, file);
 
 	LOG("Delete successful");
 	pthread_mutex_unlock(&entries_mutex);
-	return 0;
+	rfree_all(mreg);
+	return res;
 }
 
 static int
 journal_rename(const char *old, const char *new, unsigned int flags)
 {
+
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
 	LOG("RENAME %s -> %s", old, new);
-	int res = do_journal_entry_rename(old, new);
+
+	int res = do_agenda_rename(mreg, old, new);
 
 	pthread_mutex_unlock(&entries_mutex);
+	rfree_all(mreg);
 	return res;
 }
 
 static int
 journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
 {
+
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
 	LOG("CREATE %s", filename);
 	// TODO: Handle mode and fuse_file_info
-	create_entry_from_fuse(filename);
+	create_entry_from_fuse(mreg, filename);
 	pthread_mutex_unlock(&entries_mutex);
+	rfree_all(mreg);
 	return 0;
 }
 
@@ -334,33 +356,38 @@ static int
 journal_removexattr(const char *path, const char *header)
 {
 	LOG("REMOVEXATTR '%s' '%s'", path, header);
+	memory_region *mreg = create_region();
 
 	if (strcmp(header, "user.categories") == 0) {
 		pthread_mutex_lock(&entries_mutex);
-		struct tree_node *node = get_node_by_path(path);
+		struct tree_node *node = get_node_by_path(mreg, path);
 		if (!node) {
 			pthread_mutex_unlock(&entries_mutex);
+
+			rfree_all(mreg);
 			return -ENONET;
 		}
 
-		int status = set_node_categories(node, "", 0);
+		int status = set_node_categories(mreg, node, "", 0);
 		pthread_mutex_unlock(&entries_mutex);
 
 		return status;
 	}
 	else if (strcmp(header, "user.class") == 0) {
 		pthread_mutex_lock(&entries_mutex);
-		struct tree_node *node = get_node_by_path(path);
+		struct tree_node *node = get_node_by_path(mreg, path);
 		if (!node) {
 			pthread_mutex_unlock(&entries_mutex);
+			rfree_all(mreg);
 			return -ENONET;
 		}
 
-		int status = delete_node_class(node);
+		int status = delete_node_class(mreg, node);
 		pthread_mutex_unlock(&entries_mutex);
 
 		return status;
 	}
+	rfree_all(mreg);
 
 	return -EINVAL;
 }
@@ -371,26 +398,30 @@ journal_setxattr(const char *path, const char *header,
 {
 	LOG("SETXATTR '%s' '%s' '%zu' '%s' '%d'", path, header, s,
 	    new_attributes, flags);
+	memory_region *mreg = create_region();
 
 	if (strcmp(header, "user.categories") == 0) {
 		pthread_mutex_lock(&entries_mutex);
-		struct tree_node *node = get_node_by_path(path);
+		struct tree_node *node = get_node_by_path(mreg, path);
 		if (!node) {
 			pthread_mutex_unlock(&entries_mutex);
+			rfree_all(mreg);
 
 			return -ENONET;
 		}
 
-		int status = set_node_categories(node, new_attributes, s);
+		int status = set_node_categories(mreg, node, new_attributes, s);
 		pthread_mutex_unlock(&entries_mutex);
 
 		return status;
 	}
 	else if (strcmp(header, "user.class") == 0) {
 		pthread_mutex_lock(&entries_mutex);
-		struct tree_node *node = get_node_by_path(path);
+		struct tree_node *node = get_node_by_path(mreg, path);
 		if (!node) {
 			pthread_mutex_unlock(&entries_mutex);
+
+			rfree_all(mreg);
 			return -ENONET;
 		}
 
@@ -399,15 +430,19 @@ journal_setxattr(const char *path, const char *header,
 
 		if (iclass == ICAL_CLASS_X) {
 			pthread_mutex_unlock(&entries_mutex);
+
+			rfree_all(mreg);
 			return -EINVAL;
 		}
 
-		int status = set_node_class(node, iclass);
+		int status = set_node_class(mreg, node, iclass);
 		pthread_mutex_unlock(&entries_mutex);
+		rfree_all(mreg);
 
 		return status;
 	}
 
+	rfree_all(mreg);
 	return -EINVAL;
 }
 
@@ -419,10 +454,12 @@ journal_listxattr(const char *path, char *list, size_t size)
 	char *membuffer = NULL;
 	size_t membuffer_len = 0;
 
+	memory_region *mreg = create_region();
 	pthread_mutex_lock(&entries_mutex);
-	struct tree_node *node = get_node_by_path(path);
+	struct tree_node *node = get_node_by_path(mreg, path);
 	if (!node) {
 		pthread_mutex_unlock(&entries_mutex);
+
 		return -ENONET;
 	}
 
@@ -431,14 +468,14 @@ journal_listxattr(const char *path, char *list, size_t size)
 		pthread_mutex_unlock(&entries_mutex);
 		return -EIO;
 	}
-	char *cats = get_node_categories(node);
+	char *cats = get_node_categories(mreg, node);
 	if (cats != NULL && strcmp("", cats) != 0) {
 		fprintf(stream, "user.categories");
 		fputc('\0', stream);
 	}
 	free(cats);
 
-	const char *iclass = get_node_class(node);
+	const char *iclass = get_node_class(mreg, node);
 	if (iclass != NULL && strcmp("", iclass) != 0) {
 		fprintf(stream, "user.class");
 		fputc('\0', stream);
@@ -470,17 +507,19 @@ static int
 journal_getxattr(const char *path, const char *header, char *buf, size_t s)
 {
 	LOG("GETXATTR '%s' '%s' '%zu'\n", path, header, s);
+	fuse_get_context();
+	memory_region *mreg = create_region();
 
 	if (strcmp(header, "user.categories") == 0) {
 		pthread_mutex_lock(&entries_mutex);
-		struct tree_node *node = get_node_by_path(path);
+		struct tree_node *node = get_node_by_path(mreg, path);
 		if (!node) {
 			pthread_mutex_unlock(&entries_mutex);
 
 			return -ENONET;
 		}
 
-		const char *cats = get_node_categories(node);
+		const char *cats = get_node_categories(mreg, node);
 		pthread_mutex_unlock(&entries_mutex);
 
 		if (!cats || *cats == '\0') {
@@ -499,14 +538,14 @@ journal_getxattr(const char *path, const char *header, char *buf, size_t s)
 	}
 	else if (strcmp(header, "user.class") == 0) {
 		pthread_mutex_lock(&entries_mutex);
-		struct tree_node *node = get_node_by_path(path);
+		struct tree_node *node = get_node_by_path(mreg, path);
 		if (!node) {
 			pthread_mutex_unlock(&entries_mutex);
 
 			return -ENONET;
 		}
 
-		const char *c = get_node_class(node);
+		const char *c = get_node_class(mreg, node);
 		pthread_mutex_unlock(&entries_mutex);
 		if (!c || *c == '\0') {
 			return -ENODATA;
@@ -568,24 +607,34 @@ watch_ics_dir(void *arg)
 			if (event->len && strstr(event->name, ".ics")) {
 				LOG("Detected change in ICS file: %s\n",
 				    event->name);
+				memory_region *mreg = create_region();
 
 				if (event->mask & IN_DELETE) {
 					pthread_mutex_lock(&entries_mutex);
-					delete_from_original_path(full_path);
+					delete_from_original_path(mreg,
+								  full_path);
+					rfree_all(mreg);
 					pthread_mutex_unlock(&entries_mutex);
 				}
 				else if (event->mask & IN_MODIFY) {
 					pthread_mutex_lock(&entries_mutex);
+					LOG("RUNNING IN_MODIFY HOOK");
 					update_or_create_fuse_entry_from_original(
-					    full_path);
+					    mreg, full_path);
+					rfree_all(mreg);
 					pthread_mutex_unlock(&entries_mutex);
 				}
 				else if (event->mask & IN_CREATE) {
 					pthread_mutex_lock(&entries_mutex);
+					LOG("RUNNING IN_CREATE HOOK");
 					update_or_create_fuse_entry_from_original(
-					    full_path);
+					    mreg, full_path);
+					rfree_all(mreg);
 					pthread_mutex_unlock(&entries_mutex);
 				}
+				else {
+					rfree_all(mreg);
+				}
 			}
 			free(full_path);
 
@@ -706,8 +755,7 @@ main(int argc, char *argv[])
 		return 1;
 	}
 
-	fuse_tree_root = create_tree_node(NULL, NULL);
-	load_journal_entries();
+	load_root_node_tree();
 	int ret = fuse_main(args.argc, args.argv, &journal_oper, NULL);
 	LOG("Cleaning up");
 
@@ -716,7 +764,7 @@ main(int argc, char *argv[])
 	LOG("Pthread freed");
 
 	hashmap_free(entries_original_ics);
-	free_tree(fuse_tree_root);
+	free_tree(fuse_root);
 	LOG("Hashmap and tree freed");
 
 	LOG("Regexp freed");
diff --git a/mregion.c b/mregion.c
@@ -0,0 +1,204 @@
+#include "mregion.h"
+#include "util.h"
+#include <assert.h>
+#include <libical/ical.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef void (*free_func_t)(void *);
+
+memory_region *
+create_region(void)
+{
+	memory_region *region = xmalloc(sizeof(memory_region));
+	region->head = NULL;
+	return region;
+}
+
+void
+region_register(memory_region *region, void *ptr, free_func_t free_func)
+{
+	assert(free_func);
+	memory_block *block = xmalloc(sizeof(memory_block));
+	block->ptr = ptr;
+	block->free_func = free_func;
+	block->next = region->head;
+	region->head = block;
+}
+
+void *
+rmalloc(memory_region *region, size_t size)
+{
+	void *ptr = xmalloc(size);
+	region_register(region, ptr, free);
+	return ptr;
+}
+
+void
+rfree_all(memory_region *region)
+{
+	if (!region)
+		return;
+	memory_block *block = region->head;
+	while (block) {
+		memory_block *next = block->next;
+		if (block->free_func)
+			block->free_func(block->ptr);
+		free(block);
+		block = next;
+	}
+	region->head = NULL;
+	free(region);
+}
+
+// String functions
+char *
+rstrdup(memory_region *region, const char *str)
+{
+	char *copy = xstrdup(str);
+	region_register(region, copy, free);
+	return copy;
+}
+
+char *
+rstrins(memory_region *region, const char *str, size_t offset, const char *buf,
+	size_t size)
+{
+	size_t str_len = strlen(str);
+
+	size_t previous_new_len = (offset <= str_len) ? offset : str_len;
+
+	char *result = NULL;
+	size_t result_len = 0;
+
+	FILE *stream = open_memstream(&result, &result_len);
+	assert(stream);
+
+	fwrite(str, 1, previous_new_len, stream);
+	fwrite(buf, 1, size, stream);
+
+	fclose(stream);
+
+	region_register(region, result, free);
+
+	return result;
+}
+
+int
+rasprintf(memory_region *region, char **strp, const char *fmt, ...)
+{
+	// Calculate length
+	va_list args;
+	va_start(args, fmt);
+	int len = vsnprintf(NULL, 0, fmt, args);
+	va_end(args);
+
+	if (len < 0) {
+		*strp = NULL;
+		return -1;
+	}
+
+	char *buffer = rmalloc(region, len + 1);
+	va_start(args, fmt);
+	int ret = vsnprintf(buffer, len + 1, fmt, args);
+	va_end(args);
+	if (ret < 0) {
+		*strp = NULL;
+		return -1;
+	}
+	*strp = buffer;
+	return ret;
+}
+
+void
+rappend(char **dest, const char *fmt, ...)
+{
+	va_list args;
+
+	// Determine formatted size
+	va_start(args, fmt);
+	int add_len = vsnprintf(NULL, 0, fmt, args);
+	va_end(args);
+
+	if (add_len < 0)
+		return;
+
+	size_t old_len = *dest ? strlen(*dest) : 0;
+	char *newbuf = realloc(*dest, old_len + add_len + 1);
+	if (!newbuf)
+		return;
+
+	// Write formatted string to end
+	va_start(args, fmt);
+	vsnprintf(newbuf + old_len, add_len + 1, fmt, args);
+	va_end(args);
+
+	*dest = newbuf;
+}
+
+// iCal functions
+
+icalparser *
+ricalparser_new(memory_region *region)
+{
+	icalparser *p = icalparser_new();
+	region_register(region, p, (void *)icalparser_free);
+	return p;
+}
+
+void
+ricalcomponent_free(icalcomponent *ic)
+{
+	if (ic)
+		icalcomponent_free(ic);
+}
+
+icalcomponent *
+ricalcomponent_empty(memory_region *region)
+{
+	icalcomponent *ic = NULL;
+	region_register(region, ic, (void *)ricalcomponent_free);
+	return ic;
+}
+
+icalcomponent *
+ricalcomponent_new_clone(memory_region *region, icalcomponent *ic)
+{
+	icalcomponent *nic = icalcomponent_new_clone(ic);
+	region_register(region, nic, (void *)ricalcomponent_free);
+	return nic;
+}
+
+icalcomponent *
+ricalcomponent_new_from_string(memory_region *region, const char *ic)
+{
+	icalcomponent *nic = icalcomponent_new_from_string(ic);
+	region_register(region, nic, (void *)ricalcomponent_free);
+	return nic;
+}
+
+char *
+ricalcomponent_as_ical_string(memory_region *region, icalcomponent *ic)
+{
+	char *c = icalcomponent_as_ical_string(ic);
+	region_register(region, c, free);
+	return c;
+}
+
+char *
+ricalcomponent_as_ical_string_r(memory_region *region, icalcomponent *ic)
+{
+	char *c = icalcomponent_as_ical_string_r(ic);
+	region_register(region, c, free);
+	return c;
+}
+
+icalcomponent *
+ricalcomponent_new_vcalendar(memory_region *region)
+{
+	icalcomponent *new_comp = icalcomponent_new_vcalendar();
+	region_register(region, new_comp, (void *)ricalcomponent_free);
+	return new_comp;
+}
diff --git a/mregion.h b/mregion.h
@@ -0,0 +1,73 @@
+#ifndef mregion_h_INCLUDED
+#define mregion_h_INCLUDED
+#include "util.h"
+#include <assert.h>
+#include <libical/ical.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef void (*free_func_t)(void *);
+
+typedef struct memory_block {
+	void *ptr;
+	free_func_t free_func;
+	struct memory_block *next;
+} memory_block;
+
+typedef struct {
+	memory_block *head;
+} memory_region;
+
+memory_region *
+create_region(void);
+
+void
+region_register(memory_region *region, void *ptr, free_func_t free_func);
+
+void *
+rmalloc(memory_region *region, size_t size);
+
+void
+rfree_all(memory_region *region);
+
+char *
+rstrins(memory_region *region, const char *str, size_t offset, const char *buf,
+	size_t size);
+// String functions
+char *
+rstrdup(memory_region *region, const char *str);
+
+int
+rasprintf(memory_region *region, char **strp, const char *fmt, ...);
+
+void
+rappend(char **dest, const char *fmt, ...);
+
+// iCal functions
+
+icalparser *
+ricalparser_new(memory_region *region);
+
+void
+ricalcomponent_free(icalcomponent *ic);
+
+icalcomponent *
+ricalcomponent_empty(memory_region *region);
+
+icalcomponent *
+ricalcomponent_new_clone(memory_region *region, icalcomponent *ic);
+
+icalcomponent *
+ricalcomponent_new_from_string(memory_region *region, const char *ic);
+
+char *
+ricalcomponent_as_ical_string(memory_region *region, icalcomponent *ic);
+
+char *
+ricalcomponent_as_ical_string_r(memory_region *region, icalcomponent *ic);
+
+icalcomponent *
+ricalcomponent_new_vcalendar(memory_region *region);
+#endif // mrgeion_h_INCLUDED
diff --git a/path.c b/path.c
@@ -1,5 +1,5 @@
 #include "path.h"
-#include "util.h"
+#include "mregion.h"
 #include <assert.h>
 #include <errno.h>
 #include <stdio.h>
@@ -7,33 +7,26 @@
 #include <string.h>
 
 path *
-without_file_extension(const path *p)
+without_file_extension(memory_region *m, const path *p)
 {
-	char *cpy = xstrdup(p);
+	char *cpy = rstrdup(m, p);
 	char *last_dot = strrchr(cpy, '.');
 	char *last_slash = strrchr(cpy, '/');
 
-	// Only remove the extension if dot is after last slash
+	// Only remove the extension if dot is after
 	if (last_dot && (!last_slash || last_dot > last_slash)) {
 		*last_dot = '\0';
 	}
-
 	return cpy;
 }
 
-void
-free_segments(char **segments, size_t count)
-{
-	for (size_t i = 0; i < count; ++i)
-		free(segments[i]);
-}
-
 path *
-append_path(const path *parentp, const char *childp)
+append_path(memory_region *m, const path *parentp, const char *childp)
 {
 	char *filepath = NULL;
 	size_t res = asprintf(&filepath, "%s/%s", parentp, childp);
 	assert(res != -1);
+	region_register(m, filepath, free);
 	return filepath;
 }
 
@@ -46,35 +39,29 @@ get_filename(const char *full_path)
 	return base_name + 1;
 }
 
-size_t
-get_parent_path(const char *path, char **buffer)
+char *
+get_parent_path(memory_region *mreg, const char *path)
 {
-	char *path_copy = strdup(path);
+	char *path_copy = rstrdup(mreg, path);
 
 	char *last_slash = strrchr(path_copy, '/');
 	if (last_slash == NULL) {
-		free(path_copy);
-		return 0;
+		return NULL;
 	}
 
 	// If they are the same, it means the parent is root
 	if (strcmp(last_slash, path_copy) == 0) {
-		int result = asprintf(buffer, "/");
-		free(path_copy);
-		return result;
+		return "/";
 	}
-	*last_slash = '\0';
-	int result = asprintf(buffer, "%s", path_copy);
-	assert(result != -1);
-	free(path_copy);
 
-	return result;
+	*last_slash = '\0';
+	return path_copy;
 }
 
 size_t
-split_path(const path *p, char **segments)
+split_path(memory_region *m, const path *p, char **segments)
 {
-	char *path_copy = xstrdup(p);
+	char *path_copy = rstrdup(m, p);
 
 	size_t count = 0;
 	char *segment;
@@ -82,11 +69,9 @@ split_path(const path *p, char **segments)
 	char *saveptr;
 	segment = strtok_r(path_copy, "/", &saveptr);
 	while (segment) {
-		segments[count++] = xstrdup(segment);
+		segments[count++] = rstrdup(m, segment);
 		segment = strtok_r(NULL, "/", &saveptr);
 	}
-	free(path_copy);
-
 	return count;
 }
 
diff --git a/path.h b/path.h
@@ -1,6 +1,9 @@
 #ifndef path_h_INCLUDED
 #define path_h_INCLUDED
+#include "mregion.h"
 #include "util.h"
+#include <assert.h>
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -8,21 +11,21 @@
 typedef char path;
 
 path *
-without_file_extension(const path *);
-
-void
-free_segments(path **, size_t);
+without_file_extension(memory_region *m, const path *p);
 
 path *
-append_path(const path *, const char *);
+append_path(memory_region *m, const path *parentp, const char *childp);
 
 const path *
-get_filename(const path *);
+get_filename(const char *full_path);
+
+char *
+get_parent_path(memory_region *mreg, const char *path);
 
 size_t
-get_parent_path(const char *, char **);
-size_t
-split_path(const path *, char **);
+split_path(memory_region *m, const path *p, char **segments);
+
 size_t
-write_to_file(const char *filepath, const char *content);
+write_to_file(const path *filepath, const char *content);
+
 #endif // path_h_INCLUDED
diff --git a/tree.c b/tree.c
@@ -1,4 +1,5 @@
 #include "tree.h"
+#include "mregion.h"
 #include "util.h"
 #include <stdbool.h>
 #include <stdio.h>
@@ -20,6 +21,22 @@ create_tree_node(void *data, void (*free_fn)(void *))
 	return node;
 }
 
+struct tree_node *
+rcreate_tree_node(void *data, memory_region *m)
+{
+	struct tree_node *node = rmalloc(m, sizeof(struct tree_node));
+	if (!node)
+		return NULL;
+	node->data = data;
+	node->parent = NULL;
+	node->children = NULL;
+	node->child_count = 0;
+	node->child_capacity = 0;
+	node->free_fn = NULL;
+
+	return node;
+}
+
 size_t
 add_child(struct tree_node *parent, struct tree_node *child)
 {
@@ -97,8 +114,6 @@ detach_tree_node(struct tree_node *node)
 			break;
 		}
 	}
-	if (index == (size_t)-1)
-		return false; // not found, shouldn't happen
 
 	// Shift remaining children to fill the gap
 	for (size_t i = index + 1; i < parent->child_count; ++i) {
diff --git a/tree.h b/tree.h
@@ -1,6 +1,7 @@
 #ifndef tree_h
 #define tree_h
 
+#include "mregion.h"
 #include <stdbool.h>
 #include <stdlib.h>
 
@@ -18,6 +19,9 @@ struct tree_node {
 struct tree_node *
 create_tree_node(void *data, void (*free_fn)(void *));
 
+struct tree_node *
+rcreate_tree_node(void *data, memory_region *m);
+
 bool
 node_has_children(const struct tree_node *node);
 
diff --git a/util.c b/util.c
@@ -1,5 +1,6 @@
 // Copyright © 2019-2024 Michael Forney
 // util.c from cproc
+#include "util.h"
 #include <assert.h>
 #include <errno.h>
 #include <stdarg.h>
@@ -15,8 +16,10 @@ xmalloc(size_t len)
 	void *buf;
 
 	buf = malloc(len);
-	if (!buf && len)
+	if (!buf && len) {
+		LOG("FAILED TO MALLOC FOR %zu", len);
 		exit(1);
+	}
 
 	return buf;
 }