/** * inotify.c from Lsyncd - Live (Mirror) Syncing Demon * * License: GPLv2 (see COPYING) or any later version * * Authors: Axel Kittenberger * * ----------------------------------------------------------------------- * * Event interface for Lsyncd to Linux´ inotify. */ #include "lsyncd.h" #ifndef HAVE_SYS_INOTIFY_H # error Missing ; supply kernel-headers and rerun configure. #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*----------------------------------------------------------------------------- * 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); }