pike.git / lib / modules / Filesystem.pmod / Monitor.pmod / basic.pike

version» Context lines:

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