Roxen.git / server / base_server / configuration.pike

version» Context lines:

Roxen.git/server/base_server/configuration.pike:743:    return modules[symbol] - ({ 0 });   }      void unregister_urls()   {    foreach( registered_urls + failed_urls, string url )    roxen.unregister_url(url, this_object());    registered_urls = ({});   }    + private mapping(RoxenModule:ModuleChangedMonitor) +  module_changed_monitors = ([]); +  + #if constant(Filesystem.Monitor.symlinks) +  + private class ModuleChangedMonitor + { +  inherit Filesystem.Monitor.symlinks; +  +  protected constant default_max_dir_check_interval = 60; +  protected constant default_file_interval_factor = 1; +  protected constant default_stable_time = 0; +  +  RoxenModule mod; +  +  void create(RoxenModule mod) +  { +  ::create(); +  set_nonblocking(1); +  this::mod = mod; +  } +  +  bool is_called = false; +  void stable_data_change(string p, Stdio.Stat st) { +  // This will be called on server start, so skip that. +  if (!is_called) { +  is_called = true; +  return; +  } +  +  if (mod) { +  mod = reload_module(mod->module_local_id()); +  } +  } + } +  + void register_module_hot_reload(RoxenModule mod) + //! Hot-reload a module when the source file is changed, e.g. reload the module + //! automatically without having to click the Reload button in the Admin + //! Interface. + //! + //! This will only have effect if the server is started with @tt{--debug@} or + //! @tt{--module-debug@} (@tt{--once@}). This can also be initalized from the + //! command line with @tt{./start --once --module-hot-reload=my-module@}, which + //! is the preferred way of enabling hot reload. + //! + //! @param mod + //! The module to enable hot reloading for + { + #if defined(DEBUG) || defined(MODULE_DEBUG) || defined(MODULE_HOT_RELOAD) +  +  // Already monitored +  if (module_changed_monitors[mod]) { +  return; +  } +  +  ModuleChangedMonitor fsw; +  // Is this one of these "relying on the interpret lock"? +  fsw = module_changed_monitors[mod] = ModuleChangedMonitor(mod); +  +  string path = roxen->filename(mod); +  +  if (!fsw->is_monitored(path)) { +  report_debug(" Adding hot reload monitor for %O.\n", mod); +  fsw->monitor(path); +  } +  + #endif // defined(...) + } +  + void unregister_module_hot_reload(RoxenModule mod) + //! Unregister the hot reload monitor for module @[mod]. + { +  if (ModuleChangedMonitor mon = m_delete(module_changed_monitors, mod)) { +  report_debug("Removing hot reload monitor for %O.\n", mod); +  mon->clear(); +  destruct(mon); +  } + } +  + #else /* Filesystem.Monitor.symlinks */ +  + //! @ignore + private class ModuleChangedMonitor {} + void register_module_hot_reload(RoxenModule mod){} + void unregister_module_hot_reload(RoxenModule mod){} + //! @endignore +  + #endif /* !Filesystem.Monitor.symlinks */ +  +    private void safe_stop_module (RoxenModule mod, string desc)   {    if (mixed err = catch (mod && mod->stop &&    call_module_func_with_cbs (mod, "stop", 0)))    report_error ("While stopping " + desc + ": " + describe_backtrace (err)); -  +  +  unregister_module_hot_reload(mod);   }      private Thread.Mutex stop_all_modules_mutex = Thread.Mutex();      private void do_stop_all_modules (Thread.MutexKey stop_lock)   {    foreach(sorted_modules, RoxenModule m) {    safe_stop_module(m, "module");    }   
Roxen.git/server/base_server/configuration.pike:1108:   }      void init_log_file()   {    end_logger();    // Only try to open the log file if logging is enabled!!    if(query("Log"))    {    string logfile = query("LogFile");    if(strlen(logfile)) -  log_function = roxen.LogFile(logfile, query("LogFileCompressor"))->write; +  log_function = roxen.LogFile(logfile, +  query("LogFileCompressor"), +  [int] query("DaysToKeepLogFiles"))->write;    }   }      private void parse_log_formats()   {    array foo=query("LogFormat")/"\n";    log_format = ([]);    foreach(foo; int i; string b)    if(strlen(b) && b[0] != '#') {    if (sscanf (b, "%d:%*[\t ]%s", int status, b))
Roxen.git/server/base_server/configuration.pike:1749:       case 401:    if (m->extra_heads["www-authenticate"])    res = sprintf("Returned authentication failed: %O ",    m->extra_heads["www-authenticate"]);    else    res = "Returned authentication failed. ";    break;       case 200: +  // NB: Note the setting of extra_heads above. +  if (sizeof(m) <= 1) { +  res = "Returned multi status. "; +  break; +  }    res = "Returned ok. ";    break;       default:    res = sprintf("Returned %O. ", m->error);    }       if (!zero_type(m->len))    if (m->len<0)    res += "No data ";
Roxen.git/server/base_server/configuration.pike:1782:       if (stringp(m->extra_heads["content-type"]) ||    stringp(m->type)) {    res += sprintf(" of %O", m->type||m->extra_heads["content-type"]);    }       return res;   }      //! Find all applicable locks for this user on @[path]. - multiset(DAVLock) find_locks(string path, int(-1..1) recursive, + mapping(string:DAVLock) find_locks(string path, int(-1..1) recursive,    int(0..1) exclude_shared, RequestID id)   {    SIMPLE_TRACE_ENTER(0, "find_locks(%O, %O, %O, X)",    path, recursive, exclude_shared); -  multiset(DAVLock) locks = (<>); +  mapping(string:DAVLock) locks = ([]);       foreach(location_module_cache||location_modules(),    [string loc, function func])    {    SIMPLE_TRACE_ENTER(function_object(func),    "Finding locks in %O.", loc);    string subpath;    if (has_prefix(path, loc)) {    // path == loc + subpath.    subpath = path[sizeof(loc)..];    } else if (recursive && has_prefix(loc, path)) {    // loc == path + ignored.    subpath = "/";    } else {    // Does not apply to this location module.    TRACE_LEAVE("Skip this module.");    continue;    }    TRACE_ENTER(sprintf("subpath: %O", subpath),    function_object(func)->find_locks); -  multiset(DAVLock) sub_locks = +  mapping(string:DAVLock) sub_locks =    function_object(func)->find_locks(subpath, recursive,    exclude_shared, id);    TRACE_LEAVE("");    if (sub_locks) {    SIMPLE_TRACE_LEAVE("Got some locks: %O", sub_locks);    locks |= sub_locks;    } else {    TRACE_LEAVE("Got no locks.");    }    }    SIMPLE_TRACE_LEAVE("Returning %O", locks);    return locks;   }    - //! Check if there are any applicable locks for this user on @[path]. - DAVLock|LockFlag check_locks(string path, int(0..1) recursive, RequestID id) + //! Check that all locks that apply to @[path] for the user the request + //! is authenticated as have been mentioned in the if-header. + //! + //! WARNING: This function has some design issues and will very likely + //! get a different interface. Compatibility is NOT guaranteed. + //! + //! @param path + //! Normalized path below the filesystem location. + //! + //! @param recursive + //! If @expr{1@} also check recursively under @[path] for locks. + //! + //! @returns + //! Returns one of + //! @mixed + //! @type int(0..0) + //! Zero if not locked, or all locks were mentioned. + //! @type mapping(zero:zero) + //! An empty mapping if @[recursive] was true and there + //! were unmentioned locks on paths with @[path] as a prefix. + //! The missing locks are registered in the multistatus for + //! the @[id] object. + //! @type mapping(string:mixed) + //! A @[Protocols.HTTP.DAV_LOCKED] error status in all other cases. + //! @endmixed + //! + //! @note + //! @[DAVLock] objects may be created if the filesystem has some + //! persistent storage of them. The default implementation does not + //! store locks persistently. + mapping(string:mixed)|int(-1..0) check_locks(string path, +  int(0..1) recursive, +  RequestID id)   { -  LockFlag state = 0; -  foreach(location_module_cache||location_modules(), -  [string loc, function func]) -  { -  string subpath; -  int check_above; -  if (has_prefix(path, loc)) { -  // path == loc + subpath. -  subpath = path[sizeof(loc)..]; -  } else if (recursive && has_prefix(loc, path)) { -  // loc == path + ignored. -  subpath = ""; -  check_above = 1; -  } else { -  // Does not apply to this location module. -  continue; +  TRACE_ENTER(sprintf("check_locks(%O, %d, X)", path, recursive), this); +  +  mapping(string:DAVLock) locks = find_locks(path, recursive, 0, id); +  // Common case. +  if (!sizeof(locks)) { +  TRACE_LEAVE ("Got no locks."); +  return 0;    } -  int/*LockFlag*/|DAVLock lock_info = -  function_object(func)->check_locks(subpath, recursive, id); -  if (objectp(lock_info)) { -  if (!check_above) { -  return lock_info; -  } else { -  lock_info = LOCK_OWN_BELOW; // We have a lock on some subpath. +  +  mapping(string:array(array(array(string)))) if_data = id->get_if_data(); +  if (if_data) { +  foreach(if_data[0], array(array(string)) tokens) { +  m_delete(locks, tokens[0][1]);    } -  +  +  if (!sizeof(locks)) { +  TRACE_LEAVE ("All locks unlocked."); +  return 0;    } -  else -  if (check_above && (lock_info & 1)) -  // Convert LOCK_*_AT to LOCK_*_BELOW. -  lock_info &= ~1; -  if (lock_info > state) state = lock_info; -  if (state == LOCK_EXCL_AT) return LOCK_EXCL_AT; // Doesn't get any worse. -  if (function_object(func)->webdav_opaque) break; +     } -  return state; +  +  // path = id->not_query; +  if (!has_suffix(path, "/")) path += "/"; +  mapping(string:mixed) ret = +  Roxen.http_dav_error(Protocols.HTTP.DAV_LOCKED, "lock-token-submitted"); +  foreach(locks;;DAVLock lock) { +  TRACE_ENTER(sprintf("Checking lock %O against %O.", lock, path), 0); +  // NB: We can't perform a string comparison here, as we don't +  // know whether the path is case-sensitive or not. But as +  // we know that all lock paths are on the path to or through +  // `path`, a comparison of the string lengths is sufficient. +  if (sizeof(lock->path) <= sizeof(path)) { +  TRACE_LEAVE("Direct lock."); +  TRACE_LEAVE("Locked."); +  return ret;    } -  +  if (lock->is_file) { +  id->set_status_for_path(lock->path[..<1], ret); +  } else { +  id->set_status_for_path(lock->path, ret); +  } +  TRACE_LEAVE("Added to multi status."); +  } +  TRACE_LEAVE("Multi status."); +  return ([]); + }      protected multiset(DAVLock) active_locks = (<>);      //! Unlock the lock represented by @[lock] on @[path].   //!   //! @returns   //! Returns a result-mapping on error, and @expr{0@} (zero) on success.   mapping(string:mixed) unlock_file(string path, DAVLock lock, RequestID id)   {    // Canonicalize path.
Roxen.git/server/base_server/configuration.pike:1973:   //! Returns a result mapping on failure,   //! and the resulting @[DAVLock] on success.   mapping(string:mixed)|DAVLock lock_file(string path,    int(0..1) recursive,    string lockscope,    string locktype,    int(0..) expiry_delta,    array(Parser.XML.Tree.Node) owner,    RequestID id)   { +  TRACE_ENTER(sprintf("%O(%O, %O, %O, %O, %O, %O, %O)", +  this_function, path, recursive, lockscope, +  locktype, expiry_delta, owner, id), 0); +  +  int is_file; +     // Canonicalize path. -  if (!has_suffix(path, "/")) path+="/"; +  if (!has_suffix(path, "/")) { +  path+="/"; +  is_file = 1; +  }    -  // First check if there's already some lock on path that prevents +  // FIXME: Race conditions! +  +  int fail; +  +  // First check if there's already some lock on the path that prevents    // us from locking it. -  int/*LockFlag*/|DAVLock lock_info = check_locks(path, recursive, id); +  mapping(string:DAVLock) locks = find_locks(path, recursive, 0, id);    -  if (!intp(lock_info)) { -  // We already hold a lock that prevents us. -  if (id->request_headers->if) { -  return Roxen.http_status(412, "Precondition Failed"); -  } else { -  return Roxen.http_status(423, "Locked"); +  foreach(locks; string lock_token; DAVLock lock) { +  TRACE_ENTER(sprintf("Checking lock %O...\n", lock), 0); +  if ((lock->lockscope == "DAV:exclusive") || +  (lockscope == "DAV:exclusive")) { +  TRACE_LEAVE("Locked."); +  id->set_status_for_path(lock->path, 423, "Locked"); +  fail = 1;    } -  } else if (lockscope == "DAV:exclusive" ? -  lock_info >= LOCK_SHARED_BELOW : -  lock_info >= LOCK_OWN_BELOW) { -  // Some other lock prevents us. -  return Roxen.http_status(423, "Locked"); +  TRACE_LEAVE("Shared.");    }    -  +  if (fail) { +  TRACE_LEAVE("Fail."); +  return ([]); +  } +     // Create the new lock.    -  string locktoken = "opaquelocktoken:" + roxen->new_uuid_string(); +  string locktoken = "urn:uuid:" + roxen->new_uuid_string();    DAVLock lock = DAVLock(locktoken, path, recursive, lockscope, locktype,    expiry_delta, owner); -  +  lock->is_file = is_file;    foreach(location_module_cache||location_modules(),    [string loc, function func])    {    string subpath;    if (has_prefix(path, loc)) {    // path == loc + subpath.    subpath = path[sizeof(loc)..];    } else if (recursive && has_prefix(loc, path)) {    // loc == path + ignored.    subpath = "/";    } else {    // Does not apply to this location module.    continue;    }    -  +  TRACE_ENTER(sprintf("Calling %O->lock_file(%O, %O, %O)...", +  function_object(func), subpath, lock, id), 0);    mapping(string:mixed) lock_error =    function_object(func)->lock_file(subpath, lock, id);    if (lock_error) {    // Failure. Unlock the new lock.    foreach(location_module_cache||location_modules(),    [string loc2, function func2])    {    if (has_prefix(path, loc2)) {    // path == loc2 + subpath.    mapping(string:mixed) ret =    function_object(func2)->unlock_file(path[sizeof(loc2)..],    lock, id);    } else if (recursive && has_prefix(loc2, path)) {    // loc2 == path + ignored.    mapping(string:mixed) ret =    function_object(func2)->unlock_file("/", lock, id);    }    if (func == func2) break;    }    // destruct(lock); -  +  TRACE_LEAVE(sprintf("Lock error: %O", lock_error));    return lock_error;    } -  +  TRACE_LEAVE("Ok.");    if (function_object(func)->webdav_opaque) break;    }       if (expiry_delta) {    // Lock with timeout.    // FIXME: Race-conditions.    if (!sizeof(active_locks)) {    // Start the lock expiration loop.    active_locks[lock] = 1;    expire_lock_loop();    } else {    active_locks[lock] = 1;    }    }       // Success. -  +  TRACE_LEAVE("Success.");    return lock;   }    -  + //! Returns the value of the specified property, or an error code + //! mapping. + //! + //! @note + //! Returning a string is shorthand for returning an array + //! with a single text node. + //! + //! @seealso + //! @[query_property_set()] + string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed) +  query_property(string path, string prop_name, RequestID id) + { +  foreach(location_module_cache||location_modules(), +  [string loc, function func]) +  { +  if (!has_prefix(path, loc)) { +  // Does not apply to this location module. +  continue; +  } +  +  // path == loc + subpath. +  string subpath = path[sizeof(loc)..]; +  +  string|array(Parser.XML.Tree.SimpleNode)|mapping(string:mixed) res = +  function_object(func)->query_property(subpath, prop_name, id); +  if (mappingp(res) && (res->error == 404)) { +  // Not found in this module; try the next. +  continue; +  } +  return res; +  } +  return Roxen.http_status(Protocols.HTTP.HTTP_NOT_FOUND, "No such property."); + } +    mapping|int(-1..0) low_get_file(RequestID id, int|void no_magic)   //! The function that actually tries to find the data requested. All   //! modules except last and filter type modules are mapped, in order,   //! and the first one that returns a suitable response is used. If   //! `no_magic' is set to one, the internal magic roxen images and the   //! @[find_internal()] callbacks will be ignored.   //!   //! The return values 0 (no such file) and -1 (the data is a   //! directory) are only returned when `no_magic' was set to 1;   //! otherwise a result mapping is always generated.
Roxen.git/server/base_server/configuration.pike:2593:       return res;   }      protected string combine_combiners(string s)   {    if (String.width(s) <= 8) return s;    return Unicode.normalize(s, "NFC");   }    + //! Get a directory listing for the virtual path @[file]. + //! + //! @param file + //! Path in the virtual filesystem. + //! + //! @param id + //! @[RequestID] for the request. + //! + //! @param verbose + //! Also list virtual lock files. + //! + //! @returns + //! Returns an array with all visible files in the specified + //! directory if it exists, and @expr{0@} (zero) otherwise. + //! Any filesystem encoding of the filenames has been decoded, + //! and they have also been Unicode-NFC normalized.   array(string) find_dir(string file, RequestID id, void|int(0..1) verbose)   {    array dir;    TRACE_ENTER(sprintf("List directory %O.", file), 0);       if(!sizeof (file) || file[0] != '/')    file = "/" + file;      #ifdef URL_MODULES   #ifdef THREADS
Roxen.git/server/base_server/configuration.pike:2640: Inside #if defined(URL_MODULES)
   if( id->misc->find_dir_nest < 20 )    dir = (id->conf || this_object())->find_dir( file, id );    else    error("Too deep recursion in roxen::find_dir() while mapping "    +file+".\n");    };    id->misc->find_dir_nest = 0;    TRACE_LEAVE("");    if(err)    throw(err); +     if (arrayp(dir)) {    return map(dir, combine_combiners);    }    return dir;    }    TRACE_LEAVE("");    id->not_query=of;    }   #endif /* URL_MODULES */   
