Golang程序  |  130行  |  3.15 KB

// 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)
}