agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 3e9ad1f5addff1205b8b90f1a1790dfe459abec0
parent e807aad93c181a99b256dd97212f74704357d59c
Author: Marc Coquand <marc@coquand.email>
Date: Sat, 31 May 2025 22:16:28 +0100
fix memory leaks
Diffstat:
| M | Makefile | | | 5 | +++-- |
| M | main.c | | | 539 | +++++++++++++++++++++++++++++++++++++++++++++---------------------------------- |
2 files changed, 311 insertions(+), 233 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,5 +1,6 @@
CC = gcc
-CFLAGS = -Wall -g
+CFLAGS = -Wall -O3
+CFLAGS_DEBUG = -Wall -g
LIBS = `pkg-config fuse3 --cflags --libs`
all: journalfs
@@ -8,7 +9,7 @@ journalfs: main.c hashmap.c
$(CC) $(CFLAGS) main.c hashmap.c -o journalfs $(LIBS)
debug: main.c hashmap.c
- $(CC) $(CFLAGS) $(DEBUG_FLAGS) main.c hashmap.c -o journalfs-debug $(LIBS)
+ $(CC) $(CFLAGS_DEBUG) $(DEBUG_FLAGS) main.c hashmap.c -o journalfs-debug $(LIBS)
clean:
rm -f journalfs journalfs-debug
diff --git a/main.c b/main.c
@@ -23,7 +23,9 @@
#define EVENT_BUF_LEN (1024 * (sizeof(struct inotify_event) + 16))
const char *display_date_fmt = "%b %d, %Y at %H:%M";
-const char *date_title_fmt = "%Y%m%d-%H%M%S";
+const char *date_title_fmt = "%Y%m%d-%H:%M";
+regex_t regex_summary, regex_description, regex_dtstamp, regex_lastmod,
+ regex_uid;
typedef struct {
char *filename;
@@ -35,46 +37,26 @@ typedef struct {
time_t created_at;
} journal_entry;
-static journal_entry *entries = NULL;
static size_t entry_count = 0;
-struct hashmap *entries_map = NULL;
-static pthread_mutex_t entries_mutex = PTHREAD_MUTEX_INITIALIZER;
-
-static void free_entry(journal_entry *entry)
-{
- if (entry) {
+struct hashmap *entries_fuse_key = NULL;
+struct hashmap *entries_original_key = NULL;
- free(entry->uid);
- free(entry->filename);
- free(entry->filename_original);
- free(entry->summary);
- free(entry->description);
- }
-}
+static pthread_mutex_t entries_mutex = PTHREAD_MUTEX_INITIALIZER;
void free_journal_entry(void *val)
{
journal_entry *entry = (journal_entry *)val;
if (!entry)
return;
- free(entry->filename_original);
+ free(entry->uid);
free(entry->filename);
+ free(entry->filename_original);
free(entry->summary);
free(entry->description);
free(entry);
}
-static void free_all_entries()
-{
- for (size_t i = 0; i < entry_count; i++) {
- free_entry(&entries[i]);
- }
- free(entries);
- entries = NULL;
- entry_count = 0;
-}
-
static time_t parse_datetime(const char *datetime)
{
struct tm tm = {0};
@@ -116,12 +98,12 @@ static int compile_regexes(regex_t *regex_summary, regex_t *regex_description,
static char *extract_match(const char *content, regmatch_t match)
{
size_t len = match.rm_eo - match.rm_so;
- char *str = calloc(len + 1, sizeof(char));
- if (!str)
+ char *m_str = calloc(len + 1, sizeof(char));
+ if (!m_str)
return NULL;
- memcpy(str, content + match.rm_so, len);
- str[len] = '\0';
- return str;
+ memcpy(m_str, content + match.rm_so, len);
+ m_str[len] = '\0';
+ return m_str;
}
// RFC 5545 specifies how human-readable text is escaped
@@ -221,7 +203,7 @@ static journal_entry *get_entry_from_path(const char *path)
}
LOG("get_entry_from_path: looking up key '%s'\n", key);
- journal_entry *entry = hashmap_get(entries_map, key);
+ journal_entry *entry = hashmap_get(entries_fuse_key, key);
if (!entry)
LOG("get_entry_from_path: no entry found for key '%s'\n", key);
return entry;
@@ -250,19 +232,9 @@ static int parse_ics_file(const char *file_path, journal_entry *entry)
file_content[file_size] = '\0'; // Null-terminate the string
fclose(file);
- regex_t regex_summary, regex_description, regex_dtstamp, regex_lastmod,
- regex_uid;
regmatch_t matches_summary[2], matches_description[2],
matches_dtstamp[2], matches_lastmod[2], matches_uid[2];
- size_t return_code = -1;
-
- if (compile_regexes(®ex_summary, ®ex_description, ®ex_dtstamp,
- ®ex_lastmod, ®ex_uid) != 0) {
- fprintf(stderr, "Failed to compile regular expressions\n");
- goto cleanup;
- }
-
// SUMMARY
if (regexec(®ex_summary, file_content, 2, matches_summary, 0) != 0) {
fprintf(stderr, "SUMMARY field not found in: %s\n", file_path);
@@ -342,102 +314,168 @@ static int parse_ics_file(const char *file_path, journal_entry *entry)
entry->timestamp = entry->created_at;
}
- // Success and goto cleanup
- return_code = 0;
+ // Success
+
+ free(file_content);
+ return 0;
cleanup:
+ // Cleanup in case of error
+ if (entry->summary)
+ free(entry->summary);
+ if (entry->description)
+ free(entry->description);
+ if (entry->uid)
+ free(entry->uid);
free(file_content);
- regfree(®ex_summary);
- regfree(®ex_description);
- regfree(®ex_dtstamp);
- regfree(®ex_lastmod);
- return return_code;
+ return -1;
+}
+
+// Dynamically create a time string using format
+char *time_string(time_t time_val, const char *format)
+{
+ struct tm tm_info;
+ if (!localtime_r(&time_val, &tm_info)) {
+ return NULL;
+ }
+
+ // Allocate a reasonably large buffer
+ size_t buf_size = 64;
+ char *buffer = malloc(buf_size);
+ if (!buffer)
+ return NULL;
+
+ size_t len = strftime(buffer, buf_size, format, &tm_info);
+ if (len == 0) {
+ free(buffer);
+ return NULL; // Formatting failed (buffer too small)
+ }
+ return buffer; // caller must free this
+}
+
+// Uses created_at as filename.
+// However, in case of collission, set the add 1,2 et.c.
+char *get_unique_filename(time_t created_at, struct hashmap *entries_map)
+{
+ char *filename_time_str = time_string(created_at, date_title_fmt);
+ if (!filename_time_str) {
+ fprintf(stderr, "Failed to format date string\n");
+ return NULL;
+ }
+
+ // Prepare base filename leaving room for suffix and extension
+ char base_filename[128];
+ size_t base_len = strnlen(filename_time_str, strlen(filename_time_str));
+ if (base_len > sizeof(base_filename) - 12) {
+ base_len = sizeof(base_filename) - 12;
+ }
+ strncpy(base_filename, filename_time_str, base_len);
+ base_filename[base_len] = '\0';
+
+ free(filename_time_str);
+
+ // Attempt filename and increment suffix if needed
+ char unique_filename[140];
+ snprintf(unique_filename, sizeof(unique_filename), "%s.txt",
+ base_filename);
+
+ int suffix = 1;
+ while (hashmap_get(entries_map, unique_filename) != NULL) {
+ snprintf(unique_filename, sizeof(unique_filename), "%s_%d.txt",
+ base_filename, suffix++);
+ }
+
+ return strdup(unique_filename);
}
static void load_journal_entries()
{
pthread_mutex_lock(&entries_mutex);
- if (entries_map) {
- hashmap_free(entries_map);
+ if (entries_original_key) {
+ hashmap_free(entries_original_key);
+ }
+ entries_original_key = hashmap_new(NULL);
+
+ if (entries_fuse_key) {
+ hashmap_free(entries_fuse_key);
}
- entries_map = hashmap_new(free_journal_entry);
+ entries_fuse_key = hashmap_new(free_journal_entry);
LOG("Loading journal entries from: %s\n", ICS_DIR);
+
DIR *dir = opendir(ICS_DIR);
if (!dir) {
- pthread_mutex_unlock(&entries_mutex);
perror("opendir");
+ pthread_mutex_unlock(&entries_mutex);
return;
}
struct dirent *entry;
char filepath[512];
+
while ((entry = readdir(dir))) {
if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
snprintf(filepath, sizeof(filepath), "%s/%s", ICS_DIR,
entry->d_name);
+
+ // Allocate memory for a new journal entry
journal_entry *new_entry =
calloc(1, sizeof(journal_entry));
if (!new_entry) {
perror("calloc failed");
continue;
}
+
new_entry->filename_original = strdup(entry->d_name);
- if (parse_ics_file(filepath, new_entry) == 0) {
- LOG("Parsed entry");
-
- char filename_time_str[64];
- struct tm tm_info;
- localtime_r(&new_entry->created_at, &tm_info);
- strftime(filename_time_str,
- sizeof(filename_time_str),
- date_title_fmt, &tm_info);
-
- // Check for duplicate and append _N if exists
- char base_filename[128];
- strncpy(base_filename, filename_time_str,
- sizeof(base_filename));
- base_filename[sizeof(base_filename) - 12] =
- '\0'; // make space for suffix + .txt
-
- char unique_filename[140]; // 128 base + 12 for
- // _N.txt suffix max
- snprintf(unique_filename,
- sizeof(unique_filename), "%s.txt",
- base_filename);
-
- int suffix = 1;
- // Check if filename already exists in hashmap
- while (hashmap_get(entries_map,
- unique_filename) != NULL) {
- snprintf(unique_filename,
- sizeof(unique_filename),
- "%s_%d.txt", base_filename,
- suffix++);
- }
-
- LOG("Inserted entry %s", unique_filename);
-
- new_entry->filename = strdup(unique_filename);
- if (hashmap_insert(entries_map, unique_filename,
- new_entry) != 0) {
- LOG("Failed to insert entry %s into "
- "hashmap\n",
- unique_filename);
- free_journal_entry(new_entry);
- }
+ if (!new_entry->filename_original) {
+ perror("strdup failed");
+ free(new_entry);
+ continue;
}
- else {
+
+ // Parse the ICS file and check for errors
+ if (parse_ics_file(filepath, new_entry) != 0) {
+ LOG("Failed to parse entry: %s", entry->d_name);
+ free(new_entry->filename_original);
+ free(new_entry);
+ continue;
+ }
+
+ LOG("Parsed entry");
+
+ // Generate a unique filename for the journal entry
+ char *unique_filename = get_unique_filename(
+ new_entry->created_at, entries_fuse_key);
+ if (!unique_filename) {
+ LOG("Failed to generate unique filename for %s",
+ entry->d_name);
+ free(new_entry->filename_original);
+ free(new_entry);
+ continue;
+ }
+
+ new_entry->filename = unique_filename;
+
+ // Insert into hashmaps and handle errors
+ if (hashmap_insert(entries_fuse_key, unique_filename,
+ new_entry) != 0 ||
+ hashmap_insert(entries_original_key,
+ new_entry->filename_original,
+ new_entry) != 0) {
+ LOG("Failed to insert entry into one of the "
+ "hashmaps");
free(new_entry->filename);
+ free(new_entry->filename_original);
+ free(new_entry);
+ continue;
}
}
}
- closedir(dir);
+ closedir(dir);
pthread_mutex_unlock(&entries_mutex);
}
-
int journal_getattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi)
{
@@ -454,30 +492,22 @@ int journal_getattr(const char *path, struct stat *stbuf,
}
journal_entry *entry = get_entry_from_path(path);
- if (entry) {
- stbuf->st_mode = S_IFREG | 0444;
- stbuf->st_nlink = 1;
-
- struct tm tm_info;
- localtime_r(&entry->timestamp, &tm_info);
- char timestamp_str[64];
- strftime(timestamp_str, sizeof(timestamp_str), display_date_fmt,
- &tm_info);
-
- int size =
- snprintf(NULL, 0, "%s\n%s\n---\n%s\n", entry->summary,
- timestamp_str, entry->description);
- stbuf->st_size = (off_t)size;
-
- stbuf->st_mtime = entry->timestamp;
- stbuf->st_ctime = entry->timestamp;
- stbuf->st_atime = time(NULL);
-
- ret_code = 0;
+ if (!entry) {
+ ret_code = -ENOENT;
goto unlock_and_return;
}
+ stbuf->st_mode = S_IFREG | 0444;
+ stbuf->st_nlink = 1;
+
+ int size = snprintf(NULL, 0, "%s\n---\n%s", entry->summary,
+ entry->description);
+ stbuf->st_size = (off_t)size;
- ret_code = -ENOENT;
+ stbuf->st_mtime = entry->timestamp;
+ stbuf->st_ctime = entry->timestamp;
+ stbuf->st_atime = time(NULL);
+
+ ret_code = 0;
unlock_and_return:
pthread_mutex_unlock(&entries_mutex);
@@ -491,7 +521,7 @@ static int journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
pthread_mutex_lock(&entries_mutex);
size_t n_keys = 0;
- char **keys = hashmap_get_keys(entries_map, &n_keys);
+ char **keys = hashmap_get_keys(entries_fuse_key, &n_keys);
if (!keys) {
pthread_mutex_unlock(&entries_mutex);
return -ENOMEM;
@@ -530,17 +560,9 @@ static int journal_read(const char *path, char *buf, size_t size, off_t offset,
goto unlock_and_return;
}
- // Format timestamp
- struct tm tm_info;
- localtime_r(&entry->created_at, &tm_info);
- char timestamp_str[64];
- strftime(timestamp_str, sizeof(timestamp_str), display_date_fmt,
- &tm_info);
-
// Calculate required length for full content
- int needed_len =
- (size_t)snprintf(NULL, 0, "%s\n%s\n---\n%s\n", entry->summary,
- timestamp_str, entry->description);
+ int needed_len = (size_t)snprintf(NULL, 0, "%s\n---\n%s",
+ entry->summary, entry->description);
if (needed_len < 0) {
ret_code = -EIO;
goto unlock_and_return;
@@ -553,8 +575,8 @@ static int journal_read(const char *path, char *buf, size_t size, off_t offset,
goto unlock_and_return;
}
- snprintf(full_content, needed_len + 1, "%s\n%s\n---\n%s\n",
- entry->summary, timestamp_str, entry->description);
+ snprintf(full_content, needed_len + 1, "%s\n---\n%s", entry->summary,
+ entry->description);
if (offset >= needed_len) {
// EOF
@@ -576,57 +598,6 @@ unlock_and_return:
return ret_code;
}
-void *watch_ics_dir(void *arg)
-{
- int fd = inotify_init1(IN_NONBLOCK);
- if (fd < 0) {
- perror("inotify_init");
- return NULL;
- }
-
- int wd =
- inotify_add_watch(fd, ICS_DIR, IN_CREATE | IN_DELETE | IN_MODIFY);
- if (wd < 0) {
- perror("inotify_add_watch");
- close(fd);
- return NULL;
- }
-
- char buffer[EVENT_BUF_LEN];
-
- while (1) {
- ssize_t length = read(fd, buffer, EVENT_BUF_LEN);
- if (length < 0) {
- if (errno == EAGAIN || errno == EINTR) {
- sleep(1);
- continue;
- }
- perror("read");
- break;
- }
-
- size_t i = 0;
- while (i < (size_t)length) {
- struct inotify_event *event =
- (struct inotify_event *)&buffer[i];
-
- if (event->len && strstr(event->name, ".ics")) {
- printf("Detected change in ICS file: %s\n",
- event->name);
- load_journal_entries(); // Reload entries on any
- // change
- break;
- }
-
- i += sizeof(struct inotify_event) + event->len;
- }
- }
-
- inotify_rm_watch(fd, wd);
- close(fd);
- return NULL;
-}
-
char *journal_entry_to_ical(const journal_entry *entry)
{
char *escaped_summary =
@@ -681,65 +652,74 @@ char *journal_entry_to_ical(const journal_entry *entry)
return ical;
}
-static int journal_write(const char *path, const char *buf, size_t size,
- off_t offset, struct fuse_file_info *fi)
+int update_journal_from_input_buffer(journal_entry *entry, const char *buf,
+ size_t size)
{
- pthread_mutex_lock(&entries_mutex);
- journal_entry *entry = get_entry_from_path(path);
- if (!entry) {
- pthread_mutex_unlock(&entries_mutex);
- return -ENOENT;
- }
-
- // Only allow overwrite of the full file for simplicity
- if (offset != 0) {
- pthread_mutex_unlock(&entries_mutex);
- return -EINVAL;
- }
-
- // Make a copy of the input buffer and null-terminate
- char *content = strndup(buf, size);
- if (!content) {
- pthread_mutex_unlock(&entries_mutex);
+ char *copy = strndup(buf, size);
+ if (!copy)
return -ENOMEM;
- }
- // Parse new content: "<summary>\n<timestamp>\n---\n<description>"
char *summary = NULL, *description = NULL;
+ char *sep = strstr(copy, "\n---\n");
- char *sep = strstr(content, "---\n");
if (!sep) {
- free(content);
- pthread_mutex_unlock(&entries_mutex);
- return -EINVAL; // Bad format
+ free(copy);
+ return -EINVAL; // Malformed input
}
*sep = '\0';
- description = sep + 4;
+ description = sep + 5;
- // Split summary and timestamp
- char *newline = strchr(content, '\n');
- if (!newline) {
- free(content);
- pthread_mutex_unlock(&entries_mutex);
- return -EINVAL; // Missing timestamp
+ // Summary and timestamp part
+ char *summary_line = strtok(copy, "\n");
+ if (!summary_line) {
+ free(copy);
+ return -EINVAL;
+ }
+
+ summary = strdup(summary_line);
+ if (!summary) {
+ free(copy);
+ return -ENOMEM;
}
- *newline = '\0';
- summary = content;
+ // Optional: parse timestamp here if needed
- // Replace in-memory values
+ // Clean up old values
free(entry->summary);
free(entry->description);
- entry->summary = strdup(summary);
- entry->description = strdup(description);
- entry->timestamp = time(NULL);
+ entry->summary = summary;
+ entry->description = strdup(description ? description : "");
- // Write back to original .ics file
+ entry->timestamp = time(NULL); // Update modified time
+
+ free(copy);
+ return 0;
+}
+
+static int journal_write(const char *path, const char *buf, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ pthread_mutex_lock(&entries_mutex);
+ journal_entry *entry = get_entry_from_path(path);
+ if (!entry) {
+ pthread_mutex_unlock(&entries_mutex);
+ return -ENOENT;
+ }
+
+ // Update hashmap with input buffer
+ int ret = update_journal_from_input_buffer(entry, buf, size);
+ if (ret != 0) {
+ pthread_mutex_unlock(&entries_mutex);
+ return ret;
+ }
+
+ // Get filepath of original file
char filepath[512];
snprintf(filepath, sizeof(filepath), "%s/%s", ICS_DIR,
entry->filename_original);
+ // Write back to original .ics file
char *ical_str = journal_entry_to_ical(entry);
if (!ical_str) {
pthread_mutex_unlock(&entries_mutex);
@@ -769,20 +749,117 @@ static const struct fuse_operations journal_oper = {
.write = journal_write,
};
+void update_or_add_entry(const char *filename)
+{
+ pthread_mutex_lock(&entries_mutex);
+ const char *base_name = strrchr(filename, '/');
+ base_name = base_name ? base_name + 1 : filename;
+ journal_entry *entry = hashmap_get(entries_original_key, base_name);
+ journal_entry *new_entry = calloc(1, sizeof(journal_entry));
+ if (!new_entry) {
+ perror("calloc");
+ pthread_mutex_unlock(&entries_mutex);
+ return;
+ }
+
+ if (parse_ics_file(filename, new_entry) != 0) {
+ // Parsing failed, clean up and maybe remove old entry if any
+ free_journal_entry(new_entry);
+ pthread_mutex_unlock(&entries_mutex);
+ return;
+ }
+
+ new_entry->filename = strndup(entry->filename, strlen(entry->filename));
+ new_entry->filename_original =
+ strndup(entry->filename_original, strlen(entry->filename));
+
+ hashmap_insert(entries_fuse_key, strdup(filename), new_entry);
+
+ pthread_mutex_unlock(&entries_mutex);
+}
+
+void *watch_ics_dir(void *arg)
+{
+ int fd = inotify_init1(IN_NONBLOCK);
+ if (fd < 0) {
+ perror("inotify_init");
+ return NULL;
+ }
+
+ int wd =
+ inotify_add_watch(fd, ICS_DIR, IN_CREATE | IN_DELETE | IN_MODIFY);
+ if (wd < 0) {
+ perror("inotify_add_watch");
+ close(fd);
+ return NULL;
+ }
+
+ char buffer[EVENT_BUF_LEN];
+
+ while (1) {
+ ssize_t length = read(fd, buffer, EVENT_BUF_LEN);
+ if (length < 0) {
+ if (errno == EAGAIN || errno == EINTR) {
+ continue;
+ }
+ perror("read");
+ break;
+ }
+
+ size_t i = 0;
+ while (i < (size_t)length) {
+ struct inotify_event *event =
+ (struct inotify_event *)&buffer[i];
+
+ if (event->len && strstr(event->name, ".ics")) {
+ printf("Detected change in ICS file: %s\n",
+ event->name);
+ load_journal_entries(); // Reload entries on any
+ // change
+ break;
+ }
+
+ i += sizeof(struct inotify_event) + event->len;
+ }
+ }
+
+ inotify_rm_watch(fd, wd);
+ close(fd);
+ return NULL;
+}
+
int main(int argc, char *argv[])
{
- // pthread_t watcher_thread;
- load_journal_entries();
+ pthread_t watcher_thread;
+
+ if (pthread_create(&watcher_thread, NULL, watch_ics_dir, NULL) != 0) {
+ perror("Failed to create inotify watcher thread");
+ return 1;
+ }
- // if (pthread_create(&watcher_thread, NULL, watch_ics_dir, NULL) != 0)
- // { perror("Failed to create inotify watcher thread"); return 1;
- //}
+ if (compile_regexes(®ex_summary, ®ex_description, ®ex_dtstamp,
+ ®ex_lastmod, ®ex_uid) != 0) {
+ fprintf(stderr, "Failed to compile regular expressions\n");
+ return -1;
+ }
+ load_journal_entries();
int ret = fuse_main(argc, argv, &journal_oper, NULL);
+ LOG("Cleaning up");
+
+ pthread_cancel(watcher_thread); // Stop the watcher when FUSE ends
+ pthread_join(watcher_thread, NULL); // Clean up
+ LOG("pthread cleaned up");
- // pthread_cancel(watcher_thread); // Stop the watcher when FUSE ends
- // pthread_join(watcher_thread, NULL); // Clean up
+ hashmap_free(entries_fuse_key);
+ hashmap_free(entries_original_key);
+ LOG("Hashmap freed");
- free_all_entries();
+ regfree(®ex_summary);
+ regfree(®ex_description);
+ regfree(®ex_dtstamp);
+ regfree(®ex_lastmod);
+ regfree(®ex_uid);
+ LOG("Regexp freed");
return ret;
}