1// Copyright 2019 The ChromiumOS Authors 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package main 6 7import ( 8 "bytes" 9 "fmt" 10 "io" 11 "os" 12 "strings" 13 "syscall" 14 "time" 15) 16 17const artifactsTmpDirEnvName = "CROS_ARTIFACTS_TMP_DIR" 18 19type env interface { 20 umask(int) int 21 getenv(key string) (string, bool) 22 environ() []string 23 getwd() string 24 stdin() io.Reader 25 stdout() io.Writer 26 stderr() io.Writer 27 run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error 28 runWithTimeout(cmd *command, duration time.Duration) error 29 exec(cmd *command) error 30} 31 32type processEnv struct { 33 wd string 34} 35 36func newProcessEnv() (env, error) { 37 wd, err := os.Getwd() 38 if err != nil { 39 return nil, wrapErrorwithSourceLocf(err, "failed to read working directory") 40 } 41 42 // Note: On Linux, Getwd may resolve to /proc/self/cwd, since it checks the PWD environment 43 // variable. We need to read the link to get the actual working directory. We can't always 44 // do this as we are calculating the path to clang, since following a symlinked cwd first 45 // would make this calculation invalid. 46 // 47 // FIXME(gbiv): It's not clear why always Readlink()ing here an issue. crrev.com/c/1764624 48 // might provide helpful context? 49 if wd == "/proc/self/cwd" { 50 wd, err = os.Readlink(wd) 51 if err != nil { 52 return nil, wrapErrorwithSourceLocf(err, "resolving /proc/self/cwd") 53 } 54 } 55 56 return &processEnv{wd: wd}, nil 57} 58 59var _ env = (*processEnv)(nil) 60 61func (env *processEnv) umask(newmask int) (oldmask int) { 62 return syscall.Umask(newmask) 63} 64 65func (env *processEnv) getenv(key string) (string, bool) { 66 return os.LookupEnv(key) 67} 68 69func (env *processEnv) environ() []string { 70 return os.Environ() 71} 72 73func (env *processEnv) getwd() string { 74 return env.wd 75} 76 77func (env *processEnv) stdin() io.Reader { 78 return os.Stdin 79} 80 81func (env *processEnv) stdout() io.Writer { 82 return os.Stdout 83} 84 85func (env *processEnv) stderr() io.Writer { 86 return os.Stderr 87} 88 89func (env *processEnv) exec(cmd *command) error { 90 return execCmd(env, cmd) 91} 92 93func (env *processEnv) runWithTimeout(cmd *command, duration time.Duration) error { 94 return runCmdWithTimeout(env, cmd, duration) 95} 96 97func (env *processEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 98 return runCmd(env, cmd, stdin, stdout, stderr) 99} 100 101type commandRecordingEnv struct { 102 env 103 stdinReader io.Reader 104 cmdResults []*commandResult 105} 106type commandResult struct { 107 Cmd *command `json:"cmd"` 108 Stdout string `json:"stdout,omitempty"` 109 Stderr string `json:"stderr,omitempty"` 110 ExitCode int `json:"exitcode,omitempty"` 111} 112 113var _ env = (*commandRecordingEnv)(nil) 114 115func (env *commandRecordingEnv) stdin() io.Reader { 116 return env.stdinReader 117} 118 119func (env *commandRecordingEnv) exec(cmd *command) error { 120 // Note: We treat exec the same as run so that we can do work 121 // after the call. 122 return env.run(cmd, env.stdin(), env.stdout(), env.stderr()) 123} 124 125func (env *commandRecordingEnv) runWithTimeout(cmd *command, duration time.Duration) error { 126 return runCmdWithTimeout(env, cmd, duration) 127} 128 129func (env *commandRecordingEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 130 stdoutBuffer := &bytes.Buffer{} 131 stderrBuffer := &bytes.Buffer{} 132 err := env.env.run(cmd, stdin, io.MultiWriter(stdout, stdoutBuffer), io.MultiWriter(stderr, stderrBuffer)) 133 if exitCode, ok := getExitCode(err); ok { 134 env.cmdResults = append(env.cmdResults, &commandResult{ 135 Cmd: cmd, 136 Stdout: stdoutBuffer.String(), 137 Stderr: stderrBuffer.String(), 138 ExitCode: exitCode, 139 }) 140 } 141 return err 142} 143 144type printingEnv struct { 145 env 146} 147 148var _ env = (*printingEnv)(nil) 149 150func (env *printingEnv) exec(cmd *command) error { 151 printCmd(env, cmd) 152 return env.env.exec(cmd) 153} 154 155func (env *printingEnv) runWithTimeout(cmd *command, duration time.Duration) error { 156 printCmd(env, cmd) 157 return env.env.runWithTimeout(cmd, duration) 158} 159 160func (env *printingEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { 161 printCmd(env, cmd) 162 return env.env.run(cmd, stdin, stdout, stderr) 163} 164 165func printCmd(env env, cmd *command) { 166 fmt.Fprintf(env.stderr(), "cd '%s' &&", env.getwd()) 167 if len(cmd.EnvUpdates) > 0 { 168 fmt.Fprintf(env.stderr(), " env '%s'", strings.Join(cmd.EnvUpdates, "' '")) 169 } 170 fmt.Fprintf(env.stderr(), " '%s'", getAbsCmdPath(env, cmd)) 171 if len(cmd.Args) > 0 { 172 fmt.Fprintf(env.stderr(), " '%s'", strings.Join(cmd.Args, "' '")) 173 } 174 io.WriteString(env.stderr(), "\n") 175} 176 177func getCompilerArtifactsDir(env env) string { 178 const defaultArtifactDir = "/tmp" 179 value, _ := env.getenv(artifactsTmpDirEnvName) 180 if value == "" { 181 fmt.Fprintf(env.stdout(), "$%s is not set, artifacts will be written to %s", artifactsTmpDirEnvName, defaultArtifactDir) 182 return defaultArtifactDir 183 } 184 return value 185 186} 187