-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathcmd_timeout.go
97 lines (77 loc) · 2.07 KB
/
cmd_timeout.go
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
package main
import (
"context"
"flag"
"fmt"
"io"
"os"
"os/exec"
"time"
"github.com/creack/pty"
"golang.org/x/term"
)
// Structure for our options and state.
type timeoutCommand struct {
duration int
}
// Arguments adds per-command args to the object.
func (t *timeoutCommand) Arguments(f *flag.FlagSet) {
f.IntVar(&t.duration, "timeout", 300, "The number of seconds to let the command run for")
}
// Info returns the name of this subcommand.
func (t *timeoutCommand) Info() (string, string) {
return "timeout", `Run a command, with a timeout.
Details:
This command allows you to execute an arbitrary command, but terminate it
after the given number of seconds.
The command is launched with a PTY to allow interactive commands to work
as expected, for example
$ sysbox timeout -duration=10 top`
}
// Execute is invoked if the user specifies `timeout` as the subcommand.
func (t *timeoutCommand) Execute(args []string) int {
if len(args) <= 0 {
fmt.Printf("Usage: timeout command [arg1] [arg2] ..[argN]\n")
return 1
}
// Create a timeout context
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(t.duration)*time.Second)
defer cancel()
// Create the command, using our context.
c := exec.CommandContext(ctx, args[0], args[1:]...)
// Start the command with a pty.
ptmx, err := pty.Start(c)
if err != nil {
fmt.Printf("Failed to launch %s\n", err.Error())
return 1
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }()
// Set stdin in raw mode.
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
// Copy stdin to the pty and the pty to stdout/stderr.
//
// If any of the copy commands complete kill our context, which
// will let us stop awaiting completion.
go func() {
io.Copy(ptmx, os.Stdin)
cancel()
}()
go func() {
io.Copy(os.Stdout, ptmx)
cancel()
}()
go func() {
io.Copy(os.Stderr, ptmx)
cancel()
}()
//
// Wait for our command to complete.
//
<-ctx.Done()
return 0
}