1
16
17 package fs
18
19 import (
20 "bytes"
21 "context"
22 "errors"
23 "io"
24 "os"
25 "path/filepath"
26 )
27
28 var errTooManyLinks = errors.New("too many links")
29
30 type currentPath struct {
31 path string
32 f os.FileInfo
33 fullPath string
34 }
35
36 func pathChange(lower, upper *currentPath) (ChangeKind, string) {
37 if lower == nil {
38 if upper == nil {
39 panic("cannot compare nil paths")
40 }
41 return ChangeKindAdd, upper.path
42 }
43 if upper == nil {
44 return ChangeKindDelete, lower.path
45 }
46
47 switch i := directoryCompare(lower.path, upper.path); {
48 case i < 0:
49
50 return ChangeKindDelete, lower.path
51 case i > 0:
52
53 return ChangeKindAdd, upper.path
54 default:
55 return ChangeKindModify, upper.path
56 }
57 }
58
59 func directoryCompare(a, b string) int {
60 l := len(a)
61 if len(b) < l {
62 l = len(b)
63 }
64 for i := 0; i < l; i++ {
65 c1, c2 := a[i], b[i]
66 if c1 == filepath.Separator {
67 c1 = byte(0)
68 }
69 if c2 == filepath.Separator {
70 c2 = byte(0)
71 }
72 if c1 < c2 {
73 return -1
74 }
75 if c1 > c2 {
76 return +1
77 }
78 }
79 if len(a) < len(b) {
80 return -1
81 }
82 if len(a) > len(b) {
83 return +1
84 }
85 return 0
86 }
87
88 func sameFile(f1, f2 *currentPath) (bool, error) {
89 if os.SameFile(f1.f, f2.f) {
90 return true, nil
91 }
92
93 equalStat, err := compareSysStat(f1.f.Sys(), f2.f.Sys())
94 if err != nil || !equalStat {
95 return equalStat, err
96 }
97
98 if eq, err := compareCapabilities(f1.fullPath, f2.fullPath); err != nil || !eq {
99 return eq, err
100 }
101
102
103 if !f1.f.IsDir() {
104 if f1.f.Size() != f2.f.Size() {
105 return false, nil
106 }
107 t1 := f1.f.ModTime()
108 t2 := f2.f.ModTime()
109
110 if t1.Unix() != t2.Unix() {
111 return false, nil
112 }
113
114
115
116 if t1.Nanosecond() == 0 && t2.Nanosecond() == 0 {
117 if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink {
118 return compareSymlinkTarget(f1.fullPath, f2.fullPath)
119 }
120 if f1.f.Size() == 0 {
121 return true, nil
122 }
123 return compareFileContent(f1.fullPath, f2.fullPath)
124 } else if t1.Nanosecond() != t2.Nanosecond() {
125 return false, nil
126 }
127 }
128
129 return true, nil
130 }
131
132 func compareSymlinkTarget(p1, p2 string) (bool, error) {
133 t1, err := os.Readlink(p1)
134 if err != nil {
135 return false, err
136 }
137 t2, err := os.Readlink(p2)
138 if err != nil {
139 return false, err
140 }
141 return t1 == t2, nil
142 }
143
144 const compareChuckSize = 32 * 1024
145
146
147
148 func compareFileContent(p1, p2 string) (bool, error) {
149 f1, err := os.Open(p1)
150 if err != nil {
151 return false, err
152 }
153 defer f1.Close()
154 f2, err := os.Open(p2)
155 if err != nil {
156 return false, err
157 }
158 defer f2.Close()
159
160 b1 := make([]byte, compareChuckSize)
161 b2 := make([]byte, compareChuckSize)
162 for {
163 n1, err1 := f1.Read(b1)
164 if err1 != nil && err1 != io.EOF {
165 return false, err1
166 }
167 n2, err2 := f2.Read(b2)
168 if err2 != nil && err2 != io.EOF {
169 return false, err2
170 }
171 if n1 != n2 || !bytes.Equal(b1[:n1], b2[:n2]) {
172 return false, nil
173 }
174 if err1 == io.EOF && err2 == io.EOF {
175 return true, nil
176 }
177 }
178 }
179
180 func pathWalk(ctx context.Context, root string, pathC chan<- *currentPath) error {
181 return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
182 if err != nil {
183 return err
184 }
185
186
187 path, err = filepath.Rel(root, path)
188 if err != nil {
189 return err
190 }
191
192 path = filepath.Join(string(os.PathSeparator), path)
193
194
195 if path == string(os.PathSeparator) {
196 return nil
197 }
198
199 p := ¤tPath{
200 path: path,
201 f: f,
202 fullPath: filepath.Join(root, path),
203 }
204
205 select {
206 case <-ctx.Done():
207 return ctx.Err()
208 case pathC <- p:
209 return nil
210 }
211 })
212 }
213
214 func nextPath(ctx context.Context, pathC <-chan *currentPath) (*currentPath, error) {
215 select {
216 case <-ctx.Done():
217 return nil, ctx.Err()
218 case p := <-pathC:
219 return p, nil
220 }
221 }
222
223
224
225 func RootPath(root, path string) (string, error) {
226 if path == "" {
227 return root, nil
228 }
229 var linksWalked int
230 for {
231 i := linksWalked
232 newpath, err := walkLinks(root, path, &linksWalked)
233 if err != nil {
234 return "", err
235 }
236 path = newpath
237 if i == linksWalked {
238 newpath = filepath.Join("/", newpath)
239 if path == newpath {
240 return filepath.Join(root, newpath), nil
241 }
242 path = newpath
243 }
244 }
245 }
246
247 func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
248 if *linksWalked > 255 {
249 return "", false, errTooManyLinks
250 }
251
252 path = filepath.Join("/", path)
253 if path == "/" {
254 return path, false, nil
255 }
256 realPath := filepath.Join(root, path)
257
258 fi, err := os.Lstat(realPath)
259 if err != nil {
260
261 if os.IsNotExist(err) {
262 return path, false, nil
263 }
264 return "", false, err
265 }
266 if fi.Mode()&os.ModeSymlink == 0 {
267 return path, false, nil
268 }
269 newpath, err = os.Readlink(realPath)
270 if err != nil {
271 return "", false, err
272 }
273 *linksWalked++
274 return newpath, true, nil
275 }
276
277 func walkLinks(root, path string, linksWalked *int) (string, error) {
278 switch dir, file := filepath.Split(path); {
279 case dir == "":
280 newpath, _, err := walkLink(root, file, linksWalked)
281 return newpath, err
282 case file == "":
283 if os.IsPathSeparator(dir[len(dir)-1]) {
284 if dir == "/" {
285 return dir, nil
286 }
287 return walkLinks(root, dir[:len(dir)-1], linksWalked)
288 }
289 newpath, _, err := walkLink(root, dir, linksWalked)
290 return newpath, err
291 default:
292 newdir, err := walkLinks(root, dir, linksWalked)
293 if err != nil {
294 return "", err
295 }
296 newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
297 if err != nil {
298 return "", err
299 }
300 if !islink {
301 return newpath, nil
302 }
303 if filepath.IsAbs(newpath) {
304 return newpath, nil
305 }
306 return filepath.Join(newdir, newpath), nil
307 }
308 }
309
View as plain text