pike.git
/
lib
/
modules
/
Filesystem.pmod
/
Monitor.pmod
/
basic.pike
version
»
Context lines:
10
20
40
80
file
none
3
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:10:
//! //! This module is intended to be used for incremental scanning of //! a filesystem. //! //! Supports FSEvents on MacOS X and Inotify on Linux to provide low //! overhead monitoring; other systems use a less efficient polling approach. //! //! @seealso //! @[System.FSEvents], @[System.Inotify]
+
#ifdef FILESYSTEM_MONITOR_DEBUG
+
#define MON_WERR(X...) werror(X)
+
#else
+
#define MON_WERR(X...)
+
#endif
+
//! The default maximum number of seconds between checks of directories //! in seconds. //! //! This value is multiplied with @[default_file_interval_factor] to //! get the corresponding default maximum number of seconds for files. //! //! The value can be changed by calling @[create()]. //! //! The value can be overridden for individual files or directories //! by calling @[monitor()].
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:189:
array(string) files; //! Register the @[Monitor] with the monitoring system. //! //! @param initial //! Indicates that the @[Monitor] is newly created. protected void register_path(int|void initial) { if (initial) { // We need to be polled...
+
MON_WERR("Registering %O for polling.\n", path);
monitor_queue->push(this); } } //! Unregister the @[Monitor] from the monitoring system. //! //! @param dying //! Indicates that the @[Monitor] is being destructed. protected void unregister_path(int|void dying) {
-
if (
!
dying) {
-
// We
now
need
to
be
polled
..
.
+
if (dying) {
+
// We
are
going
away
permanently,
so remove ourselves from
+
// from the monitor_queue
.
+
MON_WERR("Unregistering %O from polling
.
\n", path);
monitor_queue->remove(this); } } int `<(mixed m) { return next_poll < m; } int `>(mixed m) { return next_poll > m; } void create() { Element::create(this);
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:557:
//! //! @note //! The return value can not be trusted to return @expr{1@} for all //! detected changes in recursive mode. //! //! @seealso //! @[check()], @[data_changed()], @[attr_changed()], @[file_created()], //! @[file_deleted()], @[stable_data_change()] int(0..1) check(MonitorFlags|void flags) {
+
MON_WERR("Checking monitor %O...\n", this);
Stdio.Stat st = file_stat(path, 1); Stdio.Stat old_st = this::st; int orig_flags = this::flags; this::flags |= MF_INITED; update(st); if (!(orig_flags & MF_INITED)) { // Initialize. if (st) { if (st->isdir) { array(string) files = get_dir(path) || ({});
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:690:
return 1; } else if (last_change != 0x7fffffff && st->isdir && status_change(old_st, st, orig_flags, flags)) { // Directory not stable yet. last_change = time(1); update(st); return 1; } return 0; }
+
+
protected void destroy()
+
{
+
unregister_path(1);
}
-
+
}
-
+
//! @class DefaultMonitor
+
//! This symbol evaluates to the @[Monitor] class used by
+
//! the default implementation of @[monitor_factory()].
+
//!
+
//! It is currently one of the values @[Monitor], @[EventStreamMonitor]
+
//! or @[InotifyMonitor].
+
//!
+
//! @seealso
+
//! @[monitor_factory()]
+
+
// NB: See further below for the actual definitions.
+
+
//! @decl inherit Monitor
+
+
//! @endclass
+
// // Some necessary setup activities for systems that provide // filesystem event monitoring // #if constant(System.FSEvents.EventStream) #define HAVE_EVENTSTREAM 1 #endif #if constant(System.Inotify) #define HAVE_INOTIFY 1 #endif #if HAVE_EVENTSTREAM protected System.FSEvents.EventStream eventstream; protected array(string) eventstream_paths = ({}); //! This function is called when the FSEvents EventStream detects a change //! in one of the monitored directories. protected void eventstream_callback(string path, int flags, int event_id) {
-
+
MON_WERR("eventstream_callback(%O, 0x%08x, %O)\n", path, flags, event_id);
if(path[-1] == '/') path = path[0..<1];
-
if(monitors[path])
-
{
+
MON_WERR("Normalized path: %O\n", path);
+
if(monitors[path]) {
monitors[path]->check(0);
-
+
} else {
+
MON_WERR("No monitor found for path %O. Checking all monitors...\n", path);
+
check_all();
}
-
else check_all();
+
} //! FSEvents EventStream-accellerated @[Monitor]. protected class EventStreamMonitor { inherit Monitor;
-
constant
accellerated
= 1
;
+
int(0..1)
accellerated;
-
+
protected void file_exists(string path, Stdio.Stat st)
+
{
+
::file_exists(path, st);
+
if ((last_change != 0x7fffffff) && accellerated) {
+
// Not stable yet.
+
int t = time(1) - last_change;
+
if (t < 0) t = 0;
+
(backend || Pike.DefaultBackend)->
+
call_out(check, (stable_time || global::stable_time) + 1 - t);
+
}
+
}
+
+
protected void file_created(string path, Stdio.Stat st)
+
{
+
if (accellerated) {
+
(backend || Pike.DefaultBackend)->
+
call_out(check, (stable_time || global::stable_time) + 1);
+
}
+
::file_created(path, st);
+
}
+
+
protected void attr_changed(string path, Stdio.Stat st)
+
{
+
if (accellerated) {
+
(backend || Pike.DefaultBackend)->
+
call_out(check, (stable_time || global::stable_time) + 1);
+
}
+
::attr_changed(path, st);
+
}
+
protected void register_path(int|void initial) {
-
+
if (backend) {
+
// CFRunLoop only works with the primary backend.
+
::register_path(initial);
+
return;
+
}
+
#ifndef INHIBIT_EVENTSTREAM_MONITOR
if (!initial) return; if (!eventstream) {
-
+
// Make sure that the main backend is in CF-mode.
+
Pike.DefaultBackend.enable_core_foundation(1);
+
+
MON_WERR("Creating event stream.\n");
eventstream = System.FSEvents.EventStream(({}), 3.0, System.FSEvents.kFSEventStreamEventIdSinceNow, System.FSEvents.kFSEventStreamCreateFlagNone); eventstream->callback_func = eventstream_callback; } else {
-
int
already_added = 0
;
+
string
found
;
foreach(eventstream_paths;;string p) {
-
+
if (path == p) {
+
// Unlikely, but...
+
// NB: Eventstream doesn't notify on the monitored path;
+
// only on its contents.
+
MON_WERR("Path %O already monitored.\n", path);
+
::register_path(initial);
+
return;
+
}
if((path == p) || has_prefix(path, p + "/")) {
-
already
_
added
=
1
;
-
break
;
+
MON
_
WERR("Path
%O
already monitored via path %O.\n", path, p)
;
+
found = p
;
} }
-
if(
already
_
added
) return;
+
if
(
found) {
+
MON
_
WERR("Path %O is accellerated via %O.\n", path, found
)
;
+
accellerated = 1;
+
return;
}
-
+
}
-
+
MON_WERR("Adding %O to the set of monitored paths.\n", path);
eventstream_paths += ({path}); if(eventstream->is_started()) eventstream->stop(); eventstream->add_path(path); eventstream->start();
-
+
#endif /* !INHIBIT_EVENTSTREAM_MONITOR */
+
// NB: Eventstream doesn't notify on the monitored path;
+
// only on its contents.
+
::register_path(initial);
} int(0..1) check(MonitorFlags|void flags) {
-
+
MON_WERR("Checking path %O...\n", path);
int orig_flags = this::flags; int(0..1) ret = ::check(flags);
-
if (ret) return ret;
-
+
if(orig_flags & MF_RECURSE) { // If using FSEvents, we won't receive the name of the file changed, // so we have to scan for it. array(string) files = get_dir(path) || ({}); this::files = files; foreach(files, string file) { file = canonic_path(Stdio.append_path(path, file)); if (monitors[file]) { if(check_monitor(monitors[file])) ret = 1; } } } return ret; } }
-
+
constant DefaultMonitor = EventStreamMonitor;
+
#elseif HAVE_INOTIFY protected System.Inotify._Instance instance;
-
protected Stdio.File file;
+
-
//! Read callback for events on the Inotify file.
-
protected
void
inotify_
parse
(
mixed
id, string data
)
+
protected
string(8bit)
inotify_
cookie
(
int
wd
)
{
-
while
(
sizeof
(
data
)) {
-
array
a
;
+
//
NB: Prefix with a NUL to make sure not to conflict with real paths.
+
return sprintf
(
"\0%8c", wd);
+
}
+
+
//! Event callback for Inotify.
+
protected void inotify_event
(
int wd, int event, int cookie, string(8bit
)
path
)
+
{
+
MON_WERR("inotify_event(%O, %s, %O, %O)...\n",
+
wd, System.Inotify.describe_mask(event), cookie, path);
+
string(8bit) icookie = inotify_cookie(wd);
+
Monitor m;
+
if((m = monitors[icookie]))
{
+
if
(event == System.Inotify.IN_IGNORED) {
+
// This Inotify watch has been removed
+
// (either by us or automatically).
+
MON_WERR("### Monitor watch descriptor %d is no more.\n", wd)
;
+
m_delete(monitors, icookie);
+
}
mixed err = catch {
-
a = System.Inotify.parse_event
(
data
);
+
m->check
(
0
);
};
-
+
if (err) {
-
// TODO: might have a partial event struct here which gets completed
-
// by the next call?? maybe add an internal buffer.
-
werror
(
"Could not parse inotify event: %s\n", describe
_error(err)
)
;
-
return;
+
master
(
)->handler
_error(err);
}
-
string path;
-
path = a[3];
-
if(path && monitors[path]) {
-
monitors[path]->check(0);
+
} else {
-
+
// Unknown monitor. Perform a full scan.
+
MON_WERR("### Unknown monitor! Performing full scan.\n");
+
check_all();
// No need to look at the other entries if we're going to do // a full scan.
-
check_all();
-
return;
+
}
-
-
data = data[a[4]..];
+
}
-
}
+
//! Inotify-accellerated @[Monitor]. protected class InotifyMonitor { inherit Monitor; protected int wd = -1; int `accellerated() { return wd != -1; }
-
+
protected void file_exists(string path, Stdio.Stat st)
+
{
+
::file_exists(path, st);
+
if ((last_change != 0x7fffffff) && (wd != -1)) {
+
// Not stable yet.
+
int t = time(1) - last_change;
+
if (t < 0) t = 0;
+
(backend || Pike.DefaultBackend)->
+
call_out(check, (stable_time || global::stable_time) + 1 - t);
+
}
+
}
+
+
protected void file_created(string path, Stdio.Stat st)
+
{
+
if (wd != -1) {
+
(backend || Pike.DefaultBackend)->
+
call_out(check, (stable_time || global::stable_time) + 1);
+
}
+
::file_created(path, st);
+
}
+
+
protected void attr_changed(string path, Stdio.Stat st)
+
{
+
if (wd != -1) {
+
(backend || Pike.DefaultBackend)->
+
call_out(check, (stable_time || global::stable_time) + 1);
+
}
+
::attr_changed(path, st);
+
}
+
protected void register_path(int|void initial) { if (wd != -1) return;
-
+
#ifndef INHIBIT_INOTIFY_MONITOR
if (initial && !instance) {
-
+
MON_WERR("Creating Inotify monitor instance.\n");
instance = System.Inotify._Instance();
-
file = Stdio.File();
-
if (backend)
file
->set_backend(backend);
-
file->assign(
instance->
fd
()
)
;
-
file->set
_
nonblocking
();
-
file
->set_
read_callback
(
inotify_parse
);
+
if (backend)
instance
->set_backend(backend);
+
instance->
set_event_callback
(
inotify_event
);
+
if (co
_
id) {
+
MON_WERR("Turning on
nonblocking
mode for Inotify.\n"
);
+
instance
->set_
nonblocking
();
}
-
+
}
catch { int new_wd = instance->add_watch(path, System.Inotify.IN_MOVED_FROM | System.Inotify.IN_UNMOUNT | System.Inotify.IN_MOVED_TO | System.Inotify.IN_MASK_ADD | System.Inotify.IN_MOVE_SELF | System.Inotify.IN_DELETE | System.Inotify.IN_MOVE | System.Inotify.IN_MODIFY | System.Inotify.IN_ATTRIB | System.Inotify.IN_DELETE_SELF | System.Inotify.IN_CREATE);
-
+
MON_WERR("Registered %O with %O ==> %d.\n", path, instance, new_wd);
if (new_wd != -1) { // We shouldn't need to be polled. if (!initial) {
-
+
MON_WERR("Unregistering from polling.\n");
release_monitor(this); } wd = new_wd;
-
+
monitors[inotify_cookie(wd)] = this;
+
if (initial) {
+
// NB: Inotify seems to not notify on preexisting paths,
+
// so we need to strap it along.
+
check();
+
}
return; } };
-
+
#endif /* !INHIBIT_INOTIFY_MONITOR */
+
MON_WERR("Registering %O for polling.\n", path);
::register_path(initial); } protected void unregister_path(int|void dying) {
-
+
MON_WERR("Unregistering %O...\n", path);
if (wd != -1) {
-
+
// NB: instance may be null if the main object has been destructed
+
// and we've been called via a destroy().
+
if (instance && dying) {
+
MON_WERR("### Unregistering from inotify.\n");
+
// NB: Inotify automatically removes watches for deleted files,
+
// and will complain if we attempt to remove them too.
+
//
+
// Since we have no idea if there's already a queued ID_IGNORED
+
// pending we just throw away any error here.
+
mixed err = catch {
instance->rm_watch(wd);
-
+
};
+
if (err) {
+
MON_WERR("### Failed: %s\n", describe_backtrace(err));
+
}
+
}
wd = -1; if (!dying) { // We now need to be polled...
-
+
MON_WERR("Registering for polling.\n");
monitor_queue->push(this); } } ::unregister_path(dying); }
-
-
protected void destroy()
-
{
-
unregister_path(1);
+
}
-
}
+
+
constant DefaultMonitor = InotifyMonitor;
+
+
#else
+
+
constant DefaultMonitor = Monitor;
+
#endif /* HAVE_EVENTSTREAM || HAVE_INOTIFY */ //! Canonicalize a path. //! //! @param path //! Path to canonicalize. //! //! @returns //! The default implementation returns @expr{combine_path(path, ".")@}, //! i.e. no trailing slashes. protected string canonic_path(string path) {
-
+
#if HAVE_EVENTSTREAM
+
if (!backend) {
+
path = System.resolvepath(path);
+
}
+
#endif
return combine_path(path, "."); } //! Mapping from monitored path to corresponding @[Monitor]. //! //! The paths are normalized to @expr{canonic_path(path)@}, //! //! @note //! All filesystems are handled as if case-sensitive. This should //! not be a problem for case-insensitive filesystems as long as
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:988:
{ if (m->accellerated) return; monitor_queue->adjust(m); } //! Create a new @[Monitor] for a @[path]. //! //! This function is called by @[monitor()] to create a new @[Monitor] //! object. //!
-
//! The default implementation just calls @[
Monitor()
]
(or one of
-
//! @[EventStreamMonitor] or @[InotifyMonitor])
with the
same
-
//! arguments.
+
//! The default implementation just calls @[
DefaultMonitor
] with the
+
//!
same
arguments.
//! //! @seealso
-
//! @[monitor()]
-
protected
Monitor
monitor_factory(string path, MonitorFlags|void flags,
+
//! @[monitor()]
, @[DefaultMonitor]
+
protected
DefaultMonitor
monitor_factory(string path, MonitorFlags|void flags,
int(0..)|void max_dir_check_interval, int(0..)|void file_interval_factor, int(0..)|void stable_time) {
-
#if
HAVE_EVENTSTREAM
-
return
EventStreamMonitor
(path, flags, max_dir_check_interval,
+
return
DefaultMonitor
(path, flags, max_dir_check_interval,
file_interval_factor, stable_time);
-
#elseif HAVE_INOTIFY
-
return InotifyMonitor(path, flags, max_dir_check_interval,
-
file_interval_factor, stable_time);
-
#else /* !HAVE_EVENTSTREAM && !HAVE_INOTIFY */
-
return Monitor(path, flags, max_dir_check_interval,
-
file_interval_factor, stable_time);
-
#endif /* HAVE_EVENTSTREAM || HAVE_INOTIFY */
+
} //! Register a @[path] for monitoring. //! //! @param path //! Path to monitor. //! //! @param flags //! @int
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:1081:
monitors[path] = m; // NB: Registering with the monitor_queue is done as // needed by register_path() as called by create(). } } int filter_file(string path) { array x = path/"/"; foreach(x;; string pc)
-
if(pc && strlen(pc) && pc[0]=='.') {
/*
werror
("skipping %O\n", path);
*/
return 1; }
+
if(pc && strlen(pc) && pc[0]=='.'
&& pc != ".."
) {
+
MON_WERR
("skipping %O\n", path);
+
return 1;
+
}
return 0; } //! Release a @[path] from monitoring. //! //! @param path //! Path to stop monitoring. //! //! @param flags
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:1199:
//! calling @[check()]. //! //! @seealso //! @[check()], @[monitor()] void check_all(mapping(string:int)|void ret_stats) { int cnt; int scanned_cnt; foreach(monitors; string path; Monitor m) { scanned_cnt++;
+
mixed err = catch {
cnt += check_monitor(m);
-
+
};
+
if (err) {
+
master()->handle_error(err);
}
-
+
}
if (ret_stats) { ret_stats->num_monitors = sizeof(monitors); ret_stats->scanned_monitors = scanned_cnt; ret_stats->updated_monitors = cnt; ret_stats->idle_time = 0; } } //! Check for changes. //!
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:1252:
//! //! @note //! Any callbacks will be called from the same thread as the one //! calling @[check()]. //! //! @seealso //! @[check_all()], @[monitor()] int check(int|void max_wait, int|void max_cnt, mapping(string:int)|void ret_stats) {
+
#if HAVE_INOTIFY
+
if (instance) {
+
/* FIXME: No statistics currently available. */
+
instance->poll();
+
}
+
#endif
int scan_cnt = max_cnt; int scan_wait = max_wait; while(1) { int ret = max_dir_check_interval; int cnt; int t = time(); if (sizeof(monitor_queue)) { Monitor m; while ((m = monitor_queue->peek()) && (m->next_poll <= t)) { cnt += check_monitor(m);
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:1317:
//! Change backend. //! //! @param backend //! Backend to use. @expr{0@} (zero) for the default backend. void set_backend(Pike.Backend|void backend) { int was_nonblocking = !!co_id; set_blocking(); this::backend = backend;
-
#if HAVE_
INOTIFY
-
if (backend) {
-
file->set_backend
(
backend)
;
-
}
else
{
-
file
->
set
_
backend
(
Pike.DefaultBackend
);
+
#if HAVE_
EVENTSTREAM
+
#if
0
/* FIXME: The following does NOT work properly. */
+
if (
eventstream &&
backend) {
+
foreach
(
monitors; string path; Monitor m
)
{
+
if (m->accellerated)
{
+
m
->
accellerated = 0;
+
monitor
_
queue->push
(
m
);
}
-
+
}
+
}
#endif
-
+
#elif HAVE_INOTIFY
+
if (instance) {
+
instance->set_backend(backend || Pike.DefaultBackend);
+
}
+
#endif
if (was_nonblocking) { set_nonblocking(); } } //! Turn off nonblocking mode. //! //! @seealso //! @[set_nonblocking()] void set_blocking() { if (co_id) { if (backend) backend->remove_call_out(co_id); else remove_call_out(co_id); co_id = 0; }
-
+
#if HAVE_INOTIFY
+
if (instance) {
+
instance->set_blocking();
}
-
+
#endif
+
}
//! Backend check callback function. //! //! This function is intended to be called from a backend, //! and performs a @[check()] followed by rescheduling //! itself via a call to @[set_nonblocking()]. //! //! @seealso //! @[check()], @[set_nonblocking()] protected void backend_check()
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:1380:
//! backend thread. //! //! @note //! If nonblocking mode is already active, this function will //! be a noop. //! //! @seealso //! @[set_blocking()], @[check()]. void set_nonblocking(int|void t) {
+
#if HAVE_INOTIFY
+
if (instance) {
+
instance->set_nonblocking();
+
}
+
#endif
if (co_id) return; // NB: Other stuff than plain files may be used with the monitoring // system, so the call_out may be needed even with accellerators. // // NB: Also note that Inotify doesn't support monitoring of non-existing // paths, so it still needs the call_out-loop. if (undefinedp(t)) { if (sizeof(monitor_queue)) { Monitor m = monitor_queue->peek(); t = (m && m->next_poll - time(1)) || max_dir_check_interval;