#!/bin/sh
# hand off to tcl
# The backslash makes the next line a comment in Tcl \
exec tclsh "$0" ${1+"$@"}

# program to control OpenACS servers with daemontools

######################################################################
# initialization
######################################################################

set version {$Id$}

#----------------------------------------------------------------------
# get command line arguments
#----------------------------------------------------------------------

set arg_1 [lindex $argv 0] 
set arg_2 [lindex $argv 1]

#----------------------------------------------------------------------
# prepare system settings
#----------------------------------------------------------------------

set server_list_file "/etc/openacs/services"

set service_dir "/service"
set svc_bin "/usr/bin/svc"
set svstat_bin "/usr/bin/svstat" 

set web_base "/var/lib/aolserver"
set verbose_status ""
set line "----------------------------------------------------------------------\n"

######################################################################
# Procedure Library
######################################################################

#----------------------------------------------------------------------
# inspect services file
#----------------------------------------------------------------------
proc init_server_list {} {
    global servers
    global verbose_status
    global hosts
    global line
    global server_list_file 

    if { [catch {set fileId [open "$server_list_file"]} result] } {
        append verbose_status $line
        append verbose_status "Error reading $server_list_file: $result\n"
        set file_contents ""
    } else {
        set file_contents [string trim [read $fileId] ]	
        close $fileId
    }
    
    while {[regexp {(.[^\n]+)} $file_contents match_fodder row] } {
        # remove each row as it's handled
        set remove_count [string length $row]
        set file_contents [string range $file_contents [expr $remove_count + 1] end]
        
        # skip comment lines
        if { [string equal [string index $row 0] \#] } {
            continue
        }
        
        set row_data [split $row {:}]
        set name        [lindex $row_data 0] 
        # build an array of all servers in the file
        array set servers [list "${name}_name" $name \
                               "${name}_svc_status" "" \
                               "${name}_time"     "" ]

    }

    svstat_getinfo 
    return

}

#----------------------------------------------------------------------
# Inspect the results of svstat
#----------------------------------------------------------------------
proc svstat_getinfo {} {
    global servers
    global verbose_status
    global line
    global svstat_bin
    global service_dir
    if { ![catch {set svstat_txt [eval exec $svstat_bin [glob ${service_dir}/*]]} result] } {
        # pluck out the channel data from svstat:
        # /service/oacs-5-1: up (pid 21952) 9497 seconds, normally down
        #          ^^^^^^^^                ^^^^^ 
        
        append verbose_status $line
        append verbose_status "$svstat_txt\n"

        while {[regexp {(.[^\n]+)} $svstat_txt match_fodder row] } {
            set server ""
            set svc_status ""
            set time ""
            # remove each row as it's handled
            set remove_count [string length $row]
            set svstat_txt [string range $svstat_txt [expr $remove_count + 1] end]
            regexp {/service/(.+):} $row match_fodder server 
            regexp {:\s([a-z]+)\s} $row match_fodder svc_status
            regexp {\s([0-9]+)\sseconds} $row match_fodder time 

            set match_list [array names servers ${server}_name]

            if {[llength $match_list] > 0} {
                array set servers [list ${server}_time $time]
                array set servers [list ${server}_svc_status [string trim $svc_status]]
            }
        }
    } else {
        append verbose_status $line
        append verbose_status "supervise error: $result\n"
    }
    return
}


#----------------------------------------------------------------------
# Help
#----------------------------------------------------------------------

proc help {}  {
    global version
    puts  "$version
Usage: openacs
	status              status report for all sites
        start   {server}    start supervised site
        stop    {server}    stop supervised site
        restart {server}    restart daemontools-supervised site

        add     {server}    Add a server for management by 
                            daemontools
	status verbose      verbose status report for all sites
	help                this message
    "
}


#----------------------------------------------------------------------
# status report, Mr Sulu
#----------------------------------------------------------------------
proc status {} {
    global servers

    puts  ""
    puts  "       all servers       |" 
    puts  "server          |  svc   |" 
    puts  "                | uptime |" 
    #      0123456789012345678901234567890123456789012345678901234567890123456789012345
    puts  "----------------+--------+"
    set server_name_list [array names servers {*_name}] 

    set real_name_list [list]
    foreach server $server_name_list {
        lappend real_name_list [lindex [array get servers $server] 1]
    }

    set server_list [lsort $real_name_list]
    foreach server $server_list {
        set temp_status    [lindex [array get servers ${server}_svc_status] 1]

        if { [string equal [string trim $temp_status] up] } {
            set svc_status [lindex [array get servers ${server}_time] 1] 
        } else {
            set svc_status $temp_status
        }

        set channel    [lindex [array get servers ${server}_channel] 1]  
        set    output [list [format %-16.16s $server]]
	lappend output [format %8.8s    $svc_status]
        lappend output ""
        puts [join $output "|"]
    }
    return
}


#----------------------------------------------------------------------
# start
#----------------------------------------------------------------------
proc start {server} {
    global servers

    # if server is not running, start it
    set status [lindex [array get servers ${server}_svc_status] 1]
    if { ![string equal $status "up"]} {
        puts "Server is not up; starting server"
        svc_cmd $server start
    }

    return
}

#----------------------------------------------------------------------
# svc_cmd
#----------------------------------------------------------------------
proc svc_cmd {server action} {
    global web_base

    global servers
    global svc_bin
    global svstat_bin
    global service_dir
    set flag [string map {
        start "-u"
        stop  "-d"
        reload "-t"
        restart "-t"
    } $action]

    set match_list [array names servers ${server}_name]
    if {[llength $match_list] < 1} {
        puts "${server} is not controlled by daemontools"
        return
    }

    svstat_getinfo
    set status [lindex [array get servers ${server}_svc_status] 1]

    set svc_command "$svc_bin $flag [glob ${service_dir}/${server}]"

    if { [catch {set svstat_txt [eval exec $svc_command]} result] } {           
        puts "Unable to $action server: $result"
        return
    }

    # TODO: should open up the config.tcl and find the actual
    # location of the error log
    # for now, guess at two common locations
    set error_log $web_base/${server}/log/error.log
    if { [catch {set fileId [open $error_log {RDONLY }]} result] } {
        set error_log $web_base/${server}/log/error-${server}.log
        if { [catch {set fileId [open $error_log_2 {RDONLY }]} result] } {
            puts "Problem with the logfile: $result"
        }
    }

    fconfigure $fileId -blocking 0
    # skip to the end of the log file
    read $fileId

    puts "Doing $action ${server} with: $svc_command "
    
    # tried to do this with fileevent and simply couldn't get it to work
    # so instead we used timed loops.  Stop is based on daemontools;
    # start and restart are based on a key phrase in the log file
 
    
    if { [string equal $action stop] } {
        for { set x 1} { $x<120} {incr x} {
            
            # show the log
            set logline [read $fileId]
            if { [string length $logline] > 0 } {
                puts $logline
            }

            # check daemontools
            svstat_getinfo
            set status [lindex [array get servers ${server}_svc_status] 1]
            if { [string equal $status "down"]} {
                puts "${server} is down"
                close $fileId
                return
            }
            # wait a second
            after 1000
        }
        if { $x >= 120 } {
            puts "gave up waiting after 2 minutes"
        }
    } else {
        if { [string equal $status "up"] && [string equal $flag "-u"]} {
            puts "$server is already up"
            return
        }
        if { [string equal $status "down"] && [string equal $flag "-t"]} {
            puts "$server is down; attempting a start instead of a
            restart"
            set flag "-u"            
        }

        puts "scanning $error_log"
        # check the server log every 100 ms 
        for { set x 1} {$x<1200} {incr x} {
            set logline [read $fileId]
            if { [string length $logline] > 0 } {
                puts $logline
                if { [regexp "accepting connections" $logline]} {
                    close $fileId
                    return
                }
            }
            if { [regexp Fatal $logline] } {
                puts "Fatal error - shutting server down"
                eval exec "$svc_bin -d [glob ${service_dir}/${server}]"
                break
            }
            after 100
        }
        if { $x >= 1200 } {
            puts "gave up waiting after 2 minutes"
        }
        
    }     
    close $fileId
    return
}

#----------------------------------------------------------------------
# add
#----------------------------------------------------------------------
proc add {server} {
    global server_list_file

    # open it or create control file
    if { [catch {set fileId [open "$server_list_file" r]} result] } {
        set file_contents ""
    } else {
        set file_contents [string trim [read $fileId] ]	
        close $fileId
    }

    set fileId [open "$server_list_file" a+]
    # check for servername in proper file format
    set already_exists [regexp $server $file_contents]
    if { $already_exists } {
        puts "$server already exists in $server_list_file\n"
    } else {
        # put the new entry at the end
        set fileId [open "$server_list_file" a+]
        puts $fileId $server
    }
    close $fileId
}


######################################################################
# execution body
######################################################################

init_server_list

switch -glob -- $arg_1 {
    start {
        if {[string length $arg_2] >0 } {
            start $arg_2
            exit
        }
    }
    stop -
    restart -
    reload {
        if {[string length $arg_2] >0 } {
            svc_cmd $arg_2 $arg_1
            exit
        }
    }
    status  {   
        status
        if { [string equal $arg_2 verbose] } {
            puts $verbose_status
        }
       exit
    }
    add {
        if {[string length $arg_2] >0 } {
            add $arg_2
            exit
        }
    }
    version -
    help    -
    default {}
}


help
exit

