diff --git a/.gitattributes b/.gitattributes
index 27466cb95a3fc40a242285d60c25616105103ec8..409a3ed01217d18cbbe4812674292375e3eb8e22 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -108,6 +108,8 @@ testfont binary
 /lib/modules/Debug.pmod/werror.pmod foreign_ident
 /lib/modules/Error.pmod foreign_ident
 /lib/modules/Float.pmod foreign_ident
+/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike foreign_ident
+/lib/modules/Filesystem.pmod/Monitor.pmod/symlinks.pike foreign_ident
 /lib/modules/GLU.pmod foreign_ident
 /lib/modules/GLUE.pmod/Driver.pmod/GTK.pike foreign_ident
 /lib/modules/GLUE.pmod/Driver.pmod/Interface.pike foreign_ident
diff --git a/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike b/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike
new file mode 100644
index 0000000000000000000000000000000000000000..c5fe7a185a18e0b895106465c73d6d4ddfd183e1
--- /dev/null
+++ b/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike
@@ -0,0 +1,1078 @@
+//
+// Basic filesystem monitor.
+//
+// $Id: basic.pike,v 1.39 2010/07/14 14:11:08 jonasw Exp $
+//
+// 2009-07-09 Henrik Grubbstr�m
+//
+
+//! Basic filesystem monitor.
+//!
+//! This module is intended to be used for incremental scanning of
+//! a filesystem.
+
+//! 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;
+
+//! The default minimum number of seconds without changes for a change
+//! to be regarded as stable (see @[stable_data_change()].
+protected constant default_stable_time = 5;
+
+protected int max_dir_check_interval = default_max_dir_check_interval;
+protected int file_interval_factor = default_file_interval_factor;
+protected int stable_time = default_stable_time;
+
+// Callbacks
+
+//! 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);
+
+//! 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
+//!   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).
+//!
+//! Overload this to do something useful.
+void file_exists(string path, Stdio.Stat st);
+
+//! 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.
+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.
+void file_deleted(string path);
+
+//! Stable change callback.
+//!
+//! @param path
+//!   Path of the file or directory that has stopped changing.
+//!
+//! @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()].
+//!
+//! Overload this to do something useful.
+void stable_data_change(string path, Stdio.Stat st);
+
+//! Flags for @[Monitor]s.
+enum MonitorFlags {
+  MF_RECURSE = 1,
+  MF_AUTO = 2,
+  MF_INITED = 4,
+  MF_HARD = 8,
+};
+
+protected constant S_IFMT = 0x7ffff000;
+
+//! Monitoring information for a single filesystem path.
+//!
+//! @seealso
+//!   @[monitor()]
+protected class Monitor(string path,
+			MonitorFlags flags,
+			int max_dir_check_interval,
+			int file_interval_factor,
+			int stable_time)
+{
+  int next_poll;
+  Stdio.Stat st;
+  int last_change = 0x7fffffff;	// Future...
+  array(string) files;
+
+  int `<(mixed m) { return next_poll < m; }
+  int `>(mixed m) { return next_poll > m; }
+
+  //! 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()].
+  //!
+  //! @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)
+  {
+    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).
+  protected void file_exists(string path, Stdio.Stat st)
+  {
+    call_callback(global::file_exists, path, st);
+  }
+
+  //! 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()].
+  protected void file_created(string path, Stdio.Stat st)
+  {
+    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()].
+  protected void file_deleted(string path, Stdio.Stat|void old_st)
+  {
+    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()].
+  protected void stable_data_change(string path, Stdio.Stat st)
+  {
+    call_callback(global::stable_data_change, path, st);
+  }
+
+  protected string _sprintf(int c)
+  {
+    return sprintf("Monitor(%O, %O, next: %s, st: %O)",
+		   path, flags, ctime(next_poll) - "\n", st);
+  }
+
+  //! Bump the monitor to an earlier scan time.
+  //!
+  //! @param seconds
+  //!   Number of seconds to bump. Defaults to @expr{30@}.
+  void bump(int|void flags, int|void seconds)
+  {
+    next_poll -= seconds || 30;
+    monitor_queue->adjust(this);
+
+    if ((flags & MF_RECURSE) && st->isdir && files) {
+      // 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);
+	}
+      }
+    }
+  }
+
+  //! Calculate and set a suitable time for the next poll of this monitor.
+  //!
+  //! @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;
+    this_program::st = st;
+    if (!st || !st->isdir) {
+      delta *= file_interval_factor || global::file_interval_factor;
+    }
+    if (!next_poll) {
+      // Attempt to distribute polls evenly at startup.
+      delta = 1 + random(delta);
+    }
+    if (st) {
+      int d = 1 + ((time(1) - st->mtime)>>8);
+      if (d < 0) d = max_dir_check_interval || global::max_dir_check_interval;
+      if (d < delta) delta = d;
+      d = 1 + ((time(1) - st->ctime)>>8);
+      if (d < 0) d = max_dir_check_interval || global::max_dir_check_interval;
+      if (d < delta) delta = d;
+    }
+    if (last_change <= time(1)) {
+      // Time until stable.
+      int d = last_change + (stable_time || global::stable_time) - time(1);
+      d >>= 1;
+      if (d < 0) d = 1;
+      if (d < delta) delta = d;
+    }
+    next_poll = time(1) + (delta || 1);
+    monitor_queue->adjust(this);
+  }
+
+  //! Check if this monitor should be removed automatically.
+  void check_for_release(int mask, int flags)
+  {
+    if ((this_program::flags & mask) == flags) {
+      m_delete(monitors, path);
+      release_monitor(this);
+    }
+  }
+
+  //! 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);
+  }
+
+  //! Called when the status has changed for an existing file.
+  protected int(0..1) status_change(Stdio.Stat old_st, Stdio.Stat st,
+				    int orig_flags, int flags)
+  {
+    if (st->isdir) {
+      array(string) files = get_dir(path) || ({});
+      array(string) new_files = files;
+      array(string) deleted_files = ({});
+      if (this_program::files) {
+	new_files -= this_program::files;
+	deleted_files = this_program::files - files;
+      }
+      this_program::files = files;
+      foreach(new_files, string file) {
+	file = canonic_path(Stdio.append_path(path, file));
+	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);
+	    }
+	  };
+	if (this_program::flags & MF_RECURSE) {
+	  monitor(file, orig_flags | MF_AUTO | MF_HARD,
+		  max_dir_check_interval,
+		  file_interval_factor,
+		  stable_time);
+	  monitors[file]->check();
+	} else if (!m2) {
+	  file_created(file, file_stat(file, 1));
+	}
+      }
+      foreach(deleted_files, string file) {
+	file = canonic_path(Stdio.append_path(path, file));
+	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);
+	    }
+	  };
+	if (this_program::flags & MF_RECURSE) {
+	  // 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);
+	}
+	if (err) throw(err);
+      }
+      if (flags & MF_RECURSE) {
+	// Check the remaining files in the directory soon.
+	foreach(((files - new_files) - deleted_files), string file) {
+	  file = canonic_path(Stdio.append_path(path, file));
+	  Monitor m2 = monitors[file];
+	  if (m2) {
+	    m2->bump(flags);
+	  }
+	}
+      }
+      if (sizeof(new_files) || sizeof(deleted_files)) return 1;
+    } else {
+      attr_changed(path, st);
+      return 1;
+    }
+    return 0;
+  }
+
+  //! 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)
+  {
+    // werror("Checking monitor %O...\n", this);
+    Stdio.Stat st = file_stat(path, 1);
+    Stdio.Stat old_st = this_program::st;
+    int orig_flags = this_program::flags;
+    this_program::flags |= MF_INITED;
+    update(st);
+    if (!(orig_flags & MF_INITED)) {
+      // Initialize.
+      if (st) {
+	if (st->isdir) {
+	  array(string) files = get_dir(path) || ({});
+	  this_program::files = files;
+	  foreach(files, string file) {
+	    file = canonic_path(Stdio.append_path(path, file));
+	    if (monitors[file]) {
+	      // There's already a monitor for the file.
+	      // Assume it has already notified about existance.
+	      continue;
+	    }
+	    if (this_program::flags & MF_RECURSE) {
+	      monitor(file, orig_flags | MF_AUTO | MF_HARD,
+		      max_dir_check_interval,
+		      file_interval_factor,
+		      stable_time);
+	      check_monitor(monitors[file]);
+	    } else {
+	      file_exists(file, file_stat(file, 1));
+	    }
+	  }
+	}
+	// Signal file_exists for path as an end marker.
+	file_exists(path, st);
+      }
+      return 1;
+    }
+    if (old_st) {
+      if (!st || ((old_st->mode & S_IFMT) != (st->mode & S_IFMT))) {
+	// File deleted or changed type.
+
+	int delay;
+	// Propagate deletions to any submonitors.
+	if (files) {
+	  foreach(files, string file) {
+	    file = canonic_path(Stdio.append_path(path, file));
+	    if (monitors[file]) {
+	      // Adjust next_poll, so that the monitor will be checked soon.
+	      monitors[file]->next_poll = time(1)-1;
+	      monitor_queue->adjust(monitors[file]);
+	      delay = 1;
+	    }
+	  }
+	}
+	if (delay) {
+	  // Delay the notification until the submonitors have notified.
+	  st = old_st;
+	  next_poll = time(1);
+	  monitor_queue->adjust(this);
+	} else {
+	  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);
+	    monitor_queue->adjust(this);
+	  } else {
+	    // The monitor no longer has a link from its parent directory.
+	    this_program::flags &= ~MF_HARD;
+
+	    // Check if we should remove the monitor.
+	    check_for_release(MF_AUTO, MF_AUTO);
+	  }
+
+	  file_deleted(path, old_st);
+	  return 1;
+	}
+	return 0;
+      }
+    } else if (st) {
+      // File created.
+
+      last_change = time(1);
+      file_created(path, st);
+      if (st->isdir) {
+	array(string) files = get_dir(path) || ({});
+	this_program::files = files;
+	foreach(files, string file) {
+	  file = canonic_path(Stdio.append_path(path, file));
+	  if (monitors[file]) {
+	    // There's already a monitor for the file.
+	    // Assume it has already notified about existance.
+	    continue;
+	  }
+	  if (this_program::flags & MF_RECURSE) {
+	    monitor(file, orig_flags | MF_AUTO | MF_HARD,
+		    max_dir_check_interval,
+		    file_interval_factor,
+		    stable_time);
+	    check_monitor(monitors[file]);
+	  } else {
+	    file_created(file, file_stat(file, 1));
+	  }
+	}
+      }
+      return 1;
+    } else {
+      return 0;
+    }
+    
+    //  Note: ctime seems to change unexpectedly when running ImageMagick
+    //        on NFS disk so we disable it for the moment [bug 5587].
+    if ((st->mtime != old_st->mtime) ||
+	/* (st->ctime != old_st->ctime) || */
+	(st->size != old_st->size)) {
+      last_change = time(1);
+      if (status_change(old_st, st, orig_flags, flags)) return 1;
+    }
+    if ((flags & MF_RECURSE) && (st->isdir)) {
+      // Check the files in the directory soon.
+      foreach(files, string file) {
+	file = canonic_path(Stdio.append_path(path, file));
+	Monitor m2 = monitors[file];
+	if (m2) {
+	  m2->bump(flags);
+	}
+      }
+    }
+    if (last_change < time(1) - (stable_time || global::stable_time)) {
+      last_change = 0x7fffffff;
+      stable_data_change(path, st);
+      return 1;
+    }
+    return 0;
+  }
+}
+
+//! 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)
+{
+  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
+//!   case is maintained.
+protected mapping(string:Monitor) monitors = ([]);
+
+//! Heap containing all active @[Monitor]s.
+//!
+//! The heap is sorted on @[Monitor()->next_poll].
+protected ADT.Heap monitor_queue = ADT.Heap();
+
+//! 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].
+//!
+//! @param stable_time
+//!   Override of @[default_stable_time].
+protected void create(int|void max_dir_check_interval,
+		      int|void file_interval_factor,
+		      int|void stable_time)
+{
+  if (max_dir_check_interval > 0) {
+    this_program::max_dir_check_interval = max_dir_check_interval;
+  }
+  if (file_interval_factor > 0) {
+    this_program::file_interval_factor = file_interval_factor;
+  }
+  if (stable_time > 0) {
+    this_program::stable_time = stable_time;
+  }
+  clear();
+}
+
+//! Clear the set of monitored files and directories.
+//!
+//! @note
+//!   Due to circular datastructures, it's recomended
+//!   to call this function prior to discarding the object.
+void clear()
+{
+  monitors = ([]);
+  monitor_queue = ADT.Heap();
+}
+
+//! Calculate a suitable time for the next poll of this monitor.
+//!
+//! @param m
+//!   Monitor to update.
+//!
+//! @param st
+//!   New stat for the monitor.
+//!
+//! This function is called by @[check_monitor()] to schedule the
+//! next check.
+protected void update_monitor(Monitor m, Stdio.Stat st)
+{
+  m->update(st);
+}
+
+//! Release a single @[Monitor] from monitoring.
+//!
+//! @seealso
+//!   @[release()]
+protected void release_monitor(Monitor m)
+{
+  m->next_poll = -1000;
+  monitor_queue->adjust(m);
+  while (monitor_queue->peek() < 0) {
+#if __VERSION__ < 7.8
+    monitor_queue->top();
+#else
+    monitor_queue->pop();
+#endif
+  }
+}
+
+//! 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()] with the same
+//! arguments.
+//!
+//! @seealso
+//!   @[monitor()]
+protected Monitor 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)
+{
+  return Monitor(path, flags, max_dir_check_interval,
+		 file_interval_factor, stable_time);
+}
+
+
+//! Register a @[path] for monitoring.
+//!
+//! @param path
+//!   Path to monitor.
+//!
+//! @param flags
+//!   @int
+//!     @value 0
+//!       Don't recurse.
+//!     @value 1
+//!       Monitor the entire subtree, and any directories
+//!       or files that may appear later.
+//!     @value 3
+//!       Monitor the entire subtree, and any directories
+//!       or files that may appear later. Remove the monitor
+//!       automatically when @[path] is deleted.
+//!   @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.
+//!
+//! @param stable_time
+//!   Override of @[default_stable_time] for this path
+//!   or subtree.
+//!
+//! @seealso
+//!   @[release()]
+void monitor(string path, MonitorFlags|void flags,
+	     int(0..)|void max_dir_check_interval,
+	     int(0..)|void file_interval_factor,
+	     int(0..)|void stable_time)
+{
+  path = canonic_path(path);
+  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;
+      m->stable_time = stable_time;
+      m->next_poll = 0;
+      monitor_queue->adjust(m);
+    }
+    if (flags & MF_HARD) {
+      m->flags |= MF_HARD;
+    }
+    // For the other cases there's no need to do anything,
+    // since we can keep the monitor as-is.
+  } else {
+    m = monitor_factory(path, flags, max_dir_check_interval,
+			file_interval_factor, stable_time);
+    monitors[path] = m;
+    monitor_queue->push(m);
+  }
+}
+
+//! Release a @[path] from monitoring.
+//!
+//! @param path
+//!   Path to stop monitoring.
+//!
+//! @param flags
+//!   @int
+//!     @value 0
+//!       Don't recurse.
+//!     @value 1
+//!       Release the entire subtree.
+//!     @value 3
+//!       Release the entire subtree, but only those paths that were
+//!       added automatically by a recursive monitor.
+//!   @endint
+//!
+//! @seealso
+//!   @[monitor()]
+void release(string path, MonitorFlags|void flags)
+{
+  path = canonic_path(path);
+  Monitor m = m_delete(monitors, path);
+  if (m) {
+    release_monitor(m);
+  }
+  if (flags && m->st && m->st->isdir) {
+    if (!sizeof(path) || path[-1] != '/') {
+      path += "/";
+    }
+    foreach(monitors; string mpath; m) {
+      if (has_prefix(mpath, path)) {
+	m->check_for_release(flags, flags);
+      }
+    }
+  }
+}
+
+//! Check whether a path is monitored or not.
+//!
+//! @param path
+//!   Path to check.
+//!
+//! @returns
+//!   Returns @expr{1@} if there is a monitor on @[path],
+//!   and @expr{0@} (zero) otherwise.
+//!
+//! @seealso
+//!   @[monitor()], @[release()]
+int(0..1) is_monitored(string path)
+{
+  return !!monitors[canonic_path(path)];
+}
+
+//! Check a single @[Monitor] for changes.
+//!
+//! @param m
+//!   @[Monitor] to check.
+//!
+//! @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()]
+protected int(0..1) check_monitor(Monitor m, MonitorFlags|void flags)
+{
+  return m->check(flags);
+}
+
+//! Check for changes.
+//!
+//! @param max_wait
+//!   Maximum time in seconds to wait for changes. @expr{-1@}
+//!   for infinite wait.
+//!
+//! @param max_cnt
+//!   Maximum number of paths to check in this call. @expr{0@}
+//!   (zero) for unlimited.
+//!
+//! @param ret_stats
+//!   Optional mapping that will be filled with statistics (see below).
+//!
+//! A suitable subset of the monitored files will be checked
+//! for changes.
+//!
+//! @returns
+//!   The function returns when either a change has been detected
+//!   or when @[max_wait] has expired. The returned value indicates
+//!   the number of seconds until the next call of @[check()].
+//!
+//!   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
+//!
+//! @note
+//!   Any callbacks will be called from the same thread as the one
+//!   calling @[check()].
+//!
+//! @seealso
+//!   @[monitor()]
+int check(int|void max_wait, int|void max_cnt,
+	  mapping(string:int)|void ret_stats)
+{
+  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(monitors)) {
+      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 (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;
+      }
+      return ret;
+    }
+    if (ret < scan_wait) {
+      scan_wait -= ret;
+      sleep(ret);
+    } else {
+      if (scan_wait > 0) scan_wait--;
+      sleep(1);
+    }
+  }
+}
+
+//! Backend to use.
+//!
+//! If @expr{0@} (zero) - use the default backend.
+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();
+  this_program::backend = backend;
+  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;
+  }
+}
+
+//! 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()
+{
+  co_id = 0;
+  int t;
+  mixed err = catch {
+      t = check(0);
+    };
+  set_nonblocking(t);
+  if (err) throw(err);
+}
+
+//! Turn on nonblocking mode.
+//!
+//! @param 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.
+//!
+//! @note
+//!   If nonblocking mode is already active, this function will
+//!   be a noop.
+//!
+//! @seealso
+//!   @[set_blocking()], @[check()].
+void set_nonblocking(int|void t)
+{
+  if (co_id) return;
+  if (zero_type(t)) {
+    Monitor m = monitor_queue->peek();
+    t = (m && m->next_poll - time(1)) || max_dir_check_interval;
+    if (t > max_dir_check_interval) t = max_dir_check_interval;
+    if (t < 0) t = 0;
+  }
+  if (backend) co_id = backend->call_out(backend_check, t);
+  else co_id = call_out(backend_check, t);
+}
+
+//! Set the @[default_max_dir_check_interval].
+void set_max_dir_check_interval(int max_dir_check_interval)
+{
+  if (max_dir_check_interval > 0) {
+    this_program::max_dir_check_interval = max_dir_check_interval;
+  } else {
+    this_program::max_dir_check_interval = default_max_dir_check_interval;
+  }
+}
+
+//! Set the @[default_file_interval_factor].
+void set_file_interval_factor(int file_interval_factor)
+{
+  if (file_interval_factor > 0) {
+    this_program::file_interval_factor = file_interval_factor;
+  } else {
+    this_program::file_interval_factor = default_file_interval_factor;
+  }
+}
+
diff --git a/lib/modules/Filesystem.pmod/Monitor.pmod/debug.pike b/lib/modules/Filesystem.pmod/Monitor.pmod/debug.pike
new file mode 100644
index 0000000000000000000000000000000000000000..224887f8be135b2e360c9ae97d9eabb6c68fe848
--- /dev/null
+++ b/lib/modules/Filesystem.pmod/Monitor.pmod/debug.pike
@@ -0,0 +1,27 @@
+inherit "symlinks.pike";
+
+void data_changed(string path) { werror("data_changed(%O)\r\n", path); }
+void attr_changed(string path, Stdio.Stat st) {
+  werror("attr_changed(%O, %O)\r\n", path, st);
+}
+void file_exists(string path, Stdio.Stat st) {
+  werror("file_exits(%O, %O)\r\n", path, st);
+}
+void file_created(string path, Stdio.Stat st) {
+  werror("file_created(%O, %O)\r\n", path, st);
+}
+void file_deleted(string path) { werror("file_deleted(%O)\r\n", path); }
+void stable_data_change(string path, Stdio.Stat st) {
+  werror("stable_data_change(%O, %O)\r\n", path, st);
+}
+
+
+int check(int|void max_wait, int|void max_cnt,
+	  mapping(string:int)|void ret_stats)
+{
+  int ret;
+  werror("check(%O, %O, %O) ==> %O\r\n",
+	 max_wait, max_cnt, ret_stats,
+	 ret = ::check(max_wait, max_cnt, ret_stats));
+  return ret;
+}
diff --git a/lib/modules/Filesystem.pmod/Monitor.pmod/symlinks.pike b/lib/modules/Filesystem.pmod/Monitor.pmod/symlinks.pike
new file mode 100644
index 0000000000000000000000000000000000000000..10d60c8323dc508f217829d1e4b2e129958615c7
--- /dev/null
+++ b/lib/modules/Filesystem.pmod/Monitor.pmod/symlinks.pike
@@ -0,0 +1,483 @@
+//
+// Filesystem monitor with support for symbolic links.
+//
+// $Id: symlinks.pike,v 1.9 2010/04/27 14:58:11 grubba Exp $
+//
+// 2010-01-25 Henrik Grubbstr�m
+//
+
+//! Filesystem monitor with support for symbolic links.
+//!
+//! This module extends @[Filesystem.Monitor.basic] with
+//! support for symbolic links.
+//!
+//! @note
+//!   For operating systems where symbolic links aren't supported,
+//!   this module will behave exactly like @[Filesystem.Monitor.basic].
+//!
+//! @seealso
+//!   @[Filesystem.Monitor.basic]
+
+//! @decl inherit Filesystem.Monitor.basic
+inherit "basic.pike" : basic;
+
+#if constant(readlink)
+
+//! @decl void attr_changed(string path, Stdio.Stat st)
+//!
+//! 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)@}.
+//!
+//! 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.
+//!
+//! @note
+//!   It differs from the @[Filesystem.Monitor.basic] version in that
+//!   symbolic links have the @[st] of their targets.
+//!
+//! Overload this to do something useful.
+
+//! @decl void file_exists(string path, Stdio.Stat st)
+//!
+//! File existance callback.
+//!
+//! @param path
+//!   Path of the file or directory.
+//!
+//! @param st
+//!   Status information for @[path] as obtained by @expr{file_stat(path)@}.
+//!
+//! 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.
+//!
+//! @note
+//!   It differs from the @[Filesystem.Monitor.basic] version in that
+//!   symbolic links have the @[st] of their targets.
+//!
+//! 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.
+
+//! @decl void file_created(string path, Stdio.Stat st)
+//!
+//! 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)@}.
+//!
+//! 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.
+//!
+//! @note
+//!   It differs from the @[Filesystem.Monitor.basic] version in that
+//!   symbolic links have the @[st] of their targets.
+//!
+//! Called by @[check()] and @[check_monitor()].
+//!
+//! Overload this to do something useful.
+
+//! @decl void stable_data_change(string path, Stdio.Stat st)
+//!
+//! Stable change callback.
+//!
+//! @param path
+//!   Path of the file or directory that has stopped changing.
+//!
+//! @param st
+//!   Status information for @[path] as obtained by @expr{file_stat(path)@}.
+//!
+//! 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.
+//!
+//! @note
+//!   It differs from the @[Filesystem.Monitor.basic] version in that
+//!   symbolic links have the @[st] of their targets.
+//!
+//! Called by @[check()] and @[check_monitor()].
+//!
+//! Overload this to do something useful.
+
+//! Monitoring information for a single filesystem path.
+//!
+//! With support for expansion of symbolic links.
+//!
+//! @seealso
+//!   @[monitor()]
+protected class Monitor
+{
+  //! Based on @[Filesystem.Monitor.basic.Monitor].
+  inherit basic::Monitor;
+
+  //! Mask of symlink ids that can reach this monitor.
+  int symlinks;
+
+  //! Call a notification callback and handle symlink expansion.
+  //!
+  //! @param cb
+  //!   Callback to call or @[UNDEFINED] for no operation.
+  //!
+  //! @param state
+  //!   State mapping to avoid multiple notification and infinite loops.
+  //!   Call with an empty mapping.
+  //!
+  //! @param symlinks
+  //!   Symlinks that have not been expanded yet.
+  //!
+  //! @param path
+  //!   Path to notify on.
+  //!
+  //! @param extras
+  //!   Extra arguments to @[cb].
+  //!
+  //! @param symlink
+  //!   Symbolic link that must have been followed for the
+  //!   callback to be called.
+  void low_call_callback(function(string, mixed ...:void) cb,
+			 mapping(string:int) state,
+			 mapping(string:string) symlink_targets,
+			 string path, Stdio.Stat|void st,
+			 string|void symlink)
+  {
+    if (!cb || state[path] || (st && st->islnk)) return;
+    state[path] = 1;
+    if (!symlink || !symlink_targets[symlink]) {
+      cb(path, st);
+    }
+    if (sizeof(symlink_targets)) {
+      // Check the list of symlink targets.
+      foreach(reverse(sort(indices(symlink_targets))), string src) {
+	string dest = symlink_targets[src];
+	if (has_prefix(path, dest)) {
+	  low_call_callback(cb, state, symlink_targets - ([ src : dest ]),
+			    src + path[sizeof(dest)..], st, symlink);
+	}
+      }
+    }
+  }
+
+  //! Call a notification callback and handle symlink expansion.
+  //!
+  //! @param cb
+  //!   Callback to call or @[UNDEFINED] for no operation.
+  //!
+  //! @param extras
+  //!   Extra arguments after the @[path] argument to @[cb].
+  protected void call_callback(function(string, mixed ...:void) cb,
+			       string path, Stdio.Stat|void st)
+  {
+    if (!cb) return;
+    low_call_callback(cb, ([]), global::symlink_targets, path, st);
+  }
+
+  protected void notify_symlink(function(string, mixed ...:void) cb,
+				string sym)
+  {
+    int sym_id = global::symlink_ids[sym];
+    if (sym_id) {
+      // Depth-first.
+      foreach(reverse(sort(filter(values(monitors),
+				  lambda(Monitor m, int sym_id) {
+				    return m->symlinks & sym_id;
+				  }, sym_id)->path)),
+	      string m_path) {
+	Monitor m = monitors[m_path];
+	m->low_call_callback(cb, ([]), global::symlink_targets,
+			     m_path, m->st, sym);
+      }
+    }
+  }
+
+  //! Called when the symlink @[path] is no more.
+  protected void zap_symlink(string path)
+  {
+    string old_dest = global::symlink_targets[path];
+
+    if (old_dest) {
+      int sym_id = global::symlink_ids[path];
+      foreach(monitors; string m_path; Monitor m) {
+	if (m->symlinks & sym_id) {
+	  m->low_call_callback(global::file_deleted, ([]),
+			       global::symlink_targets,
+			       m_path, UNDEFINED, path);
+	  m->symlinks -= sym_id;
+	  // Unregister the monitor if it is the last ref,
+	  // and there are no hard links to the file.
+	  m->check_for_release(MF_AUTO|MF_HARD, MF_AUTO);
+	}
+      }
+      global::available_ids |= m_delete(symlink_ids, path);
+      m_delete(global::symlink_targets, path);
+    }
+  }
+
+  //! Check whether a symlink has changed.
+  protected void check_symlink(string path, Stdio.Stat st,
+			       int|void inhibit_notify)
+  {
+    string dest;
+    if (st && st->islnk) {
+      dest = readlink(path);
+      if (dest) {
+	dest = canonic_path(combine_path(path, "..", dest));
+	if (symlink_targets[path] == dest) return;
+      }
+    }
+    if (symlink_targets[path]) {
+      zap_symlink(path);
+    }
+    if (dest) {
+      // We have a new symbolic link.
+      symlink_targets[path] = dest;
+      int sym_id = allocate_symlink(path);
+      int sym_mask = sym_id | symlink_ids[dest];
+      int sym_done = sym_id;
+      Monitor m;
+      if (!(m = monitors[dest])) {
+	MonitorFlags m_flags = (flags & ~MF_HARD) | MF_AUTO;
+	if (inhibit_notify) {
+	  m_flags &= ~MF_INITED;
+	}
+	monitor(dest, m_flags,
+		max_dir_check_interval,
+		file_interval_factor,
+		stable_time);
+	m = monitors[dest];
+      }
+      m->symlinks |= sym_id;
+      if (!has_suffix(dest, "/")) {
+	dest += "/";
+      }
+      foreach(monitors; string mm_path; Monitor mm) {
+	if (has_prefix(mm_path, dest)) {
+	  mm->symlinks |= sym_id;
+	  sym_mask |= symlink_ids[mm_path];
+	}
+      }
+      // Follow any found symlinks.
+      while (sym_mask != sym_done) {
+	int mask = sym_mask - sym_done;
+	foreach(monitors; string mm_path; Monitor mm) {
+	  if ((mm->symlinks & mask) && !(mm->symlinks & sym_id)) {
+	    mm->symlinks |= sym_id;
+	    sym_mask |= symlink_ids[mm_path];
+	  }
+	}
+	sym_done |= mask;
+      }
+      if (!inhibit_notify) {
+	notify_symlink(global::file_created, path);
+      }
+    }
+  }
+
+  //! 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()].
+  //!
+  //! @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)
+  {
+    check_symlink(path, st);
+    if (st && st->islnk) {
+      return;
+    }
+    ::attr_changed(path, st);
+  }
+
+  protected void low_file_exists(string path, Stdio.Stat st)
+  {
+    // Note: May be called for symlink targets before they have
+    //       initialized properly, in which case st will be 0.
+    if (!st || !global::file_exists) return;
+    global::file_exists(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).
+  protected void file_exists(string path, Stdio.Stat st)
+  {
+    check_symlink(path, st, 1);
+    if (st && st->islnk) {
+      notify_symlink(low_file_exists, path);
+      return;
+    }
+    ::file_exists(path, st);
+  }
+
+  //! 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()].
+  protected void file_created(string path, Stdio.Stat st)
+  {
+    check_symlink(path, st);
+    if (st && st->islnk) {
+      // Note: check_symlink() above has already called the file_created()
+      // callback for us.
+      return;
+    }
+    ::file_created(path, 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()].
+  protected void file_deleted(string path, Stdio.Stat old_st)
+  {
+    check_symlink(path, UNDEFINED);
+    if (old_st && old_st->islnk) {
+      return;
+    }
+    ::file_deleted(path, old_st);
+  }
+
+  //! 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()].
+  protected void stable_data_change(string path, Stdio.Stat st)
+  {
+    if (st && st->islnk) {
+      int sym_id = global::symlink_ids[path];
+      if (sym_id) {
+	foreach(monitors; string m_path; Monitor m) {
+	  if ((m->symlinks & sym_id) && (m->last_change == 0x7fffffff)) {
+	    m->low_call_callback(global::stable_data_change, ([]),
+				 global::symlink_targets, m_path, m->st, path);
+	  }
+	}
+      }
+      return;
+    }
+    ::stable_data_change(path, st);
+  }
+
+  //! Check if this monitor should be removed automatically.
+  void check_for_release(int mask, int flags)
+  {
+    if (symlinks) {
+      // We need to check if this is the direct target of a symlink.
+      foreach(symlink_targets;; string dest) {
+	if (path == dest) {
+	  // The monitor still has a symlink pointing to it.
+	  return;
+	}
+      }
+    }
+    ::check_for_release(mask, flags);
+  }
+
+  //! Called to create a sub monitor.
+  protected void monitor(string path, int flags, int max_dir_interval,
+			 int file_interval_factor, int stable_time)
+  {
+    ::monitor(path, flags, max_dir_check_interval,
+	      file_interval_factor, stable_time);
+    monitors[path]->symlinks |= symlinks;
+  }
+
+  //! Called when the status has changed for an existing file.
+  protected int(0..1) status_change(Stdio.Stat old_st, Stdio.Stat st,
+				    int orig_flags, int flags)
+  {
+    check_symlink(path, st);
+    if (st && st->islnk) {
+      return 1;
+    }
+    return ::status_change(old_st, st, orig_flags, flags);
+  }
+
+}
+
+#endif /* constant(readlink) */
+
+//! Mapping from symlink name to symlink target.
+protected mapping(string:string) symlink_targets = ([]);
+
+//! Mapping from symlink name to symlink id.
+protected mapping(string:int) symlink_ids = ([]);
+
+//! Bitmask of all unallocated symlink ids.
+protected int available_ids = -1;
+
+//! Allocates a symlink id for the link @[sym].
+protected int allocate_symlink(string sym)
+{
+  int res = symlink_ids[sym];
+  if (res) return res;
+  res = available_ids & ~(available_ids - 1);
+  available_ids -= res;
+  return symlink_ids[sym] = res;
+}