package cmd import ( "fmt" "strings" "io" "strconv" "time" "os" "os/exec" "os/signal" "github.com/creack/pty" "golang.org/x/term" "syscall" "github.com/spf13/cobra" ) func init() { rootCmd.AddCommand(timeCmd) } var timeCmd = &cobra.Command{ Use: "timer", Short: "set the duration followed by the binary to be executed", Long: "Set the timer duration after which the kill signal will be sent followed by the binary which should be executed.\n\nExamples:\nkilt timer 30 watch lscpu\nkilt timer 10 less /etc/passwd\nkilt timer 20 yes\nkilt timer 60 htop", Args: cobra.MinimumNArgs(2), // Set at least and Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { fmt.Println("Usage: timer [arguments...]") return } fmt.Println("Timer set to: " + strings.Join(args, " ")) if err:= runBinaryWithTimeout(args); err !=nil { fmt.Println("Error:", err) } }, // Do not parse the flags which are meant for the started process, so that they are not interpreted as an arguemnt for kilt DisableFlagParsing: true, } func resetTerminal() { _ = exec.Command("stty", "sane").Run() } func runBinaryWithTimeout(args []string) error { var durationStr string = args[0] duration, err := strconv.Atoi(durationStr) if err != nil { return fmt.Errorf("Invalid duration: %w", err) } binaryPath := args[1] binaryArgs := args[2:] cmd := exec.Command(binaryPath, binaryArgs...) oldState, err := term.MakeRaw(int(syscall.Stdin)) if err != nil { return fmt.Errorf("Failed to set terminal to raw mode: %w", err) } defer term.Restore(int(syscall.Stdin), oldState) // cmd.Stdout = os.Stdout // cmd.Stderr = os.Stderr // if err := cmd.Start(); err != nil { // return fmt.Errorf("Failed to start binary: %w", err) // } ptyFile, err := pty.Start(cmd) if err != nil { return fmt.Errorf("Failed to start pty: %w", err) } defer func() { _ = ptyFile.Close() term.Restore(int(syscall.Stdin), oldState) resetTerminal() }() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) go func() { <- sigChan _ = cmd.Process.Kill() resetTerminal() os.Exit(1) }() go func () { _, _ = io.Copy(os.Stdout, ptyFile) }() done := make(chan error, 1) go func() { done <- cmd.Wait() }() select { case <- time.After(time.Duration(duration) * time.Second): if err := cmd.Process.Kill(); err != nil { return fmt.Errorf("Failed to kill process: %w", err) } fmt.Println("Process killed as timeout has been reached.") resetTerminal() return nil case err := <-done: resetTerminal() return err } }