main.c 7.5 KB

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