#include "filewatch.h" #include "common.h" #include "filewatch_context.h" #include "filewatch_type.h" #include #include #include #include /* * File Watch */ struct WatchMaskParams { u32 file_mask; u32 parent_mask; }; static inline struct WatchMaskParams inode_watch_masks_create(u32 flags) { u32 file_mask = 0u; u32 parent_mask = 0u; if (has_mask(flags, HI_FILE_MODIFY)) { file_mask |= IN_MODIFY; } if (has_mask(flags, HI_FILE_CREATE)) { parent_mask |= IN_CREATE; } if (has_mask(flags, HI_FILE_DELETE)) { file_mask |= IN_DELETE_SELF; parent_mask |= IN_DELETE; } if (has_mask(flags, HI_FILE_MOVE)) { file_mask |= IN_MOVE_SELF; parent_mask |= IN_MOVED_TO; } // Append if watch exists file_mask |= IN_MASK_ADD; parent_mask |= IN_MASK_ADD; return (struct WatchMaskParams){.file_mask = file_mask, .parent_mask = parent_mask}; } static void file_watch_destroy(int fd, FileWatch *fw) { inotify_rm_watch(fd, fw->wd); free((void *)fw->path); fw->wd = -1; fw->path = NULL; } void file_watch_destroy_watches(FileWatcherContext *ctx) { for (size_t i = 0; i < sc_array_size(&ctx->watches); i++) { FileWatch *fw = &sc_array_at(&ctx->watches, i); file_watch_destroy(ctx->fd, fw); } sc_array_term(&ctx->watches); } FileWatch *file_watch_find_by_wd(FileWatcherContext *ctx, int wd, size_t *index) { struct sc_array_fwatch *watches = &ctx->watches; for (size_t i = 0; i < sc_array_size(watches); ++i) { FileWatch *fw = &sc_array_at(watches, i); if (fw->wd == wd) { if (index) { *index = i; } return fw; } } return NULL; } FileWatch *file_watch_find_by_path(FileWatcherContext *ctx, const char *path, size_t *index) { for (size_t i = 0; i < sc_array_size(&ctx->watches); i++) { FileWatch *watch = &sc_array_at(&ctx->watches, i); if (strcmp(watch->path, path) == 0) { if (index) { *index = i; } return watch; } } return NULL; } /** * Return null terminated char array with the parent, or NULL if no path * separator was found. * */ static char *get_parent_folder(const char path[static 1]) { const char *last_separator = strrchr(path, '/'); if (!last_separator) return NULL; // add one for the trailing slash size_t length = last_separator - path + 1; char *parent = calloc(length, sizeof(char)); strncpy(parent, path, length); parent[length - 1] = '/'; parent[length] = '\0'; return parent; } HiResult file_watch_add(FileWatcherContext *ctx, u32 mask, const char *path) { if (!ctx || ctx->fd == -1) { log_error("Invalid inotify context\n"); return HI_FAIL; } struct WatchMaskParams params = inode_watch_masks_create(mask); FileWatch *watch = file_watch_find_by_path(ctx, path, NULL); if (!watch) { int wd = inotify_add_watch(ctx->fd, path, params.file_mask); if (wd == -1) { log_error("Couldn't watch: %s: %s\n", path, strerror(errno)); return HI_FAIL; } FileWatch wp = {.wd = wd, .mask = mask, .path = strdup(path)}; sc_array_add(&ctx->watches, wp); watch = &sc_array_last(&ctx->watches); } else { inotify_add_watch(ctx->fd, path, params.file_mask); } if (!has_mask(HI_FILE_PARENT, mask)) { char *parent_name = get_parent_folder(path); FileWatch *parent_watch = file_watch_find_by_path(ctx, parent_name, NULL); if (!parent_watch) { // parent not yet watched int wd = inotify_add_watch(ctx->fd, parent_name, params.parent_mask); FileWatch wp = {.wd = wd, .mask = HI_FILE_PARENT, .path = parent_name}; sc_array_init(&wp.files); sc_array_add(&wp.files, strdup(watch->path)); sc_array_add(&ctx->watches, wp); } else { free(parent_name); inotify_add_watch(ctx->fd, parent_watch->path, params.parent_mask); { // Make sure we don't add a duplicate if the file is already marked bool exists = false; for (size_t i = 0; i < sc_array_size(&parent_watch->files); i++) { const char *file = sc_array_at(&parent_watch->files, i); // we store the pointer directly, so this should be fine :D if (file == watch->path) { exists = true; break; } } if (!exists) { sc_array_add(&parent_watch->files, watch->path); } } } } return HI_OK; } HiResult file_watch_remove(FileWatcherContext *ctx, const char *path) { size_t i = 0; FileWatch *watch = file_watch_find_by_path(ctx, path, &i); if (watch) { // Destroy parent reference. We assume it will only have one. char *parent = get_parent_folder(path); if (parent) { FileWatch *pw = file_watch_find_by_path(ctx, parent, NULL); if (pw) { for (size_t i = 0; i < sc_array_size(&pw->files); i++) { const char *fn = sc_array_at(&pw->files, i); if (fn == watch->path) { sc_array_del(&pw->files, i); break; } } } free(parent); } file_watch_destroy(ctx->fd, watch); sc_array_del(&ctx->watches, i); return HI_OK; } return HI_FAIL; }