// Copyright 2017 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. package main import ( "os" "os/exec" "syscall" "time" "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" ) // ManagerCmd encapsulates a single instance of syz-manager process. // It automatically restarts syz-manager if it exits unexpectedly, // and supports graceful shutdown via SIGINT. type ManagerCmd struct { name string log string errorf Errorf bin string args []string closing chan bool } type Errorf func(msg string, args ...interface{}) // NewManagerCmd starts new syz-manager process. // name - name for logging. // log - manager log file with stdout/stderr. // bin/args - process binary/args. func NewManagerCmd(name, log string, errorf Errorf, bin string, args ...string) *ManagerCmd { mc := &ManagerCmd{ name: name, log: log, errorf: errorf, bin: bin, args: args, closing: make(chan bool), } go mc.loop() return mc } // Close gracefully shutdowns the process and waits for its termination. func (mc *ManagerCmd) Close() { mc.closing <- true <-mc.closing } func (mc *ManagerCmd) loop() { const ( restartPeriod = 10 * time.Minute // don't restart crashing manager more frequently than that interruptTimeout = time.Minute // give manager that much time to react to SIGINT ) var ( cmd *exec.Cmd started time.Time interrupted time.Time stopped = make(chan error, 1) closing = mc.closing ticker1 = time.NewTicker(restartPeriod) ticker2 = time.NewTicker(interruptTimeout) ) defer func() { ticker1.Stop() ticker2.Stop() }() for closing != nil || cmd != nil { if cmd == nil { // cmd is not running // don't restart too frequently (in case it instantly exits with an error) if time.Since(started) > restartPeriod { started = time.Now() os.Rename(mc.log, mc.log+".old") logfile, err := os.Create(mc.log) if err != nil { mc.errorf("failed to create manager log: %v", err) } else { cmd = osutil.Command(mc.bin, mc.args...) cmd.Stdout = logfile cmd.Stderr = logfile err := cmd.Start() logfile.Close() if err != nil { mc.errorf("failed to start manager: %v", err) cmd = nil } else { log.Logf(1, "%v: started manager", mc.name) go func() { stopped <- cmd.Wait() }() } } } } else { // cmd is running if closing == nil && time.Since(interrupted) > interruptTimeout { log.Logf(1, "%v: killing manager", mc.name) cmd.Process.Kill() interrupted = time.Now() } } select { case <-closing: closing = nil if cmd != nil { log.Logf(1, "%v: stopping manager", mc.name) cmd.Process.Signal(syscall.SIGINT) interrupted = time.Now() } case err := <-stopped: if cmd == nil { mc.errorf("spurious stop signal: %v", err) } if closing != nil { mc.errorf("manager exited unexpectedly: %v", err) } cmd = nil log.Logf(1, "%v: manager exited with %v", mc.name, err) case <-ticker1.C: case <-ticker2.C: } } close(mc.closing) }