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