Roxen.git/server/base_server/configuration.pike:3154:    // id->misc->common is here for compatibility; it's better to use    // id->root_id->misc.    if ( !id->misc->common )    id->misc->common = ([]);       fake_id = id->clone_me();       fake_id->misc->common = id->misc->common;    fake_id->conf = this_object();    -  fake_id->raw_url=s; +  // HTTP transport encode the path. +  // NB: This is required to make scan_for_query() happy. +  s = string_to_utf8(s); +  sscanf(s, "%s?%s", string path, string query); +  s = map((path || s) / "/", Protocols.HTTP.percent_encode) * "/"; +  if (query) { +  s += "?" + query; +  }    -  +  fake_id->raw_url = s; +     if (fake_id->scan_for_query)    // FIXME: If we're using e.g. ftp this doesn't exist. But the    // right solution might be that clone_me() in an ftp id object    // returns a vanilla (i.e. http) id instead when this function is    // used.    s = fake_id->scan_for_query (s);       s = http_decode_string(s);    -  +  catch { s = utf8_to_string(s); }; +     s = Roxen.fix_relative (s, id);       // s is sent to Unix API's that take NUL-terminated strings...    if (search(s, "\0") != -1)    sscanf(s, "%s\0", s);       fake_id->not_query=s;       return fake_id;   }
Roxen.git/server/base_server/configuration.pike:4644:    report_error(LOC_M(45, "Failed to enable the module %s. Skipping.")    +"\n%s\n", tmp_string, describe_backtrace(err));    got_no_delayed_load = -1;    }    }    enable_module_batch_msgs = 0;   // roxenloader.pop_compile_error_handler();    forcibly_added = ([]);    }    -  foreach( ({this_object()})+indices( otomod ), RoxenModule mod ) -  if( mod->ready_to_receive_requests ) + #ifdef MODULE_HOT_RELOAD +  array(string) hot_mods = roxen->query_hot_reload_modules(); +  array(string) hot_confs = roxen->query_hot_reload_modules_conf(); + #endif +  +  foreach( ({this_object()})+indices( otomod ), RoxenModule mod ) { +  if( mod->ready_to_receive_requests ) {    if( mixed q = catch( mod->ready_to_receive_requests( this_object() ) ) ) {    report_error( "While calling ready_to_receive_requests in "+    otomod[mod]+":\n"+    describe_backtrace( q ) );    got_no_delayed_load = -1;    } -  +  }    -  + #ifdef MODULE_HOT_RELOAD +  if (has_index(mod, "is_module") && +  (!hot_confs || has_value(hot_confs, name))) +  { +  sscanf (mod->module_local_id(), "%s#", string mod_name); +  +  if (has_value(hot_mods, mod_name)) { +  register_module_hot_reload(mod); +  } +  } + #endif +  } +     foreach( after_init_hooks, function q )    if( mixed w = catch( q(this_object()) ) ) {    report_error( "While calling after_init_hook %O:\n%s",    q, describe_backtrace( w ) );    got_no_delayed_load = -1;    }       after_init_hooks = ({});       inited = 1;
Roxen.git/server/base_server/configuration.pike:5215:    "<b>Note&nbsp;1:</b> The active log file is never compressed. "    "Log rotation needs to be used using the \"Log file\" "    "filename substitutions "    "(e.g. <tt>$LOGDIR/mysite/Log.%y-%m-%d</tt>). "    "<b>Note&nbsp;2:</b> Compression is limited to scanning files "    "with filename substitutions within a fixed directory (e.g. "    "<tt>$LOGDIR/mysite/Log.%y-%m-%d</tt>, "    "not <tt>$LOGDIR/mysite/%y/Log.%m-%d</tt>)."),    0, lambda(){ return !query("Log");});    +  defvar("DaysToKeepLogFiles", 0, +  DLOCALE(0, "Logging: Number of days to keep log files"), TYPE_INT, +  DLOCALE(0, "Log files in the log directory older than specified number of " +  "days will automatically be deleted. Set to <tt>0</tt> (<tt>zero</tt>) " +  "to disable and keep log files forever. Currently active log file will " +  "never be deleted, nor will files with names not matching the pattern " +  "specified under <b>Log file</b>.")) +  ->set_range(0, Variable.no_limit);; +     defvar("NoLog", ({ }),    DLOCALE(32, "Logging: No Logging for"), TYPE_STRING_LIST|VAR_MORE,    DLOCALE(33, "Don't log requests from hosts with an IP number which "    "matches any of the patterns in this list. This also affects "    "the access counter log."),    0, lambda(){ return !query("Log");});       defvar("JSONLogEndpoints", ({ "$JSONLOGDIR/" + Roxen.short_name(name) + ".jsonlog" }),    DLOCALE(1076, "Logging: JSON Logging endpoints"), TYPE_STRING_LIST,    DLOCALE(1077, "Socket paths and/or IP:ports to bind for log output from this configuration. "