Roxen.git / server / base_server / roxen.pike

version» Context lines:

Roxen.git/server/base_server/roxen.pike:57:   #else   # define SSL3_WERR(X)   #endif      #ifdef THREAD_DEBUG   # define THREAD_WERR(X) report_debug("Thread: "+X+"\n")   #else   # define THREAD_WERR(X)   #endif    + #ifdef LOG_GC_VERBOSE + #define LOG_GC_HISTOGRAM + #endif +  + #ifdef LOG_GC_HISTOGRAM + #define LOG_GC_TIMESTAMPS + #endif +    // Needed to get core dumps of seteuid()'ed processes on Linux.   #if constant(System.dumpable)   #define enable_coredumps(X) System.dumpable(X)   #else   #define enable_coredumps(X)   #endif      #define DDUMP(X) sol( combine_path( __FILE__, "../../" + X ), dump )   protected function sol = master()->set_on_load;   
Roxen.git/server/base_server/roxen.pike:108:       else if( !fname ) {    fname = master()->program_name( o );    if (!fname)    return ({0, 0});    }       string cwd = getcwd() + "/";    if (has_prefix (fname, cwd))    fname = fname[sizeof (cwd)..]; -  else if (has_prefix (fname, roxenloader.server_dir)) -  fname = fname[sizeof (roxenloader.server_dir)..]; +  else if (has_prefix (fname, roxenloader.server_dir + "/")) +  fname = fname[sizeof (roxenloader.server_dir + "/")..];       return ({fname, line});   }      string filename( program|object o )   {    [string fname, int line] = filename_2 (o);    return fname || "(unknown program)";   }      protected int once_mode;      // Note that 2.5 is a nonexisting version. It's only used for the   // cache static optimization for tags such as <if> and <emit> inside   // <cache> since that optimization can give tricky incompatibilities   // with 2.4. -  + // Note also that 5.3 only existed in the Print repository, and + // thus is skipped here.   array(string) compat_levels = ({"2.1", "2.2", "2.4", "2.5",    "3.3", "3.4",    "4.0", "4.5", -  "5.0", "5.1", "5.2" }); +  "5.0", "5.1", "5.2", "5.4", "5.5", +  "6.0", + });      #ifdef THREADS   mapping(string:string) thread_names = ([]);   string thread_name( object thread, int|void skip_auto_name )   {    string tn;    if( thread_names[ tn=sprintf("%O",thread) ] || skip_auto_name )    return thread_names[tn];    return tn;   }
Roxen.git/server/base_server/roxen.pike:513:   {    if(shutdown_recurse >= 4)    {    if (mixed err =    catch (report_notice("Exiting roxen (spurious signals received).\n")) ||    catch (stop_all_configurations()))    master()->handle_error (err);    // Zap some of the remaining caches.    destruct(argcache);    destruct(cache); +  stop_error_log_cleaner();   #ifdef THREADS   #if constant(Filesystem.Monitor.basic)    stop_fsgarb();   #endif    if (mixed err = catch (stop_handler_threads()))    master()->handle_error (err);   #endif /* THREADS */    roxenloader.real_exit(exit_code);    }    if (shutdown_recurse++) return;
Roxen.git/server/base_server/roxen.pike:780: Inside #if undefined(NO_SLOW_REQ_BT)
   if (count > 0) set ("slow_req_bt_count", count - 1);       if (thread == backend_thread && !slow_be_call_out) {    // Avoid false alarms for the backend thread if we got here due to    // a race. Should perhaps have something like this for the handler    // threads too, but otoh races are more rare there due to the    // longer timeouts.    }       else { -  report_debug ("###### %s 0x%x has been busy for more than %g seconds.\n", +  string th_name = +  ((thread != backend_thread) && thread_name(thread, 1)) || ""; +  if (sizeof(th_name)) +  th_name = " - " + th_name + " -"; +  report_debug ("###### %s 0x%x%s has been busy for more than %g seconds.\n",    thread == backend_thread ? "Backend thread" : "Thread", -  thread->id_number(), timeout); +  thread->id_number(), th_name, timeout);    describe_all_threads (0, threads_disabled);    }       threads_disabled = 0; // Paranoia.   }      #endif // !NO_SLOW_REQ_BT      // // This is easier than when there are no threads.   // // See the discussion below. :-)
Roxen.git/server/base_server/roxen.pike:1521:    function func;    array args;    int stopping = 0;       protected void repeat (function func, array args)    {    // Got a minimum of four refs to this:    // o One in the task array in bg_process_queue.    // o One on the stack in the call in bg_process_queue.    // o One as current_object in the stack frame. -  // o One on the stack as argument to _refs. -  int self_refs = _refs (this); +  // o One on the stack as argument to Debug.refs. +  int self_refs = Debug.refs (this);   #ifdef DEBUG    if (self_refs < 4)    error ("Minimum ref calculation wrong - have only %d refs.\n", self_refs);   #endif    if (stopping || (self_refs <= 4) || !func) {    stopping = 2; // Stopped.    return;    }    mixed err = catch {    func (@args);
Roxen.git/server/base_server/roxen.pike:1754:    raw_url = path;    method = "GET";    raw = "GET " + raw_url + " HTTP/1.1\r\n\r\n";    [port_obj, mapping(string:mixed) url_data] = find_port_for_url (uri, 0);    if (url_data) {    conf = url_data->conf;    if (!conf->inited) conf->enable_all_modules();    if (string config_path = url_data->path)    adjust_for_config_path (config_path);    } +  +  // Update the cached URL base to keep url_base() happy. +  uri->path = (misc->site_prefix_path || "") + "/"; +  uri->query = UNDEFINED; +  uri->fragment = UNDEFINED; +  cached_url_base = sprintf("%s", uri);    return set_path( raw_url );    }       protected string _sprintf()    {    return sprintf("InternalRequestID(conf=%O; not_query=%O)", conf, not_query );    }       protected void create()    {    client = ({ "Roxen" });    prot = "INTERNAL"; -  +  port_obj = InternalProtocol();    method = "GET";    real_variables = ([]);    variables = FakedVariables( real_variables );    root_id = this_object(); -  +  cached_url_base = "internal://0.0.0.0:0/";       misc = ([ "pref_languages": PrefLanguages(),    "cacheable": INITIAL_CACHEABLE,    ]);    connection_misc = ([]);    cookies = CookieJar();    throttle = ([]);    client_var = ([]);    request_headers = ([]);    prestate = (<>);
Roxen.git/server/base_server/roxen.pike:2280:    setup (pn, i);    bind (ignore_eaddrinuse);    }       protected string _sprintf( )    {    return "Protocol(" + get_url() + ")";    }   }    - #if constant(SSL.sslfile) + class InternalProtocol + //! Protocol for internal requests that are not linked to any real request. + { +  inherit Protocol;    -  +  constant name = "internal"; +  +  constant prot_name = "internal"; +  +  constant supports_ipless = 1; +  constant default_port = 0; +  +  protected void create() +  { +  path = ""; +  port = default_port; +  ip = "0.0.0.0"; +  } + } +  + #if constant(SSL.File) +    class SSLContext {   #if constant(SSL.Context)    inherit SSL.Context;      #if defined(DEBUG) || defined(SSL3_DEBUG)    SSL.Alert alert_factory(SSL.Connection con, int level, int description,    SSL.Constants.ProtocolVersion version,    string|void debug_message)    {    if (description != SSL.Constants.ALERT_close_notify) {
Roxen.git/server/base_server/roxen.pike:2354:    string msg = (MSG); \    array args = ({ARGS}); \    if (sizeof (args)) msg = sprintf (msg, @args); \    report_error ("TLS port %s: %s", get_url(), msg); \    (VAR)->add_warning (msg); \    cert_err_unbind(); \    cert_failure = 1; \    return; \    } while (0)    - #if constant(SSL.ServerConnection) + #if constant(SSL.Constants.PROTOCOL_TLS_MAX)    protected void set_version()    {    ctx->min_version = query("ssl_min_version");    }   #endif       protected void filter_preferred_suites()    {   #if constant(SSL.ServerConnection)    int mode = query("ssl_suite_filter");
Roxen.git/server/base_server/roxen.pike:2395: Inside #if constant(SSL.ServerConnection)
   default:    ctx->configure_suite_b(bits);    break;    }    suites = ctx->preferred_suites;       if (ctx->min_version < query("ssl_min_version")) {    set_version();    }    } else { -  suites = ctx->get_suites(bits); +  suites = ctx->get_suites(bits, 1);       // Make sure the min version is restored in case we've    // switched from Suite B.    set_version();    }    if (mode & 4) {    // Ephemeral suites only.    suites = filter(suites,    lambda(int suite) {    return (<
Roxen.git/server/base_server/roxen.pike:2555: Inside #if constant(Standards.X509)
   ctx = SSLContext();    set_version();    filter_preferred_suites();       mapping(string:array(int)) cert_lookup = ([]);    foreach(decoded_certs; int no; Standards.X509.TBSCertificate tbs) {    cert_lookup[tbs->subject->get_der()] += ({ no });    }       foreach(decoded_keys, Crypto.Sign key) { -  // FIXME: Multiple certificates with the same key? -  array(int) cert_nos; +  // NB: We need to support multiple certificates with the same key. +  int found;    Standards.X509.TBSCertificate tbs;    foreach(decoded_certs; int no; tbs) { -  if (tbs->public_key->pkc->public_key_equal(key)) { -  cert_nos = ({ no }); -  break; -  } -  } -  if (!cert_nos) { -  CERT_ERROR (KeyFile, -  LOC_M(14, "Certificate and private key do not match.\n")); +  if (!tbs->public_key->pkc->public_key_equal(key))    continue; -  } +     -  +  array(int) cert_nos = ({ no }); +     // Build the certificate chain.    Standards.X509.TBSCertificate issuer;    do {    string issuer_der = tbs->issuer->get_der();    array(int) issuer_nos = cert_lookup[issuer_der];    if (!issuer_nos) break;       issuer = decoded_certs[issuer_nos[0]];       // FIXME: Verify that the issuer has signed the cert.
Roxen.git/server/base_server/roxen.pike:2592: Inside #if constant(Standards.X509)
   cert_nos += ({ issuer_nos[0] });    } else {    // Self-signed.    issuer = UNDEFINED;    break;    }    } while ((tbs = issuer));       report_notice("Adding %s certificate (%d certs) for %s\n",    key->name(), sizeof(cert_nos), get_url()); -  ctx->add_cert(key, rows(certificates, cert_nos), ({ name })); +  // FIXME: Ought to only add "*" for the certificate chains +  // belonging to the default server. +  ctx->add_cert(key, rows(certificates, cert_nos), ({ name, "*" })); +  found = 1;    } -  +  if (!found) { +  CERT_ERROR (KeyFile, +  LOC_M(14, "Private key without matching certificate.\n")); +  continue; +  } +  }      #if 0    // FIXME: How do this in current Pike 8.0?    if (!sizeof(ctx->cert_pairs)) {    CERT_ERROR(Certificates,    LOC_M(0,"No matching keys and certificates found.\n"));    report_error ("TLS port %s: %s", get_url(),    LOC_M(0,"No matching keys and certificates found.\n"));    cert_err_unbind();    cert_failure = 1;
Roxen.git/server/base_server/roxen.pike:2830:    getcwd());    }    }       void create(int pn, string i, void|int ignore_eaddrinuse)    {    ctx->random = Crypto.Random.random_string;       set_up_ssl_variables( this_object() );    + #if constant(SSL.Constants.PROTOCOL_TLS_MAX) +  set_version(); + #endif +     filter_preferred_suites();       ::setup(pn, i);       certificates_changed (0, ignore_eaddrinuse);       // Install the change callbacks here to avoid duplicate calls    // above.    // FIXME: Both variables ought to be updated on save before the    // changed callback is called. Currently you can get warnings    // that the files don't match if you update both variables    // at the same time.    getvar ("ssl_cert_file")->set_changed_callback (certificates_changed);    getvar ("ssl_key_file")->set_changed_callback (certificates_changed);      #if constant(SSL.ServerConnection)    getvar("ssl_key_bits")->set_changed_callback(filter_preferred_suites);    getvar("ssl_suite_filter")->set_changed_callback(filter_preferred_suites); -  + #endif + #if constant(SSL.Constants.PROTOCOL_TLS_MAX)    getvar("ssl_min_version")->set_changed_callback(set_version);   #endif    }       string _sprintf( )    {    return "StartTLSProtocol(" + get_url() + ")";    }   }      class SSLProtocol   //! Base protocol for SSL ports.   //!   //! Exactly like Port, but uses SSL.   {    inherit StartTLSProtocol;    -  SSL.sslfile accept() +  SSL.File accept()    {    Stdio.File q = ::accept();    if (q) { -  SSL.sslfile ssl = SSL.sslfile (q, ctx); +  SSL.File ssl = SSL.File (q, ctx);    if (ssl->accept) ssl->accept();    return ssl;    }    return 0;    }      #if constant(SSL.Connection)    protected void bind (void|int ignore_eaddrinuse)    {    // Don't bind if we don't have correct certs. -  if (!sizeof(ctx->cert_pairs)) return; +  // if (!sizeof(ctx->cert_pairs)) return;    ::bind (ignore_eaddrinuse);    }   #else    protected void bind (void|int ignore_eaddrinuse)    {    // Don't bind if we don't have correct certs.    if (!ctx->certificates) return;    ::bind (ignore_eaddrinuse);    }   #endif
Roxen.git/server/base_server/roxen.pike:2933: Inside #if undefined(DEBUG)
   mixed `->( string x )    {    if(!real) realize();    return predef::`->(real, x);    }    };   #endif    foreach( glob( "prot_*.pike", get_dir("protocols") ), string s )    {    sscanf( s, "prot_%s.pike", s ); - #if !constant(SSL.sslfile) + #if !constant(SSL.File)    switch( s )    {    case "https":    case "ftps":    continue;    }   #endif    report_debug( "\b%s \b", s );       catch
