├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cli.c ├── cli.h ├── common.h ├── compat.c ├── compat.h ├── flags └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | fsevent_watch 2 | *.o 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2013 Thibaud Guillaume-Gentil & Travis Tilley 2 | Copyright (c) 2013 Vladimir Kirillov 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROG= fsevent_watch 2 | SRC= $(wildcard *.c) 3 | OBJ= $(SRC:.c=.o) 4 | 5 | CC= clang 6 | CFLAGS= -DCLI_VERSION="\"$(shell git describe --always)\"" 7 | CFLAGS+= -Wno-deprecated-declarations 8 | #CFLAGS+= -DDEBUG 9 | LDFLAGS= -framework CoreFoundation -framework CoreServices 10 | 11 | PREFIX ?= /usr/local 12 | 13 | $(PROG): $(OBJ) 14 | $(CC) $(LDFLAGS) $(OBJ) -o $@ 15 | 16 | %.o: %.c 17 | $(CC) -c $(CFLAGS) $< -o $@ 18 | 19 | install: $(PROG) 20 | install -m 755 $< $(PREFIX)/bin 21 | 22 | clean: 23 | rm -f *.o $(PROG) 24 | 25 | printflags: 26 | perl -lane 'printf "\\%o%s", $$., $$F[0]' < flags | pbcopy 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fsevent_watch 2 | 3 | The tool allows you to watch filesystem events on a Mac. 4 | 5 | This is a standalone fork the C tool inside https://github.com/thibaudgg/rb-fsevent 6 | focused on usability and *composability* with other tools. 7 | 8 | ## Awesomeness 9 | 10 | * `fsevent_watch` has a damn simple parseable output (tab-delimited) 11 | 12 | ``` 13 | % ./fsevent_watch -F . # in the mean time i run `make clean fsevent_watch` 14 | 86693279 0x00010200=[IRemoved] /tank/proger/fsevent_watch/main.o 15 | 86693282 0x00010200=[IRemoved] /tank/proger/fsevent_watch/fsevent_watch 16 | 86693291 0x00011900=[ICreated,IRenamed,IModified] /tank/proger/fsevent_watch/cli.o-b995d254 17 | 86693292 0x00010a00=[IRemoved,IRenamed] /tank/proger/fsevent_watch/cli.o 18 | 86693301 0x00011900=[ICreated,IRenamed,IModified] /tank/proger/fsevent_watch/compat.o-b9dbbb8a 19 | 86693302 0x00010a00=[IRemoved,IRenamed] /tank/proger/fsevent_watch/compat.o 20 | 86693305 0x00010100=[ICreated] /tank/proger/fsevent_watch/main.o-2f7870a4 21 | 86693311 0x00011900=[ICreated,IRenamed,IModified] /tank/proger/fsevent_watch/main.o-2f7870a4 22 | 86693312 0x00010a00=[IRemoved,IRenamed] /tank/proger/fsevent_watch/main.o 23 | 86693324 0x00014d00=[ICreated,IInodeMetaMod,IRenamed,IChangeOwner] /tank/proger/fsevent_watch/fsevent_watch.ld_gYfEvE 24 | 86693325 0x00010a00=[IRemoved,IRenamed] /tank/proger/fsevent_watch/fsevent_watch 25 | ``` 26 | 27 | * `fsevent_watch` does line-buffering so you can even develop the tool with itself! 28 | 29 | ``` 30 | % ./fsevent_watch -F . | egrep --line-buffered '\.[ch]$' | xargs -t -n1 -I% make 31 | make 32 | clang -c -DCLI_VERSION="\"f785a34\"" -Wno-deprecated-declarations main.c -o main.o 33 | clang -framework CoreFoundation -framework CoreServices cli.o compat.o main.o -o fsevent_watch 34 | make 35 | make: `fsevent_watch' is up to date. 36 | make 37 | make: `fsevent_watch' is up to date. 38 | ^C 39 | ``` 40 | 41 | * or, for example, authoring a Markdown file with vim and redcarpet 42 | 43 | ``` 44 | % fsevent_watch -F | grep --line-buffered README.md'$' | xargs -I% -n1 sh -c 'redcarpet --parse-fenced-code-blocks --parse-autolink --render-prettify README.md > README.html' 45 | ``` 46 | 47 | ## Building 48 | 49 | * just run `make install` (and make sure you have a compiler) 50 | 51 | ## Caveats 52 | 53 | * fsevents API does not follow symlinks 54 | -------------------------------------------------------------------------------- /cli.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "cli.h" 3 | 4 | const char* cli_info_purpose = "A flexible command-line interface for the FSEvents API"; 5 | const char* cli_info_usage = "Usage: fsevent_watch [OPTIONS]... [PATHS]..."; 6 | const char* cli_info_help[] = { 7 | " -h, --help you're looking at it", 8 | " -V, --version print version number and exit", 9 | " -p, --show-plist display the embedded Info.plist values", 10 | " -s, --since-when=EventID fire historical events since ID", 11 | " -l, --latency=seconds latency period (default='0.5')", 12 | " -n, --no-defer enable no-defer latency modifier", 13 | " -r, --watch-root watch for when the root path has changed", 14 | // " -i, --ignore-self ignore current process", 15 | " -F, --file-events provide file level event data", 16 | " -f, --format=name output format (ignored)", 17 | 0 18 | }; 19 | 20 | static void default_args (struct cli_info* args_info) 21 | { 22 | args_info->since_when_arg = kFSEventStreamEventIdSinceNow; 23 | args_info->latency_arg = 0.5; 24 | args_info->no_defer_flag = false; 25 | args_info->watch_root_flag = false; 26 | args_info->ignore_self_flag = false; 27 | args_info->file_events_flag = false; 28 | args_info->mark_self_flag = false; 29 | args_info->format_arg = 0; 30 | } 31 | 32 | static void cli_parser_release (struct cli_info* args_info) 33 | { 34 | unsigned int i; 35 | 36 | for (i=0; i < args_info->inputs_num; ++i) { 37 | free(args_info->inputs[i]); 38 | } 39 | 40 | if (args_info->inputs_num) { 41 | free(args_info->inputs); 42 | } 43 | 44 | args_info->inputs_num = 0; 45 | } 46 | 47 | void cli_parser_init (struct cli_info* args_info) 48 | { 49 | default_args(args_info); 50 | 51 | args_info->inputs = 0; 52 | args_info->inputs_num = 0; 53 | } 54 | 55 | void cli_parser_free (struct cli_info* args_info) 56 | { 57 | cli_parser_release(args_info); 58 | } 59 | 60 | static void cli_print_info_dict (const void *key, 61 | const void *value, 62 | void *context) 63 | { 64 | CFStringRef entry = CFStringCreateWithFormat(NULL, NULL, 65 | CFSTR("%@:\n %@"), key, value); 66 | if (entry) { 67 | CFShow(entry); 68 | CFRelease(entry); 69 | } 70 | } 71 | 72 | void cli_show_plist (void) 73 | { 74 | CFBundleRef mainBundle = CFBundleGetMainBundle(); 75 | CFRetain(mainBundle); 76 | CFDictionaryRef mainBundleDict = CFBundleGetInfoDictionary(mainBundle); 77 | if (mainBundleDict) { 78 | CFRetain(mainBundleDict); 79 | printf("Embedded Info.plist metadata:\n\n"); 80 | CFDictionaryApplyFunction(mainBundleDict, cli_print_info_dict, NULL); 81 | CFRelease(mainBundleDict); 82 | } 83 | CFRelease(mainBundle); 84 | printf("\n"); 85 | } 86 | 87 | void cli_print_version (void) 88 | { 89 | printf("%s %s\n\n", CLI_NAME, CLI_VERSION); 90 | } 91 | 92 | void cli_print_help (void) 93 | { 94 | cli_print_version(); 95 | 96 | printf("\n%s\n", cli_info_purpose); 97 | printf("\n%s\n", cli_info_usage); 98 | printf("\n"); 99 | 100 | int i = 0; 101 | while (cli_info_help[i]) { 102 | printf("%s\n", cli_info_help[i++]); 103 | } 104 | } 105 | 106 | int cli_parser (int argc, const char** argv, struct cli_info* args_info) 107 | { 108 | static struct option longopts[] = { 109 | { "help", no_argument, NULL, 'h' }, 110 | { "version", no_argument, NULL, 'V' }, 111 | { "show-plist", no_argument, NULL, 'p' }, 112 | { "since-when", required_argument, NULL, 's' }, 113 | { "latency", required_argument, NULL, 'l' }, 114 | { "no-defer", no_argument, NULL, 'n' }, 115 | { "watch-root", no_argument, NULL, 'r' }, 116 | { "ignore-self", no_argument, NULL, 'i' }, 117 | { "file-events", no_argument, NULL, 'F' }, 118 | { "mark-self", no_argument, NULL, 'm' }, 119 | { "format", required_argument, NULL, 'f' }, 120 | { 0, 0, 0, 0 } 121 | }; 122 | 123 | const char* shortopts = "hVps:l:nriFf:"; 124 | 125 | int c = -1; 126 | 127 | while ((c = getopt_long(argc, (char * const*)argv, shortopts, longopts, NULL)) != -1) { 128 | switch(c) { 129 | case 's': // since-when 130 | args_info->since_when_arg = strtoull(optarg, NULL, 0); 131 | break; 132 | case 'l': // latency 133 | args_info->latency_arg = strtod(optarg, NULL); 134 | break; 135 | case 'n': // no-defer 136 | args_info->no_defer_flag = true; 137 | break; 138 | case 'r': // watch-root 139 | args_info->watch_root_flag = true; 140 | break; 141 | case 'i': // ignore-self 142 | args_info->ignore_self_flag = true; 143 | break; 144 | case 'F': // file-events 145 | args_info->file_events_flag = true; 146 | break; 147 | case 'm': // mark-self 148 | args_info->mark_self_flag = true; 149 | break; 150 | case 'f': // format 151 | // XXX: ignored 152 | break; 153 | case 'V': // version 154 | cli_print_version(); 155 | exit(EXIT_SUCCESS); 156 | case 'p': // show-plist 157 | cli_show_plist(); 158 | exit(EXIT_SUCCESS); 159 | case 'h': // help 160 | case '?': // invalid option 161 | case ':': // missing argument 162 | cli_print_help(); 163 | exit((c == 'h') ? EXIT_SUCCESS : EXIT_FAILURE); 164 | } 165 | } 166 | 167 | if (optind < argc) { 168 | int i = 0; 169 | args_info->inputs_num = (unsigned int)(argc - optind); 170 | args_info->inputs = 171 | (char**)(malloc ((args_info->inputs_num)*sizeof(char*))); 172 | while (optind < argc) 173 | if (argv[optind++] != argv[0]) { 174 | args_info->inputs[i++] = strdup(argv[optind-1]); 175 | } 176 | } 177 | 178 | return EXIT_SUCCESS; 179 | } 180 | 181 | -------------------------------------------------------------------------------- /cli.h: -------------------------------------------------------------------------------- 1 | #ifndef CLI_H 2 | #define CLI_H 3 | 4 | #include "common.h" 5 | 6 | #ifndef CLI_NAME 7 | #define CLI_NAME "fsevent_watch" 8 | #endif /* CLI_NAME */ 9 | 10 | struct cli_info { 11 | UInt64 since_when_arg; 12 | double latency_arg; 13 | bool no_defer_flag; 14 | bool watch_root_flag; 15 | bool ignore_self_flag; 16 | bool file_events_flag; 17 | bool mark_self_flag; 18 | int format_arg; 19 | 20 | char** inputs; 21 | unsigned inputs_num; 22 | }; 23 | 24 | extern const char* cli_info_purpose; 25 | extern const char* cli_info_usage; 26 | extern const char* cli_info_help[]; 27 | 28 | void cli_print_help(void); 29 | void cli_print_version(void); 30 | 31 | int cli_parser (int argc, const char** argv, struct cli_info* args_info); 32 | void cli_parser_init (struct cli_info* args_info); 33 | void cli_parser_free (struct cli_info* args_info); 34 | 35 | 36 | #endif /* CLI_H */ 37 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #ifndef fsevent_watch_common_h 2 | #define fsevent_watch_common_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "compat.h" 9 | 10 | #define _str(s) #s 11 | #define _xstr(s) _str(s) 12 | 13 | #define COMPILED_AT __DATE__ " " __TIME__ 14 | 15 | #define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \ 16 | do { \ 17 | if ((flags) & (flag)) { \ 18 | fprintf(fd, "%s\n", msg); } } \ 19 | while (0) 20 | 21 | #define FLAG_CHECK_STDERR(flags, flag, msg) \ 22 | FPRINTF_FLAG_CHECK(flags, flag, msg, stderr) 23 | 24 | /* 25 | * FSEVENTSBITS: 26 | * generated by `make printflags` (and pasted here) 27 | * flags MUST be ordered (bits ascending) and sorted 28 | * 29 | * idea from: http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/ifconfig/ifconfig.c (see printb()) 30 | */ 31 | #define FSEVENTSBITS \ 32 | "\1mustscansubdirs\2userdropped\3kerneldropped\4eventidswrapped\5historydone\6rootchanged\7mount\10unmount\11created\12removed\13inodemetamod\14renamed\15modified\16finderinfomod\17changeowner\20xattrmod\21isfile\22isdir\23issymlink\24ownevent" 33 | 34 | static inline void 35 | sprintb(char *buf, unsigned short v, char *bits) 36 | { 37 | int i, any = 0; 38 | char c; 39 | char *bufp = buf; 40 | 41 | while ((i = *bits++)) { 42 | if (v & (1 << (i-1))) { 43 | if (any) 44 | *bufp++ = ','; 45 | any = 1; 46 | for (; (c = *bits) > 32; bits++) 47 | *bufp++ = c; 48 | } else 49 | for (; *bits > 32; bits++) 50 | ; 51 | } 52 | *bufp = '\0'; 53 | } 54 | 55 | #endif /* fsevent_watch_common_h */ 56 | -------------------------------------------------------------------------------- /compat.c: -------------------------------------------------------------------------------- 1 | #include "compat.h" 2 | 3 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 4 | FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf = 0x00000008; 5 | #endif 6 | 7 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 8 | FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents = 0x00000010; 9 | FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated = 0x00000100; 10 | FSEventStreamEventFlags kFSEventStreamEventFlagItemRemoved = 0x00000200; 11 | FSEventStreamEventFlags kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400; 12 | FSEventStreamEventFlags kFSEventStreamEventFlagItemRenamed = 0x00000800; 13 | FSEventStreamEventFlags kFSEventStreamEventFlagItemModified = 0x00001000; 14 | FSEventStreamEventFlags kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000; 15 | FSEventStreamEventFlags kFSEventStreamEventFlagItemChangeOwner = 0x00004000; 16 | FSEventStreamEventFlags kFSEventStreamEventFlagItemXattrMod = 0x00008000; 17 | FSEventStreamEventFlags kFSEventStreamEventFlagItemIsFile = 0x00010000; 18 | FSEventStreamEventFlags kFSEventStreamEventFlagItemIsDir = 0x00020000; 19 | FSEventStreamEventFlags kFSEventStreamEventFlagItemIsSymlink = 0x00040000; 20 | #endif 21 | 22 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1090 23 | FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf = 0x00000020; 24 | FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent = 0x00080000; 25 | #endif 26 | -------------------------------------------------------------------------------- /compat.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @headerfile compat.h 3 | * FSEventStream flag compatibility shim 4 | * 5 | * In order to compile a binary against an older SDK yet still support the 6 | * features present in later OS releases, we need to define any missing enum 7 | * constants not present in the older SDK. This allows us to safely defer 8 | * feature detection to runtime (and avoid recompilation). 9 | */ 10 | 11 | 12 | #ifndef fsevent_watch_compat_h 13 | #define fsevent_watch_compat_h 14 | 15 | #ifndef __CORESERVICES__ 16 | #include 17 | #endif // __CORESERVICES__ 18 | 19 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1060 20 | // ignoring events originating from the current process introduced in 10.6 21 | extern FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf; 22 | #endif 23 | 24 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 25 | // file-level events introduced in 10.7 26 | extern FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents; 27 | extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated, 28 | kFSEventStreamEventFlagItemRemoved, 29 | kFSEventStreamEventFlagItemInodeMetaMod, 30 | kFSEventStreamEventFlagItemRenamed, 31 | kFSEventStreamEventFlagItemModified, 32 | kFSEventStreamEventFlagItemFinderInfoMod, 33 | kFSEventStreamEventFlagItemChangeOwner, 34 | kFSEventStreamEventFlagItemXattrMod, 35 | kFSEventStreamEventFlagItemIsFile, 36 | kFSEventStreamEventFlagItemIsDir, 37 | kFSEventStreamEventFlagItemIsSymlink; 38 | #endif 39 | 40 | #if MAC_OS_X_VERSION_MAX_ALLOWED < 1090 41 | // marking, rather than ignoring, events originating from the current process introduced in 10.9 42 | extern FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf; 43 | extern FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent; 44 | #endif 45 | 46 | 47 | #endif // fsevent_watch_compat_h 48 | -------------------------------------------------------------------------------- /flags: -------------------------------------------------------------------------------- 1 | mustscansubdirs = 0x00000001 2 | userdropped = 0x00000002 3 | kerneldropped = 0x00000004 4 | eventidswrapped = 0x00000008 5 | historydone = 0x00000010 6 | rootchanged = 0x00000020 7 | mount = 0x00000040 8 | unmount = 0x00000080 9 | created = 0x00000100 10 | removed = 0x00000200 11 | inodemetamod = 0x00000400 12 | renamed = 0x00000800 13 | modified = 0x00001000 14 | finderinfomod = 0x00002000 15 | changeowner = 0x00004000 16 | xattrmod = 0x00008000 17 | isfile = 0x00010000 18 | isdir = 0x00020000 19 | issymlink = 0x00040000 20 | ownevent = 0x00080000 21 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 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 == 11) || (osMajorVersion == 12) || ((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 == 11) || (osMajorVersion == 12) || ((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 == 11) || (osMajorVersion == 12) || ((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 235 | --------------------------------------------------------------------------------