/** fsevents.c from Lsyncd - Live (Mirror) Syncing Demon * * License: GPLv2 (see COPYING) or any later version * * Authors: Axel Kittenberger * Damian Steward * * ----------------------------------------------------------------------- * * Event interface for MacOS 10.5 (Leopard) /dev/fsevents interface. * * Special thanks go to Amit Singh and his fslogger demonstration that showed * how apples /dev/fsevents can be used. http://osxbook.com/software/fslogger/ * * -- WARNING -- Quoting http://www.osxbook.com/software/fslogger/ -- * * The interface that fslogger [and thus Lsyncd] uses is private to Apple. * Currently, there is a caveat regarding the use of this interface by third * parties (including fslogger [and thus Lsyncd]). While the change * notification interface supports multiple clients, there is a single kernel * buffer for holding events that are to be delivered to one or more * subscribers, with the primary subscriber being Spotlight. Now, the kernel * must hold events until it has notified all subscribers that are interested * in them. Since there is a single buffer, a slow subscriber can cause it to * overflow. If this happens, events will be dropped — for all subscribers, * including Spotlight. Consequently, Spotlight may need to look at the entire * volume to determine "what changed". */ #include "lsyncd.h" #include #include #include #include #include #include #include #include #include #include "bsd/sys/fsevents.h" #include #include #include /* the fsevents pseudo-device */ #define DEV_FSEVENTS "/dev/fsevents" /* buffer for reading from the device */ #define FSEVENT_BUFSIZ 131072 /* limited by MAX_KFS_EVENTS */ #define EVENT_QUEUE_SIZE 4096 #define KFS_NUM_ARGS FSE_MAX_ARGS /* OS 10.5 structuce */ /* an event argument */ struct kfs_event_arg { /* argument type */ u_int16_t type; /* size of argument data that follows this field */ u_int16_t len; union { struct vnode *vp; char *str; void *ptr; int32_t int32; dev_t dev; ino_t ino; int32_t mode; uid_t uid; gid_t gid; uint64_t timestamp; } data; }; /* OS 10.5 structuce */ /* an event */ struct kfs_event { /* event type */ int32_t type; /* pid of the process that performed the operation */ pid_t pid; /* event arguments */ struct kfs_event_arg* args[FSE_MAX_ARGS]; }; /** * fsevents (cloned) filedescriptor */ static int fsevents_fd = -1; /* event names */ /*static const char *eventNames[FSE_MAX_EVENTS] = { "CREATE_FILE", "DELETE", "STAT_CHANGED", "RENAME", "CONTENT_MODIFIED", "EXCHANGE", "FINDER_INFO_CHANGED", "CREATE_DIR", "CHOWN", "XATTR_MODIFIED", "XATTR_REMOVED", };*/ /* argument names*/ /*static const char *argNames[] = { "UNKNOWN", "VNODE", "STRING", "PATH", "INT32", "INT64", "RAW", "INO", "UID", "DEV", "MODE", "GID", "FINFO", };*/ /** * The read buffer */ static size_t const readbuf_size = 131072; static char * readbuf = NULL; /** * The event buffer */ static size_t const eventbuf_size = FSEVENT_BUFSIZ; static char* eventbuf = NULL; /** * Handles one fsevents event */ static void handle_event(lua_State *L, struct kfs_event *event, ssize_t mlen) { int32_t atype; const char *path = NULL; const char *trg = NULL; const char *etype = NULL; int isdir = -1; #ifdef LSYNCD_TARGET_APPLE \ // Since macOS 10.15, fsevent paths are prefixed with this string. const char *const INOTIFY_PREFIX = "/System/Volumes/Data"; #endif if (event->type == FSE_EVENTS_DROPPED) { logstring("Fsevents", "Events dropped!"); load_runner_func(L, "overflow"); if (lua_pcall(L, 0, 0, -2)) { exit(-1); // ERRNO } lua_pop(L, 1); hup = 1; return; } atype = event->type & FSE_TYPE_MASK; /*uint32_t aflags = FSE_GET_FLAGS(event->type);*/ if ((atype < FSE_MAX_EVENTS) && (atype >= -1)) { /*printlogf(L, "Fsevents", "got event %s", eventNames[atype]); if (aflags & FSE_COMBINED_EVENTS) { logstring("Fsevents", "combined events"); } if (aflags & FSE_CONTAINS_DROPPED_EVENTS) { logstring("Fsevents", "contains dropped events"); }*/ } else { printlogf( L, "Error", "unknown event(%d) in fsevents.", atype ); exit(-1); // ERRNO } { /* assigns the expected arguments */ int whichArg = 0; while (whichArg < FSE_MAX_ARGS) { struct kfs_event_arg * arg = event->args[whichArg++]; if (arg->type == FSE_ARG_DONE) { break; } switch (arg->type) { case FSE_ARG_STRING : switch(atype) { case FSE_RENAME : if (path) { // for move events second string is target trg = (char *) &arg->data.str; } // fallthrough case FSE_CHOWN : case FSE_CONTENT_MODIFIED : case FSE_CREATE_FILE : case FSE_CREATE_DIR : case FSE_DELETE : case FSE_STAT_CHANGED : if (!path) path = (char *)&arg->data.str; break; } break; case FSE_ARG_MODE : switch(atype) { case FSE_RENAME : case FSE_CHOWN : case FSE_CONTENT_MODIFIED : case FSE_CREATE_FILE : case FSE_CREATE_DIR : case FSE_DELETE : case FSE_STAT_CHANGED : isdir = arg->data.mode & S_IFDIR ? 1 : 0; break; } break; } } } switch(atype) { case FSE_CHOWN : case FSE_STAT_CHANGED : etype = "Attrib"; break; case FSE_CREATE_DIR : case FSE_CREATE_FILE : etype = "Create"; break; case FSE_DELETE : etype = "Delete"; break; case FSE_RENAME : etype = "Move"; break; case FSE_CONTENT_MODIFIED : etype = "Modify"; break; } if (etype) { if (!path) { printlogf(L, "Error", "Internal fail, fsevents, no path."); exit(-1); } if (isdir < 0) { printlogf(L, "Error", "Internal fail, fsevents, neither dir nor file."); exit(-1); } load_runner_func(L, "fsEventsEvent"); lua_pushstring(L, etype); lua_pushboolean(L, isdir); l_now(L); #ifdef LSYNCD_TARGET_APPLE if (!strncmp(path, INOTIFY_PREFIX, strlen(INOTIFY_PREFIX ))) { path += strlen(INOTIFY_PREFIX); } #endif lua_pushstring(L, path); if (trg) { lua_pushstring(L, trg); } else { lua_pushnil(L); } if (lua_pcall(L, 5, 0, -7)) { exit(-1); // ERRNO } lua_pop(L, 1); } } /** * Called when fsevents has something to read */ static void fsevents_ready(lua_State *L, struct observance *obs) { if (obs->fd != fsevents_fd) { logstring("Error", "Internal, fsevents_fd != ob->fd"); exit(-1); // ERRNO } ptrdiff_t len = read (fsevents_fd, readbuf, readbuf_size); int err = errno; if (len == 0) { return; } if (len < 0) { if (err == EAGAIN) { /* nothing more */ return; } else { printlogf(L, "Error", "Read fail on fsevents"); exit(-1); // ERRNO } } { int off = 0; while (off < len && !hup && !term) { /* deals with alignment issues on 64 bit by copying data bit by bit */ struct kfs_event* event = (struct kfs_event *) eventbuf; event->type = *(int32_t*)(readbuf+off); off += sizeof(int32_t); event->pid = *(pid_t*)(readbuf+off); off += sizeof(pid_t); /* arguments */ int whichArg = 0; int eventbufOff = sizeof(struct kfs_event); size_t ptrSize = sizeof(void*); if ((eventbufOff % ptrSize) != 0) { eventbufOff += ptrSize-(eventbufOff%ptrSize); } while (off < len && whichArg < FSE_MAX_ARGS) { /* assign argument pointer to eventbuf based on known current offset into eventbuf */ uint16_t argLen = 0; event->args[whichArg] = (struct kfs_event_arg *) (eventbuf + eventbufOff); /* copy type */ uint16_t argType = *(uint16_t*)(readbuf + off); event->args[whichArg]->type = argType; off += sizeof(uint16_t); if (argType == FSE_ARG_DONE) { /* done */ break; } else { /* copy data length */ argLen = *(uint16_t *)(readbuf + off); event->args[whichArg]->len = argLen; off += sizeof(uint16_t); /* copy data */ memcpy(&(event->args[whichArg]->data), readbuf + off, argLen); off += argLen; } /* makes sure alignment is correct for 64 bit systems */ size_t argStructLen = sizeof(uint16_t) + sizeof(uint16_t); if ((argStructLen % ptrSize) != 0) { argStructLen += ptrSize-(argStructLen % ptrSize); } argStructLen += argLen; if ((argStructLen % ptrSize) != 0) { argStructLen += ptrSize-(argStructLen % ptrSize); } eventbufOff += argStructLen; whichArg++; } handle_event(L, event, len); } } } /** * Called to close/tidy fsevents */ static void fsevents_tidy(struct observance *obs) { if (obs->fd != fsevents_fd) { logstring("Error", "Internal, fsevents_fd != ob->fd"); exit(-1); // ERRNO } close(fsevents_fd); free(readbuf); readbuf = NULL; free(eventbuf); eventbuf = NULL; } /** * opens and initalizes fsevents. */ extern void open_fsevents(lua_State *L) { int8_t event_list[] = { // action to take for each event FSE_REPORT, // FSE_CREATE_FILE FSE_REPORT, // FSE_DELETE FSE_REPORT, // FSE_STAT_CHANGED FSE_REPORT, // FSE_RENAME FSE_REPORT, // FSE_CONTENT_MODIFIED FSE_REPORT, // FSE_EXCHANGE FSE_REPORT, // FSE_FINDER_INFO_CHANGED FSE_REPORT, // FSE_CREATE_DIR FSE_REPORT, // FSE_CHOWN FSE_REPORT, // FSE_XATTR_MODIFIED FSE_REPORT, // FSE_XATTR_REMOVED }; struct fsevent_clone_args fca = { .event_list = (int8_t *) event_list, .num_events = sizeof(event_list)/sizeof(int8_t), .event_queue_depth = EVENT_QUEUE_SIZE, .fd = &fsevents_fd, }; int fd = open(DEV_FSEVENTS, O_RDONLY); int err = errno; printlogf(L, "Warn", "Using /dev/fsevents which is considered an OSX internal interface."); printlogf(L, "Warn", "Functionality might break across OSX versions (This is for 10.5.X)"); printlogf(L, "Warn", "A hanging Lsyncd might cause Spotlight/Timemachine doing extra work."); if (fd < 0) { printlogf(L, "Error", "Cannot access %s monitor! (%d:%s)", DEV_FSEVENTS, err, strerror(err)); exit(-1); // ERRNO } if (ioctl(fd, FSEVENTS_CLONE, (char *)&fca) < 0) { printlogf(L, "Error", "Cannot control %s monitor! (%d:%s)", DEV_FSEVENTS, errno, strerror(errno)); exit(-1); // ERRNO } if (readbuf) { logstring("Error", "internal fail, inotify readbuf!=NULL in open_inotify()") exit(-1); // ERRNO } readbuf = s_malloc(readbuf_size); eventbuf = s_malloc(eventbuf_size); // fd has been cloned, closes access fd close(fd); close_exec_fd(fsevents_fd); non_block_fd(fsevents_fd); observe_fd(fsevents_fd, fsevents_ready, NULL, fsevents_tidy, NULL); }