/* | 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" #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; /* | 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) which inotify event to react upon | "CloseWrite", "CloseWrite or Modify" | | returns (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; // checks the desired inotify reaction mode if (*imode) { if ( !strcmp( imode, "Modify" ) ) { // acts on modify instead of closeWrite mask |= IN_MODIFY; mask &= ~IN_CLOSE_WRITE; } else if ( !strcmp( imode, "CloseWrite" ) ) { // thats 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); } else { printlogf( L, "Error", "'%s' not a valid inotfiyMode.", imode ); exit(-1); } } // kernel call to create the inotify watch int wd = inotify_add_watch( inotify_fd, path, mask ); if( wd < 0 ) { if( errno == ENOSPC ) { printlogf( L, "Error", "%s\n%s", "Terminating since out of inotify watches.", "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; } /* | Lsyncd's core's inotify functions. */ static const luaL_Reg inotifylib[ ] = { { "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_mci( L, "overflow" ); if( lua_pcall( L, 0, 0, -2 ) ) exit( -1 ); 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 | IN_MODIFY) & event->mask ) { // modify, or closed after written something // the event type received depends settings.inotifyMode 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", "skipped some inotify event."); return; } // hands the event over to the runner load_mci( L, "inotifyEvent" ); if( !event_type ) { logstring( "Error", "internal failure: unknown event in handle_event()" ); exit( -1 ); } 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 ); 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 when the inotify file descriptor became ready. | Reads it contents and forwards all received events | to the runner. */ static void inotify_ready( lua_State *L, struct observance *obs ) { // sanity check if( obs->fd != inotify_fd ) { logstring( "Error", "internal failure, inotify_fd != obs->fd" ); exit( -1 ); } 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 ) { // no more inotify events break; } if (len < 0) { if( err != EAGAIN ) { printlogf( L, "Error", "Read fail on inotify" ); exit( -1 ); } // nothing more on inotify break; } { 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", "handling unary move from." ); handle_event( L, NULL ); } } /* | Registers the inotify functions. | | Leaves a table of the inotify functions on Lua stack. */ extern void register_inotify( lua_State *L ) { lua_newtable( L ); luaL_setfuncs( L, inotifylib, 0 ); } /* | Cleans up the inotify handling. */ static void inotify_tidy( struct observance *obs ) { if( obs->fd != inotify_fd ) { logstring( "Error", "internal failure: inotify_fd != ob->fd" ); exit( -1 ); } close( inotify_fd ); free( readbuf ); readbuf = NULL; } /* | Initalizes inotify handling */ extern void open_inotify( lua_State *L ) { if( readbuf ) { logstring( "Error", "internal failure, inotify readbuf != NULL in open_inotify()" ) exit(-1); } 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 ); } 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 ); }