835c6c2001-06-17Martin Nilsson // This file is part of Roxen WebServer.
f41b982009-05-07Martin Stjernholm // Copyright © 1996 - 2009, Roxen IS.
835c6c2001-06-17Martin Nilsson // // The Roxen WebServer main program.
24c6c12000-02-20Martin Nilsson //
02ee412000-12-17Henrik Grubbström (Grubba) // Per Hedbor, Henrik Grubbström, Pontus Hagland, David Hedbor and others.
edc9af1998-07-11David Hedbor // ABS and suicide systems contributed freely by Francesco Chemolli
835c6c2001-06-17Martin Nilsson 
8a1a762013-01-16Marcus Wellhardh constant cvs_version="$Id$";
5d3e442002-12-09Henrik Grubbström (Grubba)  //! @appears roxen //! //! The Roxen WebServer main program.
8fb5172000-05-27Per Hedbor  // The argument cache. Used by the image cache.
855c391999-11-29Per Hedbor ArgCache argcache;
1720541998-10-04Henrik Grubbström (Grubba)  // Some headerfiles
4dd97c1996-12-04Per Hedbor #define IN_ROXEN
8540e11997-06-14Francesco Chemolli #include <roxen.h> #include <config.h>
b1fca01996-11-12Per Hedbor #include <module.h>
753a831999-08-30Per Hedbor #include <stat.h>
9c19002001-02-27Per Hedbor #include <timers.h>
14179b1997-01-29Per Hedbor 
1720541998-10-04Henrik Grubbström (Grubba) // Inherits
753a831999-08-30Per Hedbor inherit "global_variables";
6897c92001-06-27Honza Petrous #ifdef SNMP_AGENT inherit "snmpagent"; #endif
4029da2007-03-02Henrik Grubbström (Grubba) #ifdef SMTP_RELAY inherit "smtprelay"; #endif
b1fca01996-11-12Per Hedbor inherit "hosts"; inherit "disk_cache";
5622892013-09-20Henrik Grubbström (Grubba) inherit "fsgc";
970e242000-09-12Per Hedbor // inherit "language";
2ff8461999-09-02Per Hedbor inherit "supports";
08eba62000-07-09Per Hedbor inherit "module_support";
970e242000-09-12Per Hedbor inherit "config_userdb";
7c92b91998-11-19Per Hedbor 
a28eca2009-01-11Martin Stjernholm // Used to find out which thread is the backend thread.
79c3a62001-04-25Jonas Wallden Thread.Thread backend_thread;
23414a2000-07-21Andreas Lange // --- Locale defines --- //<locale-token project="roxen_start"> LOC_S </locale-token> //<locale-token project="roxen_message"> LOC_M </locale-token> #define LOC_S(X,Y) _STR_LOCALE("roxen_start",X,Y) #define LOC_M(X,Y) _STR_LOCALE("roxen_message",X,Y) #define CALL_M(X,Y) _LOCALE_FUN("roxen_message",X,Y)
be788e2000-03-27Martin Nilsson // --- Debug defines ---
81f8af1999-12-20Martin Nilsson  #ifdef SSL3_DEBUG
fe1c132005-05-25Martin Stjernholm # define SSL3_WERR(X) report_debug("TLS port %s: %s\n", get_url(), X)
81f8af1999-12-20Martin Nilsson #else
bfb4d41999-12-28Martin Nilsson # define SSL3_WERR(X)
81f8af1999-12-20Martin Nilsson #endif #ifdef THREAD_DEBUG
3e6f6b2001-03-11Martin Nilsson # define THREAD_WERR(X) report_debug("Thread: "+X+"\n")
81f8af1999-12-20Martin Nilsson #else
bfb4d41999-12-28Martin Nilsson # define THREAD_WERR(X)
81f8af1999-12-20Martin Nilsson #endif
60a9121999-10-10Henrik Grubbström (Grubba) 
8cc5fc2003-03-05Martin Stjernholm // 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
8514832000-11-27Per Hedbor #define DDUMP(X) sol( combine_path( __FILE__, "../../" + X ), dump )
fc40392008-08-15Martin Stjernholm protected function sol = master()->set_on_load;
8514832000-11-27Per Hedbor 
32cd1c2008-10-13Martin Stjernholm // Tell Pike.count_memory this is global. constant pike_cycle_depth = 0;
aa78722001-08-24Martin Stjernholm #ifdef TEST_EUID_CHANGE int test_euid_change; #endif
50b6972001-08-31Per Hedbor string md5( string what ) {
4e32072009-01-09Stephen R. van den Berg  return Gmp.mpz(Crypto.MD5.hash( what ),256)->digits(32);
50b6972001-08-31Per Hedbor }
970e242000-09-12Per Hedbor string query_configuration_dir() { return configuration_dir; }
855c391999-11-29Per Hedbor 
f494782008-10-02Martin Stjernholm array(string|int) filename_2 (program|object o)
95b69e1999-09-05Per Hedbor {
5964251999-11-19Per Hedbor  if( objectp( o ) ) o = object_program( o );
f494782008-10-02Martin Stjernholm  string fname = Program.defined (o); int line; if (fname) { array(string) p = fname / ":"; if (sizeof (p) > 1 && p[-1] != "" && sscanf (p[-1], "%d%*c", int l) == 1) { fname = p[..<1] * ":"; line = l; } } 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)..];
92dc212011-12-27Martin Stjernholm  else if (has_prefix (fname, roxenloader.server_dir)) fname = fname[sizeof (roxenloader.server_dir)..];
f494782008-10-02Martin Stjernholm  return ({fname, line}); } string filename( program|object o ) { [string fname, int line] = filename_2 (o); return fname || "(unknown program)";
95b69e1999-09-05Per Hedbor }
fc40392008-08-15Martin Stjernholm protected int once_mode;
b215032004-05-04Henrik Grubbström (Grubba) 
8cbc5f2002-09-03Martin Stjernholm // 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.
3be6fd2006-10-30Martin Stjernholm array(string) compat_levels = ({"2.1", "2.2", "2.4", "2.5", "3.3", "3.4",
1f6b0c2008-12-23Martin Stjernholm  "4.0", "4.5",
7d07882011-01-20Martin Stjernholm  "5.0", "5.1", "5.2" });
b0a9ea2001-04-18Martin Stjernholm 
0f8b2f1999-03-27Henrik Grubbström (Grubba) #ifdef THREADS
6664122001-02-23Per Hedbor mapping(string:string) thread_names = ([]);
c320bc2012-02-14Jonas Wallden string thread_name( object thread, int|void skip_auto_name )
6664122001-02-23Per Hedbor { string tn;
c320bc2012-02-14Jonas Wallden  if( thread_names[ tn=sprintf("%O",thread) ] || skip_auto_name )
6664122001-02-23Per Hedbor  return thread_names[tn]; return tn; } void name_thread( object thread, string name ) {
663a692012-02-14Jonas Wallden  string th_key = sprintf("%O", thread); if (name) thread_names[th_key] = name; else m_delete(thread_names, th_key);
6664122001-02-23Per Hedbor }
0f8b2f1999-03-27Henrik Grubbström (Grubba) // This mutex is used by Privs
855c391999-11-29Per Hedbor Thread.Mutex euid_egid_lock = Thread.Mutex();
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* THREADS */
c5e0961999-10-04Per Hedbor /*
8fb5172000-05-27Per Hedbor  * The privilege changer. Works like a mutex lock, but changes the UID/GID * while held. Blocks all threads. *
c5e0961999-10-04Per Hedbor  * Based on privs.pike,v 1.36. */
0f8b2f1999-03-27Henrik Grubbström (Grubba) int privs_level;
fc40392008-08-15Martin Stjernholm protected class Privs
c5e0961999-10-04Per Hedbor {
0f8b2f1999-03-27Henrik Grubbström (Grubba) #if efun(seteuid) int saved_uid; int saved_gid; int new_uid; int new_gid;
7b798d2000-07-04Per Hedbor #define LOGP (variables && variables->audit && variables->audit->query())
0f8b2f1999-03-27Henrik Grubbström (Grubba)  #if constant(geteuid) && constant(getegid) && constant(seteuid) && constant(setegid) #define HAVE_EFFECTIVE_USER #endif
fc40392008-08-15Martin Stjernholm  private string _getcwd()
0f8b2f1999-03-27Henrik Grubbström (Grubba)  { if (catch{return(getcwd());}) { return("Unknown directory (no x-bit on current directory?)"); } }
fc40392008-08-15Martin Stjernholm  private string dbt(array t)
0f8b2f1999-03-27Henrik Grubbström (Grubba)  { if(!arrayp(t) || (sizeof(t)<2)) return ""; return (((t[0]||"Unknown program")-(_getcwd()+"/"))-"base_server/")+":"+t[1]+"\n"; } #ifdef THREADS
fc40392008-08-15Martin Stjernholm  protected mixed mutex_key; // Only one thread may modify the euid/egid at a time. protected object threads_disabled;
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* THREADS */ int p_level; void create(string reason, int|string|void uid, int|string|void gid) {
f198282001-09-27Henrik Grubbström (Grubba)  // No need for Privs if the uid has been changed permanently. if(getuid()) return;
0f8b2f1999-03-27Henrik Grubbström (Grubba) #ifdef PRIVS_DEBUG
3e6f6b2001-03-11Martin Nilsson  report_debug(sprintf("Privs(%O, %O, %O)\n" "privs_level: %O\n", reason, uid, gid, privs_level));
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* PRIVS_DEBUG */ #ifdef HAVE_EFFECTIVE_USER array u; #ifdef THREADS if (euid_egid_lock) {
d307c72008-08-06Martin Stjernholm  if (mixed err = catch { mutex_key = euid_egid_lock->lock(); })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
0f8b2f1999-03-27Henrik Grubbström (Grubba)  }
d3a71c1999-04-20Martin Stjernholm  threads_disabled = _disable_threads();
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* THREADS */ p_level = privs_level++; /* Needs to be here since root-priviliges may be needed to * use getpw{uid,nam}. */ saved_uid = geteuid(); saved_gid = getegid(); seteuid(0); /* A string of digits? */
c5e0961999-10-04Per Hedbor  if(stringp(uid) && (replace(uid,"0123456789"/"",({""})*10)==""))
0f8b2f1999-03-27Henrik Grubbström (Grubba)  uid = (int)uid;
c5e0961999-10-04Per Hedbor 
3a4b9a1999-12-27Martin Nilsson  if(stringp(gid) && (replace(gid, "0123456789"/"", ({"" })*10) == ""))
0f8b2f1999-03-27Henrik Grubbström (Grubba)  gid = (int)gid;
c5e0961999-10-04Per Hedbor  if(!stringp(uid))
0f8b2f1999-03-27Henrik Grubbström (Grubba)  u = getpwuid(uid);
81f8af1999-12-20Martin Nilsson  else
c5e0961999-10-04Per Hedbor  {
0f8b2f1999-03-27Henrik Grubbström (Grubba)  u = getpwnam(uid);
81f8af1999-12-20Martin Nilsson  if(u)
0f8b2f1999-03-27Henrik Grubbström (Grubba)  uid = u[2]; }
eae43a2001-08-13Per Hedbor 
81f8af1999-12-20Martin Nilsson  if(u && !gid)
c5e0961999-10-04Per Hedbor  gid = u[3];
81f8af1999-12-20Martin Nilsson  if(!u)
c5e0961999-10-04Per Hedbor  {
81f8af1999-12-20Martin Nilsson  if (uid && (uid != "root"))
c5e0961999-10-04Per Hedbor  {
81f8af1999-12-20Martin Nilsson  if (intp(uid) && (uid >= 60000))
c5e0961999-10-04Per Hedbor  {
b84a161999-06-07Martin Stjernholm  report_warning(sprintf("Privs: User %d is not in the password database.\n" "Assuming nobody.\n", uid));
0f8b2f1999-03-27Henrik Grubbström (Grubba)  // Nobody. gid = gid || uid; // Fake a gid also. u = ({ "fake-nobody", "x", uid, gid, "A real nobody", "/", "/sbin/sh" }); } else { error("Unknown user: "+uid+"\n"); } } else { u = ({ "root", "x", 0, gid, "The super-user", "/", "/sbin/sh" }); } } if(LOGP)
49cd282000-08-11Andreas Lange  report_notice(LOC_M(1, "Change to %s(%d):%d privs wanted (%s), from %s"),
23414a2000-07-21Andreas Lange  (string)u[0], (int)uid, (int)gid, (string)reason, (string)dbt(backtrace()[-2]));
0f8b2f1999-03-27Henrik Grubbström (Grubba) 
9963842001-09-26Martin Stjernholm  if (u[2]) {
0f8b2f1999-03-27Henrik Grubbström (Grubba) #if efun(cleargroups)
d307c72008-08-06Martin Stjernholm  if (mixed err = catch { cleargroups(); })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* cleargroups */ #if efun(initgroups)
d307c72008-08-06Martin Stjernholm  if (mixed err = catch { initgroups(u[0], u[3]); })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif
9963842001-09-26Martin Stjernholm  }
0f8b2f1999-03-27Henrik Grubbström (Grubba)  gid = gid || getgid(); int err = (int)setegid(new_gid = gid); if (err < 0) {
49cd282000-08-11Andreas Lange  report_warning(LOC_M(2, "Privs: WARNING: Failed to set the "
23414a2000-07-21Andreas Lange  "effective group id to %d!\n" "Check that your password database is correct " "for user %s(%d),\n and that your group " "database is correct.\n"), gid, (string)u[0], (int)uid);
0f8b2f1999-03-27Henrik Grubbström (Grubba)  int gid2 = gid; #ifdef HPUX_KLUDGE if (gid >= 60000) { /* HPUX has doesn't like groups higher than 60000, * but has assigned nobody to group 60001 (which isn't even * in /etc/group!). * * HPUX's libc also insists on filling numeric fields it doesn't like * with the value 60001! */
81f8af1999-12-20Martin Nilsson  report_debug("Privs: WARNING: Assuming nobody-group.\n"
0f8b2f1999-03-27Henrik Grubbström (Grubba)  "Trying some alternatives...\n"); // Assume we want the nobody group, and try a couple of alternatives foreach(({ 60001, 65534, -2 }), gid2) {
81f8af1999-12-20Martin Nilsson  report_debug("%d... ", gid2);
0f8b2f1999-03-27Henrik Grubbström (Grubba)  if (initgroups(u[0], gid2) >= 0) { if ((err = setegid(new_gid = gid2)) >= 0) {
81f8af1999-12-20Martin Nilsson  report_debug("Success!\n");
0f8b2f1999-03-27Henrik Grubbström (Grubba)  break; } } } } #endif /* HPUX_KLUDGE */ if (err < 0) {
81f8af1999-12-20Martin Nilsson  report_debug("Privs: Failed\n");
3b76dc2002-01-29Martin Stjernholm  error ("Failed to set EGID to %d\n", gid);
0f8b2f1999-03-27Henrik Grubbström (Grubba)  }
81f8af1999-12-20Martin Nilsson  report_debug("Privs: WARNING: Set egid to %d instead of %d.\n",
0f8b2f1999-03-27Henrik Grubbström (Grubba)  gid2, gid); gid = gid2; } if(getgid()!=gid) setgid(gid||getgid()); seteuid(new_uid = uid);
0217b82003-03-03Henrik Grubbström (Grubba)  enable_coredumps(1);
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* HAVE_EFFECTIVE_USER */ } void destroy() {
f198282001-09-27Henrik Grubbström (Grubba)  // No need for Privs if the uid has been changed permanently. if(getuid()) return;
0f8b2f1999-03-27Henrik Grubbström (Grubba) #ifdef PRIVS_DEBUG
3e6f6b2001-03-11Martin Nilsson  report_debug(sprintf("Privs->destroy()\n" "privs_level: %O\n", privs_level));
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* PRIVS_DEBUG */ #ifdef HAVE_EFFECTIVE_USER /* Check that we don't increase the privs level */ if (p_level >= privs_level) { report_error(sprintf("Change back to uid#%d gid#%d from uid#%d gid#%d\n" "in wrong order! Saved level:%d Current level:%d\n" "Occurs in:\n%s\n", saved_uid, saved_gid, new_uid, new_gid, p_level, privs_level, describe_backtrace(backtrace()))); return(0); } if (p_level != privs_level-1) { report_error(sprintf("Change back to uid#%d gid#%d from uid#%d gid#%d\n" "Skips privs level. Saved level:%d Current level:%d\n" "Occurs in:\n%s\n", saved_uid, saved_gid, new_uid, new_gid, p_level, privs_level, describe_backtrace(backtrace()))); } privs_level = p_level; if(LOGP) {
d307c72008-08-06Martin Stjernholm  if (mixed err = catch {
0f8b2f1999-03-27Henrik Grubbström (Grubba)  array bt = backtrace(); if (sizeof(bt) >= 2) {
c8ee712000-09-09Andreas Lange  report_notice(LOC_M(3,"Change back to uid#%d gid#%d, from %s")+"\n",
23414a2000-07-21Andreas Lange  saved_uid, saved_gid, dbt(bt[-2]));
0f8b2f1999-03-27Henrik Grubbström (Grubba)  } else {
49cd282000-08-11Andreas Lange  report_notice(LOC_M(4,"Change back to uid#%d gid#%d, "
c8ee712000-09-09Andreas Lange  "from backend")+"\n", saved_uid, saved_gid);
0f8b2f1999-03-27Henrik Grubbström (Grubba)  }
d307c72008-08-06Martin Stjernholm  })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
0f8b2f1999-03-27Henrik Grubbström (Grubba)  }
81f8af1999-12-20Martin Nilsson #ifdef PRIVS_DEBUG
0f8b2f1999-03-27Henrik Grubbström (Grubba)  int uid = geteuid(); if (uid != new_uid) {
3e6f6b2001-03-11Martin Nilsson  report_debug("Privs: UID #%d differs from expected #%d\n" "%s\n", uid, new_uid, describe_backtrace(backtrace()));
0f8b2f1999-03-27Henrik Grubbström (Grubba)  } int gid = getegid(); if (gid != new_gid) {
3e6f6b2001-03-11Martin Nilsson  report_debug("Privs: GID #%d differs from expected #%d\n" "%s\n", gid, new_gid, describe_backtrace(backtrace()));
0f8b2f1999-03-27Henrik Grubbström (Grubba)  }
81f8af1999-12-20Martin Nilsson #endif /* PRIVS_DEBUG */
0f8b2f1999-03-27Henrik Grubbström (Grubba)  seteuid(0); array u = getpwuid(saved_uid); #if efun(cleargroups)
d307c72008-08-06Martin Stjernholm  if (mixed err = catch { cleargroups(); })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* cleargroups */ if(u && (sizeof(u) > 3)) {
d307c72008-08-06Martin Stjernholm  if (mixed err = catch { initgroups(u[0], u[3]); })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
0f8b2f1999-03-27Henrik Grubbström (Grubba)  } setegid(saved_gid); seteuid(saved_uid);
0217b82003-03-03Henrik Grubbström (Grubba)  enable_coredumps(1);
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* HAVE_EFFECTIVE_USER */ }
dbfe9d2000-04-13Per Hedbor #else /* efun(seteuid) */ void create(string reason, int|string|void uid, int|string|void gid){}
0f8b2f1999-03-27Henrik Grubbström (Grubba) #endif /* efun(seteuid) */
c5e0961999-10-04Per Hedbor }
0f8b2f1999-03-27Henrik Grubbström (Grubba)  /* Used by read_config.pike, since there seems to be problems with * overloading otherwise. */
fc40392008-08-15Martin Stjernholm protected Privs PRIVS(string r, int|string|void u, int|string|void g)
0f8b2f1999-03-27Henrik Grubbström (Grubba) { return Privs(r, u, g); }
333b542005-02-25Henrik Grubbström (Grubba) // Current Configuration. Thread.Local current_configuration = Thread.Local();
8fb5172000-05-27Per Hedbor // font cache and loading. // // This will be changed to a list of server global modules, to make it // easier to implement new types of fonts (such as PPM color fonts, as // an example)
3e3bab2001-01-19Per Hedbor class Fonts { class Font { Image.Image write( string ... what ); array(int) text_extents( string ... what ); }; array available_font_versions(string name, int size); string describe_font_type(string n); Font get_font(string f, int size, int bold, int italic, string justification, float|int xspace, float|int yspace); Font resolve_font(string f, string|void justification);
8a12902001-11-14Henrik Grubbström (Grubba)  array(string) available_fonts(int(0..1)|void force_reload);
3e3bab2001-01-19Per Hedbor } Fonts fonts;
8fb5172000-05-27Per Hedbor 
d093992000-09-25Per Hedbor // Will replace Configuration after create() is run.
9a8a152000-09-25Per Hedbor program _configuration; /*set in create*/
b1fca01996-11-12Per Hedbor 
d093992000-09-25Per Hedbor array(Configuration) configurations = ({});
b1fca01996-11-12Per Hedbor 
9567c12001-02-23Martin Stjernholm private void stop_all_configurations() { configurations->unregister_urls(); #ifdef THREADS // Spend some time waiting out the handler threads before starting // to shut down the modules. hold_handler_threads(); release_handler_threads(3); #endif configurations->stop(1); }
1720541998-10-04Henrik Grubbström (Grubba) // Function that actually shuts down Roxen. (see low_shutdown).
1e5cc42001-03-17Martin Stjernholm private void really_low_shutdown(int exit_code)
a9d8111998-09-01Henrik Grubbström (Grubba) {
8fb5172000-05-27Per Hedbor  // Die nicely. Catch for paranoia reasons
3107101998-09-01Marcus Comstedt #ifdef THREADS
d307c72008-08-06Martin Stjernholm  if (mixed err = catch (stop_handler_threads())) werror (describe_backtrace (err));
3107101998-09-01Marcus Comstedt #endif /* THREADS */
b215032004-05-04Henrik Grubbström (Grubba)  if (!exit_code || once_mode) {
c7ca242004-03-09Henrik Grubbström (Grubba)  // We're shutting down; Attempt to take mysqld with us.
d307c72008-08-06Martin Stjernholm  if (mixed err = catch { report_notice("Shutting down MySQL.\n"); } || catch { Sql.Sql db = connect_to_my_mysql(0, "mysql"); db->shutdown(); })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
c7ca242004-03-09Henrik Grubbström (Grubba)  }
72ab7c2010-04-27Henrik Grubbström (Grubba)  // Zap some of the remaining caches. destruct (argcache);
532b902001-08-13Martin Stjernholm  destruct (cache);
8de8702004-08-23Martin Stjernholm #if 0 // Disabled since it's lying when the server is shut down with a // SIGTERM or SIGINT to the start script (which include the stop // action of the init.d script).
d307c72008-08-06Martin Stjernholm  if (mixed err = catch { if (exit_code && !once_mode) report_notice("Restarting Roxen.\n"); else report_notice("Shutting down Roxen.\n"); })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
8de8702004-08-23Martin Stjernholm #endif
4eeda62003-09-15Martin Stjernholm  roxenloader.real_exit( exit_code ); // Now we die...
a9d8111998-09-01Henrik Grubbström (Grubba) }
f931d72008-09-29Martin Stjernholm private int shutdown_recurse;
e83c432001-03-11Martin Nilsson 
a9d8111998-09-01Henrik Grubbström (Grubba) // Shutdown Roxen // exit_code = 0 True shutdown // exit_code = -1 Restart
bddc992015-12-03Henrik Grubbström (Grubba) private void low_shutdown(int exit_code, int|void apply_patches)
a9d8111998-09-01Henrik Grubbström (Grubba) {
f931d72008-09-29Martin Stjernholm  if(shutdown_recurse >= 4)
c5e0961999-10-04Per Hedbor  {
d307c72008-08-06Martin Stjernholm  if (mixed err = catch (report_notice("Exiting roxen (spurious signals received).\n")) || catch (stop_all_configurations()))
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
72ab7c2010-04-27Henrik Grubbström (Grubba)  // Zap some of the remaining caches. destruct(argcache);
532b902001-08-13Martin Stjernholm  destruct(cache);
bcf2c52015-12-01Henrik Grubbström (Grubba)  stop_hourly_maintenance();
e83c432001-03-11Martin Nilsson #ifdef THREADS
5622892013-09-20Henrik Grubbström (Grubba) #if constant(Filesystem.Monitor.basic) stop_fsgarb(); #endif
d307c72008-08-06Martin Stjernholm  if (mixed err = catch (stop_handler_threads()))
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
e83c432001-03-11Martin Nilsson #endif /* THREADS */
4eeda62003-09-15Martin Stjernholm  roxenloader.real_exit(exit_code);
e83c432001-03-11Martin Nilsson  }
f931d72008-09-29Martin Stjernholm  if (shutdown_recurse++) return;
dde9792008-09-30Martin Stjernholm #ifndef NO_SLOW_REQ_BT
f931d72008-09-29Martin Stjernholm  // Turn off the backend thread monitor while we're shutting down. slow_be_timeout_changed();
dde9792008-09-30Martin Stjernholm #endif
e83c432001-03-11Martin Nilsson 
bddc992015-12-03Henrik Grubbström (Grubba)  if ((apply_patches || query("patch_on_restart")) > 0) {
622a242015-12-03Henrik Grubbström (Grubba)  mixed err = catch { foreach(plib->file_list_imported(), mapping(string:mixed) item) { report_notice("Applying patch %s...\n", item->metadata->id); mixed err = catch { plib->install_patch(item->metadata->id, "Internal Administrator"); }; if (err) { report_error("Failed to install patch %s: %s\n", item->metadata->id, describe_backtrace(err)); } } }; if (err) { master()->handle_error(err); } }
d307c72008-08-06Martin Stjernholm  if (mixed err = catch(stop_all_configurations()))
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
79b7c32001-09-13Honza Petrous 
6897c92001-06-27Honza Petrous #ifdef SNMP_AGENT
79b7c32001-09-13Honza Petrous  if(objectp(snmpagent)) { snmpagent->stop_trap();
6897c92001-06-27Honza Petrous  snmpagent->disable();
79b7c32001-09-13Honza Petrous  }
6897c92001-06-27Honza Petrous #endif
8fb5172000-05-27Per Hedbor  call_out(really_low_shutdown, 0.1, exit_code);
b1fca01996-11-12Per Hedbor }
d474832008-10-26Martin Stjernholm private int shutdown_started;
a9d8111998-09-01Henrik Grubbström (Grubba) // Perhaps somewhat misnamed, really... This function will close all
c974c91999-06-27Per Hedbor // listen ports and then quit. The 'start' script should then start a // new copy of roxen automatically.
bddc992015-12-03Henrik Grubbström (Grubba) void restart(float|void i, void|int exit_code, void|int apply_patches)
a6e4a12000-07-09Per Hedbor //! Restart roxen, if the start script is running
81f8af1999-12-20Martin Nilsson {
d474832008-10-26Martin Stjernholm  shutdown_started = 1;
bddc992015-12-03Henrik Grubbström (Grubba)  call_out(low_shutdown, i, exit_code || -1, apply_patches);
81f8af1999-12-20Martin Nilsson }
8fb5172000-05-27Per Hedbor 
bddc992015-12-03Henrik Grubbström (Grubba) void shutdown(float|void i, void|int apply_patches)
a6e4a12000-07-09Per Hedbor //! Shut down roxen
81f8af1999-12-20Martin Nilsson {
d474832008-10-26Martin Stjernholm  shutdown_started = 1;
bddc992015-12-03Henrik Grubbström (Grubba)  call_out(low_shutdown, i, 0, apply_patches);
f4e1b71999-10-08Per Hedbor }
14179b1997-01-29Per Hedbor 
e83c432001-03-11Martin Nilsson void exit_when_done() {
d474832008-10-26Martin Stjernholm  shutdown_started = 1;
1d71a02009-02-11Jonas Wallden  report_notice("Interrupt request received.\n");
e83c432001-03-11Martin Nilsson  low_shutdown(-1); }
d474832008-10-26Martin Stjernholm int is_shutting_down() //! Returns true if Roxen is shutting down. { return shutdown_started; }
e83c432001-03-11Martin Nilsson 
4820e01999-07-27Henrik Grubbström (Grubba) /* * handle() stuff */
34fbbc1998-02-05Henrik Grubbström (Grubba) #ifdef THREADS
50b58b2001-01-31Per Hedbor // function handle = threaded_handle;
34fbbc1998-02-05Henrik Grubbström (Grubba) 
3e3bab2001-01-19Per Hedbor Thread do_thread_create(string id, function f, mixed ... args)
4f4bc11998-02-04Per Hedbor {
3e3bab2001-01-19Per Hedbor  Thread.Thread t = thread_create(f, @args);
6664122001-02-23Per Hedbor  name_thread( t, id );
4f4bc11998-02-04Per Hedbor  return t; }
fa9db42010-11-05Martin Stjernholm #if 1 constant Queue = Thread.Queue; #else
676f582000-03-24Per Hedbor // Shamelessly uses facts about pikes preemting algorithm. // Might have to be fixed in the future. class Queue
a6e4a12000-07-09Per Hedbor //! Thread.Queue lookalike, which uses some archaic and less //! known features of the preempting algorithm in pike to optimize the //! read function.
03aa462002-10-01Martin Stjernholm // // If those archaic and less known features are to depend on the // interpreter lock in the while loop that waits on the condition // variable then it doesn't work since Pike always might yield before // a function call (specifically, the wait() call in the condition // variable). Thus a handler thread might wait even though there is a // request to process. However, the only effect is that that specific // request isn't serviced timely; when the next request comes in the // thread will be woken up and both requests will be handled. // Furthermore it's extremely rare in the first place since there // normally are several handler threads.
676f582000-03-24Per Hedbor { inherit Thread.Condition : r_cond; array buffer=allocate(8); int r_ptr, w_ptr; int size() { return w_ptr - r_ptr; } mixed read() {
8140af2002-10-27Martin Stjernholm  while(!(w_ptr - r_ptr)) {
291cf02002-10-23Martin Stjernholm  // Make a MutexKey for wait() to please 7.3. This will of course // not fix the race, but we ignore that. See the discussion
8140af2002-10-27Martin Stjernholm  // above. (Must have an extra ref to the mutex since the // MutexKey doesn't keep one.) Thread.Mutex m = Thread.Mutex(); r_cond::wait (m->lock()); }
676f582000-03-24Per Hedbor  mixed tmp = buffer[r_ptr]; buffer[r_ptr++] = 0; // Throw away any references. return tmp; }
9567c12001-02-23Martin Stjernholm 
fe7cc92012-06-07Henrik Grubbström (Grubba)  mixed try_read()
9567c12001-02-23Martin Stjernholm  { if (!(w_ptr - r_ptr)) return ([])[0]; mixed tmp = buffer[r_ptr]; buffer[r_ptr++] = 0; // Throw away any references. return tmp; }
164a582003-04-14Martin Stjernholm  // Warning: This function isn't thread safe.
676f582000-03-24Per Hedbor  void write(mixed v) { if(w_ptr >= sizeof(buffer)) { buffer=buffer[r_ptr..]+allocate(8); w_ptr-=r_ptr; r_ptr=0; } buffer[w_ptr++]=v; r_cond::signal(); } }
fa9db42010-11-05Martin Stjernholm #endif
676f582000-03-24Per Hedbor 
b1b4e42008-09-22Martin Stjernholm #ifndef NO_SLOW_REQ_BT // This is a system to dump all threads whenever a request takes // longer than a configurable timeout. protected Pike.Backend slow_req_monitor; // Set iff slow req bt is enabled.
f931d72008-09-29Martin Stjernholm protected float slow_req_timeout, slow_be_timeout;
b1b4e42008-09-22Martin Stjernholm  protected void slow_req_monitor_thread (Pike.Backend my_monitor) { // my_monitor is just a safeguard to ensure we don't get multiple // monitor threads.
ec7a482012-07-13Jonas Wallden  name_thread(this_thread(), "Slow request monitor");
b1b4e42008-09-22Martin Stjernholm  while (slow_req_monitor == my_monitor)
ec7a482012-07-13Jonas Wallden  slow_req_monitor (3600.0); name_thread(this_thread(), 0);
b1b4e42008-09-22Martin Stjernholm }
f931d72008-09-29Martin Stjernholm protected mixed slow_be_call_out; protected void slow_be_before_cb() { #ifdef DEBUG if (this_thread() != backend_thread) error ("Run from wrong thread.\n"); #endif if (Pike.Backend monitor = slow_be_call_out && slow_req_monitor) { monitor->remove_call_out (slow_be_call_out); slow_be_call_out = 0; } } protected void slow_be_after_cb() {
34fe1e2011-08-22Martin Stjernholm  // FIXME: This should try to compensate for delays due to the pike // gc, because it just causes noise here.
f931d72008-09-29Martin Stjernholm #ifdef DEBUG if (this_thread() != backend_thread) error ("Run from wrong thread.\n"); #endif if (slow_be_timeout > 0.0) if (Pike.Backend monitor = slow_req_monitor) slow_be_call_out = monitor->call_out (dump_slow_req, slow_be_timeout, this_thread(), slow_be_timeout); }
0be3252008-09-25Martin Stjernholm void slow_req_count_changed()
b1b4e42008-09-22Martin Stjernholm { Pike.Backend monitor = slow_req_monitor;
0be3252008-09-25Martin Stjernholm  int count = query ("slow_req_bt_count");
b1b4e42008-09-22Martin Stjernholm 
0be3252008-09-25Martin Stjernholm  if (count && monitor) { // Just a change of the count - nothing to do.
b1b4e42008-09-22Martin Stjernholm  }
0be3252008-09-25Martin Stjernholm  else if (count) { // Start.
b1b4e42008-09-22Martin Stjernholm  monitor = slow_req_monitor = Pike.SmallBackend(); Thread.thread_create (slow_req_monitor_thread, monitor); monitor->call_out (lambda () {}, 0); // Safeguard if there's a race.
f931d72008-09-29Martin Stjernholm  slow_be_timeout_changed();
b1b4e42008-09-22Martin Stjernholm  } else if (monitor) { // Stop. slow_req_monitor = 0; monitor->call_out (lambda () {}, 0); // To wake up the thread.
f931d72008-09-29Martin Stjernholm  slow_be_timeout_changed();
b1b4e42008-09-22Martin Stjernholm  } }
0be3252008-09-25Martin Stjernholm void slow_req_timeout_changed() { #ifdef DEBUG if (query ("slow_req_bt_timeout") < 0) error ("Invalid timeout.\n"); #endif slow_req_timeout = query ("slow_req_bt_timeout"); }
f931d72008-09-29Martin Stjernholm void slow_be_timeout_changed() { #ifdef DEBUG if (query ("slow_be_bt_timeout") < 0) error ("Invalid timeout.\n"); #endif slow_be_timeout = query ("slow_be_bt_timeout"); #ifdef DEBUG if ((Pike.DefaultBackend->before_callback && Pike.DefaultBackend->before_callback != slow_be_before_cb) || (Pike.DefaultBackend->after_callback && Pike.DefaultBackend->after_callback != slow_be_after_cb)) werror ("Pike.DefaultBackend already hooked up with " "other before/after callbacks - they get overwritten: %O/%O\n", Pike.DefaultBackend->before_callback, Pike.DefaultBackend->after_callback); #endif if (query ("slow_req_bt_count") && slow_be_timeout > 0.0 && // Don't trig if we're shutting down.
d474832008-10-26Martin Stjernholm  !shutdown_started) {
f931d72008-09-29Martin Stjernholm  Pike.DefaultBackend->before_callback = slow_be_before_cb; Pike.DefaultBackend->after_callback = slow_be_after_cb; } else { Pike.DefaultBackend->before_callback = 0; Pike.DefaultBackend->after_callback = 0; if (Pike.Backend monitor = slow_be_call_out && slow_req_monitor) { monitor->remove_call_out (slow_be_call_out); slow_be_call_out = 0; } } }
8243922008-09-22Martin Stjernholm protected void dump_slow_req (Thread.Thread thread, float timeout)
b1b4e42008-09-22Martin Stjernholm {
f931d72008-09-29Martin Stjernholm  object threads_disabled = _disable_threads();
0be3252008-09-25Martin Stjernholm  int count = query ("slow_req_bt_count"); if (count > 0) set ("slow_req_bt_count", count - 1);
f931d72008-09-29Martin Stjernholm  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", thread == backend_thread ? "Backend thread" : "Thread", thread->id_number(), timeout);
5b5c802008-09-29Martin Stjernholm  describe_all_threads (0, threads_disabled);
f931d72008-09-29Martin Stjernholm  } threads_disabled = 0; // Paranoia.
b1b4e42008-09-22Martin Stjernholm } #endif // !NO_SLOW_REQ_BT
4c3c532001-08-09Per Hedbor // // This is easier than when there are no threads. // // See the discussion below. :-) // // // But there is extra functionality below we really want, though, // // so let's use that one instead... // function async_sig_start( function f, int really ) // { // return lambda( mixed ... args ) { // thread_create( f, @args ); // }; // }
fc40392008-08-15Martin Stjernholm local protected Queue handle_queue = Queue();
a6e4a12000-07-09Per Hedbor //! Queue of things to handle. //! An entry consists of an array(function fp, array args)
1720541998-10-04Henrik Grubbström (Grubba) 
fc40392008-08-15Martin Stjernholm local protected int thread_reap_cnt;
9567c12001-02-23Martin Stjernholm //! Number of handler threads in the process of being stopped.
fc40392008-08-15Martin Stjernholm protected int threads_on_hold;
9567c12001-02-23Martin Stjernholm //! Number of handler threads on hold.
14179b1997-01-29Per Hedbor 
a0c8a32010-05-06Fredrik Noring // Global variables for statistics int handler_num_runs = 0; int handler_num_runs_001s = 0; int handler_num_runs_005s = 0; int handler_num_runs_015s = 0; int handler_num_runs_05s = 0; int handler_num_runs_1s = 0; int handler_num_runs_5s = 0; int handler_num_runs_15s = 0; int handler_acc_time = 0; int handler_acc_cpu_time = 0;
63f41c2010-11-10Martin Stjernholm protected string debug_format_queue_task (array(function|array) task) // Debug formatter of an entry in the handler or background_run queues. { return ((functionp (task[0]) ? sprintf ("%s: %s", Function.defined (task[0]), master()->describe_function (task[0])) : programp (task[0]) ? sprintf ("%s: %s", Program.defined (task[0]), master()->describe_program (task[0])) : sprintf ("%O", task[0])) + "(" + map (task[1], lambda (mixed arg)
d4adb92012-07-12Martin Stjernholm  {return RXML.utils.format_short (arg, 200);}) * ", " +
63f41c2010-11-10Martin Stjernholm  ")"); }
3457772012-01-26Martin Stjernholm protected mapping(Thread.Thread:int) thread_task_start_times = ([]);
4e167a2012-02-10Jonas Wallden mapping(Thread.Thread:int) get_thread_task_start_times() { // Also needed in Admin interface's thread wizard return thread_task_start_times + ([ ]); }
fc40392008-08-15Martin Stjernholm local protected void handler_thread(int id)
a6e4a12000-07-09Per Hedbor //! The actual handling function. This functions read function and //! parameters from the queue, calls it, then reads another one. There //! is a lot of error handling to ensure that nothing serious happens if //! the handler function throws an error.
14179b1997-01-29Per Hedbor {
9567c12001-02-23Martin Stjernholm  THREAD_WERR("Handle thread ["+id+"] started"); mixed h, q;
aa78722001-08-24Martin Stjernholm  set_u_and_gid (1); #ifdef TEST_EUID_CHANGE if (test_euid_change) { Stdio.File f = Stdio.File(); if (f->open ("rootonly", "r") && f->read()) werror ("Handler thread %d can read rootonly\n", id); else werror ("Handler thread %d can't read rootonly\n", id); } #endif
9567c12001-02-23Martin Stjernholm  while(1)
4f4bc11998-02-04Per Hedbor  {
a11c322004-05-07Martin Stjernholm  int thread_flagged_as_busy;
b1b4e42008-09-22Martin Stjernholm #ifndef NO_SLOW_REQ_BT
f931d72008-09-29Martin Stjernholm  Pike.Backend monitor; mixed call_out;
b1b4e42008-09-22Martin Stjernholm #endif
4f4bc11998-02-04Per Hedbor  if(q=catch {
45cae31998-03-06Henrik Grubbström (Grubba)  do {
4eb3192001-09-02Martin Stjernholm // if (!busy_threads) werror ("GC: %d\n", gc());
1bcd222007-09-06Henrik Grubbström (Grubba)  cache_clear_deltas();
bfb4d41999-12-28Martin Nilsson  THREAD_WERR("Handle thread ["+id+"] waiting for next event");
9567c12001-02-23Martin Stjernholm  if(arrayp(h=handle_queue->read()) && h[0]) {
63f41c2010-11-10Martin Stjernholm  THREAD_WERR(sprintf("Handle thread [%O] calling %s", id, debug_format_queue_task (h)));
67f60e2000-07-09Martin Nilsson  set_locale();
76ae182001-02-23Martin Stjernholm  busy_threads++;
a11c322004-05-07Martin Stjernholm  thread_flagged_as_busy = 1;
a0c8a32010-05-06Fredrik Noring  handler_num_runs++;
b1b4e42008-09-22Martin Stjernholm 
a0c8a32010-05-06Fredrik Noring  int start_hrtime = gethrtime();
3457772012-01-26Martin Stjernholm  thread_task_start_times[this_thread()] = start_hrtime;
a0c8a32010-05-06Fredrik Noring  float handler_vtime = gauge {
b1b4e42008-09-22Martin Stjernholm #ifndef NO_SLOW_REQ_BT
a0c8a32010-05-06Fredrik Noring  if (h[0] != bg_process_queue && // Leave out bg_process_queue. It makes a timeout on // every individual job instead. (monitor = slow_req_monitor) && slow_req_timeout > 0.0) { call_out = monitor->call_out (dump_slow_req, slow_req_timeout, this_thread(), slow_req_timeout); h[0](@h[1]); monitor->remove_call_out (call_out); } else
b1b4e42008-09-22Martin Stjernholm #endif
a0c8a32010-05-06Fredrik Noring  { h[0](@h[1]); } }; float handler_rtime = (gethrtime() - start_hrtime)/1E6;
3457772012-01-26Martin Stjernholm  thread_task_start_times[this_thread()] = 0;
b1b4e42008-09-22Martin Stjernholm 
45cae31998-03-06Henrik Grubbström (Grubba)  h=0;
76ae182001-02-23Martin Stjernholm  busy_threads--;
a11c322004-05-07Martin Stjernholm  thread_flagged_as_busy = 0;
a0c8a32010-05-06Fredrik Noring  if (handler_rtime > 0.01) handler_num_runs_001s++; if (handler_rtime > 0.05) handler_num_runs_005s++; if (handler_rtime > 0.15) handler_num_runs_015s++; if (handler_rtime > 0.50) handler_num_runs_05s++; if (handler_rtime > 1.00) handler_num_runs_1s++; if (handler_rtime > 5.00) handler_num_runs_5s++; if (handler_rtime > 15.00) handler_num_runs_15s++; handler_acc_cpu_time += (int)(1E6*handler_vtime); handler_acc_time += (int)(1E6*handler_rtime);
3107101998-09-01Marcus Comstedt  } else if(!h) { // Roxen is shutting down.
3bc3a72001-02-22Martin Stjernholm  report_debug("Handle thread ["+id+"] stopped.\n");
3107101998-09-01Marcus Comstedt  thread_reap_cnt--;
23007b2000-05-28Martin Nilsson #ifdef NSERIOUS
10089c2000-05-17Martin Nilsson  if(!thread_reap_cnt) report_debug("+++ATH\n");
23007b2000-05-28Martin Nilsson #endif
3107101998-09-01Marcus Comstedt  return;
45cae31998-03-06Henrik Grubbström (Grubba)  }
9567c12001-02-23Martin Stjernholm #ifdef DEBUG else if (h != 1) error ("Unknown message in handle_queue: %O\n", h); #endif else { num_hold_messages--; THREAD_WERR("Handle thread [" + id + "] put on hold"); threads_on_hold++;
8140af2002-10-27Martin Stjernholm  if (Thread.Condition cond = hold_wakeup_cond) {
291cf02002-10-23Martin Stjernholm  // Make a MutexKey for wait() to please 7.3. This will of // course not fix the race, but we ignore that. See the
8140af2002-10-27Martin Stjernholm  // comment at the declaration of hold_wakeup_cond. (Must // have an extra ref to the mutex since the MutexKey // doesn't keep one.) Thread.Mutex m = Thread.Mutex(); cond->wait (m->lock()); }
9567c12001-02-23Martin Stjernholm  threads_on_hold--; THREAD_WERR("Handle thread [" + id + "] released"); }
45cae31998-03-06Henrik Grubbström (Grubba)  } while(1); }) {
a11c322004-05-07Martin Stjernholm  if (thread_flagged_as_busy) busy_threads--;
b1b4e42008-09-22Martin Stjernholm #ifndef NO_SLOW_REQ_BT
f931d72008-09-29Martin Stjernholm  if (call_out) monitor->remove_call_out (call_out);
b1b4e42008-09-22Martin Stjernholm #endif
774c9e2000-01-13Henrik Grubbström (Grubba)  if (h = catch {
44818f2012-02-18Martin Stjernholm  master()->handle_error (q);
774c9e2000-01-13Henrik Grubbström (Grubba)  if (q = catch {h = 0;}) {
52faaf2001-03-28Martin Stjernholm  report_error(LOC_M(5, "Uncaught error in handler thread: %sClient "
c8ee712000-09-09Andreas Lange  "will not get any response from Roxen.")+"\n",
67f60e2000-07-09Martin Nilsson  describe_backtrace(q));
8d31322001-06-06Martin Stjernholm  catch (q = 0);
774c9e2000-01-13Henrik Grubbström (Grubba)  } }) { catch { report_error("Error reporting error:\n"); report_error(sprintf("Raw error: %O\n", h[0])); report_error(sprintf("Original raw error: %O\n", q[0])); };
8d31322001-06-06Martin Stjernholm  catch (q = 0); catch (h = 0);
45cae31998-03-06Henrik Grubbström (Grubba)  } }
4f4bc11998-02-04Per Hedbor  }
b1fca01996-11-12Per Hedbor }
e824002009-06-24Martin Stjernholm void handle(function f, mixed ... args)
3aaaa71997-06-12Wilhelm Köhler { handle_queue->write(({f, args })); }
e824002009-06-24Martin Stjernholm int handle_queue_length() { return handle_queue->size(); }
14179b1997-01-29Per Hedbor int number_of_threads;
a6e4a12000-07-09Per Hedbor //! The number of handler threads to run.
76ae182001-02-23Martin Stjernholm  int busy_threads; //! The number of currently busy threads.
fc40392008-08-15Martin Stjernholm protected array(object) handler_threads = ({});
a6e4a12000-07-09Per Hedbor //! The handler threads, the list is kept for debug reasons.
8fb5172000-05-27Per Hedbor 
14179b1997-01-29Per Hedbor void start_handler_threads() {
8552d92001-01-13Martin Nilsson  if (query("numthreads") <= 1) {
7b798d2000-07-04Per Hedbor  set( "numthreads", 1 );
76ae182001-02-23Martin Stjernholm  report_warning (LOC_S(1, "Starting one thread to handle requests.")+"\n");
23414a2000-07-21Andreas Lange  } else {
c8ee712000-09-09Andreas Lange  report_notice (LOC_S(2, "Starting %d threads to handle requests.")+"\n",
8552d92001-01-13Martin Nilsson  query("numthreads") );
a60c4c1997-07-03Henrik Grubbström (Grubba)  }
f526692000-05-16Henrik Grubbström (Grubba)  array(object) new_threads = ({});
8552d92001-01-13Martin Nilsson  for(; number_of_threads < query("numthreads"); number_of_threads++)
f526692000-05-16Henrik Grubbström (Grubba)  new_threads += ({ do_thread_create( "Handle thread [" + number_of_threads + "]", handler_thread, number_of_threads ) }); handler_threads += new_threads;
14179b1997-01-29Per Hedbor }
06583f1997-09-03Per Hedbor 
fc40392008-08-15Martin Stjernholm protected int num_hold_messages; protected Thread.Condition hold_wakeup_cond = Thread.Condition();
03aa462002-10-01Martin Stjernholm // Note: There are races in the use of this condition variable, but // the only effect of that is that some handler thread might be // considered hung when it's actually waiting on hold_wakeup_cond, and // the hold/release handler threads function deal with hung threads // anyway. The outcome would only be that release_handler_threads // starts some extra handler thread unnecessarily.
9567c12001-02-23Martin Stjernholm  void hold_handler_threads() //! Tries to put all handler threads on hold, but gives up if it takes //! too long. { if (!hold_wakeup_cond) { THREAD_WERR("Ignoring request to hold handler threads during stop"); return; } int timeout=10; #if constant(_reset_dmalloc) // DMALLOC slows stuff down a bit... timeout *= 10; #endif /* constant(_reset_dmalloc) */ THREAD_WERR("Putting " + (number_of_threads - threads_on_hold) + " handler threads on hold, " + threads_on_hold + " on hold already"); for (int i = number_of_threads - threads_on_hold - num_hold_messages; i-- > 0;) { handle_queue->write (1); num_hold_messages++; } while (threads_on_hold < number_of_threads && timeout--) sleep (0.1); THREAD_WERR(threads_on_hold + " handler threads on hold, " + (number_of_threads - threads_on_hold) + " not responding"); } void release_handler_threads (int numthreads) //! Releases any handler threads put on hold. If necessary new threads //! are started to ensure that at least @[numthreads] threads are
1d32602001-08-13Martin Stjernholm //! responding. Threads that haven't arrived to the hold state since //! @[hold_handler_threads] are considered nonresponding.
9567c12001-02-23Martin Stjernholm { if (Thread.Condition cond = hold_wakeup_cond) { // Flush out any remaining hold messages from the queue. for (int i = handle_queue->size(); i && num_hold_messages; i--) {
fe7cc92012-06-07Henrik Grubbström (Grubba)  mixed task = handle_queue->try_read();
9567c12001-02-23Martin Stjernholm  if (task == 1) num_hold_messages--; else handle_queue->write (task); } #ifdef DEBUG if (num_hold_messages) error ("num_hold_messages is bogus (%d).\n", num_hold_messages); #endif num_hold_messages = 0; int blocked_threads = number_of_threads - threads_on_hold; int threads_to_create = numthreads - threads_on_hold;
1d32602001-08-13Martin Stjernholm  THREAD_WERR("Releasing " + threads_on_hold + " threads on hold");
9567c12001-02-23Martin Stjernholm  cond->broadcast(); if (threads_to_create > 0) { array(object) new_threads = ({});
1d32602001-08-13Martin Stjernholm  for (int n = 0; n < threads_to_create; number_of_threads++, n++)
9567c12001-02-23Martin Stjernholm  new_threads += ({ do_thread_create( "Handle thread [" + number_of_threads + "]", handler_thread, number_of_threads ) }); handler_threads += new_threads; report_notice ("Created %d new handler threads to compensate " "for %d blocked ones.\n", threads_to_create, blocked_threads); } } else { THREAD_WERR("Ignoring request to release handler threads during stop"); return; } }
8b51062012-02-14Martin Stjernholm //! int handler_threads_on_hold() {return !!hold_wakeup_cond;}
fc40392008-08-15Martin Stjernholm protected Thread.MutexKey backend_block_lock;
3bc3a72001-02-22Martin Stjernholm 
3107101998-09-01Marcus Comstedt void stop_handler_threads()
3bc3a72001-02-22Martin Stjernholm //! Stop all the handler threads and the backend, but give up if it //! takes too long.
3107101998-09-01Marcus Comstedt {
7183912011-10-17Martin Stjernholm  int timeout=15; // Timeout if the bg queue doesn't get shorter. int background_run_timeout = 100; // Hard timeout that cuts off the bg queue.
f526692000-05-16Henrik Grubbström (Grubba) #if constant(_reset_dmalloc) // DMALLOC slows stuff down a bit... timeout *= 10;
7183912011-10-17Martin Stjernholm  background_run_timeout *= 3;
f526692000-05-16Henrik Grubbström (Grubba) #endif /* constant(_reset_dmalloc) */
7183912011-10-17Martin Stjernholm 
81f8af1999-12-20Martin Nilsson  report_debug("Stopping all request handler threads.\n");
9567c12001-02-23Martin Stjernholm  // Wake up any handler threads on hold, and ensure none gets on hold // after this. if (Thread.Condition cond = hold_wakeup_cond) { hold_wakeup_cond = 0; cond->broadcast(); }
3107101998-09-01Marcus Comstedt  while(number_of_threads>0) { number_of_threads--; handle_queue->write(0); thread_reap_cnt++; }
f526692000-05-16Henrik Grubbström (Grubba)  handler_threads = ({});
3bc3a72001-02-22Martin Stjernholm 
9567c12001-02-23Martin Stjernholm  if (this_thread() != backend_thread && !backend_block_lock) {
3bc3a72001-02-22Martin Stjernholm  thread_reap_cnt++; Thread.Mutex mutex = Thread.Mutex(); backend_block_lock = mutex->lock(); call_out (lambda () { thread_reap_cnt--; report_debug("Backend thread stopped.\n"); mutex->lock(); error("Backend stop failed.\n"); }, 0); }
7183912011-10-17Martin Stjernholm  int prev_bg_len = bg_queue_length(); while (thread_reap_cnt) {
f526692000-05-16Henrik Grubbström (Grubba)  sleep(0.1);
7183912011-10-17Martin Stjernholm  if (--timeout <= 0) { int cur_bg_len = bg_queue_length(); if (prev_bg_len < cur_bg_len) // Allow more time if the background queue is being worked off. timeout = 10; prev_bg_len = cur_bg_len; } if(--background_run_timeout <= 0 || timeout <= 0) {
9567c12001-02-23Martin Stjernholm  report_debug("Giving up waiting on threads; "
7183912011-10-17Martin Stjernholm  "%d threads blocked, %d jobs in the background queue.\n", thread_reap_cnt, bg_queue_length());
9567c12001-02-23Martin Stjernholm #ifdef DEBUG describe_all_threads(); #endif
3107101998-09-01Marcus Comstedt  return; } } }
50b58b2001-01-31Per Hedbor  #else // handle function used when THREADS is not enabled.
68d2562001-01-31Per Hedbor  void handle(function f, mixed ... args)
50b58b2001-01-31Per Hedbor { f(@args); } // function handle = unthreaded_handle;
4c3c532001-08-09Per Hedbor #endif /* THREADS */
50b58b2001-01-31Per Hedbor function async_sig_start( function f, int really ) { class SignalAsyncVerifier( function f ) {
fc40392008-08-15Martin Stjernholm  protected int async_called;
50b58b2001-01-31Per Hedbor  void really_call( array args ) { async_called = 0; f( @args ); } void call( mixed ... args ) {
6146222001-06-24Per Hedbor  if( async_called && async_called-time() )
50b58b2001-01-31Per Hedbor  {
40acb32002-04-15Martin Stjernholm  report_debug("Received signal %s\n", (string) signame( args[0] ) );
50b58b2001-01-31Per Hedbor  report_debug("\n\n" "Async calling failed for %O, calling synchronous\n", f); report_debug("Backtrace at time of hangup:\n%s\n", describe_backtrace( backtrace() )); f( @args ); return; }
20bd1e2001-06-24Per Hedbor  if( !async_called ) // Do not queue more than one call at a time. {
40acb32002-04-15Martin Stjernholm  report_debug("Received signal %s\n", (string) signame( args[0] ) );
20bd1e2001-06-24Per Hedbor  async_called=time(); call_out( really_call, 0, args ); }
50b58b2001-01-31Per Hedbor  } }; // call_out is not really useful here, since we probably want to run // the signal handler immediately, not whenever the backend thread // is available. /per // // Calling directly like this may however lead to recursive mutex // lock errors. The problem cannot be solved using lock(2) since the // internal structures may be in an inconsistent state from the // previous call, and waiting for the lock probably leads to a // deadlock. /noring // // But on the other hand, you are not very likely to have any mutex // locks in an unthreaded pike, since it's quite impossible. /per // // But still, the problems with inconsistent internal states are // there. The API:s for many (thread safe) objects are designed to // only allow one (1) caller at any given time. It's a bug if this // restriction can be circumvented using signals. I suggest that // Thread.Mutex takes care of this problem in non-threaded mode. // / noring // // Apparantly it already did that. :-) // // I also fixed SIGHUP to be somewhat more asynchronous. // // I also added a rather small amount of magic so that it is called // asynchronously the first time it is received, but repeated // signals are not called asynchronously unless the first signal // handler was actually called. // // Except for the SIGQUIT signal, which dumps a backtrace. It would // be an excercise in futility to call that one asynchronously. // // I hope that this will solve all your problems. /per if( really > 0 ) return lambda( mixed ... args ){ call_out( f, 0, @args ); }; if( really < 0 ) return f; return SignalAsyncVerifier( f )->call; }
81f8af1999-12-20Martin Nilsson 
76ae182001-02-23Martin Stjernholm #ifdef THREADS
fc40392008-08-15Martin Stjernholm protected Thread.Queue bg_queue = Thread.Queue();
34fe1e2011-08-22Martin Stjernholm protected Thread.Thread bg_process_thread;
76ae182001-02-23Martin Stjernholm 
8f63822001-02-27Martin Stjernholm // Use a time buffer to strike a balance if the server is busy and // always have at least one busy thread: The maximum waiting time in // that case is somewhere between bg_time_buffer_min and // bg_time_buffer_max. If there are only short periods of time between // the queue runs, the max waiting time will shrink towards the // minimum.
fc40392008-08-15Martin Stjernholm protected constant bg_time_buffer_max = 30; protected constant bg_time_buffer_min = 0; protected int bg_last_busy = 0;
a0c8a32010-05-06Fredrik Noring int bg_num_runs = 0; int bg_num_runs_001s = 0; int bg_num_runs_005s = 0; int bg_num_runs_015s = 0; int bg_num_runs_05s = 0; int bg_num_runs_1s = 0; int bg_num_runs_5s = 0; int bg_num_runs_15s = 0; int bg_acc_time = 0; int bg_acc_cpu_time = 0; int bg_queue_length() { return bg_queue->size(); }
8f63822001-02-27Martin Stjernholm 
fc40392008-08-15Martin Stjernholm protected void bg_process_queue()
76ae182001-02-23Martin Stjernholm {
34fe1e2011-08-22Martin Stjernholm  if (bg_process_thread) return;
76ae182001-02-23Martin Stjernholm  // Relying on the interpreter lock here.
34fe1e2011-08-22Martin Stjernholm  bg_process_thread = this_thread();
8f63822001-02-27Martin Stjernholm  int maxbeats =
186b722010-11-10Martin Stjernholm  min (time() - bg_last_busy, bg_time_buffer_max) * (int) (1 / 0.01);
8f63822001-02-27Martin Stjernholm 
b1b4e42008-09-22Martin Stjernholm #ifndef NO_SLOW_REQ_BT
f931d72008-09-29Martin Stjernholm  Pike.Backend monitor; mixed call_out;
b1b4e42008-09-22Martin Stjernholm #endif
76ae182001-02-23Martin Stjernholm  if (mixed err = catch {
4e91e32010-11-02Martin Jonsson  // Make sure we don't run forever if background jobs are queued // over and over again, to avoid starving other threads. (If they // are starved enough, busy_threads will never be incremented.) // If jobs are enqueued while running another background job, // bg_process_queue is put on the handler queue again at the very // end of this function.
7183912011-10-17Martin Stjernholm  // // However, during shutdown we continue until the queue is really // empty. background_run won't queue new jobs then, and if this // takes too long, stop_handler_threads will exit anyway.
4e91e32010-11-02Martin Jonsson  int jobs_to_process = bg_queue->size();
7183912011-10-17Martin Stjernholm  while (hold_wakeup_cond ? jobs_to_process-- : bg_queue->size()) {
164a582003-04-14Martin Stjernholm  // Not a race here since only one thread is reading the queue. array task = bg_queue->read();
8f63822001-02-27Martin Stjernholm  // Wait a while if another thread is busy already. if (busy_threads > 1) {
31745a2010-11-10Martin Stjernholm  for (maxbeats = max (maxbeats, (int) (bg_time_buffer_min / 0.01));
8f63822001-02-27Martin Stjernholm  busy_threads > 1 && maxbeats > 0; maxbeats--)
31745a2010-11-10Martin Stjernholm  sleep (0.01);
8f63822001-02-27Martin Stjernholm  bg_last_busy = time(); }
54671c2001-11-12Martin Stjernholm #ifdef DEBUG_BACKGROUND_RUN
63f41c2010-11-10Martin Stjernholm  report_debug ("background_run run %s [%d jobs left in queue]\n", debug_format_queue_task (task),
a99a332004-10-11Marcus Wellhardh  bg_queue->size()); #endif
b1b4e42008-09-22Martin Stjernholm  float task_vtime, task_rtime;
a0c8a32010-05-06Fredrik Noring  bg_num_runs++;
b1b4e42008-09-22Martin Stjernholm  #ifndef NO_SLOW_REQ_BT
f931d72008-09-29Martin Stjernholm  if ((monitor = slow_req_monitor) && slow_req_timeout > 0.0) { call_out = monitor->call_out (dump_slow_req, slow_req_timeout, this_thread(), slow_req_timeout);
a9a2822012-02-14Jonas Wallden  int start_hrtime = gethrtime();
3457772012-01-26Martin Stjernholm  thread_task_start_times[this_thread()] = start_hrtime;
b1b4e42008-09-22Martin Stjernholm  task_vtime = gauge { if (task[0]) // Ignore things that have become destructed. // Note: BackgroundProcess.repeat assumes that there are // exactly two refs to task[0] during the call below. task[0] (@task[1]); };
a9a2822012-02-14Jonas Wallden  task_rtime = (gethrtime() - start_hrtime) / 1e6;
3457772012-01-26Martin Stjernholm  thread_task_start_times[this_thread()] = 0;
f931d72008-09-29Martin Stjernholm  monitor->remove_call_out (call_out);
b1b4e42008-09-22Martin Stjernholm  }
4905ea2008-09-30Martin Stjernholm  else
b1b4e42008-09-22Martin Stjernholm #endif
4905ea2008-09-30Martin Stjernholm  {
a9a2822012-02-14Jonas Wallden  int start_hrtime = gethrtime();
3457772012-01-26Martin Stjernholm  thread_task_start_times[this_thread()] = start_hrtime;
4905ea2008-09-30Martin Stjernholm  task_vtime = gauge { if (task[0]) task[0] (@task[1]); };
a9a2822012-02-14Jonas Wallden  task_rtime = (gethrtime() - start_hrtime) / 1e6;
3457772012-01-26Martin Stjernholm  thread_task_start_times[this_thread()] = 0;
4905ea2008-09-30Martin Stjernholm  }
f154ad2008-05-13Martin Stjernholm 
a0c8a32010-05-06Fredrik Noring  if (task_rtime > 0.01) bg_num_runs_001s++; if (task_rtime > 0.05) bg_num_runs_005s++; if (task_rtime > 0.15) bg_num_runs_015s++; if (task_rtime > 0.50) bg_num_runs_05s++; if (task_rtime > 1.00) bg_num_runs_1s++; if (task_rtime > 5.00) bg_num_runs_5s++; if (task_rtime > 15.00) bg_num_runs_15s++; bg_acc_cpu_time += (int)(1E6*task_vtime); bg_acc_time += (int)(1E6*task_rtime);
f154ad2008-05-13Martin Stjernholm  if (task_rtime > 60.0) report_warning ("Warning: Background job took more than one minute " "(%g s real time and %g s cpu time):\n" " %s (%s)\n%s", task_rtime, task_vtime, functionp (task[0]) ? sprintf ("%s: %s", Function.defined (task[0]), master()->describe_function (task[0])) : programp (task[0]) ? sprintf ("%s: %s", Program.defined (task[0]), master()->describe_program (task[0])) : sprintf ("%O", task[0]), map (task[1], lambda (mixed arg) {return sprintf ("%O", arg);}) * ", ", bg_queue->size() ? (bg_queue->size() > 1 ? " " + bg_queue->size() + " more jobs in the " "background queue were delayed.\n" : " 1 more job in the background queue was delayed.\n"): ""); #ifdef DEBUG_BACKGROUND_RUN else report_debug ("background_run done, " "took %g ms cpu time and %g ms real time\n", task_vtime * 1000, task_rtime * 1000);
76ae182001-02-23Martin Stjernholm #endif
8f63822001-02-27Martin Stjernholm  if (busy_threads > 1) bg_last_busy = time();
76ae182001-02-23Martin Stjernholm  } }) {
b1b4e42008-09-22Martin Stjernholm #ifndef NO_SLOW_REQ_BT
f931d72008-09-29Martin Stjernholm  if (call_out) monitor->remove_call_out (call_out);
b1b4e42008-09-22Martin Stjernholm #endif
34fe1e2011-08-22Martin Stjernholm  bg_process_thread = 0;
76ae182001-02-23Martin Stjernholm  handle (bg_process_queue); throw (err); }
34fe1e2011-08-22Martin Stjernholm  bg_process_thread = 0;
31745a2010-11-10Martin Stjernholm  if (bg_queue->size()) { handle (bg_process_queue); // Sleep a short while to encourage a thread switch. This is a // kludge to avoid starving non-handler threads, since pike // currently (7.8.503) doesn't switch threads reliably on yields. sleep (0.001); }
76ae182001-02-23Martin Stjernholm } #endif
34fe1e2011-08-22Martin Stjernholm Thread.Thread background_run_thread() //! Returns the thread currently executing the background_run queue, //! or 0 if it isn't being processed. { #ifdef THREADS return bg_process_thread; #else // FIXME: This is not correct. Should return something nonzero when // called from the call out. But noone is running without threads // nowadays anyway. return 0; #endif }
63a8572006-11-14Martin Stjernholm mixed background_run (int|float delay, function func, mixed... args)
76ae182001-02-23Martin Stjernholm //! Enqueue a task to run in the background in a way that makes as //! little impact as possible on the incoming requests. No matter how //! many tasks are queued to run in the background, only one is run at
76e01e2008-09-11Martin Stjernholm //! a time. The tasks won't be starved regardless of server load, //! though.
76ae182001-02-23Martin Stjernholm //! //! The function @[func] will be enqueued after approximately @[delay] //! seconds, to be called with the rest of the arguments as its //! arguments. //!
76e01e2008-09-11Martin Stjernholm //! In a multithreaded server the function will be executed in one of //! the handler threads. The function is executed in the backend //! thread if no thread support is available, although that //! practically never occurs anymore. //! //! To avoid starving other background jobs, the function should never //! run for a long time. Instead do another call to @[background_run] //! to queue it up again after some work has been done, or use //! @[BackgroundProcess].
63a8572006-11-14Martin Stjernholm //! //! @returns //! If the function is queued for execution right away then zero is //! returned. Otherwise its call out identifier is returned, which can //! be used with @[find_call_out] or @[remove_call_out].
76ae182001-02-23Martin Stjernholm {
24f9642007-05-03Martin Stjernholm  // FIXME: Make it possible to associate the background job with a // RoxenModule or Configuration, so that report_error etc can log in // a good place.
164a582003-04-14Martin Stjernholm #ifdef DEBUG_BACKGROUND_RUN
a99a332004-10-11Marcus Wellhardh  report_debug ("background_run enqueue %s (%s) [%d jobs in queue]\n",
164a582003-04-14Martin Stjernholm  functionp (func) ? sprintf ("%s: %s", Function.defined (func), master()->describe_function (func)) : programp (func) ? sprintf ("%s: %s", Program.defined (func), master()->describe_program (func)) : sprintf ("%O", func), map (args, lambda (mixed arg)
a99a332004-10-11Marcus Wellhardh  {return sprintf ("%O", arg);}) * ", ", bg_queue->size());
164a582003-04-14Martin Stjernholm #endif
76ae182001-02-23Martin Stjernholm #ifdef THREADS
7183912011-10-17Martin Stjernholm  if (!hold_wakeup_cond) {
76ae182001-02-23Martin Stjernholm  // stop_handler_threads is running; ignore more work.
7183912011-10-17Martin Stjernholm #ifdef DEBUG report_debug ("Ignoring background job queued during shutdown: %O\n", func); #endif
63a8572006-11-14Martin Stjernholm  return 0;
7183912011-10-17Martin Stjernholm  }
76ae182001-02-23Martin Stjernholm 
f6ef152010-06-03Fredrik Noring  class enqueue(function func, mixed ... args)
43642b2009-06-29Martin Stjernholm  {
f6ef152010-06-03Fredrik Noring  int __hash() { return hash_value(func); } int `==(mixed gunc) { return func == gunc; } string _sprintf() { return sprintf("background_run(%O)", func); } mixed `()() { bg_queue->write (({func, args}));
34fe1e2011-08-22Martin Stjernholm  if (!bg_process_thread)
f6ef152010-06-03Fredrik Noring  handle (bg_process_queue); }
43642b2009-06-29Martin Stjernholm  }; if (delay)
f6ef152010-06-03Fredrik Noring  return call_out (enqueue(func, @args), delay);
43642b2009-06-29Martin Stjernholm  else {
f6ef152010-06-03Fredrik Noring  enqueue(func, @args)();
43642b2009-06-29Martin Stjernholm  return 0; }
76ae182001-02-23Martin Stjernholm #else // Can't do much better when we haven't got threads..
63a8572006-11-14Martin Stjernholm  return call_out (func, delay, @args);
76ae182001-02-23Martin Stjernholm #endif } class BackgroundProcess //! A class to do a task repeatedly in the background, in a way that //! makes as little impact as possible on the incoming requests (using //! @[background_run]).
5ee4b72004-05-25Martin Stjernholm //!
0a558e2004-10-19Martin Stjernholm //! The user must keep a reference to this object, otherwise it will remove //! itself and the callback won't be called anymore.
76ae182001-02-23Martin Stjernholm { int|float period;
eaadcc2010-05-10Henrik Grubbström (Grubba)  function func; array args;
76ae182001-02-23Martin Stjernholm  int stopping = 0;
eaadcc2010-05-10Henrik Grubbström (Grubba)  protected void repeat (function func, array args)
76ae182001-02-23Martin Stjernholm  {
5ee4b72004-05-25Martin Stjernholm  // 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); #ifdef DEBUG if (self_refs < 4)
0a558e2004-10-19Martin Stjernholm  error ("Minimum ref calculation wrong - have only %d refs.\n", self_refs);
5ee4b72004-05-25Martin Stjernholm #endif
f534162010-05-10Henrik Grubbström (Grubba)  if (stopping || (self_refs <= 4) || !func) { stopping = 2; // Stopped. return; }
b9e5cb2007-10-11Henrik Grubbström (Grubba)  mixed err = catch { func (@args); };
44818f2012-02-18Martin Stjernholm  if (err) master()->handle_error (err);
76ae182001-02-23Martin Stjernholm  background_run (period, repeat, func, args); }
d35d5f2001-06-13Jonas Wallden  //! @decl void set_period (int|float period); //! //! Changes the period to @[period] seconds between calls.
63a8572006-11-14Martin Stjernholm  //! //! @note //! This does not change the currently ongoing period, if any. That //! might be remedied.
d35d5f2001-06-13Jonas Wallden  void set_period (int|float period_) { period = period_; }
fc40392008-08-15Martin Stjernholm  //! @decl protected void create (int|float period, function func, mixed... args);
76ae182001-02-23Martin Stjernholm  //! //! The function @[func] will be called with the following arguments //! after approximately @[period] seconds, and then kept being //! called with approximately that amount of time between each call. //! //! The repetition will stop if @[stop] is called, or if @[func] //! throws an error.
eaadcc2010-05-10Henrik Grubbström (Grubba)  protected void create (int|float period_, function func_, mixed... args_)
76ae182001-02-23Martin Stjernholm  { period = period_;
eaadcc2010-05-10Henrik Grubbström (Grubba)  func = func_; args = args_;
76ae182001-02-23Martin Stjernholm  background_run (period, repeat, func, args); } void stop() //! Sets a flag to stop the succession of calls. {
f534162010-05-10Henrik Grubbström (Grubba)  stopping |= 1; } void start() //! Restart a stopped process. { int state = stopping; stopping = 0; if (state & 2) { background_run (period, repeat, func, args); }
76ae182001-02-23Martin Stjernholm  }
54671c2001-11-12Martin Stjernholm 
f6ef152010-06-03Fredrik Noring  string _sprintf() { return sprintf("BackgroundProcess(%O, %O)", period, func); }
76ae182001-02-23Martin Stjernholm }
df36c61999-10-08Henrik Grubbström (Grubba) 
934b3f2000-02-04Per Hedbor mapping get_port_options( string key )
a6e4a12000-07-09Per Hedbor //! Get the options for the key 'key'. //! The intepretation of the options is protocol specific.
934b3f2000-02-04Per Hedbor { return (query( "port_options" )[ key ] || ([])); } void set_port_options( string key, mapping value )
a6e4a12000-07-09Per Hedbor //! Set the options for the key 'key'. //! The intepretation of the options is protocol specific.
934b3f2000-02-04Per Hedbor { mapping q = query("port_options"); q[ key ] = value; set( "port_options" , q ); save( ); }
41a5c02003-10-22Henrik Grubbström (Grubba) #ifdef DEBUG_URL2CONF #define URL2CONF_MSG(X...) report_debug (X) #else #define URL2CONF_MSG(X...) #endif
fc40392008-08-15Martin Stjernholm protected mapping(string:int(0..1)) host_is_local_cache = ([]);
cd86f32003-10-22Henrik Grubbström (Grubba)  //! Check if @[hostname] is local to this machine. int(0..1) host_is_local(string hostname) { int(0..1) res; if (!zero_type(res = host_is_local_cache[hostname])) return res; // Look up the IP. string ip = blocking_host_to_ip(hostname); // Can we bind to it? Stdio.Port port = Stdio.Port();
bfa4d72005-10-07Anders Johansson  // bind() can trow error if ip is an invalid hostname. catch { res = port->bind(0, 0, ip); };
cd86f32003-10-22Henrik Grubbström (Grubba)  destruct(port); return host_is_local_cache[hostname] = res; }
d02e952008-11-04Martin Stjernholm array(Protocol|mapping(string:mixed)) find_port_for_url ( Standards.URI url, void|Configuration only_this_conf)
ad8cfd2009-01-10Martin Stjernholm // Returns ({port_obj, url_data}) for a url that matches the given
d02e952008-11-04Martin Stjernholm // one. url_data is the mapping for the url in port_obj->urls. If // only_this_conf is given then only ports for that configuration are // searched.
6dcf352002-09-20Anders Johansson {
4d12712009-01-16Martin Stjernholm  // Cannot use the uri formatter in Standards.URI here since we // always want the port number to be present. string host = url->host;
8410c22009-01-17Martin Stjernholm  if (has_value (host, ":")) host = "[" + (Protocols.IPv6.normalize_addr_basic (host) || host) + "]";
4d12712009-01-16Martin Stjernholm  string url_with_port = sprintf ("%s://%s:%d%s", url->scheme, host, url->port, sizeof (url->path) ? url->path : "/");
41a5c02003-10-22Henrik Grubbström (Grubba)  URL2CONF_MSG("URL with port: %s\n", url_with_port);
d02e952008-11-04Martin Stjernholm  foreach (urls; string u; mapping(string:mixed) q)
6dcf352002-09-20Anders Johansson  {
41a5c02003-10-22Henrik Grubbström (Grubba)  URL2CONF_MSG("Trying %O:%O\n", u, q);
9ec1002002-10-01Anders Johansson  if( glob( u+"*", url_with_port ) ) {
41a5c02003-10-22Henrik Grubbström (Grubba)  URL2CONF_MSG("glob match\n");
d02e952008-11-04Martin Stjernholm  if (Protocol p = q->port) if (mapping(string:mixed) url_data =
ad8cfd2009-01-10Martin Stjernholm  p->find_url_data_for_url (url_with_port, 0, 0))
d02e952008-11-04Martin Stjernholm  {
0bb7d32008-12-03Jonas Wallden  Configuration c = url_data->conf; URL2CONF_MSG("Found config: %O\n", url_data->conf);
d02e952008-11-04Martin Stjernholm  if ((only_this_conf && (c != only_this_conf)) ||
dc89282008-12-11Jonas Wallden  (sscanf (u, "%*s://%*[^*?]%*c") == 3 && // u contains * or ?.
d02e952008-11-04Martin Stjernholm  // u is something like "http://*:80/" (!host_is_local(url->host)))) { // Bad match. URL2CONF_MSG("Bad match: only_this_conf:%O, host_is_local:%O\n", (only_this_conf && (c == only_this_conf)), (!host_is_local(url->host))); c = 0; continue; } URL2CONF_MSG("Result: %O\n", c); return ({p, url_data});
9ec1002002-10-01Anders Johansson  } }
6dcf352002-09-20Anders Johansson  }
d02e952008-11-04Martin Stjernholm  return ({0, 0}); } Configuration find_configuration_for_url(Standards.URI url, void|Configuration only_this_conf, void|array(Protocol) return_port) //! Tries to to determine if a request for the given url would end up //! in this server, and if so returns the corresponding configuration. //! //! If @[only_this_conf] has been specified only matches against it //! will be returned. { [Protocol port_obj, mapping(string:mixed) url_data] = find_port_for_url (url, only_this_conf);
8bb1f62005-05-31Jonas Wallden  if (return_port)
d02e952008-11-04Martin Stjernholm  return_port[0] = port_obj; return url_data && url_data->conf;
6dcf352002-09-20Anders Johansson }
4c04e52000-07-25Marcus Comstedt class InternalRequestID //! ID for internal requests that are not linked to any real request. { inherit RequestID;
7ead5a2001-08-15Per Hedbor  this_program set_path( string f ) {
f491312004-10-11Martin Stjernholm  raw_url = Roxen.http_encode_invalids( f );
8cbc5f2002-09-03Martin Stjernholm 
7ead5a2001-08-15Per Hedbor  if( strlen( f ) > 5 ) { string a; switch( f[1] ) { case '<': if (sscanf(f, "/<%s>/%s", a, f)==2) { config = (multiset)(a/","); f = "/"+f; } // intentional fall-through case '(': if(strlen(f) && sscanf(f, "/(%s)/%s", a, f)==2) { prestate = (multiset)( a/","-({""}) ); f = "/"+f; } } } not_query = Roxen.simplify_path( scan_for_query( f ) );
0a558e2004-10-19Martin Stjernholm  return this;
7ead5a2001-08-15Per Hedbor  } this_program set_url( string url ) {
41a5c02003-10-22Henrik Grubbström (Grubba)  object uri = Standards.URI(url); prot = upper_case(uri->scheme); misc->host = uri->host;
fe6b382006-09-29Jonas Wallden  if ((prot == "HTTP" && uri->port != 80) || (prot == "HTTPS" && uri->port != 443)) misc->host += ":" + uri->port;
41a5c02003-10-22Henrik Grubbström (Grubba)  string path = uri->path; raw_url = path;
8cbc5f2002-09-03Martin Stjernholm  method = "GET";
41a5c02003-10-22Henrik Grubbström (Grubba)  raw = "GET " + raw_url + " HTTP/1.1\r\n\r\n";
d02e952008-11-04Martin Stjernholm  [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();
a261b52008-11-04Martin Stjernholm  if (string config_path = url_data->path) adjust_for_config_path (config_path);
d02e952008-11-04Martin Stjernholm  }
8cbc5f2002-09-03Martin Stjernholm  return set_path( raw_url );
7ead5a2001-08-15Per Hedbor  }
fc40392008-08-15Martin Stjernholm  protected string _sprintf()
7ead5a2001-08-15Per Hedbor  {
c6bb1a2010-02-19Martin Stjernholm  return sprintf("InternalRequestID(conf=%O; not_query=%O)", conf, not_query );
7ead5a2001-08-15Per Hedbor  }
fc40392008-08-15Martin Stjernholm  protected void create()
4c04e52000-07-25Marcus Comstedt  { client = ({ "Roxen" }); prot = "INTERNAL"; method = "GET";
99cb5e2001-02-05Per Hedbor  real_variables = ([]); variables = FakedVariables( real_variables );
4dd3e02001-08-17Martin Nilsson  root_id = this_object();
99cb5e2001-02-05Per Hedbor 
bb7b502003-01-24Anders Johansson  misc = ([ "pref_languages": PrefLanguages(), "cacheable": INITIAL_CACHEABLE, ]);
4410ed2003-11-03Martin Stjernholm  connection_misc = ([]);
f6bf152005-12-07Henrik Grubbström (Grubba)  cookies = CookieJar();
4c04e52000-07-25Marcus Comstedt  throttle = ([]);
db4ca92000-09-18Henrik Grubbström (Grubba)  client_var = ([]);
4c04e52000-07-25Marcus Comstedt  request_headers = ([]); prestate = (<>); config = (<>); supports = (<>); pragma = (<>); rest_query = ""; extra_extension = "";
2622752000-11-13Marcus Comstedt  remoteaddr = "127.0.0.1";
4c04e52000-07-25Marcus Comstedt  } }
49284b2000-02-17Per Hedbor 
c5e0961999-10-04Per Hedbor class Protocol
c091442000-08-15Johan Sundström //! The basic protocol. //! Implements reference handling, finding Configuration objects
a6e4a12000-07-09Per Hedbor //! for URLs, and the bind/accept handling.
c5e0961999-10-04Per Hedbor {
fc40392008-08-15Martin Stjernholm  protected Stdio.Port port_obj;
722c9e2001-11-07Henrik Grubbström (Grubba) 
934b3f2000-02-04Per Hedbor  inherit "basic_defvar";
3a53152008-09-17Martin Stjernholm 
abc59a2000-08-23Per Hedbor  int bound;
3a53152008-09-17Martin Stjernholm  //! 0 if the port isn't bound, 1 if it is, and -1 if it binding it //! failed with EADDRINUSE when told to ignore that error. //! //! @note //! The -1 state should be uncommon since @[register_url] should //! remove such objects after the failed bind attempt.
c5e0961999-10-04Per Hedbor 
5773f62000-08-27Martin Stjernholm  string path;
c5e0961999-10-04Per Hedbor  constant name = "unknown";
ad8cfd2009-01-10Martin Stjernholm 
c5e0961999-10-04Per Hedbor  constant supports_ipless = 0;
a6e4a12000-07-09Per Hedbor  //! If true, the protocol handles ip-less virtual hosting
ea65f72001-07-21Martin Stjernholm 
c5e0961999-10-04Per Hedbor  constant requesthandlerfile = "";
a6e4a12000-07-09Per Hedbor  //! Filename of a by-connection handling class. It is also possible //! to set the 'requesthandler' class member in a overloaded create //! function.
c091442000-08-15Johan Sundström 
c5e0961999-10-04Per Hedbor  constant default_port = 4711;
a6e4a12000-07-09Per Hedbor  //! If no port is specified in the URL, use this one
934b3f2000-02-04Per Hedbor 
07fe262005-11-25Henrik Grubbström (Grubba)  string url_prefix = name + "://";
c5e0961999-10-04Per Hedbor  int port;
a6e4a12000-07-09Per Hedbor  //! The currently bound portnumber
ea65f72001-07-21Martin Stjernholm 
c5e0961999-10-04Per Hedbor  string ip;
d55e642005-03-02Henrik Grubbström (Grubba)  //! The canonical IP-number (0 for ANY) this port is bound to. //! //! IPv6 numbers are in colon separated four-digit lower-case hexadecimal //! notation with the first longest sequence of zeros compressed.
ea65f72001-07-21Martin Stjernholm 
8fb5172000-05-27Per Hedbor  int refs;
c80d9f2009-01-11Stephen R. van den Berg  //! The number of references to this port. This is the same as the //! size of @[urls] and @[sorted_urls].
ea65f72001-07-21Martin Stjernholm 
c5e0961999-10-04Per Hedbor  program requesthandler;
a6e4a12000-07-09Per Hedbor  //! The per-connection request handling class
ea65f72001-07-21Martin Stjernholm 
ddefa61999-10-04Marcus Comstedt  array(string) sorted_urls = ({});
a6e4a12000-07-09Per Hedbor  //! Sorted by length, longest first
ea65f72001-07-21Martin Stjernholm 
d02e952008-11-04Martin Stjernholm  mapping(string:mapping(string:mixed)) urls = ([]);
a6e4a12000-07-09Per Hedbor  //! .. url -> ([ "conf":.., ... ])
1bcd222007-09-06Henrik Grubbström (Grubba)  //! //! Indexed by URL. The following data is stored: //! @mapping
a253282009-04-03Henrik Grubbström (Grubba)  //! @member Configuration "conf"
1bcd222007-09-06Henrik Grubbström (Grubba)  //! The Configuration object for this URL.
a253282009-04-03Henrik Grubbström (Grubba)  //! @member string "hostname"
1bcd222007-09-06Henrik Grubbström (Grubba)  //! The hostname from the URL.
a253282009-04-03Henrik Grubbström (Grubba)  //! @member string|void "path"
1bcd222007-09-06Henrik Grubbström (Grubba)  //! The path (if any) from the URL.
a253282009-04-03Henrik Grubbström (Grubba)  //! @member Protocol "port"
1bcd222007-09-06Henrik Grubbström (Grubba)  //! The protocol handler for this URL.
a253282009-04-03Henrik Grubbström (Grubba)  //! @member int "mib_version"
1bcd222007-09-06Henrik Grubbström (Grubba)  //! (Only SNMP). The version number for the configuration MIB //! tree when it was last merged. //! @endmapping
c5e0961999-10-04Per Hedbor 
d02e952008-11-04Martin Stjernholm  mapping(Configuration:mapping(string:mixed)) conf_data = ([]);
ea65f72001-07-21Martin Stjernholm  //! Maps the configuration objects to the data mappings in @[urls].
d02e952008-11-04Martin Stjernholm  void ref(string name, mapping(string:mixed) data)
15d3b32011-06-15Henrik Grubbström (Grubba)  //! Add a ref for the URL @[name] with the data @[data]. //! //! See @[urls] for documentation about the supported //! fields in @[data].
c5e0961999-10-04Per Hedbor  {
ddefa61999-10-04Marcus Comstedt  if(urls[name])
8fb5172000-05-27Per Hedbor  {
ea65f72001-07-21Martin Stjernholm  conf_data[urls[name]->conf] = urls[name] = data;
8fb5172000-05-27Per Hedbor  return; // only ref once per URL }
5773f62000-08-27Martin Stjernholm  if (!refs) path = data->path; else if (path != (data->path || "")) path = 0;
c5e0961999-10-04Per Hedbor  refs++;
71e4122009-01-11Martin Stjernholm  mu = 0;
ea65f72001-07-21Martin Stjernholm  conf_data[data->conf] = urls[name] = data; sorted_urls = Array.sort_array(indices(urls),
24625e2000-08-28Per Hedbor  lambda(string a, string b) { return sizeof(a)<sizeof(b); });
c5e0961999-10-04Per Hedbor  }
b43d382002-04-19Anders Johansson  void unref(string _name) //! Remove a ref for the URL '_name'
c5e0961999-10-04Per Hedbor  {
bc5c2a2000-08-23Per Hedbor // if(!urls[name]) // only unref once // return;
5773f62000-08-27Martin Stjernholm 
b43d382002-04-19Anders Johansson  m_delete(conf_data, urls[_name]->conf); m_delete(urls, _name);
c80d9f2009-01-11Stephen R. van den Berg  if (!path) { array(string) paths = Array.uniq (values (urls)->path); if (sizeof (paths) == 1) path = paths[0]; }
b43d382002-04-19Anders Johansson  sorted_urls -= ({_name});
f74e252002-04-24Henrik Grubbström (Grubba) #ifdef PORT_DEBUG
fe1c132005-05-25Martin Stjernholm  report_debug("Protocol(%s)->unref(%O): refs:%d\n", get_url(), _name, refs);
f74e252002-04-24Henrik Grubbström (Grubba) #endif /* PORT_DEBUG */
722c9e2001-11-07Henrik Grubbström (Grubba)  if( !--refs ) {
0830d12003-11-05Henrik Grubbström (Grubba)  if (retries) { remove_call_out(bind); }
61d1c32001-11-27Henrik Grubbström (Grubba)  if (port_obj) { destruct(port_obj); }
722c9e2001-11-07Henrik Grubbström (Grubba)  port_obj = 0;
d6adf42005-11-18Henrik Grubbström (Grubba)  if (open_ports[name]) { if (open_ports[name][ip]) { m_delete(open_ports[name][ip], port);
d787202005-11-30Henrik Grubbström (Grubba)  if(!sizeof(open_ports[name][ip])) { // Make sure the entries for IPv4 and IPv6 ANY are left alone. if (ip && ip != "::") m_delete(open_ports[name], ip); } } if (sizeof(open_ports[name]) <= 2) { // Only ANY left. int empty = 1; foreach(open_ports[name]; string ip; mapping m) { if (sizeof(m)) { empty = 0; break; } } if (empty) m_delete(open_ports, name);
d6adf42005-11-18Henrik Grubbström (Grubba)  } }
5d33552009-01-10Martin Stjernholm  any_port = 0; // Avoid possibly cyclic ref.
722c9e2001-11-07Henrik Grubbström (Grubba)  //destruct( ); // Close the port. } } Stdio.File accept() { return port_obj->accept(); } string query_address() { return port_obj && port_obj->query_address();
c5e0961999-10-04Per Hedbor  }
d02e952008-11-04Martin Stjernholm  mapping(string:mixed) mu;
8514832000-11-27Per Hedbor  string rrhf;
fc40392008-08-15Martin Stjernholm  protected void got_connection()
c5e0961999-10-04Per Hedbor  {
55f9272006-12-21Henrik Grubbström (Grubba)  Stdio.File q; while( q = accept() )
1550aa2000-08-12Per Hedbor  {
03b7a22007-09-10Henrik Grubbström (Grubba)  if( !requesthandler && rrhf )
8514832000-11-27Per Hedbor  { requesthandler = (program)(rrhf); }
3e3bab2001-01-19Per Hedbor  Configuration c;
24625e2000-08-28Per Hedbor  if( refs < 2 )
1550aa2000-08-12Per Hedbor  { if(!mu) {
c80d9f2009-01-11Stephen R. van den Berg  mu = get_iterator(urls)->value();
5b5c802008-09-29Martin Stjernholm  if(!(c=mu->conf)->inited ) { handle (lambda () { c->enable_all_modules();
64efb82011-02-15Martin Jonsson  call_out (lambda () { // The connection might have been // closed already, e.g. by // http_fallback.ssl_alert_callback() // (prot_https.pike). Avoid a // backtrace in that case. if (q->is_open()) requesthandler (q, this, c); }, 0);
5b5c802008-09-29Martin Stjernholm  }); return; }
1f4a6c2000-08-28Per Hedbor  } else c = mu->conf;
1550aa2000-08-12Per Hedbor  } requesthandler( q, this_object(), c ); }
ddefa61999-10-04Marcus Comstedt  }
5d33552009-01-10Martin Stjernholm  private Protocol any_port;
347c722000-08-31Per Hedbor 
ad8cfd2009-01-10Martin Stjernholm  mapping(string:mixed) find_url_data_for_url (string url, int no_default, RequestID id)
ddefa61999-10-04Marcus Comstedt  {
c80d9f2009-01-11Stephen R. van den Berg  if( refs == 1 )
4c87772000-08-11Per Hedbor  {
c80d9f2009-01-11Stephen R. van den Berg  if (!no_default) { if(!mu) mu=get_iterator(urls)->value(); URL2CONF_MSG ("%O %O Only one configuration: %O\n", this, url, mu->conf); return mu; } } else if (!refs) {
d02e952008-11-04Martin Stjernholm  URL2CONF_MSG("%O %O No active URLS!\n", this, url);
3d18ba2001-12-19Henrik Grubbström (Grubba)  return 0;
4c87772000-08-11Per Hedbor  }
41a5c02003-10-22Henrik Grubbström (Grubba)  URL2CONF_MSG("sorted_urls: %O\n" "url: %O\n", sorted_urls, url);
8fb5172000-05-27Per Hedbor  // The URLs are sorted from longest to shortest, so that short // urls (such as http://*/) will not match before more complete // ones (such as http://*.roxen.com/)
ddefa61999-10-04Marcus Comstedt  foreach( sorted_urls, string in ) { if( glob( in+"*", url ) ) {
d02e952008-11-04Martin Stjernholm  URL2CONF_MSG ("%O %O sorted_urls: %O\n", this, url, urls[in]->conf); return urls[in];
ddefa61999-10-04Marcus Comstedt  } }
8fb5172000-05-27Per Hedbor 
ea65f72001-07-21Martin Stjernholm  if( no_default ) {
d02e952008-11-04Martin Stjernholm  URL2CONF_MSG ("%O %O no default\n", this, url);
8fb5172000-05-27Per Hedbor  return 0;
ea65f72001-07-21Martin Stjernholm  }
ad8cfd2009-01-10Martin Stjernholm  // Note: The docs for RequestID.misc->default_conf has a // description of this fallback procedure.
8fb5172000-05-27Per Hedbor  // No host matched, or no host header was included in the request.
347c722000-08-31Per Hedbor  // Is the URL in the '*' ports?
5d33552009-01-10Martin Stjernholm  if (!any_port) any_port = open_ports[ name ][ 0 ][ port ]; if (any_port && any_port != this) if (mapping(string:mixed) u = any_port->find_url_data_for_url (url, 1, id)) { URL2CONF_MSG ("%O %O found on ANY port: %O\n", this, url, u->conf); if (id) { id->misc->defaulted_conf = 1; id->port_obj = any_port; }
d02e952008-11-04Martin Stjernholm  return u; }
8fb5172000-05-27Per Hedbor  // No. We have to default to one of the other ports. // It might be that one of the servers is tagged as a default server.
d02e952008-11-04Martin Stjernholm  mapping(Configuration:int(1..1)) choices = ([]);
d093992000-09-25Per Hedbor  foreach( configurations, Configuration c )
8fb5172000-05-27Per Hedbor  if( c->query( "default_server" ) )
d02e952008-11-04Martin Stjernholm  choices[c] = 1;
8fb5172000-05-27Per Hedbor  if( sizeof( choices ) )
7120b82000-03-27Per Hedbor  {
d02e952008-11-04Martin Stjernholm  // Pick a default server bound to this port foreach (urls;; mapping cc)
347c722000-08-31Per Hedbor  if( choices[ cc->conf ] )
8fb5172000-05-27Per Hedbor  {
d02e952008-11-04Martin Stjernholm  URL2CONF_MSG ("%O %O conf in choices: %O\n", this, url, cc->conf);
ad8cfd2009-01-10Martin Stjernholm  if (id) id->misc->defaulted_conf = 2;
d02e952008-11-04Martin Stjernholm  return cc;
8fb5172000-05-27Per Hedbor  }
d02e952008-11-04Martin Stjernholm  }
347c722000-08-31Per Hedbor 
d02e952008-11-04Martin Stjernholm  return 0; }
347c722000-08-31Per Hedbor 
d02e952008-11-04Martin Stjernholm  Configuration find_configuration_for_url( string url, RequestID id ) //! Given a url and requestid, try to locate a suitable configuration //! (virtual site) for the request. //! This interface is not at all set in stone, and might change at //! any time. {
ad8cfd2009-01-10Martin Stjernholm  mapping(string:mixed) url_data = find_url_data_for_url (url, 0, id);
d02e952008-11-04Martin Stjernholm  if (!url_data) { // Pick the first default server available. FIXME: This makes // it impossible to handle the server path correctly. foreach (configurations, Configuration c) if (c->query ("default_server")) { URL2CONF_MSG ("%O %O any default server: %O\n", this, url, c);
ad8cfd2009-01-10Martin Stjernholm  if (id) id->misc->defaulted_conf = 3;
d02e952008-11-04Martin Stjernholm  if(!c->inited) // FIXME: We can be called from the backend thread, so // this should be queued for a handler thread. c->enable_all_modules(); return c; }
8fb5172000-05-27Per Hedbor 
d02e952008-11-04Martin Stjernholm  // if we end up here, there is no default port at all available // so grab the first configuration that is available at all.
ad8cfd2009-01-10Martin Stjernholm  // We choose the last entry in sorted_urls since that's the most // generic one and therefore probably the best option for a // fallback. url_data = urls[sorted_urls[-1]]; if (id) { id->misc->defaulted_conf = 4; id->misc->defaulted=1; // Compat. } URL2CONF_MSG ("%O %O last in sorted_urls: %O\n", this, url,
d02e952008-11-04Martin Stjernholm  url_data->conf); }
8fb5172000-05-27Per Hedbor 
5d33552009-01-10Martin Stjernholm  // It's assumed nothing below uses data in this object, since // find_url_data_for_url might have switched Protocol object.
d02e952008-11-04Martin Stjernholm  string config_path = url_data->path; if (config_path && id && id->adjust_for_config_path) id->adjust_for_config_path (config_path); Configuration c = url_data->conf; if(!c->inited) // FIXME: We can be called from the backend thread, so this // should be queued for a handler thread. c->enable_all_modules();
8fb5172000-05-27Per Hedbor  return c;
c5e0961999-10-04Per Hedbor  }
934b3f2000-02-04Per Hedbor  mixed query_option( string x )
a6e4a12000-07-09Per Hedbor  //! Query the port-option 'x' for this port.
10bdc51999-10-08Henrik Grubbström (Grubba)  {
934b3f2000-02-04Per Hedbor  return query( x );
10bdc51999-10-08Henrik Grubbström (Grubba)  }
934b3f2000-02-04Per Hedbor  string get_key()
d55e642005-03-02Henrik Grubbström (Grubba)  //! Return the key used for this port (protocol:ip:portno)
df36c61999-10-08Henrik Grubbström (Grubba)  {
5357612011-08-15Henrik Grubbström (Grubba) #if 0
fe1c132005-05-25Martin Stjernholm  if (ip == "::") return name + ":0:" + port; else
5357612011-08-15Henrik Grubbström (Grubba) #endif
fe1c132005-05-25Martin Stjernholm  return name+":"+ip+":"+port; } string get_url() //! Return the port on URL form. { return (string) name + "://" + (!ip ? "*" : has_value (ip, ":") ? "[" + ip + "]" : ip) + ":" + port + "/";
934b3f2000-02-04Per Hedbor  }
df36c61999-10-08Henrik Grubbström (Grubba) 
934b3f2000-02-04Per Hedbor  void save()
a6e4a12000-07-09Per Hedbor  //! Save all port options
934b3f2000-02-04Per Hedbor  { set_port_options( get_key(), mkmapping( indices(variables), map(indices(variables),query))); }
df36c61999-10-08Henrik Grubbström (Grubba) 
934b3f2000-02-04Per Hedbor  void restore()
a6e4a12000-07-09Per Hedbor  //! Restore all port options from saved values
934b3f2000-02-04Per Hedbor  { foreach( (array)get_port_options( get_key() ), array kv ) set( kv[0], kv[1] );
df36c61999-10-08Henrik Grubbström (Grubba)  }
fc40392008-08-15Martin Stjernholm  protected int retries; protected void bind (void|int ignore_eaddrinuse)
044f142003-11-05Henrik Grubbström (Grubba)  { if (bound) return; if (!port_obj) port_obj = Stdio.Port();
fe1c132005-05-25Martin Stjernholm  Privs privs = Privs (sprintf ("Binding %s", get_url()));
044f142003-11-05Henrik Grubbström (Grubba)  if (port_obj->bind(port, got_connection, ip)) {
537a592005-02-10Martin Stjernholm  privs = 0;
044f142003-11-05Henrik Grubbström (Grubba)  bound = 1; return; }
537a592005-02-10Martin Stjernholm  privs = 0;
eecdf12005-02-23Henrik Grubbström (Grubba) #if constant(System.EAFNOSUPPORT) if (port_obj->errno() == System.EAFNOSUPPORT) { // Fail permanently. error("Invalid address " + ip); } #endif /* System.EAFNOSUPPORT */
044f142003-11-05Henrik Grubbström (Grubba) #if constant(System.EADDRINUSE)
3a53152008-09-17Martin Stjernholm  if (port_obj->errno() == System.EADDRINUSE) { if (ignore_eaddrinuse) { // Told to ignore the bind problem. bound = -1; return; } if (retries++ < 10) {
24f9642007-05-03Martin Stjernholm  // We may get spurious failures on rebinding ports on some OS'es // (eg Linux, WIN32). See [bug 3031]. report_error(LOC_M(6, "Failed to bind %s (%s)")+"\n", get_url(), strerror(port_obj->errno())); report_notice(LOC_M(62, "Attempt %d. Retrying in 1 minute.")+"\n", retries); call_out(bind, 60); }
044f142003-11-05Henrik Grubbström (Grubba)  }
24f9642007-05-03Martin Stjernholm  else
3a53152008-09-17Martin Stjernholm #endif /* constant(System.EADDRINUSE) */
24f9642007-05-03Martin Stjernholm  { report_error(LOC_M(6, "Failed to bind %s (%s)")+"\n", get_url(), strerror(port_obj->errno())); #if 0 werror (describe_backtrace (backtrace())); #endif }
044f142003-11-05Henrik Grubbström (Grubba)  }
d55e642005-03-02Henrik Grubbström (Grubba)  string canonical_ip(string i) {
157e6e2005-03-07Martin Stjernholm  if (!i) return 0;
8410c22009-01-17Martin Stjernholm  if (has_value(i, ":")) return Protocols.IPv6.normalize_addr_short (i); else {
d55e642005-03-02Henrik Grubbström (Grubba)  // IPv4 array(int) segments = array_sscanf(i, "%d.%d.%d.%d"); string bytes; switch(sizeof(segments)) { default: case 4: bytes = sprintf("%1c%1c%1c%1c", @segments); break; case 0: return 0; // ANY. case 1: /* When only one part is given, the value is stored directly in * the network address without any byte rearrangement. */ bytes = sprintf("%4c", @segments); break; case 2: /* When a two part address is supplied, the last part is inter- * preted as a 24-bit quantity and placed in the right most * three bytes of the network address. This makes the two part * address format convenient for specifying Class A network * addresses as net.host. */ bytes = sprintf("%1c%3c", @segments); break; case 3: /* When a three part address is specified, the last part is * interpreted as a 16-bit quantity and placed in the right * most two bytes of the network address. This makes the three * part address format convenient for specifying Class B net- * work addresses as 128.net.host. */ bytes = sprintf("%1c%1c%2c", @segments); break; } if (bytes == "\0\0\0\0") return 0; // ANY. return sprintf("%d.%d.%d.%d", @((array(int))bytes)); } }
fc40392008-08-15Martin Stjernholm  protected void setup (int pn, string i)
c5e0961999-10-04Per Hedbor  {
934b3f2000-02-04Per Hedbor  port = pn;
d55e642005-03-02Henrik Grubbström (Grubba)  ip = canonical_ip(i);
934b3f2000-02-04Per Hedbor  restore();
03b7a22007-09-10Henrik Grubbström (Grubba)  if (sizeof(requesthandlerfile)) { if( file_stat( "../local/"+requesthandlerfile ) ) rrhf = "../local/"+requesthandlerfile; else rrhf = requesthandlerfile; DDUMP( rrhf );
8514832000-11-27Per Hedbor #ifdef DEBUG
03b7a22007-09-10Henrik Grubbström (Grubba)  if( !requesthandler ) requesthandler = (program)(rrhf);
8514832000-11-27Per Hedbor #endif
03b7a22007-09-10Henrik Grubbström (Grubba)  }
044f142003-11-05Henrik Grubbström (Grubba)  bound = 0; port_obj = 0; retries = 0;
fe1c132005-05-25Martin Stjernholm  }
fc40392008-08-15Martin Stjernholm  protected void create( int pn, string i, void|int ignore_eaddrinuse )
fe1c132005-05-25Martin Stjernholm  //! Constructor. Bind to the port 'pn' ip 'i' { setup (pn, i);
24f9642007-05-03Martin Stjernholm  bind (ignore_eaddrinuse);
c5e0961999-10-04Per Hedbor  }
81f8af1999-12-20Martin Nilsson 
fc40392008-08-15Martin Stjernholm  protected string _sprintf( )
b6fb051999-11-02Per Hedbor  {
fe1c132005-05-25Martin Stjernholm  return "Protocol(" + get_url() + ")";
b6fb051999-11-02Per Hedbor  }
c5e0961999-10-04Per Hedbor }
4820e01999-07-27Henrik Grubbström (Grubba) 
b8fd5c2000-09-28Per Hedbor #if constant(SSL.sslfile)
df36c61999-10-08Henrik Grubbström (Grubba) class SSLProtocol
a6e4a12000-07-09Per Hedbor //! Base protocol for SSL ports. Exactly like Port, but uses SSL.
df36c61999-10-08Henrik Grubbström (Grubba) { inherit Protocol; // SSL context
85fbbc2004-08-19Henrik Grubbström (Grubba)  SSL.context ctx = SSL.context();
df36c61999-10-08Henrik Grubbström (Grubba) 
fe1c132005-05-25Martin Stjernholm  int cert_failure;
fc40392008-08-15Martin Stjernholm  protected void cert_err_unbind()
fe1c132005-05-25Martin Stjernholm  {
3a53152008-09-17Martin Stjernholm  if (bound > 0) {
fe1c132005-05-25Martin Stjernholm  port_obj->close(); report_warning ("TLS port %s closed.\n", get_url()); bound = 0; } } #define CERT_WARNING(VAR, MSG, ARGS...) do { \ string msg = (MSG); \ array args = ({ARGS}); \ if (sizeof (args)) msg = sprintf (msg, @args); \ report_warning ("TLS port %s: %s", get_url(), msg); \ (VAR)->add_warning (msg); \ } while (0) #define CERT_ERROR(VAR, MSG, ARGS...) do { \ 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)
8506b32010-12-21Henrik Grubbström (Grubba)  protected void filter_preferred_suites() { #ifndef ALLOW_WEAK_SSL // Filter weak and really weak cipher suites. ctx->preferred_suites -= ({
016cfc2011-12-12Henrik Grubbström (Grubba)  SSL.Constants.SSL_rsa_with_des_cbc_sha,
c701da2012-01-19Henrik Grubbström (Grubba)  SSL.Constants.SSL_dhe_dss_with_des_cbc_sha,
8506b32010-12-21Henrik Grubbström (Grubba)  SSL.Constants.SSL_rsa_export_with_rc4_40_md5,
c701da2012-01-19Henrik Grubbström (Grubba)  SSL.Constants.TLS_rsa_with_null_sha256,
8506b32010-12-21Henrik Grubbström (Grubba)  SSL.Constants.SSL_rsa_with_null_sha, SSL.Constants.SSL_rsa_with_null_md5, SSL.Constants.SSL_dhe_dss_export_with_des40_cbc_sha, SSL.Constants.SSL_null_with_null_null, }); #endif }
fc40392008-08-15Martin Stjernholm  void certificates_changed(Variable.Variable|void ignored,
24f9642007-05-03Martin Stjernholm  void|int ignore_eaddrinuse)
df36c61999-10-08Henrik Grubbström (Grubba)  {
fe1c132005-05-25Martin Stjernholm  int old_cert_failure = cert_failure;
85fbbc2004-08-19Henrik Grubbström (Grubba)  string raw_keydata; array(string) certificates = ({});
fe1c132005-05-25Martin Stjernholm  array(object) decoded_certs = ({});
fc40392008-08-15Martin Stjernholm  Variable.Variable Certificates = getvar("ssl_cert_file");
df36c61999-10-08Henrik Grubbström (Grubba) 
d0a4f82001-04-18Per Hedbor  object privs = Privs("Reading cert file");
df36c61999-10-08Henrik Grubbström (Grubba) 
85fbbc2004-08-19Henrik Grubbström (Grubba)  foreach(map(Certificates->query(), String.trim_whites), string cert_file) { string raw_cert;
fe1c132005-05-25Martin Stjernholm  SSL3_WERR (sprintf ("Reading cert file %O", cert_file));
85fbbc2004-08-19Henrik Grubbström (Grubba)  if( catch{ raw_cert = lopen(cert_file, "r")->read(); } )
d0a4f82001-04-18Per Hedbor  {
fe1c132005-05-25Martin Stjernholm  CERT_WARNING (Certificates, LOC_M(8, "Reading certificate file %O failed: %s\n"), cert_file, strerror (errno()));
85fbbc2004-08-19Henrik Grubbström (Grubba)  continue;
d0a4f82001-04-18Per Hedbor  }
df36c61999-10-08Henrik Grubbström (Grubba) 
85fbbc2004-08-19Henrik Grubbström (Grubba)  object msg = Tools.PEM.pem_msg()->init( raw_cert ); object part = msg->parts["CERTIFICATE"] || msg->parts["X509 CERTIFICATE"];
d0a4f82001-04-18Per Hedbor  string cert;
85fbbc2004-08-19Henrik Grubbström (Grubba)  if (msg->parts["RSA PRIVATE KEY"] || msg->parts["DSA PRIVATE KEY"]) { raw_keydata = raw_cert; }
d0a4f82001-04-18Per Hedbor  if (!part || !(cert = part->decoded_body()))
2dd46b2000-03-24Per Hedbor  {
fe1c132005-05-25Martin Stjernholm  CERT_WARNING (Certificates, LOC_M(10, "No certificate found in %O.\n"), cert_file);
85fbbc2004-08-19Henrik Grubbström (Grubba)  continue;
df36c61999-10-08Henrik Grubbström (Grubba)  }
85fbbc2004-08-19Henrik Grubbström (Grubba)  certificates += ({ cert });
df36c61999-10-08Henrik Grubbström (Grubba) 
fe1c132005-05-25Martin Stjernholm  // FIXME: Support PKCS7 object tbs = Tools.X509.decode_certificate (cert); if (!tbs) { CERT_WARNING (Certificates, LOC_M(13, "Certificate not valid (DER).\n")); continue; } decoded_certs += ({tbs}); }
d0a4f82001-04-18Per Hedbor 
fe1c132005-05-25Martin Stjernholm  if (!sizeof(decoded_certs)) { report_error ("TLS port %s: %s", get_url(),
eabb192006-01-25Anders Johansson  LOC_M(63,"No certificates found.\n"));
fe1c132005-05-25Martin Stjernholm  cert_err_unbind(); cert_failure = 1;
85fbbc2004-08-19Henrik Grubbström (Grubba)  return; }
81f8af1999-12-20Martin Nilsson 
fc40392008-08-15Martin Stjernholm  Variable.Variable KeyFile = getvar("ssl_key_file");
81f8af1999-12-20Martin Nilsson 
fe1c132005-05-25Martin Stjernholm  if( strlen(KeyFile->query())) { SSL3_WERR (sprintf ("Reading key file %O", KeyFile->query())); if (catch{ raw_keydata = lopen(KeyFile->query(), "r")->read(); } ) CERT_ERROR (KeyFile, LOC_M(9, "Reading key file %O failed: %s\n"), KeyFile->query(), strerror (errno()));
85fbbc2004-08-19Henrik Grubbström (Grubba)  }
fe1c132005-05-25Martin Stjernholm  else KeyFile = Certificates;
81f8af1999-12-20Martin Nilsson 
fe1c132005-05-25Martin Stjernholm  privs = 0; if (!raw_keydata) CERT_ERROR (KeyFile, LOC_M (17,"No private key found.\n"));
df36c61999-10-08Henrik Grubbström (Grubba) 
85fbbc2004-08-19Henrik Grubbström (Grubba)  object msg = Tools.PEM.pem_msg()->init( raw_keydata );
d0a4f82001-04-18Per Hedbor 
85fbbc2004-08-19Henrik Grubbström (Grubba)  SSL3_WERR(sprintf("key file contains: %O", indices(msg->parts)));
d0a4f82001-04-18Per Hedbor 
85fbbc2004-08-19Henrik Grubbström (Grubba)  object part; if (part = msg->parts["RSA PRIVATE KEY"]) { string key;
d0a4f82001-04-18Per Hedbor 
fe1c132005-05-25Martin Stjernholm  if (!(key = part->decoded_body())) CERT_ERROR (KeyFile, LOC_M(11,"Private rsa key not valid")+" (PEM).\n");
d0a4f82001-04-18Per Hedbor 
85fbbc2004-08-19Henrik Grubbström (Grubba)  object rsa = Standards.PKCS.RSA.parse_private_key(key);
fe1c132005-05-25Martin Stjernholm  if (!rsa) CERT_ERROR (KeyFile, LOC_M(11,"Private rsa key not valid")+" (DER).\n");
d0a4f82001-04-18Per Hedbor 
85fbbc2004-08-19Henrik Grubbström (Grubba)  ctx->rsa = rsa; SSL3_WERR(sprintf("RSA key size: %d bits", rsa->rsa_size())); if (rsa->rsa_size() > 512) { /* Too large for export */ ctx->short_rsa = Crypto.RSA()->generate_key(512, ctx->random);
fc40392008-08-15Martin Stjernholm  // ctx->long_rsa = Crypto.RSA()->generate_key(rsa->rsa_size(), ctx->random);
85fbbc2004-08-19Henrik Grubbström (Grubba)  } ctx->rsa_mode();
8506b32010-12-21Henrik Grubbström (Grubba)  filter_preferred_suites();
85fbbc2004-08-19Henrik Grubbström (Grubba)  array(int) key_matches =
fe1c132005-05-25Martin Stjernholm  map(decoded_certs, lambda (object tbs) {
85fbbc2004-08-19Henrik Grubbström (Grubba)  return tbs->public_key->rsa->public_key_equal (rsa);
fe1c132005-05-25Martin Stjernholm  });
85fbbc2004-08-19Henrik Grubbström (Grubba)  int num_key_matches; // DWIM: Make sure the main cert comes first. array(string) new_certificates = allocate(sizeof(certificates)); int i,j; for (i=0; i < sizeof(certificates); i++) { if (key_matches[i]) { new_certificates[j++] = certificates[i]; num_key_matches++;
d0a4f82001-04-18Per Hedbor  }
85fbbc2004-08-19Henrik Grubbström (Grubba)  } for (i=0; i < sizeof(certificates); i++) { if (!key_matches[i]) { new_certificates[j++] = certificates[i];
1e7b6f2002-01-31Jens Larsson  }
df36c61999-10-08Henrik Grubbström (Grubba)  }
85fbbc2004-08-19Henrik Grubbström (Grubba)  if( !num_key_matches )
fe1c132005-05-25Martin Stjernholm  CERT_ERROR (KeyFile, LOC_M(14, "Certificate and private key do not match.\n"));
85fbbc2004-08-19Henrik Grubbström (Grubba)  ctx->certificates = new_certificates; } else if (part = msg->parts["DSA PRIVATE KEY"]) { string key;
df36c61999-10-08Henrik Grubbström (Grubba) 
fe1c132005-05-25Martin Stjernholm  if (!(key = part->decoded_body())) CERT_ERROR (KeyFile, LOC_M(15,"Private dsa key not valid")+" (PEM).\n");
d0a4f82001-04-18Per Hedbor 
85fbbc2004-08-19Henrik Grubbström (Grubba)  object dsa = Standards.PKCS.DSA.parse_private_key(key);
fe1c132005-05-25Martin Stjernholm  if (!dsa) CERT_ERROR (KeyFile, LOC_M(15,"Private dsa key not valid")+" (DER).\n");
81f8af1999-12-20Martin Nilsson 
85fbbc2004-08-19Henrik Grubbström (Grubba)  SSL3_WERR(sprintf("Using DSA key."));
d0a4f82001-04-18Per Hedbor 
85fbbc2004-08-19Henrik Grubbström (Grubba)  //dsa->use_random(ctx->random); ctx->dsa = dsa; /* Use default DH parameters */
0217b82003-03-03Henrik Grubbström (Grubba) #if constant(SSL.Cipher)
85fbbc2004-08-19Henrik Grubbström (Grubba)  ctx->dh_params = SSL.Cipher.DHParameters();
0217b82003-03-03Henrik Grubbström (Grubba) #else
85fbbc2004-08-19Henrik Grubbström (Grubba)  ctx->dh_params = SSL.cipher()->dh_parameters();
0217b82003-03-03Henrik Grubbström (Grubba) #endif
d0a4f82001-04-18Per Hedbor 
85fbbc2004-08-19Henrik Grubbström (Grubba)  ctx->dhe_dss_mode();
8506b32010-12-21Henrik Grubbström (Grubba)  filter_preferred_suites();
d0a4f82001-04-18Per Hedbor 
85fbbc2004-08-19Henrik Grubbström (Grubba)  // FIXME: Add cert <-> private key check.
1e7b6f2002-01-31Jens Larsson 
85fbbc2004-08-19Henrik Grubbström (Grubba)  ctx->certificates = certificates;
df36c61999-10-08Henrik Grubbström (Grubba)  }
fe1c132005-05-25Martin Stjernholm  else CERT_ERROR (KeyFile, LOC_M(17,"No private key found.\n"));
85fbbc2004-08-19Henrik Grubbström (Grubba) 
df36c61999-10-08Henrik Grubbström (Grubba) #if EXPORT ctx->export_mode(); #endif
fe1c132005-05-25Martin Stjernholm  if (!bound) {
24f9642007-05-03Martin Stjernholm  bind (ignore_eaddrinuse);
fe1c132005-05-25Martin Stjernholm  if (old_cert_failure && bound)
eabb192006-01-25Anders Johansson  report_notice (LOC_M(64, "TLS port %s opened.\n"), get_url());
fe1c132005-05-25Martin Stjernholm  }
85fbbc2004-08-19Henrik Grubbström (Grubba)  } class CertificateListVariable { inherit Variable.FileList; string doc() { return sprintf(::doc() + "\n", combine_path(getcwd(), "../local"), getcwd()); } } class KeyFileVariable { inherit Variable.String; string doc() { return sprintf(::doc() + "\n", combine_path(getcwd(), "../local"), getcwd()); } }
fc40392008-08-15Martin Stjernholm  SSL.sslfile accept()
85fbbc2004-08-19Henrik Grubbström (Grubba)  { Stdio.File q = ::accept(); if (q)
fc40392008-08-15Martin Stjernholm  return SSL.sslfile (q, ctx);
85fbbc2004-08-19Henrik Grubbström (Grubba)  return 0; }
fc40392008-08-15Martin Stjernholm  protected void bind (void|int ignore_eaddrinuse)
fe1c132005-05-25Martin Stjernholm  { // Don't bind if we don't have correct certs. if (!ctx->certificates) return;
24f9642007-05-03Martin Stjernholm  ::bind (ignore_eaddrinuse);
fe1c132005-05-25Martin Stjernholm  }
24f9642007-05-03Martin Stjernholm  void create(int pn, string i, void|int ignore_eaddrinuse)
85fbbc2004-08-19Henrik Grubbström (Grubba)  { ctx->random = Crypto.Random.random_string;
8506b32010-12-21Henrik Grubbström (Grubba)  filter_preferred_suites();
85fbbc2004-08-19Henrik Grubbström (Grubba)  set_up_ssl_variables( this_object() );
fe1c132005-05-25Martin Stjernholm  ::setup(pn, i);
85fbbc2004-08-19Henrik Grubbström (Grubba) 
24f9642007-05-03Martin Stjernholm  certificates_changed (0, ignore_eaddrinuse);
fe1c132005-05-25Martin Stjernholm  // Install the change callbacks here to avoid duplicate calls // above.
58258e2006-05-16Henrik Grubbström (Grubba)  // 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.
fe1c132005-05-25Martin Stjernholm  getvar ("ssl_cert_file")->set_changed_callback (certificates_changed); getvar ("ssl_key_file")->set_changed_callback (certificates_changed);
df36c61999-10-08Henrik Grubbström (Grubba)  }
b8fd5c2000-09-28Per Hedbor 
b6fb051999-11-02Per Hedbor  string _sprintf( ) {
fe1c132005-05-25Martin Stjernholm  return "SSLProtocol(" + get_url() + ")";
b6fb051999-11-02Per Hedbor  }
dd7e661999-10-09Henrik Grubbström (Grubba) }
b8fd5c2000-09-28Per Hedbor #endif
df36c61999-10-08Henrik Grubbström (Grubba) 
3a53152008-09-17Martin Stjernholm mapping(string:program/*(Protocol)*/) build_protocols_mapping()
761baa1999-12-08Per Hedbor {
0d0e952000-11-13Per Hedbor  mapping protocols = ([]); int st = gethrtime();
56509c2001-06-30Martin Stjernholm  report_debug("Protocol handlers ... \b");
0d0e952000-11-13Per Hedbor #ifndef DEBUG class lazy_load( string prog, string name )
761baa1999-12-08Per Hedbor  {
0d0e952000-11-13Per Hedbor  program real;
fc40392008-08-15Martin Stjernholm  protected void realize()
761baa1999-12-08Per Hedbor  {
0d0e952000-11-13Per Hedbor  if( catch {
8514832000-11-27Per Hedbor  DDUMP( prog );
0d0e952000-11-13Per Hedbor  real = (program)prog; protocols[name] = real; } ) report_error("Failed to compile protocol handler for "+name+"\n");
761baa1999-12-08Per Hedbor  }
0d0e952000-11-13Per Hedbor  Protocol `()(mixed ... x)
761baa1999-12-08Per Hedbor  {
0d0e952000-11-13Per Hedbor  if(!real) realize(); return real(@x); }; mixed `->( string x )
761baa1999-12-08Per Hedbor  {
0d0e952000-11-13Per Hedbor  if(!real) realize(); return predef::`->(real, x);
761baa1999-12-08Per Hedbor  }
0d0e952000-11-13Per Hedbor  }; #endif foreach( glob( "prot_*.pike", get_dir("protocols") ), string s )
6796aa1999-12-11Per Hedbor  {
0d0e952000-11-13Per Hedbor  sscanf( s, "prot_%s.pike", s ); #if !constant(SSL.sslfile) switch( s )
761baa1999-12-08Per Hedbor  {
0d0e952000-11-13Per Hedbor  case "https": case "ftps": continue;
761baa1999-12-08Per Hedbor  }
0d0e952000-11-13Per Hedbor #endif
56509c2001-06-30Martin Stjernholm  report_debug( "\b%s \b", s );
6f72d42000-02-08Per Hedbor 
0d0e952000-11-13Per Hedbor  catch
761baa1999-12-08Per Hedbor  {
0d0e952000-11-13Per Hedbor #ifdef DEBUG protocols[ s ] = (program)("protocols/prot_"+s+".pike"); #else protocols[ s ] = lazy_load( ("protocols/prot_"+s+".pike"),s );
761baa1999-12-08Per Hedbor #endif
0d0e952000-11-13Per Hedbor  };
934b3f2000-02-04Per Hedbor  }
0d0e952000-11-13Per Hedbor  foreach( glob("prot_*.pike",get_dir("../local/protocols")||({})), string s )
1d7d6d2000-02-16Per Hedbor  {
0d0e952000-11-13Per Hedbor  sscanf( s, "prot_%s.pike", s ); #if !constant(SSL.sslfile) switch( s )
1d7d6d2000-02-16Per Hedbor  {
0d0e952000-11-13Per Hedbor  case "https": case "ftps": continue;
df36c61999-10-08Henrik Grubbström (Grubba)  }
b8fd5c2000-09-28Per Hedbor #endif
56509c2001-06-30Martin Stjernholm  report_debug( "\b%s \b", s );
0d0e952000-11-13Per Hedbor  catch { #ifdef DEBUG protocols[ s ] = (program)("../local/protocols/prot_"+s+".pike");
761baa1999-12-08Per Hedbor #else
0d0e952000-11-13Per Hedbor  protocols[ s ] = lazy_load( ("../local/protocols/prot_"+s+".pike"),s );
761baa1999-12-08Per Hedbor #endif
0d0e952000-11-13Per Hedbor  }; }
56509c2001-06-30Martin Stjernholm  report_debug("\bDone [%.1fms]\n", (gethrtime()-st)/1000.0 );
0d0e952000-11-13Per Hedbor  return protocols; }
b00cae2000-09-30Per Hedbor 
479d8a1999-11-25Henrik Grubbström (Grubba) 
3a53152008-09-17Martin Stjernholm mapping(string:program/*(Protocol)*/) protocols;
c5e0961999-10-04Per Hedbor 
15d3b32011-06-15Henrik Grubbström (Grubba) //! Lookup from protocol, IP number and port to //! the corresponding open @[Protocol] port. //! //! @mapping //! @member mapping(string:mapping(int:Protocol)) protocol_name //! @mapping //! @member mapping(int:Protocol) ip_number //! @mapping //! @member Protocol port_number //! @[Protocol] object that holds this ip_number and port open. //! @endmapping //! @endmapping //! @endmapping
41a5c02003-10-22Henrik Grubbström (Grubba) mapping(string:mapping(string:mapping(int:Protocol))) open_ports = ([ ]);
15d3b32011-06-15Henrik Grubbström (Grubba) //! Lookup from URL string to the corresponding open @[Protocol] ports. //! //! Note that there are two classes of URL strings used as indices in //! this mapping: //! @dl //! @item "prot://host_glob:port/path/" //! A normalized URL string as returned by @[normalize_url()]. //! //! @[Protocol()->ref()] in the contained ports as been called //! with the url. //! //! @item "port://host_glob:port/path/#opt1=val1;opt2=val2" //! An URL string containing options as stored in the @tt{"URLs"@} //! configuration variable, and expected as argument by //! @[register_url()] and @[unregister_url()]. Also known //! as an ourl. //! @enddl //! //! In both cases the same set of data is stored: //! @mapping
7302372011-07-14Henrik Grubbström (Grubba) //! @member mapping(string:Configuration|Protocol|string|array(Protocol)|array(string)) url
15d3b32011-06-15Henrik Grubbström (Grubba) //! @mapping //! @member Protocol "port" //! Representative open port for this URL. //! @member array(Protocol) "ports" //! Array of all open ports for this URL. //! @member Configuration "conf" //! Configuration that has registered the URL. //! @member string "path" //! Path segment of the URL. //! @member string "host" //! Hostname segment of the URL.
7302372011-07-14Henrik Grubbström (Grubba) //! @member array(string) "skipped" //! List of IP numbers not bound due to a corresponding //! ANY port already being open.
15d3b32011-06-15Henrik Grubbström (Grubba) //! @endmapping //! @endmapping
7302372011-07-14Henrik Grubbström (Grubba) mapping(string:mapping(string:Configuration|Protocol|string|array(Protocol)|array(string)))
654f432009-03-23Martin Stjernholm  urls = ([]);
15d3b32011-06-15Henrik Grubbström (Grubba) 
c5e0961999-10-04Per Hedbor array sorted_urls = ({});
dd7e661999-10-09Henrik Grubbström (Grubba) array(string) find_ips_for( string what )
b1fca01996-11-12Per Hedbor {
60bab72012-01-19Henrik Grubbström (Grubba)  if( what == "*" || lower_case(what) == "any" || has_value(what, "*") )
d67c162010-05-03Martin Jonsson  return ({
c86a472005-11-14Martin Stjernholm #if constant(__ROXEN_SUPPORTS_IPV6__)
0c12822005-03-10Henrik Grubbström (Grubba)  "::", #endif /* __ROXEN_SUPPORTS_IPV6__ */
d67c162010-05-03Martin Jonsson  0,
0c12822005-03-10Henrik Grubbström (Grubba)  }); // ANY
c5e0961999-10-04Per Hedbor 
15635b1999-10-10Per Hedbor  if( is_ip( what ) )
dd7e661999-10-09Henrik Grubbström (Grubba)  return ({ what });
98acd52009-07-21Martin Stjernholm  else if (has_prefix (what, "[") && what[-1] == ']') {
f999152005-03-30Henrik Grubbström (Grubba)  /* RFC 3986 3.2.2. Host * * host = IP-literal / IPv4address / reg-name * IP-literal = "[" ( IPv6address / IPvFuture ) "]" * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) * * IPv6address is as in RFC3513. */ return ({ what[1..sizeof(what)-2] }); } else if (has_suffix(lower_case(what), ".ipv6")) {
935e1b2005-02-23Henrik Grubbström (Grubba)  // draft-masinter-url-ipv6-00 3 // // a) replace every colon ":" with a "-" // b) append ".ipv6" to the end. return ({ replace(what[..sizeof(what)-6], "-", ":") }); }
c5e0961999-10-04Per Hedbor  array res = gethostbyname( what );
41a5c02003-10-22Henrik Grubbström (Grubba)  if( res && sizeof( res[1] ) )
92898c1999-10-10Marcus Comstedt  return Array.uniq(res[1]);
41a5c02003-10-22Henrik Grubbström (Grubba)  report_error(LOC_M(46, "Cannot possibly bind to %O, that host is " "unknown. Substituting with ANY")+"\n", what); return 0; // FAIL
c5e0961999-10-04Per Hedbor }
db4db52009-01-21Martin Stjernholm string normalize_url(string url, void|int port_match_form) //! Normalizes the given url to a short form. //! //! If @[port_match_form] is set, it normalizes to the form that is //! used for port matching, i.e. what //! @[roxen.Protocol.find_configuration_for_url] expects.
2d732e2011-08-30Henrik Grubbström (Grubba) //! //! @note //! Returns @expr{""@} for @[url]s that are incomplete.
c5e0961999-10-04Per Hedbor {
38c4892002-01-11Henrik Grubbström (Grubba)  if (!sizeof (url - " " - "\t")) return "";
21182a2001-10-05Per Hedbor  Standards.URI ui = Standards.URI(url);
db4db52009-01-21Martin Stjernholm  string host = ui->host; if (lower_case (host) == "any" || host == "::") host = "*";
f53ac52009-07-15Henrik Grubbström (Grubba)  else { // Note: zone_to_ascii() can throw errors on invalid hostnames. if (catch { // FIXME: Maybe Standards.URI should do this internally? host = lower_case(Standards.IDNA.zone_to_ascii (host)); }) { host = lower_case(host); } }
db4db52009-01-21Martin Stjernholm  if (has_value (host, ":")) if (string h = port_match_form ? Protocols.IPv6.normalize_addr_basic (host) : Protocols.IPv6.normalize_addr_short (host)) { ui->host = h; host = "[" + h + "]"; }
dc89282008-12-11Jonas Wallden  string protocol = ui->scheme;
db4db52009-01-21Martin Stjernholm  if (host == "" || !protocols[protocol])
dc89282008-12-11Jonas Wallden  return "";
db4db52009-01-21Martin Stjernholm  if (port_match_form) { int port = ui->port || protocols[protocol]->default_port; string path = ui->path; if (path) { if (has_suffix(path, "/")) path = path[..<1]; } else path = ""; return sprintf ("%s://%s:%d%s/", protocol, host, port, // If the path is set it's assumed to begin with a // "/", but not end with one. path); } else { ui->fragment = 0; return (string) ui; }
38c4892002-01-11Henrik Grubbström (Grubba) }
15d3b32011-06-15Henrik Grubbström (Grubba) //! Unregister an URL from a configuration. //! //! @seealso //! @[register_url()]
38c4892002-01-11Henrik Grubbström (Grubba) void unregister_url(string url, Configuration conf) { string ourl = url;
15d3b32011-06-15Henrik Grubbström (Grubba)  mapping(string:mixed) data = m_delete(urls, ourl); if (!data) return; // URL not registered.
8817ca2009-01-23Martin Stjernholm  if (!sizeof(url = normalize_url(url, 1))) return;
bc5c2a2000-08-23Per Hedbor 
8817ca2009-01-23Martin Stjernholm  report_debug ("Unregister %s%s.\n", normalize_url (ourl), conf ? sprintf (" for %O", conf->query_name()) : "");
0aee222000-08-15Martin Stjernholm 
15d3b32011-06-15Henrik Grubbström (Grubba)  mapping(string:mixed) shared_data = urls[url]; if (!shared_data) return; // Strange case, but URL not registered.
7302372011-07-14Henrik Grubbström (Grubba)  int was_any_ip; if (!data->skipped && data->port) { if (!data->port->ip || (data->port->ip == "::")) { was_any_ip = data->port->port; report_debug("Unregistering ANY port: %O:%d\n", data->port->ip, data->port->port); } }
60bab72012-01-19Henrik Grubbström (Grubba)  foreach(data->ports || ({}), Protocol port) {
15d3b32011-06-15Henrik Grubbström (Grubba)  shared_data->ports -= ({ port }); port->unref(url); m_delete(shared_data, "port"); }
60bab72012-01-19Henrik Grubbström (Grubba)  if (!sizeof(shared_data->ports || ({}))) {
15d3b32011-06-15Henrik Grubbström (Grubba)  m_delete(urls, url); } else if (!shared_data->port) { shared_data->port = shared_data->ports[0];
b1fca01996-11-12Per Hedbor  }
15d3b32011-06-15Henrik Grubbström (Grubba)  sort_urls();
7302372011-07-14Henrik Grubbström (Grubba)  if (was_any_ip) { foreach(urls; string url; mapping(string:mixed) url_info) { if (!url_info->skipped || !url_info->conf || (url_info->port && (url_info->port->port != was_any_ip))) { continue; } // Re-register the ports that may have bound to the removed ANY port. register_url(url, url_info->conf); } }
b1fca01996-11-12Per Hedbor }
934b3f2000-02-04Per Hedbor array all_ports( ) {
15d3b32011-06-15Henrik Grubbström (Grubba)  // FIXME: Consider using open_ports instead.
60bab72012-01-19Henrik Grubbström (Grubba)  return Array.uniq( (values( urls )->ports - ({0})) * ({}) )-({0});
934b3f2000-02-04Per Hedbor } Protocol find_port( string name ) { foreach( all_ports(), Protocol p ) if( p->get_key() == name ) return p; }
c5e0961999-10-04Per Hedbor void sort_urls()
1668b21998-04-23Henrik Grubbström (Grubba) {
c5e0961999-10-04Per Hedbor  sorted_urls = indices( urls ); sort( map( map( sorted_urls, strlen ), `-), sorted_urls );
1668b21998-04-23Henrik Grubbström (Grubba) }
15d3b32011-06-15Henrik Grubbström (Grubba) //! Register an URL for a configuration. //! //! @seealso //! @[unregister_url()]
d093992000-09-25Per Hedbor int register_url( string url, Configuration conf )
c5e0961999-10-04Per Hedbor {
03aa492000-08-23Per Hedbor  string ourl = url;
1e3cd52000-02-02Martin Stjernholm  if (!sizeof (url - " " - "\t")) return 1;
b1fca01996-11-12Per Hedbor 
21182a2001-10-05Per Hedbor  Standards.URI ui = Standards.URI(url); mapping opts = ([]); string a, b; foreach( (ui->fragment||"")/";", string x ) { sscanf( x, "%s=%s", a, b ); opts[a]=b; }
db4db52009-01-21Martin Stjernholm 
21182a2001-10-05Per Hedbor  if( (int)opts->nobind ) { report_warning(
98acd52009-07-21Martin Stjernholm  LOC_M(61,"Not binding the port %O - disabled in configuration.")+"\n",
dc89282008-12-11Jonas Wallden  (string) ui );
21182a2001-10-05Per Hedbor  return 0; }
1f6b0c2008-12-23Martin Stjernholm 
98acd52009-07-21Martin Stjernholm  string display_url = normalize_url (url, 0);
db4db52009-01-21Martin Stjernholm  url = normalize_url (url, 1);
2d732e2011-08-30Henrik Grubbström (Grubba)  if (url == "") return 1;
db4db52009-01-21Martin Stjernholm  ui = Standards.URI (url); string protocol = ui->scheme; string host = ui->host;
4d12712009-01-16Martin Stjernholm  if (host == "" || !protocols[protocol]) {
98acd52009-07-21Martin Stjernholm  report_error(LOC_M(19,"Bad URL %O for server %O.")+"\n",
db4db52009-01-21Martin Stjernholm  ourl, conf->query_name());
228fea2000-02-15Leif Stensson  }
b8fd5c2000-09-28Per Hedbor 
db4db52009-01-21Martin Stjernholm  int port = ui->port || protocols[protocol]->default_port;
4d12712009-01-16Martin Stjernholm 
db4db52009-01-21Martin Stjernholm  string path = ui->path; if (has_suffix(path, "/")) path = path[..<1]; if (path == "") path = 0;
c5e0961999-10-04Per Hedbor 
f034ab2001-04-17Per Hedbor  if( urls[ url ] )
c5e0961999-10-04Per Hedbor  {
f034ab2001-04-17Per Hedbor  if( !urls[ url ]->port ) m_delete( urls, url ); else if( urls[ url ]->conf )
c5e0961999-10-04Per Hedbor  {
f034ab2001-04-17Per Hedbor  if( urls[ url ]->conf != conf ) { report_error(LOC_M(20,
98acd52009-07-21Martin Stjernholm  "Cannot register URL %s - " "already registered by %s.")+"\n", display_url, urls[ url ]->conf->name);
f034ab2001-04-17Per Hedbor  return 0; }
15d3b32011-06-15Henrik Grubbström (Grubba)  // FIXME: Is this correct?
f034ab2001-04-17Per Hedbor  urls[ url ]->port->ref(url, urls[url]);
c5e0961999-10-04Per Hedbor  }
f034ab2001-04-17Per Hedbor  else urls[ url ]->port->unref( url );
c5e0961999-10-04Per Hedbor  }
3a53152008-09-17Martin Stjernholm  program prot;
dd7e661999-10-09Henrik Grubbström (Grubba) 
c5e0961999-10-04Per Hedbor  if( !( prot = protocols[ protocol ] ) ) {
98acd52009-07-21Martin Stjernholm  report_error(LOC_M(21, "Cannot register URL %s - " "cannot find the protocol %s.")+"\n", display_url, protocol);
c5e0961999-10-04Per Hedbor  return 0; }
7302372011-07-14Henrik Grubbström (Grubba)  // FIXME: Do we need to unref the old ports first in case of a reregister? urls[ ourl ] = ([ "conf":conf, "path":path, "hostname": host ]);
15d3b32011-06-15Henrik Grubbström (Grubba)  if (!urls[url]) { urls[ url ] = urls[ourl] + ([]); sorted_urls += ({ url }); // FIXME: Not exactly sorted... }
41a5c02003-10-22Henrik Grubbström (Grubba)  array(string)|int(-1..0) required_hosts;
dd7e661999-10-09Henrik Grubbström (Grubba) 
8fb5172000-05-27Per Hedbor  if (is_ip(host))
f526692000-05-16Henrik Grubbström (Grubba)  required_hosts = ({ host });
5ee2852004-01-14Henrik Grubbström (Grubba)  else if(!sizeof(required_hosts =
98acd52009-07-21Martin Stjernholm  filter(replace(opts->ip||"", " ","")/",", is_ip)) ) {
8fb5172000-05-27Per Hedbor  required_hosts = find_ips_for( host );
98acd52009-07-21Martin Stjernholm  if (!required_hosts) { // FIXME: Used to fallback to ANY. // Will this work with glob URLs? return 0; }
41a5c02003-10-22Henrik Grubbström (Grubba)  }
c5e0961999-10-04Per Hedbor 
654f432009-03-23Martin Stjernholm  mapping(string:mapping(int:Protocol)) m;
c5e0961999-10-04Per Hedbor  if( !( m = open_ports[ protocol ] ) )
935e1b2005-02-23Henrik Grubbström (Grubba)  // always add 'ANY' (0) and 'IPv6_ANY' (::) here, as empty mappings, // for speed reasons.
8fb5172000-05-27Per Hedbor  // There is now no need to check for both open_ports[prot][0] and // open_ports[prot][0][port], we can go directly to the latter // test.
c86a472005-11-14Martin Stjernholm  m = open_ports[ protocol ] = ([ 0:([]), "::":([]) ]);
8fb5172000-05-27Per Hedbor 
935e1b2005-02-23Henrik Grubbström (Grubba)  if (prot->supports_ipless ) { // Check if the ANY port is already open for this port, since this
8fb5172000-05-27Per Hedbor  // protocol supports IP-less virtual hosting, there is no need to
15d3b32011-06-15Henrik Grubbström (Grubba)  // open yet another port if it is, since that would most probably
935e1b2005-02-23Henrik Grubbström (Grubba)  // only conflict with the ANY port anyway. (this is true on most // OSes, it works on Solaris, but fails on linux) array(string) ipv6 = filter(required_hosts - ({ 0 }), has_value, ":"); array(string) ipv4 = required_hosts - ipv6; if (m[0][port] && sizeof(ipv4 - ({ 0 }))) { // We have a non-ANY IPv4 IP number.
7302372011-07-14Henrik Grubbström (Grubba)  // Keep track of the ips in case the ANY port is removed. urls[ourl]->skipped = ipv4;
935e1b2005-02-23Henrik Grubbström (Grubba)  ipv4 = ({ 0 }); }
c86a472005-11-14Martin Stjernholm #if constant(__ROXEN_SUPPORTS_IPV6__)
935e1b2005-02-23Henrik Grubbström (Grubba)  if (m["::"][port] && sizeof(ipv6 - ({ "::" }))) { // We have a non-ANY IPv6 IP number.
7302372011-07-14Henrik Grubbström (Grubba)  // Keep track of the ips in case the ANY port is removed. urls[ourl]->skipped += ipv6;
935e1b2005-02-23Henrik Grubbström (Grubba)  ipv6 = ({ "::" }); }
fc32ac2010-04-21Stefan Wallström  required_hosts = ipv6 + ipv4;
0c12822005-03-10Henrik Grubbström (Grubba) #else if (sizeof(ipv6)) { foreach(ipv6, string p) {
98acd52009-07-21Martin Stjernholm  report_warning(LOC_M(65, "Cannot open port %s for URL %s - " "IPv6 support disabled.\n"), p, display_url);
0c12822005-03-10Henrik Grubbström (Grubba)  } } required_hosts = ipv4; #endif /* __ROXEN_SUPPORTS_IPV6__ */
935e1b2005-02-23Henrik Grubbström (Grubba)  }
8fb5172000-05-27Per Hedbor 
dd7e661999-10-09Henrik Grubbström (Grubba)  int failures;
fc32ac2010-04-21Stefan Wallström  int opened_ipv6_any_port;
c5e0961999-10-04Per Hedbor 
38c5072000-02-28Per Hedbor  foreach(required_hosts, string required_host) {
dd7e661999-10-09Henrik Grubbström (Grubba)  if( m[ required_host ] && m[ required_host ][ port ] ) {
fc32ac2010-04-21Stefan Wallström  if (required_host == "::") opened_ipv6_any_port = 1;
a2ec272008-09-20Martin Stjernholm 
7c708f2000-02-05Henrik Grubbström (Grubba)  m[required_host][port]->ref(url, urls[url]);
38c5072000-02-28Per Hedbor 
7c708f2000-02-05Henrik Grubbström (Grubba)  urls[url]->port = m[required_host][port];
15d3b32011-06-15Henrik Grubbström (Grubba)  if (urls[url]->ports) { urls[url]->ports += ({ m[required_host][port] }); } else { urls[url]->ports = ({ m[required_host][port] }); }
65d2002000-09-25Per Hedbor  urls[ourl]->port = m[required_host][port];
96af3e2009-03-23Henrik Grubbström (Grubba)  if (urls[ourl]->ports) { urls[ourl]->ports += ({ m[required_host][port] }); } else { urls[ourl]->ports = ({ m[required_host][port] }); }
dd7e661999-10-09Henrik Grubbström (Grubba)  continue; /* No need to open a new port */ } if( !m[ required_host ] ) m[ required_host ] = ([ ]);
98acd52009-07-21Martin Stjernholm  Protocol prot_obj;
3a53152008-09-17Martin Stjernholm  if (mixed err = catch {
98acd52009-07-21Martin Stjernholm  prot_obj = m[ required_host ][ port ] =
24f9642007-05-03Martin Stjernholm  prot( port, required_host,
fc32ac2010-04-21Stefan Wallström  // Don't complain if binding IPv4 ANY fails with // EADDRINUSE after we've bound IPv6 ANY.
15d3b32011-06-15Henrik Grubbström (Grubba)  // Most systems seems to bind both IPv4 ANY and
fc32ac2010-04-21Stefan Wallström  // IPv6 ANY for "::" !required_host && opened_ipv6_any_port);
41a5c02003-10-22Henrik Grubbström (Grubba)  }) {
7c708f2000-02-05Henrik Grubbström (Grubba)  failures++;
98acd52009-07-21Martin Stjernholm #if 0
eecdf12005-02-23Henrik Grubbström (Grubba)  if (has_prefix(describe_error(err), "Invalid address") && required_host && has_value(required_host, ":")) {
8c46222005-02-23Henrik Grubbström (Grubba)  report_error(sprintf("Failed to initialize IPv6 port for URL %s"
98acd52009-07-21Martin Stjernholm  " (ip %s).\n", display_url, required_host)); } else #endif report_error(sprintf("Initializing the port handler for " "URL %s (ip %s) failed: %s\n", display_url,
eecdf12005-02-23Henrik Grubbström (Grubba)  required_host||"ANY",
98acd52009-07-21Martin Stjernholm #ifdef DEBUG describe_backtrace(err) #else describe_error (err) #endif ));
7c708f2000-02-05Henrik Grubbström (Grubba)  continue; }
abc59a2000-08-23Per Hedbor 
fc32ac2010-04-21Stefan Wallström  if (required_host == "::") opened_ipv6_any_port = 1;
24f9642007-05-03Martin Stjernholm 
3a53152008-09-17Martin Stjernholm  if (prot_obj->bound == -1) { // Got EADDRINUSE for the IPv6 case - see above. Just forget // about this one. m_delete (m[required_host], port); continue; }
abc59a2000-08-23Per Hedbor 
3a53152008-09-17Martin Stjernholm  urls[ url ]->port = prot_obj;
15d3b32011-06-15Henrik Grubbström (Grubba)  if (urls[url]->ports) { urls[url]->ports += ({ prot_obj }); } else { urls[url]->ports = ({ prot_obj }); }
3a53152008-09-17Martin Stjernholm  urls[ ourl ]->port = prot_obj;
96af3e2009-03-23Henrik Grubbström (Grubba)  if (urls[ourl]->ports) { urls[ourl]->ports += ({ prot_obj }); } else { urls[ourl]->ports = ({ prot_obj }); }
3a53152008-09-17Martin Stjernholm  prot_obj->ref(url, urls[url]);
03aa492000-08-23Per Hedbor 
3a53152008-09-17Martin Stjernholm  if( !prot_obj->bound )
03aa492000-08-23Per Hedbor  failures++;
dd7e661999-10-09Henrik Grubbström (Grubba)  }
abc59a2000-08-23Per Hedbor  if (failures == sizeof(required_hosts)) {
98acd52009-07-21Martin Stjernholm  report_error(LOC_M(23, "Failed to register URL %s for %O.")+"\n", display_url, conf->query_name());
c5e0961999-10-04Per Hedbor  return 0; } sort_urls();
1f6b0c2008-12-23Martin Stjernholm  // The following will show the punycoded version for IDN hostnames. // That is intentional, to make it clear what actually happens.
98acd52009-07-21Martin Stjernholm  report_notice(" "+LOC_S(3, "Registered URL %s for %O.")+"\n", display_url, conf->query_name() );
c5e0961999-10-04Per Hedbor  return 1; }
d093992000-09-25Per Hedbor Configuration find_configuration( string name )
45dc022000-08-16Martin Stjernholm //! Searches for a configuration with a name or fullname like the //! given string. See also get_configuration().
b1fca01996-11-12Per Hedbor {
b613482001-01-31Per Hedbor  // Optimization, in case the exact name is given... if( Configuration o = get_configuration( name ) ) return o;
9699bf1999-10-11Per Hedbor  name = replace( lower_case( replace(name,"-"," ") )-" ", "/", "-" );
d093992000-09-25Per Hedbor  foreach( configurations, Configuration o )
9699bf1999-10-11Per Hedbor  {
0666741999-12-07Henrik Grubbström (Grubba)  if( (lower_case( replace( replace(o->name, "-"," ") - " " , "/", "-" ) ) == name) || (lower_case( replace( replace(o->query_name(), "-", " ") - " " , "/", "-" ) ) == name) )
c5e0961999-10-04Per Hedbor  return o;
9699bf1999-10-11Per Hedbor  }
0666741999-12-07Henrik Grubbström (Grubba)  return 0;
b1fca01996-11-12Per Hedbor }
fc40392008-08-15Martin Stjernholm protected int last_hrtime = gethrtime(1)/100; protected int clock_sequence = random(0x4000); protected string hex_mac_address = String.string2hex(Crypto.Random.random_string (6)| "\1\0\0\0\0\0"); // Multicast bit.
27047d2004-05-03Henrik Grubbström (Grubba) // Generate an uuid string. string new_uuid_string() { int now = gethrtime(1)/100; if (now != last_hrtime) {
1c66df2004-05-03Henrik Grubbström (Grubba)  clock_sequence = random(0x4000);
27047d2004-05-03Henrik Grubbström (Grubba)  last_hrtime = now; }
1c66df2004-05-03Henrik Grubbström (Grubba)  int seq = clock_sequence++; // FIXME: Check if clock_sequence has wrapped during this @[now]. // Adjust @[now] with the number of 100ns intervals between // 1582-10-15 00:00:00.00 GMT and 1970-01-01 00:00:00.00 GMT.
27047d2004-05-03Henrik Grubbström (Grubba) #if 0 now -= Calendar.parse("%Y-%M-%D %h:%m:%s.%f %z", "1582-10-15 00:00:00.00 GMT")->unix_time() * 10000000; #else /* !0 */
1c66df2004-05-03Henrik Grubbström (Grubba)  now += 0x01b21dd213814000; // Same as above.
27047d2004-05-03Henrik Grubbström (Grubba) #endif /* 0 */ now &= 0x0fffffffffffffff; now |= 0x1000000000000000; // DCE version 1. clock_sequence &= 0x3fff;
ce6a732004-05-03Henrik Grubbström (Grubba)  clock_sequence |= 0x8000; // DCE variant of UUIDs.
27047d2004-05-03Henrik Grubbström (Grubba)  return sprintf("%08x-%04x-%04x-%04x-%s", now & 0xffffffff, (now >> 32) & 0xffff, (now >> 48) & 0xffff, clock_sequence,
0cae032004-05-03Henrik Grubbström (Grubba)  hex_mac_address);
27047d2004-05-03Henrik Grubbström (Grubba) }
c5e0961999-10-04Per Hedbor mapping(string:array(int)) error_log=([]);
ec2fe11997-06-09Henrik Grubbström (Grubba) 
a6ef1f2000-03-28Johan Sundström // Write a string to the administration interface error log and to stderr.
c60cae2000-02-02Johan Sundström void nwrite(string s, int|void perr, int|void errtype,
c821c31999-12-27Martin Stjernholm  object|void mod, object|void conf)
b1fca01996-11-12Per Hedbor {
add34e2000-08-17Per Hedbor  int log_time = time(1);
0fe69d2000-03-19Martin Nilsson  string reference = (mod ? Roxen.get_modname(mod) : conf && conf->name) || "";
c60cae2000-02-02Johan Sundström  string log_index = sprintf("%d,%s,%s", errtype, reference, s); if(!error_log[log_index]) error_log[log_index] = ({ log_time });
c5e0961999-10-04Per Hedbor  else
c60cae2000-02-02Johan Sundström  error_log[log_index] += ({ log_time });
e99fd41999-11-17Per Hedbor  if( mod ) {
040f882001-08-20Per Hedbor  if( mod->error_log ) mod->error_log[log_index] += ({ log_time });
e99fd41999-11-17Per Hedbor  } if( conf ) {
040f882001-08-20Per Hedbor  if( conf->error_log ) conf->error_log[log_index] += ({ log_time });
e99fd41999-11-17Per Hedbor  }
c60cae2000-02-02Johan Sundström  if(errtype >= 1)
81f8af1999-12-20Martin Nilsson  report_debug( s );
b1fca01996-11-12Per Hedbor }
bcf2c52015-12-01Henrik Grubbström (Grubba) protected BackgroundProcess hourly_maintenance_process;
943a602014-12-02Henrik Grubbström (Grubba)  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); } } }
5a10e22015-12-02Henrik Grubbström (Grubba) protected void patcher_report_notice(string msg, mixed ... args) { if (sizeof(args)) msg = sprintf(msg, @args);
f2b6812015-12-03Henrik Grubbström (Grubba)  report_notice(RoxenPatch.wash_output(msg));
5a10e22015-12-02Henrik Grubbström (Grubba) } protected void patcher_report_error(string msg, mixed ... args) { if (sizeof(args)) msg = sprintf(msg, @args);
f2b6812015-12-03Henrik Grubbström (Grubba)  report_error(RoxenPatch.wash_output(msg));
5a10e22015-12-02Henrik Grubbström (Grubba) }
e838242015-12-02Henrik Grubbström (Grubba) RoxenPatch.Patcher plib =
5a10e22015-12-02Henrik Grubbström (Grubba)  RoxenPatch.Patcher(patcher_report_notice, patcher_report_error, getcwd(), getenv("LOCALDIR"));
e838242015-12-02Henrik Grubbström (Grubba) 
bcf2c52015-12-01Henrik Grubbström (Grubba) protected void hourly_maintenance()
943a602014-12-02Henrik Grubbström (Grubba) {
bcf2c52015-12-01Henrik Grubbström (Grubba)  error_log_cleaner();
e838242015-12-02Henrik Grubbström (Grubba)  if (query("auto_fetch_rxps")) { plib->import_file_http(); }
bcf2c52015-12-01Henrik Grubbström (Grubba) } protected void start_hourly_maintenance() { if (hourly_maintenance_process) return;
943a602014-12-02Henrik Grubbström (Grubba) 
bcf2c52015-12-01Henrik Grubbström (Grubba)  // Start a background process that performs maintenance tasks every hour // (eg cleaning the error log). hourly_maintenance_process = BackgroundProcess(3600, hourly_maintenance);
943a602014-12-02Henrik Grubbström (Grubba) }
bcf2c52015-12-01Henrik Grubbström (Grubba) protected void stop_hourly_maintenance()
943a602014-12-02Henrik Grubbström (Grubba) {
bcf2c52015-12-01Henrik Grubbström (Grubba)  if (hourly_maintenance_process) { hourly_maintenance_process->stop(); hourly_maintenance_process = UNDEFINED;
943a602014-12-02Henrik Grubbström (Grubba)  } }
b1fca01996-11-12Per Hedbor // When was Roxen started?
2ff8461999-09-02Per Hedbor int boot_time =time(); int start_time =time();
b1fca01996-11-12Per Hedbor  string version() {
10089c2000-05-17Martin Nilsson #ifndef NSERIOUS
8552d92001-01-13Martin Nilsson  return query("default_ident")?real_version:query("ident");
daff902000-02-23Martin Nilsson #else multiset choices=(<>);
8552d92001-01-13Martin Nilsson  string version=query("default_ident")?real_version:query("ident");
0893642000-02-25Martin Nilsson  return version+", "+ ({ "Applier of Templates", "Beautifier of Layouts", "Conqueror of Comdex", "Deliverer of Documents", "Enhancer of Abilities", "Freer of Webmasters", "Generator of Logs", "Helper of Users", "Interpreter of Scripts", "Juggler of Java-code", "Keeper of Databases", "Locator of Keywords", "Manipulator of Data", "Negatiator of Protocols", "Operator of Sites", "Provider of Contents", "Quintessence of Quality", "Responder to Connections", "Server of Webs", "Translator of Texts", "Unifier of Interfaces", "Valet of Visitors", "Watcher for Requests", "Xylem of Services", "Yielder of Information", "Zenith of Extensibility" })[random(26)];
daff902000-02-23Martin Nilsson #endif
b1fca01996-11-12Per Hedbor }
81f8af1999-12-20Martin Nilsson public void log(mapping file, RequestID request_id)
b1fca01996-11-12Per Hedbor {
81f8af1999-12-20Martin Nilsson  if(!request_id->conf) return;
14179b1997-01-29Per Hedbor  request_id->conf->log(file, request_id);
b1fca01996-11-12Per Hedbor }
3e3bab2001-01-19Per Hedbor #if ROXEN_COMPAT < 2.2
81f8af1999-12-20Martin Nilsson // Support for unique user id's
3e3bab2001-01-19Per Hedbor private Stdio.File current_user_id_file;
b1fca01996-11-12Per Hedbor private int current_user_id_number, current_user_id_file_last_mod; private void restore_current_user_id_number() { if(!current_user_id_file) current_user_id_file = open(configuration_dir + "LASTUSER~", "rwc"); if(!current_user_id_file) { call_out(restore_current_user_id_number, 2); return;
81f8af1999-12-20Martin Nilsson  }
b1fca01996-11-12Per Hedbor  current_user_id_number = (int)current_user_id_file->read(100); current_user_id_file_last_mod = current_user_id_file->stat()[2];
81f8af1999-12-20Martin Nilsson  report_debug("Restoring unique user ID information. (" + current_user_id_number + ")\n");
3235691998-03-26Per Hedbor #ifdef FD_DEBUG
434bac2000-07-14Andreas Lange  mark_fd(current_user_id_file->query_fd(), "Unique user ID logfile.\n");
3235691998-03-26Per Hedbor #endif
b1fca01996-11-12Per Hedbor } int increase_id() { if(!current_user_id_file) { restore_current_user_id_number();
add34e2000-08-17Per Hedbor  return current_user_id_number+time(1);
b1fca01996-11-12Per Hedbor  } if(current_user_id_file->stat()[2] != current_user_id_file_last_mod) restore_current_user_id_number(); current_user_id_number++; current_user_id_file->seek(0); current_user_id_file->write((string)current_user_id_number); current_user_id_file_last_mod = current_user_id_file->stat()[2]; return current_user_id_number; }
1138fd2001-05-29Martin Nilsson #endif // ROXEN_COMPAT < 2.2
b1fca01996-11-12Per Hedbor 
38b81e2001-01-06Martin Nilsson private int unique_id_counter; string create_unique_id() {
4e32072009-01-09Stephen R. van den Berg  return String.string2hex(Crypto.MD5.hash(query("server_salt") + start_time + "|" + (unique_id_counter++) + "|" + time(1)));
38b81e2001-01-06Martin Nilsson }
c8ee712000-09-09Andreas Lange 
dbfe9d2000-04-13Per Hedbor #ifndef __NT__
fc40392008-08-15Martin Stjernholm protected int abs_started; protected int handlers_alive;
679c9e2006-11-30Henrik Grubbström (Grubba) 
fc40392008-08-15Martin Stjernholm protected void low_engage_abs()
679c9e2006-11-30Henrik Grubbström (Grubba) { report_debug("**** %s: ABS exiting roxen!\n\n", ctime(time()) - "\n"); _exit(1); // It might not quit correctly otherwise, if it's
622a242015-12-03Henrik Grubbström (Grubba)  // locked up. Note that this also inhibits the delay // caused by the possible automatic installation of // any pending patches.
679c9e2006-11-30Henrik Grubbström (Grubba) }
fc40392008-08-15Martin Stjernholm protected void engage_abs(int n)
679c9e2006-11-30Henrik Grubbström (Grubba) { if (!query("abs_engage")) { abs_started = 0; report_debug("Anti-Block System Disabled.\n"); return; } report_debug("**** %s: ABS engaged!\n"
082b5e2007-08-14Henrik Grubbström (Grubba)  "Waited more than %d minute(s).\n",
679c9e2006-11-30Henrik Grubbström (Grubba)  ctime(time()) - "\n", query("abs_timeout")); // Paranoia exit in case describe_all_threads below hangs. signal(signum("SIGALRM"), low_engage_abs); int t = alarm(20);
9773512011-11-23Henrik Grubbström (Grubba)  report_debug("\nTrying to dump backlog: \n"); if (mixed err = catch { // Catch for paranoia reasons. describe_all_threads(); })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
082b5e2007-08-14Henrik Grubbström (Grubba) #ifdef THREADS
9773512011-11-23Henrik Grubbström (Grubba)  report_debug("\nHandler queue:\n");
d307c72008-08-06Martin Stjernholm  if (mixed err = catch {
9773512011-11-23Henrik Grubbström (Grubba)  t = alarm(20); // Restart the timeout timer.
9fe96a2007-08-14Henrik Grubbström (Grubba)  array(mixed) queue = handle_queue->buffer[handle_queue->r_ptr..];
d601192007-08-14Henrik Grubbström (Grubba)  foreach(queue, mixed v) {
082b5e2007-08-14Henrik Grubbström (Grubba)  if (!v) continue; if (!arrayp(v)) { report_debug(" *** Strange entry: %O ***\n", v); } else {
9fe96a2007-08-14Henrik Grubbström (Grubba)  report_debug(" %{%O, %}\n", v/({}));
082b5e2007-08-14Henrik Grubbström (Grubba)  } }
d307c72008-08-06Martin Stjernholm  })
44818f2012-02-18Martin Stjernholm  master()->handle_error (err);
082b5e2007-08-14Henrik Grubbström (Grubba) #endif
679c9e2006-11-30Henrik Grubbström (Grubba)  low_engage_abs(); }
6ca8f61998-10-13Per Hedbor 
81f8af1999-12-20Martin Nilsson void restart_if_stuck (int force)
622e7a2001-12-04Martin Stjernholm //! @note //! Must be called from the backend thread due to Linux peculiarities.
6ca8f61998-10-13Per Hedbor {
ee8b201998-07-13David Hedbor  remove_call_out(restart_if_stuck);
8552d92001-01-13Martin Nilsson  if (!(query("abs_engage") || force))
edc9af1998-07-11David Hedbor  return;
81f8af1999-12-20Martin Nilsson  if(!abs_started)
6ca8f61998-10-13Per Hedbor  {
ee8b201998-07-13David Hedbor  abs_started = 1;
082b5e2007-08-14Henrik Grubbström (Grubba)  handlers_alive = time();
81f8af1999-12-20Martin Nilsson  report_debug("Anti-Block System Enabled.\n");
ee8b201998-07-13David Hedbor  } call_out (restart_if_stuck,10);
e0e50e2001-11-23Anders Johansson // werror("call_out_info %O\n", filter(call_out_info(), lambda(array a) { // return a[2] == restart_if_stuck; }));
679c9e2006-11-30Henrik Grubbström (Grubba)  signal(signum("SIGALRM"), engage_abs);
e0e50e2001-11-23Anders Johansson  int t = alarm (60*query("abs_timeout")+20); // werror("alarm: %d seconds left, set to %d\n", t, 60*query("abs_timeout")+20);
679c9e2006-11-30Henrik Grubbström (Grubba)  if ((time(1) - handlers_alive) > 60*query("abs_timeout")) { // The handler installed below hasn't run. report_debug("**** %s: ABS: Handlers are dead!\n", ctime(time()) - "\n"); engage_abs(0); }
082b5e2007-08-14Henrik Grubbström (Grubba)  handle(lambda() { handlers_alive = time(); });
edc9af1998-07-11David Hedbor }
dbfe9d2000-04-13Per Hedbor #endif
edc9af1998-07-11David Hedbor 
2cb5142006-08-21Henrik Grubbström (Grubba) #if constant(ROXEN_MYSQL_SUPPORTS_UNICODE) // NOTE: We need to mark binary data as binary in case // the Mysql character_set_connection has been // set to anything other than "latin1". #define MYSQL__BINARY "_binary" #else #define MYSQL__BINARY "" #endif
4ed0992003-01-15Henrik Grubbström (Grubba) function(string:Sql.Sql) dbm_cached_get;
753a831999-08-30Per Hedbor 
067ef92011-12-22Marcus Wellhardh // Threshold in seconds for updating atime records Currently set to // one day. #define ATIME_THRESHOLD 60*60*24
2ff8461999-09-02Per Hedbor class ImageCache
93dd0e2000-07-27Johan Sundström //! The image cache handles the behind-the-scenes caching and //! regeneration features of graphics generated on the fly. Besides //! being a cache, however, it serves a wide variety of other //! interesting image conversion/manipulation functions as well.
2ff8461999-09-02Per Hedbor {
0b41772001-09-06Per Hedbor #define QUERY(X,Y...) get_db()->query(X,Y)
2ff8461999-09-02Per Hedbor  string name; string dir; function draw_function;
e7ac492003-04-06Anders Johansson  mapping(string:array(mapping|int)) meta_cache = ([]);
0f8b2f1999-03-27Henrik Grubbström (Grubba) 
23b7ec2001-08-27Per Hedbor  string documentation(void|string tag_n_args) {
29fac12001-01-04Martin Nilsson  string doc = Stdio.read_file("base_server/image_cache.xml"); if(!doc) return "";
6f76f12000-06-01Martin Nilsson  if(!tag_n_args)
4a45f12001-03-13Martin Nilsson  return Parser.HTML()->add_container("ex", "")-> add_quote_tag("!--","","--")->finish(doc)->read();
6f76f12000-06-01Martin Nilsson  return replace(doc, "###", tag_n_args); }
34d3fa1999-04-22Per Hedbor 
fc40392008-08-15Martin Stjernholm  protected mapping meta_cache_insert( string i, mapping what )
2ff8461999-09-02Per Hedbor  {
23b7ec2001-08-27Per Hedbor #ifdef ARG_CACHE_DEBUG werror("MD insert for %O: %O\n", i, what ); #endif if( sizeof( meta_cache ) > 1000 )
e7ac492003-04-06Anders Johansson  sync_meta(); if( what ) { meta_cache[i] = ({ what, 0 }); return what; }
4afcd42001-08-09Per Hedbor  else m_delete( meta_cache, i ); return 0;
2ff8461999-09-02Per Hedbor  }
81f8af1999-12-20Martin Nilsson 
fc40392008-08-15Martin Stjernholm  protected mixed frommapp( mapping what )
753a831999-08-30Per Hedbor  {
482ac32001-03-16Per Hedbor  if( !what ) error( "Got invalid argcache-entry\n" );
ede1352000-08-12Martin Stjernholm  if( !zero_type(what[""]) ) return what[""];
2ff8461999-09-02Per Hedbor  return what;
753a831999-08-30Per Hedbor  }
fc40392008-08-15Martin Stjernholm  protected void|mapping draw( string name, RequestID id )
2ff8461999-09-02Per Hedbor  {
23b7ec2001-08-27Per Hedbor #ifdef ARG_CACHE_DEBUG
6f339f2009-11-24Henrik Grubbström (Grubba)  werror("draw: %O id: %O\n", name, id );
23b7ec2001-08-27Per Hedbor #endif
4afcd42001-08-09Per Hedbor  mixed args = Array.map( Array.map( name/"$", argcache->lookup,
482ac32001-03-16Per Hedbor  id->client ), frommapp);
aea1652001-06-26Per Hedbor 
45063f2009-03-20Henrik Grubbström (Grubba)  id->cache_status["icachedraw"] = 1;
2ff8461999-09-02Per Hedbor  mapping meta; string data;
6f03962001-02-09Per Hedbor  array guides;
6f339f2009-11-24Henrik Grubbström (Grubba) #ifdef ARG_CACHE_DEBUG werror("draw args: %O\n", args ); #endif
2ff8461999-09-02Per Hedbor  mixed reply = draw_function( @copy_value(args), id );
b1fca01996-11-12Per Hedbor 
e96b502004-06-11Henrik Grubbström (Grubba)  if( !reply ) { #ifdef ARG_CACHE_DEBUG werror("%O(%{%O, %}%O) ==> 0\n", draw_function, args, id); #endif
aea1652001-06-26Per Hedbor  return;
e96b502004-06-11Henrik Grubbström (Grubba)  }
aea1652001-06-26Per Hedbor 
2ff8461999-09-02Per Hedbor  if( arrayp( args ) ) args = args[0];
b1fca01996-11-12Per Hedbor 
37ecea2000-02-21Per Hedbor  if( arrayp( reply ) ) // layers.
6f03962001-02-09Per Hedbor  { guides = reply->get_misc_value( "image_guides" )-({}); if( sizeof( guides ) ) guides = guides[0];
37ecea2000-02-21Per Hedbor  reply = Image.lay( reply );
6f03962001-02-09Per Hedbor  }
37ecea2000-02-21Per Hedbor  if( objectp( reply ) && reply->image ) // layer. {
6f03962001-02-09Per Hedbor  if( !guides ) guides = reply->get_misc_value( "image_guides" );
37ecea2000-02-21Per Hedbor  reply = ([ "img":reply->image(), "alpha":reply->alpha(), ]); }
b1fca01996-11-12Per Hedbor 
2ff8461999-09-02Per Hedbor  if( objectp( reply ) || (mappingp(reply) && reply->img) )
b1fca01996-11-12Per Hedbor  {
2ff8461999-09-02Per Hedbor  int quant = (int)args->quant; string format = lower_case(args->format || "gif"); string dither = args->dither; Image.Colortable ct;
e9d7c51999-11-02Per Hedbor  Image.Color.Color bgcolor;
3e3bab2001-01-19Per Hedbor  Image.Image alpha;
81f8af1999-12-20Martin Nilsson  int true_alpha;
b1fca01996-11-12Per Hedbor 
2ff8461999-09-02Per Hedbor  if( args->fs || dither == "fs" ) dither = "floyd_steinberg";
b1fca01996-11-12Per Hedbor 
6f03962001-02-09Per Hedbor  if( dither == "random" )
2ff8461999-09-02Per Hedbor  dither = "random_dither";
b1fca01996-11-12Per Hedbor 
81f8af1999-12-20Martin Nilsson  if( format == "jpg" )
2ff8461999-09-02Per Hedbor  format = "jpeg";
b1fca01996-11-12Per Hedbor 
112b1c2000-02-02Per Hedbor  if( dither ) dither = replace( dither, "-", "_" );
2ff8461999-09-02Per Hedbor  if(mappingp(reply)) { alpha = reply->alpha; reply = reply->img;
93ebdd1999-03-11Martin Stjernholm  }
81f8af1999-12-20Martin Nilsson 
c3a53d1999-06-25Per Hedbor  if( args["true-alpha"] ) true_alpha = 1;
e7e6031999-11-05Per Hedbor  if( args["background"] || args["background-color"])
6f03962001-02-09Per Hedbor  bgcolor = Image.Color( args["background"]||args["background-color"] );
e9d7c51999-11-02Per Hedbor 
b79be21999-06-11Per Hedbor  if( args["opaque-value"] ) {
6f03962001-02-09Per Hedbor  if( !bgcolor ) true_alpha = 1;
b79be21999-06-11Per Hedbor  int ov = (int)(((float)args["opaque-value"])*2.55); if( ov < 0 ) ov = 0; else if( ov > 255 ) ov = 255; if( alpha )
3564842000-09-15Per Hedbor  alpha *= ov;
b79be21999-06-11Per Hedbor  else
2537c32000-02-14Per Hedbor  alpha = Image.Image( reply->xsize(), reply->ysize(), ov,ov,ov );
b79be21999-06-11Per Hedbor  }
c526921999-05-18Per Hedbor 
91a1562000-04-11Per Hedbor  if( args->gamma ) reply = reply->gamma( (float)args->gamma ); if( bgcolor && alpha && !true_alpha ) { reply = Image.Image( reply->xsize(), reply->ysize(), bgcolor ) ->paste_mask( reply, alpha ); alpha = alpha->threshold( 4 ); }
6f03962001-02-09Per Hedbor  int x0, y0, x1=reply->xsize(), y1=reply->ysize(), xc, yc;
3cbd0d2000-01-31Per Hedbor  if( args["x-offset"] || args["xoffset"] ) x0 = (int)(args["x-offset"]||args["xoffset"]); if( args["y-offset"] || args["yoffset"] ) y0 = (int)(args["y-offset"]||args["yoffset"]);
6f03962001-02-09Per Hedbor  if( args["width"] || args["x-size"] ) x1 = (int)(args["x-size"]||args["width"]); if( args["height"] || args["y-size"] ) y1 = (int)(args["y-size"]||args["height"]);
3cbd0d2000-01-31Per Hedbor 
6f03962001-02-09Per Hedbor  array xguides, yguides;
83f54a2003-01-16Martin Stjernholm  function sort_guides = lambda()
6f03962001-02-09Per Hedbor  { xguides = ({}); yguides = ({}); if( guides ) { foreach( guides, object g ) if( g->pos > 0 ) if( g->vertical ) { if( g->pos < reply->xsize() ) xguides += ({ g->pos }); } else if( g->pos < reply->ysize() ) yguides += ({ g->pos }); sort( xguides ); sort( yguides ); } };
3cbd0d2000-01-31Per Hedbor  if( args->crop ) {
6f03962001-02-09Per Hedbor  int gx=1, gy=1, gx2, gy2; if( sscanf( args["guides-index"]||"", "%d,%d", gx, gy ) == 2 ) { gx2 = gx+1; gy2 = gy+1; sscanf( args["guides-index"]||"", "%d,%d-%d,%d", gx, gy, gx2, gy2 ); } /* No, I did not forget the break statements. */ switch( args->crop ) { case "guides-cross": sort_guides(); if( sizeof(xguides) && sizeof(yguides) ) { xc = xguides[ min(sizeof(xguides),gx) - 1 ]; yc = yguides[ min(sizeof(yguides),gy) - 1 ]; break; } guides=0; case "guides-region": sort_guides(); if( (sizeof(xguides)>1) && (sizeof(yguides)>1) ) { gx = min(sizeof(xguides)-1,gx) - 1; gy = min(sizeof(yguides)-1,gy) - 1; gx2 = min(sizeof(xguides),gx2) - 1; gy2 = min(sizeof(yguides),gy2) - 1; x0 = xguides[gx]; x1 = xguides[gx2] - x0; y0 = yguides[gy]; y1 = yguides[gy2] - y0; break; } default: if( sscanf( args->crop, "%d,%d-%d,%d", x0, y0, x1, y1 ) == 4) { x1 -= x0; y1 -= y0; }
b0e2f82001-11-28Anders Johansson  break;
6f03962001-02-09Per Hedbor  case "auto": [ x0, y0, x1, y1 ] = reply->find_autocrop();
de130c2004-01-21Jonas Wallden  x1 = x1 - x0 + 1; y1 = y1 - y0 + 1;
6f03962001-02-09Per Hedbor  }
3cbd0d2000-01-31Per Hedbor  }
83f54a2003-01-16Martin Stjernholm  sort_guides = 0; // To avoid garbage.
3cbd0d2000-01-31Per Hedbor 
6f03962001-02-09Per Hedbor #define SCALEI 1 #define SCALEF 2
482ac32001-03-16Per Hedbor #define SCALEA 4 #define CROP 8
6f03962001-02-09Per Hedbor 
83f54a2003-01-16Martin Stjernholm  function do_scale_and_crop = lambda ( int x0, int y0, int x1, int y1, int|float w, int|float h, int type )
3cbd0d2000-01-31Per Hedbor  {
6f03962001-02-09Per Hedbor  if( (type & CROP) && x1 && y1 && ((x1 != reply->xsize()) || (y1 != reply->ysize()) || x0 || y0 ) ) { reply = reply->copy( x0, y0, x0+x1-1, y0+y1-1, (bgcolor?bgcolor->rgb():0) ); if( alpha ) alpha = alpha->copy( x0, y0, x0+x1-1,y0+y1-1, Image.Color.black); }
3cbd0d2000-01-31Per Hedbor 
6f03962001-02-09Per Hedbor  if( type & SCALEI ) { if( xc || yc ) { if( h && !w ) w = (reply->xsize() * h) / reply->ysize(); if( w && !h ) h = (reply->ysize() * w) / reply->xsize(); x0 = max( xc - w/2, 0 ); y0 = max( yc - h/2, 0 ); x1 = w; y1 = h; if( x0 + x1 > reply->xsize() ) { x0 = reply->xsize()-w; if( x0 < 0 ) { x0 = 0; x1 = reply->xsize(); } } if( y0 + y1 > reply->ysize() ) { y0 = reply->ysize()-h; if( y0 < 0 ) { y0 = 0; y1 = reply->ysize(); } } reply = reply->copy( x0, y0, x0+x1-1, y0+y1-1, (bgcolor?bgcolor->rgb():0) ); if( alpha ) alpha = alpha->copy( x0, y0, x0+x1-1,y0+y1-1, Image.Color.black); } }
482ac32001-03-16Per Hedbor  if( (type & SCALEF) && (w != 1.0) )
6f03962001-02-09Per Hedbor  { reply = reply->scale( w ); if( alpha ) alpha = alpha->scale( w ); }
482ac32001-03-16Per Hedbor  else if( (type & SCALEA) && ((reply->xsize() != w) || (reply->ysize() != h)) )
6f03962001-02-09Per Hedbor  {
482ac32001-03-16Per Hedbor  reply = reply->scale( w,h ); if( alpha ) alpha = alpha->scale( w,h ); } else if( (type & SCALEI) && ((reply->xsize() != w) || (reply->ysize() != h)) ) {
d228c72001-03-17Per Hedbor  if( w && h ) {
f78ca42004-03-09Stephen R. van den Berg  if( (w * (float)reply->ysize()) < (h * (float)reply->xsize()) )
d228c72001-03-17Per Hedbor  h = 0; else w = 0; }
e0dda02001-04-17Per Hedbor  w = min( w, reply->xsize() ); h = min( h, reply->ysize() );
6f03962001-02-09Per Hedbor  reply = reply->scale( w,h ); if( alpha ) alpha = alpha->scale( w,h ); } };
fa91d92005-01-17Jonas Wallden  if( sizeof((string) (args->scale || "")) )
c526921999-05-18Per Hedbor  { int x, y; if( sscanf( args->scale, "%d,%d", x, y ) == 2)
482ac32001-03-16Per Hedbor  do_scale_and_crop( x0, y0, x1, y1, x, y, SCALEA|CROP );
3255b11999-05-19Per Hedbor  else if( (float)args->scale < 3.0)
6f03962001-02-09Per Hedbor  do_scale_and_crop( x0, y0, x1, y1, ((float)args->scale), ((float)args->scale), SCALEF|CROP );
c526921999-05-18Per Hedbor  }
6f03962001-02-09Per Hedbor  else
fa91d92005-01-17Jonas Wallden  if( sizeof( (string) (args->maxwidth || args->maxheight || args["max-width"] || args["max-height"] || "")) )
c526921999-05-18Per Hedbor  {
6f03962001-02-09Per Hedbor  int x = (int)args->maxwidth|| (int)args["max-width"];
e7e6031999-11-05Per Hedbor  int y = (int)args->maxheight||(int)args["max-height"];
6f03962001-02-09Per Hedbor  do_scale_and_crop( x0, y0, x1, y1, x, y, SCALEI|CROP );
c526921999-05-18Per Hedbor  }
6f03962001-02-09Per Hedbor  else do_scale_and_crop( x0, y0, x1, y1, 0, 0, CROP );
83f54a2003-01-16Martin Stjernholm  do_scale_and_crop = 0; // To avoid garbage.
c526921999-05-18Per Hedbor 
6f03962001-02-09Per Hedbor  if( args["span-width"] || args["span-height"] )
d593702001-01-23Anders Johansson  { int width = (int)args["span-width"]; int height = (int)args["span-height"];
6f03962001-02-09Per Hedbor 
d593702001-01-23Anders Johansson  if( (width && reply->xsize() > width) || (height && reply->ysize() > height) ) { if( (width && height && (reply->xsize() / (float)width > reply->ysize() / (float)height)) || !height ) { reply = reply->scale( width, 0 ); if( alpha ) alpha = alpha->scale( width, 0 ); } else if( height ) { reply = reply->scale( 0, height ); if( alpha ) alpha = alpha->scale( 0, height ); } } int x1,x2,y1,y2; if( width ) { x1 = -((width - reply->xsize()) / 2); x2 = x1 + width - 1; } if( height ) { y1 = -((height - reply->ysize()) / 2); y2 = y1 + height - 1; } if( width && height ) { reply = reply->copy(x1,y1,x2,y2,(bgcolor?bgcolor->rgb():0)); if( alpha ) alpha = alpha->copy(x1,y1,x2,y2); } else if( width ) { reply = reply->copy(x1,0,x2,reply->ysize(),(bgcolor?bgcolor->rgb():0)); if ( alpha ) alpha = alpha->copy(x1,0,x2,alpha->ysize()); } else { reply = reply->copy(0,y1,reply->xsize(),y2,(bgcolor?bgcolor->rgb():0)); if( alpha ) alpha = alpha->copy(0,y1,alpha->xsize(),y2); } }
112b1c2000-02-02Per Hedbor  if( args["rotate-cw"] || args["rotate-ccw"]) { float degree = (float)(args["rotate-cw"] || args["rotate-ccw"]);
66868a2000-04-15Per Hedbor  switch( args["rotate-unit"] && args["rotate-unit"][0..0] )
112b1c2000-02-02Per Hedbor  {
9792c72000-05-16Henrik Grubbström (Grubba)  case "r": degree = (degree / (2*3.1415)) * 360; break;
f526692000-05-16Henrik Grubbström (Grubba)  case "d": break;
66868a2000-04-15Per Hedbor  case "n": degree = (degree / 400) * 360; break; case "p": degree = (degree / 1.0) * 360; break;
112b1c2000-02-02Per Hedbor  }
66868a2000-04-15Per Hedbor  if( args["rotate-cw"] )
112b1c2000-02-02Per Hedbor  degree = -degree;
f526692000-05-16Henrik Grubbström (Grubba)  if(!alpha)
66868a2000-04-15Per Hedbor  alpha = reply->copy()->clear(255,255,255); reply = reply->rotate_expand( degree );
f526692000-05-16Henrik Grubbström (Grubba)  alpha = alpha->rotate( degree, 0,0,0 );
112b1c2000-02-02Per Hedbor  } if( args["mirror-x"] ) { if( alpha ) alpha = alpha->mirrorx(); reply = reply->mirrorx(); } if( args["mirror-y"] ) { if( alpha ) alpha = alpha->mirrory(); reply = reply->mirrory(); }
91a1562000-04-11Per Hedbor  if( bgcolor && alpha && !true_alpha )
e7e6031999-11-05Per Hedbor  { reply = Image.Image( reply->xsize(), reply->ysize(), bgcolor ) ->paste_mask( reply, alpha ); }
91a1562000-04-11Per Hedbor  if( args["cs-rgb-hsv"] )reply = reply->rgb_to_hsv(); if( args["cs-grey"] ) reply = reply->grey(); if( args["cs-invert"] ) reply = reply->invert(); if( args["cs-hsv-rgb"] )reply = reply->hsv_to_rgb(); if( !true_alpha && alpha ) alpha = alpha->threshold( 4 );
69a8691999-05-18Per Hedbor  if( quant || (format=="gif") ) {
641a882000-06-01Martin Nilsson  int ncols = quant; if( format=="gif" ) { ncols = ncols||id->misc->defquant||32; if( ncols > 254 ) ncols = 254; }
000c781999-05-25Per Hedbor  ct = Image.Colortable( reply, ncols );
69a8691999-05-18Per Hedbor  if( dither )
23264c2000-10-17Per Hedbor  { if( dither == "random" ) dither = "random_grey";
000c781999-05-25Per Hedbor  if( ct[ dither ] ) ct[ dither ]();
69a8691999-05-18Per Hedbor  else ct->ordered();
23264c2000-10-17Per Hedbor  }
69a8691999-05-18Per Hedbor  } mapping enc_args = ([]); if( ct ) enc_args->colortable = ct;
a1c8692000-09-12Per Hedbor 
69a8691999-05-18Per Hedbor  if( alpha ) enc_args->alpha = alpha;
c526921999-05-18Per Hedbor  foreach( glob( "*-*", indices(args)), string n ) if(sscanf(n, "%*[^-]-%s", string opt ) == 2)
a1c8692000-09-12Per Hedbor  if( opt != "alpha" ) enc_args[opt] = (int)args[n];
c526921999-05-18Per Hedbor 
69a8691999-05-18Per Hedbor  switch(format) {
34fc592001-03-01Per Hedbor  case "wbf": format = "wbmp"; case "wbmp":
387fa62001-03-05Per Hedbor  Image.Colortable bw=Image.Colortable( ({ ({ 0,0,0 }), ({ 255,255,255 }) }) ); bw->floyd_steinberg(); data = Image.WBF.encode( bw->map( reply ), enc_args );
34fc592001-03-01Per Hedbor  break;
69a8691999-05-18Per Hedbor  case "gif":
f04b922000-09-13Jonas Wallden #if constant(Image.GIF) && constant(Image.GIF.encode)
c3a53d1999-06-25Per Hedbor  if( alpha && true_alpha )
b79be21999-06-11Per Hedbor  {
91a1562000-04-11Per Hedbor  Image.Colortable bw=Image.Colortable( ({ ({ 0,0,0 }), ({ 255,255,255 }) }) );
e9d7c51999-11-02Per Hedbor  bw->floyd_steinberg(); alpha = bw->map( alpha );
b79be21999-06-11Per Hedbor  }
000c781999-05-25Per Hedbor  if( catch { if( alpha ) data = Image.GIF.encode_trans( reply, ct, alpha ); else data = Image.GIF.encode( reply, ct ); }) data = Image.GIF.encode( reply );
23264c2000-10-17Per Hedbor  break;
f04b922000-09-13Jonas Wallden #else
23264c2000-10-17Per Hedbor  // Fall-through when there is no GIF encoder available -- // use PNG with a colortable instead. format = "png";
f04b922000-09-13Jonas Wallden #endif
e9d7c51999-11-02Per Hedbor 
69a8691999-05-18Per Hedbor  case "png":
e9d7c51999-11-02Per Hedbor  if( ct ) enc_args->palette = ct;
69a8691999-05-18Per Hedbor  m_delete( enc_args, "colortable" );
36a6ad2000-09-15Per Hedbor  if( !(args["png-use-alpha"] || args["true-alpha"]) ) m_delete( enc_args, "alpha" ); else if( enc_args->alpha ) // PNG encoder doesn't handle alpha and palette simultaneously // which is rather sad, since that's the only thing 100% supported // by all common browsers. m_delete( enc_args, "palette"); else
f04b922000-09-13Jonas Wallden  m_delete( enc_args, "alpha" );
81f8af1999-12-20Martin Nilsson 
69a8691999-05-18Per Hedbor  default:
23264c2000-10-17Per Hedbor  if(!Image[upper_case( format )] || !Image[upper_case( format )]->encode )
34fc592001-03-01Per Hedbor  error("Image format "+format+" not supported\n");
f04b922000-09-13Jonas Wallden  data = Image[upper_case( format )]->encode( reply, enc_args );
69a8691999-05-18Per Hedbor  }
81f8af1999-12-20Martin Nilsson  meta = ([
69a8691999-05-18Per Hedbor  "xsize":reply->xsize(), "ysize":reply->ysize(),
34fc592001-03-01Per Hedbor  "type":(format == "wbmp" ? "image/vnd.wap.wbmp" : "image/"+format ),
69a8691999-05-18Per Hedbor  ]);
c526921999-05-18Per Hedbor  }
81f8af1999-12-20Martin Nilsson  else if( mappingp(reply) )
69a8691999-05-18Per Hedbor  {
6a613a2002-06-17Anders Johansson  // This could be an error from get_file() if(reply->error) return reply;
69a8691999-05-18Per Hedbor  meta = reply->meta; data = reply->data; if( !meta || !data ) error("Invalid reply mapping.\n"
29fac12001-01-04Martin Nilsson  "Expected ([ \"meta\": ([metadata]), \"data\":\"data\" ])\n" "Got %O\n", reply);
69a8691999-05-18Per Hedbor  }
23b7ec2001-08-27Per Hedbor #ifdef ARG_CACHE_DEBUG werror("draw %O done\n", name ); #endif
31885f2001-01-02Per Hedbor  // Avoid throwing and error if the same image is rendered twice.
e96b502004-06-11Henrik Grubbström (Grubba)  mixed err = catch(store_data( name, data, meta )); #ifdef ARG_CACHE_DEBUG if (err) { werror("store_data failed with:\n" "%s\n", describe_backtrace(err)); } #endif
69a8691999-05-18Per Hedbor  }
fc40392008-08-15Martin Stjernholm  protected void store_data( string id, string data, mapping meta )
69a8691999-05-18Per Hedbor  {
55fc492000-12-30Per Hedbor  if(!stringp(data)) return;
23b7ec2001-08-27Per Hedbor #ifdef ARG_CACHE_DEBUG werror("store %O (%d bytes)\n", id, strlen(data) ); #endif
29fac12001-01-04Martin Nilsson  meta_cache_insert( id, meta ); string meta_data = encode_value( meta );
23b7ec2001-08-27Per Hedbor #ifdef ARG_CACHE_DEBUG
4da2ef2003-11-11Henrik Grubbström (Grubba)  werror("Replacing entry for %O\n", id );
23b7ec2001-08-27Per Hedbor #endif
4da2ef2003-11-11Henrik Grubbström (Grubba)  QUERY("REPLACE INTO "+name+
d700c42006-08-21Henrik Grubbström (Grubba)  " (id,size,atime,meta,data) VALUES"
2cb5142006-08-21Henrik Grubbström (Grubba)  " (%s,%d,UNIX_TIMESTAMP()," MYSQL__BINARY "%s," MYSQL__BINARY "%s)",
2af7302004-02-05Anders Johansson  id, strlen(data)+strlen(meta_data), meta_data, data );
9b0aaa2006-08-21Henrik Grubbström (Grubba) #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); } } #endif
69a8691999-05-18Per Hedbor  }
fc40392008-08-15Martin Stjernholm  protected mapping restore_meta( string id, RequestID rid )
69a8691999-05-18Per Hedbor  {
796ebc2007-01-10Martin Jonsson  if( array item = meta_cache[ id ] )
e7ac492003-04-06Anders Johansson  {
796ebc2007-01-10Martin Jonsson  item[ 1 ] = time(1); // Update cached atime. return item[ 0 ];
e7ac492003-04-06Anders Johansson  }
6e05b22001-01-01Martin Nilsson 
23b7ec2001-08-27Per Hedbor #ifdef ARG_CACHE_DEBUG
6789262001-09-06Per Hedbor  werror("restore meta %O\n", id );
23b7ec2001-08-27Per Hedbor #endif array(mapping(string:string)) q =
067ef92011-12-22Marcus Wellhardh  QUERY("SELECT meta, UNIX_TIMESTAMP() - atime as atime_diff " "FROM "+name+" WHERE id=%s", id );
4afcd42001-08-09Per Hedbor 
3294072001-09-06Per Hedbor  string s; if(!sizeof(q) || !strlen(s = q[0]->meta))
69a8691999-05-18Per Hedbor  return 0;
9fcbed2001-01-20Per Hedbor 
18e19c2000-06-12Martin Stjernholm  mapping m;
55fc492000-12-30Per Hedbor  if (catch (m = decode_value (s))) { report_error( "Corrupt data in cache-entry "+id+".\n" );
4afcd42001-08-09Per Hedbor  QUERY( "DELETE FROM "+name+" WHERE id=%s", id);
18e19c2000-06-12Martin Stjernholm  return 0; }
6789262001-09-06Per Hedbor 
067ef92011-12-22Marcus Wellhardh  // Update atime only if older than threshold. if((int)q[0]->atime_diff > ATIME_THRESHOLD) { QUERY("UPDATE LOW_PRIORITY "+name+" " " SET atime = UNIX_TIMESTAMP() " " WHERE id = %s ", id); }
18e19c2000-06-12Martin Stjernholm  return meta_cache_insert( id, m );
69a8691999-05-18Per Hedbor  }
fc40392008-08-15Martin Stjernholm  protected void sync_meta()
e7ac492003-04-06Anders Johansson  {
4051ba2011-11-30Marcus Wellhardh  mapping tmp = meta_cache; meta_cache = ([]);
e7ac492003-04-06Anders Johansson  // Sync cached atimes.
4051ba2011-11-30Marcus Wellhardh  foreach(tmp; string id; array value) {
1e04a02006-11-14Anders Johansson  if (value[1])
e7ac492003-04-06Anders Johansson  QUERY("UPDATE "+name+" SET atime=%d WHERE id=%s",
1e04a02006-11-14Anders Johansson  value[1], id);
e7ac492003-04-06Anders Johansson  } }
93dd0e2000-07-27Johan Sundström  void flush(int|void age) //! Flush the cache. If an age (an integer as returned by
23b7ec2001-08-27Per Hedbor  //! @[time()]) is provided, only images with their latest access before
b5e3862000-12-31Martin Nilsson  //! that time are flushed.
4f2d5f2000-03-13Per Hedbor  {
3548012001-08-10Per Hedbor  int num;
5171b12011-11-29Anders Johansson #if defined(DEBUG) || defined(IMG_CACHE_DEBUG)
3548012001-08-10Per Hedbor  int t = gethrtime(); report_debug("Cleaning "+name+" image cache ... "); #endif
e7ac492003-04-06Anders Johansson  sync_meta();
5c1b622001-08-01Per Hedbor  uid_cache = ([]); rst_cache = ([]);
55fc492000-12-30Per Hedbor  if( !age ) {
5171b12011-11-29Anders Johansson #if defined(DEBUG) || defined(IMG_CACHE_DEBUG)
e7ac492003-04-06Anders Johansson  report_debug("cleared\n"); #endif
21207e2001-06-13Per Hedbor  QUERY( "DELETE FROM "+name );
3548012001-08-10Per Hedbor  num = -1;
b5e3862000-12-31Martin Nilsson  return;
55fc492000-12-30Per Hedbor  }
b5e3862000-12-31Martin Nilsson 
5c1b622001-08-01Per Hedbor  array(string) ids = QUERY( "SELECT id FROM "+name+" WHERE atime < "+age)->id;
b5e3862000-12-31Martin Nilsson 
3548012001-08-10Per Hedbor  num = sizeof( ids );
b5e3862000-12-31Martin Nilsson  int q; while(q<sizeof(ids)) {
2c64162002-12-04Marcus Wellhardh  string list = map(ids[q..q+99], get_db()->quote) * "','";
b5e3862000-12-31Martin Nilsson  q+=100;
5171b12011-11-29Anders Johansson  QUERY( "DELETE LOW_PRIORITY FROM "+name+" WHERE id in ('"+list+"')" );
55fc492000-12-30Per Hedbor  }
b5e3862000-12-31Martin Nilsson 
2f0ae12007-05-10Martin Stjernholm #if 0 // Disabled. This can take a significant amount of time to run, // and we really can't afford an unresponsive image cache - it can // easily hang all handler threads. Besides, it's doubtful if this // is of any use since the space for the deleted records probably // will get reused soon enough anyway. /mast
3548012001-08-10Per Hedbor  if( num ) catch { // Old versions of Mysql lacks OPTIMIZE. Not that we support // them, really, but it might be nice not to throw an error, at // least.
5171b12011-11-29Anders Johansson #if defined(DEBUG) || defined(IMG_CACHE_DEBUG)
a4a5fc2007-01-30Jonas Wallden  report_debug("Optimizing database ... ", name);
03b1742007-01-29Jonas Wallden #endif
3548012001-08-10Per Hedbor  QUERY( "OPTIMIZE TABLE "+name ); };
2f0ae12007-05-10Martin Stjernholm #endif
29fac12001-01-04Martin Nilsson 
5171b12011-11-29Anders Johansson #if defined(DEBUG) || defined(IMG_CACHE_DEBUG)
a4a5fc2007-01-30Jonas Wallden  report_debug("%s removed (%dms)\n",
eae43a2001-08-13Per Hedbor  (num==-1?"all":num?(string)num:"none"),
3548012001-08-10Per Hedbor  (gethrtime()-t)/1000); #endif
1f58d02000-01-02Martin Nilsson  }
93dd0e2000-07-27Johan Sundström  array(int) status(int|void age) //! Return the total number of images in the cache, their cumulative //! sizes in bytes and, if an age time_t was supplied, the number of
b5e3862000-12-31Martin Nilsson  //! images that has not been accessed after that time is returned //! (see <ref>flush()</ref>). (Three integers are returned //! regardless of whether an age parameter was given.)
93dd0e2000-07-27Johan Sundström  {
b5e3862000-12-31Martin Nilsson  int imgs=0, size=0, aged=0; array(mapping(string:string)) q;
55fc492000-12-30Per Hedbor 
21207e2001-06-13Per Hedbor  q=QUERY("SHOW TABLE STATUS");
6e05b22001-01-01Martin Nilsson  foreach(q, mapping qq) if(has_prefix(qq->Name, name)) { imgs = (int)qq->Rows; size += (int)qq->Data_length; }
b5e3862000-12-31Martin Nilsson 
6e05b22001-01-01Martin Nilsson  if(age) {
21207e2001-06-13Per Hedbor  q=QUERY("select SUM(1) as num from "+name+" where atime < "+age);
6e05b22001-01-01Martin Nilsson  aged = (int)q[0]->num; }
b5e3862000-12-31Martin Nilsson  return ({ imgs, size, aged });
1f58d02000-01-02Martin Nilsson  }
fc40392008-08-15Martin Stjernholm  protected mapping(string:mapping) rst_cache = ([ ]); protected mapping(string:string) uid_cache = ([ ]);
4afcd42001-08-09Per Hedbor 
fc40392008-08-15Martin Stjernholm  protected mapping restore( string id, RequestID rid )
69a8691999-05-18Per Hedbor  {
aea1652001-06-26Per Hedbor  array q; string uid; if( zero_type(uid = uid_cache[id]) ) {
4afcd42001-08-09Per Hedbor  q = QUERY( "SELECT uid FROM "+name+" WHERE id=%s",id);
aea1652001-06-26Per Hedbor  if( sizeof(q) ) uid = q[0]->uid; else uid = 0; uid_cache[id] = uid; }
90274d2001-08-09Per Hedbor  if( uid && strlen(uid) )
aea1652001-06-26Per Hedbor  { User u; if( !(u=rid->conf->authenticate(rid)) || (u->name() != uid ) ) return rid->conf->authenticate_throw(rid, "User"); }
45063f2009-03-20Henrik Grubbström (Grubba)  if( rst_cache[ id ] ) { rid->cache_status["icacheram"] = 1;
9fcbed2001-01-20Per Hedbor  return rst_cache[ id ] + ([]);
45063f2009-03-20Henrik Grubbström (Grubba)  }
29fac12001-01-04Martin Nilsson 
23b7ec2001-08-27Per Hedbor #ifdef ARG_CACHE_DEBUG werror("restore %O\n", id ); #endif
23f8432001-09-05Per Hedbor  q = QUERY( "SELECT meta,atime,data FROM "+name+" WHERE id=%s",id);
9fcbed2001-01-20Per Hedbor  if( sizeof(q) )
55fc492000-12-30Per Hedbor  {
4afcd42001-08-09Per Hedbor  if( sizeof(q[0]->data) ) {
23b7ec2001-08-27Per Hedbor  // Case 1: We have cache entry and image.
4afcd42001-08-09Per Hedbor  string f = q[0]->data;
23b7ec2001-08-27Per Hedbor  mapping m;
d307c72008-08-06Martin Stjernholm  if (mixed err = catch( m = decode_value( q[0]->meta ) )) report_debug ("Failed to decode meta mapping for id %O in %s: %s", id, name, describe_error (err));
4afcd42001-08-09Per Hedbor  if( !m ) return 0;
9fcbed2001-01-20Per Hedbor 
45063f2009-03-20Henrik Grubbström (Grubba)  rid->cache_status["icachedisk"] = 1;
4afcd42001-08-09Per Hedbor  m = Roxen.http_string_answer( f, m->type||("image/gif") );
23f8432001-09-05Per Hedbor 
4afcd42001-08-09Per Hedbor  if( strlen( f ) > 6000 ) return m; rst_cache[ id ] = m; if( sizeof( rst_cache ) > 100 )
41240d2001-09-03Stefan Wallström  rst_cache = ([ id : m ]);
4afcd42001-08-09Per Hedbor  return rst_cache[ id ] + ([]); }
23b7ec2001-08-27Per Hedbor  // Case 2: We have cache entry, but no data. return 0;
29fac12001-01-04Martin Nilsson  }
4afcd42001-08-09Per Hedbor  else {
23b7ec2001-08-27Per Hedbor  // Case 3: No cache entry. Create one
4afcd42001-08-09Per Hedbor  User u = rid->conf->authenticate(rid); string uid = ""; if( u ) uid = u->name();
23b7ec2001-08-27Per Hedbor  // Might have been insterted from elsewhere.
4da2ef2003-11-11Henrik Grubbström (Grubba)  QUERY("REPLACE INTO "+name+ " (id,uid,atime) VALUES (%s,%s,UNIX_TIMESTAMP())", id, uid );