#!/usr/local/bin/pike -M/usr/local/caudium/server/etc/modules
/*
 * Caudium - An extensible World Wide Web server
 * Copyright  2000-2004 The Caudium Group
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */
 
/*
 * Authors: 
 *   Marek Habersack <grendel@caudium.net>
 *   Bill Welliver <hww3@riverweb.com>
 *   David Gourdelier <vida@caudium.net>
 *   Bertrand Lupart <bertrand@caudium.net>
 *
 * License: MPL/LGPL
 * 
 * $Id: start-caudium.in,v 1.37.2.13 2007/11/21 10:20:43 bertrand Exp $
 */

// Fields for ports configuration in spider module
#define CFG_PORT_NUMBER 0
#define CFG_PORT_PROTOCOL 1
#define CFG_PORT_BIND 2
 
// for convenience
string pikever = sprintf("%u.%u.%u/", __REAL_MAJOR__, __REAL_MINOR__, __REAL_BUILD__);

// arguments we understand
array(array(string | array(string))) arguments = ({
  ({"quiet", Getopt.NO_ARG, ({"--quiet"})}),
  ({"truss", Getopt.NO_ARG, ({"--truss"})}),
  ({"strace", Getopt.NO_ARG, ({"--strace"})}),
  ({"ltrace", Getopt.NO_ARG, ({"--ltrace"})}),
  ({"ktrace", Getopt.NO_ARG, ({"--ktrace"})}),
  ({"log-dir", Getopt.HAS_ARG, ({"--log-dir", "--logdir"})}),
  ({"config-dir", Getopt.HAS_ARG, ({"--config-dir", "--configdir"})}),
  ({"pike-version", Getopt.HAS_ARG, ({"--pike-version", "--pikeversion"})}),
  ({"watchdog-socket", Getopt.HAS_ARG, ({"--watchdog-socket", "--watchdog-socket"})}),
  ({"pid-file", Getopt.HAS_ARG, ({"--pid-file", "--pidfile"})}),
  ({"debug", Getopt.NO_ARG, ({"--debug", "--with-debug", "--enable-debug"})}),
  ({"watchdog-debug", Getopt.NO_ARG, ({"--watchdog-debug", "--with-watchdog-debug", "--enable-watchdog-debug"})}),
  ({"watchdog", Getopt.NO_ARG, ({"--with-watchdog", "--watchdog", "--enable-watchdog"})}),
  ({"watchdog-pidcheck", Getopt.NO_ARG, ({"--with-watchdog-pidcheck", "--watchdog-pidcheck", "--enable-watchdog-pidcheck"})}),
  ({"nowatchdog", Getopt.NO_ARG, ({"--without-watchdog", "--nowatchdog", "--disable-watchdog"})}),
  ({"nowatchdog-pidcheck", Getopt.NO_ARG, ({"--without-watchdog-pidcheck", "--nowatchdog-pidcheck", "--disable-watchdog-pidcheck"})}),
  ({"nodebug", Getopt.NO_ARG, ({"--without-debug", "--nodebug", "--disable-debug"})}),
  ({"fddebug", Getopt.NO_ARG, ({"--fd-debug", "--with-fd-debug", "--enable-fd-debug"})}),
  ({"threads", Getopt.NO_ARG, ({"--threads", "--with-threads", "--enable-threads"})}),
  ({"nothreads", Getopt.NO_ARG, ({"--no-threads", "--without-threads", "--disable-threads"})}),
  ({"profile", Getopt.NO_ARG, ({"--profile", "--with-profile", "--enable-profile"})}),
  ({"fileprofile", Getopt.NO_ARG, ({"--file-profile", "--with-file-profile", "--enable-file-profile"})}),
  ({"keepalive", Getopt.NO_ARG, ({"--keep-alive", "--with-keep-alive", "--enable-keep-alive"})}),
  ({"pike", Getopt.HAS_ARG, ({"--with-pike"})}),
  ({"once", Getopt.NO_ARG, ({"--once"})}),
  ({"gdb", Getopt.MAY_HAVE_ARG, ({"--gdb"}), 0, "gdb"}),
  ({"program", Getopt.HAS_ARG, ({"--program"})}),
  ({"version", Getopt.NO_ARG, ({"--version"})}),
  ({"piketrace", Getopt.MAY_HAVE_ARG, ({ "--piketrace", "-t" })}),
  ({"help", Getopt.NO_ARG, ({"--help", "-?"})}),
	({"nbio", Getopt.NO_ARG, ({ "--nbio", "--with-nbio" })}),
});

// loader options (with defaults)
mapping(string:mixed) options = ([
  "gdb"              : "gdb",
  "pike"             : 0,
  "pikever"          : 0,
  "threads"          : 1,
  "watchdog"         : 1,
	"watchdog_pidcheck": 1,
	"watchdog_debug"   : 0,
  "program"          : "base_server/caudiumloader.pike", 
]);

// stderr and stdout
Stdio.File stderr;
Stdio.File stdout;

