mirror of
https://github.com/kopia/kopia.git
synced 2026-01-19 20:07:56 -05:00
252 lines
6.8 KiB
Go
252 lines
6.8 KiB
Go
package fio
|
|
|
|
import (
|
|
"log"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// WriteFiles writes files to the directory specified by path, up to the
|
|
// provided size and number of files.
|
|
func (fr *Runner) WriteFiles(relPath string, opt Options) error {
|
|
lock, err := fr.PathLock.Lock(relPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer lock.Unlock()
|
|
|
|
fullPath := filepath.Join(fr.LocalDataDir, relPath)
|
|
|
|
return fr.writeFiles(fullPath, opt)
|
|
}
|
|
|
|
func (fr *Runner) writeFiles(fullPath string, opt Options) error {
|
|
err := os.MkdirAll(fullPath, 0o700)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to make directory for write")
|
|
}
|
|
|
|
relWritePath, err := filepath.Rel(fr.LocalDataDir, fullPath)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error finding relative file path between %v and %v", fr.LocalDataDir, fullPath)
|
|
}
|
|
|
|
absWritePath := filepath.Join(fr.FioWriteBaseDir, relWritePath)
|
|
|
|
_, _, err = fr.RunConfigs(Config{
|
|
{
|
|
Name: "writeFiles",
|
|
Options: opt.Merge(
|
|
Options{
|
|
"readwrite": RandWriteFio,
|
|
"filename_format": "file_$filenum",
|
|
}.WithDirectory(absWritePath),
|
|
),
|
|
},
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// WriteFilesAtDepth writes files to a directory "depth" layers deep below
|
|
// the base data directory.
|
|
func (fr *Runner) WriteFilesAtDepth(relBasePath string, depth int, opt Options) error {
|
|
lock, err := fr.PathLock.Lock(relBasePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer lock.Unlock()
|
|
|
|
fullBasePath := filepath.Join(fr.LocalDataDir, relBasePath)
|
|
|
|
err = os.MkdirAll(fullBasePath, 0o700)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to make base dir %v for writing at depth", fullBasePath)
|
|
}
|
|
|
|
return fr.writeFilesAtDepth(fullBasePath, depth, depth, opt)
|
|
}
|
|
|
|
// WriteFilesAtDepthRandomBranch writes files to a directory "depth" layers deep below
|
|
// the base data directory and branches at a random depth.
|
|
func (fr *Runner) WriteFilesAtDepthRandomBranch(relBasePath string, depth int, opt Options) error {
|
|
lock, err := fr.PathLock.Lock(relBasePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer lock.Unlock()
|
|
|
|
fullBasePath := filepath.Join(fr.LocalDataDir, relBasePath)
|
|
|
|
err = os.MkdirAll(fullBasePath, 0o700)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to make base dir %v for writing at depth with a branch", fullBasePath)
|
|
}
|
|
|
|
return fr.writeFilesAtDepth(fullBasePath, depth, rand.Intn(depth+1), opt)
|
|
}
|
|
|
|
// DeleteRelDir deletes a relative directory in the runner's data directory.
|
|
func (fr *Runner) DeleteRelDir(relDirPath string) error {
|
|
lock, err := fr.PathLock.Lock(relDirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer lock.Unlock()
|
|
|
|
return os.RemoveAll(filepath.Join(fr.LocalDataDir, relDirPath))
|
|
}
|
|
|
|
// DeleteDirAtDepth deletes a random directory at the given depth.
|
|
func (fr *Runner) DeleteDirAtDepth(relBasePath string, depth int) error {
|
|
lock, err := fr.PathLock.Lock(relBasePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer lock.Unlock()
|
|
|
|
if depth == 0 {
|
|
return ErrCanNotDeleteRoot
|
|
}
|
|
|
|
fullBasePath := filepath.Join(fr.LocalDataDir, relBasePath)
|
|
|
|
return fr.operateAtDepth(fullBasePath, depth, os.RemoveAll)
|
|
}
|
|
|
|
// DeleteContentsAtDepth deletes some or all of the contents of a directory
|
|
// at the provided depths. The probability argument denotes the probability in the
|
|
// range [0.0,1.0] that a given file system entry in the directory at this depth will be
|
|
// deleted. Probability set to 0 will delete nothing. Probability set to 1 will delete
|
|
// everything.
|
|
func (fr *Runner) DeleteContentsAtDepth(relBasePath string, depth int, prob float32) error {
|
|
lock, err := fr.PathLock.Lock(relBasePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer lock.Unlock()
|
|
|
|
fullBasePath := filepath.Join(fr.LocalDataDir, relBasePath)
|
|
|
|
return fr.operateAtDepth(fullBasePath, depth, func(dirPath string) error {
|
|
dirEntries, err := os.ReadDir(dirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, entry := range dirEntries {
|
|
if rand.Float32() < prob {
|
|
path := filepath.Join(dirPath, entry.Name())
|
|
|
|
err = os.RemoveAll(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// List of known errors.
|
|
var (
|
|
ErrNoDirFound = errors.New("no directory found at this depth")
|
|
ErrCanNotDeleteRoot = errors.New("can not delete root directory")
|
|
)
|
|
|
|
func (fr *Runner) operateAtDepth(path string, depth int, f func(string) error) error {
|
|
if depth <= 0 {
|
|
log.Printf("performing operation on directory %s\n", path)
|
|
return f(path)
|
|
}
|
|
|
|
dirEntries, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to read dir at path %v", path)
|
|
}
|
|
|
|
var dirList []string
|
|
|
|
for _, entry := range dirEntries {
|
|
if entry.IsDir() {
|
|
dirList = append(dirList, filepath.Join(path, entry.Name()))
|
|
}
|
|
}
|
|
|
|
rand.Shuffle(len(dirList), func(i, j int) {
|
|
dirList[i], dirList[j] = dirList[j], dirList[i]
|
|
})
|
|
|
|
for _, dirName := range dirList {
|
|
err = fr.operateAtDepth(dirName, depth-1, f)
|
|
if !errors.Is(err, ErrNoDirFound) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return ErrNoDirFound
|
|
}
|
|
|
|
// writeFilesAtDepth traverses the file system tree until it reaches a given "depth", at which
|
|
// point it uses fio to write one or more files controlled by the provided Options.
|
|
// The "branchDepth" argument gives control over whether the files will be written into
|
|
// existing directories or a new path entirely. The function will traverse existing directories
|
|
// (if any) until it reaches "branchDepth", after which it will begin creating new directories
|
|
// for the remainder of the path until "depth" is reached.
|
|
// If "branchDepth" is zero, the entire path will be newly created directories.
|
|
// If "branchDepth" is greater than or equal to "depth", the files will be written to
|
|
// a random existing directory, if one exists at that depth.
|
|
func (fr *Runner) writeFilesAtDepth(fromDirPath string, depth, branchDepth int, opt Options) error {
|
|
if depth <= 0 {
|
|
return fr.writeFiles(fromDirPath, opt)
|
|
}
|
|
|
|
var subdirPath string
|
|
|
|
if branchDepth > 0 {
|
|
subdirPath = pickRandSubdirPath(fromDirPath)
|
|
}
|
|
|
|
if subdirPath == "" {
|
|
var err error
|
|
|
|
// Couldn't find a subdir, create one instead
|
|
subdirPath, err = os.MkdirTemp(fromDirPath, "dir_")
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to create temp dir at %v", fromDirPath)
|
|
}
|
|
}
|
|
|
|
return fr.writeFilesAtDepth(subdirPath, depth-1, branchDepth-1, opt)
|
|
}
|
|
|
|
func pickRandSubdirPath(dirPath string) (subdirPath string) {
|
|
dirEntries, err := os.ReadDir(dirPath)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
// Find all entries that are directories, record each of their dirEntries indexes
|
|
dirIdxs := make([]int, 0, len(dirEntries))
|
|
|
|
for idx, entry := range dirEntries {
|
|
if entry.IsDir() {
|
|
dirIdxs = append(dirIdxs, idx)
|
|
}
|
|
}
|
|
|
|
if len(dirIdxs) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// Pick a random index from the list of indexes of DirEntry entries known to be directories.
|
|
randDirIdx := dirIdxs[rand.Intn(len(dirIdxs))] //nolint:gosec
|
|
randDirInfo := dirEntries[randDirIdx]
|
|
|
|
return filepath.Join(dirPath, randDirInfo.Name())
|
|
}
|