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