1
2
3
4
5 package script
6
7 import (
8 "bytes"
9 "context"
10 "fmt"
11 "internal/txtar"
12 "io"
13 "io/fs"
14 "os"
15 "os/exec"
16 "path/filepath"
17 "regexp"
18 "strings"
19 )
20
21
22
23 type State struct {
24 engine *Engine
25
26 ctx context.Context
27 cancel context.CancelFunc
28 file string
29 log bytes.Buffer
30
31 workdir string
32 pwd string
33 env []string
34 envMap map[string]string
35 stdout string
36 stderr string
37
38 background []backgroundCmd
39 }
40
41 type backgroundCmd struct {
42 *command
43 wait WaitFunc
44 }
45
46
47
48
49
50
51
52
53 func NewState(ctx context.Context, workdir string, initialEnv []string) (*State, error) {
54 absWork, err := filepath.Abs(workdir)
55 if err != nil {
56 return nil, err
57 }
58
59 ctx, cancel := context.WithCancel(ctx)
60
61
62
63
64 env := cleanEnv(initialEnv, absWork)
65
66 envMap := make(map[string]string, len(env))
67
68
69
70 envMap["/"] = string(os.PathSeparator)
71 envMap[":"] = string(os.PathListSeparator)
72
73 for _, kv := range env {
74 if k, v, ok := strings.Cut(kv, "="); ok {
75 envMap[k] = v
76 }
77 }
78
79 s := &State{
80 ctx: ctx,
81 cancel: cancel,
82 workdir: absWork,
83 pwd: absWork,
84 env: env,
85 envMap: envMap,
86 }
87 s.Setenv("PWD", absWork)
88 return s, nil
89 }
90
91
92
93
94 func (s *State) CloseAndWait(log io.Writer) error {
95 s.cancel()
96 wait, err := Wait().Run(s)
97 if wait != nil {
98 panic("script: internal error: Wait unexpectedly returns its own WaitFunc")
99 }
100 if flushErr := s.flushLog(log); err == nil {
101 err = flushErr
102 }
103 return err
104 }
105
106
107 func (s *State) Chdir(path string) error {
108 dir := s.Path(path)
109 if _, err := os.Stat(dir); err != nil {
110 return &fs.PathError{Op: "Chdir", Path: dir, Err: err}
111 }
112 s.pwd = dir
113 s.Setenv("PWD", dir)
114 return nil
115 }
116
117
118 func (s *State) Context() context.Context {
119 return s.ctx
120 }
121
122
123
124 func (s *State) Environ() []string {
125 return append([]string(nil), s.env...)
126 }
127
128
129
130
131 func (s *State) ExpandEnv(str string, inRegexp bool) string {
132 return os.Expand(str, func(key string) string {
133 e := s.envMap[key]
134 if inRegexp {
135
136
137 e = regexp.QuoteMeta(e)
138 }
139 return e
140 })
141 }
142
143
144
145
146
147
148 func (s *State) ExtractFiles(ar *txtar.Archive) error {
149 wd := s.workdir
150
151
152
153
154 if wd == "" {
155 panic("s.workdir is unexpectedly empty")
156 }
157 if !os.IsPathSeparator(wd[len(wd)-1]) {
158 wd += string(filepath.Separator)
159 }
160
161 for _, f := range ar.Files {
162 name := s.Path(s.ExpandEnv(f.Name, false))
163
164 if !strings.HasPrefix(name, wd) {
165 return fmt.Errorf("file %#q is outside working directory", f.Name)
166 }
167
168 if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil {
169 return err
170 }
171 if err := os.WriteFile(name, f.Data, 0666); err != nil {
172 return err
173 }
174 }
175
176 return nil
177 }
178
179
180 func (s *State) Getwd() string { return s.pwd }
181
182
183
184 func (s *State) Logf(format string, args ...any) {
185 fmt.Fprintf(&s.log, format, args...)
186 }
187
188
189 func (s *State) flushLog(w io.Writer) error {
190 _, err := w.Write(s.log.Bytes())
191 s.log.Reset()
192 return err
193 }
194
195
196 func (s *State) LookupEnv(key string) (string, bool) {
197 v, ok := s.envMap[key]
198 return v, ok
199 }
200
201
202
203 func (s *State) Path(path string) string {
204 if filepath.IsAbs(path) {
205 return filepath.Clean(path)
206 }
207 return filepath.Join(s.pwd, path)
208 }
209
210
211 func (s *State) Setenv(key, value string) error {
212 s.env = cleanEnv(append(s.env, key+"="+value), s.pwd)
213 s.envMap[key] = value
214 return nil
215 }
216
217
218
219 func (s *State) Stdout() string { return s.stdout }
220
221
222
223 func (s *State) Stderr() string { return s.stderr }
224
225
226
227
228
229 func cleanEnv(env []string, pwd string) []string {
230
231
232
233 cmd := &exec.Cmd{Env: env}
234 cmd.Dir = pwd
235 return cmd.Environ()
236 }
237
View as plain text