549 lines
20 KiB
C
549 lines
20 KiB
C
#include "common.h"
|
|
#include "signal_handlers.h"
|
|
#include "cli.h"
|
|
#include "FSEventsFix.h"
|
|
|
|
// TODO: set on fire. cli.{h,c} handle both parsing and defaults, so there's
|
|
// no need to set those here. also, in order to scope metadata by path,
|
|
// each stream will need its own configuration... so this won't work as
|
|
// a global any more. In the end the goal is to make the output format
|
|
// able to declare not just that something happened and what flags were
|
|
// attached, but what path it was watching that caused those events (so
|
|
// that the path itself can be used for routing that information to the
|
|
// relevant callback).
|
|
//
|
|
// Structure for storing metadata parsed from the commandline
|
|
static struct {
|
|
FSEventStreamEventId sinceWhen;
|
|
CFTimeInterval latency;
|
|
FSEventStreamCreateFlags flags;
|
|
CFMutableArrayRef paths;
|
|
enum FSEventWatchOutputFormat format;
|
|
} config = {
|
|
(UInt64) kFSEventStreamEventIdSinceNow,
|
|
(double) 0.3,
|
|
(CFOptionFlags) kFSEventStreamCreateFlagNone,
|
|
NULL,
|
|
kFSEventWatchOutputFormatOTNetstring
|
|
};
|
|
|
|
// Prototypes
|
|
static void append_path(const char* path);
|
|
static inline void parse_cli_settings(int argc, const char* argv[]);
|
|
static void callback(FSEventStreamRef streamRef,
|
|
void* clientCallBackInfo,
|
|
size_t numEvents,
|
|
void* eventPaths,
|
|
const FSEventStreamEventFlags eventFlags[],
|
|
const FSEventStreamEventId eventIds[]);
|
|
static bool needs_fsevents_fix = false;
|
|
|
|
// Resolve a path and append it to the CLI settings structure
|
|
// The FSEvents API will, internally, resolve paths using a similar scheme.
|
|
// Performing this ahead of time makes things less confusing, IMHO.
|
|
static void append_path(const char* path)
|
|
{
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "append_path called for: %s\n", path);
|
|
#endif
|
|
|
|
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "compiled against 10.6+, using CFURLCreateFileReferenceURL\n");
|
|
#endif
|
|
|
|
CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)path, (CFIndex)strlen(path), false);
|
|
CFURLRef placeholder = CFURLCopyAbsoluteURL(url);
|
|
CFRelease(url);
|
|
|
|
CFMutableArrayRef imaginary = NULL;
|
|
|
|
// if we don't have an existing url, spin until we get to a parent that
|
|
// does exist, saving any imaginary components for appending back later
|
|
while(!CFURLResourceIsReachable(placeholder, NULL)) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "path does not exist\n");
|
|
#endif
|
|
|
|
CFStringRef child;
|
|
|
|
if (imaginary == NULL) {
|
|
imaginary = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
|
}
|
|
|
|
child = CFURLCopyLastPathComponent(placeholder);
|
|
CFArrayInsertValueAtIndex(imaginary, 0, child);
|
|
CFRelease(child);
|
|
|
|
url = CFURLCreateCopyDeletingLastPathComponent(NULL, placeholder);
|
|
CFRelease(placeholder);
|
|
placeholder = url;
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "parent: ");
|
|
CFShow(placeholder);
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "path exists\n");
|
|
#endif
|
|
|
|
// realpath() doesn't always return the correct case for a path, so this
|
|
// is a funky workaround that converts a path into a (volId/inodeId) pair
|
|
// and asks what the path should be for that. since it looks at the actual
|
|
// inode instead of returning the same case passed in like realpath()
|
|
// appears to do for HFS+, it should always be correct.
|
|
url = CFURLCreateFileReferenceURL(NULL, placeholder, NULL);
|
|
CFRelease(placeholder);
|
|
placeholder = CFURLCreateFilePathURL(NULL, url, NULL);
|
|
CFRelease(url);
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "path resolved to: ");
|
|
CFShow(placeholder);
|
|
#endif
|
|
|
|
// if we stripped off any imaginary path components, append them back on
|
|
if (imaginary != NULL) {
|
|
CFIndex count = CFArrayGetCount(imaginary);
|
|
for (CFIndex i = 0; i<count; i++) {
|
|
CFStringRef component = CFArrayGetValueAtIndex(imaginary, i);
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "appending component: ");
|
|
CFShow(component);
|
|
#endif
|
|
url = CFURLCreateCopyAppendingPathComponent(NULL, placeholder, component, false);
|
|
CFRelease(placeholder);
|
|
placeholder = url;
|
|
}
|
|
CFRelease(imaginary);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "result: ");
|
|
CFShow(placeholder);
|
|
#endif
|
|
|
|
CFStringRef cfPath = CFURLCopyFileSystemPath(placeholder, kCFURLPOSIXPathStyle);
|
|
CFRelease(placeholder);
|
|
|
|
char cPath[PATH_MAX];
|
|
if (CFStringGetCString(cfPath, cPath, PATH_MAX, kCFStringEncodingUTF8)) {
|
|
FSEventsFixRepairStatus status = FSEventsFixRepairIfNeeded(cPath);
|
|
if (status == FSEventsFixRepairStatusFailed) {
|
|
needs_fsevents_fix = true;
|
|
}
|
|
}
|
|
|
|
CFArrayAppendValue(config.paths, cfPath);
|
|
CFRelease(cfPath);
|
|
|
|
#else
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "compiled against 10.5, using realpath()\n");
|
|
#endif
|
|
|
|
char fullPath[PATH_MAX + 1];
|
|
|
|
if (realpath(path, fullPath) == NULL) {
|
|
#ifdef DEBUG
|
|
fprintf(stderr, " realpath not directly resolvable from path\n");
|
|
#endif
|
|
|
|
if (path[0] != '/') {
|
|
#ifdef DEBUG
|
|
fprintf(stderr, " passed path is not absolute\n");
|
|
#endif
|
|
size_t len;
|
|
getcwd(fullPath, sizeof(fullPath));
|
|
#ifdef DEBUG
|
|
fprintf(stderr, " result of getcwd: %s\n", fullPath);
|
|
#endif
|
|
len = strlen(fullPath);
|
|
fullPath[len] = '/';
|
|
strlcpy(&fullPath[len + 1], path, sizeof(fullPath) - (len + 1));
|
|
} else {
|
|
#ifdef DEBUG
|
|
fprintf(stderr, " assuming path does not YET exist\n");
|
|
#endif
|
|
strlcpy(fullPath, path, sizeof(fullPath));
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, " resolved path to: %s\n", fullPath);
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
|
|
CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault,
|
|
fullPath,
|
|
kCFStringEncodingUTF8);
|
|
CFArrayAppendValue(config.paths, pathRef);
|
|
CFRelease(pathRef);
|
|
|
|
#endif
|
|
}
|
|
|
|
// Parse commandline settings
|
|
static inline void parse_cli_settings(int argc, const char* argv[])
|
|
{
|
|
// runtime os version detection
|
|
SInt32 osMajorVersion, osMinorVersion;
|
|
if (!(Gestalt(gestaltSystemVersionMajor, &osMajorVersion) == noErr)) {
|
|
osMajorVersion = 0;
|
|
}
|
|
if (!(Gestalt(gestaltSystemVersionMinor, &osMinorVersion) == noErr)) {
|
|
osMinorVersion = 0;
|
|
}
|
|
|
|
if ((osMajorVersion == 10) & (osMinorVersion < 5)) {
|
|
fprintf(stderr, "The FSEvents API is unavailable on this version of macos!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
struct cli_info args_info;
|
|
cli_parser_init(&args_info);
|
|
|
|
if (cli_parser(argc, argv, &args_info) != 0) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
config.paths = CFArrayCreateMutable(NULL,
|
|
(CFIndex)0,
|
|
&kCFTypeArrayCallBacks);
|
|
|
|
config.sinceWhen = args_info.since_when_arg;
|
|
config.latency = args_info.latency_arg;
|
|
config.format = args_info.format_arg;
|
|
|
|
if (args_info.no_defer_flag) {
|
|
config.flags |= kFSEventStreamCreateFlagNoDefer;
|
|
}
|
|
if (args_info.watch_root_flag) {
|
|
config.flags |= kFSEventStreamCreateFlagWatchRoot;
|
|
}
|
|
|
|
if (args_info.ignore_self_flag) {
|
|
if ((osMajorVersion == 10) & (osMinorVersion >= 6)) {
|
|
config.flags |= kFSEventStreamCreateFlagIgnoreSelf;
|
|
} else {
|
|
fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (args_info.file_events_flag) {
|
|
if ((osMajorVersion == 10) & (osMinorVersion >= 7)) {
|
|
config.flags |= kFSEventStreamCreateFlagFileEvents;
|
|
} else {
|
|
fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (args_info.mark_self_flag) {
|
|
if ((osMajorVersion == 10) & (osMinorVersion >= 9)) {
|
|
config.flags |= kFSEventStreamCreateFlagMarkSelf;
|
|
} else {
|
|
fprintf(stderr, "MacOSX 10.9 or later required for --mark-self\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (args_info.inputs_num == 0) {
|
|
append_path(".");
|
|
} else {
|
|
for (unsigned int i=0; i < args_info.inputs_num; ++i) {
|
|
append_path(args_info.inputs[i]);
|
|
}
|
|
}
|
|
|
|
cli_parser_free(&args_info);
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen);
|
|
fprintf(stderr, "config.latency %f\n", config.latency);
|
|
|
|
// STFU clang
|
|
#if defined(__LP64__)
|
|
fprintf(stderr, "config.flags %#.8x\n", config.flags);
|
|
#else
|
|
fprintf(stderr, "config.flags %#.8lx\n", config.flags);
|
|
#endif
|
|
|
|
FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes,
|
|
" Using CF instead of C types");
|
|
FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer,
|
|
" NoDefer latency modifier enabled");
|
|
FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot,
|
|
" WatchRoot notifications enabled");
|
|
FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf,
|
|
" IgnoreSelf enabled");
|
|
FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents,
|
|
" FileEvents enabled");
|
|
|
|
fprintf(stderr, "config.paths\n");
|
|
|
|
long numpaths = CFArrayGetCount(config.paths);
|
|
|
|
for (long i = 0; i < numpaths; i++) {
|
|
char path[PATH_MAX];
|
|
CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i),
|
|
path,
|
|
PATH_MAX,
|
|
kCFStringEncodingUTF8);
|
|
fprintf(stderr, " %s\n", path);
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
}
|
|
|
|
// original output format for rb-fsevent
|
|
static void classic_output_format(size_t numEvents,
|
|
char** paths)
|
|
{
|
|
for (size_t i = 0; i < numEvents; i++) {
|
|
fprintf(stdout, "%s:", paths[i]);
|
|
}
|
|
fprintf(stdout, "\n");
|
|
}
|
|
|
|
// output format used in the Yoshimasa Niwa branch of rb-fsevent
|
|
static void niw_output_format(size_t numEvents,
|
|
char** paths,
|
|
const FSEventStreamEventFlags eventFlags[],
|
|
const FSEventStreamEventId eventIds[])
|
|
{
|
|
for (size_t i = 0; i < numEvents; i++) {
|
|
fprintf(stdout, "%lu:%llu:%s\n",
|
|
(unsigned long)eventFlags[i],
|
|
(unsigned long long)eventIds[i],
|
|
paths[i]);
|
|
}
|
|
fprintf(stdout, "\n");
|
|
}
|
|
|
|
static void tstring_output_format(size_t numEvents,
|
|
char** paths,
|
|
const FSEventStreamEventFlags eventFlags[],
|
|
const FSEventStreamEventId eventIds[],
|
|
TSITStringFormat format)
|
|
{
|
|
CFMutableArrayRef events = CFArrayCreateMutable(kCFAllocatorDefault,
|
|
0, &kCFTypeArrayCallBacks);
|
|
|
|
for (size_t i = 0; i < numEvents; i++) {
|
|
CFMutableDictionaryRef event = CFDictionaryCreateMutable(kCFAllocatorDefault,
|
|
0,
|
|
&kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks);
|
|
|
|
CFStringRef path = CFStringCreateWithBytes(kCFAllocatorDefault,
|
|
(const UInt8*)paths[i],
|
|
(CFIndex)strlen(paths[i]),
|
|
kCFStringEncodingUTF8,
|
|
false);
|
|
CFDictionarySetValue(event, CFSTR("path"), path);
|
|
|
|
CFNumberRef ident = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &eventIds[i]);
|
|
CFDictionarySetValue(event, CFSTR("id"), ident);
|
|
|
|
CFNumberRef cflags = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &eventFlags[i]);
|
|
CFDictionarySetValue(event, CFSTR("cflags"), cflags);
|
|
|
|
CFMutableArrayRef flags = CFArrayCreateMutable(kCFAllocatorDefault,
|
|
0, &kCFTypeArrayCallBacks);
|
|
|
|
#define FLAG_ADD_NAME(flagsnum, flagnum, flagname, flagarray) \
|
|
do { \
|
|
if (FLAG_CHECK(flagsnum, flagnum)) { \
|
|
CFArrayAppendValue(flagarray, CFSTR(flagname)); } } \
|
|
while(0)
|
|
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs, "MustScanSubDirs", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagUserDropped, "UserDropped", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagKernelDropped, "KernelDropped", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped, "EventIdsWrapped", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagHistoryDone, "HistoryDone", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagRootChanged, "RootChanged", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagMount, "Mount", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagUnmount, "Unmount", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemCreated, "ItemCreated", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemRemoved, "ItemRemoved", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod, "ItemInodeMetaMod", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemRenamed, "ItemRenamed", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemModified, "ItemModified", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod, "ItemFinderInfoMod", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner, "ItemChangeOwner", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemXattrMod, "ItemXattrMod", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsFile, "ItemIsFile", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsDir, "ItemIsDir", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink, "ItemIsSymlink", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagOwnEvent, "OwnEvent", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsHardlink, "ItemIsHardLink", flags);
|
|
FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsLastHardlink, "ItemIsLastHardLink", flags);
|
|
|
|
CFDictionarySetValue(event, CFSTR("flags"), flags);
|
|
|
|
|
|
CFArrayAppendValue(events, event);
|
|
|
|
CFRelease(event);
|
|
CFRelease(path);
|
|
CFRelease(ident);
|
|
CFRelease(cflags);
|
|
CFRelease(flags);
|
|
}
|
|
|
|
CFMutableDictionaryRef meta = CFDictionaryCreateMutable(kCFAllocatorDefault,
|
|
0,
|
|
&kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks);
|
|
CFDictionarySetValue(meta, CFSTR("events"), events);
|
|
|
|
CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &numEvents);
|
|
CFDictionarySetValue(meta, CFSTR("numEvents"), num);
|
|
|
|
CFDataRef data = TSICTStringCreateRenderedDataFromObjectWithFormat(meta, format);
|
|
fprintf(stdout, "%s", CFDataGetBytePtr(data));
|
|
|
|
CFRelease(events);
|
|
CFRelease(num);
|
|
CFRelease(meta);
|
|
CFRelease(data);
|
|
}
|
|
|
|
static void callback(__attribute__((unused)) FSEventStreamRef streamRef,
|
|
__attribute__((unused)) void* clientCallBackInfo,
|
|
size_t numEvents,
|
|
void* eventPaths,
|
|
const FSEventStreamEventFlags eventFlags[],
|
|
const FSEventStreamEventId eventIds[])
|
|
{
|
|
char** paths = eventPaths;
|
|
|
|
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "FSEventStreamCallback fired!\n");
|
|
fprintf(stderr, " numEvents: %lu\n", numEvents);
|
|
|
|
for (size_t i = 0; i < numEvents; i++) {
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, " event ID: %llu\n", eventIds[i]);
|
|
|
|
// STFU clang
|
|
#if defined(__LP64__)
|
|
fprintf(stderr, " event flags: %#.8x\n", eventFlags[i]);
|
|
#else
|
|
fprintf(stderr, " event flags: %#.8lx\n", eventFlags[i]);
|
|
#endif
|
|
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs,
|
|
" Recursive scanning of directory required");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUserDropped,
|
|
" Buffering problem: events dropped user-side");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagKernelDropped,
|
|
" Buffering problem: events dropped kernel-side");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped,
|
|
" Event IDs have wrapped");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagHistoryDone,
|
|
" All historical events have been processed");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagRootChanged,
|
|
" Root path has changed");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMount,
|
|
" A new volume was mounted at this path");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUnmount,
|
|
" A volume was unmounted from this path");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemCreated,
|
|
" Item created");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRemoved,
|
|
" Item removed");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod,
|
|
" Item metadata modified");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRenamed,
|
|
" Item renamed");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemModified,
|
|
" Item modified");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod,
|
|
" Item Finder Info modified");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner,
|
|
" Item changed ownership");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemXattrMod,
|
|
" Item extended attributes modified");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsFile,
|
|
" Item is a file");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsDir,
|
|
" Item is a directory");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink,
|
|
" Item is a symbolic link");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsHardlink,
|
|
" Item is a hard link");
|
|
FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsLastHardlink,
|
|
" Item is the last hard link");
|
|
fprintf(stderr, " event path: %s\n", paths[i]);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
|
|
if (config.format == kFSEventWatchOutputFormatClassic) {
|
|
classic_output_format(numEvents, paths);
|
|
} else if (config.format == kFSEventWatchOutputFormatNIW) {
|
|
niw_output_format(numEvents, paths, eventFlags, eventIds);
|
|
} else if (config.format == kFSEventWatchOutputFormatTNetstring) {
|
|
tstring_output_format(numEvents, paths, eventFlags, eventIds,
|
|
kTSITStringFormatTNetstring);
|
|
} else if (config.format == kFSEventWatchOutputFormatOTNetstring) {
|
|
tstring_output_format(numEvents, paths, eventFlags, eventIds,
|
|
kTSITStringFormatOTNetstring);
|
|
}
|
|
|
|
fflush(stdout);
|
|
}
|
|
|
|
int main(int argc, const char* argv[])
|
|
{
|
|
install_signal_handlers();
|
|
parse_cli_settings(argc, argv);
|
|
|
|
if (needs_fsevents_fix) {
|
|
FSEventsFixEnable();
|
|
}
|
|
|
|
FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
|
|
FSEventStreamRef stream;
|
|
stream = FSEventStreamCreate(kCFAllocatorDefault,
|
|
(FSEventStreamCallback)&callback,
|
|
&context,
|
|
config.paths,
|
|
config.sinceWhen,
|
|
config.latency,
|
|
config.flags);
|
|
|
|
#ifdef DEBUG
|
|
FSEventStreamShow(stream);
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
|
|
if (needs_fsevents_fix) {
|
|
FSEventsFixDisable();
|
|
}
|
|
|
|
FSEventStreamScheduleWithRunLoop(stream,
|
|
CFRunLoopGetCurrent(),
|
|
kCFRunLoopDefaultMode);
|
|
FSEventStreamStart(stream);
|
|
CFRunLoopRun();
|
|
FSEventStreamFlushSync(stream);
|
|
FSEventStreamStop(stream);
|
|
|
|
return 0;
|
|
}
|