file.go 18 KB

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