ronnie
2022-10-14 1504bb53e29d3d46222c0b3ea994fc494b48e153
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// 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)
}