Roxen.git/server/base_server/roxen.pike:2955: Inside #if defined(DEBUG)
  #ifdef DEBUG    protocols[ s ] = (program)("protocols/prot_"+s+".pike");   #else    protocols[ s ] = lazy_load( ("protocols/prot_"+s+".pike"),s );   #endif    };    }    foreach( glob("prot_*.pike",get_dir("../local/protocols")||({})), string s )    {    sscanf( s, "prot_%s.pike", s ); - #if !constant(SSL.sslfile) + #if !constant(SSL.File)    switch( s )    {    case "https":    case "ftps":    continue;    }   #endif    report_debug( "\b%s \b", s );    catch {   #ifdef DEBUG
Roxen.git/server/base_server/roxen.pike:3534:    if( conf )    {    if( conf->error_log )    conf->error_log[log_index] += ({ log_time });    }       if(errtype >= 1)    report_debug( s );   }    + protected BackgroundProcess error_log_cleaner_process; +  + protected void clean_error_log(mapping(string:array(int)) log, +  mapping(string:int) cutoffs) + { +  if (!log || !sizeof(log)) return; +  foreach(cutoffs; string prefix; int cutoff) { +  foreach(log; string key; array(int) times) { +  if (!has_prefix(key, prefix)) continue; +  int sz = sizeof(times); +  times = filter(times, `>=, cutoff); +  if (sizeof(times) == sz) continue; +  // NB: There's a race here, where newly triggered errors may be lost. +  // It's very unlikely to be a problem in practice though. +  if (!sizeof(times)) { +  m_delete(log, key); +  } else { +  log[key] = times; +  } +  } +  } + } +  + protected void error_log_cleaner() + { +  mapping(string:int) cutoffs = ([ +  "1,": time(1) - 3600*24*7, // Keep notices for 7 days. +  ]); +  +  // First the global error_log. +  clean_error_log(error_log, cutoffs); +  +  // Then all configurations and modules. +  foreach(configurations, Configuration conf) { +  clean_error_log(conf->error_log, cutoffs); +  +  foreach(indices(conf->otomod), RoxenModule mod) { +  clean_error_log(mod->error_log, cutoffs); +  } +  } + } +  + protected void start_error_log_cleaner() + { +  if (error_log_cleaner_process) return; +  +  // Clean the error log once every hour. +  error_log_cleaner_process = BackgroundProcess(3600, error_log_cleaner); + } +  + protected void stop_error_log_cleaner() + { +  if (error_log_cleaner_process) { +  error_log_cleaner_process->stop(); +  error_log_cleaner_process = UNDEFINED; +  } + } +    // When was Roxen started?   int boot_time =time();   int start_time =time();      string version()   {   #ifndef NSERIOUS    return query("default_ident")?real_version:query("ident");   #else    multiset choices=(<>);
Roxen.git/server/base_server/roxen.pike:3795:    id->client ), frommapp);       id->cache_status["icachedraw"] = 1;       mapping meta;    string data;    array guides;   #ifdef ARG_CACHE_DEBUG    werror("draw args: %O\n", args );   #endif -  mixed reply = draw_function( @copy_value(args), id ); +  mixed reply; +  if (mixed err = catch { +  reply = draw_function( @copy_value(args), id ); +  }) { +  master()->handle_error(err); +  return; +  }       if( !reply ) {   #ifdef ARG_CACHE_DEBUG    werror("%O(%{%O, %}%O) ==> 0\n",    draw_function, args, id);   #endif    return;    }       if( arrayp( args ) )
Roxen.git/server/base_server/roxen.pike:4312:    {    if(!stringp(data)) return;   #ifdef ARG_CACHE_DEBUG    werror("store %O (%d bytes)\n", id, strlen(data) );   #endif    meta_cache_insert( id, meta );    string meta_data = encode_value( meta );   #ifdef ARG_CACHE_DEBUG    werror("Replacing entry for %O\n", id );   #endif -  QUERY("REPLACE INTO "+name+ +  if (sizeof(data) <= 8*1024*1024) { +  // Should fit in the 16 MB query limit without problem. +  // Albeit it might trigger a slow query entry for large +  // entries. +  QUERY("REPLACE INTO " + name +    " (id,size,atime,meta,data) VALUES"    " (%s,%d,UNIX_TIMESTAMP()," MYSQL__BINARY "%s," MYSQL__BINARY "%s)",    id, strlen(data)+strlen(meta_data), meta_data, data ); -  +  } else { +  // We need to perform multiple queries.   #ifdef ARG_CACHE_DEBUG -  +  werror("Writing %d bytes of padding for %s.\n", sizeof(data), id); + #endif +  array(string) a = data/(8.0*1024*1024); +  // NB: We clear the meta field to ensure that the entry +  // is invalid while we perform the insert. +  QUERY("REPLACE INTO " + name + +  " (id,size,atime,meta,data) VALUES" +  " (%s,%d,UNIX_TIMESTAMP(),'',SPACE(%d))", +  id, strlen(data)+strlen(meta_data), sizeof(data)); +  int pos; +  foreach(a, string frag) { + #ifdef ARG_CACHE_DEBUG +  werror("Writing fragment at position %d for %s.\n", pos, id); + #endif +  QUERY("UPDATE " + name + +  " SET data = INSERT(data, %d, %d, "MYSQL__BINARY "%s)" +  " WHERE id = %s", +  pos+1, sizeof(frag), frag, id); +  pos += sizeof(frag); +  } +  /* Set the meta data field to a valid value to enable the entry. */ + #ifdef ARG_CACHE_DEBUG +  werror("Writing metadata for %s.\n", id); + #endif +  QUERY("UPDATE " + name + +  " SET meta = " MYSQL__BINARY "%s" +  " WHERE id = %s", +  meta_data, id); +  } + #ifdef ARG_CACHE_DEBUG    array(mapping(string:string)) q =    QUERY("SELECT meta, data FROM " + name +    " WHERE id = %s", id);    if (!q || sizeof(q) != 1) {    werror("Unexpected result size: %d\n",    q && sizeof(q));    } else {    if (q[0]->meta != meta_data) {    werror("Meta data differs: %O != %O\n",    meta_data, q[0]->meta);    }    if (q[0]->data != data) { -  werror("Data differs: %O != %O\n", -  data, q[0]->data); +  string d = q[0]->data; +  int i; +  int cnt; +  for (i = 0; i < sizeof(data); i++) { +  if (data[i] == d[i]) continue; +  werror("Data differs at offset %d: %d != %d\n", +  i, data[i], d[i]); +  if (cnt++ > 10) break;    }    } -  +  }   #endif    }       protected mapping restore_meta( string id, RequestID rid )    {    if( array item = meta_cache[ id ] )    {    item[ 1 ] = time(1); // Update cached atime.    return item[ 0 ];    }
Roxen.git/server/base_server/roxen.pike:4608:    report_debug("Requesting unknown key %s %O from %O\n",    message,    id->not_query,    (sizeof(id->referer)?id->referer[0]:"unknown page"));    return 0;    }    }    throw (err);    }    if( !(res = restore( na,id )) ) { -  error("Draw callback %O did not generate any data.\n" +  report_error("Draw callback %O did not generate any data.\n"    "na: %O\n"    "id: %O\n",    draw_function, na, id); -  +  return 0;    }    }    res->stat = ({ 0, 0, 0, 900000000, 0, 0, 0, 0, 0 });       // Setting the cacheable flag is done in order to get headers sent which    // cause the image to be cached in the client even when using https    // sessions. -  RAISE_CACHE(INITIAL_CACHEABLE); +  // +  // NB: Raise it above INITIAL_CACHEABLE to force an Expires header. +  RAISE_CACHE(31557600); // A year.       // With the new (5.0 and newer) arg-cache enabled by default we can    // allow authenticated images in the protocol cache. At this point    // http.pike will have cleared it so re-enable explicitly.    PROTO_CACHE();       return res;    }       mapping metadata( array|string|mapping data,
Roxen.git/server/base_server/roxen.pike:4764:       master()->resolv("DBManager.is_module_table")    ( 0,"local",name,"Image cache for "+name);       QUERY("CREATE TABLE "+name+" ("    "id CHAR(64) NOT NULL PRIMARY KEY, "    "size INT UNSIGNED NOT NULL DEFAULT 0, "    "uid CHAR(32) NOT NULL DEFAULT '', "    "atime INT UNSIGNED NOT NULL DEFAULT 0,"    "meta MEDIUMBLOB NOT NULL DEFAULT ''," -  "data MEDIUMBLOB NOT NULL DEFAULT ''," +  "data LONGBLOB NOT NULL DEFAULT '',"    "INDEX atime_id (atime, id)"    ")" );    }       // Create index in old databases. Index is used when flushing old    // entries. Column 'id' is included in index in order to avoid    // reading data file.    array(mapping(string:mixed)) res = QUERY("SHOW INDEX FROM " + name);    if(search(res->Key_name, "atime_id") < 0) {    report_debug("Updating " + name + " image cache: "    "Adding index atime_id on %s... ", name);    int start_time = gethrtime();    QUERY("CREATE INDEX atime_id ON " + name + " (atime, id)");    report_debug("complete. [%f s]\n", (gethrtime() - start_time)/1000000.0);    report_debug("Updating " + name + " image cache: "    "Dropping index atime on %s... ", name);    start_time = gethrtime();    QUERY("DROP INDEX atime ON " + name);    report_debug("complete. [%f s]\n", (gethrtime() - start_time)/1000000.0);    } -  +  res = QUERY("SHOW COLUMNS FROM " + name + " WHERE Field = 'data'"); +  if (lower_case(res[0]->Type) != "longblob") { +  report_debug("Updating " + name + " image cache: " +  "Increasing maximum blob size..."); +  int start_time = gethrtime(); +  QUERY("ALTER TABLE " + name + +  " MODIFY COLUMN data LONGBLOB NOT NULL DEFAULT ''"); +  report_debug("complete. [%f s]\n", (gethrtime() - start_time)/1000000.0);    } -  +  }       Sql.Sql get_db()    {    return dbm_cached_get("local");    }       protected void init_db( )    {    catch(sync_meta());    setup_tables();
Roxen.git/server/base_server/roxen.pike:4807:       void do_cleanup( )    {    // Flushes may be costly in large sites (at least the OPTIMIZE TABLE    // command) so schedule next run sometime after 04:30 the day after    // tomorrow.    //    // Note: The OPTIMIZE TABLE step has been disabled. /mast    int now = time();    mapping info = localtime(now); -  int wait = (int) ((24 - info->hour) + 24 + 4.5) * 3600 + random(500); +  int wait = (int) (((24 - info->hour) + 24 + 4.5) % 24) * 3600 + random(500);    background_run(wait, do_cleanup);       // Remove items older than one week    flush(now - 7 * 3600 * 24);    }       void create( string id, function draw_func )    //! Instantiate an image cache of your own, whose image files will    //! be stored in a table `id' in the cache mysql database,    //!
Roxen.git/server/base_server/roxen.pike:5328:       void refresh_arg(string id)    //! Indicate that the entry @[id] needs to be included in the next    //! @[write_dump]. @[id] must be an existing entry.    {    GET_DB();    QUERY("UPDATE "+name+"2 SET rep_time=NOW() WHERE id = %s", id);    }   }    - mapping cached_decoders = ([]); + mapping(string:Charset.Decoder) cached_decoders = ([]);   string decode_charset( string charset, string data )   {    // FIXME: This code is probably not thread-safe!    if( charset == "iso-8859-1" ) return data;    if( !cached_decoders[ charset ] ) -  cached_decoders[ charset ] = Locale.Charset.decoder( charset ); +  cached_decoders[ charset ] = Charset.decoder( charset );    data = cached_decoders[ charset ]->feed( data )->drain();    cached_decoders[ charset ]->clear();    return data;   }      //! Check if a cache key has been marked invalid (aka stale).   int(0..1) invalidp(CacheKey key)   {    catch {    return !key || (key->invalidp && key->invalidp());
Roxen.git/server/base_server/roxen.pike:6204: Inside #if 0
   while (1) {    sleep (10);    describe_all_threads();    }    });   #endif      #ifdef LOG_GC_TIMESTAMPS    Pike.gc_parameters(([ "pre_cb": lambda() {    gc_start = gethrtime(); +  gc_histogram = ([]);    werror("GC runs at %s", ctime(time()));    },    "post_cb":lambda() {    werror("GC done after %dus\n",    gethrtime() - gc_start);    }, -  + #ifdef LOG_GC_HISTOGRAM    "destruct_cb":lambda(object o) { -  +  // NB: These calls to sprintf(%O) can +  // take significant time.    gc_histogram[sprintf("%O", object_program(o))]++; -  + #ifdef LOG_GC_VERBOSE    werror("GC cyclic reference in %O.\n",    o); -  + #endif    }, -  + #endif /* LOG_GC_HISTOGRAM */    "done_cb":lambda(int n) {    if (!n) return;    werror("GC zapped %d things.\n", n); -  mapping h = gc_histogram + ([]); + #ifdef LOG_GC_HISTOGRAM +  mapping h = gc_histogram; +  gc_histogram = ([]);    if (!sizeof(h)) return;    array i = indices(h);    array v = values(h);    sort(v, i); -  werror("GC histogram (accumulative):\n"); +  werror("GC histogram:\n");    foreach(reverse(i)[..9], string p) {    werror("GC: %s: %d\n", p, h[p]);    } -  + #endif /* LOG_GC_HISTOGRAM */    },    ]));    if (!Pike.gc_parameters()->pre_cb) {    // GC callbacks not available.    GCTimestamp();    }   #endif       // For RBF    catch(mkdir(getenv("VARDIR") || "../var"));
Roxen.git/server/base_server/roxen.pike:6268:       if (!has_value (compat_levels, roxen_ver))    report_debug ("Warning: The current version %s does not exist in "    "roxen.compat_levels.\n", roxen_ver);       add_constant( "Protocol", Protocol );   #ifdef TIMERS    call_out( show_timers, 30 );   #endif    - #if constant(SSL.sslfile) + #if constant(SSL.File)    add_constant( "StartTLSProtocol", StartTLSProtocol );    add_constant( "SSLProtocol", SSLProtocol );   #endif       dump( "etc/modules/Variable.pmod/module.pmod" );    dump( "etc/modules/Variable.pmod/Language.pike" );    dump( "etc/modules/Variable.pmod/Schedule.pike" );       foreach( glob("*.pike", get_dir( "etc/modules/Variable.pmod/"))    -({"Language.pike", "Schedule.pike"}), string f )
Roxen.git/server/base_server/roxen.pike:6406: Inside #if constant(Standards.X509)
   report_error("Failed to read certificate %s.\n", file_name);    continue;    }       // Note: set_u_and_gid() hasn't been called yet,    // so there's no need for Privs.    Standards.PEM.Messages msgs = Standards.PEM.Messages(old_cert);       int upgrade_needed;    -  foreach(msgs->parts; string part; Standards.PEM.Message msg) { +  foreach(msgs->parts; string part; array(Standards.PEM.Message) msg) {    if (!has_suffix(part, "CERTIFICATE")) continue;    Standards.X509.TBSCertificate tbs = -  Standards.X509.decode_certificate(msg->body); +  Standards.X509.decode_certificate(msg[0]->body);    upgrade_needed = (tbs->version < 3);    break;    }       if (!upgrade_needed || (sizeof(msgs->parts) != 2)) continue;       // NB: We reuse the old key.    Crypto.Sign key; -  foreach(msgs->parts; string part; Standards.PEM.Message msg) { +  foreach(msgs->parts; string part; array(Standards.PEM.Message) msg) {    if (!has_suffix(part, "PRIVATE KEY")) continue; -  if (msg->headers["dek-info"]) { +  if (msg[0]->headers["dek-info"]) {    // Not supported here.    break;    } -  key = Standards.X509.parse_private_key(msg->body); +  key = Standards.X509.parse_private_key(msg[0]->body);    }    if (!key) continue;       report_notice("Renewing certificate: %O...\n", file_name);    cert = Roxen.generate_self_signed_certificate("*", key);   #endif /* constant(Standards.X509) */    }       if (cert) {    // Note: set_u_and_gid() hasn't been called yet,
Roxen.git/server/base_server/roxen.pike:6477:    c->enable_all_modules();   #endif // RUN_SELF_TEST      #ifdef THREADS    start_handler_threads();   #if constant(Filesystem.Monitor.basic)    start_fsgarb();   #endif   #endif /* THREADS */    +  start_error_log_cleaner(); +    #ifdef TEST_EUID_CHANGE    if (test_euid_change) {    Stdio.File f = Stdio.File();    if (f->open ("rootonly", "r") && f->read())    werror ("Backend thread can read rootonly\n");    else    werror ("Backend thread can't read rootonly\n");    }   #endif   
Roxen.git/server/base_server/roxen.pike:7311: Inside #if defined(SECURITY_PATTERN_DEBUG) || defined(HTACCESS_DEBUG)
   },   #if defined(SECURITY_PATTERN_DEBUG) || defined(HTACCESS_DEBUG)    " report_debug(sprintf(\"Verifying against IP %%O (0x%%08x).\\n\",\n"    " id->remoteaddr, remote_ip));\n"   #endif /* SECURITY_PATTERN_DEBUG || HTACCESS_DEBUG */    " if (%s)",    (< " int remote_ip = Roxen.ip_to_int(id->remoteaddr)" >),    }), "ip", }),    ({ "user=%s",1,({ 1,    lambda( string x ) { -  return ({sprintf("(< %{%O, %}>)", x/"," )}); +  return ({sprintf("((multiset)(< %{%O, %}>))", x/"," )});    },       " if (((user || (user = authmethod->authenticate(id, userdb_module)))\n"    " && ((%[0]s->any) || (%[0]s[user->name()]))) || %[0]s->ANY) ",    (<" User user" >),    // No need to NOCACHE () here, since it's up to the    // auth-modules to do that.    }), "user", }),    ({ "group=%s",1,({ 1,    lambda( string x ) { -  return ({sprintf("(< %{%O, %}>)", x/"," )}); +  return ({sprintf("((multiset)(< %{%O, %}>))", x/"," )});    },    " if ((user || (user = authmethod->authenticate(id, userdb_module)))\n"    " && ((%[0]s->any && sizeof(user->groups())) ||\n"    " sizeof(mkmultiset(user->groups())&%[0]s)))",    (<" User user" >),    // No need to NOCACHE () here, since it's up to the    // auth-modules to do that.    }), "group", }),    ({ "dns=%s",1,({    " if(!dns && \n"