#!/usr/bin/env python
#
# File:      foo.py
#
# Version:   0.2
#
# License:   GPL
#
# Author:    Christian Hopp <chopp@iei.tu-clausthal.de>
#
# Desc.:     This is a test daemon doing absolutely nothing but
#            being noisy in its logfile.
#
# ChangeLog: 2002/07/08 (CH) - Initial version
#            2002/07/12 (CH) - Wait pidfile/fork support
#            2002/07/17 (CH) - network server support
#            2002/07/24 (CH) - Solved zombie problem in network
#                              code by double fork 
#            2002/07/27 (CH) - Included unix socket support
#            2002/07/30 (CH) - Help updated
#                            - Version 0.3
#
# ToDo:      - parse arguments via getopt
#            - multiple ports/sockets
#            - fake protocols (HTTP, FTP, SMTP, NNTP)
#

import sys
import os
import signal
import time
import glob
import errno
import socket

# Initial settings
# ----------------
logfile="/var/log/foo_daemon.log"
pidfile="/var/run/foo_daemon.pid"
waitpid=0
waitfork=0
nofork=0
listen_port=12349
listen_host=""
version="0.2"
unix_socket_file=None
socket_type=socket.AF_INET
helptext="""Usage: foo.py [-s | -k | -h] [--waitpid <time>] [--waitfork <time>]
             [--logfile <filename> ] [--pidfile <filename> ]
             [--host-port <port>] [--host-address <address>]
             [--unix-socket <path>]

   foo.py is a test daemon doing absolutely nothing but being noisy
   in its logfile.

   -h , --help          print this message

   -s , --status        give status of running foo.py

   -k , --kill, --quit  stop running foo.py

   --logfile <filename> send logging output to the specified file
                        [default=/var/log/foo_daemon.log]

   --pidfile <filename> write the pid number to the specified file
                        [default=/var/run/foo_daemon.pid]

   --waitfork <time>    wait <time> seconds before forking [default=0s], the
                        number can be an integer or a float
   --waitpid <time>     wait <time> seconds before writing the pid file
                        [default=0s], the number can be an integer or a float

   --unix-socket <path> The server listens to a unix-socket located at <path>
                        
   --host-port <port>   Port the FooDaemon is listening on [default=12349]

   --host-address <address> Network address the FooDaemon is listening
                            on [default='' means any]

   The time of --waitpid and --waitfork are cummulative, thus, the pid file
   is written after '--waitpid'+'--waitfork' seconds.
"""


def write_to_logfile(s):
    handler=open(logfile,"a+")
    handler.write(time.strftime("%b %d %X")+" foo.py[%i]: "%os.getpid()+s+"\n")
    handler.close()

def exit_signal_handler(signum, frame):
    write_to_logfile('signal handler called with signal %i.'%signum)
    write_to_logfile('exiting!')
    sys.exit(1)

def hello_signal_handler(signum, frame):
    write_to_logfile('signal handler called with signal %i.'%signum)
    write_to_logfile('continuing!')

def alarm_signal_handler(signum, frame):
    raise "ALARM"

def daemon(listen_socket):
    signal.signal(signal.SIGQUIT, exit_signal_handler)
    signal.signal(signal.SIGTERM, exit_signal_handler)
    signal.signal(signal.SIGABRT, exit_signal_handler)
    signal.signal(signal.SIGINT, exit_signal_handler)

    signal.signal(signal.SIGHUP, hello_signal_handler)

    signal.signal(signal.SIGALRM, alarm_signal_handler)

    write_to_logfile('signals set!')

    while 1:
        signal.alarm(10)
        try:
            (conn, address) = listen_socket.accept()
            conn.setblocking(0)
            signal.alarm(0)
        except "ALARM":
            write_to_logfile('still alive!')
            continue

        
        childpid = os.fork()

        if childpid == 0:
            if os.fork() == 0:
                if socket_type==socket.AF_INET:
                    write_to_logfile('got connection from %s!'%str(address))
                elif socket_type==socket.AF_UNIX:
                    write_to_logfile('got connection on socket %s!'%unix_socket_file)
                signal.alarm(10)
                try:
                    conn.send("FOODAEMON/%s\n"%version)
                    signal.alarm(0)
                except "ALARM":
                    if socket_type==socket.AF_INET:
                        write_to_logfile('connection timeout from %s!'%str(address))
                    elif socket_type==socket.AF_UNIX:
                        write_to_logfile('connection timeout on socket %s!'%unix_socket_file)
                except:
                    if socket_type==socket.AF_INET:
                        write_to_logfile('send() failed to %s!'%str(address))
                    elif socket_type==socket.AF_UNIX:
                        write_to_logfile('send() failed on socket %s!'%unix_socket_file)
                conn.close()
                if socket_type==socket.AF_INET:
                    write_to_logfile('connection closed from %s!'%str(address))
                elif socket_type==socket.AF_UNIX:
                    write_to_logfile('connection closed on socket %s!'%unix_socket_file)
                
                sys.exit(0)
            else:
                sys.exit(0)
        else:
            os.waitpid(childpid,0)


