github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/vfs/clone.go (about)

     1  // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package vfs
     6  
     7  import (
     8  	"io"
     9  	"sort"
    10  
    11  	"github.com/cockroachdb/errors/oserror"
    12  )
    13  
    14  type cloneOpts struct {
    15  	skip    func(string) bool
    16  	sync    bool
    17  	tryLink bool
    18  }
    19  
    20  // A CloneOption configures the behavior of Clone.
    21  type CloneOption func(*cloneOpts)
    22  
    23  // CloneSkip configures Clone to skip files for which the provided function
    24  // returns true when passed the file's path.
    25  func CloneSkip(fn func(string) bool) CloneOption {
    26  	return func(co *cloneOpts) { co.skip = fn }
    27  }
    28  
    29  // CloneSync configures Clone to sync files and directories.
    30  var CloneSync CloneOption = func(o *cloneOpts) { o.sync = true }
    31  
    32  // CloneTryLink configures Clone to link files to the destination if the source and
    33  // destination filesystems are the same. If the source and destination
    34  // filesystems are not the same or the filesystem does not support linking, then
    35  // Clone falls back to copying.
    36  var CloneTryLink CloneOption = func(o *cloneOpts) { o.tryLink = true }
    37  
    38  // Clone recursively copies a directory structure from srcFS to dstFS. srcPath
    39  // specifies the path in srcFS to copy from and must be compatible with the
    40  // srcFS path format. dstDir is the target directory in dstFS and must be
    41  // compatible with the dstFS path format. Returns (true,nil) on a successful
    42  // copy, (false,nil) if srcPath does not exist, and (false,err) if an error
    43  // occurred.
    44  func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool, error) {
    45  	var o cloneOpts
    46  	for _, opt := range opts {
    47  		opt(&o)
    48  	}
    49  
    50  	srcFile, err := srcFS.Open(srcPath)
    51  	if err != nil {
    52  		if oserror.IsNotExist(err) {
    53  			// Ignore non-existent errors. Those will translate into non-existent
    54  			// files in the destination filesystem.
    55  			return false, nil
    56  		}
    57  		return false, err
    58  	}
    59  	defer srcFile.Close()
    60  
    61  	stat, err := srcFile.Stat()
    62  	if err != nil {
    63  		return false, err
    64  	}
    65  
    66  	if stat.IsDir() {
    67  		if err := dstFS.MkdirAll(dstPath, 0755); err != nil {
    68  			return false, err
    69  		}
    70  		list, err := srcFS.List(srcPath)
    71  		if err != nil {
    72  			return false, err
    73  		}
    74  		// Sort the paths so we get deterministic test output.
    75  		sort.Strings(list)
    76  		for _, name := range list {
    77  			if o.skip != nil && o.skip(srcFS.PathJoin(srcPath, name)) {
    78  				continue
    79  			}
    80  			_, err := Clone(srcFS, dstFS, srcFS.PathJoin(srcPath, name), dstFS.PathJoin(dstPath, name), opts...)
    81  			if err != nil {
    82  				return false, err
    83  			}
    84  		}
    85  
    86  		if o.sync {
    87  			dir, err := dstFS.OpenDir(dstPath)
    88  			if err != nil {
    89  				return false, err
    90  			}
    91  			if err := dir.Sync(); err != nil {
    92  				return false, err
    93  			}
    94  			if err := dir.Close(); err != nil {
    95  				return false, err
    96  			}
    97  		}
    98  
    99  		return true, nil
   100  	}
   101  
   102  	// If the source and destination filesystems are the same and the user
   103  	// specified they'd prefer to link if possible, try to use a hardlink,
   104  	// falling back to copying if it fails.
   105  	if srcFS == dstFS && o.tryLink {
   106  		if err := LinkOrCopy(srcFS, srcPath, dstPath); oserror.IsNotExist(err) {
   107  			// Clone's semantics are such that it returns (false,nil) if the
   108  			// source does not exist.
   109  			return false, nil
   110  		} else if err != nil {
   111  			return false, err
   112  		} else {
   113  			return true, nil
   114  		}
   115  	}
   116  
   117  	data, err := io.ReadAll(srcFile)
   118  	if err != nil {
   119  		return false, err
   120  	}
   121  	dstFile, err := dstFS.Create(dstPath)
   122  	if err != nil {
   123  		return false, err
   124  	}
   125  	if _, err = dstFile.Write(data); err != nil {
   126  		return false, err
   127  	}
   128  	if o.sync {
   129  		if err := dstFile.Sync(); err != nil {
   130  			return false, err
   131  		}
   132  	}
   133  	if err := dstFile.Close(); err != nil {
   134  		return false, err
   135  	}
   136  	return true, nil
   137  }