// environment variables we set for caudium
// all of them are merged with the values of variables from the shell
// environment, if they exist. The existing variables are appended to the
// values below. Some of the variables are constructed dynamically, those
// are not included in the mapping below.
mapping(string:string|array) envvars = ([
  "CAUDIUM_CONFIGDIR" : getenv("CAUDIUM_CONFIGDIR") || "../configurations",
  "CAUDIUM_LOGDIR" : getenv("CAUDIUM_LOGDIR") || "../logs",
  "CAUDIUM_PID_FILE" : getenv("CAUDIUM_PID_FILE") || ("/tmp/caudium_" + uniq_watchdog_id()),
  "CAUDIUM_WATCHDOG_SOCKET" : getenv("CAUDIUM_WATCHDOG_SOCKET") || ("/tmp/caudium_watchdog_socket_" + uniq_watchdog_id()),
  "CLASSPATH" : (getenv("CLASSPATH")?getenv("CLASSPATH") +":":"") + 
  (getenv("JAVA_HOME")?getenv("JAVA_HOME") +"/lib/tools.jar:":"") + 
  ("etc/classes:etc/classes/caudium_search.jar:etc/classes/lucene_1.2.jar:"
   "etc/classes/caudium_module.jar:etc/classes/caudium_servlet.jar:etc/classes/servlet.jar:etc/classes/jsdk.jar"),
  "PIKE_MODULE_PATH" : getenv("PIKE_MODULE_PATH") || "",
  "CAUDIUM_LANG" : getenv("CAUDIUM_LANG") || "en"
]);

// locations that we should search for modules in, if present
array lib_locations=({"lib/modules", 
                      "lib/pike/modules", "etc/modules", "lib/" + pikever,
                      "share/pike/modules"});
      
// locations that we should search for programs in, if present
array program_locations=({});
      
// locations that we should search for includes in, if present
array include_locations=({"lib/include", "etc/include", 
                          "share/pike/include", "base_server"});

// parsed arguments
array parsed_args=({});

// so we can spawn ourselves.
array passed_args=({});
mapping passed_env=getenv();

// The webserver process
Process.Process   proc;

// components of the Caudium command line.
mapping(string:string|array) command_line = ([
  "DEFINES" : ({}),
  "INCLUDES" : ({}),
  "PROGRAMS" : ({}),
  "COTHER": ({})
]);

// to be filled in by the watchdog starter.
mapping watchdog_params = ([ ]);

// the watchdog communication socket
Stdio.File watchdog_socket;

// a list of valid interpreters we should try to use.
array valid_interpreters=({"/usr/local/bin/pike", "bin/caudium", "bin/pike"});

// call out for the watchdog
mixed cs_callout;

// object for the watchdog
object conn;


class SiteChecker
{
	string url;
	int port;
	mixed conn;
	mixed timeout_callout;

	//! Constructor for the SiteCheck object
	//!
	//! @param _url
	//! The URL of the site to check
	//!
	//! @param _port
	//! The port of the site to check
	void create(string _url, int _port)
	{
		url = _url;
		port = _port;
		conn = Stdio.File();
	}

	//! Run an HTTP GET on the site
	void check()
	{
		if(!conn->connect(url, port))
		{
			flex_write("Unable to connect to " + url + ":" + (string)port + "\n");
			in_check=0;
			// if we're running, we need to be kicked, otherwise it will get
			// restarted at the next check of the starter. 

			int proc_status = proc->status();

			if(proc && proc->status()==1)
			{
				flex_write("Killing Caudium...\n");
				// try to make caudium describing all the threads
				proc->kill(signum("SIGTRAP"));
				proc->kill(signum("SIGKILL"));
			}
			else
			{
				flex_write("Not killing Caudium because we are not running\n");
			}
		}

		// let's send the request and wait for a response.
		flex_debug("GET request to "+url+" "+(string)port+"\n");

		conn->set_nonblocking();
		conn->set_read_callback(GET_response_received);
		conn->write("GET / HTTP/1.0\r\n");
		conn->write("Host: "+url+"\r\n");
		conn->write("User-Agent: Caudium_Watchdog ("+(string)getpid()+") (Pike/"+pikever+" "+CaudiumVersion.real_version+")\r\n");
		conn->write("\r\n");
		timeout_callout=call_out(check_timedout, (int)(watchdog_params->timeout));
	}

	//! Callback called when some data is received
	void GET_response_received(mixed id, string data)
	{
		// we are now not at the first request.
		first_ping=0;
		in_check=0;
		// did we get the expected response?
		if(sizeof(data))
		{
			// request was successful.
			flex_debug("Got response for GET on "+url+":"+(string)port+"\n");
			remove_call_out(timeout_callout);
			conn->close();
			conn=0;
		}
		else  // we got no response. kill the process.
		{
			flex_write("Got no response for GET on "+url+":"+(string)port+"\n");
			conn->close();
			conn=0;
			kill_caudium();
		}
	}

	//! Callout method called in case the connection to the site did timeout
	//! 
	//! If called, this method kills Caudium
	void check_timedout()
	{
		conn->close();
		conn=0;

		flex_write("Timeout on "+url+":"+port+". Killing Caudium...\n");

		in_check=0;

		kill_caudium();

		// remove any of the check call outs.
		if(timeout_callout)
			remove_call_out(timeout_callout);
	}
}


int first_ping=1;

