235 lines
7.6 KiB
C
235 lines
7.6 KiB
C
|
#include "common.h"
|
||
|
#include "cli.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;
|
||
|
int format;
|
||
|
} config = {
|
||
|
(UInt64) kFSEventStreamEventIdSinceNow,
|
||
|
(double) 0.3,
|
||
|
(CFOptionFlags) kFSEventStreamCreateFlagNone,
|
||
|
NULL,
|
||
|
0
|
||
|
};
|
||
|
|
||
|
// 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 void append_path(const char* path)
|
||
|
{
|
||
|
CFStringRef pathRef = CFStringCreateWithCString(kCFAllocatorDefault,
|
||
|
path,
|
||
|
kCFStringEncodingUTF8);
|
||
|
CFArrayAppendValue(config.paths, pathRef);
|
||
|
CFRelease(pathRef);
|
||
|
}
|
||
|
|
||
|
// 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) | ((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) | ((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) | ((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);
|
||
|
fprintf(stderr, "config.flags %#.8x\n", config.flags);
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
char *buf = calloc(sizeof(FSEVENTSBITS), sizeof(char));
|
||
|
|
||
|
for (size_t i = 0; i < numEvents; i++) {
|
||
|
sprintb(buf, eventFlags[i], FSEVENTSBITS);
|
||
|
printf("%llu\t%#.8x=[%s]\t%s\n", eventIds[i], eventFlags[i], buf, paths[i]);
|
||
|
}
|
||
|
fflush(stdout);
|
||
|
free(buf);
|
||
|
|
||
|
if (fcntl(STDIN_FILENO, F_GETFD) == -1) {
|
||
|
CFRunLoopStop(CFRunLoopGetCurrent());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void stdin_callback(CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, void *info)
|
||
|
{
|
||
|
char buf[1024];
|
||
|
int nread;
|
||
|
|
||
|
do {
|
||
|
nread = read(STDIN_FILENO, buf, sizeof(buf));
|
||
|
if (nread == -1 && errno == EAGAIN) {
|
||
|
CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
|
||
|
return;
|
||
|
} else if (nread == 0) {
|
||
|
exit(1);
|
||
|
return;
|
||
|
}
|
||
|
} while (nread > 0);
|
||
|
}
|
||
|
|
||
|
int main(int argc, const char* argv[])
|
||
|
{
|
||
|
parse_cli_settings(argc, argv);
|
||
|
|
||
|
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
|
||
|
|
||
|
fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
|
||
|
|
||
|
CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, STDIN_FILENO, false, stdin_callback, NULL);
|
||
|
CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
|
||
|
CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
|
||
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
|
||
|
CFRelease(source);
|
||
|
|
||
|
FSEventStreamScheduleWithRunLoop(stream,
|
||
|
CFRunLoopGetCurrent(),
|
||
|
kCFRunLoopDefaultMode);
|
||
|
FSEventStreamStart(stream);
|
||
|
CFRunLoopRun();
|
||
|
FSEventStreamFlushSync(stream);
|
||
|
FSEventStreamStop(stream);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// vim: ts=2 sts=2 et sw=2
|