#!/usr/bin/pike7.6 -M/usr/lib/caudium/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>
 *
 * License: MPL/LGPL
 * 
 * $Id: start-caudium.in,v 1.37.2.1 2004/09/17 09:34:47 kiwi Exp $
 */
 
// 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", Getopt.NO_ARG, ({"--with-watchdog", "--watchdog", "--enable-watchdog"})}),
  ({"nowatchdog", Getopt.NO_ARG, ({"--without-watchdog", "--nowatchdog", "--disable-watchdog"})}),
  ({"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", "-?"})})
});

// loader options (with defaults)
mapping(string:mixed) options = ([
  "gdb" : "gdb",
  "pike" : 0,
  "pikever" : 0,
  "threads" : 1,
  "watchdog": 1,
  "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_" + getuid()),
  "CAUDIUM_WATCHDOG_SOCKET" : getenv("CAUDIUM_WATCHDOG_SOCKET") || ("/tmp/caudium_watchdog_socket_" + getuid()),
  "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/bin/pike7.6", "bin/caudium", "bin/pike"});

// call out for the watchdog
mixed cs_callout;

// object for the watchdog
object conn;

int first_ping=1;

// 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;
  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"});
  }

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

  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 "nowatchdog":
	  options->watchdog=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;
          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;
    }    
  }
  
  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--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..
      .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--without-watchdogB.:  Disable watchdog

      .B--with-debugB.:  Enable 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);
  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;

    object cfr=Config.Files.File(config_dir, cf->name);
    cfr->parse();
    mapping cfg=cfr->retrieve_region("spider#0");
    if(cfg)
    {
      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)
      {
        int port = uri->port;
        if(!port)
          port = 80;
        srvs+=({ ({ uri->host, port}) });
      }
    }
  }
           
  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;
  }

  array srvrs=get_servers() || ({});
  if(srvrs && sizeof(srvrs))
    watchdog_params->site=srvrs[0];
 
  // 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(pid != proc->pid())
  {
    flex_write("Got invalid PID for caudium on the watchdog control socket.\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_site();
}

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_site));
  } 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_site()
{

  if(in_check)
    flex_write("Watchdog is already in a check... skipping.\n");

  if(watchdog_params->site && !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->site[0] && watchdog_params->site[1])) 
      return;

    // if so, can we connect?
    if(!conn->connect(watchdog_params->site[0], 
        (int)(watchdog_params->site[1])))
    {
      flex_write("Unable to connect to " + watchdog_params->site[0] + ":" 
        + watchdog_params->site[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. 
      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"));
      }
    }
    // let's send the request and wait for a response.
    conn->set_nonblocking();
    conn->set_read_callback(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");
    
}

void 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")
  {
    // request was successful.
    remove_call_out(cs_callout);
    conn->close();
    conn=0;

    // restart the watchdog.
    call_out(check_site, 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();
  }
}

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"));
    proc->kill(signum("SIGKILL"));
  }

}

void check_timedout()
{

  conn->close();
  conn=0;

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

  in_check=0;

  kill_caudium();

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

void flex_write(string s)
{
  stderr->write(s);
}
