Home | History | Annotate | Download | only in osutil
      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 	"fmt"
      9 	"io/ioutil"
     10 	"os"
     11 	"os/exec"
     12 	"path/filepath"
     13 	"time"
     14 )
     15 
     16 const (
     17 	DefaultDirPerm  = 0755
     18 	DefaultFilePerm = 0644
     19 	DefaultExecPerm = 0755
     20 )
     21 
     22 // RunCmd runs "bin args..." in dir with timeout and returns its output.
     23 func RunCmd(timeout time.Duration, dir, bin string, args ...string) ([]byte, error) {
     24 	cmd := Command(bin, args...)
     25 	cmd.Dir = dir
     26 	return Run(timeout, cmd)
     27 }
     28 
     29 // Run runs cmd with the specified timeout.
     30 // Returns combined output. If the command fails, err includes output.
     31 func Run(timeout time.Duration, cmd *exec.Cmd) ([]byte, error) {
     32 	output := new(bytes.Buffer)
     33 	if cmd.Stdout == nil {
     34 		cmd.Stdout = output
     35 	}
     36 	if cmd.Stderr == nil {
     37 		cmd.Stderr = output
     38 	}
     39 	if err := cmd.Start(); err != nil {
     40 		return nil, fmt.Errorf("failed to start %v %+v: %v", cmd.Path, cmd.Args, err)
     41 	}
     42 	done := make(chan bool)
     43 	timedout := make(chan bool, 1)
     44 	timer := time.NewTimer(timeout)
     45 	go func() {
     46 		select {
     47 		case <-timer.C:
     48 			timedout <- true
     49 			cmd.Process.Kill()
     50 		case <-done:
     51 			timedout <- false
     52 			timer.Stop()
     53 		}
     54 	}()
     55 	err := cmd.Wait()
     56 	close(done)
     57 	if err != nil {
     58 		text := fmt.Sprintf("failed to run %q: %v", cmd.Args, err)
     59 		if <-timedout {
     60 			text = fmt.Sprintf("timedout %q", cmd.Args)
     61 		}
     62 		return nil, &VerboseError{
     63 			Title:  text,
     64 			Output: output.Bytes(),
     65 		}
     66 	}
     67 	return output.Bytes(), nil
     68 }
     69 
     70 // Command is similar to os/exec.Command, but also sets PDEATHSIG on linux.
     71 func Command(bin string, args ...string) *exec.Cmd {
     72 	cmd := exec.Command(bin, args...)
     73 	setPdeathsig(cmd)
     74 	return cmd
     75 }
     76 
     77 type VerboseError struct {
     78 	Title  string
     79 	Output []byte
     80 }
     81 
     82 func (err *VerboseError) Error() string {
     83 	if len(err.Output) == 0 {
     84 		return err.Title
     85 	}
     86 	return fmt.Sprintf("%v\n%s", err.Title, err.Output)
     87 }
     88 
     89 func PrependContext(ctx string, err error) error {
     90 	switch err1 := err.(type) {
     91 	case *VerboseError:
     92 		err1.Title = fmt.Sprintf("%v: %v", ctx, err1.Title)
     93 		return err1
     94 	default:
     95 		return fmt.Errorf("%v: %v", ctx, err)
     96 	}
     97 }
     98 
     99 // IsExist returns true if the file name exists.
    100 func IsExist(name string) bool {
    101 	_, err := os.Stat(name)
    102 	return err == nil
    103 }
    104 
    105 // IsAccessible checks if the file can be opened.
    106 func IsAccessible(name string) error {
    107 	if !IsExist(name) {
    108 		return fmt.Errorf("%v does not exist", name)
    109 	}
    110 	f, err := os.Open(name)
    111 	if err != nil {
    112 		return fmt.Errorf("%v can't be opened (%v)", name, err)
    113 	}
    114 	f.Close()
    115 	return nil
    116 }
    117 
    118 // FilesExist returns true if all files exist in dir.
    119 // Files are assumed to be relative names in slash notation.
    120 func FilesExist(dir string, files map[string]bool) bool {
    121 	for f, required := range files {
    122 		if !required {
    123 			continue
    124 		}
    125 		if !IsExist(filepath.Join(dir, filepath.FromSlash(f))) {
    126 			return false
    127 		}
    128 	}
    129 	return true
    130 }
    131 
    132 // CopyFiles copies files from srcDir to dstDir as atomically as possible.
    133 // Files are assumed to be relative names in slash notation.
    134 // All other files in dstDir are removed.
    135 func CopyFiles(srcDir, dstDir string, files map[string]bool) error {
    136 	// Linux does not support atomic dir replace, so we copy to tmp dir first.
    137 	// Then remove dst dir and rename tmp to dst (as atomic as can get on Linux).
    138 	tmpDir := dstDir + ".tmp"
    139 	if err := os.RemoveAll(tmpDir); err != nil {
    140 		return err
    141 	}
    142 	if err := MkdirAll(tmpDir); err != nil {
    143 		return err
    144 	}
    145 	for f, required := range files {
    146 		src := filepath.Join(srcDir, filepath.FromSlash(f))
    147 		if !required && !IsExist(src) {
    148 			continue
    149 		}
    150 		dst := filepath.Join(tmpDir, filepath.FromSlash(f))
    151 		if err := MkdirAll(filepath.Dir(dst)); err != nil {
    152 			return err
    153 		}
    154 		if err := CopyFile(src, dst); err != nil {
    155 			return err
    156 		}
    157 	}
    158 	if err := os.RemoveAll(dstDir); err != nil {
    159 		return err
    160 	}
    161 	return os.Rename(tmpDir, dstDir)
    162 }
    163 
    164 // LinkFiles creates hard links for files from dstDir to srcDir.
    165 // Files are assumed to be relative names in slash notation.
    166 // All other files in dstDir are removed.
    167 func LinkFiles(srcDir, dstDir string, files map[string]bool) error {
    168 	if err := os.RemoveAll(dstDir); err != nil {
    169 		return err
    170 	}
    171 	if err := MkdirAll(dstDir); err != nil {
    172 		return err
    173 	}
    174 	for f, required := range files {
    175 		src := filepath.Join(srcDir, filepath.FromSlash(f))
    176 		if !required && !IsExist(src) {
    177 			continue
    178 		}
    179 		dst := filepath.Join(dstDir, filepath.FromSlash(f))
    180 		if err := MkdirAll(filepath.Dir(dst)); err != nil {
    181 			return err
    182 		}
    183 		if err := os.Link(src, dst); err != nil {
    184 			return err
    185 		}
    186 	}
    187 	return nil
    188 }
    189 
    190 func MkdirAll(dir string) error {
    191 	return os.MkdirAll(dir, DefaultDirPerm)
    192 }
    193 
    194 func WriteFile(filename string, data []byte) error {
    195 	return ioutil.WriteFile(filename, data, DefaultFilePerm)
    196 }
    197 
    198 func WriteExecFile(filename string, data []byte) error {
    199 	if err := ioutil.WriteFile(filename, data, DefaultExecPerm); err != nil {
    200 		return err
    201 	}
    202 	return os.Chmod(filename, DefaultExecPerm)
    203 }
    204 
    205 // TempFile creates a unique temp filename.
    206 // Note: the file already exists when the function returns.
    207 func TempFile(prefix string) (string, error) {
    208 	f, err := ioutil.TempFile("", prefix)
    209 	if err != nil {
    210 		return "", fmt.Errorf("failed to create temp file: %v", err)
    211 	}
    212 	f.Close()
    213 	return f.Name(), nil
    214 }
    215 
    216 // Return all files in a directory.
    217 func ListDir(dir string) ([]string, error) {
    218 	f, err := os.Open(dir)
    219 	if err != nil {
    220 		return nil, err
    221 	}
    222 	defer f.Close()
    223 	return f.Readdirnames(-1)
    224 }
    225 
    226 var wd string
    227 
    228 func init() {
    229 	var err error
    230 	wd, err = os.Getwd()
    231 	if err != nil {
    232 		panic(fmt.Sprintf("failed to get wd: %v", err))
    233 	}
    234 }
    235 
    236 func Abs(path string) string {
    237 	if wd1, err := os.Getwd(); err == nil && wd1 != wd {
    238 		panic("don't mess with wd in a concurrent program")
    239 	}
    240 	if path == "" || filepath.IsAbs(path) {
    241 		return path
    242 	}
    243 	return filepath.Join(wd, path)
    244 }
    245