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
+21
View File
@@ -0,0 +1,21 @@
Copyright (c) 2011-2013 Travis Tilley
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.
@@ -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
+231
View File
@@ -0,0 +1,231 @@
# -*- encoding: utf-8 -*-
require 'rubygems' unless defined?(Gem)
require 'pathname'
require 'date'
require 'time'
require 'rake/clean'
raise "unable to find xcodebuild" unless system('which', 'xcodebuild')
FSEVENT_WATCH_EXE_VERSION = '0.1.5'
$this_dir = Pathname.new(__FILE__).dirname.expand_path
$final_exe = $this_dir.parent.join('bin/fsevent_watch')
$src_dir = $this_dir.join('fsevent_watch')
$obj_dir = $this_dir.join('build')
SRC = Pathname.glob("#{$src_dir}/*.c")
OBJ = SRC.map {|s| $obj_dir.join("#{s.basename('.c')}.o")}
$now = DateTime.now.xmlschema rescue Time.now.xmlschema
$CC = ENV['CC'] || `which clang || which gcc`.strip
$CFLAGS = ENV['CFLAGS'] || '-fconstant-cfstrings -fasm-blocks -fstrict-aliasing -Wall'
$ARCHFLAGS = ENV['ARCHFLAGS'] || '-arch x86_64'
$DEFINES = "-DNS_BUILD_32_LIKE_64 -DNS_BLOCK_ASSERTIONS -DPROJECT_VERSION=#{FSEVENT_WATCH_EXE_VERSION}"
$GCC_C_LANGUAGE_STANDARD = ENV['GCC_C_LANGUAGE_STANDARD'] || 'gnu11'
# generic developer id name so it'll match correctly for anyone who has only
# one developer id in their keychain (not that I expect anyone else to bother)
$CODE_SIGN_IDENTITY = 'Developer ID Application'
$arch = `uname -m`.strip
$os_release = `uname -r`.strip
$BUILD_TRIPLE = "#{$arch}-apple-darwin#{$os_release}"
$CCVersion = `#{$CC} --version | head -n 1`.strip
CLEAN.include OBJ.map(&:to_s)
CLEAN.include $obj_dir.join('Info.plist').to_s
CLEAN.include $obj_dir.join('fsevent_watch').to_s
CLOBBER.include $final_exe.to_s
task :sw_vers do
$mac_product_version = `sw_vers -productVersion`.strip
$mac_build_version = `sw_vers -buildVersion`.strip
$MACOSX_DEPLOYMENT_TARGET = ENV['MACOSX_DEPLOYMENT_TARGET'] || $mac_product_version.sub(/\.\d*$/, '')
$CFLAGS = "#{$CFLAGS} -mmacosx-version-min=#{$MACOSX_DEPLOYMENT_TARGET}"
end
task :get_sdk_info => :sw_vers do
$SDK_INFO = {}
version_info = `xcodebuild -version -sdk macosx#{$MACOSX_DEPLOYMENT_TARGET}`
raise "invalid SDK" unless !!$?.exitstatus
version_info.strip.each_line do |line|
next if line.strip.empty?
next unless line.include?(':')
match = line.match(/([^:]*): (.*)/)
next unless match
$SDK_INFO[match[1]] = match[2]
end
end
task :debug => :sw_vers do
$DEFINES = "-DDEBUG #{$DEFINES}"
$CFLAGS = "#{$CFLAGS} -O0 -fno-omit-frame-pointer -g"
end
task :release => :sw_vers do
$DEFINES = "-DNDEBUG #{$DEFINES}"
$CFLAGS = "#{$CFLAGS} -Ofast"
end
desc 'configure build type depending on whether ENV var FWDEBUG is set'
task :set_build_type => :sw_vers do
if ENV['FWDEBUG']
Rake::Task[:debug].invoke
else
Rake::Task[:release].invoke
end
end
desc 'set build arch to ppc'
task :ppc do
$ARCHFLAGS = '-arch ppc'
end
desc 'set build arch to x86_64'
task :x86_64 do
$ARCHFLAGS = '-arch x86_64'
end
desc 'set build arch to i386'
task :x86 do
$ARCHFLAGS = '-arch i386'
end
desc 'set build arch to arm64'
task :arm64 do
$ARCHFLAGS = '-arch arm64'
end
task :setup_env => [:set_build_type, :sw_vers, :get_sdk_info]
directory $obj_dir.to_s
file $obj_dir.to_s => :setup_env
SRC.zip(OBJ).each do |source, object|
file object.to_s => [source.to_s, $obj_dir.to_s] do
cmd = [
$CC,
$ARCHFLAGS,
"-std=#{$GCC_C_LANGUAGE_STANDARD}",
$CFLAGS,
$DEFINES,
"-I#{$src_dir}",
'-isysroot',
$SDK_INFO['Path'],
'-c', source,
'-o', object
]
sh(cmd.map {|s| s.to_s}.join(' '))
end
end
file $obj_dir.join('Info.plist').to_s => [$obj_dir.to_s, :setup_env] do
File.open($obj_dir.join('Info.plist').to_s, 'w+') do |file|
indentation = ''
indent = lambda {|num| indentation = ' ' * num }
add = lambda {|str| file << "#{indentation}#{str}\n" }
key = lambda {|str| add["<key>#{str}</key>"] }
string = lambda {|str| add["<string>#{str}</string>"] }
add['<?xml version="1.0" encoding="UTF-8"?>']
add['<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">']
add['<plist version="1.0">']
indent[2]
add['<dict>']
indent[4]
key['CFBundleExecutable']
string['fsevent_watch']
key['CFBundleIdentifier']
string['com.teaspoonofinsanity.fsevent_watch']
key['CFBundleName']
string['fsevent_watch']
key['CFBundleDisplayName']
string['FSEvent Watch CLI']
key['NSHumanReadableCopyright']
string['Copyright (C) 2011-2017 Travis Tilley']
key['CFBundleVersion']
string["#{FSEVENT_WATCH_EXE_VERSION}"]
key['LSMinimumSystemVersion']
string["#{$MACOSX_DEPLOYMENT_TARGET}"]
key['DTSDKBuild']
string["#{$SDK_INFO['ProductBuildVersion']}"]
key['DTSDKName']
string["macosx#{$SDK_INFO['SDKVersion']}"]
key['DTSDKPath']
string["#{$SDK_INFO['Path']}"]
key['BuildMachineOSBuild']
string["#{$mac_build_version}"]
key['BuildMachineOSVersion']
string["#{$mac_product_version}"]
key['FSEWCompiledAt']
string["#{$now}"]
key['FSEWVersionInfoBuilder']
string["#{`whoami`.strip}"]
key['FSEWBuildTriple']
string["#{$BUILD_TRIPLE}"]
key['FSEWCC']
string["#{$CC}"]
key['FSEWCCVersion']
string["#{$CCVersion}"]
key['FSEWCFLAGS']
string["#{$CFLAGS}"]
indent[2]
add['</dict>']
indent[0]
add['</plist>']
end
end
desc 'generate an Info.plist used for code signing as well as embedding build settings into the resulting binary'
task :plist => $obj_dir.join('Info.plist').to_s
file $obj_dir.join('fsevent_watch').to_s => [$obj_dir.to_s, $obj_dir.join('Info.plist').to_s] + OBJ.map(&:to_s) do
cmd = [
$CC,
$ARCHFLAGS,
"-std=#{$GCC_C_LANGUAGE_STANDARD}",
$CFLAGS,
$DEFINES,
"-I#{$src_dir}",
'-isysroot',
$SDK_INFO['Path'],
'-framework CoreFoundation -framework CoreServices',
'-sectcreate __TEXT __info_plist',
$obj_dir.join('Info.plist')
] + OBJ + [
'-o', $obj_dir.join('fsevent_watch')
]
sh(cmd.map {|s| s.to_s}.join(' '))
end
desc 'compile and link build/fsevent_watch'
task :build => $obj_dir.join('fsevent_watch').to_s
desc 'codesign build/fsevent_watch binary'
task :codesign => :build do
sh "codesign -s '#{$CODE_SIGN_IDENTITY}' #{$obj_dir.join('fsevent_watch')}"
end
directory $this_dir.parent.join('bin')
desc 'replace bundled fsevent_watch binary with build/fsevent_watch'
task :replace_exe => [$this_dir.parent.join('bin'), :build] do
sh "mv #{$obj_dir.join('fsevent_watch')} #{$final_exe}"
end
task :default => [:replace_exe, :clean]