OSDN Git Service

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