rb-fsevent 0.4.3.1 → 0.9.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }