This commit is contained in:
@@ -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
|
||||
@@ -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]
|
||||
Reference in New Issue
Block a user