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.
- data/CHANGELOG.md +17 -0
- data/README.md +50 -30
- data/bin/fsevent_watch_guard_guard +0 -0
- data/lib/guard.rb +44 -26
- data/lib/guard/cli.rb +23 -6
- data/lib/guard/dsl.rb +33 -15
- data/lib/guard/listener.rb +1 -1
- data/lib/guard/listeners/darwin.rb +1 -1
- data/lib/guard/notifier.rb +3 -1
- data/lib/guard/notifiers/notifysend.rb +81 -0
- data/lib/guard/version.rb +1 -1
- data/lib/guard/version.rbc +180 -0
- data/lib/vendor/darwin/README.rdoc +3 -2
- data/lib/vendor/darwin/bin/fsevent_watch +0 -0
- data/lib/vendor/darwin/ext/fsevent_watch/Info.plist +38 -0
- data/lib/vendor/darwin/ext/fsevent_watch/LICENSE +21 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch.xcodeproj/project.pbxproj +254 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/TSICTString.c +394 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/TSICTString.h +74 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/cli.c +160 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/cli.h +45 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/common.h +34 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/compat.c +20 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/compat.h +40 -0
- data/lib/vendor/darwin/ext/fsevent_watch/fsevent_watch/main.c +509 -0
- data/lib/vendor/darwin/ext/fsevent_watch/xcconfig/Common.xcconfig +82 -0
- data/lib/vendor/darwin/ext/fsevent_watch/xcconfig/Debug.xcconfig +19 -0
- data/lib/vendor/darwin/ext/fsevent_watch/xcconfig/Release.xcconfig +23 -0
- data/lib/vendor/darwin/ext/fsevent_watch/xcconfig/fsevent_watch.xcconfig +17 -0
- data/lib/vendor/darwin/ext/rakefile.rb +47 -0
- data/lib/vendor/darwin/ext/rb-fsevent.xcconfig +33 -0
- data/lib/vendor/darwin/lib/rb-fsevent/version.rb +1 -1
- data/lib/vendor/darwin/rb-fsevent.gemspec +3 -2
- data/lib/vendor/darwin/spec/spec_helper.rb +1 -2
- metadata +40 -25
- data/lib/vendor/darwin/ext/extconf.rb +0 -64
- 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
|
+
}
|