Merge branch '20705-crunchstat-warn-missing-data'
[arvados.git] / lib / crunchstat / command.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package crunchstat
6
7 import (
8         "flag"
9         "fmt"
10         "io"
11         "log"
12         "os/exec"
13         "syscall"
14         "time"
15
16         "git.arvados.org/arvados.git/lib/cmd"
17 )
18
19 var Command = command{}
20
21 type command struct{}
22
23 func (command) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
24         flags := flag.NewFlagSet(prog, flag.ExitOnError)
25         poll := flags.Duration("poll", 10*time.Second, "reporting interval")
26         debug := flags.Bool("debug", false, "show additional debug info")
27         dump := flags.String("dump", "", "save snapshot of OS files in given `directory` (for creating test cases)")
28         getVersion := flags.Bool("version", false, "print version information and exit")
29
30         if ok, code := cmd.ParseFlags(flags, prog, args, "program [args ...]", stderr); !ok {
31                 return code
32         } else if *getVersion {
33                 fmt.Printf("%s %s\n", prog, cmd.Version.String())
34                 return 0
35         } else if flags.NArg() == 0 {
36                 fmt.Fprintf(stderr, "missing required argument: program (try -help)\n")
37                 return 2
38         }
39
40         reporter := &Reporter{
41                 Logger:     log.New(stderr, prog+": ", 0),
42                 Debug:      *debug,
43                 PollPeriod: *poll,
44         }
45         reporter.Logger.Printf("%s %s", prog, cmd.Version.String())
46         reporter.Logger.Printf("running %v", flags.Args())
47         cmd := exec.Command(flags.Arg(0), flags.Args()[1:]...)
48
49         // Child process will use our stdin and stdout pipes (we close
50         // our copies below)
51         cmd.Stdin = stdin
52         cmd.Stdout = stdout
53         // Child process stderr and our stats will both go to stderr
54         cmd.Stderr = stderr
55
56         if err := cmd.Start(); err != nil {
57                 reporter.Logger.Printf("error in cmd.Start: %v", err)
58                 return 1
59         }
60         reporter.Pid = func() int {
61                 return cmd.Process.Pid
62         }
63         reporter.Start()
64         defer reporter.Stop()
65         if stdin, ok := stdin.(io.Closer); ok {
66                 stdin.Close()
67         }
68         if stdout, ok := stdout.(io.Closer); ok {
69                 stdout.Close()
70         }
71
72         failed := false
73         if *dump != "" {
74                 err := reporter.dumpSourceFiles(*dump)
75                 if err != nil {
76                         fmt.Fprintf(stderr, "error dumping source files: %s\n", err)
77                         failed = true
78                 }
79         }
80
81         err := cmd.Wait()
82
83         if err, ok := err.(*exec.ExitError); ok {
84                 // The program has exited with an exit code != 0
85
86                 // This works on both Unix and Windows. Although
87                 // package syscall is generally platform dependent,
88                 // WaitStatus is defined for both Unix and Windows and
89                 // in both cases has an ExitStatus() method with the
90                 // same signature.
91                 if status, ok := err.Sys().(syscall.WaitStatus); ok {
92                         return status.ExitStatus()
93                 } else {
94                         reporter.Logger.Printf("ExitError without WaitStatus: %v", err)
95                         return 1
96                 }
97         } else if err != nil {
98                 reporter.Logger.Printf("error running command: %v", err)
99                 return 1
100         }
101
102         if failed {
103                 return 1
104         }
105         return 0
106 }