Line data Source code
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 0 : func CloneSkip(fn func(string) bool) CloneOption {
26 0 : return func(co *cloneOpts) { co.skip = fn }
27 : }
28 :
29 : // CloneSync configures Clone to sync files and directories.
30 0 : 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 0 : 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 0 : func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool, error) {
45 0 : var o cloneOpts
46 0 : for _, opt := range opts {
47 0 : opt(&o)
48 0 : }
49 :
50 0 : srcFile, err := srcFS.Open(srcPath)
51 0 : if err != nil {
52 0 : if oserror.IsNotExist(err) {
53 0 : // Ignore non-existent errors. Those will translate into non-existent
54 0 : // files in the destination filesystem.
55 0 : return false, nil
56 0 : }
57 0 : return false, err
58 : }
59 0 : defer srcFile.Close()
60 0 :
61 0 : stat, err := srcFile.Stat()
62 0 : if err != nil {
63 0 : return false, err
64 0 : }
65 :
66 0 : if stat.IsDir() {
67 0 : if err := dstFS.MkdirAll(dstPath, 0755); err != nil {
68 0 : return false, err
69 0 : }
70 0 : list, err := srcFS.List(srcPath)
71 0 : if err != nil {
72 0 : return false, err
73 0 : }
74 : // Sort the paths so we get deterministic test output.
75 0 : sort.Strings(list)
76 0 : for _, name := range list {
77 0 : if o.skip != nil && o.skip(srcFS.PathJoin(srcPath, name)) {
78 0 : continue
79 : }
80 0 : _, err := Clone(srcFS, dstFS, srcFS.PathJoin(srcPath, name), dstFS.PathJoin(dstPath, name), opts...)
81 0 : if err != nil {
82 0 : return false, err
83 0 : }
84 : }
85 :
86 0 : if o.sync {
87 0 : dir, err := dstFS.OpenDir(dstPath)
88 0 : if err != nil {
89 0 : return false, err
90 0 : }
91 0 : if err := dir.Sync(); err != nil {
92 0 : return false, err
93 0 : }
94 0 : if err := dir.Close(); err != nil {
95 0 : return false, err
96 0 : }
97 : }
98 :
99 0 : 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 0 : if srcFS == dstFS && o.tryLink {
106 0 : if err := LinkOrCopy(srcFS, srcPath, dstPath); oserror.IsNotExist(err) {
107 0 : // Clone's semantics are such that it returns (false,nil) if the
108 0 : // source does not exist.
109 0 : return false, nil
110 0 : } else if err != nil {
111 0 : return false, err
112 0 : } else {
113 0 : return true, nil
114 0 : }
115 : }
116 :
117 0 : data, err := io.ReadAll(srcFile)
118 0 : if err != nil {
119 0 : return false, err
120 0 : }
121 0 : dstFile, err := dstFS.Create(dstPath)
122 0 : if err != nil {
123 0 : return false, err
124 0 : }
125 0 : if _, err = dstFile.Write(data); err != nil {
126 0 : return false, err
127 0 : }
128 0 : if o.sync {
129 0 : if err := dstFile.Sync(); err != nil {
130 0 : return false, err
131 0 : }
132 : }
133 0 : if err := dstFile.Close(); err != nil {
134 0 : return false, err
135 0 : }
136 0 : return true, nil
137 : }
|