diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:19:13 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:19:13 +0000 |
commit | ccd992355df7192993c666236047820244914598 (patch) | |
tree | f00fea65147227b7743083c6148396f74cd66935 /src/cmd/go/internal/script/state.go | |
parent | Initial commit. (diff) | |
download | golang-1.21-ccd992355df7192993c666236047820244914598.tar.xz golang-1.21-ccd992355df7192993c666236047820244914598.zip |
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/go/internal/script/state.go')
-rw-r--r-- | src/cmd/go/internal/script/state.go | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/src/cmd/go/internal/script/state.go b/src/cmd/go/internal/script/state.go new file mode 100644 index 0000000..548f673 --- /dev/null +++ b/src/cmd/go/internal/script/state.go @@ -0,0 +1,236 @@ +// Copyright 2022 The Go 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 script + +import ( + "bytes" + "context" + "fmt" + "internal/txtar" + "io" + "io/fs" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// A State encapsulates the current state of a running script engine, +// including the script environment and any running background commands. +type State struct { + engine *Engine // the engine currently executing the script, if any + + ctx context.Context + cancel context.CancelFunc + file string + log bytes.Buffer + + workdir string // initial working directory + pwd string // current working directory during execution + env []string // environment list (for os/exec) + envMap map[string]string // environment mapping (matches env) + stdout string // standard output from last 'go' command; for 'stdout' command + stderr string // standard error from last 'go' command; for 'stderr' command + + background []backgroundCmd +} + +type backgroundCmd struct { + *command + wait WaitFunc +} + +// NewState returns a new State permanently associated with ctx, with its +// initial working directory in workdir and its initial environment set to +// initialEnv (or os.Environ(), if initialEnv is nil). +// +// The new State also contains pseudo-environment-variables for +// ${/} and ${:} (for the platform's path and list separators respectively), +// but does not pass those to subprocesses. +func NewState(ctx context.Context, workdir string, initialEnv []string) (*State, error) { + absWork, err := filepath.Abs(workdir) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithCancel(ctx) + + // Make a fresh copy of the env slice to avoid aliasing bugs if we ever + // start modifying it in place; this also establishes the invariant that + // s.env contains no duplicates. + env := cleanEnv(initialEnv, absWork) + + envMap := make(map[string]string, len(env)) + + // Add entries for ${:} and ${/} to make it easier to write platform-independent + // paths in scripts. + envMap["/"] = string(os.PathSeparator) + envMap[":"] = string(os.PathListSeparator) + + for _, kv := range env { + if k, v, ok := strings.Cut(kv, "="); ok { + envMap[k] = v + } + } + + s := &State{ + ctx: ctx, + cancel: cancel, + workdir: absWork, + pwd: absWork, + env: env, + envMap: envMap, + } + s.Setenv("PWD", absWork) + return s, nil +} + +// CloseAndWait cancels the State's Context and waits for any background commands to +// finish. If any remaining background command ended in an unexpected state, +// Close returns a non-nil error. +func (s *State) CloseAndWait(log io.Writer) error { + s.cancel() + wait, err := Wait().Run(s) + if wait != nil { + panic("script: internal error: Wait unexpectedly returns its own WaitFunc") + } + if flushErr := s.flushLog(log); err == nil { + err = flushErr + } + return err +} + +// Chdir changes the State's working directory to the given path. +func (s *State) Chdir(path string) error { + dir := s.Path(path) + if _, err := os.Stat(dir); err != nil { + return &fs.PathError{Op: "Chdir", Path: dir, Err: err} + } + s.pwd = dir + s.Setenv("PWD", dir) + return nil +} + +// Context returns the Context with which the State was created. +func (s *State) Context() context.Context { + return s.ctx +} + +// Environ returns a copy of the current script environment, +// in the form "key=value". +func (s *State) Environ() []string { + return append([]string(nil), s.env...) +} + +// ExpandEnv replaces ${var} or $var in the string according to the values of +// the environment variables in s. References to undefined variables are +// replaced by the empty string. +func (s *State) ExpandEnv(str string, inRegexp bool) string { + return os.Expand(str, func(key string) string { + e := s.envMap[key] + if inRegexp { + // Quote to literal strings: we want paths like C:\work\go1.4 to remain + // paths rather than regular expressions. + e = regexp.QuoteMeta(e) + } + return e + }) +} + +// ExtractFiles extracts the files in ar to the state's current directory, +// expanding any environment variables within each name. +// +// The files must reside within the working directory with which the State was +// originally created. +func (s *State) ExtractFiles(ar *txtar.Archive) error { + wd := s.workdir + + // Add trailing separator to terminate wd. + // This prevents extracting to outside paths which prefix wd, + // e.g. extracting to /home/foobar when wd is /home/foo + if wd == "" { + panic("s.workdir is unexpectedly empty") + } + if !os.IsPathSeparator(wd[len(wd)-1]) { + wd += string(filepath.Separator) + } + + for _, f := range ar.Files { + name := s.Path(s.ExpandEnv(f.Name, false)) + + if !strings.HasPrefix(name, wd) { + return fmt.Errorf("file %#q is outside working directory", f.Name) + } + + if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil { + return err + } + if err := os.WriteFile(name, f.Data, 0666); err != nil { + return err + } + } + + return nil +} + +// Getwd returns the directory in which to run the next script command. +func (s *State) Getwd() string { return s.pwd } + +// Logf writes output to the script's log without updating its stdout or stderr +// buffers. (The output log functions as a kind of meta-stderr.) +func (s *State) Logf(format string, args ...any) { + fmt.Fprintf(&s.log, format, args...) +} + +// flushLog writes the contents of the script's log to w and clears the log. +func (s *State) flushLog(w io.Writer) error { + _, err := w.Write(s.log.Bytes()) + s.log.Reset() + return err +} + +// LookupEnv retrieves the value of the environment variable in s named by the key. +func (s *State) LookupEnv(key string) (string, bool) { + v, ok := s.envMap[key] + return v, ok +} + +// Path returns the absolute path in the host operating system for a +// script-based (generally slash-separated and relative) path. +func (s *State) Path(path string) string { + if filepath.IsAbs(path) { + return filepath.Clean(path) + } + return filepath.Join(s.pwd, path) +} + +// Setenv sets the value of the environment variable in s named by the key. +func (s *State) Setenv(key, value string) error { + s.env = cleanEnv(append(s.env, key+"="+value), s.pwd) + s.envMap[key] = value + return nil +} + +// Stdout returns the stdout output of the last command run, +// or the empty string if no command has been run. +func (s *State) Stdout() string { return s.stdout } + +// Stderr returns the stderr output of the last command run, +// or the empty string if no command has been run. +func (s *State) Stderr() string { return s.stderr } + +// cleanEnv returns a copy of env with any duplicates removed in favor of +// later values and any required system variables defined. +// +// If env is nil, cleanEnv copies the environment from os.Environ(). +func cleanEnv(env []string, pwd string) []string { + // There are some funky edge-cases in this logic, especially on Windows (with + // case-insensitive environment variables and variables with keys like "=C:"). + // Rather than duplicating exec.dedupEnv here, cheat and use exec.Cmd directly. + cmd := &exec.Cmd{Env: env} + cmd.Dir = pwd + return cmd.Environ() +} |