Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -0,0 +1,626 @@
/*
* FSEventsFix
*
* Resolves a long-standing bug in realpath() that prevents FSEvents API from
* monitoring certain folders on a wide range of OS X released (10.6-10.10 at least).
*
* The underlying issue is that for some folders, realpath() call starts returning
* a path with incorrect casing (e.g. "/users/smt" instead of "/Users/smt").
* FSEvents is case-sensitive and calls realpath() on the paths you pass in, so
* an incorrect value returned by realpath() prevents FSEvents from seeing any
* change events.
*
* See the discussion at https://github.com/thibaudgg/rb-fsevent/issues/10 about
* the history of this bug and how this library came to exist.
*
* This library uses Facebook's fishhook to replace a custom implementation of
* realpath in place of the system realpath; FSEvents will then invoke our custom
* implementation (which does not screw up the names) and will thus work correctly.
*
* Our implementation of realpath is based on the open-source implementation from
* OS X 10.10, with a single change applied (enclosed in "BEGIN WORKAROUND FOR
* OS X BUG" ... "END WORKAROUND FOR OS X BUG").
*
* Include FSEventsFix.{h,c} into your project and call FSEventsFixInstall().
*
* It is recommended that you install FSEventsFix on demand, using FSEventsFixIsBroken
* to check if the folder you're about to pass to FSEventStreamCreate needs the fix.
* Note that the fix must be applied before calling FSEventStreamCreate.
*
* FSEventsFixIsBroken requires a path that uses the correct case for all folder names,
* i.e. a path provided by the system APIs or constructed from folder names provided
* by the directory enumeration APIs.
*
* Copyright (c) 2015 Andrey Tarantsov <andrey@tarantsov.com>
* Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Based on a realpath implementation from Apple libc 498.1.7, taken from
* http://www.opensource.apple.com/source/Libc/Libc-498.1.7/stdlib/FreeBSD/realpath.c
* and provided under the following license:
*
* Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The names of the authors may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "FSEventsFix.h"
#include <dispatch/dispatch.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <dlfcn.h>
const char *const FSEventsFixVersionString = "0.11.0";
#pragma mark - Forward declarations
static char *(*orig_realpath)(const char *restrict file_name, char resolved_name[PATH_MAX]);
static char *CFURL_realpath(const char *restrict file_name, char resolved_name[PATH_MAX]);
static char *FSEventsFix_realpath_wrapper(const char *restrict src, char *restrict dst);
static void _FSEventsFixHookInstall();
static void _FSEventsFixHookUninstall();
#pragma mark - Internal state
static dispatch_queue_t g_queue = NULL;
static int64_t g_enable_refcount = 0;
static bool g_in_self_test = false;
static bool g_hook_operational = false;
static void(^g_logging_block)(FSEventsFixMessageType type, const char *message);
static FSEventsFixDebugOptions g_debug_opt = 0;
typedef struct {
char *name;
void *replacement;
void *original;
uint hooked_symbols;
} rebinding_t;
static rebinding_t g_rebindings[] = {
{ "_realpath$DARWIN_EXTSN", (void *) &FSEventsFix_realpath_wrapper, (void *) &realpath, 0 }
};
static const uint g_rebindings_nel = sizeof(g_rebindings) / sizeof(g_rebindings[0]);
#pragma mark - Logging
static void _FSEventsFixLog(FSEventsFixMessageType type, const char *__restrict fmt, ...) __attribute__((__format__ (__printf__, 2, 3)));
static void _FSEventsFixLog(FSEventsFixMessageType type, const char *__restrict fmt, ...) {
if (g_logging_block) {
char *message = NULL;
va_list va;
va_start(va, fmt);
vasprintf(&message, fmt, va);
va_end(va);
if (message) {
if (!!(g_debug_opt & FSEventsFixDebugOptionLogToStderr)) {
fprintf(stderr, "FSEventsFix: %s\n", message);
}
if (g_logging_block) {
g_logging_block(type, message);
}
free(message);
}
}
}
#pragma mark - API
void _FSEventsFixInitialize() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
g_queue = dispatch_queue_create("FSEventsFix", DISPATCH_QUEUE_SERIAL);
});
}
void FSEventsFixConfigure(FSEventsFixDebugOptions debugOptions, void(^loggingBlock)(FSEventsFixMessageType severity, const char *message)) {
_FSEventsFixInitialize();
loggingBlock = Block_copy(loggingBlock);
dispatch_sync(g_queue, ^{
g_debug_opt = debugOptions;
g_logging_block = loggingBlock;
});
}
// Must be called from the private serial queue.
void _FSEventsFixSelfTest() {
g_in_self_test = true;
g_hook_operational = false;
static char result[1024];
realpath("/Etc/__!FSEventsFixSelfTest!__", result);
g_in_self_test = false;
}
void FSEventsFixEnable() {
_FSEventsFixInitialize();
dispatch_sync(g_queue, ^{
if (++g_enable_refcount == 1) {
orig_realpath = dlsym(RTLD_DEFAULT, "realpath");
_FSEventsFixHookInstall();
_FSEventsFixSelfTest();
if (g_hook_operational) {
_FSEventsFixLog(FSEventsFixMessageTypeStatusChange, "Enabled");
} else {
_FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Failed to enable (hook not called)");
}
}
});
}
void FSEventsFixDisable() {
_FSEventsFixInitialize();
dispatch_sync(g_queue, ^{
if (g_enable_refcount == 0) {
abort();
}
if (--g_enable_refcount == 0) {
_FSEventsFixHookUninstall();
_FSEventsFixSelfTest();
if (!g_hook_operational) {
_FSEventsFixLog(FSEventsFixMessageTypeStatusChange, "Disabled");
} else {
_FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Failed to disable (hook still called)");
}
}
});
}
bool FSEventsFixIsOperational() {
_FSEventsFixInitialize();
__block bool result = false;
dispatch_sync(g_queue, ^{
result = g_hook_operational;
});
return result;
}
bool _FSEventsFixIsBroken_noresolve(const char *resolved) {
if (!!(g_debug_opt & FSEventsFixDebugOptionSimulateBroken)) {
if (strstr(resolved, FSEventsFixSimulatedBrokenFolderMarker)) {
return true;
}
}
char *reresolved = realpath(resolved, NULL);
if (reresolved) {
bool broken = (0 != strcmp(resolved, reresolved));
free(reresolved);
return broken;
} else {
return true;
}
}
bool FSEventsFixIsBroken(const char *path) {
char *resolved = CFURL_realpath(path, NULL);
if (!resolved) {
return true;
}
bool broken = _FSEventsFixIsBroken_noresolve(resolved);
free(resolved);
return broken;
}
char *FSEventsFixCopyRootBrokenFolderPath(const char *inpath) {
if (!FSEventsFixIsBroken(inpath)) {
return NULL;
}
// get a mutable copy of an absolute path
char *path = CFURL_realpath(inpath, NULL);
if (!path) {
return NULL;
}
for (;;) {
char *sep = strrchr(path, '/');
if ((sep == NULL) || (sep == path)) {
break;
}
*sep = 0;
if (!_FSEventsFixIsBroken_noresolve(path)) {
*sep = '/';
break;
}
}
return path;
}
static void _FSEventsFixAttemptRepair(const char *folder) {
int rv = rename(folder, folder);
if (!!(g_debug_opt & FSEventsFixDebugOptionSimulateRepair)) {
const char *pos = strstr(folder, FSEventsFixSimulatedBrokenFolderMarker);
if (pos) {
char *fixed = strdup(folder);
fixed[pos - folder] = 0;
strcat(fixed, pos + strlen(FSEventsFixSimulatedBrokenFolderMarker));
rv = rename(folder, fixed);
free(fixed);
}
}
if (rv != 0) {
if (errno == EPERM) {
_FSEventsFixLog(FSEventsFixMessageTypeResult, "Permission error when trying to repair '%s'", folder);
} else {
_FSEventsFixLog(FSEventsFixMessageTypeExpectedFailure, "Unknown error when trying to repair '%s': errno = %d", folder, errno);
}
}
}
FSEventsFixRepairStatus FSEventsFixRepairIfNeeded(const char *inpath) {
char *root = FSEventsFixCopyRootBrokenFolderPath(inpath);
if (root == NULL) {
return FSEventsFixRepairStatusNotBroken;
}
for (;;) {
_FSEventsFixAttemptRepair(root);
char *newRoot = FSEventsFixCopyRootBrokenFolderPath(inpath);
if (newRoot == NULL) {
_FSEventsFixLog(FSEventsFixMessageTypeResult, "Repaired '%s' in '%s'", root, inpath);
free(root);
return FSEventsFixRepairStatusRepaired;
}
if (0 == strcmp(root, newRoot)) {
_FSEventsFixLog(FSEventsFixMessageTypeResult, "Failed to repair '%s' in '%s'", root, inpath);
free(root);
free(newRoot);
return FSEventsFixRepairStatusFailed;
}
_FSEventsFixLog(FSEventsFixMessageTypeResult, "Partial success, repaired '%s' in '%s'", root, inpath);
free(root);
root = newRoot;
}
}
#pragma mark - FSEventsFix realpath wrapper
static char *FSEventsFix_realpath_wrapper(const char * __restrict src, char * __restrict dst) {
if (g_in_self_test) {
if (strstr(src, "__!FSEventsFixSelfTest!__")) {
g_hook_operational = true;
}
}
// CFURL_realpath doesn't support putting where resolution failed into the
// dst buffer, so we call the original realpath here first and if it gets a
// result, replace that with the output of CFURL_realpath. that way all the
// features of the original realpath are available.
char *rv = NULL;
char *orv = orig_realpath(src, dst);
if (orv != NULL) { rv = CFURL_realpath(src, dst); }
if (!!(g_debug_opt & FSEventsFixDebugOptionLogCalls)) {
char *result = rv ?: dst;
_FSEventsFixLog(FSEventsFixMessageTypeCall, "realpath(%s) => %s\n", src, result);
}
if (!!(g_debug_opt & FSEventsFixDebugOptionUppercaseReturn)) {
char *result = rv ?: dst;
if (result) {
for (char *pch = result; *pch; ++pch) {
*pch = (char)toupper(*pch);
}
}
}
return rv;
}
#pragma mark - realpath
// naive implementation of realpath on top of CFURL
// NOTE: doesn't quite support the full range of errno results one would
// expect here, in part because some of these functions just return a boolean,
// and in part because i'm not dealing with messy CFErrorRef objects and
// attempting to translate those to sane errno values.
// NOTE: the OSX realpath will return _where_ resolution failed in resolved_name
// if passed in and return NULL. we can't properly support that extension here
// since the resolution happens entirely behind the scenes to us in CFURL.
static char* CFURL_realpath(const char *file_name, char resolved_name[PATH_MAX])
{
char* resolved;
CFURLRef url1;
CFURLRef url2;
CFStringRef path;
if (file_name == NULL) {
errno = EINVAL;
return (NULL);
}
#if __DARWIN_UNIX03
if (*file_name == 0) {
errno = ENOENT;
return (NULL);
}
#endif
// create a buffer to store our result if we weren't passed one
if (!resolved_name) {
if ((resolved = malloc(PATH_MAX)) == NULL) return (NULL);
} else {
resolved = resolved_name;
}
url1 = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)file_name, (CFIndex)strlen(file_name), false);
if (url1 == NULL) { goto error_return; }
url2 = CFURLCopyAbsoluteURL(url1);
CFRelease(url1);
if (url2 == NULL) { goto error_return; }
url1 = CFURLCreateFileReferenceURL(NULL, url2, NULL);
CFRelease(url2);
if (url1 == NULL) { goto error_return; }
// if there are multiple hard links to the original path, this may end up
// being _completely_ different from what was intended
url2 = CFURLCreateFilePathURL(NULL, url1, NULL);
CFRelease(url1);
if (url2 == NULL) { goto error_return; }
path = CFURLCopyFileSystemPath(url2, kCFURLPOSIXPathStyle);
CFRelease(url2);
if (path == NULL) { goto error_return; }
bool success = CFStringGetCString(path, resolved, PATH_MAX, kCFStringEncodingUTF8);
CFRelease(path);
if (!success) { goto error_return; }
return resolved;
error_return:
if (!resolved_name) {
// we weren't passed in an output buffer and created our own. free it
int e = errno;
free(resolved);
errno = e;
}
return (NULL);
}
#pragma mark - fishhook
// Copyright (c) 2013, Facebook, Inc.
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name Facebook nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific
// prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#import <stdlib.h>
#import <string.h>
#import <sys/types.h>
#import <mach-o/dyld.h>
#import <mach-o/loader.h>
#import <mach-o/nlist.h>
#ifdef __LP64__
typedef struct mach_header_64 mach_header_t;
typedef struct segment_command_64 segment_command_t;
typedef struct section_64 section_t;
typedef struct nlist_64 nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
#else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct section section_t;
typedef struct nlist nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif
static volatile bool g_hook_installed = false;
static void _FSEventsFixHookUpdateSection(section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab)
{
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
for (uint i = 0; i < section->size / sizeof(void *); i++) {
uint32_t symtab_index = indirect_symbol_indices[i];
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
continue;
}
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
char *symbol_name = strtab + strtab_offset;
for (rebinding_t *cur = g_rebindings, *end = g_rebindings + g_rebindings_nel; cur < end; ++cur) {
if (strcmp(symbol_name, cur->name) == 0) {
if (g_hook_installed) {
if (indirect_symbol_bindings[i] != cur->replacement) {
indirect_symbol_bindings[i] = cur->replacement;
++cur->hooked_symbols;
}
} else if (cur->original != NULL) {
if (indirect_symbol_bindings[i] == cur->replacement) {
indirect_symbol_bindings[i] = cur->original;
if (cur->hooked_symbols > 0) {
--cur->hooked_symbols;
}
}
}
goto symbol_loop;
}
}
symbol_loop:;
}
}
static void _FSEventsFixHookUpdateImage(const struct mach_header *header, intptr_t slide) {
Dl_info info;
if (dladdr(header, &info) == 0) {
return;
}
segment_command_t *cur_seg_cmd;
segment_command_t *linkedit_segment = NULL;
struct symtab_command* symtab_cmd = NULL;
struct dysymtab_command* dysymtab_cmd = NULL;
uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
linkedit_segment = cur_seg_cmd;
}
} else if (cur_seg_cmd->cmd == LC_SYMTAB) {
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
}
}
if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
!dysymtab_cmd->nindirectsyms) {
return;
}
// Find base symbol/string table addresses
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0) {
continue;
}
for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
section_t *sect =
(section_t *)(cur + sizeof(segment_command_t)) + j;
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
_FSEventsFixHookUpdateSection(sect, slide, symtab, strtab, indirect_symtab);
}
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
_FSEventsFixHookUpdateSection(sect, slide, symtab, strtab, indirect_symtab);
}
}
}
}
}
static void _FSEventsFixHookSaveOriginals() {
for (rebinding_t *cur = g_rebindings, *end = g_rebindings + g_rebindings_nel; cur < end; ++cur) {
void *original = cur->original = dlsym(RTLD_DEFAULT, cur->name+1);
if (!original) {
const char *error = dlerror();
_FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Cannot find symbol %s, dlsym says: %s\n", cur->name, error);
}
}
}
static void _FSEventsFixHookUpdate() {
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
_FSEventsFixHookUpdateImage(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
}
}
static void _FSEventsFixHookInstall() {
static bool first_rebinding_done = false;
if (!g_hook_installed) {
g_hook_installed = true;
if (!first_rebinding_done) {
first_rebinding_done = true;
_FSEventsFixHookSaveOriginals();
_dyld_register_func_for_add_image(_FSEventsFixHookUpdateImage);
} else {
_FSEventsFixHookUpdate();
}
}
}
static void _FSEventsFixHookUninstall() {
if (g_hook_installed) {
g_hook_installed = false;
_FSEventsFixHookUpdate();
}
}
@@ -0,0 +1,105 @@
/*
* FSEventsFix
*
* Works around a long-standing bug in realpath() that prevents FSEvents API from
* monitoring certain folders on a wide range of OS X releases (10.6-10.10 at least).
*
* The underlying issue is that for some folders, realpath() call starts returning
* a path with incorrect casing (e.g. "/users/smt" instead of "/Users/smt").
* FSEvents is case-sensitive and calls realpath() on the paths you pass in, so
* an incorrect value returned by realpath() prevents FSEvents from seeing any
* change events.
*
* See the discussion at https://github.com/thibaudgg/rb-fsevent/issues/10 about
* the history of this bug and how this library came to exist.
*
* This library uses Facebook's fishhook to replace a custom implementation of
* realpath in place of the system realpath; FSEvents will then invoke our custom
* implementation (which does not screw up the names) and will thus work correctly.
*
* Our implementation of realpath is based on the open-source implementation from
* OS X 10.10, with a single change applied (enclosed in "BEGIN WORKAROUND FOR
* OS X BUG" ... "END WORKAROUND FOR OS X BUG").
*
* Include FSEventsFix.{h,c} into your project and call FSEventsFixInstall().
*
* It is recommended that you install FSEventsFix on demand, using FSEventsFixIsBroken
* to check if the folder you're about to pass to FSEventStreamCreate needs the fix.
* Note that the fix must be applied before calling FSEventStreamCreate.
*
* FSEventsFixIsBroken requires a path that uses the correct case for all folder names,
* i.e. a path provided by the system APIs or constructed from folder names provided
* by the directory enumeration APIs.
*
* See .c file for license & copyrights, but basically this is available under a mix
* of MIT and BSD licenses.
*/
#ifndef __FSEventsFix__
#define __FSEventsFix__
#include <CoreFoundation/CoreFoundation.h>
/// A library version string (e.g. 1.2.3) for displaying and logging purposes
extern const char *const FSEventsFixVersionString;
/// See FSEventsFixDebugOptionSimulateBroken
#define FSEventsFixSimulatedBrokenFolderMarker "__!FSEventsBroken!__"
typedef CF_OPTIONS(unsigned, FSEventsFixDebugOptions) {
/// Always return an uppercase string from realpath
FSEventsFixDebugOptionUppercaseReturn = 0x01,
/// Log all calls to realpath using the logger configured via FSEventsFixConfigure
FSEventsFixDebugOptionLogCalls = 0x02,
/// In addition to the logging block (if any), log everything to stderr
FSEventsFixDebugOptionLogToStderr = 0x08,
/// Report paths containing FSEventsFixSimulatedBrokenFolderMarker as broken
FSEventsFixDebugOptionSimulateBroken = 0x10,
/// Repair paths containing FSEventsFixSimulatedBrokenFolderMarker by renaming them
FSEventsFixDebugOptionSimulateRepair = 0x20,
};
typedef CF_ENUM(int, FSEventsFixMessageType) {
/// Call logging requested via FSEventsFixDebugOptionLogCalls
FSEventsFixMessageTypeCall,
/// Results of actions like repair, and other pretty verbose, but notable, stuff.
FSEventsFixMessageTypeResult,
/// Enabled/disabled status change
FSEventsFixMessageTypeStatusChange,
/// Expected failure (treat as a warning)
FSEventsFixMessageTypeExpectedFailure,
/// Severe failure that most likely means that the library won't work
FSEventsFixMessageTypeFatalError
};
typedef CF_ENUM(int, FSEventsFixRepairStatus) {
FSEventsFixRepairStatusNotBroken,
FSEventsFixRepairStatusRepaired,
FSEventsFixRepairStatusFailed,
};
/// Note that the logging block can be called on any dispatch queue.
void FSEventsFixConfigure(FSEventsFixDebugOptions debugOptions, void(^loggingBlock)(FSEventsFixMessageType type, const char *message));
void FSEventsFixEnable();
void FSEventsFixDisable();
bool FSEventsFixIsOperational();
bool FSEventsFixIsBroken(const char *path);
/// If the path is broken, returns a string identifying the root broken folder,
/// otherwise, returns NULL. You need to free() the returned string.
char *FSEventsFixCopyRootBrokenFolderPath(const char *path);
FSEventsFixRepairStatus FSEventsFixRepairIfNeeded(const char *path);
#endif
@@ -0,0 +1,373 @@
//
// TSICTString.c
// TSITString
//
// Created by Travis Tilley on 9/27/11.
//
#include "TSICTString.h"
const char* const TNetstringTypes = ",#^!~}]Z";
const char* const OTNetstringTypes = ",#^!~{[Z";
const UInt8 TNetstringSeparator = ':';
TSITStringFormat TSITStringDefaultFormat = kTSITStringFormatTNetstring;
static const CFRange BeginningRange = {0,0};
static CFTypeID kCFDataTypeID = -1UL;
static CFTypeID kCFStringTypeID = -1UL;
static CFTypeID kCFNumberTypeID = -1UL;
static CFTypeID kCFBooleanTypeID = -1UL;
static CFTypeID kCFNullTypeID = -1UL;
static CFTypeID kCFArrayTypeID = -1UL;
static CFTypeID kCFDictionaryTypeID = -1UL;
__attribute__((constructor)) void Init_TSICTString(void)
{
kCFDataTypeID = CFDataGetTypeID();
kCFStringTypeID = CFStringGetTypeID();
kCFNumberTypeID = CFNumberGetTypeID();
kCFBooleanTypeID = CFBooleanGetTypeID();
kCFNullTypeID = CFNullGetTypeID();
kCFArrayTypeID = CFArrayGetTypeID();
kCFDictionaryTypeID = CFDictionaryGetTypeID();
}
void TSICTStringSetDefaultFormat(TSITStringFormat format)
{
if (format == kTSITStringFormatDefault) {
TSITStringDefaultFormat = kTSITStringFormatTNetstring;
} else {
TSITStringDefaultFormat = format;
}
}
TSITStringFormat TSICTStringGetDefaultFormat(void)
{
return TSITStringDefaultFormat;
}
void TSICTStringDestroy(TStringIRep* rep)
{
CFRelease(rep->data);
free(rep->length);
free(rep);
}
static inline TStringIRep* TSICTStringCreateWithDataOfTypeAndFormat(CFDataRef data, TSITStringTag type, TSITStringFormat format)
{
if (format == kTSITStringFormatDefault) {
format = TSICTStringGetDefaultFormat();
}
TStringIRep* rep = calloc(1, sizeof(TStringIRep));
rep->data = CFDataCreateCopy(kCFAllocatorDefault, data);
rep->type = type;
rep->format = format;
rep->length = calloc(10, sizeof(char));
CFIndex len = CFDataGetLength(rep->data);
if (snprintf(rep->length, 10, "%lu", len)) {
return rep;
} else {
TSICTStringDestroy(rep);
return NULL;
}
}
static inline CFDataRef TSICTStringCreateDataFromIntermediateRepresentation(TStringIRep* rep)
{
CFIndex len = CFDataGetLength(rep->data);
CFMutableDataRef buffer = CFDataCreateMutableCopy(kCFAllocatorDefault, (len + 12), rep->data);
UInt8* bufferBytes = CFDataGetMutableBytePtr(buffer);
size_t prefixLength = strlen(rep->length) + 1;
CFDataReplaceBytes(buffer, BeginningRange, (const UInt8*)rep->length, (CFIndex)prefixLength);
if (rep->format == kTSITStringFormatTNetstring) {
const UInt8 ftag = (UInt8)TNetstringTypes[rep->type];
CFDataAppendBytes(buffer, &ftag, 1);
bufferBytes[(prefixLength - 1)] = TNetstringSeparator;
} else if (rep->format == kTSITStringFormatOTNetstring) {
const UInt8 ftag = (UInt8)OTNetstringTypes[rep->type];
bufferBytes[(prefixLength - 1)] = ftag;
}
CFDataRef dataRep = CFDataCreateCopy(kCFAllocatorDefault, buffer);
CFRelease(buffer);
return dataRep;
}
static inline CFStringRef TSICTStringCreateStringFromIntermediateRepresentation(TStringIRep* rep)
{
CFDataRef data = TSICTStringCreateDataFromIntermediateRepresentation(rep);
CFStringRef string = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
CFRelease(data);
return string;
}
static inline void TSICTStringAppendObjectToMutableDataWithFormat(CFTypeRef object, CFMutableDataRef buffer, TSITStringFormat format)
{
if (object == NULL) {
object = kCFNull;
}
CFRetain(object);
TStringIRep* objRep = TSICTStringCreateWithObjectAndFormat(object, format);
CFDataRef objData = TSICTStringCreateDataFromIntermediateRepresentation(objRep);
CFDataAppendBytes(buffer, (CFDataGetBytePtr(objData)), CFDataGetLength(objData));
CFRelease(objData);
TSICTStringDestroy(objRep);
CFRelease(object);
}
static void ArrayBufferAppendCallback(const void* item, void* context)
{
TStringCollectionCallbackContext* cx = (TStringCollectionCallbackContext*)context;
CFMutableDataRef buffer = cx->buffer;
TSITStringFormat format = cx->format;
TSICTStringAppendObjectToMutableDataWithFormat(item, buffer, format);
}
static void DictionaryBufferAppendCallback(const void* key, const void* value, void* context)
{
TStringCollectionCallbackContext* cx = (TStringCollectionCallbackContext*)context;
CFMutableDataRef buffer = cx->buffer;
TSITStringFormat format = cx->format;
TSICTStringAppendObjectToMutableDataWithFormat(key, buffer, format);
TSICTStringAppendObjectToMutableDataWithFormat(value, buffer, format);
}
CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep)
{
return TSICTStringCreateDataFromIntermediateRepresentation(rep);
}
CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format)
{
if (object == NULL) {
object = kCFNull;
}
CFRetain(object);
TStringIRep* rep = TSICTStringCreateWithObjectAndFormat(object, format);
CFDataRef data = TSICTStringCreateDataFromIntermediateRepresentation(rep);
TSICTStringDestroy(rep);
CFRelease(object);
return data;
}
CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep)
{
return TSICTStringCreateStringFromIntermediateRepresentation(rep);
}
CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format)
{
if (object == NULL) {
object = kCFNull;
}
CFRetain(object);
TStringIRep* rep = TSICTStringCreateWithObjectAndFormat(object, format);
CFStringRef string = TSICTStringCreateStringFromIntermediateRepresentation(rep);
TSICTStringDestroy(rep);
CFRelease(object);
return string;
}
TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format)
{
if (object == NULL) {
return TSICTStringCreateNullWithFormat(format);
}
CFRetain(object);
CFTypeID cfType = CFGetTypeID(object);
TStringIRep* rep = NULL;
if (cfType == kCFDataTypeID) {
rep = TSICTStringCreateWithDataOfTypeAndFormat(object, kTSITStringTagString, format);
} else if (cfType == kCFStringTypeID) {
rep = TSICTStringCreateWithStringAndFormat(object, format);
} else if (cfType == kCFNumberTypeID) {
rep = TSICTStringCreateWithNumberAndFormat(object, format);
} else if (cfType == kCFBooleanTypeID) {
if (CFBooleanGetValue(object)) {
rep = TSICTStringCreateTrueWithFormat(format);
} else {
rep = TSICTStringCreateFalseWithFormat(format);
}
} else if (cfType == kCFNullTypeID) {
rep = TSICTStringCreateNullWithFormat(format);
} else if (cfType == kCFArrayTypeID) {
rep = TSICTStringCreateWithArrayAndFormat(object, format);
} else if (cfType == kCFDictionaryTypeID) {
rep = TSICTStringCreateWithDictionaryAndFormat(object, format);
} else {
rep = TSICTStringCreateInvalidWithFormat(format);
}
CFRelease(object);
return rep;
}
TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format)
{
CFRetain(string);
CFDataRef data = CFStringCreateExternalRepresentation(kCFAllocatorDefault, string, kCFStringEncodingUTF8, '?');
TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagString, format);
CFRelease(data);
CFRelease(string);
return rep;
}
TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format)
{
CFRetain(number);
TSITStringTag tag = kTSITStringTagNumber;
CFDataRef data;
CFNumberType numType = CFNumberGetType(number);
switch(numType) {
case kCFNumberCharType:
{
int value;
if (CFNumberGetValue(number, kCFNumberIntType, &value)) {
if (value == 0 || value == 1) {
tag = kTSITStringTagBool;
} else {
tag = kTSITStringTagString;
}
}
break;
}
case kCFNumberFloat32Type:
case kCFNumberFloat64Type:
case kCFNumberFloatType:
case kCFNumberDoubleType:
{
tag = kTSITStringTagFloat;
break;
}
}
if (tag == kTSITStringTagBool) {
bool value;
CFNumberGetValue(number, kCFNumberIntType, &value);
if (value) {
data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"true", 4);
} else {
data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"false", 5);
}
} else if (tag == kTSITStringTagFloat) {
char buf[32];
char *p, *e;
double value;
CFNumberGetValue(number, numType, &value);
sprintf(buf, "%#.15g", value);
e = buf + strlen(buf);
p = e;
while (p[-1]=='0' && ('0' <= p[-2] && p[-2] <= '9')) {
p--;
}
memmove(p, e, strlen(e)+1);
data = CFDataCreate(kCFAllocatorDefault, (UInt8*)buf, (CFIndex)strlen(buf));
} else {
char buf[32];
SInt64 value;
CFNumberGetValue(number, numType, &value);
sprintf(buf, "%lli", value);
data = CFDataCreate(kCFAllocatorDefault, (UInt8*)buf, (CFIndex)strlen(buf));
}
TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, tag, format);
CFRelease(data);
CFRelease(number);
return rep;
}
TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format)
{
CFDataRef data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"true", 4);
TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagBool, format);
CFRelease(data);
return rep;
}
TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format)
{
CFDataRef data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"false", 5);
TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagBool, format);
CFRelease(data);
return rep;
}
TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format)
{
CFDataRef data = CFDataCreate(kCFAllocatorDefault, NULL, 0);
TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagNull, format);
CFRelease(data);
return rep;
}
TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format)
{
CFDataRef data = CFDataCreate(kCFAllocatorDefault, NULL, 0);
TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagInvalid, format);
CFRelease(data);
return rep;
}
TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format)
{
CFRetain(array);
CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0);
CFRange all = CFRangeMake(0, CFArrayGetCount(array));
TStringCollectionCallbackContext cx = {buffer, format};
CFArrayApplyFunction(array, all, ArrayBufferAppendCallback, &cx);
TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(buffer, kTSITStringTagList, format);
CFRelease(buffer);
CFRelease(array);
return rep;
}
TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format)
{
CFRetain(dictionary);
CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0);
TStringCollectionCallbackContext cx = {buffer, format};
CFDictionaryApplyFunction(dictionary, DictionaryBufferAppendCallback, &cx);
TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(buffer, kTSITStringTagDict, format);
CFRelease(buffer);
CFRelease(dictionary);
return rep;
}
@@ -0,0 +1,74 @@
//
// TSICTString.h
// TSITString
//
// Created by Travis Tilley on 9/27/11.
//
#ifndef TSICTString_H
#define TSICTString_H
#include <CoreFoundation/CoreFoundation.h>
typedef enum {
kTSITStringTagString = 0,
kTSITStringTagNumber = 1,
kTSITStringTagFloat = 2,
kTSITStringTagBool = 3,
kTSITStringTagNull = 4,
kTSITStringTagDict = 5,
kTSITStringTagList = 6,
kTSITStringTagInvalid = 7,
} TSITStringTag;
extern const char* const TNetstringTypes;
extern const char* const OTNetstringTypes;
extern const UInt8 TNetstringSeparator;
typedef enum {
kTSITStringFormatDefault = 0,
kTSITStringFormatOTNetstring = 1,
kTSITStringFormatTNetstring = 2,
} TSITStringFormat;
extern TSITStringFormat TSITStringDefaultFormat;
typedef struct TSITStringIntermediate {
CFDataRef data;
char* length;
TSITStringTag type;
TSITStringFormat format;
} TStringIRep;
typedef struct {
CFMutableDataRef buffer;
TSITStringFormat format;
} TStringCollectionCallbackContext;
void Init_TSICTString(void);
void TSICTStringSetDefaultFormat(TSITStringFormat format);
TSITStringFormat TSICTStringGetDefaultFormat(void);
void TSICTStringDestroy(TStringIRep* rep);
CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep);
CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format);
CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep);
CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format);
TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format);
TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format);
TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format);
TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format);
TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format);
TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format);
TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format);
TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format);
TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format);
#endif
@@ -0,0 +1,201 @@
#include <getopt.h>
#include "cli.h"
const char* cli_info_purpose = "A flexible command-line interface for the FSEvents API";
const char* cli_info_usage = "Usage: fsevent_watch [OPTIONS]... [PATHS]...";
const char* cli_info_help[] = {
" -h, --help you're looking at it",
" -V, --version print version number and exit",
" -p, --show-plist display the embedded Info.plist values",
" -s, --since-when=EventID fire historical events since ID",
" -l, --latency=seconds latency period (default='0.5')",
" -n, --no-defer enable no-defer latency modifier",
" -r, --watch-root watch for when the root path has changed",
// " -i, --ignore-self ignore current process",
" -F, --file-events provide file level event data",
" -f, --format=name output format (classic, niw, \n"
" tnetstring, otnetstring)",
0
};
static void default_args (struct cli_info* args_info)
{
args_info->since_when_arg = kFSEventStreamEventIdSinceNow;
args_info->latency_arg = 0.5;
args_info->no_defer_flag = false;
args_info->watch_root_flag = false;
args_info->ignore_self_flag = false;
args_info->file_events_flag = false;
args_info->mark_self_flag = false;
args_info->format_arg = kFSEventWatchOutputFormatOTNetstring;
}
static void cli_parser_release (struct cli_info* args_info)
{
unsigned int i;
for (i=0; i < args_info->inputs_num; ++i) {
free(args_info->inputs[i]);
}
if (args_info->inputs_num) {
free(args_info->inputs);
}
args_info->inputs_num = 0;
}
void cli_parser_init (struct cli_info* args_info)
{
default_args(args_info);
args_info->inputs = 0;
args_info->inputs_num = 0;
}
void cli_parser_free (struct cli_info* args_info)
{
cli_parser_release(args_info);
}
static void cli_print_info_dict (const void *key,
const void *value,
void *context)
{
CFStringRef entry = CFStringCreateWithFormat(NULL, NULL,
CFSTR("%@:\n %@"), key, value);
if (entry) {
CFShow(entry);
CFRelease(entry);
}
}
void cli_show_plist (void)
{
CFBundleRef mainBundle = CFBundleGetMainBundle();
CFRetain(mainBundle);
CFDictionaryRef mainBundleDict = CFBundleGetInfoDictionary(mainBundle);
if (mainBundleDict) {
CFRetain(mainBundleDict);
printf("Embedded Info.plist metadata:\n\n");
CFDictionaryApplyFunction(mainBundleDict, cli_print_info_dict, NULL);
CFRelease(mainBundleDict);
}
CFRelease(mainBundle);
printf("\n");
}
void cli_print_version (void)
{
printf("%s %s\n\n", CLI_NAME, CLI_VERSION);
#ifdef COMPILED_AT
printf("Compiled at: %s\n", COMPILED_AT);
#endif
#ifdef COMPILER
printf("Compiled with: %s\n", COMPILER);
#endif
#ifdef TARGET_CPU
printf("Compiled for: %s\n", TARGET_CPU);
#endif
printf("\n");
}
void cli_print_help (void)
{
cli_print_version();
printf("\n%s\n", cli_info_purpose);
printf("\n%s\n", cli_info_usage);
printf("\n");
int i = 0;
while (cli_info_help[i]) {
printf("%s\n", cli_info_help[i++]);
}
}
int cli_parser (int argc, const char** argv, struct cli_info* args_info)
{
static struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "show-plist", no_argument, NULL, 'p' },
{ "since-when", required_argument, NULL, 's' },
{ "latency", required_argument, NULL, 'l' },
{ "no-defer", no_argument, NULL, 'n' },
{ "watch-root", no_argument, NULL, 'r' },
{ "ignore-self", no_argument, NULL, 'i' },
{ "file-events", no_argument, NULL, 'F' },
{ "mark-self", no_argument, NULL, 'm' },
{ "format", required_argument, NULL, 'f' },
{ 0, 0, 0, 0 }
};
const char* shortopts = "hVps:l:nriFf:";
int c = -1;
while ((c = getopt_long(argc, (char * const*)argv, shortopts, longopts, NULL)) != -1) {
switch(c) {
case 's': // since-when
args_info->since_when_arg = strtoull(optarg, NULL, 0);
break;
case 'l': // latency
args_info->latency_arg = strtod(optarg, NULL);
break;
case 'n': // no-defer
args_info->no_defer_flag = true;
break;
case 'r': // watch-root
args_info->watch_root_flag = true;
break;
case 'i': // ignore-self
args_info->ignore_self_flag = true;
break;
case 'F': // file-events
args_info->file_events_flag = true;
break;
case 'm': // mark-self
args_info->mark_self_flag = true;
break;
case 'f': // format
if (strcmp(optarg, "classic") == 0) {
args_info->format_arg = kFSEventWatchOutputFormatClassic;
} else if (strcmp(optarg, "niw") == 0) {
args_info->format_arg = kFSEventWatchOutputFormatNIW;
} else if (strcmp(optarg, "tnetstring") == 0) {
args_info->format_arg = kFSEventWatchOutputFormatTNetstring;
} else if (strcmp(optarg, "otnetstring") == 0) {
args_info->format_arg = kFSEventWatchOutputFormatOTNetstring;
} else {
fprintf(stderr, "Unknown output format: %s\n", optarg);
exit(EXIT_FAILURE);
}
break;
case 'V': // version
cli_print_version();
exit(EXIT_SUCCESS);
case 'p': // show-plist
cli_show_plist();
exit(EXIT_SUCCESS);
case 'h': // help
case '?': // invalid option
case ':': // missing argument
cli_print_help();
exit((c == 'h') ? EXIT_SUCCESS : EXIT_FAILURE);
}
}
if (optind < argc) {
int i = 0;
args_info->inputs_num = (unsigned int)(argc - optind);
args_info->inputs =
(char**)(malloc ((args_info->inputs_num)*sizeof(char*)));
while (optind < argc)
if (argv[optind++] != argv[0]) {
args_info->inputs[i++] = strdup(argv[optind-1]);
}
}
return EXIT_SUCCESS;
}
@@ -0,0 +1,45 @@
#ifndef CLI_H
#define CLI_H
#include "common.h"
#ifndef CLI_NAME
#define CLI_NAME "fsevent_watch"
#endif /* CLI_NAME */
#ifndef PROJECT_VERSION
#error "PROJECT_VERSION not set"
#endif /* PROJECT_VERSION */
#ifndef CLI_VERSION
#define CLI_VERSION _xstr(PROJECT_VERSION)
#endif /* CLI_VERSION */
struct cli_info {
UInt64 since_when_arg;
double latency_arg;
bool no_defer_flag;
bool watch_root_flag;
bool ignore_self_flag;
bool file_events_flag;
bool mark_self_flag;
enum FSEventWatchOutputFormat format_arg;
char** inputs;
unsigned inputs_num;
};
extern const char* cli_info_purpose;
extern const char* cli_info_usage;
extern const char* cli_info_help[];
void cli_print_help(void);
void cli_print_version(void);
int cli_parser (int argc, const char** argv, struct cli_info* args_info);
void cli_parser_init (struct cli_info* args_info);
void cli_parser_free (struct cli_info* args_info);
#endif /* CLI_H */
@@ -0,0 +1,22 @@
#ifndef fsevent_watch_common_h
#define fsevent_watch_common_h
#include <CoreFoundation/CoreFoundation.h>
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif
#include <CoreServices/CoreServices.h>
#include <unistd.h>
#include "compat.h"
#include "defines.h"
#include "TSICTString.h"
enum FSEventWatchOutputFormat {
kFSEventWatchOutputFormatClassic,
kFSEventWatchOutputFormatNIW,
kFSEventWatchOutputFormatTNetstring,
kFSEventWatchOutputFormatOTNetstring
};
#endif /* fsevent_watch_common_h */
@@ -0,0 +1,41 @@
#include "compat.h"
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0)
FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf = 0x00000008;
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_7) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0)
FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents = 0x00000010;
FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated = 0x00000100;
FSEventStreamEventFlags kFSEventStreamEventFlagItemRemoved = 0x00000200;
FSEventStreamEventFlags kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400;
FSEventStreamEventFlags kFSEventStreamEventFlagItemRenamed = 0x00000800;
FSEventStreamEventFlags kFSEventStreamEventFlagItemModified = 0x00001000;
FSEventStreamEventFlags kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000;
FSEventStreamEventFlags kFSEventStreamEventFlagItemChangeOwner = 0x00004000;
FSEventStreamEventFlags kFSEventStreamEventFlagItemXattrMod = 0x00008000;
FSEventStreamEventFlags kFSEventStreamEventFlagItemIsFile = 0x00010000;
FSEventStreamEventFlags kFSEventStreamEventFlagItemIsDir = 0x00020000;
FSEventStreamEventFlags kFSEventStreamEventFlagItemIsSymlink = 0x00040000;
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0)
FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf = 0x00000020;
FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent = 0x00080000;
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0)
FSEventStreamEventFlags kFSEventStreamEventFlagItemIsHardlink = 0x00100000;
FSEventStreamEventFlags kFSEventStreamEventFlagItemIsLastHardlink = 0x00200000;
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_13) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0)
FSEventStreamCreateFlags kFSEventStreamCreateFlagUseExtendedData = 0x00000040;
FSEventStreamEventFlags kFSEventStreamEventFlagItemCloned = 0x00400000;
#endif
@@ -0,0 +1,100 @@
/**
* @headerfile compat.h
* FSEventStream flag compatibility shim
*
* In order to compile a binary against an older SDK yet still support the
* features present in later OS releases, we need to define any missing enum
* constants not present in the older SDK. This allows us to safely defer
* feature detection to runtime (and avoid recompilation).
*/
#ifndef listen_fsevents_compat_h
#define listen_fsevents_compat_h
#ifndef __CORESERVICES__
#include <CoreServices/CoreServices.h>
#endif // __CORESERVICES__
#ifndef __AVAILABILITY__
#include <Availability.h>
#endif // __AVAILABILITY__
#ifndef __MAC_10_6
#define __MAC_10_6 1060
#endif
#ifndef __MAC_10_7
#define __MAC_10_7 1070
#endif
#ifndef __MAC_10_9
#define __MAC_10_9 1090
#endif
#ifndef __MAC_10_10
#define __MAC_10_10 101000
#endif
#ifndef __MAC_10_13
#define __MAC_10_13 101300
#endif
#ifndef __IPHONE_6_0
#define __IPHONE_6_0 60000
#endif
#ifndef __IPHONE_7_0
#define __IPHONE_7_0 70000
#endif
#ifndef __IPHONE_9_0
#define __IPHONE_9_0 90000
#endif
#ifndef __IPHONE_11_0
#define __IPHONE_11_0 110000
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0)
extern FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf;
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_7) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0)
extern FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents;
extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated,
kFSEventStreamEventFlagItemRemoved,
kFSEventStreamEventFlagItemInodeMetaMod,
kFSEventStreamEventFlagItemRenamed,
kFSEventStreamEventFlagItemModified,
kFSEventStreamEventFlagItemFinderInfoMod,
kFSEventStreamEventFlagItemChangeOwner,
kFSEventStreamEventFlagItemXattrMod,
kFSEventStreamEventFlagItemIsFile,
kFSEventStreamEventFlagItemIsDir,
kFSEventStreamEventFlagItemIsSymlink;
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0)
extern FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf;
extern FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent;
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0)
extern FSEventStreamEventFlags kFSEventStreamEventFlagItemIsHardlink,
kFSEventStreamEventFlagItemIsLastHardlink;
#endif
#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_13) || \
(defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0)
extern FSEventStreamCreateFlags kFSEventStreamCreateFlagUseExtendedData;
extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCloned;
#endif
#ifdef __cplusplus
}
#endif
#endif // listen_fsevents_compat_h
@@ -0,0 +1,42 @@
#ifndef fsevent_watch_defines_h
#define fsevent_watch_defines_h
#define _str(s) #s
#define _xstr(s) _str(s)
#define COMPILED_AT __DATE__ " " __TIME__
#if defined (__clang__)
#define COMPILER "clang " __clang_version__
#elif defined (__GNUC__)
#define COMPILER "gcc " __VERSION__
#else
#define COMPILER "unknown"
#endif
#if defined(__ppc__)
#define TARGET_CPU "ppc"
#elif defined(__ppc64__)
#define TARGET_CPU "ppc64"
#elif defined(__i386__)
#define TARGET_CPU "i386"
#elif defined(__x86_64__)
#define TARGET_CPU "x86_64"
#elif defined(__arm64__)
#define TARGET_CPU "arm64"
#else
#define TARGET_CPU "unknown"
#endif
#define FLAG_CHECK(flags, flag) ((flags) & (flag))
#define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \
do { \
if (FLAG_CHECK(flags, flag)) { \
fprintf(fd, "%s", msg "\n"); } } \
while (0)
#define FLAG_CHECK_STDERR(flags, flag, msg) \
FPRINTF_FLAG_CHECK(flags, flag, msg, stderr)
#endif /* fsevent_watch_defines_h */
@@ -0,0 +1,548 @@
#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;
}
@@ -0,0 +1,66 @@
#include "signal_handlers.h"
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#define PPID_ALARM_INTERVAL 2 // send SIGALRM every this seconds
static pid_t orig_ppid;
static void signal_handler(int _) {
exit(EXIT_FAILURE);
}
static void check_ppid(void) {
if (getppid() != orig_ppid) {
exit(EXIT_FAILURE);
}
}
static void check_stdout_open(void) {
if (fcntl(STDOUT_FILENO, F_GETFD) < 0) {
exit(EXIT_FAILURE);
}
}
static void alarm_handler(int _) {
check_ppid();
check_stdout_open();
alarm(PPID_ALARM_INTERVAL);
signal(SIGALRM, alarm_handler);
}
static void die(const char *msg) {
fprintf(stderr, "\nFATAL: %s\n", msg);
abort();
}
static void install_signal_handler(int sig, void (*handler)(int)) {
if (signal(sig, handler) == SIG_ERR) {
die("Could not install signal handler");
}
}
void install_signal_handlers(void) {
// check pipe is still connected
check_stdout_open();
// watch getppid() every PPID_ALARM_INTERVAL seconds
orig_ppid = getppid();
if (orig_ppid <= 1) {
die("prematurely zombied");
}
install_signal_handler(SIGALRM, alarm_handler);
alarm(PPID_ALARM_INTERVAL);
// be sure to exit on SIGHUP, SIGPIPE
install_signal_handler(SIGHUP, signal_handler);
install_signal_handler(SIGPIPE, signal_handler);
}
@@ -0,0 +1,16 @@
/**
* @headerfile signal_handlers.h
* Signal handlers to stop the zombie hordes
*
* Catch and handle signals better so that we die faster like a good meat puppet.
*/
#ifndef fsevent_watch_signal_handlers_h
#define fsevent_watch_signal_handlers_h
void install_signal_handlers(void);
#endif // fsevent_watch_signal_handlers_h