// return a uniq identifier for each running watchdog
string uniq_watchdog_id()
{
	return getuid()+"_"+getpid();
}

// run Caudium and return its exit status.
int run_caudium(array(string) args, mapping|void opts)
{
  mapping           myopts = opts || ([]);

  if(opts->stdout)
    stdout=opts->stdout;
  else
    stdout=Stdio.File("stdout");
  if(opts->stderr)
    stderr=opts->stderr;
  else
    stderr=Stdio.File("stderr");

  opts->cwd = getcwd();
  opts->env = getenv() | envvars;
  if(!options->quiet)
  {
    write("Starting the Caudium Webserver.\n"
          "Log dir is " + envvars->CAUDIUM_LOGDIR + ", Configuration dir is " + envvars->CAUDIUM_CONFIGDIR 
          + "\n");
    if(!options->once)
      write("Debug output is located in " + envvars->CAUDIUM_LOGDIR + "/debug/default.1\n");
  }
  if(opts->stdout && !options->quiet)
    opts->stdout->write("Running " + (args*" ") + "\n");

  args-=({""});
  proc = Process.create_process(args, opts);

  if (!proc) {
    if(opts->stderr)
      opts->stderr->write("Failed to execute the child process\n");
    else werror("Failed to execute the child process\n");
    return 1;
  }

  return 1;

}

void append_env_path(string envvar, string value)
{
  if (!envvar || !sizeof(envvar))
    return;
  
  if (envvars[envvar] && sizeof(envvars[envvar]))
    envvars[envvar] += ":";
  envvars[envvar] += value;
}

// Sets some initial and extra values. This function MUST be ran with the
// cwd set to the caudium toplevel directory.
void preamble()
{
  mapping(string:string)  osdata = System.uname();
  int                     go_threads = 0;
	int                     watchdog_pidcheck = 1;
  string                  os_label = "";
  array(string)           os_rel;
  Stdio.Stat              fstat;

  if (options->pikever) {
    fstat = file_stat("bin/caudium-" + options->pikever);
    if (fstat && fstat->isreg && (fstat->mode & 0111))
      options->pike = "bin/caudium-" + options->pikever;
    if (!options->pike) {
      fstat = file_stat("bin/pike-" + options->pikever);
      if (fstat && fstat->isreg && (fstat->mode & 0111))
        options->pike = "bin/pike-" + options->pikever;
    }
    if (!options->pike) {
      write("Cannot find Pike v%s in %s/bin/\n", options->pikever, getcwd());
      exit(1);
    }
  }

  // we're not specifying a pike version to use internally
  else {
    if(options->pike)
    {
      fstat=file_stat(options->pike);
      if(!(fstat && fstat->isreg && (fstat->mode & 0111)))
      {
        write("Specificed Pike %s does not exist or is not executable.\n", options->pike);
        exit(1);
      }
    }
    else
    {
      foreach(valid_interpreters, string vi)
      {
        fstat=file_stat(vi);
        if (fstat && fstat->isreg && (fstat->mode & 0111))
        {
          options->pike = vi;
          break;
        }
      }
      if(!options->pike || options->pike=="")
      {
        write("Unable to find a usable Pike interpreter.\n");
        exit(1);
      }

    }
  }

  os_rel = osdata->release / ".";
  if (sizeof(os_rel) < 2)
    os_rel += ({"0"}); // better this than nothing
  
  switch(osdata->sysname) {
      case "SunOS":
        if ((int)os_rel[0] >= 5 && (int)os_rel[1] >= 5) {
          os_label = "Solaris 2.5 or later";
          go_threads = 1;
        }
        break;

      case "FreeBSD":
        if ((int)os_rel[0] >= 4) {
          os_label = "FreeBSD 4.0 or later";
          go_threads = 1;
        }
        break;

      case "Linux":
        if ((int)os_rel[0] >= 2 && (int)os_rel[1] >= 2) {
          os_label = "Linux 2.2 or later";
          go_threads = 1;
        }
        break;

      case "Darwin":
        os_label = "Darwin or MacOS X";
        go_threads = 1;
        break;
  }

  if (go_threads && options->threads) {
    if(!options->quiet)
      write("%s detected, enabling threads (if available in Pike)\n",
            os_label);
    command_line->DEFINES += ({"ENABLE_THREADS"});
  }

	os_label = "";

  switch(osdata->sysname) {
		case "Linux":
			if ((int)os_rel[0] >= 2 && (int)os_rel[1] < 6) {
				os_label = "Linux pre-2.6";
				watchdog_pidcheck = 0;
			}
			break;
	}

  if (!watchdog_pidcheck || !options->watchdog_pidcheck) {
    if(!options->quiet)
		{
			if(os_label)
			{
				write("%s detected, ", os_label);
			}
			write("disabling Watchdog PID check\n");
		}
		options->watchdog_pidcheck = 0;
  }

	if(options->debug || options->watchdog_debug)
	{
		if(!options->quiet)
		{
			write("Enabling watchdog debug\n");
		}
		options->watchdog_debug = 1;
	}

  command_line->DEFINES += ({"CAUDIUM", "CAUDIUM_CACHE", "ROXEN", "USE_SHUFFLER"});

  System.umask(022);

  if (!getenv("PIKE_NO_DEFAULT_PATHS")) {
    if (!getenv("PIKE_MASTER")) { // Pike default master program

      command_line->OTHER += ({"-w"});
/*
  foreach(master_locations, string ml)
  {
  if(Stdio.is_file(ml))
  {
  command_line->OTHER += ({"-m" + ml});
  }
  }
*/
      foreach(include_locations, string il)
      {
        if(Stdio.is_dir(il))
        {
          command_line->INCLUDES += ({il});
          add_include_path(il);
        }
      }

      foreach(lib_locations, string ll)
      {
        if(Stdio.is_dir(ll))
        {
          command_line->MODULES += ({ll});
          add_module_path(ll);
        }
      }

      foreach(program_locations, string pl)
      {
        if(Stdio.is_dir(pl))
        {
          command_line->PROGRAMS += ({pl});
          add_program_path(pl);
        }
      }
    }
    else {
      command_line->OTHER += ({"-m" + getenv("PIKE_MASTER")});
    }
  }

  // a kludge for HPUX which doesn't like group 60001 (nobody)
  if (osdata->sysname == "HP-UX") {
    if(!options->quiet)
      write("WARNING: applying a kludge for HPUX (see base_server/privs.pike)\n");
    command_line->DEFINES += ({"HPUX_KLUDGE"});
  }
}

