blob: 32a8054e70602d2e7fb73abbccdea3581721d728 [file] [log] [blame]
// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package osutil
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"time"
)
const (
DefaultDirPerm = 0755
DefaultFilePerm = 0644
DefaultExecPerm = 0755
)
// RunCmd runs "bin args..." in dir with timeout and returns its output.
func RunCmd(timeout time.Duration, dir, bin string, args ...string) ([]byte, error) {
cmd := Command(bin, args...)
cmd.Dir = dir
return Run(timeout, cmd)
}
// Run runs cmd with the specified timeout.
// Returns combined output. If the command fails, err includes output.
func Run(timeout time.Duration, cmd *exec.Cmd) ([]byte, error) {
output := new(bytes.Buffer)
if cmd.Stdout == nil {
cmd.Stdout = output
}
if cmd.Stderr == nil {
cmd.Stderr = output
}
setPdeathsig(cmd)
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start %v %+v: %v", cmd.Path, cmd.Args, err)
}
done := make(chan bool)
timedout := make(chan bool, 1)
timer := time.NewTimer(timeout)
go func() {
select {
case <-timer.C:
timedout <- true
killPgroup(cmd)
cmd.Process.Kill()
case <-done:
timedout <- false
timer.Stop()
}
}()
err := cmd.Wait()
close(done)
if err != nil {
text := fmt.Sprintf("failed to run %q: %v", cmd.Args, err)
if <-timedout {
text = fmt.Sprintf("timedout %q", cmd.Args)
}
return output.Bytes(), &VerboseError{
Title: text,
Output: output.Bytes(),
}
}
return output.Bytes(), nil
}
// Command is similar to os/exec.Command, but also sets PDEATHSIG on linux.
func Command(bin string, args ...string) *exec.Cmd {
cmd := exec.Command(bin, args...)
setPdeathsig(cmd)
return cmd
}
type VerboseError struct {
Title string
Output []byte
}
func (err *VerboseError) Error() string {
if len(err.Output) == 0 {
return err.Title
}
return fmt.Sprintf("%v\n%s", err.Title, err.Output)
}
func PrependContext(ctx string, err error) error {
switch err1 := err.(type) {
case *VerboseError:
err1.Title = fmt.Sprintf("%v: %v", ctx, err1.Title)
return err1
default:
return fmt.Errorf("%v: %v", ctx, err)
}
}
// IsExist returns true if the file name exists.
func IsExist(name string) bool {
_, err := os.Stat(name)
return err == nil
}
// IsAccessible checks if the file can be opened.
func IsAccessible(name string) error {
if !IsExist(name) {
return fmt.Errorf("%v does not exist", name)
}
f, err := os.Open(name)
if err != nil {
return fmt.Errorf("%v can't be opened (%v)", name, err)
}
f.Close()
return nil
}
// FilesExist returns true if all files exist in dir.
// Files are assumed to be relative names in slash notation.
func FilesExist(dir string, files map[string]bool) bool {
for f, required := range files {
if !required {
continue
}
if !IsExist(filepath.Join(dir, filepath.FromSlash(f))) {
return false
}
}
return true
}
// CopyFiles copies files from srcDir to dstDir as atomically as possible.
// Files are assumed to be relative names in slash notation.
// All other files in dstDir are removed.
func CopyFiles(srcDir, dstDir string, files map[string]bool) error {
// Linux does not support atomic dir replace, so we copy to tmp dir first.
// Then remove dst dir and rename tmp to dst (as atomic as can get on Linux).
tmpDir := dstDir + ".tmp"
if err := os.RemoveAll(tmpDir); err != nil {
return err
}
if err := MkdirAll(tmpDir); err != nil {
return err
}
for f, required := range files {
src := filepath.Join(srcDir, filepath.FromSlash(f))
if !required && !IsExist(src) {
continue
}
dst := filepath.Join(tmpDir, filepath.FromSlash(f))
if err := MkdirAll(filepath.Dir(dst)); err != nil {
return err
}
if err := CopyFile(src, dst); err != nil {
return err
}
}
if err := os.RemoveAll(dstDir); err != nil {
return err
}
return os.Rename(tmpDir, dstDir)
}
// LinkFiles creates hard links for files from dstDir to srcDir.
// Files are assumed to be relative names in slash notation.
// All other files in dstDir are removed.
func LinkFiles(srcDir, dstDir string, files map[string]bool) error {
if err := os.RemoveAll(dstDir); err != nil {
return err
}
if err := MkdirAll(dstDir); err != nil {
return err
}
for f, required := range files {
src := filepath.Join(srcDir, filepath.FromSlash(f))
if !required && !IsExist(src) {
continue
}
dst := filepath.Join(dstDir, filepath.FromSlash(f))
if err := MkdirAll(filepath.Dir(dst)); err != nil {
return err
}
if err := os.Link(src, dst); err != nil {
return err
}
}
return nil
}
func MkdirAll(dir string) error {
return os.MkdirAll(dir, DefaultDirPerm)
}
func WriteFile(filename string, data []byte) error {
return ioutil.WriteFile(filename, data, DefaultFilePerm)
}
func WriteExecFile(filename string, data []byte) error {
os.Remove(filename)
return ioutil.WriteFile(filename, data, DefaultExecPerm)
}
// TempFile creates a unique temp filename.
// Note: the file already exists when the function returns.
func TempFile(prefix string) (string, error) {
f, err := ioutil.TempFile("", prefix)
if err != nil {
return "", fmt.Errorf("failed to create temp file: %v", err)
}
f.Close()
return f.Name(), nil
}
// Return all files in a directory.
func ListDir(dir string) ([]string, error) {
f, err := os.Open(dir)
if err != nil {
return nil, err
}
defer f.Close()
return f.Readdirnames(-1)
}
var wd string
func init() {
var err error
wd, err = os.Getwd()
if err != nil {
panic(fmt.Sprintf("failed to get wd: %v", err))
}
}
func Abs(path string) string {
if wd1, err := os.Getwd(); err == nil && wd1 != wd {
panic("don't mess with wd in a concurrent program")
}
if path == "" || filepath.IsAbs(path) {
return path
}
return filepath.Join(wd, path)
}