lsyncd/inotify.c
2012-02-16 16:49:34 +01:00

399 lines
9.6 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;
/**
* Standard inotify events to listen to.
*/
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
* @param inotifyMode (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);
const char *imode = luaL_checkstring(L, 2);
uint32_t mask = standard_event_mask;
if (*imode) {
if (!strcmp(imode, "Modify")) {
// act on modify instead of closeWrite
mask |= IN_MODIFY;
mask &= ~IN_CLOSE_WRITE;
} else if (!strcmp(imode, "CloseWrite")) {
// default
} else if (!strcmp(imode, "CloseWrite or Modify")) {
// acts on modify and closeWrite
mask |= IN_MODIFY;
} else if (!strcmp(imode, "CloseWrite after Modify")) {
// might be done in future
printlogf(L, "Error", "'CloseWrite after Modify' not implemented.");
exit(-1); // ERRNO
} else {
printlogf(L, "Error", "'%s' not a valid inotfiyMode.", imode);
exit(-1); // ERRNO
}
}
int wd = inotify_add_watch(inotify_fd, path, mask);
if (wd < 0) {
if (errno == ENOSPC) {
printlogf(L, "Error", "Terminating since out of inotify watches.");
printlogf(L, "Error", "Consider increasing /proc/sys/fs/inotify/max_user_watches");
exit(-1); // ERRNO.
}
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_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);
}
} while(len < 0 && err == EINVAL);
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);
}