array parse_arguments(array(string) argv)
{
  array(array)  parsed=({});
  parsed = Getopt.find_all_options(argv, arguments, 0, 0);

  argv-=({0});
  if(sizeof(argv)>1) command_line->PROG_OTHER += argv[1..];

  return parsed;
}

int main(int argc, array(string) argv)
{

  // first, copy argv to a safe place.
  passed_args=copy_value(argv);
  // next, change into the directory that start is living in.
  string d=dirname(argv[0]);
  if(d) cd(d);

  program_locations += ({ getcwd() });

  // is the directory we're in a valid caudium server root?
  if(!file_stat("base_server")) 
  {
    write("Cannot find Caudium server root\n");
    exit(1);
  }       

  if(search(argv, "--help")!=-1)
  {
    write_help();
    return 1;
  }

  parsed_args=parse_arguments(argv);

  int code=act_on_args();

  // do we get a "quit" code from the arg handler?
  if(code) return 0;

  preamble();

  if(!options->once)
  {
    object havechild = fork();
    if(havechild)  // are we the parent?
    {
      sleep(1);
      return 0;
    }
  }

  // set up signal handlers
  setup_signals();

  call_out(continue_startup, 0);
  return -1;
  
}

void setup_signals()
{
  signal(signum("SIGHUP"), signal_hup);
  signal(signum("SIGINT"), signal_int);
  signal(signum("SIGTERM"), signal_term);
}

// we pass HUP on to Caudium
void signal_hup()
{
  write("Sending HUP to Caudium (to force reload of configurations.)\n");
  if(proc && proc->status()==0)
  {
    proc->kill(signum("SIGHUP"));
  }
}

// we stop caudium and the start process
void signal_term()
{
  // avoid infinite signal loop (exit and backtrace sends a sigterm to ourself)
  signal(signum("SIGTERM"), 0);
  if(proc && proc->status()==0)
  {
    write("Sending shutdown request to Caudium.\n");
    proc->kill(signum("SIGTERM"));
  }
  cleanup_watchdog();
  exit(0);
}

// we stop caudium and restart it
void signal_int()
{
  if(options->once)
  {
    if(proc && proc->status()==0)
    {
      proc->kill(signum("SIGTERM"));
      // we shouldn't have a watchdog running since we're only running once.
      exit(0);
    }
  }
  write("Restarting Caudium.\n");
  if(proc && proc->status()==0)
  {
    proc->kill(signum("SIGINT"));
  }
}

void continue_startup()
{
  array o = generate_command_options();

  mapping opt=([]);

  if(options->once)
  {
    opt->stdout = Stdio.File("stdout");
    opt->stderr = Stdio.File("stderr");

    if(options->precmd && options->precmd[0]=="gdb")
    {
      Stdio.write_file(".gdbinit", "handle SIGPIPE nostop noprint pass\n"
                       "handle SIGUSR1 nostop noprint pass\n"
                       "handle SIGUSR2 nostop noprint pass\n"
                       "run " + (o*" ") + "\n");
      do_one_run(options->precmd + ({options->pike}), opt);
    }

    else do_one_run(o, opt);

  }

  else // we're backgrounded, and we should start caudium now.
  {
    do_multi_run(o, opt);
  }

  // fire up the watchdog listener.
  if(options->watchdog)
    start_watchdog();

  return 0;
}

void do_one_run(array o, mapping opt)
{
  call_out(do_one_run, 2, o, opt);
  // if we don't have a running process, exit.
  if(!proc)
  {
    run_caudium(o, opt);
  }
  else if(proc->status()!=0)
  {
    if(options->precmd && options->precmd[0]=="gdb")
      rm(".gdbinit");
    exit(0);
  }
}

