guard 1.0.0 → 1.0.1

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.
Files changed (37) hide show
  1. data/CHANGELOG.md +17 -0
  2. data/README.md +50 -30
  3. data/bin/fsevent_watch_guard_guard +0 -0
  4. data/lib/guard.rb +44 -26
  5. data/lib/guard/cli.rb +23 -6
  6. data/lib/guard/dsl.rb +33 -15
  7. data/lib/guard/listener.rb +1 -1
  8. data/lib/guard/listeners/darwin.rb +1 -1
  9. data/lib/guard/notifier.rb +3 -1
  10. data/lib/guard/notifiers/notifysend.rb +81 -0
  11. data/lib/guard/version.rb +1 -1
  12. data/lib/guard/version.rbc +180 -0
  13. data/lib/vendor/darwin/README.rdoc +3 -2
  14. data/lib/vendor/darwin/bin/fsevent_watch +0 -0
  15. data/lib/vendor/darwin/ext/fsevent_watch/Info.plist +38 -0
  16. data/lib/vendor/darwin/ext/fsevent_watch/LICENSE +21 -0
  17. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch.xcodeproj/project.pbxproj +254 -0
  18. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/TSICTString.c +394 -0
  19. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/TSICTString.h +74 -0
  20. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/cli.c +160 -0
  21. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/cli.h +45 -0
  22. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/common.h +34 -0
  23. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/compat.c +20 -0
  24. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/compat.h +40 -0
  25. data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/main.c +509 -0
  26. data/lib/vendor/darwin/ext/fsevent_watch/xcconfig/Common.xcconfig +82 -0
  27. data/lib/vendor/darwin/ext/fsevent_watch/xcconfig/Debug.xcconfig +19 -0
  28. data/lib/vendor/darwin/ext/fsevent_watch/xcconfig/Release.xcconfig +23 -0
  29. data/lib/vendor/darwin/ext/fsevent_watch/xcconfig/fsevent_watch.xcconfig +17 -0
  30. data/lib/vendor/darwin/ext/rakefile.rb +47 -0
  31. data/lib/vendor/darwin/ext/rb-fsevent.xcconfig +33 -0
  32. data/lib/vendor/darwin/lib/rb-fsevent/version.rb +1 -1
  33. data/lib/vendor/darwin/rb-fsevent.gemspec +3 -2
  34. data/lib/vendor/darwin/spec/spec_helper.rb +1 -2
  35. metadata +40 -25
  36. data/lib/vendor/darwin/ext/extconf.rb +0 -64
  37. data/lib/vendor/darwin/ext/fsevent/fsevent_watch.c +0 -226
