lsyncd/fsevents.c

398 lines
9.0 KiB
C
Raw Normal View History

/** fsevents.c from Lsyncd - Live (Mirror) Syncing Demon
*
* License: GPLv2 (see COPYING) or any later version
*
* Authors: Axel Kittenberger <axkibe@gmail.com>
*
* -----------------------------------------------------------------------
*
* 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 <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "bsd/sys/fsevents.h"
2010-11-26 17:01:18 +00:00
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
/* 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 */
2010-12-07 15:42:46 +00:00
struct kfs_event_arg args[];
/* struct kfs_event_arg args[KFS_NUM_ARGS]; */
};
/**
* fsevents (cloned) filedescriptor
*/
static int fsevents_fd = -1;
2010-12-07 15:42:46 +00:00
/* event names */
2010-12-10 13:28:10 +00:00
/*static const char *eventNames[FSE_MAX_EVENTS] = {
2010-12-01 16:12:15 +00:00
"CREATE_FILE",
"DELETE",
"STAT_CHANGED",
"RENAME",
"CONTENT_MODIFIED",
"EXCHANGE",
"FINDER_INFO_CHANGED",
"CREATE_DIR",
"CHOWN",
"XATTR_MODIFIED",
"XATTR_REMOVED",
2010-12-10 13:28:10 +00:00
};*/
2010-12-07 15:42:46 +00:00
/* argument names*/
2010-12-09 15:12:08 +00:00
/*static const char *argNames[] = {
2010-12-07 15:42:46 +00:00
"UNKNOWN",
"VNODE",
"STRING",
"PATH",
"INT32",
"INT64",
"RAW",
"INO",
"UID",
"DEV",
"MODE",
"GID",
"FINFO",
2010-12-09 15:12:08 +00:00
};*/
2010-12-07 15:42:46 +00:00
/**
* The read buffer
*/
static size_t const readbuf_size = 131072;
static char * readbuf = NULL;
2010-12-07 15:42:46 +00:00
/**
* Handles one fsevents event
* @returns the size of the event
*/
static ssize_t
handle_event(lua_State *L, struct kfs_event *event, ssize_t mlen)
{
/* the len of the event */
ssize_t len = sizeof(int32_t) + sizeof(pid_t);
2010-12-10 13:28:10 +00:00
int32_t atype;
2010-12-07 15:42:46 +00:00
const char *path = NULL;
2010-12-09 15:12:08 +00:00
const char *trg = NULL;
2010-12-10 13:28:10 +00:00
const char *etype = NULL;
int isdir = -1;
2010-12-07 15:42:46 +00:00
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;
len += sizeof(u_int16_t);
return len;
}
{
2010-12-10 13:28:10 +00:00
atype = event->type & FSE_TYPE_MASK;
/*uint32_t aflags = FSE_GET_FLAGS(event->type);*/
2010-12-07 15:42:46 +00:00
if ((atype < FSE_MAX_EVENTS) && (atype >= -1)) {
2010-12-10 13:28:10 +00:00
/*printlogf(L, "Fsevents", "got event %s", eventNames[atype]);
2010-12-07 15:42:46 +00:00
if (aflags & FSE_COMBINED_EVENTS) {
logstring("Fsevents", "combined events");
}
if (aflags & FSE_CONTAINS_DROPPED_EVENTS) {
logstring("Fsevents", "contains dropped events");
2010-12-10 13:28:10 +00:00
}*/
2010-12-07 15:42:46 +00:00
} else {
printlogf(L, "Error", "unknown event(%d) in fsevents.",
atype);
exit(-1); // ERRNO
}
}
{
2010-12-09 15:12:08 +00:00
/* assigns the expected arguments */
2010-12-07 15:42:46 +00:00
struct kfs_event_arg *arg = event->args;
while (len < mlen) {
int eoff;
if (arg->type == FSE_ARG_DONE) {
len += sizeof(u_int16_t);
break;
}
switch (arg->type) {
case FSE_ARG_STRING :
2010-12-10 13:28:10 +00:00
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 :
2010-12-10 16:12:15 +00:00
case FSE_CHOWN :
case FSE_CONTENT_MODIFIED :
case FSE_CREATE_FILE :
case FSE_CREATE_DIR :
2010-12-10 13:28:10 +00:00
case FSE_DELETE :
case FSE_STAT_CHANGED :
isdir = arg->data.mode & S_IFDIR ? 1 : 0;
break;
2010-12-07 15:42:46 +00:00
}
break;
}
eoff = sizeof(arg->type) + sizeof(arg->len) + arg->len;
len += eoff;
arg = (struct kfs_event_arg *) ((char *) arg + eoff);
}
}
2010-12-10 13:28:10 +00:00
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;
2010-12-09 15:12:08 +00:00
}
2010-12-10 13:28:10 +00:00
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);
lua_pushstring(L, path);
if (trg) {
2010-12-10 16:12:15 +00:00
lua_pushstring(L, trg);
2010-12-10 13:28:10 +00:00
} else {
lua_pushnil(L);
}
if (lua_pcall(L, 5, 0, -7)) {
2010-12-15 14:17:27 +00:00
exit(-1); // ERRNO
}
lua_pop(L, 1);
2010-12-07 15:42:46 +00:00
}
return len;
}
/**
* 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
}
2010-12-10 15:30:45 +00:00
{
2010-12-15 14:20:34 +00:00
ptrdiff_t len = read (fsevents_fd, readbuf, readbuf_size);
int err = errno;
if (len == 0) {
2010-12-10 15:30:45 +00:00
return;
}
if (len < 0) {
if (err == EAGAIN) {
2010-12-07 15:42:46 +00:00
/* nothing more */
2010-12-10 15:30:45 +00:00
return;
} else {
printlogf(L, "Error", "Read fail on fsevents");
exit(-1); // ERRNO
}
}
{
int off = 0;
while (off < len && !hup && !term) {
struct kfs_event *event = (struct kfs_event *) &readbuf[off];
2010-12-07 15:42:46 +00:00
off += handle_event(L, event, len);
2010-12-01 16:12:15 +00:00
}
}
}
}
/**
* 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);
2010-12-01 16:12:15 +00:00
free(readbuf);
readbuf = NULL;
}
/**
* opens and initalizes fsevents.
*/
extern void
2010-11-29 17:45:04 +00:00
open_fsevents(lua_State *L)
{
int8_t event_list[] = { // action to take for each event
2010-12-15 14:20:34 +00:00
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;
2010-12-10 13:28:10 +00:00
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)");
2010-12-10 13:28:10 +00:00
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
}
2010-12-15 14:20:34 +00:00
if (ioctl(fd, FSEVENTS_CLONE, (char *)&fca) < 0) {
printlogf(L, "Error",
"Cannot control %s monitor! (%d:%s)",
DEV_FSEVENTS, errno, strerror(errno));
2010-12-15 14:20:34 +00:00
exit(-1); // ERRNO
}
2010-12-01 16:12:15 +00:00
if (readbuf) {
logstring("Error",
"internal fail, inotify readbuf!=NULL in open_inotify()")
exit(-1); // ERRNO
}
readbuf = s_malloc(readbuf_size);
/* fd has been cloned, closes access fd */
2010-12-15 14:20:34 +00:00
close(fd);
close_exec_fd(fsevents_fd);
non_block_fd(fsevents_fd);
observe_fd(fsevents_fd, fsevents_ready, NULL, fsevents_tidy, NULL);
}