void do_multi_run(array o, mapping opt)
{
  call_out(do_multi_run, 2, o, opt);
  // if we don't have a running process, start it.
  if(!proc || proc->status()!=0)
  {
    if(proc && proc->wait()==0) // caudium returns 0, we quit.
    {
      write("start-caudium: exiting.\n");
      cleanup_watchdog();
      exit(0);
    }
    rotate_logs(o, opt);
    opt->stdout=Stdio.File(envvars->CAUDIUM_LOGDIR + "/debug/default.1", "crw");
    opt->stderr=opt->stdout;
    opt->setsid=1;

    // remove the watchdog while we're restarting Caudium
    if(cs_callout)
      remove_call_out(cs_callout);

    run_caudium(o, opt);

  }

}

void rotate_logs(array o, mapping opt)
{
  for(int i=8; i>0; i--)
  {
    if (!Stdio.is_dir(envvars->CAUDIUM_LOGDIR + "/debug/")) {
      Stdio.mkdirhier(envvars->CAUDIUM_LOGDIR + "/debug/");
      return;
    }
    mv(envvars->CAUDIUM_LOGDIR + "/debug/default." + i, envvars->CAUDIUM_LOGDIR + "/debug/default." + (i+1));
  }
}

array generate_command_options()
{
  array o=({});

  if(!options->precmd ||  options->precmd[0]!="gdb")
  {
    if(options->precmd)
      o+=options->precmd;
    o+=({options->pike});
  }

  if(command_line->DEFINES)
    foreach(command_line->DEFINES, string d)
      o+=({"-D" + d});
  if(command_line->INCLUDES)
    foreach(command_line->INCLUDES, string d)
      o+=({"-I" + d});
  if(command_line->MODULES)
    foreach(command_line->MODULES, string d)
      o+=({"-M" + d});
  if(command_line->OTHER)
    foreach(command_line->OTHER, string d)
      o+=({d});
  if(command_line->PROG_OTHER)
    foreach(command_line->PROG_OTHER, string d)
      o+=({d});
  if(command_line->PROGRAMS)
    foreach(command_line->PROGRAMS, string d)
      o+=({"-P" + d});

  o+=({options->program});

  if(command_line->PROG_OTHER)
    foreach(command_line->PROG_OTHER, string d)
      o+=({d});

  return o;
} 

int act_on_args()
{
  foreach(parsed_args, array m)
  {
    switch(m[0])
    {
        case "truss":
          options->precmd=({"truss"});
          options->once=1;
          break;
        case "strace":
          options->precmd=({"strace", "-f"});
          options->once=1;
          break;
        case "ltrace":
          options->precmd=({"ltrace", "-f"});
          options->once=1;
          break;
        case "ktrace":
          options->precmd=({"ktrace", "-d"});
          options->once=1;
          break;
        case "log-dir":
          envvars->CAUDIUM_LOGDIR=m[1];        
          break;
				case "watchdog":
	  			options->watchdog=1;
					break;
				case "watchdog-debug":
					options->watchdog_debug = 1;
					break;
				case "watchdog-pidcheck":
					options->watchdog_pidcheck = 1;
          break;
				case "nowatchdog":
	  			options->watchdog=0;
          break;
				case "nowatchdog_pidcheck":
					options->watchdog_pidcheck = 0;
					break;
        case "config-dir":
          envvars->CAUDIUM_CONFIGDIR=m[1];        
          break;
        case "pike-version":
          options->pikever=m[1];
          break;
        case "pid-file":
          envvars->CAUDIUM_PID_FILE=m[1];
          break;
        case "watchdog-socket":
          envvars->CAUDIUM_WATCHDOG_SOCKET=m[1];
          break;
        case "backgrounded":
          options->backgrounded=1;
          break;
        case "help":
          write_help();
          return 1;
          break;
        case "threads":
          options->threads=1;
          break;
        case "nothreads":
          options->threads=0;
          break;
        case "profile":
          command_line->DEFINES+=({"PROFILE"});
          break;
        case "fileprofile":
          command_line->DEFINES+=({"FILE_PROFILE"});
          break;
        case "once":
          options->once=1;
          options->watchdog=0;
          break;
        case "gdb":
          options->precmd=({"gdb"});
          options->once=1;
					options->watchdog=0;
          break;
        case "nodebug":
          command_line->DEFINES-=({"DEBUG", "MODULE_DEBUG", "CACHE_DEBUG"});
          break;
        case "debug":
          command_line->DEFINES+=({"DEBUG", "MODULE_DEBUG", "CACHE_DEBUG"});
          break;
        case "fddebug":
          command_line->DEFINES+=({"FD_DEBUG"});
          break;
        case "keepalive":
          command_line->DEFINES+=({"KEEP_ALIVE"});
          break;
        case "version":
	  write(CaudiumVersion.real_version + "\n");
	  return 1;
          break;
	case "piketrace":
	  command_line->OTHER+=({"-t"+m[1] });
          break;
        case "pike":
          options->pike=m[1];
          break;
        case "program":
          options->program=m[1];
          options->once=1;
          break;
        case "quiet":
          options->quiet=1;
          break;
				case "nbio":
					command_line->DEFINES-=({"USE_SHUFFLER"});
					break;
    }    
  }
  
  return 0;
}

