/* vim:syntax=c |
*/ |
/* -*- 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) |
* |
*/ |
|
#ifdef HAVE_CONFIG_H |
# include "config.h" |
#endif |
|
#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 |
#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" |
|
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. |
*/ |
|
/*! @decl array(string|int) parse_event(string data) |
*! Parses one inotify_event struct from @expr{data@}. |
*! @returns |
*! Returns an array consisting of |
*! @array |
*! @elem int 0 |
*! The watch descriptor returned by @[_Instance()->add_watch()] |
*! when the watch for this file was added. |
*! @elem int 1 |
*! An integer that describes the event that occured. See |
*! the inotify manpage for a list of possible events and |
*! their numerical identifiers. |
*! @elem int 2 |
*! An integer cookie that can be used to group together |
*! different events that were triggered by moving a file |
*! from one location to another. |
*! @elem string 3 |
*! 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. |
*! @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 |
*! an argument. |
*! @endarray |
*/ |
PIKEFUN array(string|int) parse_event(string data) { |
struct inotify_event event; |
size_t len; |
const char * d; |
|
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"); |
|
d = data->str; |
|
memcpy(&event, d, sizeof(struct inotify_event)); |
|
push_int(event.wd); |
push_int((int)event.mask); |
push_int((int)event.cookie); |
|
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); |
} |
|
push_int((int)(event.len + sizeof(struct inotify_event))); |
|
f_aggregate(5); |
stack_swap(); |
pop_stack(); |
} |
|
/*! @class _Instance |
*! Simple wrapper class that gives direct access to the @tt{inotify(7)@} |
*! 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 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. |
*! 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]). |
*! @returns |
*! Returns a watch descriptor. |
*! @note |
*! Subdirectories are not watched. If you want to watch |
*! subdirectories as well, you need to add watches for |
*! them individually. |
*! |
*! @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->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->box.fd); |
} |
|
/*! @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->box.fd, wd); |
|
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 == 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 { |
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: |
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; |
} |
} |
set_nonblocking(THIS->box.fd, 1); |
} |
|
EXIT { |
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 |
* on the order of 100 ms |
*/ |
THREADS_ALLOW(); |
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); |
|
#else /* !HAVE_SYS_INOTIFY_H */ |
|
#define ADD_ICONST(name) |
|
#endif /* HAVE_SYS_INOTIFY_H */ |
|
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 |
*/ |
|