196 lines
5.2 KiB
C
196 lines
5.2 KiB
C
#include "filewatch.h"
|
|
|
|
#include "common.h"
|
|
#include "filewatch_context.h"
|
|
#include "filewatch_type.h"
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/inotify.h>
|
|
#include <unistd.h>
|
|
|
|
/*
|
|
* 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;
|
|
}
|