pike.git
/
src
/
modules
/
Inotify
/
inotify.cmod
version
»
Context lines:
10
20
40
80
file
none
3
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);