agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 0e1a87120d934b61c81efe2a865cf1931875e167
parent 2cc5c4798900180da0cf7fc5a98e62d039db0a51
Author: Marc Coquand <marc@coquand.email>
Date: Mon, 28 Jul 2025 11:53:31 +0200
Bugfixes; refactor; handle duplicate names
Closes: https://todo.sr.ht/~marcc/agendafs/8
Diffstat:
8 files changed, 192 insertions(+), 129 deletions(-)
diff --git a/fuse_node.c b/fuse_node.c
@@ -69,6 +69,9 @@ get_node_by_path(memory_region *mreg, const char *path)
break;
}
}
+ if (next == NULL) {
+ return NULL;
+ }
current = next;
}
LOG("Done");
@@ -266,7 +269,7 @@ load_root_node_tree()
struct tree_node *parent =
get_node_by_uuid(mreg, parent_uid);
if (parent) {
- move_node(parent, child);
+ move_fuse_node(parent, child);
}
else {
LOG("COULD NOT FIND PARENT NODE: %s",
@@ -274,7 +277,7 @@ load_root_node_tree()
}
}
else {
- add_child(fuse_root, child);
+ add_fuse_child(fuse_root, child);
}
}
LOG("Inserted %zu entries", n_keys);
@@ -522,14 +525,11 @@ insert_fuse_node_to_path(memory_region *mreg, const char *fuse_path,
int
create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
{
- // TODO: We are mixing some responsibilities here.
- // Any function that operates on the global state should not
- // have memory_region as a parameter, as that makes it easier
- // to mix up the contexts.
const char *entry_prefix = "/";
if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
return -EINVAL;
}
+
const char *new_filename = get_filename(fuse_path);
icalcomponent *new_component =
create_vjournal_entry(mreg, new_filename);
@@ -638,41 +638,6 @@ update_or_create_fuse_entry_from_vdir(memory_region *mreg,
}
int
-delete_dir_from_fuse_path(memory_region *mreg, const char *filepath)
-{
- 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;
- }
-
- const char *filepath_original = get_vdir_filepath(mreg, node);
- if (!filepath_original) {
- LOG("Entry not found");
- return -EIO;
- }
- LOG("Deleting file %s, located at %s", filepath, filepath_original);
-
- int res = remove(filepath_original);
- if (res != 0) {
- LOG("Failed to delete file");
- return -res;
- }
-
- delete_fuse_node(node);
-
- return 0;
-}
-
-int
do_agenda_rename(memory_region *mreg, const char *old, const char *new)
{
char *new_copy = rstrdup(mreg, new);
@@ -691,6 +656,7 @@ do_agenda_rename(memory_region *mreg, const char *old, const char *new)
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);
@@ -723,21 +689,13 @@ do_agenda_rename(memory_region *mreg, const char *old, const char *new)
}
int
-delete_from_fuse_path(memory_region *mreg, const char *filepath)
+delete_vdir_entry(memory_region *mreg, struct tree_node *node)
{
int res = 0;
- struct tree_node *node = get_node_by_path(mreg, filepath);
- if (!node) {
- return -ENOENT;
- }
- icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
- if (node_is_directory(mreg, node, node_ics)) {
- return -EISDIR;
- }
const char *filepath_original = get_vdir_filepath(mreg, node);
- LOG("Deleting file %s, located at %s", filepath, filepath_original);
+ LOG("Deleting file %s", filepath_original);
res = remove(filepath_original);
if (res == -1) {
@@ -757,6 +715,8 @@ delete_from_vdir_path(memory_region *mreg, const char *filepath)
return -ENOENT;
}
+ LOG("Found node to delete");
+
// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
// Instead, update the children so they have a new parent
// which is the parent node.
diff --git a/fuse_node.h b/fuse_node.h
@@ -112,10 +112,7 @@ int
do_agenda_rename(memory_region *mreg, const char *, const char *);
int
-delete_from_fuse_path(memory_region *mreg, const char *filepath);
-
-char *
-get_fuse_path_from_vdir(memory_region *mreg, const char *filepath);
+delete_vdir_entry(memory_region *mreg, struct tree_node *node);
int
delete_from_vdir_path(memory_region *mreg, const char *filepath);
diff --git a/fuse_node_store.c b/fuse_node_store.c
@@ -1,5 +1,6 @@
#include "agenda_entry.h"
#include "hashmap.h"
+#include "path.h"
#include "tree.h"
#include "util.h"
#include <stddef.h>
@@ -41,6 +42,13 @@ get_entry(const struct tree_node *node)
return node->data;
}
+const char *
+oget_node_filename(struct tree_node *node)
+{
+ const struct agenda_entry *entry = get_entry(node);
+ return entry->filename;
+}
+
bool
is_root_node(const struct tree_node *node)
{
@@ -57,50 +65,6 @@ get_node_filename(memory_region *mreg, const struct tree_node *node)
}
const char *
-get_node_path(memory_region *mreg, struct tree_node *node)
-{
- if (!node->parent) {
- return "/";
- }
-
- struct tree_node *curr = node;
- char *result = NULL;
- size_t result_len = 0;
-
- FILE *stream = open_memstream(&result, &result_len);
- assert(stream);
-
- do {
- if (!is_root_node(curr)) {
- const char *filename = get_node_filename(mreg, curr);
- fprintf(stream, "/%s", filename);
- }
-
- curr = curr->parent;
-
- } while (curr->parent);
-
- fclose(stream);
- if (result) {
- region_register(mreg, result, free);
- }
-
- return result;
-}
-
-const char *
-get_fuse_path_from_vdir(memory_region *mreg, const char *filepath_vdir)
-{
- struct tree_node *node =
- hashmap_get(entries_vdir, get_filename(filepath_vdir));
-
- if (node) {
- return get_node_path(mreg, node);
- }
- return NULL;
-}
-
-const char *
get_vdir_filepath(memory_region *mreg, const struct tree_node *node)
{
const struct agenda_entry *entry = get_entry(node);
@@ -145,3 +109,45 @@ upsert_fuse_node(const struct agenda_entry *entry)
return node;
}
+// Add fuse child to node.
+// OBS: Modifies filename if not unique!
+size_t
+add_fuse_child(struct tree_node *parent, struct tree_node *child)
+{
+ const char *filename = oget_node_filename(child);
+
+ char *new_filename = xstrdup(filename);
+
+ size_t conflict_count = 1;
+
+ while (true) {
+ bool is_unique = true;
+ for (size_t i = 0; i < parent->child_count; i++) {
+ const char *sibling_filename =
+ oget_node_filename(parent->children[i]);
+
+ if (strcmp(new_filename, sibling_filename) == 0) {
+ is_unique = false;
+ break;
+ }
+ }
+
+ if (is_unique) {
+ break;
+ }
+
+ free(new_filename);
+ new_filename = filename_numbered(filename, conflict_count++);
+ }
+
+ set_node_filename(child, new_filename);
+
+ return add_child(parent, child);
+}
+
+size_t
+move_fuse_node(struct tree_node *new_parent, struct tree_node *child)
+{
+ detach_tree_node(child);
+ return add_fuse_child(new_parent, child);
+}
diff --git a/fuse_node_store.h b/fuse_node_store.h
@@ -35,9 +35,6 @@ is_root_node(const struct tree_node *node);
const char *
get_node_filename(memory_region *mreg, const struct tree_node *node);
-const char *
-get_node_path(memory_region *mreg, struct tree_node *node);
-
int
set_node_filename(struct tree_node *node, const char *filename);
@@ -50,4 +47,10 @@ delete_fuse_node(struct tree_node *node);
struct tree_node *
upsert_fuse_node(const struct agenda_entry *entry);
+size_t
+add_fuse_child(struct tree_node *parent, struct tree_node *child);
+
+size_t
+move_fuse_node(struct tree_node *new_parent, struct tree_node *child);
+
#endif // fuse_node_store_h_INCLUDED
diff --git a/ical_extra.c b/ical_extra.c
@@ -119,11 +119,14 @@ get_ical_now()
size_t
get_entry_content_size(icalcomponent *component)
{
- size_t size =
- snprintf(NULL, 0, "%s", icalcomponent_get_description(component));
-
- assert(size != -1);
- return size;
+ const char *description = icalcomponent_get_description(component);
+ if (!description) {
+ return 0;
+ }
+ else {
+ size_t size = strlen(description);
+ return size;
+ }
}
char *
diff --git a/main.c b/main.c
@@ -131,6 +131,7 @@ fuse_open(const char *path, struct fuse_file_info *fi)
const struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
+ LOG("Entry not found");
status = -ENOENT;
}
@@ -160,44 +161,47 @@ static int
fuse_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
- LOG("READ %s", path);
-
+ LOG("READ called: offset=%ld, size=%zu", offset, size);
memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
- int ret_code = 0;
+ int ret_i = 0;
const struct tree_node *n = get_node_by_path(mreg, path);
if (!n) {
- ret_code = -ENOENT;
+ ret_i = -ENOENT;
goto cleanup_return;
}
icalcomponent *ic = get_icalcomponent_from_node(mreg, n);
+ if (!ic) {
+ ret_i = -EIO;
+ goto cleanup_return;
+ }
+
const char *description = icalcomponent_get_description(ic);
if (!description) {
- description = "";
+ goto cleanup_return;
}
size_t content_len = strlen(description);
if (offset >= content_len) {
- ret_code = 0;
goto cleanup_return;
}
- if (offset + size > content_len) {
- size = content_len - offset;
+ size_t bytes_to_read = content_len - offset;
+ if (bytes_to_read > size) {
+ bytes_to_read = size;
}
-
- if (size > 0) {
- memcpy(buf, description + offset, size);
+ if (bytes_to_read > 0) {
+ memcpy(buf, description + offset, bytes_to_read);
}
- ret_code = size;
+ ret_i = bytes_to_read;
cleanup_return:
pthread_mutex_unlock(&entries_mutex);
rfree_all(mreg);
- return ret_code;
+ return ret_i;
}
static int
@@ -249,24 +253,54 @@ fuse_unlink(const char *file)
pthread_mutex_lock(&entries_mutex);
int res = 0;
- res = delete_from_fuse_path(mreg, file);
+ struct tree_node *node = get_node_by_path(mreg, file);
+ if (!node) {
+ res = -ENOENT;
+ goto cleanup_return;
+ }
+
+ icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+ if (node_is_directory(mreg, node, node_ics)) {
+ res = -EISDIR;
+ goto cleanup_return;
+ }
+
+ res = delete_vdir_entry(mreg, node);
+cleanup_return:
pthread_mutex_unlock(&entries_mutex);
rfree_all(mreg);
return res;
}
static int
-fuse_rmdir(const char *file)
+fuse_rmdir(const char *filepath)
{
- LOG("DELETE %s", file);
+ LOG("DELETE %s", filepath);
memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
int res = 0;
- res = delete_dir_from_fuse_path(mreg, file);
+ struct tree_node *node = get_node_by_path(mreg, filepath);
+ if (!node) {
+ res = -ENOENT;
+ goto cleanup_return;
+ }
+
+ icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+ if (!node_is_directory(mreg, node, node_ics)) {
+ res = -ENOTDIR;
+ goto cleanup_return;
+ }
+
+ if (node->child_count > 0) {
+ return -ENOTEMPTY;
+ }
+ res = delete_vdir_entry(mreg, node);
+
+cleanup_return:
pthread_mutex_unlock(&entries_mutex);
rfree_all(mreg);
return res;
@@ -279,28 +313,57 @@ fuse_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 = 0;
+
+ struct tree_node *existing_node = get_node_by_path(mreg, new);
+ if (existing_node && node_has_children(existing_node)) {
+ res = -ENOTEMPTY;
+ goto cleanup_return;
+ }
+
+ if (existing_node) {
+ if (delete_vdir_entry(mreg, existing_node) != 0) {
+ res = -EIO;
+ goto cleanup_return;
+ }
+ }
- int res = do_agenda_rename(mreg, old, new);
+ res = do_agenda_rename(mreg, old, new);
+cleanup_return:
pthread_mutex_unlock(&entries_mutex);
rfree_all(mreg);
return res;
}
static int
-fuse_create(const char *filename, mode_t mode, struct fuse_file_info *info)
+fuse_create(const char *filepath, mode_t mode, struct fuse_file_info *info)
{
memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
- LOG("CREATE %s", filename);
+ LOG("CREATE %s", filepath);
+
+ int status = 0;
+
+ if (pathIsHidden(filepath)) {
+ status = -EPERM;
+ goto cleanup_return;
+ }
+
+ struct tree_node *node = get_node_by_path(mreg, filepath);
+ if (node) {
+ status = -EEXIST;
+ goto cleanup_return;
+ }
// TODO: Handle mode and fuse_file_info
- create_entry_from_fuse(mreg, filename);
+ create_entry_from_fuse(mreg, filepath);
+cleanup_return:
pthread_mutex_unlock(&entries_mutex);
rfree_all(mreg);
- return 0;
+ return status;
}
static int
diff --git a/path.c b/path.c
@@ -103,3 +103,31 @@ write_to_file(const path *filepath, const char *content)
fclose(f);
return 0;
}
+
+// Creates a new path
+// my-file.txt -> my-file.1.txt
+// Caller is responsible for freeing memory
+path *
+filename_numbered(const char *filename, size_t n)
+{
+ const char *ext = strrchr(filename, '.');
+ char *new_filename;
+ size_t len = 0;
+
+ FILE *stream = open_memstream(&new_filename, &len);
+ assert(stream);
+
+ if (ext) {
+ size_t base_len = ext - filename;
+ fwrite(filename, 1, base_len, stream);
+ fprintf(stream, ".%zu%s", n, ext);
+ }
+ else {
+ fprintf(stream, "%s.%zu", filename, n);
+ }
+
+ fclose(stream);
+
+ return new_filename;
+}
+
diff --git a/path.h b/path.h
@@ -35,4 +35,7 @@ split_path(memory_region *m, const path *p, char **segments);
size_t
write_to_file(const path *filepath, const char *content);
+path *
+filename_numbered(const char *filename, size_t n);
+
#endif // path_h_INCLUDED