Skip to content

Instantly share code, notes, and snippets.

@jweinst1
Last active December 7, 2024 00:03
Show Gist options
  • Save jweinst1/02c58e4f3df0aee0d5d3293443a91b7b to your computer and use it in GitHub Desktop.
Save jweinst1/02c58e4f3df0aee0d5d3293443a91b7b to your computer and use it in GitHub Desktop.
Mac queue directory monitor with queue
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <assert.h>
#include <sys/event.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
static char* str_dupl(const char* src) {
size_t src_size = strlen(src) + 1;
char* newstr = (char*)malloc(src_size);
memcpy(newstr, src, src_size);
return newstr;
}
/*
dirp = opendir(argv[1]);
if (dirp == NULL) {
perror("opendir");
return 1;
}
entry = malloc(offsetof(struct dirent, d_name) + NAME_MAX + 1);
if (entry == NULL) {
perror("malloc");
return 1;
}
while (readdir_r(dirp, entry, &result) == 0 && result != NULL) {
printf("%s\n", entry->d_name);
}
if (errno != 0) {
perror("readdir_r");
}
free(entry);
closedir(dirp);
*/
void print_directory(const char* path) {
DIR* dirp = opendir(path);
struct dirent *entry, *result;
entry = malloc(offsetof(struct dirent, d_name) + NAME_MAX + 1);
if (dirp == NULL) {
fprintf(stderr,"cannot open %s\n", path);
return;
}
while (readdir_r(dirp, entry, &result) == 0 && result != NULL) {
printf("%s\n", entry->d_name);
}
free(entry);
closedir(dirp);
}
int is_directory(const char *path) {
struct stat statbuf;
if (stat(path, &statbuf) != 0)
return 0;
return S_ISDIR(statbuf.st_mode);
}
int is_regular_file(const char *path) {
struct stat statbuf;
if (stat(path, &statbuf) != 0)
return 0;
return S_ISREG(statbuf.st_mode);
}
int is_dot_or_dot_dot(const char* name) {
return name[0] == '.' && (name[1] == '.' || name[1] == '\0');
}
enum fs_entry {
FS_TYPE_FILE,
FS_TYPE_DIR
};
struct fs_node {
enum fs_entry type;
char* name;
int fd;
struct kevent change;
struct fs_node* next;
struct fs_node* child;
};
struct fs_monitor {
struct fs_node* root;
int kq_fd;
};
struct fs_node* fs_node_create(int kq_fd, const char* fname) {
printf("Adding %s to monitoring\n", fname);
struct fs_node* fnode = calloc(1, sizeof(struct fs_node));
if (is_directory(fname)) {
fnode->type = FS_TYPE_DIR;
} else if (is_regular_file(fname)) {
fnode->type = FS_TYPE_FILE;
} else {
fprintf(stderr, "Unknown file type for %s\n", fname);
abort();
}
fnode->fd = open(fname, O_EVTONLY);
if (fnode->fd == -1) {
fprintf(stderr, "Cannot open %s\n", fname);
errno = 0;
return NULL;
}
fnode->name = str_dupl(fname);
EV_SET(&fnode->change, fnode->fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_RENAME | NOTE_LINK | NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB, 0, fnode);
if (kevent(kq_fd, &fnode->change, 1, NULL, 0, NULL) == -1) {
perror("kevent");
return NULL;
}
printf("DONE Adding %s to monitoring\n", fname);
return fnode;
}
int fs_monitor_init(struct fs_monitor* mon) {
mon->root = NULL;
mon->kq_fd = kqueue();
if (mon->kq_fd == -1) {
return 0;
}
return 1;
}
int fs_monitor_add(struct fs_monitor* mon, const char* dirname) {
if (!is_directory(dirname)) {
return 0;
}
struct fs_node* new_root = fs_node_create(mon->kq_fd, dirname);
new_root->next = mon->root;
mon->root = new_root;
DIR* dirp = opendir(dirname);
if (dirp == NULL) {
fprintf(stderr,"cannot open %s\n", dirname);
return 0;
}
struct dirent *entry, *result;
entry = malloc(offsetof(struct dirent, d_name) + NAME_MAX + 1);
struct fs_node* child_base = NULL;
while (readdir_r(dirp, entry, &result) == 0 && result != NULL) {
char pathbuf[512] = {'\0'};
if (is_dot_or_dot_dot(entry->d_name))
continue;
sprintf(pathbuf, "%s/%s", dirname, entry->d_name);
struct fs_node* child = fs_node_create(mon->kq_fd, pathbuf);
if (child == NULL)
continue;
child->next = child_base;
child_base = child;
}
new_root->child = child_base;
free(entry);
return 1;
}
int fs_monitor_check_new_files(struct fs_monitor* mon, struct fs_node* dirtrig) {
DIR* dirp = opendir(dirtrig->name);
if (dirp == NULL) {
fprintf(stderr, "Cannot open %s\n", dirtrig->name);
return 0;
}
struct dirent *entry, *result;
entry = malloc(offsetof(struct dirent, d_name) + NAME_MAX + 1);
while (readdir_r(dirp, entry, &result) == 0 && result != NULL) {
char pathbuf[512] = {'\0'};
if (is_dot_or_dot_dot(entry->d_name))
continue;
sprintf(pathbuf, "%s/%s", dirtrig->name, entry->d_name);
struct fs_node* dirlst = dirtrig->child;
int found_obj = 0;
while (dirlst != NULL) {
if (strcmp(pathbuf, dirlst->name) == 0) {
found_obj = 1;
break;
}
dirlst = dirlst->next;
}
if (!found_obj) {
printf("Detected a new node %s\n", pathbuf);
struct fs_node* new_node = fs_node_create(mon->kq_fd, pathbuf);
if (new_node != NULL) {
new_node->next = dirtrig->child;
dirtrig->child = new_node;
}
}
}
free(entry);
return 1;
}
#define MAX_EVENTS 10
int fs_monitor_wait(struct fs_monitor* mon) {
struct kevent events[MAX_EVENTS];
memset(events, 0, sizeof(events));
int event_count = kevent(mon->kq_fd, NULL, 0, events, MAX_EVENTS, NULL);
if (event_count == -1) {
perror("kevent");
return 0;
}
for (int i = 0; i < event_count; ++i)
{
/* code */
if (events[i].flags == EV_ERROR) {
fprintf(stderr, "%s has an error\n", ((struct fs_node*)events[i].udata)->name);
}
if (events[i].fflags & NOTE_WRITE) {
struct fs_node* triged = (struct fs_node*)events[i].udata;
printf("%s was written to\n", triged->name);
if (triged->type == FS_TYPE_DIR) {
fs_monitor_check_new_files(mon, triged);
}
}
if (events[i].fflags & NOTE_EXTEND) {
printf("%s was extended to\n", ((struct fs_node*)events[i].udata)->name);
}
if (events[i].fflags & NOTE_DELETE) {
// todo need to remove from queue
printf("%s was deleted to\n", ((struct fs_node*)events[i].udata)->name);
}
if (events[i].fflags & NOTE_LINK) {
printf("%s was linked to\n", ((struct fs_node*)events[i].udata)->name);
}
if (events[i].fflags & NOTE_ATTRIB) {
printf("%s was attribed to\n", ((struct fs_node*)events[i].udata)->name);
}
if (events[i].fflags & NOTE_RENAME) {
// todo handle dup error rename
printf("%s was renamed to\n", ((struct fs_node*)events[i].udata)->name);
}
}
return 1;
}
int main(int argc, char const *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage : <dir to monitor>\n");
exit(2);
}
const char * dirmon = argv[1];
print_directory(dirmon);
struct fs_monitor mon;
assert(fs_monitor_init(&mon));
assert(fs_monitor_add(&mon, dirmon));
while (1) {
if (!fs_monitor_wait(&mon)) {
fprintf(stderr, "Could not wait on events!\n");
abort();
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment