pike.git / src / modules / Inotify / inotify.cmod

version» Context lines:

pike.git/src/modules/Inotify/inotify.cmod:19:   #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"   #include "fdlib.h" + #include "fd_control.h"   #include "pike_threadlib.h"      #ifdef HAVE_SYS_INOTIFY_H      #include <sys/inotify.h>   #include <errno.h>   #include <unistd.h>      /* Autoconf helpfully litters generated files with colliding defines. */   #undef PACKAGE_BUGREPORT
pike.git/src/modules/Inotify/inotify.cmod:158:    *! interface. On create an inotify instance is initiated by calling    *! @tt{inotify_init(2)@}. Every object of this class has its own inotify    *! 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].    *!    *! @seealso    *! @[System.Inotify.Instance]    */   PIKECLASS _Instance { -  CVAR int fd; -  CVAR struct object * fd_object; +  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; +  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; +  } +     /*! @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.
pike.git/src/modules/Inotify/inotify.cmod:191:    *!    *! @seealso    *! @[rm_watch()], @[parse_event()]    */    PIKEFUN int add_watch(string file, int mask) {    INT32 err;       if (file->size_shift)    Pike_error("Widestring filenames are not allowed.\n");    -  err = inotify_add_watch(THIS->fd, file->str, (INT32)mask); +  err = inotify_add_watch(THIS->box.fd, file->str, mask);       if (err == -1)    Pike_error("inotify_add_watch failed: %s\n",    strerror(errno));    else    RETURN err;    }          /*! @decl object get_fd()    *! @returns    *! Returns the file descriptor associated with this inotify instance.    *! @note    *! Use @[fd()] instead.    */    PIKEFUN int get_fd() { -  push_int(THIS->fd); +  push_int(THIS->box.fd);    }    -  /*! @decl object fd() -  *! @returns -  *! Returns a instance of @[Stdio.Fd] corresponding to the inotify instance. This can passed to -  *! @[Stdio.File()->assign()]. -  */ -  PIKEFUN object fd() { -  ref_push_object(THIS->fd_object); -  } -  +     /*! @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;    -  err = inotify_rm_watch(THIS->fd, wd); +  err = inotify_rm_watch(THIS->box.fd, wd);    -  if (err == 0) { +  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. +  */    return;    }    -  if (errno == EINVAL) { -  Pike_error("Wrong argument to rm_watch().\n"); -  } else if (errno == EBADF) { +  if (errno == EBADF) {    Pike_error("Oups. I feel funny inside.\n");    } -  +  Pike_error("Unexpected error: %d.\n", errno);    }    -  +  /*! @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) +  { +  /* 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); +  } +  +  /*! @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) +  SIMPLE_BAD_ARG_ERROR("set_backend", 1, "Pike.Backend"); +  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))) { +  /* NB: Assumes that e->len has a valid alignment +  * for the struct. This could cause problems +  * on non-x86 systems and injected data. +  */ +  struct inotify_event *e = (void *)(THIS->buf.s->str + off); +  const char *path = (char *)(e + 1); +  ptrdiff_t new_off = off + sizeof(struct inotify_event) + e->len; +  if (new_off > THIS->buf.s->len) { +  /* Not enough data for the filename yet. */ +  break; +  } +  off = new_off; +  +  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 { -  struct object * o; -  THIS->fd = inotify_init(); -  THIS->fd_object = NULL; +  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->fd == -1) switch (errno) { +  if (THIS->box.fd == -1) { +  switch (errno) {    case EMFILE:    Pike_error("User limit on inotify instances reached.\n"); -  +  break;    case ENFILE:    Pike_error("User limit on file descriptors reached.\n"); -  +  break;    case ENOMEM:    Pike_error("No free kernel memory available.\n"); -  +  break; +  default: +  Pike_error("Failed to initialize.\n"); +  break;    } -  -  o = file_make_object_from_fd(THIS->fd, FILE_READ, fd_CAN_NONBLOCK); -  /* We will close the inotify fd on EXIT */ -  ((struct my_file *)(o->storage + o->prog->inherits->storage_offset))->flags |= FILE_NO_CLOSE_ON_DESTRUCT; -  THIS->fd_object = o; +     } -  +  set_nonblocking(THIS->box.fd, 1); +  }       EXIT { -  if (THIS->fd_object) { -  free_object(THIS->fd_object); -  THIS->fd_object = NULL; -  } -  if (THIS->fd != -1) { -  int fd = THIS->fd; +  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);    /* -  * currently (linux 3.4.9) closing an inotify fd takes in the order of 100 ms +  * Currently (linux 3.4.9) closing an inotify fd takes +  * on the order of 100 ms    */    THREADS_ALLOW(); -  close(fd); +  while ((close(fd) == -1) && (errno == EINTR)) +  ;    THREADS_DISALLOW();    } -  +  free_string_builder(&THIS->buf);    }   }      /*! @endclass    */      #define ADD_ICONST(name) do { \    add_integer_constant(#name, name, 0); \   } while(0);