Last active
May 2, 2025 00:31
-
-
Save jweinst1/4e527fd1da35b112de2fab975f336826 to your computer and use it in GitHub Desktop.
Monitor FSEvents in C with apple library core services
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 <CoreServices/CoreServices.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
void fsevent_callback( | |
ConstFSEventStreamRef streamRef, | |
void *clientCallBackInfo, | |
size_t numEvents, | |
void *eventPaths, | |
const FSEventStreamEventFlags eventFlags[], | |
const FSEventStreamEventId eventIds[]) { | |
char **paths = (char **)eventPaths; | |
for (int i = 0; i < numEvents; i++) { | |
printf("Change %llu in %s, flags %d id %llu\n", eventIds[i], paths[i], eventFlags[i], eventIds[i]); | |
} | |
} | |
int main(int argc, char **argv) { | |
if (argc < 2) { | |
fprintf(stderr, "Usage: %s <path_to_watch>\n", argv[0]); | |
return 1; | |
} | |
CFStringRef path = CFStringCreateWithCString(NULL, argv[1], kCFStringEncodingUTF8); | |
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&path, 1, NULL); | |
CFRelease(path); | |
FSEventStreamContext context; | |
context.version = 0; | |
context.info = NULL; | |
context.retain = NULL; | |
context.release = NULL; | |
context.copyDescription = NULL; | |
dispatch_queue_t Queue = | |
dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL); | |
FSEventStreamRef stream = FSEventStreamCreate( | |
NULL, &fsevent_callback, &context, pathsToWatch, kFSEventStreamEventIdSinceNow, 0.1, | |
kFSEventStreamCreateFlagFileEvents); | |
CFRelease(pathsToWatch); | |
if (stream == NULL) { | |
fprintf(stderr, "Failed to create FSEventStream\n"); | |
return 1; | |
} | |
FSEventStreamSetDispatchQueue(stream, Queue); | |
//FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | |
FSEventStreamStart(stream); | |
//CFRunLoopRun(); | |
dispatch_main(); | |
FSEventStreamStop(stream); | |
FSEventStreamInvalidate(stream); | |
FSEventStreamRelease(stream); | |
// Never reached, but cleanup would look like: | |
// FSEventStreamStop(stream); | |
// FSEventStreamInvalidate(stream); | |
// FSEventStreamRelease(stream); | |
// CFRelease(pathsToWatch); | |
// dispatch_release(eventQueue); | |
return 0; | |
} |
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 <CoreServices/CoreServices.h> | |
#include <dispatch/dispatch.h> | |
#include <pthread.h> | |
#include <signal.h> | |
#include <stdbool.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
// === Internal state === | |
static FSEventStreamRef stream = NULL; | |
static dispatch_queue_t event_queue = NULL; | |
static dispatch_semaphore_t stop_signal = NULL; | |
static pthread_t monitor_thread; | |
static bool is_monitor_running = false; | |
static char monitored_path[PATH_MAX] = {0}; | |
// === Event callback === | |
void event_callback( | |
ConstFSEventStreamRef streamRef, | |
void *clientCallBackInfo, | |
size_t numEvents, | |
void *eventPaths, | |
const FSEventStreamEventFlags eventFlags[], | |
const FSEventStreamEventId eventIds[] | |
) { | |
char **paths = eventPaths; | |
for (size_t i = 0; i < numEvents; ++i) { | |
printf("[Callback] Changed: %s (flags: %u)\n", paths[i], eventFlags[i]); | |
} | |
} | |
// === Monitor thread entry point === | |
void *monitor_thread_func(void *arg) { | |
CFStringRef path = CFStringCreateWithCString(NULL, monitored_path, kCFStringEncodingUTF8); | |
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&path, 1, &kCFTypeArrayCallBacks); | |
FSEventStreamContext context = {0, NULL, NULL, NULL, NULL}; | |
event_queue = dispatch_queue_create("com.example.fsmonitor", DISPATCH_QUEUE_SERIAL); | |
stream = FSEventStreamCreate(NULL, | |
&event_callback, | |
&context, | |
pathsToWatch, | |
kFSEventStreamEventIdSinceNow, | |
1.0, | |
kFSEventStreamCreateFlagFileEvents); | |
FSEventStreamSetDispatchQueue(stream, event_queue); | |
if (!FSEventStreamStart(stream)) { | |
fprintf(stderr, "Failed to start event stream.\n"); | |
CFRelease(pathsToWatch); | |
CFRelease(path); | |
is_monitor_running = false; | |
return NULL; | |
} | |
printf("[Monitor] Started on %s\n", monitored_path); | |
dispatch_semaphore_wait(stop_signal, DISPATCH_TIME_FOREVER); | |
printf("[Monitor] Stop signal received. Cleaning up...\n"); | |
FSEventStreamStop(stream); | |
FSEventStreamInvalidate(stream); | |
FSEventStreamRelease(stream); | |
stream = NULL; | |
CFRelease(pathsToWatch); | |
CFRelease(path); | |
dispatch_release(event_queue); | |
event_queue = NULL; | |
is_monitor_running = false; | |
return NULL; | |
} | |
// === API: Start monitor === | |
bool start_monitor(const char *path) { | |
if (is_monitor_running) { | |
printf("[Start] Monitor already running.\n"); | |
return false; | |
} | |
strncpy(monitored_path, path, sizeof(monitored_path) - 1); | |
stop_signal = dispatch_semaphore_create(0); | |
if (pthread_create(&monitor_thread, NULL, monitor_thread_func, NULL) != 0) { | |
perror("[Start] Failed to create monitor thread"); | |
return false; | |
} | |
is_monitor_running = true; | |
return true; | |
} | |
// === API: Stop monitor === | |
void stop_monitor() { | |
if (!is_monitor_running) { | |
printf("[Stop] Monitor not running.\n"); | |
return; | |
} | |
dispatch_semaphore_signal(stop_signal); | |
pthread_join(monitor_thread, NULL); | |
dispatch_release(stop_signal); | |
stop_signal = NULL; | |
printf("[Stop] Monitor stopped.\n"); | |
} | |
// === CLI or controlling logic === | |
int main(int argc, const char *argv[]) { | |
if (argc < 2) { | |
fprintf(stderr, "Usage: %s <directory-to-monitor>\n", argv[0]); | |
return 1; | |
} | |
signal(SIGINT, [](int sig) { | |
printf("\n[Main] Ctrl+C received. Stopping monitor and exiting...\n"); | |
stop_monitor(); | |
exit(0); | |
}); | |
printf("[Main] Type 'start', 'stop', or 'exit'\n"); | |
char cmd[128]; | |
while (1) { | |
printf("> "); | |
fflush(stdout); | |
if (!fgets(cmd, sizeof(cmd), stdin)) break; | |
// Strip newline | |
cmd[strcspn(cmd, "\n")] = '\0'; | |
if (strcmp(cmd, "start") == 0) { | |
start_monitor(argv[1]); | |
} else if (strcmp(cmd, "stop") == 0) { | |
stop_monitor(); | |
} else if (strcmp(cmd, "exit") == 0) { | |
stop_monitor(); | |
break; | |
} else { | |
printf("Unknown command: %s\n", cmd); | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment