github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/osutil/osutil.go (about) 1 // Copyright 2017 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package osutil 5 6 import ( 7 "bytes" 8 "compress/gzip" 9 "context" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "io" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "strings" 18 "sync" 19 "syscall" 20 "time" 21 _ "unsafe" // required to use go:linkname 22 ) 23 24 const ( 25 DefaultDirPerm = 0755 26 DefaultFilePerm = 0644 27 DefaultExecPerm = 0755 28 ) 29 30 // RunCmd runs "bin args..." in dir with timeout and returns its output. 31 func RunCmd(timeout time.Duration, dir, bin string, args ...string) ([]byte, error) { 32 cmd := Command(bin, args...) 33 cmd.Dir = dir 34 return Run(timeout, cmd) 35 } 36 37 // Run runs cmd with the specified timeout. 38 // Returns combined output. If the command fails, err includes output. 39 func Run(timeout time.Duration, cmd *exec.Cmd) ([]byte, error) { 40 output := new(bytes.Buffer) 41 if cmd.Stdout == nil { 42 cmd.Stdout = output 43 } 44 if cmd.Stderr == nil { 45 cmd.Stderr = output 46 } 47 setPdeathsig(cmd, true) 48 if err := cmd.Start(); err != nil { 49 return nil, fmt.Errorf("failed to start %v %+v: %w", cmd.Path, cmd.Args, err) 50 } 51 done := make(chan bool) 52 timedout := make(chan bool, 1) 53 timer := time.NewTimer(timeout) 54 go func() { 55 select { 56 case <-timer.C: 57 timedout <- true 58 killPgroup(cmd) 59 cmd.Process.Kill() 60 case <-done: 61 timedout <- false 62 timer.Stop() 63 } 64 }() 65 err := cmd.Wait() 66 close(done) 67 if err != nil { 68 retErr := fmt.Errorf("failed to run %q: %w", cmd.Args, err) 69 if <-timedout { 70 retErr = fmt.Errorf("timedout after %v %q", timeout, cmd.Args) 71 } 72 exitCode := cmd.ProcessState.ExitCode() 73 var exitErr *exec.ExitError 74 if errors.As(err, &exitErr) { 75 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { 76 exitCode = status.ExitStatus() 77 } 78 } 79 return output.Bytes(), &VerboseError{ 80 Err: retErr, 81 Output: output.Bytes(), 82 ExitCode: exitCode, 83 } 84 } 85 return output.Bytes(), nil 86 } 87 88 // CommandContext is similar to os/exec.CommandContext, but also sets PDEATHSIG to SIGKILL on linux, 89 // i.e. the child will be killed immediately. 90 func CommandContext(ctx context.Context, bin string, args ...string) *exec.Cmd { 91 cmd := exec.CommandContext(ctx, bin, args...) 92 setPdeathsig(cmd, true) 93 return cmd 94 } 95 96 // Command is similar to os/exec.Command, but also sets PDEATHSIG to SIGKILL on linux, 97 // i.e. the child will be killed immediately. 98 func Command(bin string, args ...string) *exec.Cmd { 99 cmd := exec.Command(bin, args...) 100 setPdeathsig(cmd, true) 101 return cmd 102 } 103 104 // Command is similar to os/exec.Command, but also sets PDEATHSIG to SIGTERM on linux, 105 // i.e. the child has a chance to exit gracefully. This may be important when running 106 // e.g. syz-manager. If it is killed immediately, it can leak GCE instances. 107 func GraciousCommand(bin string, args ...string) *exec.Cmd { 108 cmd := exec.Command(bin, args...) 109 setPdeathsig(cmd, false) 110 return cmd 111 } 112 113 type VerboseError struct { 114 Err error 115 Output []byte 116 ExitCode int 117 } 118 119 func (err *VerboseError) Error() string { 120 return err.Err.Error() 121 } 122 123 func (err *VerboseError) Unwrap() error { 124 return err.Err 125 } 126 127 func IsDir(name string) bool { 128 fileInfo, err := os.Stat(name) 129 return err == nil && fileInfo.IsDir() 130 } 131 132 // IsExist returns true if the file name exists. 133 func IsExist(name string) bool { 134 _, err := os.Stat(name) 135 return err == nil 136 } 137 138 // FilesExist returns true if all files exist in dir. 139 // Files are assumed to be relative names in slash notation. 140 func FilesExist(dir string, files map[string]bool) bool { 141 for pattern, required := range files { 142 if !required { 143 continue 144 } 145 files, err := filepath.Glob(filepath.Join(dir, filepath.FromSlash(pattern))) 146 if err != nil || len(files) == 0 { 147 return false 148 } 149 } 150 return true 151 } 152 153 // CopyFiles copies files from srcDir to dstDir as atomically as possible. 154 // Files are assumed to be relative glob patterns in slash notation in srcDir. 155 // All other files in dstDir are removed. 156 func CopyFiles(srcDir, dstDir string, files map[string]bool) error { 157 // Linux does not support atomic dir replace, so we copy to tmp dir first. 158 // Then remove dst dir and rename tmp to dst (as atomic as can get on Linux). 159 tmpDir := dstDir + ".tmp" 160 if err := os.RemoveAll(tmpDir); err != nil { 161 return err 162 } 163 if err := MkdirAll(tmpDir); err != nil { 164 return err 165 } 166 if err := foreachPatternFile(srcDir, tmpDir, files, CopyFile); err != nil { 167 return err 168 } 169 if err := os.RemoveAll(dstDir); err != nil { 170 return err 171 } 172 return os.Rename(tmpDir, dstDir) 173 } 174 175 func foreachPatternFile(srcDir, dstDir string, files map[string]bool, fn func(src, dst string) error) error { 176 srcDir = filepath.Clean(srcDir) 177 dstDir = filepath.Clean(dstDir) 178 for pattern, required := range files { 179 files, err := filepath.Glob(filepath.Join(srcDir, filepath.FromSlash(pattern))) 180 if err != nil { 181 return err 182 } 183 if len(files) == 0 { 184 if !required { 185 continue 186 } 187 return fmt.Errorf("file %v does not exist", pattern) 188 } 189 for _, file := range files { 190 if !strings.HasPrefix(file, srcDir) { 191 return fmt.Errorf("file %q matched from %q in %q doesn't have src prefix", file, pattern, srcDir) 192 } 193 dst := filepath.Join(dstDir, strings.TrimPrefix(file, srcDir)) 194 if err := MkdirAll(filepath.Dir(dst)); err != nil { 195 return err 196 } 197 if err := fn(file, dst); err != nil { 198 return err 199 } 200 } 201 } 202 return nil 203 } 204 205 func CopyDirRecursively(srcDir, dstDir string) error { 206 if err := MkdirAll(dstDir); err != nil { 207 return err 208 } 209 files, err := os.ReadDir(srcDir) 210 if err != nil { 211 return err 212 } 213 for _, file := range files { 214 src := filepath.Join(srcDir, file.Name()) 215 dst := filepath.Join(dstDir, file.Name()) 216 if file.IsDir() { 217 if err := CopyDirRecursively(src, dst); err != nil { 218 return err 219 } 220 continue 221 } 222 if err := CopyFile(src, dst); err != nil { 223 return err 224 } 225 } 226 return nil 227 } 228 229 // LinkFiles creates hard links for files from dstDir to srcDir. 230 // Files are assumed to be relative names in slash notation. 231 // All other files in dstDir are removed. 232 func LinkFiles(srcDir, dstDir string, files map[string]bool) error { 233 if err := os.RemoveAll(dstDir); err != nil { 234 return err 235 } 236 if err := MkdirAll(dstDir); err != nil { 237 return err 238 } 239 return foreachPatternFile(srcDir, dstDir, files, os.Link) 240 } 241 242 func MkdirAll(dir string) error { 243 return os.MkdirAll(dir, DefaultDirPerm) 244 } 245 246 func WriteFile(filename string, data []byte) error { 247 return os.WriteFile(filename, data, DefaultFilePerm) 248 } 249 250 // WriteFileAtomically writes data to file filename without exposing and empty/partially-written file. 251 // This is useful for writing generated source files. Exposing an empty file may break tools 252 // that run on source files in parallel. 253 func WriteFileAtomically(filename string, data []byte) error { 254 // We can't use os.CreateTemp b/c it may be on a different mount, 255 // and Rename can't move across mounts. 256 tmpFile := filename + ".tmp" 257 if err := WriteFile(tmpFile, data); err != nil { 258 return err 259 } 260 return os.Rename(tmpFile, filename) 261 } 262 263 func WriteJSON[T any](filename string, obj T) error { 264 jsonData, err := json.MarshalIndent(obj, "", "\t") 265 if err != nil { 266 return fmt.Errorf("failed to marshal: %w", err) 267 } 268 return WriteFile(filename, jsonData) 269 } 270 271 func ReadJSON[T any](filename string) (T, error) { 272 data, err := os.ReadFile(filename) 273 if err != nil { 274 var v T 275 return v, err 276 } 277 return ParseJSON[T](data) 278 } 279 280 func ParseJSON[T any](data []byte) (T, error) { 281 dec := json.NewDecoder(bytes.NewReader(data)) 282 dec.DisallowUnknownFields() 283 var v T 284 if err := dec.Decode(&v); err != nil { 285 return v, fmt.Errorf("failed to unmarshal %T: %w", v, err) 286 } 287 return v, nil 288 } 289 290 func WriteGzipStream(filename string, reader io.Reader) error { 291 f, err := os.Create(filename) 292 if err != nil { 293 return err 294 } 295 defer f.Close() 296 gz := gzip.NewWriter(f) 297 defer gz.Close() 298 _, err = io.Copy(gz, reader) 299 return err 300 } 301 302 func WriteExecFile(filename string, data []byte) error { 303 os.Remove(filename) 304 return os.WriteFile(filename, data, DefaultExecPerm) 305 } 306 307 // TempFile creates a unique temp filename. 308 // Note: the file already exists when the function returns. 309 func TempFile(prefix string) (string, error) { 310 f, err := os.CreateTemp("", prefix) 311 if err != nil { 312 return "", fmt.Errorf("failed to create temp file: %w", err) 313 } 314 f.Close() 315 return f.Name(), nil 316 } 317 318 // Return all files in a directory. 319 func ListDir(dir string) ([]string, error) { 320 f, err := os.Open(dir) 321 if err != nil { 322 return nil, err 323 } 324 defer f.Close() 325 return f.Readdirnames(-1) 326 } 327 328 var ( 329 wd string 330 wdOnce sync.Once 331 ) 332 333 func Abs(path string) string { 334 wdOnce.Do(func() { 335 var err error 336 wd, err = os.Getwd() 337 if err != nil { 338 panic(fmt.Sprintf("failed to get wd: %v", err)) 339 } 340 }) 341 if wd1, err := os.Getwd(); err == nil && wd1 != wd { 342 panic(fmt.Sprintf("wd changed: %q -> %q", wd, wd1)) 343 } 344 if path == "" { 345 return path 346 } 347 if !filepath.IsAbs(path) { 348 path = filepath.Join(wd, path) 349 } 350 return filepath.Clean(path) 351 } 352 353 // CreationTime returns file creation time. 354 // May return zero time, if not known. 355 func CreationTime(fi os.FileInfo) time.Time { 356 return creationTime(fi) 357 } 358 359 // MonotonicNano returns monotonic time in nanoseconds from some unspecified point in time. 360 // Useful mostly to measure time intervals. 361 // This function should be used inside of tested VMs b/c time.Now may reject to use monotonic time 362 // if the fuzzer messes with system time (sets time past Y2157, see comments in time/time.go). 363 // This is a hacky way to use the private runtime function. 364 // If this ever breaks, we can either provide specializations for different Go versions 365 // using build tags, or fall back to time.Now. 366 // 367 //go:linkname MonotonicNano runtime.nanotime 368 func MonotonicNano() time.Duration