#!/usr/bin/python

'''
sagator.py
(c) 2003-2008 Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>

 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.

'''

__pychecker__ = 'unusednames=_ \
                 blacklist=StringIO \
                 maxreturns=20 maxlines=300 \
                 no-local no-argsused no-stringiter no-abstract no-isliteral \
                 no-stdlib no-callinit no-badexcept'

# Check unwanted fds. This code need to be before loading configuration,
# because configuration opens new fds (sockets, files, ...).
import os
try:
  maxfds = os.sysconf(os.sysconf_names['SC_OPEN_MAX'])
except (AttributeError, ValueError):
  maxfds = 256
alien_fds = {}
for fdi in range(3, maxfds):
  try:
    f_stat = os.fstat(fdi)
    alien_fds[fdi] = (f_stat.st_dev, f_stat.st_ino)
  except OSError:
    pass

from aglib import *
import signal, time, avlib

# load config
try:
  from etc import *
except ImportError,import_es:
  if str(import_es)[-6:]=="etc":
    print "ERROR! Config file not found! Exiting now."
    sys.exit(1)
  else:
    raise

def wait_for_pid_remove(pidfile):
    for t in range(50):
      time.sleep(0.1)
      if not os.path.isfile(pidfile):
        return 0
    print "PID file has been not removed! SAGATOR is still running?"
    return 1

if __name__ == '__main__':
  debug.set_level(DEBUG_LEVEL)
  try:
    opts,files = getopt.gnu_getopt(sys.argv[1:], 'hnc:l:',
      ['help', 'config=', 'daemon', 'nodaemon', 'logfile=',
       'debug=', 'test', 'wait', 'kill'])
  except getopt.GetoptError,(msg,opt):
    print "Error:", msg
    sys.exit(1)
  if files:
    print "Unknown parameter(s):", ' '.join(files)
    sys.exit(1)
  for key,value in opts:
    if key in ('--help', '-h'):
      print "SAGATOR",SG_VER_REL
      print "(c) Jan ONDREJ (SAL) <ondrejj(at)salstar.sk>"
      print "Licensed under GNU GPL."
      print ""
      print "Params: --help        this help"
      print "        --config=f    load \"f\" as alternate config "\
            "(without .py extension)"
      print "        --nodaemon    don't daemonize after startup"
      print "        --logfile=l   filename for logging ('-' for stdout)"
      print "        --debug=l     set debug level to l"
      print "        --test        test configuration and exit"
      print "        --kill        kill all processes and "\
                                   "wait for pid file removation"
      print "        --wait        wait for pid file removation"
      sys.exit(0)
    elif key in ('--config', '-c'):
      try:
        exec("from %s import *" % value)
      except ImportError, import_es:
        print "ImportError:", import_es
    elif key=='--daemon':
      globals.daemon = True
    elif key in ('--nodaemon', '-n'):
      globals.daemon = False
      LOGFILE='-'
    elif key in ('--logfile', '-l'):
      LOGFILE=value
    elif key=='--debug':
      debug.set_level(int(value))
    elif key=='--test':
      sys.exit(0)
    elif key=='--wait':
      sys.exit(wait_for_pid_remove(PID_FILE))
    elif key=='--kill':
      try:
        for pid in open(PID_FILE).readlines():
          os.kill(int(pid.strip()), signal.SIGTERM)
      except OSError, eces:
        print "Process not running [%s]?" % eces
        sys.exit(1)
      except IOError, eces:
        print "Can't open PID file %s [%s]." % (PID_FILE, eces)
        sys.exit(1)
      sys.exit(wait_for_pid_remove(PID_FILE))
  debug.set_logfile(LOGFILE)
  safe.ROOT_PATH = CHROOT
  globals.setuidgid(USER, GROUP)
  globals.SRV = SRV
  globals.pid_file = PID_FILE
  avlib.smtp.SMTP_SERVER = SMTP_SERVER
  signal.signal(signal.SIGUSR2, sigusr2)
  try:
    # start services
    if globals.daemon:
      # first fork
      if os.fork()>0:
        os._exit(0)
      # try to create a new SID for the child process
      try:
        os.setsid()
      except OSError, e:
        debug.echo(1, "Unable to change process SID: %s" % e)
      # second fork, we never acquire a terminal
      if os.fork()>0:
        os._exit(0)
      # change current directory
      os.chdir("/")
      # try to close all unwanted fds
      for i, inode in alien_fds.items():
        try:
          debug.echo(4, "Closing alien fd: %d [%s]" % (i, inode))
          os.close(i)
        except OSError, e:
          debug.echo(1, "Error closing alien fd: %d [%s], %s" % (i, inode, e))
      os.close(0) # close stdin
      os.open('/dev/null', os.O_RDONLY)
      debug.dup()
    debug.echo(1, "SAGATOR %s starting at %s" \
      % (SG_VER_REL, time.strftime("%c", time.localtime())))
    for srv in SRV:
      pids=srv.start()
    signal.signal(signal.SIGHUP,sighup)
    signal.signal(signal.SIGTERM,sigterm)
    signal.signal(signal.SIGCHLD,sigchld)
    if globals.daemon and globals.pid_file:
      # open PID file
      try:
        pidf=open(globals.pid_file, 'w')
        pidf.write(str(os.getpid()))
        pidf.close()
      except Exception, e:
        debug.echo(1, "Error writing pid file %s: %s" % (globals.pid_file, e))
    service_pids=[srv.childs for srv in SRV]
    debug.echo(6, "Services finished, pids: %s" % service_pids)
    fork_time=time.time()
    while True:
      time.sleep(60)
      for srv in SRV:
        debug.echo(9, "%s: processes %d/%d %s"
                      % (srv.name, len(srv.childs),
                         srv.MIN_CHILDS, srv.childs))
        if len(srv.childs)<srv.MIN_CHILDS:
          delta = fork_time+30-time.time()
          if delta>0:
            debug.echo(1,"%s: WARNING: Respawning too fast!"
                         " Please wait at least %d seconds!"
                         % (srv.name,delta))
          else:
            # start another child
            debug.echo(1, "%s: A new child was started ... [%s]"
                          % (srv.name, srv.fork()))
            fork_time = time.time()
  except KeyboardInterrupt:
    for srv in SRV:
      srv.stop()
    sigterm('KeyboardInterrupt') # kill self