@@ -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 = (unsigned int)(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,45 @@
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 PROJECT_VERSION
9
+ #error "PROJECT_VERSION not set"
10
+ #endif /* PROJECT_VERSION */
11
+
12
+ #ifndef CLI_VERSION
13
+ #define _str(s) #s
14
+ #define _xstr(s) _str(s)
15
+ #define CLI_VERSION _xstr(PROJECT_VERSION)
16
+ #endif /* CLI_VERSION */
17
+
18
+ #include "common.h"
19
+
20
+ struct cli_info {
21
+ UInt64 since_when_arg;
22
+ double latency_arg;
23
+ bool no_defer_flag;
24
+ bool watch_root_flag;
25
+ bool ignore_self_flag;
26
+ bool file_events_flag;
27
+ enum FSEventWatchOutputFormat format_arg;
28
+
29
+ char** inputs;
30
+ unsigned inputs_num;
31
+ };
32
+
33
+ extern const char* cli_info_purpose;
34
+ extern const char* cli_info_usage;
35
+ extern const char* cli_info_help[];
36
+
37
+ void cli_print_help(void);
38
+ void cli_print_version(void);
39
+
40
+ int cli_parser (int argc, const char** argv, struct cli_info* args_info);
41
+ void cli_parser_init (struct cli_info* args_info);
42
+ void cli_parser_free (struct cli_info* args_info);
43
+
44
+
45
+ #endif /* CLI_H */
@@ -0,0 +1,34 @@
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 <unistd.h>
11
+ #include "compat.h"
12
+ #include "TSICTString.h"
13
+
14
+ #define COMPILED_AT __DATE__ " " __TIME__
15
+
16
+ #define FLAG_CHECK(flags, flag) ((flags) & (flag))
17
+
18
+ #define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \
19
+ do { \
20
+ if (FLAG_CHECK(flags, flag)) { \
21
+ fprintf(fd, "%s", msg "\n"); } } \
22
+ while (0)
23
+
24
+ #define FLAG_CHECK_STDERR(flags, flag, msg) \
25
+ FPRINTF_FLAG_CHECK(flags, flag, msg, stderr)
26
+
27
+ enum FSEventWatchOutputFormat {
28
+ kFSEventWatchOutputFormatClassic,
29
+ kFSEventWatchOutputFormatNIW,
30
+ kFSEventWatchOutputFormatTNetstring,
31
+ kFSEventWatchOutputFormatOTNetstring
32
+ };
33
+
34
+ #endif /* fsevent_watch_common_h */
@@ -0,0 +1,20 @@
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
@@ -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
+ 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
+ #endif // fsevent_watch_compat_h
@@ -0,0 +1,509 @@
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 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
+ // 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
+ 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
+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
50
+
51
+ #ifdef DEBUG
52
+ fprintf(stderr, "compiled against 10.6+, using CFURLCreateFileReferenceURL\n");
53
+ #endif
54
+
55
+ CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)path, (CFIndex)strlen(path), false);
56
+ CFURLRef placeholder = CFURLCopyAbsoluteURL(url);
57
+ CFRelease(url);
58
+
59
+ CFMutableArrayRef imaginary = NULL;
60
+
61
+ // if we don't have an existing url, spin until we get to a parent that
62
+ // does exist, saving any imaginary components for appending back later
63
+ while(!CFURLResourceIsReachable(placeholder, NULL)) {
64
+ #ifdef DEBUG
65
+ fprintf(stderr, "path does not exist\n");
66
+ #endif
67
+
68
+ CFStringRef child;
69
+
70
+ if (imaginary == NULL) {
71
+ imaginary = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
72
+ }
73
+
74
+ child = CFURLCopyLastPathComponent(placeholder);
75
+ CFArrayInsertValueAtIndex(imaginary, 0, child);
76
+ CFRelease(child);
77
+
78
+ url = CFURLCreateCopyDeletingLastPathComponent(NULL, placeholder);
79
+ CFRelease(placeholder);
80
+ placeholder = url;
81
+
82
+ #ifdef DEBUG
83
+ fprintf(stderr, "parent: ");
84
+ CFShow(placeholder);
85
+ #endif
86
+ }
87
+
88
+ #ifdef DEBUG
89
+ fprintf(stderr, "path exists\n");
90
+ #endif
91
+
92
+ // realpath() doesn't always return the correct case for a path, so this
93
+ // is a funky workaround that converts a path into a (volId/inodeId) pair
94
+ // and asks what the path should be for that. since it looks at the actual
95
+ // inode instead of returning the same case passed in like realpath()
96
+ // appears to do for HFS+, it should always be correct.
97
+ url = CFURLCreateFileReferenceURL(NULL, placeholder, NULL);
98
+ CFRelease(placeholder);
99
+ placeholder = CFURLCreateFilePathURL(NULL, url, NULL);
100
+ CFRelease(url);
101
+
102
+ #ifdef DEBUG
103
+ fprintf(stderr, "path resolved to: ");
104
+ CFShow(placeholder);
105
+ #endif
106
+
107
+ // if we stripped off any imaginary path components, append them back on
108
+ if (imaginary != NULL) {
109
+ CFIndex count = CFArrayGetCount(imaginary);
110
+ for (CFIndex i = 0; i<count; i++) {
111
+ CFStringRef component = CFArrayGetValueAtIndex(imaginary, i);
112
+ #ifdef DEBUG
113
+ fprintf(stderr, "appending component: ");
114
+ CFShow(component);
115
+ #endif
116
+ url = CFURLCreateCopyAppendingPathComponent(NULL, placeholder, component, false);
117
+ CFRelease(placeholder);
118
+ placeholder = url;
119
+ }
120
+ CFRelease(imaginary);
121
+ }
122
+
123
+ #ifdef DEBUG
124
+ fprintf(stderr, "result: ");
125
+ CFShow(placeholder);
126
+ #endif
127
+
128
+ CFStringRef cfPath = CFURLCopyFileSystemPath(placeholder, kCFURLPOSIXPathStyle);
129
+ CFArrayAppendValue(config.paths, cfPath);
130
+ CFRelease(cfPath);
131
+ CFRelease(placeholder);
132
+
133
+ #else
134
+
135
+ #ifdef DEBUG
136
+ fprintf(stderr, "compiled against 10.5, using realpath()\n");
137
+ #endif
138
+
139
+ char fullPath[PATH_MAX + 1];
140
+
141
+ if (realpath(path, fullPath) == NULL) {
142
+ #ifdef DEBUG
143
+ fprintf(stderr, " realpath not directly resolvable from path\n");
144
+ #endif
145
+
146
+ if (path[0] != '/') {
147
+ #ifdef DEBUG
148
+ fprintf(stderr, " passed path is not absolute\n");
149
+ #endif
150
+ size_t len;
151
+ getcwd(fullPath, sizeof(fullPath));
152
+ #ifdef DEBUG
153
+ fprintf(stderr, " result of getcwd: %s\n", fullPath);
154
+ #endif
155
+ len = strlen(fullPath);
156
+ fullPath[len] = '/';
157
+ strlcpy(&fullPath[len + 1], path, sizeof(fullPath) - (len + 1));
158
+ } else {
159
+ #ifdef DEBUG
160
+ fprintf(stderr, " assuming path does not YET exist\n");
161
+ #endif
162
+ strlcpy(fullPath, path, sizeof(fullPath));
163
+ }
164
+ }
165
+
166
+ #ifdef DEBUG
167
+ fprintf(stderr, " resolved path to: %s\n", fullPath);
168
+ fprintf(stderr, "\n");
169
+ #endif
170
+
171
+ CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault,
172
+ fullPath,
173
+ kCFStringEncodingUTF8);
174
+ CFArrayAppendValue(config.paths, pathRef);
175
+ CFRelease(pathRef);
176
+
177
+ #endif
178
+ }
179
+
180
+ // Parse commandline settings
181
+ static inline void parse_cli_settings(int argc, const char* argv[])
182
+ {
183
+ // runtime os version detection
184
+ SInt32 osMajorVersion, osMinorVersion;
185
+ if (!(Gestalt(gestaltSystemVersionMajor, &osMajorVersion) == noErr)) {
186
+ osMajorVersion = 0;
187
+ }
188
+ if (!(Gestalt(gestaltSystemVersionMinor, &osMinorVersion) == noErr)) {
189
+ osMinorVersion = 0;
190
+ }
191
+
192
+ if ((osMajorVersion == 10) & (osMinorVersion < 5)) {
193
+ fprintf(stderr, "The FSEvents API is unavailable on this version of macos!\n");
194
+ exit(EXIT_FAILURE);
195
+ }
196
+
197
+ struct cli_info args_info;
198
+ cli_parser_init(&args_info);
199
+
200
+ if (cli_parser(argc, argv, &args_info) != 0) {
201
+ exit(EXIT_FAILURE);
202
+ }
203
+
204
+ config.paths = CFArrayCreateMutable(NULL,
205
+ (CFIndex)0,
206
+ &kCFTypeArrayCallBacks);
207
+
208
+ config.sinceWhen = args_info.since_when_arg;
209
+ config.latency = args_info.latency_arg;
210
+ config.format = args_info.format_arg;
211
+
212
+ if (args_info.no_defer_flag) {
213
+ config.flags |= kFSEventStreamCreateFlagNoDefer;
214
+ }
215
+ if (args_info.watch_root_flag) {
216
+ config.flags |= kFSEventStreamCreateFlagWatchRoot;
217
+ }
218
+
219
+ if (args_info.ignore_self_flag) {
220
+ if ((osMajorVersion == 10) & (osMinorVersion >= 6)) {
221
+ config.flags |= kFSEventStreamCreateFlagIgnoreSelf;
222
+ } else {
223
+ fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n");
224
+ exit(EXIT_FAILURE);
225
+ }
226
+ }
227
+
228
+ if (args_info.file_events_flag) {
229
+ if ((osMajorVersion == 10) & (osMinorVersion >= 7)) {
230
+ config.flags |= kFSEventStreamCreateFlagFileEvents;
231
+ } else {
232
+ fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n");
233
+ exit(EXIT_FAILURE);
234
+ }
235
+ }
236
+
237
+ if (args_info.inputs_num == 0) {
238
+ append_path(".");
239
+ } else {
240
+ for (unsigned int i=0; i < args_info.inputs_num; ++i) {
241
+ append_path(args_info.inputs[i]);
242
+ }
243
+ }
244
+
245
+ cli_parser_free(&args_info);
246
+
247
+ #ifdef DEBUG
248
+ fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen);
249
+ fprintf(stderr, "config.latency %f\n", config.latency);
250
+
251
+ // STFU clang
252
+ #if __LP64__
253
+ fprintf(stderr, "config.flags %#.8x\n", config.flags);
254
+ #else
255
+ fprintf(stderr, "config.flags %#.8lx\n", config.flags);
256
+ #endif
257
+
258
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes,
259
+ " Using CF instead of C types");
260
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer,
261
+ " NoDefer latency modifier enabled");
262
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot,
263
+ " WatchRoot notifications enabled");
264
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf,
265
+ " IgnoreSelf enabled");
266
+ FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents,
267
+ " FileEvents enabled");
268
+
269
+ fprintf(stderr, "config.paths\n");
270
+
271
+ long numpaths = CFArrayGetCount(config.paths);
272
+
273
+ for (long i = 0; i < numpaths; i++) {
274
+ char path[PATH_MAX];
275
+ CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i),
276
+ path,
277
+ PATH_MAX,
278
+ kCFStringEncodingUTF8);
279
+ fprintf(stderr, " %s\n", path);
280
+ }
281
+
282
+ fprintf(stderr, "\n");
283
+ #endif
284
+ }
285
+
286
+ // original output format for rb-fsevent
287
+ static void classic_output_format(size_t numEvents,
288
+ char** paths)
289
+ {
290
+ for (size_t i = 0; i < numEvents; i++) {
291
+ fprintf(stdout, "%s:", paths[i]);
292
+ }
293
+ fprintf(stdout, "\n");
294
+ }
295
+
296
+ // output format used in the Yoshimasa Niwa branch of rb-fsevent
297
+ static void niw_output_format(size_t numEvents,
298
+ char** paths,
299
+ const FSEventStreamEventFlags eventFlags[],
300
+ const FSEventStreamEventId eventIds[])
301
+ {
302
+ for (size_t i = 0; i < numEvents; i++) {
303
+ fprintf(stdout, "%lu:%llu:%s\n",
304
+ (unsigned long)eventFlags[i],
305
+ (unsigned long long)eventIds[i],
306
+ paths[i]);
307
+ }
308
+ fprintf(stdout, "\n");
309
+ }
310
+
311
+ static void tstring_output_format(size_t numEvents,
312
+ char** paths,
313
+ const FSEventStreamEventFlags eventFlags[],
314
+ const FSEventStreamEventId eventIds[],
315
+ TSITStringFormat format)
316
+ {
317
+ CFMutableArrayRef events = CFArrayCreateMutable(kCFAllocatorDefault,
318
+ 0, &kCFTypeArrayCallBacks);
319
+
320
+ for (size_t i = 0; i < numEvents; i++) {
321
+ CFMutableDictionaryRef event = CFDictionaryCreateMutable(kCFAllocatorDefault,
322
+ 0,
323
+ &kCFTypeDictionaryKeyCallBacks,
324
+ &kCFTypeDictionaryValueCallBacks);
325
+
326
+ CFStringRef path = CFStringCreateWithBytes(kCFAllocatorDefault,
327
+ (const UInt8*)paths[i],
328
+ (CFIndex)strlen(paths[i]),
329
+ kCFStringEncodingUTF8,
330
+ false);
331
+ CFDictionarySetValue(event, CFSTR("path"), path);
332
+
333
+ CFNumberRef flags = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &eventFlags[i]);
334
+ CFDictionarySetValue(event, CFSTR("flags"), flags);
335
+
336
+ CFNumberRef ident = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &eventIds[i]);
337
+ CFDictionarySetValue(event, CFSTR("id"), ident);
338
+
339
+ CFArrayAppendValue(events, event);
340
+
341
+ CFRelease(event);
342
+ CFRelease(path);
343
+ CFRelease(flags);
344
+ CFRelease(ident);
345
+ }
346
+
347
+ CFMutableDictionaryRef meta = CFDictionaryCreateMutable(kCFAllocatorDefault,
348
+ 0,
349
+ &kCFTypeDictionaryKeyCallBacks,
350
+ &kCFTypeDictionaryValueCallBacks);
351
+ CFDictionarySetValue(meta, CFSTR("events"), events);
352
+
353
+ CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &numEvents);
354
+ CFDictionarySetValue(meta, CFSTR("numEvents"), num);
355
+
356
+ CFDataRef data = TSICTStringCreateRenderedDataFromObjectWithFormat(meta, format);
357
+ fprintf(stdout, "%s", CFDataGetBytePtr(data));
358
+
359
+ CFRelease(events);
360
+ CFRelease(num);
361
+ CFRelease(meta);
362
+ CFRelease(data);
363
+ }
364
+
365
+ static void callback(__attribute__((unused)) FSEventStreamRef streamRef,
366
+ __attribute__((unused)) void* clientCallBackInfo,
367
+ size_t numEvents,
368
+ void* eventPaths,
369
+ const FSEventStreamEventFlags eventFlags[],
370
+ const FSEventStreamEventId eventIds[])
371
+ {
372
+ char** paths = eventPaths;
373
+
374
+
375
+ #ifdef DEBUG
376
+ fprintf(stderr, "\n");
377
+ fprintf(stderr, "FSEventStreamCallback fired!\n");
378
+ fprintf(stderr, " numEvents: %lu\n", numEvents);
379
+
380
+ for (size_t i = 0; i < numEvents; i++) {
381
+ fprintf(stderr, "\n");
382
+ fprintf(stderr, " event ID: %llu\n", eventIds[i]);
383
+
384
+ // STFU clang
385
+ #if __LP64__
386
+ fprintf(stderr, " event flags: %#.8x\n", eventFlags[i]);
387
+ #else
388
+ fprintf(stderr, " event flags: %#.8lx\n", eventFlags[i]);
389
+ #endif
390
+
391
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs,
392
+ " Recursive scanning of directory required");
393
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUserDropped,
394
+ " Buffering problem: events dropped user-side");
395
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagKernelDropped,
396
+ " Buffering problem: events dropped kernel-side");
397
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped,
398
+ " Event IDs have wrapped");
399
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagHistoryDone,
400
+ " All historical events have been processed");
401
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagRootChanged,
402
+ " Root path has changed");
403
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMount,
404
+ " A new volume was mounted at this path");
405
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUnmount,
406
+ " A volume was unmounted from this path");
407
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemCreated,
408
+ " Item created");
409
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRemoved,
410
+ " Item removed");
411
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod,
412
+ " Item metadata modified");
413
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRenamed,
414
+ " Item renamed");
415
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemModified,
416
+ " Item modified");
417
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod,
418
+ " Item Finder Info modified");
419
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner,
420
+ " Item changed ownership");
421
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemXattrMod,
422
+ " Item extended attributes modified");
423
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsFile,
424
+ " Item is a file");
425
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsDir,
426
+ " Item is a directory");
427
+ FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink,
428
+ " Item is a symbolic link");
429
+
430
+ fprintf(stderr, " event path: %s\n", paths[i]);
431
+ fprintf(stderr, "\n");
432
+ }
433
+
434
+ fprintf(stderr, "\n");
435
+ #endif
436
+
437
+ if (config.format == kFSEventWatchOutputFormatClassic) {
438
+ classic_output_format(numEvents, paths);
439
+ } else if (config.format == kFSEventWatchOutputFormatNIW) {
440
+ niw_output_format(numEvents, paths, eventFlags, eventIds);
441
+ } else if (config.format == kFSEventWatchOutputFormatTNetstring) {
442
+ tstring_output_format(numEvents, paths, eventFlags, eventIds,
443
+ kTSITStringFormatTNetstring);
444
+ } else if (config.format == kFSEventWatchOutputFormatOTNetstring) {
445
+ tstring_output_format(numEvents, paths, eventFlags, eventIds,
446
+ kTSITStringFormatOTNetstring);
447
+ }
448
+
449
+ fflush(stdout);
450
+ }
451
+
452
+ int main(int argc, const char* argv[])
453
+ {
454
+ /*
455
+ * a subprocess will initially inherit the process group of its parent. the
456
+ * process group may have a control terminal associated with it, which would
457
+ * be the first tty device opened by the group leader. typically the group
458
+ * leader is your shell and the control terminal is your login device. a
459
+ * subset of signals triggered on the control terminal are sent to all members
460
+ * of the process group, in large part to facilitate sane and consistent
461
+ * cleanup (ex: control terminal was closed).
462
+ *
463
+ * so why the overly descriptive lecture style comment?
464
+ * 1. SIGINT and SIGQUIT are among the signals with this behavior
465
+ * 2. a number of applications gank the above for their own use
466
+ * 3. ruby's insanely useful "guard" is one of these applications
467
+ * 4. despite having some level of understanding of POSIX signals and a few
468
+ * of the scenarios that might cause problems, i learned this one only
469
+ * after reading ruby 1.9's process.c
470
+ * 5. if left completely undocumented, even slightly obscure bugfixes
471
+ * may be removed as cruft by a future maintainer
472
+ *
473
+ * hindsight is 20/20 addition: if you're single-threaded and blocking on IO
474
+ * with a subprocess, then handlers for deferrable signals might not get run
475
+ * when you expect them to. In the case of Ruby 1.8, that means making use of
476
+ * IO::select, which will preserve correct signal handling behavior.
477
+ */
478
+ if (setpgid(0,0) < 0) {
479
+ fprintf(stderr, "Unable to set new process group.\n");
480
+ return 1;
481
+ }
482
+
483
+ parse_cli_settings(argc, argv);
484
+
485
+ FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
486
+ FSEventStreamRef stream;
487
+ stream = FSEventStreamCreate(kCFAllocatorDefault,
488
+ (FSEventStreamCallback)&callback,
489
+ &context,
490
+ config.paths,
491
+ config.sinceWhen,
492
+ config.latency,
493
+ config.flags);
494
+
495
+ #ifdef DEBUG
496
+ FSEventStreamShow(stream);
497
+ fprintf(stderr, "\n");
498
+ #endif
499
+
500
+ FSEventStreamScheduleWithRunLoop(stream,
501
+ CFRunLoopGetCurrent(),
502
+ kCFRunLoopDefaultMode);
503
+ FSEventStreamStart(stream);
504
+ CFRunLoopRun();
505
+ FSEventStreamFlushSync(stream);
506
+ FSEventStreamStop(stream);
507
+
508
+ return 0;
509
+ }