Files
aboba/watcher.c
2025-06-15 14:27:26 +02:00

200 lines
4.8 KiB
C

#include <stdalign.h>
#include <dirent.h>
#include <signal.h>
#include <errno.h>
#include <sys/inotify.h>
#define STB_DS_IMPLEMENTATION
#include "stb/stb_ds.h"
#define GEBS_NO_PREFIX
#define GEBS_IMPLEMENTATION
#include "gebs/gebs.h"
#define EVENT_BUFFER_SIZE (5 * (sizeof(struct inotify_event) + NAME_MAX + 1))
#define EVENT_BUFFER_ALIGNMENT __attribute__((aligned(alignof(struct inotify_event))))
typedef struct {
int key; // watch descriptor
char *value; // the path
} File_Map;
typedef struct {
File_Map *fmap;
int fd;
NString_List ignored;
} Watcher;
bool watcher_make(Watcher *w)
{
String_Builder watcherignore = {0};
defer { sb_free(&watcherignore); }
if (sb_read_file(&watcherignore, "./.watcherignore")) {
sb_finish(&watcherignore);
char *line = strtok(watcherignore.items, "\n");
while (line != nil) {
char *line_copy = malloc(strlen(line)+1);
strcpy(line_copy, line);
list_append(&w->ignored, line_copy);
line = strtok(nil, "\n");
}
}
if ((w->fd = inotify_init1(IN_NONBLOCK)) < 0) {
LOGE("inotify_init1(): %s\n", strerror(errno));
return false;
}
return true;
}
void watcher_free(Watcher *w)
{
for (size_t i = 0; i < w->ignored.count; i++) {
free(w->ignored.items[i]);
}
list_free(&w->ignored);
for (size_t i = 0; i < hmlen(w->fmap); i++) {
free(w->fmap[i].value);
inotify_rm_watch(w->fd, w->fmap[i].key);
}
hmfree(w->fmap);
close(w->fd);
}
void watcher_add_file1(Watcher *w, int wd, char *path)
{
char *path_copy = gebs_malloc(&default_allocator, strlen(path)+1);
strcpy(path_copy, path);
hmput(w->fmap, wd, path_copy);
}
void watcher_add_file(Watcher *w, char *path)
{
uint32_t file_mask = IN_MODIFY | IN_DONT_FOLLOW;
int file_wd = inotify_add_watch(w->fd, path, file_mask);
if (file_wd < 0) {
LOGE("Could not add %s to watcher\n", path);
return;
}
watcher_add_file1(w, file_wd, path);
}
void watcher_add_all(Watcher *w, char *root)
{
DIR *dir;
struct dirent *entry;
if ((dir = opendir(root)) == nil) {
LOGE("Could not open dir %s\n", root);
return;
}
defer { closedir(dir); }
while ((entry = readdir(dir)) != nil) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/%s", root, entry->d_name);
for (size_t i = 0; i < w->ignored.count; i++) {
if (strcmp(w->ignored.items[i], path) == 0) {
LOGI("Skipping %s\n", path);
goto next;
}
}
if (entry->d_type == DT_DIR) {
watcher_add_all(w, path);
} else {
watcher_add_file(w, path);
}
next:
}
}
char *prog = nil;
bool running = true;
void handle_sigint(int signo)
{
running = false;
}
int main(int argc, char ** argv)
{
prog = SHIFT(&argc, &argv);
if (argc < 2) {
LOGI("Usage: ./watcher <dir> <command>\n");
return 0;
}
char *watch_dir = SHIFT(&argc, &argv);
Cmd cmd = {0};
defer { cmd_free(&cmd); }
while (argc > 0) {
cmd_append(&cmd, SHIFT(&argc, &argv));
}
signal(SIGINT, &handle_sigint);
Watcher watcher = {0};
if (!watcher_make(&watcher)) {
LOGE("Failed to create the watcher\n");
return 1;
}
defer { watcher_free(&watcher); }
watcher_add_all(&watcher, watch_dir);
pid_t cmd_pid = cmd_run_async(&cmd);
char event_buffer[EVENT_BUFFER_SIZE] EVENT_BUFFER_ALIGNMENT;
while (running) {
ssize_t nread;
if ((nread = read(watcher.fd, event_buffer, EVENT_BUFFER_SIZE)) < 0) {
if (errno != EAGAIN) {
LOGE("read(): %s\n", strerror(errno));
return 1;
} else {
continue;
}
}
for (char *event_ptr = event_buffer; event_ptr < event_buffer + nread; ) {
const struct inotify_event *event = (const struct inotify_event *)event_ptr;
ssize_t idx = hmgeti(watcher.fmap, event->wd);
char *path = watcher.fmap[idx].value;
if (event->mask & IN_IGNORED) {
hmdel(watcher.fmap, event->wd);
watcher_add_file(&watcher, path);
}
if (event->mask & (IN_MODIFY | IN_IGNORED)) {
LOGI("%s changed (%04x)\n", path, event->mask);
if (cmd_pid != -1) {
LOGI("Killing %d\n", cmd_pid);
kill(cmd_pid, SIGKILL);
}
cmd_pid = cmd_run_async(&cmd);
}
event_ptr += sizeof(struct inotify_event) + event->len;
}
sleep(1);
}
return 0;
}