github.com/Andyfoo/golang/x/net@v0.0.0-20190901054642-57c1bf301704/webdav/file.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package webdav 6 7 import ( 8 "context" 9 "encoding/xml" 10 "io" 11 "net/http" 12 "os" 13 "path" 14 "path/filepath" 15 "strings" 16 "sync" 17 "time" 18 ) 19 20 // slashClean is equivalent to but slightly more efficient than 21 // path.Clean("/" + name). 22 func slashClean(name string) string { 23 if name == "" || name[0] != '/' { 24 name = "/" + name 25 } 26 return path.Clean(name) 27 } 28 29 // A FileSystem implements access to a collection of named files. The elements 30 // in a file path are separated by slash ('/', U+002F) characters, regardless 31 // of host operating system convention. 32 // 33 // Each method has the same semantics as the os package's function of the same 34 // name. 35 // 36 // Note that the os.Rename documentation says that "OS-specific restrictions 37 // might apply". In particular, whether or not renaming a file or directory 38 // overwriting another existing file or directory is an error is OS-dependent. 39 type FileSystem interface { 40 Mkdir(ctx context.Context, name string, perm os.FileMode) error 41 OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) 42 RemoveAll(ctx context.Context, name string) error 43 Rename(ctx context.Context, oldName, newName string) error 44 Stat(ctx context.Context, name string) (os.FileInfo, error) 45 } 46 47 // A File is returned by a FileSystem's OpenFile method and can be served by a 48 // Handler. 49 // 50 // A File may optionally implement the DeadPropsHolder interface, if it can 51 // load and save dead properties. 52 type File interface { 53 http.File 54 io.Writer 55 } 56 57 // A Dir implements FileSystem using the native file system restricted to a 58 // specific directory tree. 59 // 60 // While the FileSystem.OpenFile method takes '/'-separated paths, a Dir's 61 // string value is a filename on the native file system, not a URL, so it is 62 // separated by filepath.Separator, which isn't necessarily '/'. 63 // 64 // An empty Dir is treated as ".". 65 type Dir string 66 67 func (d Dir) resolve(name string) string { 68 // This implementation is based on Dir.Open's code in the standard net/http package. 69 if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || 70 strings.Contains(name, "\x00") { 71 return "" 72 } 73 dir := string(d) 74 if dir == "" { 75 dir = "." 76 } 77 return filepath.Join(dir, filepath.FromSlash(slashClean(name))) 78 } 79 80 func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error { 81 if name = d.resolve(name); name == "" { 82 return os.ErrNotExist 83 } 84 return os.Mkdir(name, perm) 85 } 86 87 func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { 88 if name = d.resolve(name); name == "" { 89 return nil, os.ErrNotExist 90 } 91 f, err := os.OpenFile(name, flag, perm) 92 if err != nil { 93 return nil, err 94 } 95 return f, nil 96 } 97 98 func (d Dir) RemoveAll(ctx context.Context, name string) error { 99 if name = d.resolve(name); name == "" { 100 return os.ErrNotExist 101 } 102 if name == filepath.Clean(string(d)) { 103 // Prohibit removing the virtual root directory. 104 return os.ErrInvalid 105 } 106 return os.RemoveAll(name) 107 } 108 109 func (d Dir) Rename(ctx context.Context, oldName, newName string) error { 110 if oldName = d.resolve(oldName); oldName == "" { 111 return os.ErrNotExist 112 } 113 if newName = d.resolve(newName); newName == "" { 114 return os.ErrNotExist 115 } 116 if root := filepath.Clean(string(d)); root == oldName || root == newName { 117 // Prohibit renaming from or to the virtual root directory. 118 return os.ErrInvalid 119 } 120 return os.Rename(oldName, newName) 121 } 122 123 func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) { 124 if name = d.resolve(name); name == "" { 125 return nil, os.ErrNotExist 126 } 127 return os.Stat(name) 128 } 129 130 // NewMemFS returns a new in-memory FileSystem implementation. 131 func NewMemFS() FileSystem { 132 return &memFS{ 133 root: memFSNode{ 134 children: make(map[string]*memFSNode), 135 mode: 0660 | os.ModeDir, 136 modTime: time.Now(), 137 }, 138 } 139 } 140 141 // A memFS implements FileSystem, storing all metadata and actual file data 142 // in-memory. No limits on filesystem size are used, so it is not recommended 143 // this be used where the clients are untrusted. 144 // 145 // Concurrent access is permitted. The tree structure is protected by a mutex, 146 // and each node's contents and metadata are protected by a per-node mutex. 147 // 148 // TODO: Enforce file permissions. 149 type memFS struct { 150 mu sync.Mutex 151 root memFSNode 152 } 153 154 // TODO: clean up and rationalize the walk/find code. 155 156 // walk walks the directory tree for the fullname, calling f at each step. If f 157 // returns an error, the walk will be aborted and return that same error. 158 // 159 // dir is the directory at that step, frag is the name fragment, and final is 160 // whether it is the final step. For example, walking "/foo/bar/x" will result 161 // in 3 calls to f: 162 // - "/", "foo", false 163 // - "/foo/", "bar", false 164 // - "/foo/bar/", "x", true 165 // The frag argument will be empty only if dir is the root node and the walk 166 // ends at that root node. 167 func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error { 168 original := fullname 169 fullname = slashClean(fullname) 170 171 // Strip any leading "/"s to make fullname a relative path, as the walk 172 // starts at fs.root. 173 if fullname[0] == '/' { 174 fullname = fullname[1:] 175 } 176 dir := &fs.root 177 178 for { 179 frag, remaining := fullname, "" 180 i := strings.IndexRune(fullname, '/') 181 final := i < 0 182 if !final { 183 frag, remaining = fullname[:i], fullname[i+1:] 184 } 185 if frag == "" && dir != &fs.root { 186 panic("webdav: empty path fragment for a clean path") 187 } 188 if err := f(dir, frag, final); err != nil { 189 return &os.PathError{ 190 Op: op, 191 Path: original, 192 Err: err, 193 } 194 } 195 if final { 196 break 197 } 198 child := dir.children[frag] 199 if child == nil { 200 return &os.PathError{ 201 Op: op, 202 Path: original, 203 Err: os.ErrNotExist, 204 } 205 } 206 if !child.mode.IsDir() { 207 return &os.PathError{ 208 Op: op, 209 Path: original, 210 Err: os.ErrInvalid, 211 } 212 } 213 dir, fullname = child, remaining 214 } 215 return nil 216 } 217 218 // find returns the parent of the named node and the relative name fragment 219 // from the parent to the child. For example, if finding "/foo/bar/baz" then 220 // parent will be the node for "/foo/bar" and frag will be "baz". 221 // 222 // If the fullname names the root node, then parent, frag and err will be zero. 223 // 224 // find returns an error if the parent does not already exist or the parent 225 // isn't a directory, but it will not return an error per se if the child does 226 // not already exist. The error returned is either nil or an *os.PathError 227 // whose Op is op. 228 func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, err error) { 229 err = fs.walk(op, fullname, func(parent0 *memFSNode, frag0 string, final bool) error { 230 if !final { 231 return nil 232 } 233 if frag0 != "" { 234 parent, frag = parent0, frag0 235 } 236 return nil 237 }) 238 return parent, frag, err 239 } 240 241 func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { 242 fs.mu.Lock() 243 defer fs.mu.Unlock() 244 245 dir, frag, err := fs.find("mkdir", name) 246 if err != nil { 247 return err 248 } 249 if dir == nil { 250 // We can't create the root. 251 return os.ErrInvalid 252 } 253 if _, ok := dir.children[frag]; ok { 254 return os.ErrExist 255 } 256 dir.children[frag] = &memFSNode{ 257 children: make(map[string]*memFSNode), 258 mode: perm.Perm() | os.ModeDir, 259 modTime: time.Now(), 260 } 261 return nil 262 } 263 264 func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) { 265 fs.mu.Lock() 266 defer fs.mu.Unlock() 267 268 dir, frag, err := fs.find("open", name) 269 if err != nil { 270 return nil, err 271 } 272 var n *memFSNode 273 if dir == nil { 274 // We're opening the root. 275 if flag&(os.O_WRONLY|os.O_RDWR) != 0 { 276 return nil, os.ErrPermission 277 } 278 n, frag = &fs.root, "/" 279 280 } else { 281 n = dir.children[frag] 282 if flag&(os.O_SYNC|os.O_APPEND) != 0 { 283 // memFile doesn't support these flags yet. 284 return nil, os.ErrInvalid 285 } 286 if flag&os.O_CREATE != 0 { 287 if flag&os.O_EXCL != 0 && n != nil { 288 return nil, os.ErrExist 289 } 290 if n == nil { 291 n = &memFSNode{ 292 mode: perm.Perm(), 293 } 294 dir.children[frag] = n 295 } 296 } 297 if n == nil { 298 return nil, os.ErrNotExist 299 } 300 if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 { 301 n.mu.Lock() 302 n.data = nil 303 n.mu.Unlock() 304 } 305 } 306 307 children := make([]os.FileInfo, 0, len(n.children)) 308 for cName, c := range n.children { 309 children = append(children, c.stat(cName)) 310 } 311 return &memFile{ 312 n: n, 313 nameSnapshot: frag, 314 childrenSnapshot: children, 315 }, nil 316 } 317 318 func (fs *memFS) RemoveAll(ctx context.Context, name string) error { 319 fs.mu.Lock() 320 defer fs.mu.Unlock() 321 322 dir, frag, err := fs.find("remove", name) 323 if err != nil { 324 return err 325 } 326 if dir == nil { 327 // We can't remove the root. 328 return os.ErrInvalid 329 } 330 delete(dir.children, frag) 331 return nil 332 } 333 334 func (fs *memFS) Rename(ctx context.Context, oldName, newName string) error { 335 fs.mu.Lock() 336 defer fs.mu.Unlock() 337 338 oldName = slashClean(oldName) 339 newName = slashClean(newName) 340 if oldName == newName { 341 return nil 342 } 343 if strings.HasPrefix(newName, oldName+"/") { 344 // We can't rename oldName to be a sub-directory of itself. 345 return os.ErrInvalid 346 } 347 348 oDir, oFrag, err := fs.find("rename", oldName) 349 if err != nil { 350 return err 351 } 352 if oDir == nil { 353 // We can't rename from the root. 354 return os.ErrInvalid 355 } 356 357 nDir, nFrag, err := fs.find("rename", newName) 358 if err != nil { 359 return err 360 } 361 if nDir == nil { 362 // We can't rename to the root. 363 return os.ErrInvalid 364 } 365 366 oNode, ok := oDir.children[oFrag] 367 if !ok { 368 return os.ErrNotExist 369 } 370 if oNode.children != nil { 371 if nNode, ok := nDir.children[nFrag]; ok { 372 if nNode.children == nil { 373 return errNotADirectory 374 } 375 if len(nNode.children) != 0 { 376 return errDirectoryNotEmpty 377 } 378 } 379 } 380 delete(oDir.children, oFrag) 381 nDir.children[nFrag] = oNode 382 return nil 383 } 384 385 func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) { 386 fs.mu.Lock() 387 defer fs.mu.Unlock() 388 389 dir, frag, err := fs.find("stat", name) 390 if err != nil { 391 return nil, err 392 } 393 if dir == nil { 394 // We're stat'ting the root. 395 return fs.root.stat("/"), nil 396 } 397 if n, ok := dir.children[frag]; ok { 398 return n.stat(path.Base(name)), nil 399 } 400 return nil, os.ErrNotExist 401 } 402 403 // A memFSNode represents a single entry in the in-memory filesystem and also 404 // implements os.FileInfo. 405 type memFSNode struct { 406 // children is protected by memFS.mu. 407 children map[string]*memFSNode 408 409 mu sync.Mutex 410 data []byte 411 mode os.FileMode 412 modTime time.Time 413 deadProps map[xml.Name]Property 414 } 415 416 func (n *memFSNode) stat(name string) *memFileInfo { 417 n.mu.Lock() 418 defer n.mu.Unlock() 419 return &memFileInfo{ 420 name: name, 421 size: int64(len(n.data)), 422 mode: n.mode, 423 modTime: n.modTime, 424 } 425 } 426 427 func (n *memFSNode) DeadProps() (map[xml.Name]Property, error) { 428 n.mu.Lock() 429 defer n.mu.Unlock() 430 if len(n.deadProps) == 0 { 431 return nil, nil 432 } 433 ret := make(map[xml.Name]Property, len(n.deadProps)) 434 for k, v := range n.deadProps { 435 ret[k] = v 436 } 437 return ret, nil 438 } 439 440 func (n *memFSNode) Patch(patches []Proppatch) ([]Propstat, error) { 441 n.mu.Lock() 442 defer n.mu.Unlock() 443 pstat := Propstat{Status: http.StatusOK} 444 for _, patch := range patches { 445 for _, p := range patch.Props { 446 pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName}) 447 if patch.Remove { 448 delete(n.deadProps, p.XMLName) 449 continue 450 } 451 if n.deadProps == nil { 452 n.deadProps = map[xml.Name]Property{} 453 } 454 n.deadProps[p.XMLName] = p 455 } 456 } 457 return []Propstat{pstat}, nil 458 } 459 460 type memFileInfo struct { 461 name string 462 size int64 463 mode os.FileMode 464 modTime time.Time 465 } 466 467 func (f *memFileInfo) Name() string { return f.name } 468 func (f *memFileInfo) Size() int64 { return f.size } 469 func (f *memFileInfo) Mode() os.FileMode { return f.mode } 470 func (f *memFileInfo) ModTime() time.Time { return f.modTime } 471 func (f *memFileInfo) IsDir() bool { return f.mode.IsDir() } 472 func (f *memFileInfo) Sys() interface{} { return nil } 473 474 // A memFile is a File implementation for a memFSNode. It is a per-file (not 475 // per-node) read/write position, and a snapshot of the memFS' tree structure 476 // (a node's name and children) for that node. 477 type memFile struct { 478 n *memFSNode 479 nameSnapshot string 480 childrenSnapshot []os.FileInfo 481 // pos is protected by n.mu. 482 pos int 483 } 484 485 // A *memFile implements the optional DeadPropsHolder interface. 486 var _ DeadPropsHolder = (*memFile)(nil) 487 488 func (f *memFile) DeadProps() (map[xml.Name]Property, error) { return f.n.DeadProps() } 489 func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) } 490 491 func (f *memFile) Close() error { 492 return nil 493 } 494 495 func (f *memFile) Read(p []byte) (int, error) { 496 f.n.mu.Lock() 497 defer f.n.mu.Unlock() 498 if f.n.mode.IsDir() { 499 return 0, os.ErrInvalid 500 } 501 if f.pos >= len(f.n.data) { 502 return 0, io.EOF 503 } 504 n := copy(p, f.n.data[f.pos:]) 505 f.pos += n 506 return n, nil 507 } 508 509 func (f *memFile) Readdir(count int) ([]os.FileInfo, error) { 510 f.n.mu.Lock() 511 defer f.n.mu.Unlock() 512 if !f.n.mode.IsDir() { 513 return nil, os.ErrInvalid 514 } 515 old := f.pos 516 if old >= len(f.childrenSnapshot) { 517 // The os.File Readdir docs say that at the end of a directory, 518 // the error is io.EOF if count > 0 and nil if count <= 0. 519 if count > 0 { 520 return nil, io.EOF 521 } 522 return nil, nil 523 } 524 if count > 0 { 525 f.pos += count 526 if f.pos > len(f.childrenSnapshot) { 527 f.pos = len(f.childrenSnapshot) 528 } 529 } else { 530 f.pos = len(f.childrenSnapshot) 531 old = 0 532 } 533 return f.childrenSnapshot[old:f.pos], nil 534 } 535 536 func (f *memFile) Seek(offset int64, whence int) (int64, error) { 537 f.n.mu.Lock() 538 defer f.n.mu.Unlock() 539 npos := f.pos 540 // TODO: How to handle offsets greater than the size of system int? 541 switch whence { 542 case os.SEEK_SET: 543 npos = int(offset) 544 case os.SEEK_CUR: 545 npos += int(offset) 546 case os.SEEK_END: 547 npos = len(f.n.data) + int(offset) 548 default: 549 npos = -1 550 } 551 if npos < 0 { 552 return 0, os.ErrInvalid 553 } 554 f.pos = npos 555 return int64(f.pos), nil 556 } 557 558 func (f *memFile) Stat() (os.FileInfo, error) { 559 return f.n.stat(f.nameSnapshot), nil 560 } 561 562 func (f *memFile) Write(p []byte) (int, error) { 563 lenp := len(p) 564 f.n.mu.Lock() 565 defer f.n.mu.Unlock() 566 567 if f.n.mode.IsDir() { 568 return 0, os.ErrInvalid 569 } 570 if f.pos < len(f.n.data) { 571 n := copy(f.n.data[f.pos:], p) 572 f.pos += n 573 p = p[n:] 574 } else if f.pos > len(f.n.data) { 575 // Write permits the creation of holes, if we've seek'ed past the 576 // existing end of file. 577 if f.pos <= cap(f.n.data) { 578 oldLen := len(f.n.data) 579 f.n.data = f.n.data[:f.pos] 580 hole := f.n.data[oldLen:] 581 for i := range hole { 582 hole[i] = 0 583 } 584 } else { 585 d := make([]byte, f.pos, f.pos+len(p)) 586 copy(d, f.n.data) 587 f.n.data = d 588 } 589 } 590 591 if len(p) > 0 { 592 // We should only get here if f.pos == len(f.n.data). 593 f.n.data = append(f.n.data, p...) 594 f.pos = len(f.n.data) 595 } 596 f.n.modTime = time.Now() 597 return lenp, nil 598 } 599 600 // moveFiles moves files and/or directories from src to dst. 601 // 602 // See section 9.9.4 for when various HTTP status codes apply. 603 func moveFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool) (status int, err error) { 604 created := false 605 if _, err := fs.Stat(ctx, dst); err != nil { 606 if !os.IsNotExist(err) { 607 return http.StatusForbidden, err 608 } 609 created = true 610 } else if overwrite { 611 // Section 9.9.3 says that "If a resource exists at the destination 612 // and the Overwrite header is "T", then prior to performing the move, 613 // the server must perform a DELETE with "Depth: infinity" on the 614 // destination resource. 615 if err := fs.RemoveAll(ctx, dst); err != nil { 616 return http.StatusForbidden, err 617 } 618 } else { 619 return http.StatusPreconditionFailed, os.ErrExist 620 } 621 if err := fs.Rename(ctx, src, dst); err != nil { 622 return http.StatusForbidden, err 623 } 624 if created { 625 return http.StatusCreated, nil 626 } 627 return http.StatusNoContent, nil 628 } 629 630 func copyProps(dst, src File) error { 631 d, ok := dst.(DeadPropsHolder) 632 if !ok { 633 return nil 634 } 635 s, ok := src.(DeadPropsHolder) 636 if !ok { 637 return nil 638 } 639 m, err := s.DeadProps() 640 if err != nil { 641 return err 642 } 643 props := make([]Property, 0, len(m)) 644 for _, prop := range m { 645 props = append(props, prop) 646 } 647 _, err = d.Patch([]Proppatch{{Props: props}}) 648 return err 649 } 650 651 // copyFiles copies files and/or directories from src to dst. 652 // 653 // See section 9.8.5 for when various HTTP status codes apply. 654 func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bool, depth int, recursion int) (status int, err error) { 655 if recursion == 1000 { 656 return http.StatusInternalServerError, errRecursionTooDeep 657 } 658 recursion++ 659 660 // TODO: section 9.8.3 says that "Note that an infinite-depth COPY of /A/ 661 // into /A/B/ could lead to infinite recursion if not handled correctly." 662 663 srcFile, err := fs.OpenFile(ctx, src, os.O_RDONLY, 0) 664 if err != nil { 665 if os.IsNotExist(err) { 666 return http.StatusNotFound, err 667 } 668 return http.StatusInternalServerError, err 669 } 670 defer srcFile.Close() 671 srcStat, err := srcFile.Stat() 672 if err != nil { 673 if os.IsNotExist(err) { 674 return http.StatusNotFound, err 675 } 676 return http.StatusInternalServerError, err 677 } 678 srcPerm := srcStat.Mode() & os.ModePerm 679 680 created := false 681 if _, err := fs.Stat(ctx, dst); err != nil { 682 if os.IsNotExist(err) { 683 created = true 684 } else { 685 return http.StatusForbidden, err 686 } 687 } else { 688 if !overwrite { 689 return http.StatusPreconditionFailed, os.ErrExist 690 } 691 if err := fs.RemoveAll(ctx, dst); err != nil && !os.IsNotExist(err) { 692 return http.StatusForbidden, err 693 } 694 } 695 696 if srcStat.IsDir() { 697 if err := fs.Mkdir(ctx, dst, srcPerm); err != nil { 698 return http.StatusForbidden, err 699 } 700 if depth == infiniteDepth { 701 children, err := srcFile.Readdir(-1) 702 if err != nil { 703 return http.StatusForbidden, err 704 } 705 for _, c := range children { 706 name := c.Name() 707 s := path.Join(src, name) 708 d := path.Join(dst, name) 709 cStatus, cErr := copyFiles(ctx, fs, s, d, overwrite, depth, recursion) 710 if cErr != nil { 711 // TODO: MultiStatus. 712 return cStatus, cErr 713 } 714 } 715 } 716 717 } else { 718 dstFile, err := fs.OpenFile(ctx, dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcPerm) 719 if err != nil { 720 if os.IsNotExist(err) { 721 return http.StatusConflict, err 722 } 723 return http.StatusForbidden, err 724 725 } 726 _, copyErr := io.Copy(dstFile, srcFile) 727 propsErr := copyProps(dstFile, srcFile) 728 closeErr := dstFile.Close() 729 if copyErr != nil { 730 return http.StatusInternalServerError, copyErr 731 } 732 if propsErr != nil { 733 return http.StatusInternalServerError, propsErr 734 } 735 if closeErr != nil { 736 return http.StatusInternalServerError, closeErr 737 } 738 } 739 740 if created { 741 return http.StatusCreated, nil 742 } 743 return http.StatusNoContent, nil 744 } 745 746 // walkFS traverses filesystem fs starting at name up to depth levels. 747 // 748 // Allowed values for depth are 0, 1 or infiniteDepth. For each visited node, 749 // walkFS calls walkFn. If a visited file system node is a directory and 750 // walkFn returns filepath.SkipDir, walkFS will skip traversal of this node. 751 func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error { 752 // This implementation is based on Walk's code in the standard path/filepath package. 753 err := walkFn(name, info, nil) 754 if err != nil { 755 if info.IsDir() && err == filepath.SkipDir { 756 return nil 757 } 758 return err 759 } 760 if !info.IsDir() || depth == 0 { 761 return nil 762 } 763 if depth == 1 { 764 depth = 0 765 } 766 767 // Read directory names. 768 f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0) 769 if err != nil { 770 return walkFn(name, info, err) 771 } 772 fileInfos, err := f.Readdir(0) 773 f.Close() 774 if err != nil { 775 return walkFn(name, info, err) 776 } 777 778 for _, fileInfo := range fileInfos { 779 filename := path.Join(name, fileInfo.Name()) 780 fileInfo, err := fs.Stat(ctx, filename) 781 if err != nil { 782 if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { 783 return err 784 } 785 } else { 786 err = walkFS(ctx, fs, depth, filename, fileInfo, walkFn) 787 if err != nil { 788 if !fileInfo.IsDir() || err != filepath.SkipDir { 789 return err 790 } 791 } 792 } 793 } 794 return nil 795 }