rb-fsevent 0.4.3.1 → 0.9.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ //
2
+ // TSICTString.h
3
+ // TSITString
4
+ //
5
+ // Created by Travis Tilley on 9/27/11.
6
+ //
7
+
8
+ #ifndef TSICTString_H
9
+ #define TSICTString_H
10
+
11
+ #include <CoreFoundation/CoreFoundation.h>
12
+
13
+
14
+ typedef enum {
15
+ kTSITStringTagString = 0,
16
+ kTSITStringTagNumber = 1,
17
+ kTSITStringTagFloat = 2,
18
+ kTSITStringTagBool = 3,
19
+ kTSITStringTagNull = 4,
20
+ kTSITStringTagDict = 5,
21
+ kTSITStringTagList = 6,
22
+ kTSITStringTagInvalid = 7,
23
+ } TSITStringTag;
24
+
25
+ extern const char* const TNetstringTypes;
26
+ extern const char* const OTNetstringTypes;
27
+ extern const UInt8 TNetstringSeparator;
28
+
29
+ typedef enum {
30
+ kTSITStringFormatDefault = 0,
31
+ kTSITStringFormatOTNetstring = 1,
32
+ kTSITStringFormatTNetstring = 2,
33
+ } TSITStringFormat;
34
+
35
+ extern TSITStringFormat TSITStringDefaultFormat;
36
+
37
+ typedef struct TSITStringIntermediate {
38
+ CFDataRef data;
39
+ char* length;
40
+ TSITStringTag type;
41
+ TSITStringFormat format;
42
+ } TStringIRep;
43
+
44
+ typedef struct {
45
+ CFMutableDataRef buffer;
46
+ TSITStringFormat format;
47
+ } TStringCollectionCallbackContext;
48
+
49
+
50
+ void Init_TSICTString(void);
51
+
52
+ void TSICTStringSetDefaultFormat(TSITStringFormat format);
53
+ TSITStringFormat TSICTStringGetDefaultFormat(void);
54
+
55
+ void TSICTStringDestroy(TStringIRep* rep);
56
+
57
+ CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep);
58
+ CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format);
59
+
60
+ CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep);
61
+ CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format);
62
+
63
+ TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format);
64
+ TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format);
65
+ TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format);
66
+ TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format);
67
+ TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format);
68
+ TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format);
69
+ TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format);
70
+ TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format);
71
+ TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format);
72
+
73
+
74
+ #endif
@@ -0,0 +1,160 @@
1
+ #include <getopt.h>
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
+ " -s, --since-when=EventID fire historical events since ID",
10
+ " -l, --latency=seconds latency period (default='0.5')",
11
+ " -n, --no-defer enable no-defer latency modifier",
12
+ " -r, --watch-root watch for when the root path has changed",
13
+ // " -i, --ignore-self ignore current process",
14
+ " -F, --file-events provide file level event data",
15
+ " -f, --format=name output format (classic, niw, \n"
16
+ " tnetstring, otnetstring)",
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->format_arg = kFSEventWatchOutputFormatClassic;
29
+ }
30
+
31
+ static void cli_parser_release (struct cli_info* args_info)
32
+ {
33
+ unsigned int i;
34
+
35
+ for (i=0; i < args_info->inputs_num; ++i) {
36
+ free(args_info->inputs[i]);
37
+ }
38
+
39
+ if (args_info->inputs_num) {
40
+ free(args_info->inputs);
41
+ }
42
+
43
+ args_info->inputs_num = 0;
44
+ }
45
+
46
+ void cli_parser_init (struct cli_info* args_info)
47
+ {
48
+ default_args(args_info);
49
+
50
+ args_info->inputs = 0;
51
+ args_info->inputs_num = 0;
52
+ }
53
+
54
+ void cli_parser_free (struct cli_info* args_info)
55
+ {
56
+ cli_parser_release(args_info);
57
+ }
58
+
59
+ void cli_print_version (void)
60
+ {
61
+ printf("%s %s\n", CLI_NAME, CLI_VERSION);
62
+ #ifdef COMPILED_AT
63
+ printf("Compiled %s\n", COMPILED_AT);
64
+ #endif
65
+ }
66
+
67
+ void cli_print_help (void)
68
+ {
69
+ cli_print_version();
70
+
71
+ printf("\n%s\n", cli_info_purpose);
72
+ printf("\n%s\n", cli_info_usage);
73
+ printf("\n");
74
+
75
+ int i = 0;
76
+ while (cli_info_help[i]) {
77
+ printf("%s\n", cli_info_help[i++]);
78
+ }
79
+ }
80
+
81
+ int cli_parser (int argc, const char** argv, struct cli_info* args_info)
82
+ {
83
+ static struct option longopts[] = {
84
+ { "help", no_argument, NULL, 'h' },
85
+ { "version", no_argument, NULL, 'V' },
86
+ { "since-when", required_argument, NULL, 's' },
87
+ { "latency", required_argument, NULL, 'l' },
88
+ { "no-defer", no_argument, NULL, 'n' },
89
+ { "watch-root", no_argument, NULL, 'r' },
90
+ { "ignore-self", no_argument, NULL, 'i' },
91
+ { "file-events", no_argument, NULL, 'F' },
92
+ { "format", required_argument, NULL, 'f' },
93
+ { 0, 0, 0, 0 }
94
+ };
95
+
96
+ const char* shortopts = "hVs:l:nriFf:";
97
+
98
+ int c = -1;
99
+
100
+ while ((c = getopt_long(argc, (char * const*)argv, shortopts, longopts, NULL)) != -1) {
101
+ switch(c) {
102
+ case 's': // since-when
103
+ args_info->since_when_arg = strtoull(optarg, NULL, 0);
104
+ break;
105
+ case 'l': // latency
106
+ args_info->latency_arg = strtod(optarg, NULL);
107
+ break;
108
+ case 'n': // no-defer
109
+ args_info->no_defer_flag = true;
110
+ break;
111
+ case 'r': // watch-root
112
+ args_info->watch_root_flag = true;
113
+ break;
114
+ case 'i': // ignore-self
115
+ args_info->ignore_self_flag = true;
116
+ break;
117
+ case 'F': // file-events
118
+ args_info->file_events_flag = true;
119
+ break;
120
+ case 'f': // format
121
+ if (strcmp(optarg, "classic") == 0) {
122
+ args_info->format_arg = kFSEventWatchOutputFormatClassic;
123
+ } else if (strcmp(optarg, "niw") == 0) {
124
+ args_info->format_arg = kFSEventWatchOutputFormatNIW;
125
+ } else if (strcmp(optarg, "tnetstring") == 0) {
126
+ args_info->format_arg = kFSEventWatchOutputFormatTNetstring;
127
+ } else if (strcmp(optarg, "otnetstring") == 0) {
128
+ args_info->format_arg = kFSEventWatchOutputFormatOTNetstring;
129
+ } else {
130
+ fprintf(stderr, "Unknown output format: %s\n", optarg);
131
+ exit(EXIT_FAILURE);
132
+ }
133
+ break;
134
+ case 'V': // version
135
+ cli_print_version();
136
+ exit(EXIT_SUCCESS);
137
+ break;
138
+ case 'h': // help
139
+ case '?': // invalid option
140
+ case ':': // missing argument
141
+ cli_print_help();
142
+ exit((c == 'h') ? EXIT_SUCCESS : EXIT_FAILURE);
143
+ break;
144
+ }
145
+ }
146
+
147
+ if (optind < argc) {
148
+ int i = 0;
149
+ args_info->inputs_num = argc - optind;
150
+ args_info->inputs =
151
+ (char**)(malloc ((args_info->inputs_num)*sizeof(char*)));
152
+ while (optind < argc)
153
+ if (argv[optind++] != argv[0]) {
154
+ args_info->inputs[i++] = strdup(argv[optind-1]);
155
+ }
156
+ }
157
+
158
+ return EXIT_SUCCESS;
159
+ }
160
+
@@ -0,0 +1,39 @@
1
+ #ifndef CLI_H
2
+ #define CLI_H
3
+
4
+ #ifndef CLI_NAME
5
+ #define CLI_NAME "fsevent_watch"
6
+ #endif /* CLI_NAME */
7
+
8
+ #ifndef CLI_VERSION
9
+ #define CLI_VERSION "0.0.1"
10
+ #endif /* CLI_VERSION */
11
+
12
+ #include "common.h"
13
+
14
+ struct cli_info {
15
+ UInt64 since_when_arg;
16
+ double latency_arg;
17
+ bool no_defer_flag;
18
+ bool watch_root_flag;
19
+ bool ignore_self_flag;
20
+ bool file_events_flag;
21
+ enum FSEventWatchOutputFormat format_arg;
22
+
23
+ char** inputs;
24
+ unsigned inputs_num;
25
+ };
26
+
27
+ extern const char* cli_info_purpose;
28
+ extern const char* cli_info_usage;
29
+ extern const char* cli_info_help[];
30
+
31
+ void cli_print_help(void);
32
+ void cli_print_version(void);
33
+
34
+ int cli_parser (int argc, const char** argv, struct cli_info* args_info);
35
+ void cli_parser_init (struct cli_info* args_info);
36
+ void cli_parser_free (struct cli_info* args_info);
37
+
38
+
39
+ #endif /* CLI_H */
@@ -0,0 +1,33 @@
1
+ #ifndef fsevent_watch_common_h
2
+ #define fsevent_watch_common_h
3
+
4
+ #include <CoreFoundation/CoreFoundation.h>
5
+ #ifdef __OBJC__
6
+ #import <Foundation/Foundation.h>
7
+ #endif
8
+
9
+ #include <CoreServices/CoreServices.h>
10
+ #include "compat.h"
11
+ #include "TSICTString.h"
12
+
13
+ #define COMPILED_AT __DATE__ " " __TIME__
14
+
15
+ #define FLAG_CHECK(flags, flag) ((flags) & (flag))
16
+
17
+ #define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \
18
+ do { \
19
+ if (FLAG_CHECK(flags, flag)) { \
20
+ fprintf(fd, "%s", msg "\n"); } } \
21
+ while (0)
22
+
23
+ #define FLAG_CHECK_STDERR(flags, flag, msg) \
24
+ FPRINTF_FLAG_CHECK(flags, flag, msg, stderr)
25
+
26
+ enum FSEventWatchOutputFormat {
27
+ kFSEventWatchOutputFormatClassic,
28
+ kFSEventWatchOutputFormatNIW,
29
+ kFSEventWatchOutputFormatTNetstring,
30
+ kFSEventWatchOutputFormatOTNetstring
31
+ };
32
+
33
+ #endif /* fsevent_watch_common_h */
@@ -0,0 +1,40 @@
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 <CoreServices/CoreServices.h>
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
+ FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf = 0x00000008;
22
+ #endif
23
+
24
+ #if MAC_OS_X_VERSION_MAX_ALLOWED < 1070
25
+ // file-level events introduced in 10.7
26
+ FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents = 0x00000010;
27
+ FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated = 0x00000100,
28
+ kFSEventStreamEventFlagItemRemoved = 0x00000200,
29
+ kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400,
30
+ kFSEventStreamEventFlagItemRenamed = 0x00000800,
31
+ kFSEventStreamEventFlagItemModified = 0x00001000,
32
+ kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000,
33
+ kFSEventStreamEventFlagItemChangeOwner = 0x00004000,
34
+ kFSEventStreamEventFlagItemXattrMod = 0x00008000,
35
+ kFSEventStreamEventFlagItemIsFile = 0x00010000,
36
+ kFSEventStreamEventFlagItemIsDir = 0x00020000,
37
+ kFSEventStreamEventFlagItemIsSymlink = 0x00040000;
38
+ #endif
39
+
40
+ #endif // fsevent_watch_compat_h
@@ -0,0 +1,486 @@
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
+ enum FSEventWatchOutputFormat format;
20
+ } config = {
21
+ (UInt64) kFSEventStreamEventIdSinceNow,
22
+ (double) 0.3,
23
+ (CFOptionFlags) kFSEventStreamCreateFlagNone,
24
+ NULL,
25
+ kFSEventWatchOutputFormatClassic
26
+ };
27
+
28
+ // Prototypes
29
+ static void append_path(const char* path);
30
+ static void append_path2(const char* path);
31
+ static inline void parse_cli_settings(int argc, const char* argv[]);
32
+ static void callback(FSEventStreamRef streamRef,
33
+ void* clientCallBackInfo,
34
+ size_t numEvents,
35
+ void* eventPaths,
36
+ const FSEventStreamEventFlags eventFlags[],
37
+ const FSEventStreamEventId eventIds[]);
38
+
39
+ // Resolve a path and append it to the CLI settings structure
40
+ // The FSEvents API will, internally, resolve paths using a similar scheme.
41
+ // Performing this ahead of time makes things less confusing, IMHO.
42
+ __attribute__((unused)) static void append_path(const char* path)
43
+ {
44
+ #ifdef DEBUG
45
+ fprintf(stderr, "\n");
46
+ fprintf(stderr, "append_path called for: %s\n", path);
47
+ #endif
48
+
49
+ char fullPath[PATH_MAX + 1];
50
+
51
+ if (realpath(path, fullPath) == NULL) {
52
+ #ifdef DEBUG
53
+ fprintf(stderr, " realpath not directly resolvable from path\n");
54
+ #endif
55
+
56
+ if (path[0] != '/') {
57
+ #ifdef DEBUG
58
+ fprintf(stderr, " passed path is not absolute\n");
59
+ #endif
60
+ size_t len;
61
+ getcwd(fullPath, sizeof(fullPath));
62
+ #ifdef DEBUG
63
+ fprintf(stderr, " result of getcwd: %s\n", fullPath);
64
+ #endif
65
+ len = strlen(fullPath);
66
+ fullPath[len] = '/';
67
+ strlcpy(&fullPath[len + 1], path, sizeof(fullPath) - (len + 1));
68
+ } else {
69
+ #ifdef DEBUG
70
+ fprintf(stderr, " assuming path does not YET exist\n");
71
+ #endif
72
+ strlcpy(fullPath, path, sizeof(fullPath));
73
+ }
74
+ }
75
+
76
+ #ifdef DEBUG
77
+ fprintf(stderr, " resolved path to: %s\n", fullPath);
78
+ fprintf(stderr, "\n");
79
+ #endif
80
+
81
+ CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault,
82
+ fullPath,
83
+ kCFStringEncodingUTF8);
84
+ CFArrayAppendValue(config.paths, pathRef);
85
+ CFRelease(pathRef);
86
+ }
87
+
88
+ // straight from the rear
89
+ static void append_path2(const char* path)
90
+ {
91
+ #ifdef DEBUG
92
+ char fullPath[PATH_MAX + 1];
93
+
94
+ fprintf(stderr, "\n");
95
+ fprintf(stderr, "append_path_ called for: %s\n", path);
96
+ #endif
97
+
98
+ OSStatus err = noErr;
99
+ FSRef fsref = {};
100
+ AliasHandle itemAlias = NULL;
101
+ CFStringRef pathString = NULL;
102
+
103
+ err = FSPathMakeRefWithOptions((const UInt8*)path, kFSPathMakeRefDefaultOptions, &fsref, NULL);
104
+
105
+ if (err == noErr) {
106
+ #ifdef DEBUG
107
+ fprintf(stderr, " FSRef created\n");
108
+ #endif
109
+ err = FSNewAlias(NULL, &fsref, &itemAlias);
110
+
111
+ if (err == noErr) {
112
+ #ifdef DEBUG
113
+ fprintf(stderr, " AliasHandle created\n");
114
+ #endif
115
+ err = FSCopyAliasInfo(itemAlias, NULL, NULL, &pathString, NULL, NULL);
116
+ }
117
+
118
+ if (err == noErr) {
119
+ #ifdef DEBUG
120
+ fprintf(stderr, " Alias Info copied\n");
121
+ CFStringGetFileSystemRepresentation(pathString, fullPath, PATH_MAX + 1);
122
+ fprintf(stderr, " resolved path to: %s\n", fullPath);
123
+ fprintf(stderr, "\n");
124
+ #endif
125
+
126
+ CFArrayAppendValue(config.paths, pathString);
127
+ }
128
+ } else {
129
+ #ifdef DEBUG
130
+ fprintf(stderr, " assuming path does not YET exist\n");
131
+ #endif
132
+
133
+ CFURLRef pathURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
134
+ (const UInt8*)path,
135
+ strlen(path), true);
136
+ pathString = CFURLCopyStrictPath(pathURL, NULL);
137
+ CFArrayAppendValue(config.paths, pathString);
138
+ CFRelease(pathURL);
139
+
140
+ #ifdef DEBUG
141
+ CFStringGetFileSystemRepresentation(pathString, fullPath, PATH_MAX + 1);
142
+ fprintf(stderr, " resolved path to: %s\n", fullPath);
143
+ fprintf(stderr, "\n");
144
+ #endif
145
+ }
146
+
147
+ if (pathString != NULL) {
148
+ CFRelease(pathString);
149
+ }
150
+
151
+ if (itemAlias != NULL) {
152
+ DisposeHandle((Handle)itemAlias);
153
+ assert(MemError() == noErr);
154
+ }
155
+ }
156
+
157
+ // Parse commandline settings
158
+ static inline void parse_cli_settings(int argc, const char* argv[])
159
+ {
160
+ // runtime os version detection
161
+ SInt32 osMajorVersion, osMinorVersion;
162
+ if (!(Gestalt(gestaltSystemVersionMajor, &osMajorVersion) == noErr)) {
163
+ osMajorVersion = 0;
164
+ }
165
+ if (!(Gestalt(gestaltSystemVersionMinor, &osMinorVersion) == noErr)) {
166
+ osMinorVersion = 0;
167
+ }
168
+
169
+ if ((osMajorVersion == 10) & (osMinorVersion < 5)) {
170
+ fprintf(stderr, "The FSEvents API is unavailable on this version of macos!\n");
171
+ exit(EXIT_FAILURE);
172
+ }
173
+
174
+ struct cli_info args_info;
175
+ cli_parser_init(&args_info);
176
+
177
+ if (cli_parser(argc, argv, &args_info) != 0) {
178
+ exit(EXIT_FAILURE);
179
+ }
180
+
181
+ config.paths = CFArrayCreateMutable(NULL,
182
+ (CFIndex)0,
183
+ &kCFTypeArrayCallBacks);
184
+
185
+ config.sinceWhen = args_info.since_when_arg;
186
+ config.latency = args_info.latency_arg;
187
+ config.format = args_info.format_arg;
188
+
189
+ if (args_info.no_defer_flag) {
190
+ config.flags |= kFSEventStreamCreateFlagNoDefer;
191
+ }
192
+ if (args_info.watch_root_flag) {
193
+ config.flags |= kFSEventStreamCreateFlagWatchRoot;
194
+ }
195
+
196
+ if (args_info.ignore_self_flag) {
197
+ if ((osMajorVersion == 10) & (osMinorVersion >= 6)) {
198
+ config.flags |= kFSEventStreamCreateFlagIgnoreSelf;
199
+ } else {
200
+ fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n");
201
+ exit(EXIT_FAILURE);
202
+ }
203
+ }
204
+
205
+ if (args_info.file_events_flag) {
206
+ if ((osMajorVersion == 10) & (osMinorVersion >= 7)) {
207
+ config.flags |= kFSEventStreamCreateFlagFileEvents;
208
+ } else {
209
+ fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n");
210
+ exit(EXIT_FAILURE);
211
+ }
212
+ }
213
+
214
+ if (args_info.inputs_num == 0) {
215
+ append_path2(".");
216
+ } else {
217
+ for (unsigned int i=0; i < args_info.inputs_num; ++i) {
218
+ append_path2(args_info.inputs[i]);
219
+ }
220
+ }
221
+
222
+ cli_parser_free(&args_info);
223
+
224
+ #ifdef DEBUG
225
+ fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen);
226
+ fprintf(stderr, "config.latency %f\n", config.latency);
227
+
228
+ // STFU clang
229
+ #if __LP64__
230
+ fprintf(stderr, "config.flags %#.8x\n", config.flags);
231
+ #else
232
+ fprintf(stderr, "config.flags %#.8lx\n", config.flags);
233
+ #endif
234
+
235
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes,
236
+ " Using CF instead of C types");
237
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer,
238
+ " NoDefer latency modifier enabled");
239
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot,
240
+ " WatchRoot notifications enabled");
241
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf,
242
+ " IgnoreSelf enabled");
243
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents,
244
+ " FileEvents enabled");
245
+
246
+ fprintf(stderr, "config.paths\n");
247
+
248
+ long numpaths = CFArrayGetCount(config.paths);
249
+
250
+ for (long i = 0; i < numpaths; i++) {
251
+ char path[PATH_MAX];
252
+ CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i),
253
+ path,
254
+ PATH_MAX,
255
+ kCFStringEncodingUTF8);
256
+ fprintf(stderr, " %s\n", path);
257
+ }
258
+
259
+ fprintf(stderr, "\n");
260
+ #endif
261
+ }
262
+
263
+ // original output format for rb-fsevent
264
+ static void classic_output_format(size_t numEvents,
265
+ char** paths)
266
+ {
267
+ for (size_t i = 0; i < numEvents; i++) {
268
+ fprintf(stdout, "%s:", paths[i]);
269
+ }
270
+ fprintf(stdout, "\n");
271
+ }
272
+
273
+ // output format used in the Yoshimasa Niwa branch of rb-fsevent
274
+ static void niw_output_format(size_t numEvents,
275
+ char** paths,
276
+ const FSEventStreamEventFlags eventFlags[],
277
+ const FSEventStreamEventId eventIds[])
278
+ {
279
+ for (size_t i = 0; i < numEvents; i++) {
280
+ fprintf(stdout, "%lu:%llu:%s\n",
281
+ (unsigned long)eventFlags[i],
282
+ (unsigned long long)eventIds[i],
283
+ paths[i]);
284
+ }
285
+ fprintf(stdout, "\n");
286
+ }
287
+
288
+ static void tstring_output_format(size_t numEvents,
289
+ char** paths,
290
+ const FSEventStreamEventFlags eventFlags[],
291
+ const FSEventStreamEventId eventIds[],
292
+ TSITStringFormat format)
293
+ {
294
+ CFMutableArrayRef events = CFArrayCreateMutable(kCFAllocatorDefault,
295
+ 0, &kCFTypeArrayCallBacks);
296
+
297
+ for (size_t i = 0; i < numEvents; i++) {
298
+ CFMutableDictionaryRef event = CFDictionaryCreateMutable(kCFAllocatorDefault,
299
+ 0,
300
+ &kCFTypeDictionaryKeyCallBacks,
301
+ &kCFTypeDictionaryValueCallBacks);
302
+
303
+ CFStringRef path = CFStringCreateWithBytes(kCFAllocatorDefault,
304
+ (const UInt8*)paths[i],
305
+ strlen(paths[i]),
306
+ kCFStringEncodingUTF8,
307
+ false);
308
+ CFDictionarySetValue(event, CFSTR("path"), path);
309
+
310
+ CFNumberRef flags = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &eventFlags[i]);
311
+ CFDictionarySetValue(event, CFSTR("flags"), flags);
312
+
313
+ CFNumberRef ident = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &eventIds[i]);
314
+ CFDictionarySetValue(event, CFSTR("id"), ident);
315
+
316
+ CFArrayAppendValue(events, event);
317
+
318
+ CFRelease(event);
319
+ CFRelease(path);
320
+ CFRelease(flags);
321
+ CFRelease(ident);
322
+ }
323
+
324
+ CFMutableDictionaryRef meta = CFDictionaryCreateMutable(kCFAllocatorDefault,
325
+ 0,
326
+ &kCFTypeDictionaryKeyCallBacks,
327
+ &kCFTypeDictionaryValueCallBacks);
328
+ CFDictionarySetValue(meta, CFSTR("events"), events);
329
+
330
+ CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &numEvents);
331
+ CFDictionarySetValue(meta, CFSTR("numEvents"), num);
332
+
333
+ CFDataRef data = TSICTStringCreateRenderedDataFromObjectWithFormat(meta, format);
334
+ fprintf(stdout, "%s", CFDataGetBytePtr(data));
335
+
336
+ CFRelease(events);
337
+ CFRelease(num);
338
+ CFRelease(meta);
339
+ CFRelease(data);
340
+ }
341
+
342
+ static void callback(__attribute__((unused)) FSEventStreamRef streamRef,
343
+ __attribute__((unused)) void* clientCallBackInfo,
344
+ size_t numEvents,
345
+ void* eventPaths,
346
+ const FSEventStreamEventFlags eventFlags[],
347
+ const FSEventStreamEventId eventIds[])
348
+ {
349
+ char** paths = eventPaths;
350
+
351
+
352
+ #ifdef DEBUG
353
+ fprintf(stderr, "\n");
354
+ fprintf(stderr, "FSEventStreamCallback fired!\n");
355
+ fprintf(stderr, " numEvents: %lu\n", numEvents);
356
+
357
+ for (size_t i = 0; i < numEvents; i++) {
358
+ fprintf(stderr, "\n");
359
+ fprintf(stderr, " event ID: %llu\n", eventIds[i]);
360
+
361
+ // STFU clang
362
+ #if __LP64__
363
+ fprintf(stderr, " event flags: %#.8x\n", eventFlags[i]);
364
+ #else
365
+ fprintf(stderr, " event flags: %#.8lx\n", eventFlags[i]);
366
+ #endif
367
+
368
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs,
369
+ " Recursive scanning of directory required");
370
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUserDropped,
371
+ " Buffering problem: events dropped user-side");
372
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagKernelDropped,
373
+ " Buffering problem: events dropped kernel-side");
374
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped,
375
+ " Event IDs have wrapped");
376
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagHistoryDone,
377
+ " All historical events have been processed");
378
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagRootChanged,
379
+ " Root path has changed");
380
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMount,
381
+ " A new volume was mounted at this path");
382
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUnmount,
383
+ " A volume was unmounted from this path");
384
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemCreated,
385
+ " Item created");
386
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRemoved,
387
+ " Item removed");
388
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod,
389
+ " Item metadata modified");
390
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRenamed,
391
+ " Item renamed");
392
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemModified,
393
+ " Item modified");
394
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod,
395
+ " Item Finder Info modified");
396
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner,
397
+ " Item changed ownership");
398
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemXattrMod,
399
+ " Item extended attributes modified");
400
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsFile,
401
+ " Item is a file");
402
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsDir,
403
+ " Item is a directory");
404
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink,
405
+ " Item is a symbolic link");
406
+
407
+ fprintf(stderr, " event path: %s\n", paths[i]);
408
+ fprintf(stderr, "\n");
409
+ }
410
+
411
+ fprintf(stderr, "\n");
412
+ #endif
413
+
414
+ if (config.format == kFSEventWatchOutputFormatClassic) {
415
+ classic_output_format(numEvents, paths);
416
+ } else if (config.format == kFSEventWatchOutputFormatNIW) {
417
+ niw_output_format(numEvents, paths, eventFlags, eventIds);
418
+ } else if (config.format == kFSEventWatchOutputFormatTNetstring) {
419
+ tstring_output_format(numEvents, paths, eventFlags, eventIds,
420
+ kTSITStringFormatTNetstring);
421
+ } else if (config.format == kFSEventWatchOutputFormatOTNetstring) {
422
+ tstring_output_format(numEvents, paths, eventFlags, eventIds,
423
+ kTSITStringFormatOTNetstring);
424
+ }
425
+
426
+ fflush(stdout);
427
+ }
428
+
429
+ int main(int argc, const char* argv[])
430
+ {
431
+ /*
432
+ * a subprocess will initially inherit the process group of its parent. the
433
+ * process group may have a control terminal associated with it, which would
434
+ * be the first tty device opened by the group leader. typically the group
435
+ * leader is your shell and the control terminal is your login device. a
436
+ * subset of signals triggered on the control terminal are sent to all members
437
+ * of the process group, in large part to facilitate sane and consistent
438
+ * cleanup (ex: control terminal was closed).
439
+ *
440
+ * so why the overly descriptive lecture style comment?
441
+ * 1. SIGINT and SIGQUIT are among the signals with this behavior
442
+ * 2. a number of applications gank the above for their own use
443
+ * 3. ruby's insanely useful "guard" is one of these applications
444
+ * 4. despite having some level of understanding of POSIX signals and a few
445
+ * of the scenarios that might cause problems, i learned this one only
446
+ * after reading ruby 1.9's process.c
447
+ * 5. if left completely undocumented, even slightly obscure bugfixes
448
+ * may be removed as cruft by a future maintainer
449
+ *
450
+ * hindsight is 20/20 addition: if you're single-threaded and blocking on IO
451
+ * with a subprocess, then handlers for deferrable signals might not get run
452
+ * when you expect them to. In the case of Ruby 1.8, that means making use of
453
+ * IO::select, which will preserve correct signal handling behavior.
454
+ */
455
+ if (setpgid(0,0) < 0) {
456
+ fprintf(stderr, "Unable to set new process group.\n");
457
+ return 1;
458
+ }
459
+
460
+ parse_cli_settings(argc, argv);
461
+
462
+ FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
463
+ FSEventStreamRef stream;
464
+ stream = FSEventStreamCreate(kCFAllocatorDefault,
465
+ (FSEventStreamCallback)&callback,
466
+ &context,
467
+ config.paths,
468
+ config.sinceWhen,
469
+ config.latency,
470
+ config.flags);
471
+
472
+ #ifdef DEBUG
473
+ FSEventStreamShow(stream);
474
+ fprintf(stderr, "\n");
475
+ #endif
476
+
477
+ FSEventStreamScheduleWithRunLoop(stream,
478
+ CFRunLoopGetCurrent(),
479
+ kCFRunLoopDefaultMode);
480
+ FSEventStreamStart(stream);
481
+ CFRunLoopRun();
482
+ FSEventStreamFlushSync(stream);
483
+ FSEventStreamStop(stream);
484
+
485
+ return 0;
486
+ }