github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/osutil/fileutil.go (about)

     1  // Copyright 2015 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"
    10  	"io/fs"
    11  	"os"
    12  	"path/filepath"
    13  )
    14  
    15  // CopyFile atomically copies oldFile to newFile preserving permissions and modification time.
    16  func CopyFile(oldFile, newFile string) error {
    17  	oldf, err := os.Open(oldFile)
    18  	if err != nil {
    19  		return err
    20  	}
    21  	defer oldf.Close()
    22  	stat, err := oldf.Stat()
    23  	if err != nil {
    24  		return err
    25  	}
    26  	tmpFile := newFile + ".tmp"
    27  	newf, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, stat.Mode()&os.ModePerm)
    28  	if err != nil {
    29  		return err
    30  	}
    31  	defer newf.Close()
    32  	_, err = io.Copy(newf, oldf)
    33  	if err != nil {
    34  		return err
    35  	}
    36  	if err := newf.Close(); err != nil {
    37  		return err
    38  	}
    39  	if err := os.Chtimes(tmpFile, stat.ModTime(), stat.ModTime()); err != nil {
    40  		return err
    41  	}
    42  	return os.Rename(tmpFile, newFile)
    43  }
    44  
    45  // Rename is similar to os.Rename but handles cross-device renaming (by copying).
    46  func Rename(oldFile, newFile string) error {
    47  	err := os.Rename(oldFile, newFile)
    48  	if err != nil {
    49  		// Can't use syscall.EXDEV because this is used in appengine app.
    50  		err = CopyFile(oldFile, newFile)
    51  		os.Remove(oldFile)
    52  	}
    53  	return err
    54  }
    55  
    56  // FillDirectory is used to fill in directory structure for tests.
    57  func FillDirectory(dir string, fileContent map[string]string) error {
    58  	for path, content := range fileContent {
    59  		fullPath := filepath.Join(dir, path)
    60  		dirPath := filepath.Dir(fullPath)
    61  		if err := MkdirAll(dirPath); err != nil {
    62  			return fmt.Errorf("mkdir %q failed: %w", dirPath, err)
    63  		}
    64  		if err := WriteFile(fullPath, []byte(content)); err != nil {
    65  			return fmt.Errorf("write file failed: %w", err)
    66  		}
    67  	}
    68  	return nil
    69  }
    70  
    71  // WriteTempFile writes data to a temp file and returns its name.
    72  func WriteTempFile(data []byte) (string, error) {
    73  	// Note: pkg/report knows about "syzkaller" prefix as it appears in crashes as process name.
    74  	f, err := os.CreateTemp("", "syzkaller")
    75  	if err != nil {
    76  		return "", fmt.Errorf("failed to create a temp file: %w", err)
    77  	}
    78  	if _, err := f.Write(data); err != nil {
    79  		f.Close()
    80  		os.Remove(f.Name())
    81  		return "", fmt.Errorf("failed to write a temp file: %w", err)
    82  	}
    83  	f.Close()
    84  	return f.Name(), nil
    85  }
    86  
    87  // GrepFiles returns the list of files (relative to root) that include target.
    88  // If ext is not empty, the files will be filtered by the extension.
    89  // The function assumes that the files are not too big and may fit in memory.
    90  func GrepFiles(root, ext string, target []byte) ([]string, error) {
    91  	var ret []string
    92  	err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
    93  		if err != nil {
    94  			return err
    95  		}
    96  		if d.IsDir() || filepath.Ext(path) != ext {
    97  			return nil
    98  		}
    99  		content, err := os.ReadFile(path)
   100  		if err != nil {
   101  			return fmt.Errorf("failed to open %s: %w", path, err)
   102  		}
   103  		if bytes.Contains(content, target) {
   104  			rel, _ := filepath.Rel(root, path)
   105  			ret = append(ret, rel)
   106  		}
   107  		return nil
   108  	})
   109  	return ret, err
   110  }