d629562011-11-05Martin Nilsson #pike __REAL_VERSION__
fe76e22009-07-13Henrik Grubbström (Grubba) // // Basic filesystem monitor. // // // 2009-07-09 Henrik Grubbström //
90569b2009-07-13Henrik Grubbström (Grubba) //! Basic filesystem monitor. //! //! This module is intended to be used for incremental scanning of //! a filesystem.
15c9822012-06-29Bill Welliver //!
3524712015-05-26Martin Nilsson //! Supports FSEvents on MacOS X and Inotify on Linux to provide low
d65aa82014-02-28Bill Welliver //! overhead monitoring; other systems use a less efficient polling approach. //! //! @seealso
49e7a82017-07-18Henrik Grubbström (Grubba) //! @[Filesystem.Monitor.symlinks], @[System.FSEvents], @[System.Inotify]
3daf4c2015-10-05Henrik Grubbström (Grubba) 
fe76e22009-07-13Henrik Grubbström (Grubba) //! 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()]. //! //! Overload this constant to change the default. protected constant default_max_dir_check_interval = 60; //! The default factor to multiply @[default_max_dir_check_interval] //! with to get the maximum number of seconds between checks of files. //! //! The value can be changed by calling @[create()]. //! //! The value can be overridden for individual files or directories //! by calling @[monitor()]. //! //! Overload this constant to change the default. protected constant default_file_interval_factor = 5;
075cf22009-07-16Henrik Grubbström (Grubba) //! The default minimum number of seconds without changes for a change //! to be regarded as stable (see @[stable_data_change()].
f3483e2009-07-16Henrik Grubbström (Grubba) protected constant default_stable_time = 5;
fe76e22009-07-13Henrik Grubbström (Grubba)  protected int max_dir_check_interval = default_max_dir_check_interval; protected int file_interval_factor = default_file_interval_factor;
f3483e2009-07-16Henrik Grubbström (Grubba) protected int stable_time = default_stable_time;
fe76e22009-07-13Henrik Grubbström (Grubba) 
49e7a82017-07-18Henrik Grubbström (Grubba) protected inline constant SeverityLevel = DefaultCompilerEnvironment.SeverityLevel; protected inline constant NOTICE = DefaultCompilerEnvironment.NOTICE; protected inline constant WARNING = DefaultCompilerEnvironment.WARNING; protected inline constant ERROR = DefaultCompilerEnvironment.ERROR; protected inline constant FATAL = DefaultCompilerEnvironment.FATAL;
fe76e22009-07-13Henrik Grubbström (Grubba) // Callbacks
49e7a82017-07-18Henrik Grubbström (Grubba) //! Event tracing callback. //! //! @param level //! Severity level of the event. //! //! @param fun //! Name of the function that called @[report()]. //! //! @param format //! @[sprintf()] formatting string describing the event. //! //! @param args //! Optional extra arguments for the @[format] string. //! //! This function is called in various places to provide //! granular tracing of the monitor state. //! //! The default implementation calls @[werror()] with //! @[format] and @[args] if @[level] is @[ERROR] or higher, //! or if @tt{FILESYSTEM_MONITOR_DEBUG@} has been defined. protected void report(SeverityLevel level, string(7bit) fun, sprintf_format format, sprintf_args ... args) { #ifndef FILESYSTEM_MONITOR_DEBUG if (level < ERROR) return; #endif werror(format, @args); } #define MON_WERR(X...) report(NOTICE, __func__, X) #define MON_WARN(X...) report(WARNING, __func__, X) #define MON_ERROR(X...) report(ERROR, __func__, X)
fe76e22009-07-13Henrik Grubbström (Grubba) //! File content changed callback. //! //! @param path //! Path of the file which has had content changed. //! //! This function is called when a change has been detected for a //! monitored file. //! //! Called by @[check()] and @[check_monitor()]. //! //! Overload this to do something useful. void data_changed(string path); //! File attribute changed callback. //! //! @param path //! Path of the file or directory which has changed attributes. //! //! @param st //! Status information for @[path] as obtained by @expr{file_stat(path, 1)@}. //! //! This function is called when a change has been detected for an //! attribute for a monitored file or directory. //! //! Called by @[check()] and @[check_monitor()]. //! //! @note //! If there is a @[data_changed()] callback, it may supersede this //! callback if the file content also has changed. //! //! Overload this to do something useful. void attr_changed(string path, Stdio.Stat st);
249a3f2009-07-13Henrik Grubbström (Grubba) //! File existance callback. //! //! @param path //! Path of the file or directory. //! //! @param st //! Status information for @[path] as obtained by @expr{file_stat(path, 1)@}. //! //! This function is called during initialization for all monitored paths, //! and subpaths for monitored directories. It represents the initial state //! for the monitor. //! //! @note
868a5f2017-12-18Henrik Grubbström (Grubba) //! For directories, @[file_exists()] will be called for the subpaths
249a3f2009-07-13Henrik Grubbström (Grubba) //! before the call for the directory itself. This can be used to detect //! when the initialization for a directory is finished. //! //! Called by @[check()] and @[check_monitor()] the first time a monitored //! path is checked (and only if it exists). //! //! Overload this to do something useful.
868a5f2017-12-18Henrik Grubbström (Grubba) //! //! @note //! This callback has similar semantics to @[file_created()], but //! is called at initialization time. //! //! @seealso //! @[file_created()], @[file_deleted()], @[stable_data_change()]
249a3f2009-07-13Henrik Grubbström (Grubba) void file_exists(string path, Stdio.Stat st);
fe76e22009-07-13Henrik Grubbström (Grubba) //! File creation callback. //! //! @param path //! Path of the new file or directory. //! //! @param st //! Status information for @[path] as obtained by @expr{file_stat(path, 1)@}. //! //! This function is called when either a monitored path has started //! existing, or when a new file or directory has been added to a //! monitored directory. //! //! Called by @[check()] and @[check_monitor()]. //! //! Overload this to do something useful.
868a5f2017-12-18Henrik Grubbström (Grubba) //! //! @note //! This callback has similar semantics to @[file_exists()], but //! is called at run time. //! //! @seealso //! @[file_exists()], @[file_deleted()], @[stable_data_change()]
fe76e22009-07-13Henrik Grubbström (Grubba) void file_created(string path, Stdio.Stat st); //! File deletion callback. //! //! @param path //! Path of the new file or directory that has been deleted. //! //! This function is called when either a monitored path has stopped //! to exist, or when a file or directory has been deleted from a //! monitored directory. //! //! Called by @[check()] and @[check_monitor()]. //! //! Overload this to do something useful.
868a5f2017-12-18Henrik Grubbström (Grubba) //! //! @seealso //! @[file_created()], @[file_exists()], @[stable_data_change()]
fe76e22009-07-13Henrik Grubbström (Grubba) void file_deleted(string path); //! Stable change callback. //! //! @param path //! Path of the file or directory that has stopped changing. //!
7ef0192009-07-15Henrik Grubbström (Grubba) //! @param st //! Status information for @[path] as obtained by @expr{file_stat(path, 1)@}. //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! This function is called when previous changes to @[path] are //! considered "stable". //! //! "Stable" in this case means that there have been no detected //! changes for at lease @[stable_time] seconds. //! //! Called by @[check()] and @[check_monitor()]. //! //! Overload this to do something useful.
868a5f2017-12-18Henrik Grubbström (Grubba) //! //! @note //! This callback being called does not mean that the contents //! or inode has changed, just that they don't seem to change any //! more. In particular it is called for paths found during //! initialization after @[stable_time] seconds have passed. //! //! @seealso //! @[file_created()], @[file_exists()], @[file_deleted()]
7ef0192009-07-15Henrik Grubbström (Grubba) void stable_data_change(string path, Stdio.Stat st);
fe76e22009-07-13Henrik Grubbström (Grubba) 
249a3f2009-07-13Henrik Grubbström (Grubba) //! Flags for @[Monitor]s. enum MonitorFlags { MF_RECURSE = 1, MF_AUTO = 2, MF_INITED = 4,
b1f9342010-02-02Henrik Grubbström (Grubba)  MF_HARD = 8,
249a3f2009-07-13Henrik Grubbström (Grubba) };
aa68fa2010-01-28Henrik Grubbström (Grubba) protected constant S_IFMT = 0x7ffff000;
915a1a2009-07-22Henrik Grubbström (Grubba) //! Monitoring information for a single filesystem path. //! //! @seealso //! @[monitor()]
249a3f2009-07-13Henrik Grubbström (Grubba) protected class Monitor(string path, MonitorFlags flags,
fe76e22009-07-13Henrik Grubbström (Grubba)  int max_dir_check_interval,
f3483e2009-07-16Henrik Grubbström (Grubba)  int file_interval_factor, int stable_time)
fe76e22009-07-13Henrik Grubbström (Grubba) {
b7f5622015-09-10Henrik Grubbström (Grubba)  inherit ADT.Heap.Element;
fe76e22009-07-13Henrik Grubbström (Grubba)  int next_poll; Stdio.Stat st;
fefe522015-02-09Martin Karlgren  int last_change = 0x7fffffff; // Future... Can be set to -0x7fffffff // to indicate immediate stabilization // (avoid an extra check() round to // let the stat stabilize).
99d5232017-10-06Karl Gustav Sterneberg  private bool initialized = false;
fe76e22009-07-13Henrik Grubbström (Grubba)  array(string) files;
49e7a82017-07-18Henrik Grubbström (Grubba)  //! Event tracing callback. //! //! @param level //! Severity level of the event. //! //! @param fun //! Name of the function that called @[report()]. //! //! @param format //! @[sprintf()] formatting string describing the event. //! //! @param args //! Optional extra arguments for the @[format] string. //! //! This function is called in various places to provide //! granular tracing of the monitor state. //! //! The default implementation just calls @[global::report()] //! with the same arguments. protected void report(SeverityLevel level, string(7bit) fun, sprintf_format format, sprintf_args ... args) { global::report(level, fun, format, @args); }
98ceae2015-09-18Henrik Grubbström (Grubba)  //! 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...
3daf4c2015-10-05Henrik Grubbström (Grubba)  MON_WERR("Registering %O for polling.\n", path);
07ea532016-02-15Henrik Grubbström (Grubba)  mixed key = monitor_mutex->lock();
98ceae2015-09-18Henrik Grubbström (Grubba)  monitor_queue->push(this);
30b3762018-02-07Henrik Grubbström (Grubba)  if (monitor_queue->peek() == this) { if (co_id) { reschedule_backend_check(); } }
98ceae2015-09-18Henrik Grubbström (Grubba)  } } //! Unregister the @[Monitor] from the monitoring system. //! //! @param dying //! Indicates that the @[Monitor] is being destructed.
c4f1092016-05-17Henrik Grubbström (Grubba)  //! It is the destruction cause value offset by one.
98ceae2015-09-18Henrik Grubbström (Grubba)  protected void unregister_path(int|void dying) {
c4f1092016-05-17Henrik Grubbström (Grubba)  if (dying == 1) { // We are going away permanently due to explicit destruct(), // so remove ourselves from from the monitor_queue.
07ea532016-02-15Henrik Grubbström (Grubba)  mixed key = monitor_mutex->lock();
c4f1092016-05-17Henrik Grubbström (Grubba)  MON_WERR("Unregistering %O from polling.\n", path);
98ceae2015-09-18Henrik Grubbström (Grubba)  monitor_queue->remove(this); } }
15c9822012-06-29Bill Welliver 
fe76e22009-07-13Henrik Grubbström (Grubba)  int `<(mixed m) { return next_poll < m; } int `>(mixed m) { return next_poll > m; }
15c9822012-06-29Bill Welliver  void create() {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("Creating monitor for %O.\n", path);
b7f5622015-09-10Henrik Grubbström (Grubba)  Element::create(this);
98ceae2015-09-18Henrik Grubbström (Grubba)  register_path(1);
15c9822012-06-29Bill Welliver  }
c96fc22018-04-05Martin Karlgren  //! Returns the parent monitor, or UNDEFINED if no such monitor exists. this_program parent() { string parent_path = canonic_path (dirname (path)); return monitors[parent_path]; } //! To be called when a (direct) submonitor is released. void submonitor_released (this_program submon) { if (files) { string filename = basename (submon->path); MON_WERR("%O->submonitor_released(%O): Removing list state for %O.\n",
5835a12018-04-05Henrik Grubbström (Grubba)  this, submon, filename);
c96fc22018-04-05Martin Karlgren  files -= ({ filename }); } }
c79e292010-01-28Henrik Grubbström (Grubba)  //! Call a notification callback. //! //! @param cb //! Callback to call or @[UNDEFINED] for no operation. //! //! @param path //! Path to notify on. //! //! @param st //! Stat for the @[path]. protected void call_callback(function(string, Stdio.Stat|void:void) cb, string path, Stdio.Stat|void st) { if (!cb) return; cb(path, st); } //! File attribute or content changed callback. //! //! @param st //! Status information for @[path] as obtained by //! @expr{file_stat(path, 1)@}. //! //! This function is called when a change has been detected for an //! attribute for a monitored file or directory. //! //! Called by @[check()] and @[check_monitor()]. //!
868a5f2017-12-18Henrik Grubbström (Grubba)  //! The default implementation calls @[global::attr_changed()] or //! @[global::data_changed()] depending on the state. //!
c79e292010-01-28Henrik Grubbström (Grubba)  //! @note //! If there is a @[data_changed()] callback, it may supersede this //! callback if the file content also has changed. protected void attr_changed(string path, Stdio.Stat st) {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("attr_changed(%O, %O)\n", path, st);
c79e292010-01-28Henrik Grubbström (Grubba)  if (global::data_changed) { call_callback(global::data_changed, path); } else { call_callback(global::attr_changed, path, st); } } //! File existance callback. //! //! @param st //! Status information for @[path] as obtained by //! @expr{file_stat(path, 1)@}. //! //! This function is called during initialization for all monitored paths, //! and subpaths for monitored directories. It represents the initial state //! for the monitor. //! //! @note //! For directories, @[file_created()] will be called for the subpaths //! before the call for the directory itself. This can be used to detect //! when the initialization for a directory is finished. //! //! Called by @[check()] and @[check_monitor()] the first time a monitored //! path is checked (and only if it exists).
868a5f2017-12-18Henrik Grubbström (Grubba)  //! //! The default implementation registers the path, and //! calls @[global::file_exists()]. //! //! @note //! This callback has similar semantics to @[file_created()], but //! is called at initialization time. //! //! @seealso //! @[file_created()], @[file_deleted()], @[stable_data_change()]
c79e292010-01-28Henrik Grubbström (Grubba)  protected void file_exists(string path, Stdio.Stat st) {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("file_exists(%O, %O)\n", path, st);
98ceae2015-09-18Henrik Grubbström (Grubba)  register_path();
5b12eb2012-07-04Henrik Grubbström (Grubba)  int t = time(1);
c79e292010-01-28Henrik Grubbström (Grubba)  call_callback(global::file_exists, path, st);
5b12eb2012-07-04Henrik Grubbström (Grubba)  if (st->mtime + (stable_time || global::stable_time) >= t) { // Not stable yet! We guess that the mtime is a // fair indication of when the file last changed. last_change = st->mtime; }
c79e292010-01-28Henrik Grubbström (Grubba)  } //! File creation callback. //! //! @param st //! Status information for @[path] as obtained by //! @expr{file_stat(path, 1)@}. //! //! This function is called when either a monitored path has started //! existing, or when a new file or directory has been added to a //! monitored directory. //! //! Called by @[check()] and @[check_monitor()].
868a5f2017-12-18Henrik Grubbström (Grubba)  //! //! The default implementation registers the path, and //! calls @[global::file_deleted()]. //! //! @note //! This callback has similar semantics to @[file_exists()], but //! is called at run time. //! //! @seealso //! @[file_exists()], @[file_deleted()], @[stable_data_change()]
c79e292010-01-28Henrik Grubbström (Grubba)  protected void file_created(string path, Stdio.Stat st) {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("file_created(%O, %O)\n", path, st);
98ceae2015-09-18Henrik Grubbström (Grubba)  register_path();
c79e292010-01-28Henrik Grubbström (Grubba)  call_callback(global::file_created, path, st); } //! File deletion callback. //! //! @param path //! Path of the new file or directory that has been deleted. //! //! @param old_st //! Stat for the file prior to deletion (if known). Note that //! this argument is not passed along to top level function. //! //! This function is called when either a monitored path has stopped //! to exist, or when a file or directory has been deleted from a //! monitored directory. //! //! Called by @[check()] and @[check_monitor()].
868a5f2017-12-18Henrik Grubbström (Grubba)  //! //! The default implementation unregisters the path, and //! calls @[global::file_deleted()]. //! //! @seealso //! @[file_created()], @[file_exists()], @[stable_data_change()]
c79e292010-01-28Henrik Grubbström (Grubba)  protected void file_deleted(string path, Stdio.Stat|void old_st) {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("file_deleted(%O, %O)\n", path, st);
98ceae2015-09-18Henrik Grubbström (Grubba)  unregister_path();
c79e292010-01-28Henrik Grubbström (Grubba)  call_callback(global::file_deleted, path); } //! Stable change callback. //! //! @param st //! Status information for @[path] as obtained by //! @expr{file_stat(path, 1)@}. //! //! This function is called when previous changes to @[path] are //! considered "stable". //! //! "Stable" in this case means that there have been no detected //! changes for at lease @[stable_time] seconds. //! //! Called by @[check()] and @[check_monitor()].
868a5f2017-12-18Henrik Grubbström (Grubba)  //! //! The default implementation calls @[global::stable_data_change()]. //! //! @note //! This callback being called does not mean that the contents //! or inode has changed, just that they don't seem to change any //! more. In particular it is called for paths found during //! initialization after @[stable_time] seconds have passed. //! //! @seealso //! @[file_created()], @[file_exists()], @[file_deleted()]
c79e292010-01-28Henrik Grubbström (Grubba)  protected void stable_data_change(string path, Stdio.Stat st) {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("stable_data_change(%O, %O)\n", path, st);
c79e292010-01-28Henrik Grubbström (Grubba)  call_callback(global::stable_data_change, path, st); }
fe76e22009-07-13Henrik Grubbström (Grubba)  protected string _sprintf(int c) { return sprintf("Monitor(%O, %O, next: %s, st: %O)",
249a3f2009-07-13Henrik Grubbström (Grubba)  path, flags, ctime(next_poll) - "\n", st);
fe76e22009-07-13Henrik Grubbström (Grubba)  }
915a1a2009-07-22Henrik Grubbström (Grubba) 
5812c12010-04-27Henrik Grubbström (Grubba)  //! Bump the monitor to an earlier scan time. //!
d0fa9f2013-07-12Jonas Walldén  //! @param flags //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Check all monitors for the entire subtree. //! @endint //!
5812c12010-04-27Henrik Grubbström (Grubba)  //! @param seconds
d0fa9f2013-07-12Jonas Walldén  //! Number of seconds from now to run next scan. Defaults to //! half of the remaining interval. void bump(MonitorFlags|void flags, int|void seconds)
5812c12010-04-27Henrik Grubbström (Grubba)  {
d0fa9f2013-07-12Jonas Walldén  int now = time(1); if (seconds) next_poll = now + seconds; else if (next_poll > now) next_poll -= (next_poll - now) / 2;
96a2372015-09-18Henrik Grubbström (Grubba)  adjust_monitor(this);
5812c12010-04-27Henrik Grubbström (Grubba) 
5c0ba22018-06-29Henrik Grubbström (Grubba)  if ((flags & MF_RECURSE) && st && st->isdir && files) {
5812c12010-04-27Henrik Grubbström (Grubba)  // Bump the files in the directory as well. foreach(files, string file) { file = canonic_path(Stdio.append_path(path, file)); Monitor m2 = monitors[file]; if (m2) { m2->bump(flags, seconds); } } } }
c51c362009-08-05Henrik Grubbström (Grubba)  //! Calculate and set a suitable time for the next poll of this monitor.
915a1a2009-07-22Henrik Grubbström (Grubba)  //! //! @param st //! New stat for the monitor. //! //! This function is called by @[check()] to schedule the //! next check. protected void update(Stdio.Stat st) { int delta = max_dir_check_interval || global::max_dir_check_interval;
8e06a32014-09-30Martin Nilsson  this::st = st;
3524712015-05-26Martin Nilsson 
d5ada02018-03-28Martin Karlgren  if (st && !st->isdir) {
2d52412017-06-21Henrik Grubbström (Grubba)  delta *= file_interval_factor || global::file_interval_factor; }
915a1a2009-07-22Henrik Grubbström (Grubba)  if (st) {
dafee22013-07-12Jonas Walldén  // Start with a delta proportional to the time since mtime/ctime, // but bound this to the max setting. A stat in the future will be // adjusted to the max interval.
2d52412017-06-21Henrik Grubbström (Grubba)  int mtime = max(st->mtime, st->ctime); int d = ((time(1) - mtime) >> 2);
99d5232017-10-06Karl Gustav Sterneberg  if (!initialized && (d >= 0)) {
2d52412017-06-21Henrik Grubbström (Grubba)  // Assume that mtime is reasonable at startup. last_change = mtime; } if ((d >= 0) && (d < delta)) delta = d;
915a1a2009-07-22Henrik Grubbström (Grubba)  }
c51c362009-08-05Henrik Grubbström (Grubba)  if (last_change <= time(1)) { // Time until stable.
067fe62009-08-07Henrik Grubbström (Grubba)  int d = last_change + (stable_time || global::stable_time) - time(1);
c51c362009-08-05Henrik Grubbström (Grubba)  d >>= 1; if (d < 0) d = 1; if (d < delta) delta = d;
dafee22013-07-12Jonas Walldén  }
99d5232017-10-06Karl Gustav Sterneberg  if (!initialized) {
3da1702017-06-30Henrik Grubbström (Grubba)  // Attempt to distribute polls evenly at startup, and to // make sure that the full set of directory contents is // found reasonably fast. delta = 1 + random(delta >> 2);
99d5232017-10-06Karl Gustav Sterneberg  initialized = true;
c51c362009-08-05Henrik Grubbström (Grubba)  }
3524712015-05-26Martin Nilsson 
2d52412017-06-21Henrik Grubbström (Grubba)  MON_WERR("Next poll in %d seconds.\n", (delta || 1));
915a1a2009-07-22Henrik Grubbström (Grubba)  next_poll = time(1) + (delta || 1);
96a2372015-09-18Henrik Grubbström (Grubba)  adjust_monitor(this);
915a1a2009-07-22Henrik Grubbström (Grubba)  }
b1f9342010-02-02Henrik Grubbström (Grubba)  //! Check if this monitor should be removed automatically. void check_for_release(int mask, int flags) {
8e06a32014-09-30Martin Nilsson  if ((this::flags & mask) == flags) {
c96fc22018-04-05Martin Karlgren  MON_WERR("Releasing in %O->check_for_release(%O, %O)\n", this, mask, flags);
b1f9342010-02-02Henrik Grubbström (Grubba)  m_delete(monitors, path); release_monitor(this);
c96fc22018-04-05Martin Karlgren  if (this_program par = parent()) { par->submonitor_released (this); } else { MON_WERR("%O has no parent monitor.\n", this); }
b1f9342010-02-02Henrik Grubbström (Grubba)  } }
cafbb92010-02-03Henrik Grubbström (Grubba)  //! Called to create a sub monitor. protected void monitor(string path, int flags, int max_dir_interval, int file_interval_factor, int stable_time) { global::monitor(path, flags, max_dir_check_interval, file_interval_factor, stable_time); }
c79e292010-01-28Henrik Grubbström (Grubba)  //! Called when the status has changed for an existing file. protected int(0..1) status_change(Stdio.Stat old_st, Stdio.Stat st,
7639942010-04-27Henrik Grubbström (Grubba)  int orig_flags, int flags)
c79e292010-01-28Henrik Grubbström (Grubba)  {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("status_change(%O, %O, 0x%04x, 0x%04x)\n", old_st, st, orig_flags, flags);
c79e292010-01-28Henrik Grubbström (Grubba)  if (st->isdir) {
2ddd922012-07-04Henrik Grubbström (Grubba)  int res = 0;
c79e292010-01-28Henrik Grubbström (Grubba)  array(string) files = get_dir(path) || ({}); array(string) new_files = files; array(string) deleted_files = ({});
8e06a32014-09-30Martin Nilsson  if (this::files) { new_files -= this::files; deleted_files = this::files - files;
c79e292010-01-28Henrik Grubbström (Grubba)  }
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("%d files created, %d files deleted.\n",
48fc552016-05-11Henrik Grubbström (Grubba)  sizeof(new_files), sizeof(deleted_files));
8e06a32014-09-30Martin Nilsson  this::files = files;
c79e292010-01-28Henrik Grubbström (Grubba)  foreach(new_files, string file) {
2ddd922012-07-04Henrik Grubbström (Grubba)  res = 1;
c79e292010-01-28Henrik Grubbström (Grubba)  file = canonic_path(Stdio.append_path(path, file));
2ddd922012-07-04Henrik Grubbström (Grubba)  if(filter_file(file)) continue;
c79e292010-01-28Henrik Grubbström (Grubba)  Monitor m2 = monitors[file]; mixed err = catch { if (m2) { // We have a separate monitor on the created file. // Let it handle the notification. m2->check(flags); } };
8e06a32014-09-30Martin Nilsson  if (this::flags & MF_RECURSE) {
b1f9342010-02-02Henrik Grubbström (Grubba)  monitor(file, orig_flags | MF_AUTO | MF_HARD,
c79e292010-01-28Henrik Grubbström (Grubba)  max_dir_check_interval, file_interval_factor, stable_time); monitors[file]->check(); } else if (!m2) { file_created(file, file_stat(file, 1)); }
6d55162016-05-10Henrik Grubbström (Grubba)  if (err) { master()->handle_error(err); }
c79e292010-01-28Henrik Grubbström (Grubba)  } foreach(deleted_files, string file) {
2ddd922012-07-04Henrik Grubbström (Grubba)  res = 1;
c79e292010-01-28Henrik Grubbström (Grubba)  file = canonic_path(Stdio.append_path(path, file));
2ddd922012-07-04Henrik Grubbström (Grubba)  if(filter_file(file)) continue;
c79e292010-01-28Henrik Grubbström (Grubba)  Monitor m2 = monitors[file]; mixed err = catch { if (m2) { // We have a separate monitor on the deleted file. // Let it handle the notification. m2->check(flags); } };
8e06a32014-09-30Martin Nilsson  if (this::flags & MF_RECURSE) {
c79e292010-01-28Henrik Grubbström (Grubba)  // The monitor for the file has probably removed itself, // or the user has done it by hand, in either case we // don't need to do anything more here. } else if (!m2) { file_deleted(file); }
7bb98b2016-05-10Henrik Grubbström (Grubba)  if (err) { master()->handle_error(err); }
c79e292010-01-28Henrik Grubbström (Grubba)  } if (flags & MF_RECURSE) {
5812c12010-04-27Henrik Grubbström (Grubba)  // Check the remaining files in the directory soon.
c79e292010-01-28Henrik Grubbström (Grubba)  foreach(((files - new_files) - deleted_files), string file) { file = canonic_path(Stdio.append_path(path, file));
2ddd922012-07-04Henrik Grubbström (Grubba)  if(filter_file(file)) continue;
c79e292010-01-28Henrik Grubbström (Grubba)  Monitor m2 = monitors[file]; if (m2) {
5812c12010-04-27Henrik Grubbström (Grubba)  m2->bump(flags);
2ddd922012-07-04Henrik Grubbström (Grubba)  } else { // Lost update due to race-condition: // // Exist ==> Deleted ==> Exists // // with no update of directory inbetween. // // Create the lost submonitor again. res = 1; monitor(file, orig_flags | MF_AUTO | MF_HARD, max_dir_check_interval, file_interval_factor, stable_time); monitors[file]->check();
c79e292010-01-28Henrik Grubbström (Grubba)  } } }
2ddd922012-07-04Henrik Grubbström (Grubba)  return res;
c79e292010-01-28Henrik Grubbström (Grubba)  } else { attr_changed(path, st); return 1; } return 0; }
915a1a2009-07-22Henrik Grubbström (Grubba)  //! Check for changes. //! //! @param flags //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Check all monitors for the entire subtree rooted in @[m]. //! @endint //! //! This function is called by @[check()] for the @[Monitor]s //! it considers need checking. If it detects any changes an //! appropriate callback will be called. //! //! @returns //! Returns @expr{1@} if a change was detected and @expr{0@} (zero) //! otherwise. //! //! @note //! Any callbacks will be called from the same thread as the one //! calling @[check_monitor()]. //! //! @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) {
3daf4c2015-10-05Henrik Grubbström (Grubba)  MON_WERR("Checking monitor %O...\n", this);
915a1a2009-07-22Henrik Grubbström (Grubba)  Stdio.Stat st = file_stat(path, 1);
8e06a32014-09-30Martin Nilsson  Stdio.Stat old_st = this::st; int orig_flags = this::flags; this::flags |= MF_INITED;
915a1a2009-07-22Henrik Grubbström (Grubba)  update(st); if (!(orig_flags & MF_INITED)) { // Initialize. if (st) { if (st->isdir) {
21e5002009-10-13Henrik Grubbström (Grubba)  array(string) files = get_dir(path) || ({});
8e06a32014-09-30Martin Nilsson  this::files = files;
915a1a2009-07-22Henrik Grubbström (Grubba)  foreach(files, string file) {
4627b62009-10-20Henrik Grubbström (Grubba)  file = canonic_path(Stdio.append_path(path, file));
2ddd922012-07-04Henrik Grubbström (Grubba)  if(filter_file(file)) continue;
915a1a2009-07-22Henrik Grubbström (Grubba)  if (monitors[file]) { // There's already a monitor for the file. // Assume it has already notified about existance. continue; }
8e06a32014-09-30Martin Nilsson  if (this::flags & MF_RECURSE) {
b1f9342010-02-02Henrik Grubbström (Grubba)  monitor(file, orig_flags | MF_AUTO | MF_HARD,
915a1a2009-07-22Henrik Grubbström (Grubba)  max_dir_check_interval, file_interval_factor, stable_time); check_monitor(monitors[file]);
c79e292010-01-28Henrik Grubbström (Grubba)  } else {
915a1a2009-07-22Henrik Grubbström (Grubba)  file_exists(file, file_stat(file, 1)); } } } // Signal file_exists for path as an end marker.
c79e292010-01-28Henrik Grubbström (Grubba)  file_exists(path, st);
b670f52017-07-19Henrik Grubbström (Grubba)  } else { // The path we're supposed to monitor is already gone. check_for_release(MF_AUTO, MF_AUTO);
915a1a2009-07-22Henrik Grubbström (Grubba)  } return 1; }
aa68fa2010-01-28Henrik Grubbström (Grubba)  if (old_st) { if (!st || ((old_st->mode & S_IFMT) != (st->mode & S_IFMT))) { // File deleted or changed type.
b0a5fc2009-10-19Henrik Grubbström (Grubba)  int delay; // Propagate deletions to any submonitors. if (files) { foreach(files, string file) { file = canonic_path(Stdio.append_path(path, file));
32c8642016-03-18Jonas Walldén  if (Monitor submon = monitors[file]) {
b0a5fc2009-10-19Henrik Grubbström (Grubba)  // Adjust next_poll, so that the monitor will be checked soon.
9a09872017-06-21Henrik Grubbström (Grubba)  submon->next_poll = time(1)-1; adjust_monitor(submon); delay = 1;
b0a5fc2009-10-19Henrik Grubbström (Grubba)  } }
915a1a2009-07-22Henrik Grubbström (Grubba)  }
b0a5fc2009-10-19Henrik Grubbström (Grubba)  if (delay) { // Delay the notification until the submonitors have notified.
4c4fed2016-03-07Henrik Grubbström (Grubba)  this::st = old_st;
b0a5fc2009-10-19Henrik Grubbström (Grubba)  next_poll = time(1);
96a2372015-09-18Henrik Grubbström (Grubba)  adjust_monitor(this);
b0a5fc2009-10-19Henrik Grubbström (Grubba)  } else {
aa68fa2010-01-28Henrik Grubbström (Grubba)  if (st) { // Avoid race when a file has been replaced with a directory // or vice versa or similar. st = UNDEFINED; // We will catch the new file at the next poll. next_poll = time(1);
96a2372015-09-18Henrik Grubbström (Grubba)  adjust_monitor(this);
b1f9342010-02-02Henrik Grubbström (Grubba)  } else { // The monitor no longer has a link from its parent directory.
8e06a32014-09-30Martin Nilsson  this::flags &= ~MF_HARD;
b1f9342010-02-02Henrik Grubbström (Grubba)  // Check if we should remove the monitor. check_for_release(MF_AUTO, MF_AUTO);
b0a5fc2009-10-19Henrik Grubbström (Grubba)  }
aa68fa2010-01-28Henrik Grubbström (Grubba) 
c79e292010-01-28Henrik Grubbström (Grubba)  file_deleted(path, old_st);
b0a5fc2009-10-19Henrik Grubbström (Grubba)  return 1;
915a1a2009-07-22Henrik Grubbström (Grubba)  }
aa68fa2010-01-28Henrik Grubbström (Grubba)  return 0;
915a1a2009-07-22Henrik Grubbström (Grubba)  }
aa68fa2010-01-28Henrik Grubbström (Grubba)  } else if (st) { // File created.
915a1a2009-07-22Henrik Grubbström (Grubba)  last_change = time(1);
c79e292010-01-28Henrik Grubbström (Grubba)  file_created(path, st);
4627b62009-10-20Henrik Grubbström (Grubba)  if (st->isdir) { array(string) files = get_dir(path) || ({});
8e06a32014-09-30Martin Nilsson  this::files = files;
4627b62009-10-20Henrik Grubbström (Grubba)  foreach(files, string file) { file = canonic_path(Stdio.append_path(path, file));
4af8f32016-03-18Jonas Walldén  if (filter_file(file)) continue;
4627b62009-10-20Henrik Grubbström (Grubba)  if (monitors[file]) { // There's already a monitor for the file. // Assume it has already notified about existance. continue; }
8e06a32014-09-30Martin Nilsson  if (this::flags & MF_RECURSE) {
b1f9342010-02-02Henrik Grubbström (Grubba)  monitor(file, orig_flags | MF_AUTO | MF_HARD,
4627b62009-10-20Henrik Grubbström (Grubba)  max_dir_check_interval, file_interval_factor, stable_time); check_monitor(monitors[file]);
c79e292010-01-28Henrik Grubbström (Grubba)  } else {
4627b62009-10-20Henrik Grubbström (Grubba)  file_created(file, file_stat(file, 1)); } } }
7c19572013-07-12Jonas Walldén  update(st);
915a1a2009-07-22Henrik Grubbström (Grubba)  return 1;
20b62e2010-02-01Henrik Grubbström (Grubba)  } else { return 0;
915a1a2009-07-22Henrik Grubbström (Grubba)  }
3524712015-05-26Martin Nilsson 
dd6a362010-07-14Jonas Wallden  // Note: ctime seems to change unexpectedly when running ImageMagick // on NFS disk so we disable it for the moment [bug 5587].
fefe522015-02-09Martin Karlgren  if (last_change != -0x7fffffff && ((st->mtime != old_st->mtime) || /* (st->ctime != old_st->ctime) || */ (st->size != old_st->size))) {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("Inode changed. New st: %O.\n", st);
915a1a2009-07-22Henrik Grubbström (Grubba)  last_change = time(1);
7c19572013-07-12Jonas Walldén  update(st);
7639942010-04-27Henrik Grubbström (Grubba)  if (status_change(old_st, st, orig_flags, flags)) return 1;
61c4532012-10-24Henrik Grubbström (Grubba)  } else if (last_change < time(1) - (stable_time || global::stable_time)) {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("Inode stable now.\n");
915a1a2009-07-22Henrik Grubbström (Grubba)  last_change = 0x7fffffff;
c79e292010-01-28Henrik Grubbström (Grubba)  stable_data_change(path, st);
915a1a2009-07-22Henrik Grubbström (Grubba)  return 1;
4cf3b52013-07-05Jonas Walldén  } else if (last_change != 0x7fffffff && st->isdir && status_change(old_st, st, orig_flags, flags)) {
38cd372016-05-10Henrik Grubbström (Grubba)  MON_WERR("Directory not stable yet.\n");
61c4532012-10-24Henrik Grubbström (Grubba)  // Directory not stable yet. last_change = time(1);
7c19572013-07-12Jonas Walldén  update(st);
61c4532012-10-24Henrik Grubbström (Grubba)  return 1;
38cd372016-05-10Henrik Grubbström (Grubba)  } else if (last_change != 0x7fffffff) { MON_WERR("Not stable yet. Age: %d seconds. Path: %O\n", time(1) - last_change, path); } else { MON_WERR("Inode stable still.\n");
915a1a2009-07-22Henrik Grubbström (Grubba)  }
98ceae2015-09-18Henrik Grubbström (Grubba)  return 0; }
3daf4c2015-10-05Henrik Grubbström (Grubba) 
c071bc2017-11-05Henrik Grubbström (Grubba)  protected void _destruct(int cause)
3daf4c2015-10-05Henrik Grubbström (Grubba)  {
c4f1092016-05-17Henrik Grubbström (Grubba)  // NB: Cause #0 == DESTRUCT_EXPLICIT. // Any other cause and unregistering is irrelevant. unregister_path(1 + cause);
3daf4c2015-10-05Henrik Grubbström (Grubba)  }
98ceae2015-09-18Henrik Grubbström (Grubba) }
a3494c2015-10-05Henrik Grubbström (Grubba) //! @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
98ceae2015-09-18Henrik Grubbström (Grubba) // // 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.
46f7e22017-06-14Martin Karlgren protected void low_eventstream_callback(string path, int flags, int event_id)
98ceae2015-09-18Henrik Grubbström (Grubba) {
c83f0f2015-10-06Henrik Grubbström (Grubba)  MON_WERR("eventstream_callback(%O, 0x%08x, %O)\n", path, flags, event_id);
98ceae2015-09-18Henrik Grubbström (Grubba)  if(path[-1] == '/') path = path[0..<1];
c83f0f2015-10-06Henrik Grubbström (Grubba)  MON_WERR("Normalized path: %O\n", path);
d3a5de2015-10-16Martin Karlgren  int monitor_flags; if (flags & System.FSEvents.kFSEventStreamEventFlagMustScanSubDirs)
cf1fd02016-03-18Jonas Walldén  monitor_flags |= MF_RECURSE;
d3a5de2015-10-16Martin Karlgren  int found; string checkpath = path; for (int i = 0; i <= 1; i++) { MON_WERR("Looking up monitor for path %O.\n", checkpath); if(Monitor m = monitors[checkpath]) { MON_WERR("Found monitor %O for path %O.\n", m, checkpath); m->check(monitor_flags); found = 1; break; } checkpath = dirname (checkpath);
98ceae2015-09-18Henrik Grubbström (Grubba)  }
d3a5de2015-10-16Martin Karlgren  if (!found)
49e7a82017-07-18Henrik Grubbström (Grubba)  MON_WARN("No monitor found for path %O.\n", path);
98ceae2015-09-18Henrik Grubbström (Grubba) }
46f7e22017-06-14Martin Karlgren protected void eventstream_callback(string path, int flags, int event_id) { if (backend) backend->call_out(low_eventstream_callback, 0, path, flags, event_id); else low_eventstream_callback(path, flags, event_id); }
375b3b2017-06-21Henrik Grubbström (Grubba) protected void start_accelerator()
98ceae2015-09-18Henrik Grubbström (Grubba) {
9a09872017-06-21Henrik Grubbström (Grubba)  // Make sure that the main backend is in CF-mode. Pike.DefaultBackend.enable_core_foundation(1);
98ceae2015-09-18Henrik Grubbström (Grubba) 
9a09872017-06-21Henrik Grubbström (Grubba)  MON_WERR("Creating event stream.\n"); #if constant (System.FSEvents.kFSEventStreamCreateFlagFileEvents) int flags = System.FSEvents.kFSEventStreamCreateFlagFileEvents; #else int flags = System.FSEvents.kFSEventStreamCreateFlagNone; #endif
98c3672016-03-07Henrik Grubbström (Grubba) 
6bfe6c2017-06-30Henrik Grubbström (Grubba)  // Note: We let the polling system find the initial contents of // any pre-existing directories.
9a09872017-06-21Henrik Grubbström (Grubba)  eventstream =
6bfe6c2017-06-30Henrik Grubbström (Grubba)  System.FSEvents.EventStream(({}), 0.1,
9a09872017-06-21Henrik Grubbström (Grubba)  System.FSEvents.kFSEventStreamEventIdSinceNow, flags); eventstream->callback_func = eventstream_callback; }
c83f0f2015-10-06Henrik Grubbström (Grubba) 
375b3b2017-06-21Henrik Grubbström (Grubba) //! FSEvents EventStream-accelerated @[Monitor].
9a09872017-06-21Henrik Grubbström (Grubba) protected class EventStreamMonitor { inherit Monitor;
c83f0f2015-10-06Henrik Grubbström (Grubba) 
98ceae2015-09-18Henrik Grubbström (Grubba)  protected void register_path(int|void initial) {
53301d2015-10-06Henrik Grubbström (Grubba) #ifndef INHIBIT_EVENTSTREAM_MONITOR
9a09872017-06-21Henrik Grubbström (Grubba)  if (initial) { if (Pike.DefaultBackend.executing_thread() != Thread.this_thread()) { // eventstream stuff (especially start()) must be called from // the backend thread, otherwise events will be fired in // CFRunLoop contexts where noone listens.
49e7a82017-07-18Henrik Grubbström (Grubba)  MON_WERR("%O: Switching to backend thread.\n", this_function);
9a09872017-06-21Henrik Grubbström (Grubba)  call_out(register_path, 0, initial); return; }
46f7e22017-06-14Martin Karlgren 
9a09872017-06-21Henrik Grubbström (Grubba)  // We're now in the main backend.
08e9492015-10-06Henrik Grubbström (Grubba) 
9a09872017-06-21Henrik Grubbström (Grubba)  if (!eventstream) {
375b3b2017-06-21Henrik Grubbström (Grubba)  start_accelerator();
9a09872017-06-21Henrik Grubbström (Grubba)  }
d3a5de2015-10-16Martin Karlgren 
08e9492015-10-06Henrik Grubbström (Grubba)  string found;
98ceae2015-09-18Henrik Grubbström (Grubba)  foreach(eventstream_paths;;string p) { if((path == p) || has_prefix(path, p + "/")) {
08e9492015-10-06Henrik Grubbström (Grubba)  MON_WERR("Path %O already monitored via path %O.\n", path, p); found = p;
79f8592016-03-17Jonas Walldén  break;
98ceae2015-09-18Henrik Grubbström (Grubba)  } }
08e9492015-10-06Henrik Grubbström (Grubba)  if (found) {
375b3b2017-06-21Henrik Grubbström (Grubba)  MON_WERR("Path %O is accelerated via %O.\n", path, found);
9a09872017-06-21Henrik Grubbström (Grubba)  } else { // NB: Eventstream doesn't notify on the monitored path; // only on its contents. mixed err = catch { 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(); };
95dd362015-11-19Martin Karlgren 
9a09872017-06-21Henrik Grubbström (Grubba)  if (err) {
49e7a82017-07-18Henrik Grubbström (Grubba)  MON_ERROR("%O: Failed to register path %O.\n", this_function, path);
9a09872017-06-21Henrik Grubbström (Grubba)  master()->handle_error(err); } } // Note: Falling through to ::register_path() below. // This is needed to handle paths mounted on eg network // filesystems that are modified on other machines.
6bfe6c2017-06-30Henrik Grubbström (Grubba)  // It is also used to find the initial contents of a monitored directory.
95dd362015-11-19Martin Karlgren  }
53301d2015-10-06Henrik Grubbström (Grubba) #endif /* !INHIBIT_EVENTSTREAM_MONITOR */
08e9492015-10-06Henrik Grubbström (Grubba)  ::register_path(initial);
98ceae2015-09-18Henrik Grubbström (Grubba)  } }
a3494c2015-10-05Henrik Grubbström (Grubba) constant DefaultMonitor = EventStreamMonitor;
98ceae2015-09-18Henrik Grubbström (Grubba) #elseif HAVE_INOTIFY protected System.Inotify._Instance instance;
81cdad2015-10-06Henrik Grubbström (Grubba) protected string(8bit) inotify_cookie(int wd) { // NB: Prefix with a NUL to make sure not to conflict with real paths. return sprintf("\0%8c", wd); }
9bdd0f2015-10-07Henrik Grubbström (Grubba) //! Event callback for Inotify. protected void inotify_event(int wd, int event, int cookie, string(8bit) path)
98ceae2015-09-18Henrik Grubbström (Grubba) {
9bdd0f2015-10-07Henrik Grubbström (Grubba)  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])) {
93ba2f2015-10-20Martin Karlgren  if (sizeof (path)) {
739ba72017-07-17Henrik Grubbström (Grubba)  string full_path = canonic_path(Stdio.append_path(m->path, path));
93ba2f2015-10-20Martin Karlgren  // We're interested in the sub monitor, if it exists.
739ba72017-07-17Henrik Grubbström (Grubba)  if (Monitor submon = monitors[full_path]) {
e809882018-04-05Martin Karlgren  MON_WERR ("inotify_event: Got submonitor %O.\n", submon);
93ba2f2015-10-20Martin Karlgren  m = submon;
e809882018-04-05Martin Karlgren  } else { MON_WERR ("inotify_event: Forcing check of %O.\n", m); // No monitor exists for the path yet (typically happens for // IN_CREATE events). Force a check on the directory monitor. m->check(m->flags); // Try again after directory check. if (Monitor submon2 = monitors[full_path]) { MON_WERR ("inotify_event: Got submonitor %O on retry.\n", submon2); m = submon2; } else { MON_WERR ("inotify_event: Failed to get monitor for file %s " "in %O.\n", path, m); }
739ba72017-07-17Henrik Grubbström (Grubba)  }
93ba2f2015-10-20Martin Karlgren  } } if (m) {
9bdd0f2015-10-07Henrik Grubbström (Grubba)  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); }
98ceae2015-09-18Henrik Grubbström (Grubba)  mixed err = catch {
93ba2f2015-10-20Martin Karlgren  if (event & System.Inotify.IN_CLOSE_WRITE) // File marked as stable immediately. m->last_change = -0x7fffffff;
9bdd0f2015-10-07Henrik Grubbström (Grubba)  m->check(0);
98ceae2015-09-18Henrik Grubbström (Grubba)  }; if (err) {
6d55162016-05-10Henrik Grubbström (Grubba)  master()->handle_error(err);
98ceae2015-09-18Henrik Grubbström (Grubba)  }
9bdd0f2015-10-07Henrik Grubbström (Grubba)  } else {
739ba72017-07-17Henrik Grubbström (Grubba)  // Most likely not reached.
49e7a82017-07-18Henrik Grubbström (Grubba)  MON_WARN("Monitor not found for cookie %O, path %O.\n", icookie, path);
915a1a2009-07-22Henrik Grubbström (Grubba)  }
fe76e22009-07-13Henrik Grubbström (Grubba) }
375b3b2017-06-21Henrik Grubbström (Grubba) protected void start_accelerator()
9a09872017-06-21Henrik Grubbström (Grubba) { MON_WERR("Creating Inotify monitor instance.\n"); instance = System.Inotify._Instance(); 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(); } }
375b3b2017-06-21Henrik Grubbström (Grubba) //! Inotify-accelerated @[Monitor].
98ceae2015-09-18Henrik Grubbström (Grubba) protected class InotifyMonitor { inherit Monitor; protected int wd = -1;
5bb8e22016-02-08Henrik Grubbström (Grubba)  protected int(0..) out_of_inotify_space;
98ceae2015-09-18Henrik Grubbström (Grubba)  protected void register_path(int|void initial) {
069ebc2015-10-05Henrik Grubbström (Grubba) #ifndef INHIBIT_INOTIFY_MONITOR
9a09872017-06-21Henrik Grubbström (Grubba)  if (wd == -1) { if (!instance) {
375b3b2017-06-21Henrik Grubbström (Grubba)  start_accelerator();
9bdd0f2015-10-07Henrik Grubbström (Grubba)  }
98ceae2015-09-18Henrik Grubbström (Grubba) 
9a09872017-06-21Henrik Grubbström (Grubba)  // NB: We need to follow symlinks here. // Currently we only support changing symlinks and symlinks to directories. // FIXME: Handle broken symlinks where the target later shows up and // symlinks to changing files.
779cdc2017-06-29Henrik Grubbström (Grubba)  Stdio.Stat st = file_stat(path, 1);
9a09872017-06-21Henrik Grubbström (Grubba)  mixed err; if (st && (!(flags & MF_AUTO) || st->isdir)) { // Note: We only want to add watchers on directories. File // notifications will take place on the directory watch // descriptors. Expansion of the path to cover notifications // on individual files is handled in the inotify_event // callback. if (err = 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 | System.Inotify.IN_CLOSE_WRITE); if (new_wd != -1) { MON_WERR("Registered %O with %O ==> %d.\n", path, instance, new_wd); out_of_inotify_space = 0; wd = new_wd; monitors[inotify_cookie(wd)] = this;
93ba2f2015-10-20Martin Karlgren  }
9a09872017-06-21Henrik Grubbström (Grubba)  }) { if (!has_value(lower_case(describe_error(err)), "no space left")) { master()->handle_error(err); } else if (!(out_of_inotify_space++ % 100)) { werror("%O: Out of inotify space (%d attempts):\n", this_function, out_of_inotify_space); master()->handle_error(err); werror("Consider increasing '/proc/sys/fs/inotify/max_user_watches'.\n");
93ba2f2015-10-20Martin Karlgren  }
5bb8e22016-02-08Henrik Grubbström (Grubba)  }
98ceae2015-09-18Henrik Grubbström (Grubba)  }
93ba2f2015-10-20Martin Karlgren  }
069ebc2015-10-05Henrik Grubbström (Grubba) #endif /* !INHIBIT_INOTIFY_MONITOR */
98ceae2015-09-18Henrik Grubbström (Grubba)  ::register_path(initial); } protected void unregister_path(int|void dying) { if (wd != -1) {
0278482015-10-02Henrik Grubbström (Grubba)  // NB: instance may be null if the main object has been destructed
c071bc2017-11-05Henrik Grubbström (Grubba)  // and we've been called via a _destruct().
dde38c2015-10-06Henrik Grubbström (Grubba)  if (instance && dying) {
3daf4c2015-10-05Henrik Grubbström (Grubba)  MON_WERR("### Unregistering from inotify.\n");
dde38c2015-10-06Henrik Grubbström (Grubba)  // 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) {
49e7a82017-07-18Henrik Grubbström (Grubba)  MON_WARN("### Failed to unregister %O: %s\n",
cf2cd72017-06-27Henrik Grubbström (Grubba)  path, describe_backtrace(err));
dde38c2015-10-06Henrik Grubbström (Grubba)  }
0278482015-10-02Henrik Grubbström (Grubba)  }
98ceae2015-09-18Henrik Grubbström (Grubba)  wd = -1; } ::unregister_path(dying); } }
a3494c2015-10-05Henrik Grubbström (Grubba)  constant DefaultMonitor = InotifyMonitor; #else constant DefaultMonitor = Monitor;
98ceae2015-09-18Henrik Grubbström (Grubba) #endif /* HAVE_EVENTSTREAM || HAVE_INOTIFY */
d5ecf22009-07-22Henrik Grubbström (Grubba) //! 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) {
08e9492015-10-06Henrik Grubbström (Grubba) #if HAVE_EVENTSTREAM if (!backend) {
9d26732015-10-15Martin Karlgren  catch { path = System.resolvepath(path); };
08e9492015-10-06Henrik Grubbström (Grubba)  } #endif
d5ecf22009-07-22Henrik Grubbström (Grubba)  return combine_path(path, "."); }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Mapping from monitored path to corresponding @[Monitor].
3d59782009-07-15Henrik Grubbström (Grubba) //!
d5ecf22009-07-22Henrik Grubbström (Grubba) //! The paths are normalized to @expr{canonic_path(path)@},
3d59782009-07-15Henrik Grubbström (Grubba) //! //! @note //! All filesystems are handled as if case-sensitive. This should //! not be a problem for case-insensitive filesystems as long as //! case is maintained.
fe76e22009-07-13Henrik Grubbström (Grubba) protected mapping(string:Monitor) monitors = ([]);
98ceae2015-09-18Henrik Grubbström (Grubba) //! Heap containing active @[Monitor]s that need polling.
fe76e22009-07-13Henrik Grubbström (Grubba) //! //! The heap is sorted on @[Monitor()->next_poll]. protected ADT.Heap monitor_queue = ADT.Heap();
07ea532016-02-15Henrik Grubbström (Grubba) //! Mutex controlling access to @[monitor_queue]. protected Thread.Mutex monitor_mutex = Thread.Mutex();
fe76e22009-07-13Henrik Grubbström (Grubba) //! Create a new monitor. //! //! @param max_dir_check_interval //! Override of @[default_max_dir_check_interval]. //! //! @param file_interval_factor //! Override of @[default_file_interval_factor].
f3483e2009-07-16Henrik Grubbström (Grubba) //! //! @param stable_time //! Override of @[default_stable_time].
fe76e22009-07-13Henrik Grubbström (Grubba) protected void create(int|void max_dir_check_interval,
f3483e2009-07-16Henrik Grubbström (Grubba)  int|void file_interval_factor, int|void stable_time)
fe76e22009-07-13Henrik Grubbström (Grubba) { if (max_dir_check_interval > 0) {
8e06a32014-09-30Martin Nilsson  this::max_dir_check_interval = max_dir_check_interval;
fe76e22009-07-13Henrik Grubbström (Grubba)  } if (file_interval_factor > 0) {
8e06a32014-09-30Martin Nilsson  this::file_interval_factor = file_interval_factor;
fe76e22009-07-13Henrik Grubbström (Grubba)  }
f3483e2009-07-16Henrik Grubbström (Grubba)  if (stable_time > 0) {
8e06a32014-09-30Martin Nilsson  this::stable_time = stable_time;
f3483e2009-07-16Henrik Grubbström (Grubba)  }
fe76e22009-07-13Henrik Grubbström (Grubba)  clear(); }
c071bc2017-11-05Henrik Grubbström (Grubba) protected void _destruct()
b160112015-10-15Martin Karlgren { // Destruct monitors before we're destructed ourselves, since they // will attempt to unregister with us. foreach (monitors;; Monitor m) destruct (m); }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Clear the set of monitored files and directories.
915a1a2009-07-22Henrik Grubbström (Grubba) //! //! @note //! Due to circular datastructures, it's recomended //! to call this function prior to discarding the object.
fe76e22009-07-13Henrik Grubbström (Grubba) void clear() {
07ea532016-02-15Henrik Grubbström (Grubba)  mixed key = monitor_mutex->lock();
fe76e22009-07-13Henrik Grubbström (Grubba)  monitors = ([]); monitor_queue = ADT.Heap();
98ceae2015-09-18Henrik Grubbström (Grubba) #if HAVE_EVENTSTREAM eventstream = 0; #elseif HAVE_INOTIFY instance = 0; #endif
fe76e22009-07-13Henrik Grubbström (Grubba) } //! Release a single @[Monitor] from monitoring. //! //! @seealso //! @[release()] protected void release_monitor(Monitor m) {
07ea532016-02-15Henrik Grubbström (Grubba)  mixed key = monitor_mutex->lock();
edfb2c2015-09-18Henrik Grubbström (Grubba)  monitor_queue->remove(m);
fe76e22009-07-13Henrik Grubbström (Grubba) }
96a2372015-09-18Henrik Grubbström (Grubba) //! Update the position in the @[monitor_queue] for the monitor @[m] //! to account for an updated next_poll value. protected void adjust_monitor(Monitor m) {
f354d02017-06-22Henrik Grubbström (Grubba)  // NB: May be called with monitors not on the monitor_queue due // to double checks when using acceleration. if (m->pos < 0) return; // Not on the monitor_queue.
07ea532016-02-15Henrik Grubbström (Grubba)  mixed key = monitor_mutex->lock();
f354d02017-06-22Henrik Grubbström (Grubba)  if (m->pos < 0) return; // Not on the monitor_queue any more (race).
96a2372015-09-18Henrik Grubbström (Grubba)  monitor_queue->adjust(m);
c6500d2018-02-07Henrik Grubbström (Grubba)  if (monitor_queue->peek() != m) {
f354d02017-06-22Henrik Grubbström (Grubba)  return; }
b71d992018-02-07Henrik Grubbström (Grubba)  if (co_id) {
84415c2017-06-21Henrik Grubbström (Grubba)  // Nonblocking mode and we need to poll earlier, // so reschedule the call_out.
c6500d2018-02-07Henrik Grubbström (Grubba)  reschedule_backend_check();
84415c2017-06-21Henrik Grubbström (Grubba)  }
96a2372015-09-18Henrik Grubbström (Grubba) }
915a1a2009-07-22Henrik Grubbström (Grubba) //! Create a new @[Monitor] for a @[path]. //! //! This function is called by @[monitor()] to create a new @[Monitor] //! object. //!
a3494c2015-10-05Henrik Grubbström (Grubba) //! The default implementation just calls @[DefaultMonitor] with the //! same arguments.
915a1a2009-07-22Henrik Grubbström (Grubba) //! //! @seealso
a3494c2015-10-05Henrik Grubbström (Grubba) //! @[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)
915a1a2009-07-22Henrik Grubbström (Grubba) {
a3494c2015-10-05Henrik Grubbström (Grubba)  return DefaultMonitor(path, flags, max_dir_check_interval,
98ceae2015-09-18Henrik Grubbström (Grubba)  file_interval_factor, stable_time);
915a1a2009-07-22Henrik Grubbström (Grubba) }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Register a @[path] for monitoring. //! //! @param path //! Path to monitor. //!
249a3f2009-07-13Henrik Grubbström (Grubba) //! @param flags
fe76e22009-07-13Henrik Grubbström (Grubba) //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Monitor the entire subtree, and any directories //! or files that may appear later.
249a3f2009-07-13Henrik Grubbström (Grubba) //! @value 3 //! Monitor the entire subtree, and any directories //! or files that may appear later. Remove the monitor //! automatically when @[path] is deleted.
fe76e22009-07-13Henrik Grubbström (Grubba) //! @endint //! //! @param max_dir_check_interval //! Override of @[default_max_dir_check_interval] for this path //! or subtree. //! //! @param file_interval_factor //! Override of @[default_file_interval_factor] for this path //! or subtree. //!
f3483e2009-07-16Henrik Grubbström (Grubba) //! @param stable_time //! Override of @[default_stable_time] for this path //! or subtree. //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! @seealso //! @[release()]
c7fb232017-10-11Karl Gustav Sterneberg Monitor|void monitor(string path, MonitorFlags|void flags,
fe76e22009-07-13Henrik Grubbström (Grubba)  int(0..)|void max_dir_check_interval,
f3483e2009-07-16Henrik Grubbström (Grubba)  int(0..)|void file_interval_factor, int(0..)|void stable_time)
fe76e22009-07-13Henrik Grubbström (Grubba) {
d5ecf22009-07-22Henrik Grubbström (Grubba)  path = canonic_path(path);
15c9822012-06-29Bill Welliver  if(filter_file(path)) return;
52367a2009-07-14Henrik Grubbström (Grubba)  Monitor m = monitors[path]; if (m) { if (!(flags & MF_AUTO)) { // The new monitor is added by hand. // Adjust the monitor. m->flags = flags; m->max_dir_check_interval = max_dir_check_interval; m->file_interval_factor = file_interval_factor;
f3483e2009-07-16Henrik Grubbström (Grubba)  m->stable_time = stable_time;
52367a2009-07-14Henrik Grubbström (Grubba)  m->next_poll = 0;
96a2372015-09-18Henrik Grubbström (Grubba)  adjust_monitor(m);
52367a2009-07-14Henrik Grubbström (Grubba)  }
b1f9342010-02-02Henrik Grubbström (Grubba)  if (flags & MF_HARD) { m->flags |= MF_HARD; }
52367a2009-07-14Henrik Grubbström (Grubba)  // For the other cases there's no need to do anything, // since we can keep the monitor as-is. } else {
915a1a2009-07-22Henrik Grubbström (Grubba)  m = monitor_factory(path, flags, max_dir_check_interval, file_interval_factor, stable_time);
52367a2009-07-14Henrik Grubbström (Grubba)  monitors[path] = m;
98ceae2015-09-18Henrik Grubbström (Grubba)  // NB: Registering with the monitor_queue is done as // needed by register_path() as called by create().
52367a2009-07-14Henrik Grubbström (Grubba)  }
c7fb232017-10-11Karl Gustav Sterneberg  return m;
fe76e22009-07-13Henrik Grubbström (Grubba) }
15c9822012-06-29Bill Welliver int filter_file(string path) { array x = path/"/"; foreach(x;; string pc)
3daf4c2015-10-05Henrik Grubbström (Grubba)  if(pc && strlen(pc) && pc[0]=='.' && pc != "..") { MON_WERR("skipping %O\n", path); return 1; }
3524712015-05-26Martin Nilsson 
15c9822012-06-29Bill Welliver  return 0; }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Release a @[path] from monitoring. //! //! @param path //! Path to stop monitoring. //!
249a3f2009-07-13Henrik Grubbström (Grubba) //! @param flags
fe76e22009-07-13Henrik Grubbström (Grubba) //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Release the entire subtree.
249a3f2009-07-13Henrik Grubbström (Grubba) //! @value 3 //! Release the entire subtree, but only those paths that were //! added automatically by a recursive monitor.
fe76e22009-07-13Henrik Grubbström (Grubba) //! @endint //! //! @seealso //! @[monitor()]
249a3f2009-07-13Henrik Grubbström (Grubba) void release(string path, MonitorFlags|void flags)
fe76e22009-07-13Henrik Grubbström (Grubba) {
d5ecf22009-07-22Henrik Grubbström (Grubba)  path = canonic_path(path);
fe76e22009-07-13Henrik Grubbström (Grubba)  Monitor m = m_delete(monitors, path);
74bc992015-09-18Henrik Grubbström (Grubba)  if (!m) return; release_monitor(m);
249a3f2009-07-13Henrik Grubbström (Grubba)  if (flags && m->st && m->st->isdir) {
d5ecf22009-07-22Henrik Grubbström (Grubba)  if (!sizeof(path) || path[-1] != '/') { path += "/"; }
fe76e22009-07-13Henrik Grubbström (Grubba)  foreach(monitors; string mpath; m) {
b1f9342010-02-02Henrik Grubbström (Grubba)  if (has_prefix(mpath, path)) { m->check_for_release(flags, flags);
fe76e22009-07-13Henrik Grubbström (Grubba)  } } } }
9fc4072009-09-21Henrik Grubbström (Grubba) //! Check whether a path is monitored or not.
136b3b2009-09-21Henrik Grubbström (Grubba) //! //! @param path
9fc4072009-09-21Henrik Grubbström (Grubba) //! Path to check. //! //! @returns //! Returns @expr{1@} if there is a monitor on @[path], //! and @expr{0@} (zero) otherwise. //! //! @seealso //! @[monitor()], @[release()]
136b3b2009-09-21Henrik Grubbström (Grubba) int(0..1) is_monitored(string path) { return !!monitors[canonic_path(path)]; }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Check a single @[Monitor] for changes. //! //! @param m //! @[Monitor] to check. //!
80f66e2009-07-14Henrik Grubbström (Grubba) //! @param flags //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Check all monitors for the entire subtree rooted in @[m]. //! @endint //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! This function is called by @[check()] for the @[Monitor]s //! it considers need checking. If it detects any changes an //! appropriate callback will be called. //! //! @returns //! Returns @expr{1@} if a change was detected and @expr{0@} (zero) //! otherwise. //! //! @note //! Any callbacks will be called from the same thread as the one //! calling @[check_monitor()]. //!
80f66e2009-07-14Henrik Grubbström (Grubba) //! @note //! The return value can not be trusted to return @expr{1@} for all //! detected changes in recursive mode. //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! @seealso //! @[check()], @[data_changed()], @[attr_changed()], @[file_created()], //! @[file_deleted()], @[stable_data_change()]
80f66e2009-07-14Henrik Grubbström (Grubba) protected int(0..1) check_monitor(Monitor m, MonitorFlags|void flags)
fe76e22009-07-13Henrik Grubbström (Grubba) {
4af8f32016-03-18Jonas Walldén  return m && m->check(flags);
fe76e22009-07-13Henrik Grubbström (Grubba) }
9c9d242015-09-18Henrik Grubbström (Grubba) //! Check all monitors for changes. //! //! @param ret_stats //! Optional mapping that will be filled with statistics (see below). //! //! All monitored paths will be checked for changes. //! //! @note //! You typically don't want to call this function, but instead //! @[check()]. //! //! @note //! Any callbacks will be called from the same thread as the one //! 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++;
dde38c2015-10-06Henrik Grubbström (Grubba)  mixed err = catch { cnt += check_monitor(m); }; if (err) { master()->handle_error(err); }
9c9d242015-09-18Henrik Grubbström (Grubba)  } 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; } }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Check for changes. //! //! @param max_wait
fb4faa2009-08-04Henrik Grubbström (Grubba) //! Maximum time in seconds to wait for changes. @expr{-1@}
fe76e22009-07-13Henrik Grubbström (Grubba) //! for infinite wait. //!
010d1c2009-08-06Henrik Grubbström (Grubba) //! @param max_cnt //! Maximum number of paths to check in this call. @expr{0@} //! (zero) for unlimited. //!
1819432009-10-12Henrik Grubbström (Grubba) //! @param ret_stats //! Optional mapping that will be filled with statistics (see below). //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! A suitable subset of the monitored files will be checked //! for changes. //! //! @returns //! The function returns when either a change has been detected
fb4faa2009-08-04Henrik Grubbström (Grubba) //! or when @[max_wait] has expired. The returned value indicates //! the number of seconds until the next call of @[check()].
fe76e22009-07-13Henrik Grubbström (Grubba) //!
1819432009-10-12Henrik Grubbström (Grubba) //! If @[ret_stats] has been provided, it will be filled with //! the following entries: //! @mapping //! @member int "num_monitors" //! The total number of active monitors when the scan completed. //! @member int "scanned_monitors" //! The number of monitors that were scanned for updates during the call. //! @member int "updated_monitors" //! The number of monitors that were updated during the call. //! @member int "idle_time" //! The number of seconds that the call slept. //! @endmapping //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! @note //! Any callbacks will be called from the same thread as the one //! calling @[check()]. //! //! @seealso
9c9d242015-09-18Henrik Grubbström (Grubba) //! @[check_all()], @[monitor()]
1819432009-10-12Henrik Grubbström (Grubba) int check(int|void max_wait, int|void max_cnt, mapping(string:int)|void ret_stats)
fe76e22009-07-13Henrik Grubbström (Grubba) {
f4370b2015-10-08Henrik Grubbström (Grubba) #if HAVE_INOTIFY if (instance) { /* FIXME: No statistics currently available. */ instance->poll(); } #endif
1819432009-10-12Henrik Grubbström (Grubba)  int scan_cnt = max_cnt; int scan_wait = max_wait;
fe76e22009-07-13Henrik Grubbström (Grubba)  while(1) {
fb4faa2009-08-04Henrik Grubbström (Grubba)  int ret = max_dir_check_interval;
fe76e22009-07-13Henrik Grubbström (Grubba)  int cnt; int t = time();
69e2c12015-09-18Henrik Grubbström (Grubba)  if (sizeof(monitor_queue)) {
de7a612016-02-09Henrik Grubbström (Grubba)  // NB: peek() can apparently in some circumstances throw errors. // cf [bug 7644]. The likely cause being that a different // thread removed the last element during the call. Make // sure not to propagate the error to the caller. mixed err = catch { Monitor m; while ((m = monitor_queue->peek()) && (m->next_poll <= t)) { cnt += check_monitor(m); if (!(--scan_cnt)) { m = monitor_queue->peek(); break; } } if (m) { ret = m->next_poll - t; if (ret <= 0) ret = 1; } else { scan_cnt--; } }; if (err) { master()->handle_error(err);
1819432009-10-12Henrik Grubbström (Grubba)  } } if (cnt || !scan_wait || !scan_cnt) { if (ret_stats) { ret_stats->num_monitors = sizeof(monitors); ret_stats->scanned_monitors = max_cnt - scan_cnt; ret_stats->updated_monitors = cnt; ret_stats->idle_time = max_wait - scan_wait;
fb4faa2009-08-04Henrik Grubbström (Grubba)  }
1819432009-10-12Henrik Grubbström (Grubba)  return ret;
fb4faa2009-08-04Henrik Grubbström (Grubba)  }
1819432009-10-12Henrik Grubbström (Grubba)  if (ret < scan_wait) { scan_wait -= ret;
fb4faa2009-08-04Henrik Grubbström (Grubba)  sleep(ret); } else {
1819432009-10-12Henrik Grubbström (Grubba)  if (scan_wait > 0) scan_wait--;
fb4faa2009-08-04Henrik Grubbström (Grubba)  sleep(1);
fe76e22009-07-13Henrik Grubbström (Grubba)  } } } //! Backend to use.
90569b2009-07-13Henrik Grubbström (Grubba) //! //! If @expr{0@} (zero) - use the default backend.
fe76e22009-07-13Henrik Grubbström (Grubba) protected Pike.Backend backend; //! Call-out identifier for @[backend_check()] if in //! nonblocking mode. //! //! @seealso //! @[set_nonblocking()], @[set_blocking()] protected mixed co_id; //! 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();
8e06a32014-09-30Martin Nilsson  this::backend = backend;
cc4b822015-10-06Henrik Grubbström (Grubba) #if HAVE_EVENTSTREAM #elif HAVE_INOTIFY
9bdd0f2015-10-07Henrik Grubbström (Grubba)  if (instance) { instance->set_backend(backend || Pike.DefaultBackend);
b2e3032015-09-18Henrik Grubbström (Grubba)  } #endif
fe76e22009-07-13Henrik Grubbström (Grubba)  if (was_nonblocking) { set_nonblocking(); } }
90569b2009-07-13Henrik Grubbström (Grubba) //! Turn off nonblocking mode. //! //! @seealso //! @[set_nonblocking()]
fe76e22009-07-13Henrik Grubbström (Grubba) void set_blocking() { if (co_id) { if (backend) backend->remove_call_out(co_id); else remove_call_out(co_id); co_id = 0; }
9bdd0f2015-10-07Henrik Grubbström (Grubba) #if HAVE_INOTIFY if (instance) { instance->set_blocking(); } #endif
fe76e22009-07-13Henrik Grubbström (Grubba) }
90569b2009-07-13Henrik Grubbström (Grubba) //! 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()]
fe76e22009-07-13Henrik Grubbström (Grubba) protected void backend_check() {
b71d992018-02-07Henrik Grubbström (Grubba)  co_id = 0;
fb4faa2009-08-04Henrik Grubbström (Grubba)  int t;
fe76e22009-07-13Henrik Grubbström (Grubba)  mixed err = catch {
fb4faa2009-08-04Henrik Grubbström (Grubba)  t = check(0);
fe76e22009-07-13Henrik Grubbström (Grubba)  };
fb4faa2009-08-04Henrik Grubbström (Grubba)  set_nonblocking(t);
fe76e22009-07-13Henrik Grubbström (Grubba)  if (err) throw(err); }
7cd26e2018-02-07Henrik Grubbström (Grubba) //! Reschedule beckend check. //! //! @param suggested_t //! Suggested time in seconds until next call of @[check()]. //! //! Register suitable callbacks with the backend to automatically //! call @[check()]. //! //! @[check()] and thus all the callbacks will be called from the //! backend thread. protected void reschedule_backend_check(int|void suggested_t) { // NB: Other stuff than plain files may be used with the monitoring // system, so the call_out may be needed even with accelerators. // // NB: Also note that Inotify doesn't support monitoring of non-existing // paths, so it still needs the call_out-loop. MON_WERR("Rescheduling call_out.\n"); int t = max_dir_check_interval; if (sizeof(monitor_queue)) { Monitor m = monitor_queue->peek(); if (m) { t = m->next_poll - time(1); } if (t > max_dir_check_interval) t = max_dir_check_interval; } if (!undefinedp(suggested_t) && (suggested_t < t)) { t = suggested_t; } if (t < 0) t = 0; if (co_id) { if (backend) backend->remove_call_out(co_id); else remove_call_out(co_id); } if (backend) co_id = backend->call_out(backend_check, t); else co_id = call_out(backend_check, t); }
90569b2009-07-13Henrik Grubbström (Grubba) //! Turn on nonblocking mode. //!
7cd26e2018-02-07Henrik Grubbström (Grubba) //! @param suggested_t
fb4faa2009-08-04Henrik Grubbström (Grubba) //! Suggested time in seconds until next call of @[check()]. //!
90569b2009-07-13Henrik Grubbström (Grubba) //! Register suitable callbacks with the backend to automatically //! call @[check()]. //! //! @[check()] and thus all the callbacks will be called from the //! backend thread. //!
fb4faa2009-08-04Henrik Grubbström (Grubba) //! @note //! If nonblocking mode is already active, this function will //! be a noop. //!
90569b2009-07-13Henrik Grubbström (Grubba) //! @seealso //! @[set_blocking()], @[check()].
7cd26e2018-02-07Henrik Grubbström (Grubba) void set_nonblocking(int|void suggested_t)
fe76e22009-07-13Henrik Grubbström (Grubba) {
9bdd0f2015-10-07Henrik Grubbström (Grubba) #if HAVE_INOTIFY if (instance) { instance->set_nonblocking(); } #endif
fe76e22009-07-13Henrik Grubbström (Grubba)  if (co_id) return;
7cd26e2018-02-07Henrik Grubbström (Grubba)  reschedule_backend_check(suggested_t);
fe76e22009-07-13Henrik Grubbström (Grubba) }
d951102009-07-17Henrik Grubbström (Grubba) 
4836932018-04-05Martin Karlgren //! Set the @[max_dir_check_interval].
d951102009-07-17Henrik Grubbström (Grubba) void set_max_dir_check_interval(int max_dir_check_interval) { if (max_dir_check_interval > 0) {
8e06a32014-09-30Martin Nilsson  this::max_dir_check_interval = max_dir_check_interval;
d951102009-07-17Henrik Grubbström (Grubba)  } else {
8e06a32014-09-30Martin Nilsson  this::max_dir_check_interval = default_max_dir_check_interval;
d951102009-07-17Henrik Grubbström (Grubba)  } }
4836932018-04-05Martin Karlgren //! Set the @[file_interval_factor].
d951102009-07-17Henrik Grubbström (Grubba) void set_file_interval_factor(int file_interval_factor) { if (file_interval_factor > 0) {
8e06a32014-09-30Martin Nilsson  this::file_interval_factor = file_interval_factor;
d951102009-07-17Henrik Grubbström (Grubba)  } else {
8e06a32014-09-30Martin Nilsson  this::file_interval_factor = default_file_interval_factor;
d951102009-07-17Henrik Grubbström (Grubba)  } }
4836932018-04-05Martin Karlgren  //! Set the @[stable_time]. void set_stable_time (int stable_time) { if (stable_time > 0) { this::stable_time = stable_time; } else { this::stable_time = default_stable_time; } }