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.
- data/README.rdoc +3 -2
- data/ext/fsevent_watch/LICENSE +21 -0
- data/ext/fsevent_watch/fsevent_watch.xcodeproj/project.pbxproj +230 -0
- data/ext/fsevent_watch/fsevent_watch/TSICTString.c +394 -0
- data/ext/fsevent_watch/fsevent_watch/TSICTString.h +74 -0
- data/ext/fsevent_watch/fsevent_watch/cli.c +160 -0
- data/ext/fsevent_watch/fsevent_watch/cli.h +39 -0
- data/ext/fsevent_watch/fsevent_watch/common.h +33 -0
- data/ext/fsevent_watch/fsevent_watch/compat.h +40 -0
- data/ext/fsevent_watch/fsevent_watch/main.c +486 -0
- data/ext/rakefile.rb +47 -0
- data/ext/rb-fsevent.xcconfig +19 -0
- data/lib/rb-fsevent/version.rb +1 -1
- metadata +52 -77
- data/ext/extconf.rb +0 -61
- data/ext/fsevent/fsevent_watch.c +0 -226
@@ -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
|
+
}
|