...
1
16
17 package exec
18
19 import (
20 "context"
21 "io"
22 "io/fs"
23 osexec "os/exec"
24 "syscall"
25 "time"
26 )
27
28
29 var ErrExecutableNotFound = osexec.ErrNotFound
30
31
32
33 type Interface interface {
34
35
36 Command(cmd string, args ...string) Cmd
37
38
39
40
41
42
43 CommandContext(ctx context.Context, cmd string, args ...string) Cmd
44
45
46 LookPath(file string) (string, error)
47 }
48
49
50
51
52 type Cmd interface {
53
54 Run() error
55
56
57 CombinedOutput() ([]byte, error)
58
59 Output() ([]byte, error)
60 SetDir(dir string)
61 SetStdin(in io.Reader)
62 SetStdout(out io.Writer)
63 SetStderr(out io.Writer)
64 SetEnv(env []string)
65
66
67
68 StdoutPipe() (io.ReadCloser, error)
69 StderrPipe() (io.ReadCloser, error)
70
71
72 Start() error
73 Wait() error
74
75
76
77
78
79 Stop()
80 }
81
82
83
84
85 type ExitError interface {
86 String() string
87 Error() string
88 Exited() bool
89 ExitStatus() int
90 }
91
92
93 type executor struct{}
94
95
96 func New() Interface {
97 return &executor{}
98 }
99
100
101 func (executor *executor) Command(cmd string, args ...string) Cmd {
102 return (*cmdWrapper)(maskErrDotCmd(osexec.Command(cmd, args...)))
103 }
104
105
106 func (executor *executor) CommandContext(ctx context.Context, cmd string, args ...string) Cmd {
107 return (*cmdWrapper)(maskErrDotCmd(osexec.CommandContext(ctx, cmd, args...)))
108 }
109
110
111 func (executor *executor) LookPath(file string) (string, error) {
112 path, err := osexec.LookPath(file)
113 return path, handleError(maskErrDot(err))
114 }
115
116
117 type cmdWrapper osexec.Cmd
118
119 var _ Cmd = &cmdWrapper{}
120
121 func (cmd *cmdWrapper) SetDir(dir string) {
122 cmd.Dir = dir
123 }
124
125 func (cmd *cmdWrapper) SetStdin(in io.Reader) {
126 cmd.Stdin = in
127 }
128
129 func (cmd *cmdWrapper) SetStdout(out io.Writer) {
130 cmd.Stdout = out
131 }
132
133 func (cmd *cmdWrapper) SetStderr(out io.Writer) {
134 cmd.Stderr = out
135 }
136
137 func (cmd *cmdWrapper) SetEnv(env []string) {
138 cmd.Env = env
139 }
140
141 func (cmd *cmdWrapper) StdoutPipe() (io.ReadCloser, error) {
142 r, err := (*osexec.Cmd)(cmd).StdoutPipe()
143 return r, handleError(err)
144 }
145
146 func (cmd *cmdWrapper) StderrPipe() (io.ReadCloser, error) {
147 r, err := (*osexec.Cmd)(cmd).StderrPipe()
148 return r, handleError(err)
149 }
150
151 func (cmd *cmdWrapper) Start() error {
152 err := (*osexec.Cmd)(cmd).Start()
153 return handleError(err)
154 }
155
156 func (cmd *cmdWrapper) Wait() error {
157 err := (*osexec.Cmd)(cmd).Wait()
158 return handleError(err)
159 }
160
161
162 func (cmd *cmdWrapper) Run() error {
163 err := (*osexec.Cmd)(cmd).Run()
164 return handleError(err)
165 }
166
167
168 func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) {
169 out, err := (*osexec.Cmd)(cmd).CombinedOutput()
170 return out, handleError(err)
171 }
172
173 func (cmd *cmdWrapper) Output() ([]byte, error) {
174 out, err := (*osexec.Cmd)(cmd).Output()
175 return out, handleError(err)
176 }
177
178
179 func (cmd *cmdWrapper) Stop() {
180 c := (*osexec.Cmd)(cmd)
181
182 if c.Process == nil {
183 return
184 }
185
186 c.Process.Signal(syscall.SIGTERM)
187
188 time.AfterFunc(10*time.Second, func() {
189 if !c.ProcessState.Exited() {
190 c.Process.Signal(syscall.SIGKILL)
191 }
192 })
193 }
194
195 func handleError(err error) error {
196 if err == nil {
197 return nil
198 }
199
200 switch e := err.(type) {
201 case *osexec.ExitError:
202 return &ExitErrorWrapper{e}
203 case *fs.PathError:
204 return ErrExecutableNotFound
205 case *osexec.Error:
206 if e.Err == osexec.ErrNotFound {
207 return ErrExecutableNotFound
208 }
209 }
210
211 return err
212 }
213
214
215
216 type ExitErrorWrapper struct {
217 *osexec.ExitError
218 }
219
220 var _ ExitError = &ExitErrorWrapper{}
221
222
223 func (eew ExitErrorWrapper) ExitStatus() int {
224 ws, ok := eew.Sys().(syscall.WaitStatus)
225 if !ok {
226 panic("can't call ExitStatus() on a non-WaitStatus exitErrorWrapper")
227 }
228 return ws.ExitStatus()
229 }
230
231
232
233 type CodeExitError struct {
234 Err error
235 Code int
236 }
237
238 var _ ExitError = CodeExitError{}
239
240 func (e CodeExitError) Error() string {
241 return e.Err.Error()
242 }
243
244 func (e CodeExitError) String() string {
245 return e.Err.Error()
246 }
247
248
249 func (e CodeExitError) Exited() bool {
250 return true
251 }
252
253
254 func (e CodeExitError) ExitStatus() int {
255 return e.Code
256 }
257
View as plain text