From 879764ed730eba356771bfbb3a73fc78659135d2 Mon Sep 17 00:00:00 2001 From: Urban Wallasch Date: Fri, 12 Apr 2019 10:11:25 +0200 Subject: [PATCH] * Added option to let parent wait for background child. * Fixed child attempts to reopen logfile bug. * Added gogopherd.sh init script. --- config.go | 6 +++++ gogopherd.cfg.example | 2 ++ gogopherd.go | 39 +++++++++++++++++---------- gogopherd.sh | 63 +++++++++++++++++++++++++++++++++++++++++++ logger.go | 2 +- 5 files changed, 97 insertions(+), 15 deletions(-) create mode 100755 gogopherd.sh diff --git a/config.go b/config.go index 0403cb0..d7c53a1 100644 --- a/config.go +++ b/config.go @@ -38,6 +38,7 @@ var cfg = struct { nworker int logfile string bkguser string + bkgwait bool verbose bool }{ iface: "localhost", @@ -57,6 +58,7 @@ var cfg = struct { njobs: 50, logfile: "", bkguser: "", + bkgwait: false, verbose: false, } @@ -111,6 +113,8 @@ func parseConfigFile(filename string) error { cfg.logfile = val case "bkguser": cfg.bkguser = val + case "bkgwait": + cfg.bkgwait = strToBool(val) case "verbose": cfg.verbose = strToBool(val) default: @@ -143,6 +147,7 @@ func initialize() { flag.IntVar(&cfg.njobs, "lj", cfg.njobs, "set worker job queue size to `num`") flag.StringVar(&cfg.logfile, "L", cfg.logfile, "write log to `file`; empty for stderr") flag.StringVar(&cfg.bkguser, "b", cfg.bkguser, "restart the process in the background as `user`") + flag.BoolVar(&cfg.bkgwait, "w", cfg.bkgwait, "wait for background process") flag.BoolVar(&cfg.verbose, "v", cfg.verbose, "produce verbose output") flag.Parse() if help { @@ -194,6 +199,7 @@ func initialize() { tracer.Print("njobs: ", cfg.njobs) tracer.Print("logfile: ", cfg.logfile) tracer.Print("bkguser: ", cfg.bkguser) + tracer.Print("bkgwait: ", cfg.bkgwait) tracer.Print("verbose: ", cfg.verbose) tracer.Print("gogopherd version ", version) } diff --git a/gogopherd.cfg.example b/gogopherd.cfg.example index 5d94518..e4e1d20 100644 --- a/gogopherd.cfg.example +++ b/gogopherd.cfg.example @@ -41,6 +41,8 @@ logfile = "" # Daemonize as this user; leave empty for foreground operation bkguser = "" +# Wait for background process started with bkguser +bkgwait = false # Produce verbose output verbose = false diff --git a/gogopherd.go b/gogopherd.go index 30773e3..3ccc609 100644 --- a/gogopherd.go +++ b/gogopherd.go @@ -31,6 +31,7 @@ import ( ) var shutting_down bool = false +var isdaemon bool = false func createIndex(dirname string, selector string) (string, error) { fi, err := ioutil.ReadDir(dirname) @@ -178,7 +179,7 @@ func handleRequest(conn net.Conn) { return } req = strings.TrimSpace(req) - logger.Print("[", conn.RemoteAddr(),"] '", req, "'") + logger.Print("[", conn.RemoteAddr(), "] '", req, "'") // canonicalize and validate referenced path rpath, err := validatePath(cfg.docroot, filepath.Join(cfg.docroot, filepath.FromSlash(req))) if check(err, "validatePath "+rpath) != nil { @@ -283,25 +284,25 @@ func serveTCP(listener net.Listener) { return } if checkFatal(err, "Accept") == nil { - logger.Print("[", conn.RemoteAddr(),"] connect") + logger.Print("[", conn.RemoteAddr(), "] connect") jobs <- conn } <-throttle } } -func daemonize(asuser string, stdfiles []*os.File, xfiles []*os.File, env []string) (int, error) { +func daemonize(asuser string, stdfiles []*os.File, xfiles []*os.File, env []string) (*exec.Cmd, error) { userCurrent, err := user.Current() if err != nil { - return -1, err + return nil, err } userRequired, err := user.Lookup(asuser) if err != nil { - return -1, err + return nil, err } executable, err := filepath.Abs(os.Args[0]) if err != nil { - return -1, err + return nil, err } cmd := exec.Command(executable) cmd.Args = append([]string{executable}, os.Args[1:]...) @@ -317,6 +318,9 @@ func daemonize(asuser string, stdfiles []*os.File, xfiles []*os.File, env []stri Credential: &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}, } } + if cfg.bkgwait { + cmd.SysProcAttr.Pdeathsig = syscall.SIGTERM + } if len(stdfiles) > 0 { cmd.Stdin = stdfiles[0] } @@ -329,12 +333,14 @@ func daemonize(asuser string, stdfiles []*os.File, xfiles []*os.File, env []stri cmd.ExtraFiles = xfiles err = cmd.Start() if err != nil { - return -1, err + return nil, err } - return cmd.Process.Pid, nil + return cmd, nil } func main() { + const isdaemon_mark = "_ISDAEMON_" + isdaemon = os.Getenv(isdaemon_mark) == "1" initialize() if cfg.bkguser == "" { @@ -344,8 +350,7 @@ func main() { checkFatal(err, "Listen") go serveTCP(listener) } else { - const isdaemon = "_ISDAEMON_" - if os.Getenv(isdaemon) != "1" { + if !isdaemon { // parent service := cfg.iface + ":" + cfg.port address, err := net.ResolveTCPAddr("tcp", service) @@ -360,15 +365,21 @@ func main() { fstd := []*os.File{ nil, nil, - os.Stderr, // inherit our logging fd + os.Stderr, // inherit our logging fd } env := []string{ "PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"", - fmt.Sprintf("%s=1", isdaemon), + fmt.Sprintf("%s=1", isdaemon_mark), } - cpid, err := daemonize(cfg.bkguser, fstd, fxtra, env) + cmd, err := daemonize(cfg.bkguser, fstd, fxtra, env) checkFatal(err, "daemonize") - tracer.Print("background process spawned, child pid=", cpid, ", bye.") + cpid := cmd.Process.Pid + tracer.Print("background process spawned, child pid=", cpid) + if cfg.bkgwait { + err = cmd.Wait() + logger.Print("child process termintated (", err, ")") + } + tracer.Print("parent: bye.") os.Exit(0) } else { // child diff --git a/gogopherd.sh b/gogopherd.sh new file mode 100755 index 0000000..1354cf6 --- /dev/null +++ b/gogopherd.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: gopherd +# Required-Start: $network +# Required-Stop: $network +# Default-Start: 2 3 4 5 +# Default-Stop: +# Short-Description: Start gopher server +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin + +. /lib/lsb/init-functions + +PREFIX=/usr/local + +DAEMON="$PREFIX/bin/gogopherd" +CFGFILE="$PREFIX/etc/gogopherd.cfg" +PIDFILE=/var/run/gogopherd.pid +GOGOPTS="$GOGOPTS -c $CFGFILE" + +test -x $DAEMON || exit 5 +test -f $CFGFILE || exit 5 + +LOCKFILE=/run/lock/gogopherd + +case $1 in + start) + log_daemon_msg "Starting gopher server" "gogopherd" + ( + flock -w 180 9 + start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON -- $GOGOPTS + ) 9>$LOCKFILE + log_end_msg $? + ;; + stop) + log_daemon_msg "Stopping gopher server" "gogopherd" + start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE --remove-pidfile --retry=TERM/30/KILL/5 --exec $DAEMON + log_end_msg $? + rm -f $PIDFILE + ;; + restart|force-reload) + $0 stop && sleep 2 && $0 start + ;; + try-restart) + if $0 status >/dev/null; then + $0 restart + else + exit 0 + fi + ;; + reload) + exit 3 + ;; + status) + status_of_proc $DAEMON "gogopherd" + ;; + *) + echo "Usage: $0 {start|stop|restart|try-restart|force-reload|status}" + exit 2 + ;; +esac diff --git a/logger.go b/logger.go index 0088207..773249a 100644 --- a/logger.go +++ b/logger.go @@ -25,7 +25,7 @@ var ( ) func initLogger(verbose bool, logfilename string) { - if len(logfilename) > 0 { + if !isdaemon && len(logfilename) > 0 { logfile, err := os.OpenFile(logfilename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { log.Print("Failed to open log file", logfilename, ":", err) -- 2.30.2