Skip to content

Instantly share code, notes, and snippets.

@jweinst1
Last active May 2, 2025 00:31
Show Gist options
  • Save jweinst1/4e527fd1da35b112de2fab975f336826 to your computer and use it in GitHub Desktop.
Save jweinst1/4e527fd1da35b112de2fab975f336826 to your computer and use it in GitHub Desktop.
Monitor FSEvents in C with apple library core services
#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;
}
#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