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 }