// Copyright 2019 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package main import ( "bytes" "fmt" "io" "os" "strings" "syscall" "time" ) type env interface { umask(int) int getenv(key string) (string, bool) environ() []string getwd() string stdin() io.Reader stdout() io.Writer stderr() io.Writer run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error runWithTimeout(cmd *command, duration time.Duration) error exec(cmd *command) error } type processEnv struct { wd string } func newProcessEnv() (env, error) { wd, err := os.Getwd() if err != nil { return nil, wrapErrorwithSourceLocf(err, "failed to read working directory") } // Note: On Linux, Getwd may resolve to /proc/self/cwd, since it checks the PWD environment // variable. We need to read the link to get the actual working directory. We can't always // do this as we are calculating the path to clang, since following a symlinked cwd first // would make this calculation invalid. // // FIXME(gbiv): It's not clear why always Readlink()ing here an issue. crrev.com/c/1764624 // might provide helpful context? if wd == "/proc/self/cwd" { wd, err = os.Readlink(wd) if err != nil { return nil, wrapErrorwithSourceLocf(err, "resolving /proc/self/cwd") } } return &processEnv{wd: wd}, nil } var _ env = (*processEnv)(nil) func (env *processEnv) umask(newmask int) (oldmask int) { return syscall.Umask(newmask) } func (env *processEnv) getenv(key string) (string, bool) { return os.LookupEnv(key) } func (env *processEnv) environ() []string { return os.Environ() } func (env *processEnv) getwd() string { return env.wd } func (env *processEnv) stdin() io.Reader { return os.Stdin } func (env *processEnv) stdout() io.Writer { return os.Stdout } func (env *processEnv) stderr() io.Writer { return os.Stderr } func (env *processEnv) exec(cmd *command) error { return execCmd(env, cmd) } func (env *processEnv) runWithTimeout(cmd *command, duration time.Duration) error { return runCmdWithTimeout(env, cmd, duration) } func (env *processEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return runCmd(env, cmd, stdin, stdout, stderr) } type commandRecordingEnv struct { env stdinReader io.Reader cmdResults []*commandResult } type commandResult struct { Cmd *command `json:"cmd"` Stdout string `json:"stdout,omitempty"` Stderr string `json:"stderr,omitempty"` ExitCode int `json:"exitcode,omitempty"` } var _ env = (*commandRecordingEnv)(nil) func (env *commandRecordingEnv) stdin() io.Reader { return env.stdinReader } func (env *commandRecordingEnv) exec(cmd *command) error { // Note: We treat exec the same as run so that we can do work // after the call. return env.run(cmd, env.stdin(), env.stdout(), env.stderr()) } func (env *commandRecordingEnv) runWithTimeout(cmd *command, duration time.Duration) error { return env.runWithTimeout(cmd, duration) } func (env *commandRecordingEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { stdoutBuffer := &bytes.Buffer{} stderrBuffer := &bytes.Buffer{} err := env.env.run(cmd, stdin, io.MultiWriter(stdout, stdoutBuffer), io.MultiWriter(stderr, stderrBuffer)) if exitCode, ok := getExitCode(err); ok { env.cmdResults = append(env.cmdResults, &commandResult{ Cmd: cmd, Stdout: stdoutBuffer.String(), Stderr: stderrBuffer.String(), ExitCode: exitCode, }) } return err } type printingEnv struct { env } var _env = (*printingEnv)(nil) func (env *printingEnv) exec(cmd *command) error { printCmd(env, cmd) return env.env.exec(cmd) } func (env *printingEnv) runWithTimeout(cmd *command, duration time.Duration) error { printCmd(env, cmd) return env.env.runWithTimeout(cmd, duration) } func (env *printingEnv) run(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { printCmd(env, cmd) return env.env.run(cmd, stdin, stdout, stderr) } func printCmd(env env, cmd *command) { fmt.Fprintf(env.stderr(), "cd '%s' &&", env.getwd()) if len(cmd.EnvUpdates) > 0 { fmt.Fprintf(env.stderr(), " env '%s'", strings.Join(cmd.EnvUpdates, "' '")) } fmt.Fprintf(env.stderr(), " '%s'", getAbsCmdPath(env, cmd)) if len(cmd.Args) > 0 { fmt.Fprintf(env.stderr(), " '%s'", strings.Join(cmd.Args, "' '")) } io.WriteString(env.stderr(), "\n") }