def check_if_running():
    try:
        pidfilehandler=open(pidfile)
    except IOError, error:
        if error[0]==errno.ENOENT: # No such file or directory
            return 0
        else:
            print "Error: [%s] %s"%(pidfile,error[1])
            sys.exit(1)

    pidfileline=pidfilehandler.readline()
    pidfilehandler.close()
    if len(glob.glob('/proc/%i'%int(pidfileline)))>0:
        return int(pidfileline)
    else:
        return 0

def startup():
    running_foo=check_if_running()
    if running_foo>0:
        print "Foo daemon is already running on pid %i."%running_foo
        write_to_logfile("trying to start over running"
                         " foo on pid %i."%running_foo)
        sys.exit(1)

    time.sleep(waitfork)

    if socket_type==socket.AF_INET:
        listen_socket=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        listen_socket.bind((listen_host, listen_port))
    elif socket_type==socket.AF_UNIX:
        listen_socket=socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        listen_socket.bind(unix_socket_file)
    else:
        sys.exit(1)
            
    listen_socket.listen(1)

    if not nofork:
        forkpid=os.fork()
    else:
        forkpid=0

    if forkpid>0:
        write_to_logfile("forked away to %i."%forkpid)
        sys.exit(0)
    elif forkpid==0:
        time.sleep(waitpid)
        pidfilehandler=open(pidfile,"w")
        pidfilehandler.write("%s\n"%os.getpid())
        pidfilehandler.close()

        daemon(listen_socket)
    else:
        write_to_logfile("can't fork away!")
        sys.exit(-1)

if sys.argv.count("--logfile")>0:
    if len(sys.argv)>sys.argv.index("--logfile")+1:
        logfile=sys.argv[sys.argv.index("--logfile")+1]
    else:
        print "Error: --logfile requires a string parameter."
        sys.exit(1)

    print "Setting logfile to %s."%logfile

if sys.argv.count("--pidfile")>0:
    if len(sys.argv)>sys.argv.index("--pidfile")+1:
        pidfile=sys.argv[sys.argv.index("--pidfile")+1]
    else:
        print "Error: --pidfile requires a string parameter."
        sys.exit(1)

    print "Setting pidfile to %s."%pidfile

if sys.argv.count("-k")>0 or sys.argv.count("--kill")>0 or sys.argv.count("--quit")>0:
    running_foo=check_if_running()
    if running_foo>0:
        print "Killing foo daemon on pid %i."%running_foo
        write_to_logfile("sending SIGQUIT to pid %i."%running_foo)
        os.kill(running_foo, signal.SIGQUIT)
        sys.exit(0)
    else:
        print "Foo daemon is not running."
        sys.exit(1)
elif sys.argv.count("-s")>0 or sys.argv.count("--status")>0:
    running_foo=check_if_running()
    if running_foo>0:
        print "Foo daemon is running on pid %i."%running_foo
    else:
        print "Foo daemon is not running."
    sys.exit(0)
elif sys.argv.count("-h")>0 or sys.argv.count("--help")>0:
    print helptext
    sys.exit(0)

if sys.argv.count("-n")>0:
    nofork=1
    print "Setting nofork flag."

if sys.argv.count("--unix-socket")>0:
    if len(sys.argv)>sys.argv.index("--unix-socket")+1:
        unix_socket_file=sys.argv[sys.argv.index("--unix-socket")+1]
        socket_type=socket.AF_UNIX
    else:
        print "Error: --unix-socket requires a string as additional parameter."
        sys.exit(1)

    print "Setting filename of unix socket server to %s."%unix_socket_file
        

if sys.argv.count("--host-port")>0:
    if len(sys.argv)>sys.argv.index("--host-port")+1:
        try:
            listen_port=int(sys.argv[sys.argv.index("--host-port")+1])
            socket_type=socket.AF_INET
        except ValueError:
            print "Error: --host-port requires a number."
            sys.exit(1)
    else:
        print "Error: --host-port requires a number as additional parameter."
        sys.exit(1)

    print "Setting port of network server to %i."%listen_port


if sys.argv.count("--host-address")>0:
    if len(sys.argv)>sys.argv.index("--host-address")+1:
        listen_address=sys.argv[sys.argv.index("--host-address")+1]
        socket_type=socket.AF_INET
    else:
        print "Error: --host-address requires a string as additional parameter."
        sys.exit(1)

    print "Setting address of network server to %s."%listen_address


if sys.argv.count("--waitpid")>0:
    if len(sys.argv)>sys.argv.index("--waitpid")+1:
        try:
            waitpid=float(sys.argv[sys.argv.index("--waitpid")+1])
        except ValueError:
            print "Error: --waitpid requires a number."
            sys.exit(1)
    else:
        print "Error: --waitpid requires a number as additional parameter."
        sys.exit(1)

    print "Setting wait time for pidfile writing to %0.2fs."%waitpid

if sys.argv.count("--waitfile")>0:
    if len(sys.argv)>sys.argv.index("--waitfile")+1:
        try:
            waitfork=float(sys.argv[sys.argv.index("--waitfile")+1])
        except ValueError:
            print "Error: --waitfile requires a number."
            sys.exit(1)
    else:
        print "Error: --waitfile requires a number as additional parameter."
        sys.exit(1)

    print "Setting wait time for forking to %0.2fs."%waitfork

startup()
