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