diff options
Diffstat (limited to 'vendor/golang.org/x/crypto/ssh/session.go')
-rw-r--r-- | vendor/golang.org/x/crypto/ssh/session.go | 627 |
1 files changed, 627 insertions, 0 deletions
diff --git a/vendor/golang.org/x/crypto/ssh/session.go b/vendor/golang.org/x/crypto/ssh/session.go new file mode 100644 index 0000000..17e2aa8 --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/session.go | |||
@@ -0,0 +1,627 @@ | |||
1 | // Copyright 2011 The Go Authors. All rights reserved. | ||
2 | // Use of this source code is governed by a BSD-style | ||
3 | // license that can be found in the LICENSE file. | ||
4 | |||
5 | package ssh | ||
6 | |||
7 | // Session implements an interactive session described in | ||
8 | // "RFC 4254, section 6". | ||
9 | |||
10 | import ( | ||
11 | "bytes" | ||
12 | "encoding/binary" | ||
13 | "errors" | ||
14 | "fmt" | ||
15 | "io" | ||
16 | "io/ioutil" | ||
17 | "sync" | ||
18 | ) | ||
19 | |||
20 | type Signal string | ||
21 | |||
22 | // POSIX signals as listed in RFC 4254 Section 6.10. | ||
23 | const ( | ||
24 | SIGABRT Signal = "ABRT" | ||
25 | SIGALRM Signal = "ALRM" | ||
26 | SIGFPE Signal = "FPE" | ||
27 | SIGHUP Signal = "HUP" | ||
28 | SIGILL Signal = "ILL" | ||
29 | SIGINT Signal = "INT" | ||
30 | SIGKILL Signal = "KILL" | ||
31 | SIGPIPE Signal = "PIPE" | ||
32 | SIGQUIT Signal = "QUIT" | ||
33 | SIGSEGV Signal = "SEGV" | ||
34 | SIGTERM Signal = "TERM" | ||
35 | SIGUSR1 Signal = "USR1" | ||
36 | SIGUSR2 Signal = "USR2" | ||
37 | ) | ||
38 | |||
39 | var signals = map[Signal]int{ | ||
40 | SIGABRT: 6, | ||
41 | SIGALRM: 14, | ||
42 | SIGFPE: 8, | ||
43 | SIGHUP: 1, | ||
44 | SIGILL: 4, | ||
45 | SIGINT: 2, | ||
46 | SIGKILL: 9, | ||
47 | SIGPIPE: 13, | ||
48 | SIGQUIT: 3, | ||
49 | SIGSEGV: 11, | ||
50 | SIGTERM: 15, | ||
51 | } | ||
52 | |||
53 | type TerminalModes map[uint8]uint32 | ||
54 | |||
55 | // POSIX terminal mode flags as listed in RFC 4254 Section 8. | ||
56 | const ( | ||
57 | tty_OP_END = 0 | ||
58 | VINTR = 1 | ||
59 | VQUIT = 2 | ||
60 | VERASE = 3 | ||
61 | VKILL = 4 | ||
62 | VEOF = 5 | ||
63 | VEOL = 6 | ||
64 | VEOL2 = 7 | ||
65 | VSTART = 8 | ||
66 | VSTOP = 9 | ||
67 | VSUSP = 10 | ||
68 | VDSUSP = 11 | ||
69 | VREPRINT = 12 | ||
70 | VWERASE = 13 | ||
71 | VLNEXT = 14 | ||
72 | VFLUSH = 15 | ||
73 | VSWTCH = 16 | ||
74 | VSTATUS = 17 | ||
75 | VDISCARD = 18 | ||
76 | IGNPAR = 30 | ||
77 | PARMRK = 31 | ||
78 | INPCK = 32 | ||
79 | ISTRIP = 33 | ||
80 | INLCR = 34 | ||
81 | IGNCR = 35 | ||
82 | ICRNL = 36 | ||
83 | IUCLC = 37 | ||
84 | IXON = 38 | ||
85 | IXANY = 39 | ||
86 | IXOFF = 40 | ||
87 | IMAXBEL = 41 | ||
88 | ISIG = 50 | ||
89 | ICANON = 51 | ||
90 | XCASE = 52 | ||
91 | ECHO = 53 | ||
92 | ECHOE = 54 | ||
93 | ECHOK = 55 | ||
94 | ECHONL = 56 | ||
95 | NOFLSH = 57 | ||
96 | TOSTOP = 58 | ||
97 | IEXTEN = 59 | ||
98 | ECHOCTL = 60 | ||
99 | ECHOKE = 61 | ||
100 | PENDIN = 62 | ||
101 | OPOST = 70 | ||
102 | OLCUC = 71 | ||
103 | ONLCR = 72 | ||
104 | OCRNL = 73 | ||
105 | ONOCR = 74 | ||
106 | ONLRET = 75 | ||
107 | CS7 = 90 | ||
108 | CS8 = 91 | ||
109 | PARENB = 92 | ||
110 | PARODD = 93 | ||
111 | TTY_OP_ISPEED = 128 | ||
112 | TTY_OP_OSPEED = 129 | ||
113 | ) | ||
114 | |||
115 | // A Session represents a connection to a remote command or shell. | ||
116 | type Session struct { | ||
117 | // Stdin specifies the remote process's standard input. | ||
118 | // If Stdin is nil, the remote process reads from an empty | ||
119 | // bytes.Buffer. | ||
120 | Stdin io.Reader | ||
121 | |||
122 | // Stdout and Stderr specify the remote process's standard | ||
123 | // output and error. | ||
124 | // | ||
125 | // If either is nil, Run connects the corresponding file | ||
126 | // descriptor to an instance of ioutil.Discard. There is a | ||
127 | // fixed amount of buffering that is shared for the two streams. | ||
128 | // If either blocks it may eventually cause the remote | ||
129 | // command to block. | ||
130 | Stdout io.Writer | ||
131 | Stderr io.Writer | ||
132 | |||
133 | ch Channel // the channel backing this session | ||
134 | started bool // true once Start, Run or Shell is invoked. | ||
135 | copyFuncs []func() error | ||
136 | errors chan error // one send per copyFunc | ||
137 | |||
138 | // true if pipe method is active | ||
139 | stdinpipe, stdoutpipe, stderrpipe bool | ||
140 | |||
141 | // stdinPipeWriter is non-nil if StdinPipe has not been called | ||
142 | // and Stdin was specified by the user; it is the write end of | ||
143 | // a pipe connecting Session.Stdin to the stdin channel. | ||
144 | stdinPipeWriter io.WriteCloser | ||
145 | |||
146 | exitStatus chan error | ||
147 | } | ||
148 | |||
149 | // SendRequest sends an out-of-band channel request on the SSH channel | ||
150 | // underlying the session. | ||
151 | func (s *Session) SendRequest(name string, wantReply bool, payload []byte) (bool, error) { | ||
152 | return s.ch.SendRequest(name, wantReply, payload) | ||
153 | } | ||
154 | |||
155 | func (s *Session) Close() error { | ||
156 | return s.ch.Close() | ||
157 | } | ||
158 | |||
159 | // RFC 4254 Section 6.4. | ||
160 | type setenvRequest struct { | ||
161 | Name string | ||
162 | Value string | ||
163 | } | ||
164 | |||
165 | // Setenv sets an environment variable that will be applied to any | ||
166 | // command executed by Shell or Run. | ||
167 | func (s *Session) Setenv(name, value string) error { | ||
168 | msg := setenvRequest{ | ||
169 | Name: name, | ||
170 | Value: value, | ||
171 | } | ||
172 | ok, err := s.ch.SendRequest("env", true, Marshal(&msg)) | ||
173 | if err == nil && !ok { | ||
174 | err = errors.New("ssh: setenv failed") | ||
175 | } | ||
176 | return err | ||
177 | } | ||
178 | |||
179 | // RFC 4254 Section 6.2. | ||
180 | type ptyRequestMsg struct { | ||
181 | Term string | ||
182 | Columns uint32 | ||
183 | Rows uint32 | ||
184 | Width uint32 | ||
185 | Height uint32 | ||
186 | Modelist string | ||
187 | } | ||
188 | |||
189 | // RequestPty requests the association of a pty with the session on the remote host. | ||
190 | func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error { | ||
191 | var tm []byte | ||
192 | for k, v := range termmodes { | ||
193 | kv := struct { | ||
194 | Key byte | ||
195 | Val uint32 | ||
196 | }{k, v} | ||
197 | |||
198 | tm = append(tm, Marshal(&kv)...) | ||
199 | } | ||
200 | tm = append(tm, tty_OP_END) | ||
201 | req := ptyRequestMsg{ | ||
202 | Term: term, | ||
203 | Columns: uint32(w), | ||
204 | Rows: uint32(h), | ||
205 | Width: uint32(w * 8), | ||
206 | Height: uint32(h * 8), | ||
207 | Modelist: string(tm), | ||
208 | } | ||
209 | ok, err := s.ch.SendRequest("pty-req", true, Marshal(&req)) | ||
210 | if err == nil && !ok { | ||
211 | err = errors.New("ssh: pty-req failed") | ||
212 | } | ||
213 | return err | ||
214 | } | ||
215 | |||
216 | // RFC 4254 Section 6.5. | ||
217 | type subsystemRequestMsg struct { | ||
218 | Subsystem string | ||
219 | } | ||
220 | |||
221 | // RequestSubsystem requests the association of a subsystem with the session on the remote host. | ||
222 | // A subsystem is a predefined command that runs in the background when the ssh session is initiated | ||
223 | func (s *Session) RequestSubsystem(subsystem string) error { | ||
224 | msg := subsystemRequestMsg{ | ||
225 | Subsystem: subsystem, | ||
226 | } | ||
227 | ok, err := s.ch.SendRequest("subsystem", true, Marshal(&msg)) | ||
228 | if err == nil && !ok { | ||
229 | err = errors.New("ssh: subsystem request failed") | ||
230 | } | ||
231 | return err | ||
232 | } | ||
233 | |||
234 | // RFC 4254 Section 6.9. | ||
235 | type signalMsg struct { | ||
236 | Signal string | ||
237 | } | ||
238 | |||
239 | // Signal sends the given signal to the remote process. | ||
240 | // sig is one of the SIG* constants. | ||
241 | func (s *Session) Signal(sig Signal) error { | ||
242 | msg := signalMsg{ | ||
243 | Signal: string(sig), | ||
244 | } | ||
245 | |||
246 | _, err := s.ch.SendRequest("signal", false, Marshal(&msg)) | ||
247 | return err | ||
248 | } | ||
249 | |||
250 | // RFC 4254 Section 6.5. | ||
251 | type execMsg struct { | ||
252 | Command string | ||
253 | } | ||
254 | |||
255 | // Start runs cmd on the remote host. Typically, the remote | ||
256 | // server passes cmd to the shell for interpretation. | ||
257 | // A Session only accepts one call to Run, Start or Shell. | ||
258 | func (s *Session) Start(cmd string) error { | ||
259 | if s.started { | ||
260 | return errors.New("ssh: session already started") | ||
261 | } | ||
262 | req := execMsg{ | ||
263 | Command: cmd, | ||
264 | } | ||
265 | |||
266 | ok, err := s.ch.SendRequest("exec", true, Marshal(&req)) | ||
267 | if err == nil && !ok { | ||
268 | err = fmt.Errorf("ssh: command %v failed", cmd) | ||
269 | } | ||
270 | if err != nil { | ||
271 | return err | ||
272 | } | ||
273 | return s.start() | ||
274 | } | ||
275 | |||
276 | // Run runs cmd on the remote host. Typically, the remote | ||
277 | // server passes cmd to the shell for interpretation. | ||
278 | // A Session only accepts one call to Run, Start, Shell, Output, | ||
279 | // or CombinedOutput. | ||
280 | // | ||
281 | // The returned error is nil if the command runs, has no problems | ||
282 | // copying stdin, stdout, and stderr, and exits with a zero exit | ||
283 | // status. | ||
284 | // | ||
285 | // If the remote server does not send an exit status, an error of type | ||
286 | // *ExitMissingError is returned. If the command completes | ||
287 | // unsuccessfully or is interrupted by a signal, the error is of type | ||
288 | // *ExitError. Other error types may be returned for I/O problems. | ||
289 | func (s *Session) Run(cmd string) error { | ||
290 | err := s.Start(cmd) | ||
291 | if err != nil { | ||
292 | return err | ||
293 | } | ||
294 | return s.Wait() | ||
295 | } | ||
296 | |||
297 | // Output runs cmd on the remote host and returns its standard output. | ||
298 | func (s *Session) Output(cmd string) ([]byte, error) { | ||
299 | if s.Stdout != nil { | ||
300 | return nil, errors.New("ssh: Stdout already set") | ||
301 | } | ||
302 | var b bytes.Buffer | ||
303 | s.Stdout = &b | ||
304 | err := s.Run(cmd) | ||
305 | return b.Bytes(), err | ||
306 | } | ||
307 | |||
308 | type singleWriter struct { | ||
309 | b bytes.Buffer | ||
310 | mu sync.Mutex | ||
311 | } | ||
312 | |||
313 | func (w *singleWriter) Write(p []byte) (int, error) { | ||
314 | w.mu.Lock() | ||
315 | defer w.mu.Unlock() | ||
316 | return w.b.Write(p) | ||
317 | } | ||
318 | |||
319 | // CombinedOutput runs cmd on the remote host and returns its combined | ||
320 | // standard output and standard error. | ||
321 | func (s *Session) CombinedOutput(cmd string) ([]byte, error) { | ||
322 | if s.Stdout != nil { | ||
323 | return nil, errors.New("ssh: Stdout already set") | ||
324 | } | ||
325 | if s.Stderr != nil { | ||
326 | return nil, errors.New("ssh: Stderr already set") | ||
327 | } | ||
328 | var b singleWriter | ||
329 | s.Stdout = &b | ||
330 | s.Stderr = &b | ||
331 | err := s.Run(cmd) | ||
332 | return b.b.Bytes(), err | ||
333 | } | ||
334 | |||
335 | // Shell starts a login shell on the remote host. A Session only | ||
336 | // accepts one call to Run, Start, Shell, Output, or CombinedOutput. | ||
337 | func (s *Session) Shell() error { | ||
338 | if s.started { | ||
339 | return errors.New("ssh: session already started") | ||
340 | } | ||
341 | |||
342 | ok, err := s.ch.SendRequest("shell", true, nil) | ||
343 | if err == nil && !ok { | ||
344 | return errors.New("ssh: could not start shell") | ||
345 | } | ||
346 | if err != nil { | ||
347 | return err | ||
348 | } | ||
349 | return s.start() | ||
350 | } | ||
351 | |||
352 | func (s *Session) start() error { | ||
353 | s.started = true | ||
354 | |||
355 | type F func(*Session) | ||
356 | for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} { | ||
357 | setupFd(s) | ||
358 | } | ||
359 | |||
360 | s.errors = make(chan error, len(s.copyFuncs)) | ||
361 | for _, fn := range s.copyFuncs { | ||
362 | go func(fn func() error) { | ||
363 | s.errors <- fn() | ||
364 | }(fn) | ||
365 | } | ||
366 | return nil | ||
367 | } | ||
368 | |||
369 | // Wait waits for the remote command to exit. | ||
370 | // | ||
371 | // The returned error is nil if the command runs, has no problems | ||
372 | // copying stdin, stdout, and stderr, and exits with a zero exit | ||
373 | // status. | ||
374 | // | ||
375 | // If the remote server does not send an exit status, an error of type | ||
376 | // *ExitMissingError is returned. If the command completes | ||
377 | // unsuccessfully or is interrupted by a signal, the error is of type | ||
378 | // *ExitError. Other error types may be returned for I/O problems. | ||
379 | func (s *Session) Wait() error { | ||
380 | if !s.started { | ||
381 | return errors.New("ssh: session not started") | ||
382 | } | ||
383 | waitErr := <-s.exitStatus | ||
384 | |||
385 | if s.stdinPipeWriter != nil { | ||
386 | s.stdinPipeWriter.Close() | ||
387 | } | ||
388 | var copyError error | ||
389 | for _ = range s.copyFuncs { | ||
390 | if err := <-s.errors; err != nil && copyError == nil { | ||
391 | copyError = err | ||
392 | } | ||
393 | } | ||
394 | if waitErr != nil { | ||
395 | return waitErr | ||
396 | } | ||
397 | return copyError | ||
398 | } | ||
399 | |||
400 | func (s *Session) wait(reqs <-chan *Request) error { | ||
401 | wm := Waitmsg{status: -1} | ||
402 | // Wait for msg channel to be closed before returning. | ||
403 | for msg := range reqs { | ||
404 | switch msg.Type { | ||
405 | case "exit-status": | ||
406 | wm.status = int(binary.BigEndian.Uint32(msg.Payload)) | ||
407 | case "exit-signal": | ||
408 | var sigval struct { | ||
409 | Signal string | ||
410 | CoreDumped bool | ||
411 | Error string | ||
412 | Lang string | ||
413 | } | ||
414 | if err := Unmarshal(msg.Payload, &sigval); err != nil { | ||
415 | return err | ||
416 | } | ||
417 | |||
418 | // Must sanitize strings? | ||
419 | wm.signal = sigval.Signal | ||
420 | wm.msg = sigval.Error | ||
421 | wm.lang = sigval.Lang | ||
422 | default: | ||
423 | // This handles keepalives and matches | ||
424 | // OpenSSH's behaviour. | ||
425 | if msg.WantReply { | ||
426 | msg.Reply(false, nil) | ||
427 | } | ||
428 | } | ||
429 | } | ||
430 | if wm.status == 0 { | ||
431 | return nil | ||
432 | } | ||
433 | if wm.status == -1 { | ||
434 | // exit-status was never sent from server | ||
435 | if wm.signal == "" { | ||
436 | // signal was not sent either. RFC 4254 | ||
437 | // section 6.10 recommends against this | ||
438 | // behavior, but it is allowed, so we let | ||
439 | // clients handle it. | ||
440 | return &ExitMissingError{} | ||
441 | } | ||
442 | wm.status = 128 | ||
443 | if _, ok := signals[Signal(wm.signal)]; ok { | ||
444 | wm.status += signals[Signal(wm.signal)] | ||
445 | } | ||
446 | } | ||
447 | |||
448 | return &ExitError{wm} | ||
449 | } | ||
450 | |||
451 | // ExitMissingError is returned if a session is torn down cleanly, but | ||
452 | // the server sends no confirmation of the exit status. | ||
453 | type ExitMissingError struct{} | ||
454 | |||
455 | func (e *ExitMissingError) Error() string { | ||
456 | return "wait: remote command exited without exit status or exit signal" | ||
457 | } | ||
458 | |||
459 | func (s *Session) stdin() { | ||
460 | if s.stdinpipe { | ||
461 | return | ||
462 | } | ||
463 | var stdin io.Reader | ||
464 | if s.Stdin == nil { | ||
465 | stdin = new(bytes.Buffer) | ||
466 | } else { | ||
467 | r, w := io.Pipe() | ||
468 | go func() { | ||
469 | _, err := io.Copy(w, s.Stdin) | ||
470 | w.CloseWithError(err) | ||
471 | }() | ||
472 | stdin, s.stdinPipeWriter = r, w | ||
473 | } | ||
474 | s.copyFuncs = append(s.copyFuncs, func() error { | ||
475 | _, err := io.Copy(s.ch, stdin) | ||
476 | if err1 := s.ch.CloseWrite(); err == nil && err1 != io.EOF { | ||
477 | err = err1 | ||
478 | } | ||
479 | return err | ||
480 | }) | ||
481 | } | ||
482 | |||
483 | func (s *Session) stdout() { | ||
484 | if s.stdoutpipe { | ||
485 | return | ||
486 | } | ||
487 | if s.Stdout == nil { | ||
488 | s.Stdout = ioutil.Discard | ||
489 | } | ||
490 | s.copyFuncs = append(s.copyFuncs, func() error { | ||
491 | _, err := io.Copy(s.Stdout, s.ch) | ||
492 | return err | ||
493 | }) | ||
494 | } | ||
495 | |||
496 | func (s *Session) stderr() { | ||
497 | if s.stderrpipe { | ||
498 | return | ||
499 | } | ||
500 | if s.Stderr == nil { | ||
501 | s.Stderr = ioutil.Discard | ||
502 | } | ||
503 | s.copyFuncs = append(s.copyFuncs, func() error { | ||
504 | _, err := io.Copy(s.Stderr, s.ch.Stderr()) | ||
505 | return err | ||
506 | }) | ||
507 | } | ||
508 | |||
509 | // sessionStdin reroutes Close to CloseWrite. | ||
510 | type sessionStdin struct { | ||
511 | io.Writer | ||
512 | ch Channel | ||
513 | } | ||
514 | |||
515 | func (s *sessionStdin) Close() error { | ||
516 | return s.ch.CloseWrite() | ||
517 | } | ||
518 | |||
519 | // StdinPipe returns a pipe that will be connected to the | ||
520 | // remote command's standard input when the command starts. | ||
521 | func (s *Session) StdinPipe() (io.WriteCloser, error) { | ||
522 | if s.Stdin != nil { | ||
523 | return nil, errors.New("ssh: Stdin already set") | ||
524 | } | ||
525 | if s.started { | ||
526 | return nil, errors.New("ssh: StdinPipe after process started") | ||
527 | } | ||
528 | s.stdinpipe = true | ||
529 | return &sessionStdin{s.ch, s.ch}, nil | ||
530 | } | ||
531 | |||
532 | // StdoutPipe returns a pipe that will be connected to the | ||
533 | // remote command's standard output when the command starts. | ||
534 | // There is a fixed amount of buffering that is shared between | ||
535 | // stdout and stderr streams. If the StdoutPipe reader is | ||
536 | // not serviced fast enough it may eventually cause the | ||
537 | // remote command to block. | ||
538 | func (s *Session) StdoutPipe() (io.Reader, error) { | ||
539 | if s.Stdout != nil { | ||
540 | return nil, errors.New("ssh: Stdout already set") | ||
541 | } | ||
542 | if s.started { | ||
543 | return nil, errors.New("ssh: StdoutPipe after process started") | ||
544 | } | ||
545 | s.stdoutpipe = true | ||
546 | return s.ch, nil | ||
547 | } | ||
548 | |||
549 | // StderrPipe returns a pipe that will be connected to the | ||
550 | // remote command's standard error when the command starts. | ||
551 | // There is a fixed amount of buffering that is shared between | ||
552 | // stdout and stderr streams. If the StderrPipe reader is | ||
553 | // not serviced fast enough it may eventually cause the | ||
554 | // remote command to block. | ||
555 | func (s *Session) StderrPipe() (io.Reader, error) { | ||
556 | if s.Stderr != nil { | ||
557 | return nil, errors.New("ssh: Stderr already set") | ||
558 | } | ||
559 | if s.started { | ||
560 | return nil, errors.New("ssh: StderrPipe after process started") | ||
561 | } | ||
562 | s.stderrpipe = true | ||
563 | return s.ch.Stderr(), nil | ||
564 | } | ||
565 | |||
566 | // newSession returns a new interactive session on the remote host. | ||
567 | func newSession(ch Channel, reqs <-chan *Request) (*Session, error) { | ||
568 | s := &Session{ | ||
569 | ch: ch, | ||
570 | } | ||
571 | s.exitStatus = make(chan error, 1) | ||
572 | go func() { | ||
573 | s.exitStatus <- s.wait(reqs) | ||
574 | }() | ||
575 | |||
576 | return s, nil | ||
577 | } | ||
578 | |||
579 | // An ExitError reports unsuccessful completion of a remote command. | ||
580 | type ExitError struct { | ||
581 | Waitmsg | ||
582 | } | ||
583 | |||
584 | func (e *ExitError) Error() string { | ||
585 | return e.Waitmsg.String() | ||
586 | } | ||
587 | |||
588 | // Waitmsg stores the information about an exited remote command | ||
589 | // as reported by Wait. | ||
590 | type Waitmsg struct { | ||
591 | status int | ||
592 | signal string | ||
593 | msg string | ||
594 | lang string | ||
595 | } | ||
596 | |||
597 | // ExitStatus returns the exit status of the remote command. | ||
598 | func (w Waitmsg) ExitStatus() int { | ||
599 | return w.status | ||
600 | } | ||
601 | |||
602 | // Signal returns the exit signal of the remote command if | ||
603 | // it was terminated violently. | ||
604 | func (w Waitmsg) Signal() string { | ||
605 | return w.signal | ||
606 | } | ||
607 | |||
608 | // Msg returns the exit message given by the remote command | ||
609 | func (w Waitmsg) Msg() string { | ||
610 | return w.msg | ||
611 | } | ||
612 | |||
613 | // Lang returns the language tag. See RFC 3066 | ||
614 | func (w Waitmsg) Lang() string { | ||
615 | return w.lang | ||
616 | } | ||
617 | |||
618 | func (w Waitmsg) String() string { | ||
619 | str := fmt.Sprintf("Process exited with status %v", w.status) | ||
620 | if w.signal != "" { | ||
621 | str += fmt.Sprintf(" from signal %v", w.signal) | ||
622 | } | ||
623 | if w.msg != "" { | ||
624 | str += fmt.Sprintf(". Reason was: %v", w.msg) | ||
625 | } | ||
626 | return str | ||
627 | } | ||