lsyncd/inotify.c

374 lines
8.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* inotify.c from Lsyncd - Live (Mirror) Syncing Demon
*
* License: GPLv2 (see COPYING) or any later version
*
* Authors: Axel Kittenberger <axkibe@gmail.com>
*
* -----------------------------------------------------------------------
*
* Event interface for Lsyncd to Linux´ inotify.
*/
#include "lsyncd.h"
#ifndef HAVE_SYS_INOTIFY_H
# error Missing <sys/inotify.h>; supply kernel-headers and rerun configure.
#endif
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/inotify.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
/*-----------------------------------------------------------------------------
* Event types.
*/
static const char * ATTRIB = "Attrib";
static const char * MODIFY = "Modify";
static const char * CREATE = "Create";
static const char * DELETE = "Delete";
static const char * MOVE = "Move";
/**
* The inotify file descriptor.
*/
static int inotify_fd = -1;
/**
* TODO allow configure.
*/
static const uint32_t standard_event_mask =
IN_ATTRIB | IN_CLOSE_WRITE | IN_CREATE |
IN_DELETE | IN_DELETE_SELF | IN_MOVED_FROM |
IN_MOVED_TO | IN_DONT_FOLLOW | IN_ONLYDIR;
/**
* Adds an inotify watch
*
* @param dir (Lua stack) path to directory
* @return (Lua stack) numeric watch descriptor
*/
static int
l_addwatch(lua_State *L)
{
const char *path = luaL_checkstring(L, 1);
int wd = inotify_add_watch(inotify_fd, path, standard_event_mask);
if (wd < 0) {
printlogf(L, "Inotify", "addwatch(%s)->%d; err=%d:%s", path, wd,
errno, strerror(errno));
} else {
printlogf(L, "Inotify", "addwatch(%s)->%d", path, wd);
}
lua_pushinteger(L, wd);
return 1;
}
/**
* Removes an inotify watch
*
* @param dir (Lua stack) numeric watch descriptor
* @return nil
*/
static int
l_rmwatch(lua_State *L)
{
int wd = luaL_checkinteger(L, 1);
inotify_rm_watch(inotify_fd, wd);
printlogf(L, "Inotify", "rmwatch()<-%d", wd);
return 0;
}
/**
* Cores inotify functions.
*/
static const luaL_reg linotfylib[] = {
{"addwatch", l_addwatch },
{"rmwatch", l_rmwatch },
{NULL, NULL}
};
/**
* Buffer for MOVE_FROM events.
* Lsyncd buffers MOVE_FROM events to check if
* they are followed by MOVE_TO events with identical cookie
* then they are condensed into one move event to be sent to the
* runner
*/
static struct inotify_event * move_event_buf = NULL;
/**
* Memory allocated for move_event_buf
*/
static size_t move_event_buf_size = 0;
/**
* true if the buffer is used.
*/
static bool move_event = false;
/**
* Handles an inotify event.
*/
static void
handle_event(lua_State *L,
struct inotify_event *event)
{
const char *event_type = NULL;
/* used to execute two events in case of unmatched MOVE_FROM buffer */
struct inotify_event *after_buf = NULL;
if (event && (IN_Q_OVERFLOW & event->mask)) {
/* and overflow happened, tells the runner */
load_runner_func(L, "overflow");
if (lua_pcall(L, 0, 0, -2)) {
exit(-1); // ERRNO
}
lua_pop(L, 1);
hup = 1;
return;
}
/* cancel on ignored or resetting */
if (event && (IN_IGNORED & event->mask)) {
return;
}
if (event && event->len == 0) {
/* sometimes inotify sends such strange events,
* (e.g. when touching a dir */
return;
}
if (event == NULL) {
/* a buffered MOVE_FROM is not followed by anything,
thus it is unary */
event = move_event_buf;
event_type = "Delete";
move_event = false;
} else if (move_event &&
( !(IN_MOVED_TO & event->mask) ||
event->cookie != move_event_buf->cookie) ) {
/* there is a MOVE_FROM event in the buffer and this is not the match
* continue in this function iteration to handle the buffer instead */
logstring("Inotify", "icore, changing unary MOVE_FROM into DELETE")
after_buf = event;
event = move_event_buf;
event_type = "Delete";
move_event = false;
} else if ( move_event &&
(IN_MOVED_TO & event->mask) &&
event->cookie == move_event_buf->cookie ) {
/* this is indeed a matched move */
event_type = "Move";
move_event = false;
} else if (IN_MOVED_FROM & event->mask) {
/* just the MOVE_FROM, buffers this event, and wait if next event is
* a matching MOVED_TO of this was an unary move out of the watched
* tree. */
size_t el = sizeof(struct inotify_event) + event->len;
if (move_event_buf_size < el) {
move_event_buf_size = el;
move_event_buf = s_realloc(move_event_buf, el);
}
memcpy(move_event_buf, event, el);
move_event = true;
return;
} else if (IN_MOVED_TO & event->mask) {
/* must be an unary move-to */
event_type = CREATE;
} else if (IN_MOVED_FROM & event->mask) {
/* must be an unary move-from */
event_type = DELETE;
} else if (IN_ATTRIB & event->mask) {
/* just attrib change */
event_type = ATTRIB;
} else if (IN_CLOSE_WRITE & event->mask) {
/* closed after written something */
event_type = MODIFY;
} else if (IN_CREATE & event->mask) {
/* a new file */
event_type = CREATE;
} else if (IN_DELETE & event->mask) {
/* rm'ed */
event_type = DELETE;
} else {
logstring("Inotify", "icore, skipped some inotify event.");
return;
}
/* and hands over to runner */
load_runner_func(L, "inotifyEvent");
if (!event_type) {
logstring("Error", "Internal: unknown event in handle_event()");
exit(-1); // ERRNO
}
lua_pushstring(L, event_type);
if (event_type != MOVE) {
lua_pushnumber(L, event->wd);
} else {
lua_pushnumber(L, move_event_buf->wd);
}
lua_pushboolean(L, (event->mask & IN_ISDIR) != 0);
l_now(L);
if (event_type == MOVE) {
lua_pushstring(L, move_event_buf->name);
lua_pushnumber(L, event->wd);
lua_pushstring(L, event->name);
} else {
lua_pushstring(L, event->name);
lua_pushnil(L);
lua_pushnil(L);
}
if (lua_pcall(L, 7, 0, -9)) {
exit(-1); // ERRNO
}
lua_pop(L, 1);
/* if there is a buffered event executes it */
if (after_buf) {
logstring("Inotify", "icore, handling buffered event.");
handle_event(L, after_buf);
}
}
/**
* buffer to read inotify events into
*/
static size_t readbuf_size = 2048;
static char * readbuf = NULL;
/**
* Called by function pointer from when the inotify file descriptor
* became ready. Reads it contents and forward all received events
* to the runner.
*/
static void
inotify_ready(lua_State *L, struct observance *obs)
{
if (obs->fd != inotify_fd) {
logstring("Error", "Internal, inotify_fd != ob->fd");
exit(-1); // ERRNO
}
while(true) {
ptrdiff_t len;
int err;
do {
len = read (inotify_fd, readbuf, readbuf_size);
err = errno;
if (len < 0 && err == EINVAL) {
/* kernel > 2.6.21 indicates that way that way that
* the buffer was too small to fit a filename.
* double its size and try again. When using a lower
* kernel and a filename > 2KB appears lsyncd
* will fail. (but does a 2KB filename really happen?)
*/
readbuf_size *= 2;
readbuf = s_realloc(readbuf, readbuf_size);
continue;
}
} while(0);
if (len == 0) {
/* nothing more inotify */
break;
}
if (len < 0) {
if (err == EAGAIN) {
/* nothing more inotify */
break;
} else {
printlogf(L, "Error", "Read fail on inotify");
exit(-1); // ERRNO
}
}
{
int i = 0;
while (i < len && !hup && !term) {
struct inotify_event *event =
(struct inotify_event *) &readbuf[i];
handle_event(L, event);
i += sizeof(struct inotify_event) + event->len;
}
}
if (!move_event) {
/* give it a pause if not endangering splitting a move */
break;
}
}
/* checks if there is an unary MOVE_FROM left in the buffer */
if (move_event) {
logstring("Inotify", "icore, handling unary move from.");
handle_event(L, NULL);
}
}
/**
* registers inotify functions.
*/
extern void
register_inotify(lua_State *L)
{
lua_pushstring(L, "inotify");
luaL_register(L, "inotify", linotfylib);
}
/**
* closes inotify
*/
static void
inotify_tidy(struct observance *obs)
{
if (obs->fd != inotify_fd) {
logstring("Error", "Internal, inotify_fd != ob->fd");
exit(-1); // ERRNO
}
close(inotify_fd);
free(readbuf);
readbuf = NULL;
}
/**
* opens and initalizes inotify.
*/
extern void
open_inotify(lua_State *L)
{
if (readbuf) {
logstring("Error",
"internal fail, inotify readbuf!=NULL in open_inotify()")
exit(-1); // ERRNO
}
readbuf = s_malloc(readbuf_size);
inotify_fd = inotify_init();
if (inotify_fd < 0) {
printlogf(L, "Error",
"Cannot access inotify monitor! (%d:%s)",
errno, strerror(errno));
exit(-1); // ERRNO
}
printlogf(L, "Inotify", "inotify fd = %d", inotify_fd);
close_exec_fd(inotify_fd);
non_block_fd(inotify_fd);
observe_fd(inotify_fd, inotify_ready, NULL, inotify_tidy, NULL);
}