1
16
17 package fs
18
19 import (
20 "fmt"
21 "io"
22 "os"
23 "path/filepath"
24
25 "github.com/sirupsen/logrus"
26 )
27
28
29
30
31 type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
32
33 type copyDirOpts struct {
34 xeh XAttrErrorHandler
35
36 xex map[string]struct{}
37 }
38
39 type CopyDirOpt func(*copyDirOpts) error
40
41
42
43
44 func WithXAttrErrorHandler(xeh XAttrErrorHandler) CopyDirOpt {
45 return func(o *copyDirOpts) error {
46 o.xeh = xeh
47 return nil
48 }
49 }
50
51
52 func WithAllowXAttrErrors() CopyDirOpt {
53 xeh := func(dst, src, xattrKey string, err error) error {
54 return nil
55 }
56 return WithXAttrErrorHandler(xeh)
57 }
58
59
60 func WithXAttrExclude(keys ...string) CopyDirOpt {
61 return func(o *copyDirOpts) error {
62 if o.xex == nil {
63 o.xex = make(map[string]struct{}, len(keys))
64 }
65 for _, key := range keys {
66 o.xex[key] = struct{}{}
67 }
68 return nil
69 }
70 }
71
72
73
74 func CopyDir(dst, src string, opts ...CopyDirOpt) error {
75 var o copyDirOpts
76 for _, opt := range opts {
77 if err := opt(&o); err != nil {
78 return err
79 }
80 }
81 inodes := map[uint64]string{}
82 return copyDirectory(dst, src, inodes, &o)
83 }
84
85 func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) error {
86 stat, err := os.Stat(src)
87 if err != nil {
88 return fmt.Errorf("failed to stat %s: %w", src, err)
89 }
90 if !stat.IsDir() {
91 return fmt.Errorf("source %s is not directory", src)
92 }
93
94 if st, err := os.Stat(dst); err != nil {
95 if err := os.Mkdir(dst, stat.Mode()); err != nil {
96 return fmt.Errorf("failed to mkdir %s: %w", dst, err)
97 }
98 } else if !st.IsDir() {
99 return fmt.Errorf("cannot copy to non-directory: %s", dst)
100 } else {
101 if err := os.Chmod(dst, stat.Mode()); err != nil {
102 return fmt.Errorf("failed to chmod on %s: %w", dst, err)
103 }
104 }
105
106 entries, err := os.ReadDir(src)
107 if err != nil {
108 return fmt.Errorf("failed to read %s: %w", src, err)
109 }
110
111 if err := copyFileInfo(stat, src, dst); err != nil {
112 return fmt.Errorf("failed to copy file info for %s: %w", dst, err)
113 }
114
115 if err := copyXAttrs(dst, src, o.xex, o.xeh); err != nil {
116 return fmt.Errorf("failed to copy xattrs: %w", err)
117 }
118
119 for _, entry := range entries {
120 source := filepath.Join(src, entry.Name())
121 target := filepath.Join(dst, entry.Name())
122
123 fileInfo, err := entry.Info()
124 if err != nil {
125 return fmt.Errorf("failed to get file info for %s: %w", entry.Name(), err)
126 }
127
128 switch {
129 case entry.IsDir():
130 if err := copyDirectory(target, source, inodes, o); err != nil {
131 return err
132 }
133 continue
134 case (fileInfo.Mode() & os.ModeType) == 0:
135 link, err := getLinkSource(target, fileInfo, inodes)
136 if err != nil {
137 return fmt.Errorf("failed to get hardlink: %w", err)
138 }
139 if link != "" {
140 if err := os.Link(link, target); err != nil {
141 return fmt.Errorf("failed to create hard link: %w", err)
142 }
143 } else if err := CopyFile(target, source); err != nil {
144 return fmt.Errorf("failed to copy files: %w", err)
145 }
146 case (fileInfo.Mode() & os.ModeSymlink) == os.ModeSymlink:
147 link, err := os.Readlink(source)
148 if err != nil {
149 return fmt.Errorf("failed to read link: %s: %w", source, err)
150 }
151 if err := os.Symlink(link, target); err != nil {
152 return fmt.Errorf("failed to create symlink: %s: %w", target, err)
153 }
154 case (fileInfo.Mode() & os.ModeDevice) == os.ModeDevice,
155 (fileInfo.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe,
156 (fileInfo.Mode() & os.ModeSocket) == os.ModeSocket:
157 if err := copyIrregular(target, fileInfo); err != nil {
158 return fmt.Errorf("failed to create irregular file: %w", err)
159 }
160 default:
161 logrus.Warnf("unsupported mode: %s: %s", source, fileInfo.Mode())
162 continue
163 }
164
165 if err := copyFileInfo(fileInfo, source, target); err != nil {
166 return fmt.Errorf("failed to copy file info: %w", err)
167 }
168
169 if err := copyXAttrs(target, source, o.xex, o.xeh); err != nil {
170 return fmt.Errorf("failed to copy xattrs: %w", err)
171 }
172 }
173
174 return nil
175 }
176
177
178
179 func CopyFile(target, source string) error {
180 return copyFile(target, source)
181 }
182
183 func openAndCopyFile(target, source string) error {
184 src, err := os.Open(source)
185 if err != nil {
186 return fmt.Errorf("failed to open source %s: %w", source, err)
187 }
188 defer src.Close()
189 tgt, err := os.Create(target)
190 if err != nil {
191 return fmt.Errorf("failed to open target %s: %w", target, err)
192 }
193 defer tgt.Close()
194
195 _, err = io.Copy(tgt, src)
196 return err
197 }
198
View as plain text