Home | History | Annotate | Download | only in syz-ci
      1 // Copyright 2017 syzkaller project authors. All rights reserved.
      2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
      3 
      4 package main
      5 
      6 import (
      7 	"os"
      8 	"os/exec"
      9 	"syscall"
     10 	"time"
     11 
     12 	"github.com/google/syzkaller/pkg/log"
     13 	"github.com/google/syzkaller/pkg/osutil"
     14 )
     15 
     16 // ManagerCmd encapsulates a single instance of syz-manager process.
     17 // It automatically restarts syz-manager if it exits unexpectedly,
     18 // and supports graceful shutdown via SIGINT.
     19 type ManagerCmd struct {
     20 	name    string
     21 	log     string
     22 	errorf  Errorf
     23 	bin     string
     24 	args    []string
     25 	closing chan bool
     26 }
     27 
     28 type Errorf func(msg string, args ...interface{})
     29 
     30 // NewManagerCmd starts new syz-manager process.
     31 // name - name for logging.
     32 // log - manager log file with stdout/stderr.
     33 // bin/args - process binary/args.
     34 func NewManagerCmd(name, log string, errorf Errorf, bin string, args ...string) *ManagerCmd {
     35 	mc := &ManagerCmd{
     36 		name:    name,
     37 		log:     log,
     38 		errorf:  errorf,
     39 		bin:     bin,
     40 		args:    args,
     41 		closing: make(chan bool),
     42 	}
     43 	go mc.loop()
     44 	return mc
     45 }
     46 
     47 // Close gracefully shutdowns the process and waits for its termination.
     48 func (mc *ManagerCmd) Close() {
     49 	mc.closing <- true
     50 	<-mc.closing
     51 }
     52 
     53 func (mc *ManagerCmd) loop() {
     54 	const (
     55 		restartPeriod    = 10 * time.Minute // don't restart crashing manager more frequently than that
     56 		interruptTimeout = time.Minute      // give manager that much time to react to SIGINT
     57 	)
     58 	var (
     59 		cmd         *exec.Cmd
     60 		started     time.Time
     61 		interrupted time.Time
     62 		stopped     = make(chan error, 1)
     63 		closing     = mc.closing
     64 		ticker1     = time.NewTicker(restartPeriod)
     65 		ticker2     = time.NewTicker(interruptTimeout)
     66 	)
     67 	defer func() {
     68 		ticker1.Stop()
     69 		ticker2.Stop()
     70 	}()
     71 	for closing != nil || cmd != nil {
     72 		if cmd == nil {
     73 			// cmd is not running
     74 			// don't restart too frequently (in case it instantly exits with an error)
     75 			if time.Since(started) > restartPeriod {
     76 				started = time.Now()
     77 				os.Rename(mc.log, mc.log+".old")
     78 				logfile, err := os.Create(mc.log)
     79 				if err != nil {
     80 					mc.errorf("failed to create manager log: %v", err)
     81 				} else {
     82 					cmd = osutil.Command(mc.bin, mc.args...)
     83 					cmd.Stdout = logfile
     84 					cmd.Stderr = logfile
     85 					err := cmd.Start()
     86 					logfile.Close()
     87 					if err != nil {
     88 						mc.errorf("failed to start manager: %v", err)
     89 						cmd = nil
     90 					} else {
     91 						log.Logf(1, "%v: started manager", mc.name)
     92 						go func() {
     93 							stopped <- cmd.Wait()
     94 						}()
     95 					}
     96 				}
     97 			}
     98 		} else {
     99 			// cmd is running
    100 			if closing == nil && time.Since(interrupted) > interruptTimeout {
    101 				log.Logf(1, "%v: killing manager", mc.name)
    102 				cmd.Process.Kill()
    103 				interrupted = time.Now()
    104 			}
    105 		}
    106 
    107 		select {
    108 		case <-closing:
    109 			closing = nil
    110 			if cmd != nil {
    111 				log.Logf(1, "%v: stopping manager", mc.name)
    112 				cmd.Process.Signal(syscall.SIGINT)
    113 				interrupted = time.Now()
    114 			}
    115 		case err := <-stopped:
    116 			if cmd == nil {
    117 				mc.errorf("spurious stop signal: %v", err)
    118 			}
    119 			if closing != nil {
    120 				mc.errorf("manager exited unexpectedly: %v", err)
    121 			}
    122 			cmd = nil
    123 			log.Logf(1, "%v: manager exited with %v", mc.name, err)
    124 		case <-ticker1.C:
    125 		case <-ticker2.C:
    126 		}
    127 	}
    128 	close(mc.closing)
    129 }
    130