|
@@ -0,0 +1,234 @@
|
|
|
+#include "common.h"
|
|
|
+#include "cli.h"
|
|
|
+
|
|
|
+// TODO: set on fire. cli.{h,c} handle both parsing and defaults, so there's
|
|
|
+// no need to set those here. also, in order to scope metadata by path,
|
|
|
+// each stream will need its own configuration... so this won't work as
|
|
|
+// a global any more. In the end the goal is to make the output format
|
|
|
+// able to declare not just that something happened and what flags were
|
|
|
+// attached, but what path it was watching that caused those events (so
|
|
|
+// that the path itself can be used for routing that information to the
|
|
|
+// relevant callback).
|
|
|
+//
|
|
|
+// Structure for storing metadata parsed from the commandline
|
|
|
+static struct {
|
|
|
+ FSEventStreamEventId sinceWhen;
|
|
|
+ CFTimeInterval latency;
|
|
|
+ FSEventStreamCreateFlags flags;
|
|
|
+ CFMutableArrayRef paths;
|
|
|
+ int format;
|
|
|
+} config = {
|
|
|
+ (UInt64) kFSEventStreamEventIdSinceNow,
|
|
|
+ (double) 0.3,
|
|
|
+ (CFOptionFlags) kFSEventStreamCreateFlagNone,
|
|
|
+ NULL,
|
|
|
+ 0
|
|
|
+};
|
|
|
+
|
|
|
+// Prototypes
|
|
|
+static void append_path(const char* path);
|
|
|
+static inline void parse_cli_settings(int argc, const char* argv[]);
|
|
|
+static void callback(FSEventStreamRef streamRef,
|
|
|
+ void* clientCallBackInfo,
|
|
|
+ size_t numEvents,
|
|
|
+ void* eventPaths,
|
|
|
+ const FSEventStreamEventFlags eventFlags[],
|
|
|
+ const FSEventStreamEventId eventIds[]);
|
|
|
+
|
|
|
+
|
|
|
+static void append_path(const char* path)
|
|
|
+{
|
|
|
+ CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault,
|
|
|
+ path,
|
|
|
+ kCFStringEncodingUTF8);
|
|
|
+ CFArrayAppendValue(config.paths, pathRef);
|
|
|
+ CFRelease(pathRef);
|
|
|
+}
|
|
|
+
|
|
|
+// Parse commandline settings
|
|
|
+static inline void parse_cli_settings(int argc, const char* argv[])
|
|
|
+{
|
|
|
+ // runtime os version detection
|
|
|
+ SInt32 osMajorVersion, osMinorVersion;
|
|
|
+ if (!(Gestalt(gestaltSystemVersionMajor, &osMajorVersion) == noErr)) {
|
|
|
+ osMajorVersion = 0;
|
|
|
+ }
|
|
|
+ if (!(Gestalt(gestaltSystemVersionMinor, &osMinorVersion) == noErr)) {
|
|
|
+ osMinorVersion = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((osMajorVersion == 10) & (osMinorVersion < 5)) {
|
|
|
+ fprintf(stderr, "The FSEvents API is unavailable on this version of macos!\n");
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+
|
|
|
+ struct cli_info args_info;
|
|
|
+ cli_parser_init(&args_info);
|
|
|
+
|
|
|
+ if (cli_parser(argc, argv, &args_info) != 0) {
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+
|
|
|
+ config.paths = CFArrayCreateMutable(NULL,
|
|
|
+ (CFIndex)0,
|
|
|
+ &kCFTypeArrayCallBacks);
|
|
|
+
|
|
|
+ config.sinceWhen = args_info.since_when_arg;
|
|
|
+ config.latency = args_info.latency_arg;
|
|
|
+ config.format = args_info.format_arg;
|
|
|
+
|
|
|
+ if (args_info.no_defer_flag) {
|
|
|
+ config.flags |= kFSEventStreamCreateFlagNoDefer;
|
|
|
+ }
|
|
|
+ if (args_info.watch_root_flag) {
|
|
|
+ config.flags |= kFSEventStreamCreateFlagWatchRoot;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (args_info.ignore_self_flag) {
|
|
|
+ if ((osMajorVersion == 10) & (osMinorVersion >= 6)) {
|
|
|
+ config.flags |= kFSEventStreamCreateFlagIgnoreSelf;
|
|
|
+ } else {
|
|
|
+ fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n");
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (args_info.file_events_flag) {
|
|
|
+ if ((osMajorVersion == 10) & (osMinorVersion >= 7)) {
|
|
|
+ config.flags |= kFSEventStreamCreateFlagFileEvents;
|
|
|
+ } else {
|
|
|
+ fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n");
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (args_info.mark_self_flag) {
|
|
|
+ if ((osMajorVersion == 10) & (osMinorVersion >= 9)) {
|
|
|
+ config.flags |= kFSEventStreamCreateFlagMarkSelf;
|
|
|
+ } else {
|
|
|
+ fprintf(stderr, "MacOSX 10.9 or later required for --mark-self\n");
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (args_info.inputs_num == 0) {
|
|
|
+ append_path(".");
|
|
|
+ } else {
|
|
|
+ for (unsigned int i=0; i < args_info.inputs_num; ++i) {
|
|
|
+ append_path(args_info.inputs[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ cli_parser_free(&args_info);
|
|
|
+
|
|
|
+#ifdef DEBUG
|
|
|
+ fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen);
|
|
|
+ fprintf(stderr, "config.latency %f\n", config.latency);
|
|
|
+ fprintf(stderr, "config.flags %#.8x\n", config.flags);
|
|
|
+
|
|
|
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes,
|
|
|
+ " Using CF instead of C types");
|
|
|
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer,
|
|
|
+ " NoDefer latency modifier enabled");
|
|
|
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot,
|
|
|
+ " WatchRoot notifications enabled");
|
|
|
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf,
|
|
|
+ " IgnoreSelf enabled");
|
|
|
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents,
|
|
|
+ " FileEvents enabled");
|
|
|
+
|
|
|
+ fprintf(stderr, "config.paths\n");
|
|
|
+
|
|
|
+ long numpaths = CFArrayGetCount(config.paths);
|
|
|
+
|
|
|
+ for (long i = 0; i < numpaths; i++) {
|
|
|
+ char path[PATH_MAX];
|
|
|
+ CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i),
|
|
|
+ path,
|
|
|
+ PATH_MAX,
|
|
|
+ kCFStringEncodingUTF8);
|
|
|
+ fprintf(stderr, " %s\n", path);
|
|
|
+ }
|
|
|
+
|
|
|
+ fprintf(stderr, "\n");
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+static void callback(__attribute__((unused)) FSEventStreamRef streamRef,
|
|
|
+ __attribute__((unused)) void* clientCallBackInfo,
|
|
|
+ size_t numEvents,
|
|
|
+ void* eventPaths,
|
|
|
+ const FSEventStreamEventFlags eventFlags[],
|
|
|
+ const FSEventStreamEventId eventIds[])
|
|
|
+{
|
|
|
+ char** paths = eventPaths;
|
|
|
+ char *buf = calloc(sizeof(FSEVENTSBITS), sizeof(char));
|
|
|
+
|
|
|
+ for (size_t i = 0; i < numEvents; i++) {
|
|
|
+ sprintb(buf, eventFlags[i], FSEVENTSBITS);
|
|
|
+ printf("%llu\t%#.8x=[%s]\t%s\n", eventIds[i], eventFlags[i], buf, paths[i]);
|
|
|
+ }
|
|
|
+ fflush(stdout);
|
|
|
+ free(buf);
|
|
|
+
|
|
|
+ if (fcntl(STDIN_FILENO, F_GETFD) == -1) {
|
|
|
+ CFRunLoopStop(CFRunLoopGetCurrent());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void stdin_callback(CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info)
|
|
|
+{
|
|
|
+ char buf[1024];
|
|
|
+ int nread;
|
|
|
+
|
|
|
+ do {
|
|
|
+ nread = read(STDIN_FILENO, buf, sizeof(buf));
|
|
|
+ if (nread == -1 && errno == EAGAIN) {
|
|
|
+ CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
|
|
|
+ return;
|
|
|
+ } else if (nread == 0) {
|
|
|
+ exit(1);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } while (nread > 0);
|
|
|
+}
|
|
|
+
|
|
|
+int main(int argc, const char* argv[])
|
|
|
+{
|
|
|
+ parse_cli_settings(argc, argv);
|
|
|
+
|
|
|
+ FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
|
|
|
+ FSEventStreamRef stream;
|
|
|
+ stream = FSEventStreamCreate(kCFAllocatorDefault,
|
|
|
+ (FSEventStreamCallback)&callback,
|
|
|
+ &context,
|
|
|
+ config.paths,
|
|
|
+ config.sinceWhen,
|
|
|
+ config.latency,
|
|
|
+ config.flags);
|
|
|
+
|
|
|
+#ifdef DEBUG
|
|
|
+ FSEventStreamShow(stream);
|
|
|
+ fprintf(stderr, "\n");
|
|
|
+#endif
|
|
|
+
|
|
|
+ fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
|
|
|
+
|
|
|
+ CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, STDIN_FILENO, false, stdin_callback, NULL);
|
|
|
+ CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
|
|
|
+ CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
|
|
|
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
|
|
|
+ CFRelease(source);
|
|
|
+
|
|
|
+ FSEventStreamScheduleWithRunLoop(stream,
|
|
|
+ CFRunLoopGetCurrent(),
|
|
|
+ kCFRunLoopDefaultMode);
|
|
|
+ FSEventStreamStart(stream);
|
|
|
+ CFRunLoopRun();
|
|
|
+ FSEventStreamFlushSync(stream);
|
|
|
+ FSEventStreamStop(stream);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+// vim: ts=2 sts=2 et sw=2
|