Last active
December 7, 2024 00:03
-
-
Save jweinst1/02c58e4f3df0aee0d5d3293443a91b7b to your computer and use it in GitHub Desktop.
Mac queue directory monitor with queue
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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