3b86e22011-01-21Arne Goedeke /* -*- c -*- || This file is part of Pike. For copyright information see COPYRIGHT. || Pike is distributed under GPL, LGPL and MPL. See the file COPYING || for more information. */ /* Module for the linux inotify api. * * Ref: inotify(7) * */
92d7832017-08-30Tobias S. Josefowitz #include "inotify_config.h"
3b86e22011-01-21Arne Goedeke  #include "global.h" #include "interpret.h" #include "module.h" #include "program.h" #include "stralloc.h" #include "svalue.h" #include "object.h" #include "pike_types.h" #include "builtin_functions.h"
4d9db42013-02-24Arne Goedeke #include "fdlib.h"
d2be1d2015-10-07Henrik Grubbström (Grubba) #include "fd_control.h"
4d9db42013-02-24Arne Goedeke #include "pike_threadlib.h"
3afb032011-03-25Henrik Grubbström (Grubba) #ifdef HAVE_SYS_INOTIFY_H #include <sys/inotify.h> #include <errno.h>
7a5c292014-08-22Martin Nilsson /* Autoconf helpfully litters generated files with colliding defines. */ #undef PACKAGE_BUGREPORT #undef PACKAGE_NAME #undef PACKAGE_STRING #undef PACKAGE_TARNAME #undef PACKAGE_URL #undef PACKAGE_VERSION #include "modules/_Stdio/file_machine.h" #include "modules/_Stdio/file.h"
3b86e22011-01-21Arne Goedeke DECLARATIONS /*! @module System */ /*! @module Inotify *! This module implements an API to linux inotify. It is available on all *! kernels from version 2.6.13 onwards. Inotify offers fast and scalable file *! notifications. */ /*! @decl constant IN_ALL_EVENTS *! This is a derived constant that is not part of the standard inotify API. It *! is the union of all other constants. *! *! @decl constant IN_ACCESS *! @decl constant IN_ATTRIB *! @decl constant IN_CLOSE *! @decl constant IN_CLOSE_WRITE *! @decl constant IN_CLOSE_NOWRITE *! @decl constant IN_CREATE *! @decl constant IN_DELETE *! @decl constant IN_DELETE_SELF *! @decl constant IN_MODIFY *! @decl constant IN_MOVE_SELF *! @decl constant IN_MOVED_FROM *! @decl constant IN_MOVED_TO *! @decl constant IN_OPEN *! @decl constant IN_MOVE *! @decl constant IN_CLOSE *! @decl constant IN_DONT_FOLLOW *! @decl constant IN_MASK_ADD *! @decl constant IN_ONESHOT *! @decl constant IN_ONLYDIR *! @decl constant IN_IGNORED *! @decl constant IN_ISDIR *! @decl constant IN_Q_OVERFLOW *! @decl constant IN_UNMOUNT *! Please have a look at the inotify(7) manpage for information about *! these constants. *! @note *! Some constants may not be available when the module has been *! compiled on a machine with linux kernel before 2.6.15. See the *! manpage for more details. */
a50d462011-01-25Henrik Grubbström (Grubba) /*! @decl array(string|int) parse_event(string data)
3b86e22011-01-21Arne Goedeke  *! Parses one inotify_event struct from @expr{data@}. *! @returns *! Returns an array consisting of
a50d462011-01-25Henrik Grubbström (Grubba)  *! @array *! @elem int 0 *! The watch descriptor returned by @[_Instance()->add_watch()] *! when the watch for this file was added. *! @elem int 1
3b86e22011-01-21Arne Goedeke  *! An integer that describes the event that occured. See *! the inotify manpage for a list of possible events and *! their numerical identifiers.
a50d462011-01-25Henrik Grubbström (Grubba)  *! @elem int 2
3b86e22011-01-21Arne Goedeke  *! An integer cookie that can be used to group together *! different events that were triggered by moving a file *! from one location to another.
a50d462011-01-25Henrik Grubbström (Grubba)  *! @elem string 3
3b86e22011-01-21Arne Goedeke  *! The name of the file. This will only be present if the *! event happened to a file in a directory that was *! watched, e.g. with @[System.Inotify.IN_CREATE]. *! Otherwise this will be 0.
a50d462011-01-25Henrik Grubbström (Grubba)  *! @elem int 4 *! The length of the data that has been parsed. If the @[data] *! string contains more than one inotify event, this parse *! function needs to be called again with the remainder as
3b86e22011-01-21Arne Goedeke  *! an argument.
a50d462011-01-25Henrik Grubbström (Grubba)  *! @endarray
3b86e22011-01-21Arne Goedeke  */
a50d462011-01-25Henrik Grubbström (Grubba) PIKEFUN array(string|int) parse_event(string data) {
aeb1b02014-03-11Arne Goedeke  struct inotify_event event;
3b86e22011-01-21Arne Goedeke  size_t len;
aeb1b02014-03-11Arne Goedeke  const char * d;
3b86e22011-01-21Arne Goedeke  if (data->size_shift) Pike_error("Inotify events should not be wide.\n"); if ((size_t)data->len < sizeof(struct inotify_event)) Pike_error("Malformed data.\n");
aeb1b02014-03-11Arne Goedeke  d = data->str;
3b86e22011-01-21Arne Goedeke 
aeb1b02014-03-11Arne Goedeke  memcpy(&event, d, sizeof(struct inotify_event));
3b86e22011-01-21Arne Goedeke 
aeb1b02014-03-11Arne Goedeke  push_int(event.wd); push_int((int)event.mask); push_int((int)event.cookie);
3b86e22011-01-21Arne Goedeke 
aeb1b02014-03-11Arne Goedeke  if (event.len) { if (event.len > data->len - sizeof(struct inotify_event)) Pike_error("Data missing. Got %u expected %u bytes.\n", (unsigned)(data->len - sizeof(struct inotify_event)), event.len); d += sizeof(struct inotify_event); push_string(make_shared_binary_string(d, strnlen(d, event.len))); } else { push_int(0); }
3b86e22011-01-21Arne Goedeke 
aeb1b02014-03-11Arne Goedeke  push_int((int)(event.len + sizeof(struct inotify_event)));
3b86e22011-01-21Arne Goedeke  f_aggregate(5);
079d332013-05-28Martin Nilsson  stack_swap(); pop_stack();
3b86e22011-01-21Arne Goedeke } /*! @class _Instance
a50d462011-01-25Henrik Grubbström (Grubba)  *! Simple wrapper class that gives direct access to the @tt{inotify(7)@}
3b86e22011-01-21Arne Goedeke  *! interface. On create an inotify instance is initiated by calling
a50d462011-01-25Henrik Grubbström (Grubba)  *! @tt{inotify_init(2)@}. Every object of this class has its own inotify
3b86e22011-01-21Arne Goedeke  *! file descriptor. Use this class only if you want direct access to *! the file descriptor to read from it manually. For a more user *! friendly inferface use @[System.Inotify.Instance].
a50d462011-01-25Henrik Grubbström (Grubba)  *! *! @seealso *! @[System.Inotify.Instance]
3b86e22011-01-21Arne Goedeke  */ PIKECLASS _Instance {
d2be1d2015-10-07Henrik Grubbström (Grubba)  CVAR struct fd_callback_box box; CVAR struct string_builder buf; /*! @decl private function(int, int, int, string:void) event_callback *! *! Callback function that is called when an event is triggered. *! *! @seealso *! @[set_event_callback()], @[query_event_callback()] */ PIKEVAR function(int, int, int, string:void) event_callback flags ID_PRIVATE;
27d1b52015-10-08Henrik Grubbström (Grubba)  static int event_callback_fun_num; EXTRA { /* NB: Inlined isidentifier() due to it not being exported. */ event_callback_fun_num = really_low_find_shared_string_identifier(MK_STRING("event_callback"), Pike_compiler->new_program, SEE_PROTECTED|SEE_PRIVATE); if (event_callback_fun_num == -1) { Pike_fatal("Inotify: Event callback variable not mapped!\n"); } /* We don't want to count references to ourselves. */ ID_FROM_INT(Pike_compiler->new_program, event_callback_fun_num)-> identifier_flags |= IDENTIFIER_NO_THIS_REF; }
3b86e22011-01-21Arne Goedeke 
046e082017-06-14Henrik Grubbström (Grubba)  static void check_schedule_poll(void) { if (THIS->box.events & PIKE_BIT_FD_READ) { /* Nonblocking mode. */ if (THIS->buf.s->len >= (ptrdiff_t)sizeof(struct inotify_event)) { /* There's stuff waiting in the buffer, so schedule * an immediate call_out of poll() to handle the data. */ ref_push_function(Pike_fp->current_object, f_Inotify_cq__Instance_poll_fun_num + Pike_fp->context->identifier_level); push_int(0); safe_apply(get_backend_obj(THIS->box.backend), "call_out", 2); pop_stack(); } } }
3b86e22011-01-21Arne Goedeke  /*! @decl int add_watch(string file, int mask) *! Add a watch for a certain file or directory and specific events. *! Adding more than one watch for one file will overwrite the *! previous watch unless @[System.Inotify.IN_MASK_ADD] is contained *! in the mask. *! @param path *! Path of the file or directory. *! @param mask *! Integer mask specifying the event type. This can be a *! combination of different event types using bitwise OR. *! See the inotify manpage for possible values and their *! description. The values defined by the inotify header *! file are exported by @[System.Inotify] as constants *! using the same names (e.g. @[System.Inotify.IN_CREATE]).
a50d462011-01-25Henrik Grubbström (Grubba)  *! @returns *! Returns a watch descriptor.
3b86e22011-01-21Arne Goedeke  *! @note *! Subdirectories are not watched. If you want to watch
a50d462011-01-25Henrik Grubbström (Grubba)  *! subdirectories as well, you need to add watches for *! them individually.
edb8fa2016-03-08Henrik Grubbström (Grubba)  *! @note *! At creation of a watch for a directory simulated *! @[IN_CREATE]-events with cookie @expr{0x7fffffff@} will *! be added for the initial contents of the directory. *! This is to reduce the risk of losing state changes *! due to races. Note that is is not known whether these *! paths are in flux or not. Note also that there may *! be multiple notifications for content that is created *! at the moment the watch is created.
a50d462011-01-25Henrik Grubbström (Grubba)  *! *! @seealso *! @[rm_watch()], @[parse_event()]
3b86e22011-01-21Arne Goedeke  */ PIKEFUN int add_watch(string file, int mask) {
edb8fa2016-03-08Henrik Grubbström (Grubba)  INT32 wd;
3b86e22011-01-21Arne Goedeke  if (file->size_shift) Pike_error("Widestring filenames are not allowed.\n");
edb8fa2016-03-08Henrik Grubbström (Grubba)  wd = inotify_add_watch(THIS->box.fd, file->str, mask);
3b86e22011-01-21Arne Goedeke 
edb8fa2016-03-08Henrik Grubbström (Grubba)  if (wd == -1)
3b86e22011-01-21Arne Goedeke  Pike_error("inotify_add_watch failed: %s\n", strerror(errno));
edb8fa2016-03-08Henrik Grubbström (Grubba)  if (mask & IN_CREATE) { DIR *dir = opendir(file->str); if (dir) { struct dirent *dirent; /* Add any paths already present to the input buffer. */ while ((dirent = readdir(dir))) { struct inotify_event ev; int pad = 0; if ((dirent->d_name[0] == '.') && (!dirent->d_name[1] || ((dirent->d_name[1] == '.') && !dirent->d_name[2]))) { /* Filter "." and ".." */ continue; } ev.wd = wd;
f0cc952016-03-09Henrik Grubbström (Grubba)  ev.mask = IN_CREATE; /* FIXME: Use custom code? */ ev.cookie = (uint32_t)0x7fffffff; /* Special marker. */
edb8fa2016-03-08Henrik Grubbström (Grubba)  ev.len = strlen(dirent->d_name) + 1; if (ev.len & 0x07) { pad = 0x08 - (ev.len & 0x07); ev.len += pad; }
7ff2672017-07-19Bill Welliver #ifdef HAVE_DIRENT_D_TYPE
edb8fa2016-03-08Henrik Grubbström (Grubba)  if (dirent->d_type == DT_DIR) { ev.mask |= IN_ISDIR; } /* FIXME: Handle DT_UNKNOWN. */
7ff2672017-07-19Bill Welliver #endif /* HAVE_DIRENT_T_TYPE */
edb8fa2016-03-08Henrik Grubbström (Grubba)  string_build_mkspace(&THIS->buf, sizeof(ev) + ev.len, 0); string_builder_binary_strcat0(&THIS->buf, (p_wchar0 *)&ev, sizeof(ev)); string_builder_strcat(&THIS->buf, dirent->d_name); string_builder_fill(&THIS->buf, pad+1, MKPCHARP("\0\0\0\0\0\0\0\0", 0), 8, 0); } closedir(dir);
046e082017-06-14Henrik Grubbström (Grubba)  /* Wake up the backend if we've added stuff to the buffer. */ check_schedule_poll();
edb8fa2016-03-08Henrik Grubbström (Grubba)  } } RETURN wd;
3b86e22011-01-21Arne Goedeke  }
4d9db42013-02-24Arne Goedeke 
7b34f52015-10-14Henrik Grubbström (Grubba)  /*! @decl int query_fd()
3b86e22011-01-21Arne Goedeke  *! @returns
4d9db42013-02-24Arne Goedeke  *! Returns the file descriptor associated with this inotify instance.
3b86e22011-01-21Arne Goedeke  */
7b34f52015-10-14Henrik Grubbström (Grubba)  PIKEFUN int query_fd() {
d2be1d2015-10-07Henrik Grubbström (Grubba)  push_int(THIS->box.fd);
4d9db42013-02-24Arne Goedeke  }
3b86e22011-01-21Arne Goedeke  /*! @decl int rm_watch(int wd) *! Remove a watch. *! @param wd *! The watch descriptor that was returned by @[add_watch()]. */ PIKEFUN void rm_watch(int wd) { INT32 err;
d2be1d2015-10-07Henrik Grubbström (Grubba)  err = inotify_rm_watch(THIS->box.fd, wd);
3b86e22011-01-21Arne Goedeke 
5d0bfd2015-10-07Henrik Grubbström (Grubba)  if (!err || (errno == EINVAL)) { /* NB: EINVAL typically means that the watch descriptor is * invalid, and is often triggered by the descriptor * having been automatically removed. */
3b86e22011-01-21Arne Goedeke  return; }
5d0bfd2015-10-07Henrik Grubbström (Grubba)  if (errno == EBADF) {
3b86e22011-01-21Arne Goedeke  Pike_error("Oups. I feel funny inside.\n"); }
5d0bfd2015-10-07Henrik Grubbström (Grubba)  Pike_error("Unexpected error: %d.\n", errno);
3b86e22011-01-21Arne Goedeke  }
d2be1d2015-10-07Henrik Grubbström (Grubba)  /*! @decl void set_event_callback(function(int, int, int, string:void) cb) *! *! Set the @[event_callback]. *! *! @seealso *! @[get_event_callback()], @[event_callback], @[poll()] */ PIKEFUN void set_event_callback(function(int, int, int, string:void) cb) {
27d1b52015-10-08Henrik Grubbström (Grubba)  /* Use object indexing to handle circular reference counting correctly. */ object_low_set_index(Pike_fp->current_object, Pike_fp->context->identifier_level + event_callback_fun_num, cb);
d2be1d2015-10-07Henrik Grubbström (Grubba)  }
3b86e22011-01-21Arne Goedeke 
d2be1d2015-10-07Henrik Grubbström (Grubba)  /*! @decl function(int, int, int, string:void) get_event_callback() *! *! Get the current @[event_callback]. *! *! @seealso *! @[set_event_callback()], @[event_callback], @[poll()] */ PIKEFUN function(int, int, int, string:void) get_event_callback() { push_svalue(&THIS->event_callback); } /*! @decl void set_backend(Pike.Backend backend) *! *! Set the backend used for callbacks. *! *! @seealso *! @[set_event_callback()], @[set_nonblocking()], @[poll()] */ PIKEFUN void set_backend(object backend_object) { struct Backend_struct *backend = get_storage(backend_object, Backend_program); if (!backend)
f982742016-01-26Martin Nilsson  SIMPLE_ARG_TYPE_ERROR("set_backend", 1, "Pike.Backend");
d2be1d2015-10-07Henrik Grubbström (Grubba)  change_backend_for_box(&THIS->box, backend); } /*! @decl void set_nonblocking() *! *! Enable backend callback mode. *! *! The configured backend will call @[poll()] automatically *! as soon as there are events pending. *! *! @seealso *! @[set_blocking()], @[poll()] */ PIKEFUN void set_nonblocking() { set_fd_callback_events(&THIS->box, PIKE_BIT_FD_READ, 0); } /*! @decl void set_blocking() *! *! Disable backend callback mode. *! *! The configured backend will stop calling @[poll()], so *! @[poll()] will need to be called by hand. *! *! @seealso *! @[set_blocking()], @[poll()] */ PIKEFUN void set_blocking() { set_fd_callback_events(&THIS->box, 0, 0); } /*! @decl void poll() *! *! Check for any pending events. *! *! Any pending events will be read and parsed, and @[event_callback] will *! be called once for each event. The arguments to the @[event_callback] *! will be: *! @array *! @elem int 1 *! The watch descriptor that was triggered. *! @elem int 2 *! The event that was triggerend (one of @tt{IN_*@}). *! @elem int 3 *! An integer cookie used to identify grouped events. *! @elem string 4 *! The name of the path segment (if any). *! @endarray *! *! @note *! This function is called by the backend when there are events *! pending. *! *! @seealso *! @[set_event_callback()] */ PIKEFUN void poll() { ptrdiff_t off = 0; ptrdiff_t bytes; do { string_build_mkspace(&THIS->buf, 1024, 0); do { bytes = read(THIS->box.fd, THIS->buf.s->str + THIS->buf.s->len, 1024); } while ((bytes == -1) && (errno == EINTR)); if (bytes > 0) { THIS->buf.s->len += bytes; } while (THIS->buf.s->len >= (off + (ptrdiff_t)sizeof(struct inotify_event))) {
3b4a2a2015-10-08Henrik Grubbström (Grubba)  /* NB: Assumes that e->len has a valid alignment * for the struct. This could cause problems * on non-x86 systems and injected data. */
d2be1d2015-10-07Henrik Grubbström (Grubba)  struct inotify_event *e = (void *)(THIS->buf.s->str + off); const char *path = (char *)(e + 1);
3b4a2a2015-10-08Henrik Grubbström (Grubba)  ptrdiff_t new_off = off + sizeof(struct inotify_event) + e->len; if (new_off > THIS->buf.s->len) {
d2be1d2015-10-07Henrik Grubbström (Grubba)  /* Not enough data for the filename yet. */ break; }
3b4a2a2015-10-08Henrik Grubbström (Grubba)  off = new_off;
d2be1d2015-10-07Henrik Grubbström (Grubba)  push_int(e->wd); push_int(e->mask); push_int(e->cookie); push_string(make_shared_binary_string(path, strnlen(path, e->len))); safe_apply_svalue(&THIS->event_callback, 4, 1); pop_stack(); } if (off == THIS->buf.s->len) { /* End of data reached. Restart at beginning of buffer. */ off = THIS->buf.s->len = 0; } } while (bytes > 0); if (off) { /* Unlikely, but... */ THIS->buf.s->len -= off; memmove(THIS->buf.s->str, THIS->buf.s->str + off, THIS->buf.s->len); } } static int got_inotify_event(struct fd_callback_box *box, int UNUSED(event)) { apply(box->ref_obj, "poll", 0); pop_stack(); return 0; } INIT { THIS->box.fd = -1; init_string_builder_alloc(&THIS->buf, 1024, 0); INIT_FD_CALLBACK_BOX(&THIS->box, default_backend, Pike_fp->current_object, inotify_init(), 0, got_inotify_event, 0); if (THIS->box.fd == -1) { switch (errno) { case EMFILE:
3b86e22011-01-21Arne Goedeke  Pike_error("User limit on inotify instances reached.\n");
d2be1d2015-10-07Henrik Grubbström (Grubba)  break; case ENFILE:
3b86e22011-01-21Arne Goedeke  Pike_error("User limit on file descriptors reached.\n");
d2be1d2015-10-07Henrik Grubbström (Grubba)  break; case ENOMEM:
4d9db42013-02-24Arne Goedeke  Pike_error("No free kernel memory available.\n");
d2be1d2015-10-07Henrik Grubbström (Grubba)  break; default: Pike_error("Failed to initialize.\n"); break; }
3b86e22011-01-21Arne Goedeke  }
d2be1d2015-10-07Henrik Grubbström (Grubba)  set_nonblocking(THIS->box.fd, 1);
3b86e22011-01-21Arne Goedeke  } EXIT {
d2be1d2015-10-07Henrik Grubbström (Grubba)  if (THIS->box.fd != -1) { int fd = THIS->box.fd; set_fd_callback_events(&THIS->box, 0, 0); change_fd_for_box(&THIS->box, -1); unhook_fd_callback_box(&THIS->box);
4d9db42013-02-24Arne Goedeke  /*
d2be1d2015-10-07Henrik Grubbström (Grubba)  * Currently (linux 3.4.9) closing an inotify fd takes * on the order of 100 ms
4d9db42013-02-24Arne Goedeke  */ THREADS_ALLOW();
d2be1d2015-10-07Henrik Grubbström (Grubba)  while ((close(fd) == -1) && (errno == EINTR)) ;
4d9db42013-02-24Arne Goedeke  THREADS_DISALLOW(); }
d2be1d2015-10-07Henrik Grubbström (Grubba)  free_string_builder(&THIS->buf);
3b86e22011-01-21Arne Goedeke  } } /*! @endclass */ #define ADD_ICONST(name) do { \ add_integer_constant(#name, name, 0); \ } while(0);
5dc6782011-03-25Henrik Grubbström (Grubba) #else /* !HAVE_SYS_INOTIFY_H */ #define ADD_ICONST(name) #endif /* HAVE_SYS_INOTIFY_H */
3b86e22011-01-21Arne Goedeke  PIKE_MODULE_INIT { ADD_ICONST(IN_ACCESS); ADD_ICONST(IN_ALL_EVENTS); ADD_ICONST(IN_ATTRIB); ADD_ICONST(IN_CLOSE_WRITE); ADD_ICONST(IN_CLOSE_NOWRITE); ADD_ICONST(IN_CREATE); ADD_ICONST(IN_DELETE); ADD_ICONST(IN_DELETE_SELF); ADD_ICONST(IN_MODIFY); ADD_ICONST(IN_MOVE_SELF); ADD_ICONST(IN_MOVED_FROM); ADD_ICONST(IN_MOVED_TO); ADD_ICONST(IN_OPEN); ADD_ICONST(IN_MOVE); ADD_ICONST(IN_CLOSE); /* some of these came with 2.6.15 linux and 2.5 glibc */ #ifdef IN_DONT_FOLLOW ADD_ICONST(IN_DONT_FOLLOW); #endif #ifdef IN_MASK_ADD ADD_ICONST(IN_MASK_ADD); #endif ADD_ICONST(IN_ONESHOT); #ifdef IN_ONLYDIR ADD_ICONST(IN_ONLYDIR); #endif ADD_ICONST(IN_IGNORED); ADD_ICONST(IN_ISDIR); ADD_ICONST(IN_Q_OVERFLOW); ADD_ICONST(IN_UNMOUNT); INIT } PIKE_MODULE_EXIT { EXIT } /*! @endmodule */ /*! @endmodule */