main.c (7779B)
1 #include "common.h" 2 #include "cli.h" 3 4 // TODO: set on fire. cli.{h,c} handle both parsing and defaults, so there's 5 // no need to set those here. also, in order to scope metadata by path, 6 // each stream will need its own configuration... so this won't work as 7 // a global any more. In the end the goal is to make the output format 8 // able to declare not just that something happened and what flags were 9 // attached, but what path it was watching that caused those events (so 10 // that the path itself can be used for routing that information to the 11 // relevant callback). 12 // 13 // Structure for storing metadata parsed from the commandline 14 static struct { 15 FSEventStreamEventId sinceWhen; 16 CFTimeInterval latency; 17 FSEventStreamCreateFlags flags; 18 CFMutableArrayRef paths; 19 int format; 20 } config = { 21 (UInt64) kFSEventStreamEventIdSinceNow, 22 (double) 0.3, 23 (CFOptionFlags) kFSEventStreamCreateFlagNone, 24 NULL, 25 0 26 }; 27 28 // Prototypes 29 static void append_path(const char* path); 30 static inline void parse_cli_settings(int argc, const char* argv[]); 31 static void callback(FSEventStreamRef streamRef, 32 void* clientCallBackInfo, 33 size_t numEvents, 34 void* eventPaths, 35 const FSEventStreamEventFlags eventFlags[], 36 const FSEventStreamEventId eventIds[]); 37 38 39 static void append_path(const char* path) 40 { 41 CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault, 42 path, 43 kCFStringEncodingUTF8); 44 CFArrayAppendValue(config.paths, pathRef); 45 CFRelease(pathRef); 46 } 47 48 // Parse commandline settings 49 static inline void parse_cli_settings(int argc, const char* argv[]) 50 { 51 // runtime os version detection 52 SInt32 osMajorVersion, osMinorVersion; 53 if (!(Gestalt(gestaltSystemVersionMajor, &osMajorVersion) == noErr)) { 54 osMajorVersion = 0; 55 } 56 if (!(Gestalt(gestaltSystemVersionMinor, &osMinorVersion) == noErr)) { 57 osMinorVersion = 0; 58 } 59 60 if ((osMajorVersion == 10) & (osMinorVersion < 5)) { 61 fprintf(stderr, "The FSEvents API is unavailable on this version of macos!\n"); 62 exit(EXIT_FAILURE); 63 } 64 65 struct cli_info args_info; 66 cli_parser_init(&args_info); 67 68 if (cli_parser(argc, argv, &args_info) != 0) { 69 exit(EXIT_FAILURE); 70 } 71 72 config.paths = CFArrayCreateMutable(NULL, 73 (CFIndex)0, 74 &kCFTypeArrayCallBacks); 75 76 config.sinceWhen = args_info.since_when_arg; 77 config.latency = args_info.latency_arg; 78 config.format = args_info.format_arg; 79 80 if (args_info.no_defer_flag) { 81 config.flags |= kFSEventStreamCreateFlagNoDefer; 82 } 83 if (args_info.watch_root_flag) { 84 config.flags |= kFSEventStreamCreateFlagWatchRoot; 85 } 86 87 if (args_info.ignore_self_flag) { 88 if ((osMajorVersion > 10) | ((osMajorVersion == 10) & (osMinorVersion >= 6))) { 89 config.flags |= kFSEventStreamCreateFlagIgnoreSelf; 90 } else { 91 fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n"); 92 exit(EXIT_FAILURE); 93 } 94 } 95 96 if (args_info.file_events_flag) { 97 if ((osMajorVersion > 10) | ((osMajorVersion == 10) & (osMinorVersion >= 7))) { 98 config.flags |= kFSEventStreamCreateFlagFileEvents; 99 } else { 100 fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n"); 101 exit(EXIT_FAILURE); 102 } 103 } 104 105 if (args_info.mark_self_flag) { 106 if ((osMajorVersion > 10) | ((osMajorVersion == 10) & (osMinorVersion >= 9))) { 107 config.flags |= kFSEventStreamCreateFlagMarkSelf; 108 } else { 109 fprintf(stderr, "MacOSX 10.9 or later required for --mark-self\n"); 110 exit(EXIT_FAILURE); 111 } 112 } 113 114 if (args_info.inputs_num == 0) { 115 append_path("."); 116 } else { 117 for (unsigned int i=0; i < args_info.inputs_num; ++i) { 118 append_path(args_info.inputs[i]); 119 } 120 } 121 122 cli_parser_free(&args_info); 123 124 #ifdef DEBUG 125 fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen); 126 fprintf(stderr, "config.latency %f\n", config.latency); 127 fprintf(stderr, "config.flags %#.8x\n", config.flags); 128 129 FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes, 130 " Using CF instead of C types"); 131 FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer, 132 " NoDefer latency modifier enabled"); 133 FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot, 134 " WatchRoot notifications enabled"); 135 FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf, 136 " IgnoreSelf enabled"); 137 FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents, 138 " FileEvents enabled"); 139 140 fprintf(stderr, "config.paths\n"); 141 142 long numpaths = CFArrayGetCount(config.paths); 143 144 for (long i = 0; i < numpaths; i++) { 145 char path[PATH_MAX]; 146 CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i), 147 path, 148 PATH_MAX, 149 kCFStringEncodingUTF8); 150 fprintf(stderr, " %s\n", path); 151 } 152 153 fprintf(stderr, "\n"); 154 #endif 155 } 156 157 static void callback(__attribute__((unused)) FSEventStreamRef streamRef, 158 __attribute__((unused)) void* clientCallBackInfo, 159 size_t numEvents, 160 void* eventPaths, 161 const FSEventStreamEventFlags eventFlags[], 162 const FSEventStreamEventId eventIds[]) 163 { 164 char** paths = eventPaths; 165 char *buf = calloc(sizeof(FSEVENTSBITS), sizeof(char)); 166 167 for (size_t i = 0; i < numEvents; i++) { 168 sprintb(buf, eventFlags[i], FSEVENTSBITS); 169 printf("%llu\t%#.8x=[%s]\t%s\n", eventIds[i], eventFlags[i], buf, paths[i]); 170 } 171 fflush(stdout); 172 free(buf); 173 174 if (fcntl(STDIN_FILENO, F_GETFD) == -1) { 175 CFRunLoopStop(CFRunLoopGetCurrent()); 176 } 177 } 178 179 static void stdin_callback(CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info) 180 { 181 char buf[1024]; 182 int nread; 183 184 do { 185 nread = read(STDIN_FILENO, buf, sizeof(buf)); 186 if (nread == -1 && errno == EAGAIN) { 187 CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); 188 return; 189 } else if (nread == 0) { 190 exit(1); 191 return; 192 } 193 } while (nread > 0); 194 } 195 196 int main(int argc, const char* argv[]) 197 { 198 parse_cli_settings(argc, argv); 199 200 FSEventStreamContext context = {0, NULL, NULL, NULL, NULL}; 201 FSEventStreamRef stream; 202 stream = FSEventStreamCreate(kCFAllocatorDefault, 203 (FSEventStreamCallback)&callback, 204 &context, 205 config.paths, 206 config.sinceWhen, 207 config.latency, 208 config.flags); 209 210 #ifdef DEBUG 211 FSEventStreamShow(stream); 212 fprintf(stderr, "\n"); 213 #endif 214 215 fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK); 216 217 CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, STDIN_FILENO, false, stdin_callback, NULL); 218 CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); 219 CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0); 220 CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); 221 CFRelease(source); 222 223 FSEventStreamScheduleWithRunLoop(stream, 224 CFRunLoopGetCurrent(), 225 kCFRunLoopDefaultMode); 226 FSEventStreamStart(stream); 227 CFRunLoopRun(); 228 FSEventStreamFlushSync(stream); 229 FSEventStreamStop(stream); 230 231 return 0; 232 } 233 234 // vim: ts=2 sts=2 et sw=2