void write_help()
{
   object ti=Stdio.Terminfo.getTerm();
   string bon=ti->tgetstr("md");
   string boff=ti->tgetstr("me");

   write( replace(
# ".BThis command will start the Caudium serverB..
The environment variable .BCAUDIUM_ARGSB. can be used to specify the
default arguments.
   .BArguments:B.
      .B--versionB.:  Output version information.
      .B--help -?B.:  This information
      .B--pike-version=VERB.:  Use an alternate pike version. For this to
				  work correctly, you need a
bin/caudium-VER
				  and the Caudium pike modules in
lib/VER/.
      .B--log-dir=DIRB.:  Set the log directory. Defaults to .B../logsB..
      .B--config-dir=DIRB.:  Use an alternate configuration directory
				  Defaults to .B../configurationB..
      .B--with-threadsB.:  If threads are available, use them.
      .B--without-threadsB.:  Even if threads are enabled by default,
                                  disable them.
      .B--with-profileB.:  Store runtime profiling information on
				  a directory basis. This information is
 				  not saved on permanent storage, it is
only
				  available until the next server restart
				  This will enable a new 'action' in the
				  configuration interface
      .B--with-file-profileB.:  Like .B--with-profileB., but save
information
                                  for each and every file.
      .B--with-keep-aliveB.:  Enable keep alive in the HTTP
			          protocol module. This will soon be
                                  the default. Some clients might have
				  problems with keepalive.

      .B--with-nbioB.:  Enable use of legacy Caudium's NBIO module instead of
				  Pike's Shuffler. 

      .B--onceB.:  Run the server only once, in the foreground.
			   	  This is very useful when debugging.
       				  Implies --without-watchdog.
      .B--gdbB.:  Run the server in gdb. Implies .B--onceB. and
			            .B--without-watchdog.B.
      .B--programB.:  Start a different program with the caudium pike. As
an example,
                                  .B./start --program bin/install.pikeB.  
will
				  start the installation program normally
                                  started with .B./installB. Implies --once.
      .B--quietB.:  Run without normal debug output from the
                                  start script. Useful mainly when starting
				  other programs with --program.

      .B--with-watchdogB.:  Enable watchdog (default)
      .B--with-watchdog-pidcheckB.:  Enable watchdog PID check (default)
      .B--without-watchdogB.:  Disable watchdog
      .B--without-watchdog-pidcheckB.:  Disable watchdog PID check

      .B--with-debugB.:  Enable debug
			.B--with-watchdog-debug:  Enable watchdog debug
      .B--without-debugB.:  Disable all debug
	
      .B--with-fd-debugB.:  Enable FD debug.
      .B--truss,--strace,--ltrace,--ktraceB.: Run the server under the selected tracer
                   		  program. This is extremely noisy, and is not
				  intented for anything but debugging purposes.
      .B--pid-file=<file>B.:  Store the caudium and startscript pids in this
				  file. Defaults to .B/tmp/caudium_\$UIDB.
      .B--watchdog-socket=<file>B.:  Create a watchdog communication socket at this 
				  path. Defaults to .B/tmp/caudium_watchdog_socket\$UIDB.
         
  .BArguments passed to pike:B.
       .B-DDEFINEB.:  Define the symbol .BDEFINEB..
       .B-d<level>B.:  Set the runtime pike debug to level.
				  This only works if pike is compiled
				  with debug.
       .B-s<size>B.:  Set the stack size.
       .B-M <path>B.:  Add the path to the pike module path.
       .B-I <path>B.:  Add the path to the pike include path.
       .B-dtB.:  Turn of tail recursion optimization.
       .B-tB.:  Turn on pike level tracing.
       .B-t<level>B.:  Turn on more pike tracing. This only
				  works if pike is compiled with debug.
  .BEnvironment variables:B.
     .BCAUDIUM_CONFIGDIRB.:  Same as .B--config-dir=... B.
     .BCAUDIUM_PID_FILEB.:  Same as .B--pid-file=... B.
     .BCAUDIUM_WATCHDOG_SOCKETB.:  Same as .B--watchdog-socket=... B.
     .BCAUDIUM_LANGB.:  The default language for all language
				    related tags. Defaults to 'en' for english.

  .BProcess Control:B.
     Sending TERM to the start process will shutdown the starter and caudium (if it's able to exit on its own)
     Sending HUP to the start process will cause caudium to reload its configurations
     Sending INT to the start process will cause caudium to restart itself (if it's able to do this on its own)
", ({".B", "B."}), ({bon, boff}))
);

}

//
// Get a list of servers and ports that speak http. 
// 
array get_servers() {
  array srvs=({});
  object config_dir=Config.Files.Dir(envvars->CAUDIUM_CONFIGDIR);

	flex_debug("Getting virtual server configurations from "+envvars->CAUDIUM_CONFIGDIR+"\n");

  if(!config_dir)
  {
    werror("unable to open config dir.\n");
    cleanup_watchdog();
    exit(1);
  }
  foreach(config_dir->list_files(), mapping cf)
  {
    // we can skip global variables, as it won't have a virtual server
    if(cf->name=="Global_Variables")
			continue;

		flex_debug("Parsing "+cf->name+"\n");

    object cfr=Config.Files.File(config_dir, cf->name);
    cfr->parse();

    mapping cfg=cfr->retrieve_region("spider#0");

    if(cfg)
    {
			flex_debug("spider#0 region found for "+cf->name+"\n");

      int port;
      string host, prot;
      object uri;

      mixed err = catch {
        uri = Standards.URI(cfg->MyWorldLocation);
      };
      if(err)
        werror("Can't parse URI: %O\n", cfg->MyWorldLocation);
      
			if(uri && uri->scheme=="http" && uri->host)
      {
				// Getting the ports configuration for the virtual server
				foreach(cfg->Ports, array port)
				{
					if(port[CFG_PORT_PROTOCOL]=="http")
					{
						// If Caudium binds on all interfaces or one that match the uri
						if(
							port[CFG_PORT_BIND]=="ANY" ||
							search(gethostbyname(uri->host)[1], port[CFG_PORT_BIND]) != -1
						)
						{
							array to_watch = ({ uri->host, port[CFG_PORT_NUMBER] });
							flex_debug("Adding "+to_watch[0]+":"+to_watch[1]+" to the watch list\n");
							srvs += ({ to_watch });
						}
						else
							flex_debug("Not adding "+uri->host+":"+port[CFG_PORT_NUMBER]+" to the watch list: watchdog won't be able to connect because of "+port[CFG_PORT_BIND]+":"+port[CFG_PORT_NUMBER]+" restriction\n");
					}
					else
						flex_debug("Not adding "+uri->host+":"+port[CFG_PORT_NUMBER]+" to the watch list: watchdog don't handle protocol "+port[CFG_PORT_PROTOCOL]+"\n");
				}
      }
    }
  }
	  
  return srvs;
} 

//
// get watchdog parameters from configuration files
//
void get_watchdog_params()
{
  flex_write("Loading watchdog parameters from " + envvars->CAUDIUM_CONFIGDIR + "\n");    

  object config_dir=Config.Files.Dir(envvars->CAUDIUM_CONFIGDIR);
  if(!config_dir)
  {
    werror("unable to open config dir.\n");
    cleanup_watchdog();
    exit(1);
  }

  object cfr=Config.Files.File(config_dir, "Global_Variables");
  cfr->parse();
  mapping cfg=cfr->retrieve_region("Variables");

  if(cfg)
  {
     if(cfg->watchdog_timer)
       watchdog_params->timer=cfg->watchdog_timer;

     if(cfg->watchdog_timeout)
       watchdog_params->timeout=cfg->watchdog_timeout;

     if(cfg->watchdog_method)
		   watchdog_params->method=cfg->watchdog_method;

			if(cfg->watchdog_checkall)
				watchdog_params->checkall=cfg->watchdog_checkall;

  }

  array srvrs=get_servers() || ({});
	
	watchdog_params->sites = srvrs;

  // try to avoid having multiple loops of get_watchdog_params going.
  remove_call_out(get_watchdog_params);
  call_out(get_watchdog_params, 7200);

} 

void watchdog_accept()
{
  int pid;
  string cmd;

  Stdio.File ds=watchdog_socket->accept();
  ds->set_blocking();
  string s = ds->read();
 
  if(sscanf(s,"WATCHDOG %s %d", cmd,pid)!=2)
  {
    flex_write("Got invalid command from watchdog control socket.\n");    
    ds->close();
    return;
  }

	if(options->watchdog_pidcheck == 1 && pid != proc->pid())
	{
		flex_write("Got invalid PID for caudium on the watchdog control socket.\n");
		flex_write("  PID received in command: "+pid+"\n");
		flex_write("   PID of process started: "+proc->pid()+"\n");
		flex_write("Maybe you should try --without-watchdog-pidcheck\n");

		ds->close();
		return;
	}

  switch(cmd)
  {
    case "ON":
      flex_write("Starting the Caudium Watchdog\n");
      watchdog_on();
      break;

    case "OFF":
      flex_write("Stopping the Caudium Watchdog\n");
      watchdog_off();
      break;  
    
    default:
      flex_write("Got invalid command for caudium on the watchdog control socket.\n");    
      break;
  }
      
  ds->close();
}

void cleanup_watchdog()
{
  string s = envvars->CAUDIUM_WATCHDOG_SOCKET;
 
  // close the socket.
  if(watchdog_socket)
    watchdog_socket->close();
  
  // turn the watchdog off.
  watchdog_off();

  // remove the socket
  catch(rm(s));
}

void watchdog_on()
{
  // easiest to shut it off first, that way we don't have 2 loops running.
  watchdog_off();

  get_watchdog_params();
  check_sites();
}

void watchdog_off()
{
  int rco;

  do
  {
    rco=zero_type(remove_call_out(get_watchdog_params));
  } while (rco!=1);

  do
  {
    rco=zero_type(remove_call_out(check_sites));
  } while (rco!=1);

  do
  {
    rco=zero_type(remove_call_out(check_timedout));
  } while (rco!=1);


}

void start_watchdog()
{
  flex_write("Starting the watchdog\n");    

  // first, create the socket.
  watchdog_socket = Stdio.Port();
  flex_write("creating watchdog communication socket at " + envvars->CAUDIUM_WATCHDOG_SOCKET + "\n");
  rm(envvars->CAUDIUM_WATCHDOG_SOCKET);
  if(!watchdog_socket || !watchdog_socket->bind_unix(envvars->CAUDIUM_WATCHDOG_SOCKET, watchdog_accept))
  {
    flex_write("unable to create watchdog communication socket. not starting watchdog.\n");
    return;
  }
}

int in_check=0;

void check_sites()
{
	switch(watchdog_params->method)
	{
		case "GET":
			check_using_GET();
			break;

		default:
			check_using_PING();
	}
}

// Check site using PING
void check_using_PING()
{
  if(in_check)
    flex_write("Watchdog is already in a check... skipping.\n");

  if(watchdog_params->sites && sizeof(watchdog_params->sites) && !in_check)
  {
    in_check=1;
    // we have a site to check.    
    conn=Stdio.File();
   
    // do we have a valid server and port?
    if(!(watchdog_params->sites[0][0] && watchdog_params->sites[0][1])) 
      return;

    // if so, can we connect?
    if(!conn->connect(watchdog_params->sites[0][0], 
        (int)(watchdog_params->sites[0][1])))
    {
      flex_write("Unable to connect to " + watchdog_params->sites[0][0] + ":" 
        + watchdog_params->sites[0][1] + "\n");       
      in_check=0;
      // if we're running, we need to be kicked, otherwise it will get
      // restarted at the next check of the starter. 
			int proc_status = proc->status();
			flex_write("proc->status(): "+proc_status+"\n");	
			
      //if(proc && proc->status()==1) 
      if(proc && proc->status()==1) 
      {
        flex_write("Killing Caudium...\n");
	// try to make caudium describing all the threads
        proc->kill(signum("SIGTRAP"));
				sleep(2);
        proc->kill(signum("SIGKILL"));
      }
    }
		flex_debug("PING request\n");
		
    // let's send the request and wait for a response.
    conn->set_nonblocking();
    conn->set_read_callback(PING_response_received);
    conn->write("PING\r\n");
    cs_callout=call_out(check_timedout, (int)(watchdog_params->timeout));  
  }
  else
   flex_write("Can't get a site to connect to: Not starting watchdog\n");
}

// Check site using GET
void check_using_GET()
{
  if(in_check)
    flex_write("Watchdog is already in a check... skipping.\n");

  if(watchdog_params->sites && sizeof(watchdog_params->sites) && !in_check)
  {
    in_check=1;
    // we have a site to check.    

		int current_site = 0;

		array sites_to_check = ({ });

		// Do we want to check all the virtual servers or only the first one?
		if(watchdog_params->checkall)
			sites_to_check = watchdog_params->sites;
		else
			sites_to_check = ({ watchdog_params->sites[0] });

		// Do the check for all virtual servers we want to
		foreach(sites_to_check, array site)
		{
			flex_debug("Checking #"+current_site+" "+site[0]+":"+site[1]+"\n");

    	// do we have a valid server and port?
    	if(!(site[0] && site[1])) 
			{
				flex_write("No server or not port, not checking\n");
      	continue;
			}

			object site_checker = SiteChecker(site[0], site[1]);
			site_checker->check();

			current_site++;
  	}
		call_out(check_sites, watchdog_params->timer);
	}
 	else
	{
 	 	flex_write("Can't get a site to connect to: Not starting watchdog\n");
	}
}


//! Handle data received from watchdog's PING request
//!	This method is a read callback from Stdio.File()
//!
//! @param id
//! 
void PING_response_received(mixed id, mixed data)
{
  // we are now not at the first request.
  first_ping=0;

  in_check=0;

  // did we get the expected response?
  if(data=="PONG\r\n")
  {
		flex_debug("Got response for PING request\n");
    // request was successful.
    remove_call_out(cs_callout);
    conn->close();
    conn=0;

    // restart the watchdog.
    call_out(check_sites, watchdog_params->timer);
  }
  else  // we got something else. kill the process.
  {
     flex_write("Got unexpected response from PING.\n");
     conn->close();
     conn=0;
     kill_caudium();
  }
}


//! Kill Caudium
void kill_caudium()
{
  // kill the Caudium process with extreme prejudice
  if(proc)
  {
    flex_write("Killing Caudium...\n");
    watchdog_off();
    // try to make caudium describing all the threads
    proc->kill(signum("SIGTRAP"));
		sleep(2);
    proc->kill(signum("SIGKILL"));
  }
}


//! Method called when a PING check did timeout
//! If The PING check timeout, Caudium is restarted
void check_timedout()
{
  conn->close();
  conn=0;

  flex_write("PING Check timed out.\n");

  in_check=0;

  kill_caudium();

  // remove any of the check call outs.
  if(cs_callout) remove_call_out(cs_callout);
}


//! Write a message if watchdog debug is enabled.
//! If debug is enabled, the string is passed to flex_write()
//!
//! @param s
//! The string to write
void flex_debug(string s)
{
	if(options->watchdog_debug)
		flex_write(s);
}


//! Write a message to stderr appended to a watchdog identifier
//!
//! @param s
//! The string to write
void flex_write(string s)
{
  stderr->write("Watchdog: "+s);
}
