blob: 21754d055778436f4afb041538355697d74db502 [file] [log] [blame]
Colin Crossb519a7e2017-02-01 13:21:35 -08001// Copyright 2016 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package pathtools
16
17import (
18 "bytes"
Colin Cross9e1ff742018-09-20 22:44:36 -070019 "fmt"
Colin Crossb519a7e2017-02-01 13:21:35 -080020 "io"
21 "io/ioutil"
22 "os"
23 "path/filepath"
Colin Crossa4fae902017-10-03 15:58:50 -070024 "sort"
25 "strings"
Colin Crossc64f2642018-09-20 21:48:44 -070026 "syscall"
27 "time"
Colin Crossb519a7e2017-02-01 13:21:35 -080028)
29
30// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"
31
Colin Crosse98d0822018-09-21 15:30:13 -070032type ShouldFollowSymlinks bool
33
34const (
35 FollowSymlinks = ShouldFollowSymlinks(true)
36 DontFollowSymlinks = ShouldFollowSymlinks(false)
37)
38
Colin Crossc5fa50e2019-12-17 13:12:35 -080039var OsFs FileSystem = &osFs{}
Colin Crossb519a7e2017-02-01 13:21:35 -080040
41func MockFs(files map[string][]byte) FileSystem {
42 fs := &mockFs{
Colin Crossc64f2642018-09-20 21:48:44 -070043 files: make(map[string][]byte, len(files)),
44 dirs: make(map[string]bool),
45 symlinks: make(map[string]string),
46 all: []string(nil),
Colin Crossb519a7e2017-02-01 13:21:35 -080047 }
48
49 for f, b := range files {
Colin Crossc64f2642018-09-20 21:48:44 -070050 if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 {
51 fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1])
52 continue
53 }
54
Colin Crossb519a7e2017-02-01 13:21:35 -080055 fs.files[filepath.Clean(f)] = b
56 dir := filepath.Dir(f)
57 for dir != "." && dir != "/" {
58 fs.dirs[dir] = true
59 dir = filepath.Dir(dir)
60 }
Colin Cross616c2782017-07-14 08:16:00 -070061 fs.dirs[dir] = true
Colin Crossb519a7e2017-02-01 13:21:35 -080062 }
63
Colin Crossa4cc6832019-03-25 15:04:17 -070064 fs.dirs["."] = true
65 fs.dirs["/"] = true
66
Colin Crossb519a7e2017-02-01 13:21:35 -080067 for f := range fs.files {
68 fs.all = append(fs.all, f)
69 }
70
71 for d := range fs.dirs {
72 fs.all = append(fs.all, d)
73 }
74
Colin Crossc64f2642018-09-20 21:48:44 -070075 for s := range fs.symlinks {
76 fs.all = append(fs.all, s)
77 }
78
Colin Crossa4fae902017-10-03 15:58:50 -070079 sort.Strings(fs.all)
80
Colin Crossb519a7e2017-02-01 13:21:35 -080081 return fs
82}
83
Colin Cross192dbc52018-09-26 16:49:55 -070084type ReaderAtSeekerCloser interface {
85 io.Reader
86 io.ReaderAt
87 io.Seeker
88 io.Closer
89}
90
Colin Crossb519a7e2017-02-01 13:21:35 -080091type FileSystem interface {
Colin Crossc64f2642018-09-20 21:48:44 -070092 // Open opens a file for reading. Follows symlinks.
Colin Cross192dbc52018-09-26 16:49:55 -070093 Open(name string) (ReaderAtSeekerCloser, error)
Colin Crossc64f2642018-09-20 21:48:44 -070094
95 // Exists returns whether the file exists and whether it is a directory. Follows symlinks.
Colin Crossb519a7e2017-02-01 13:21:35 -080096 Exists(name string) (bool, bool, error)
Colin Crossc64f2642018-09-20 21:48:44 -070097
Colin Crosse98d0822018-09-21 15:30:13 -070098 Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error)
Colin Crossb519a7e2017-02-01 13:21:35 -080099 glob(pattern string) (matches []string, err error)
Colin Crossc64f2642018-09-20 21:48:44 -0700100
101 // IsDir returns true if the path points to a directory, false it it points to a file. Follows symlinks.
102 // Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist.
Colin Crossb519a7e2017-02-01 13:21:35 -0800103 IsDir(name string) (bool, error)
Colin Crossc64f2642018-09-20 21:48:44 -0700104
105 // IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does
106 // not exist. Returns os.ErrNotExist if the path does not exist.
Colin Crosse3b7ec32018-09-20 14:36:10 -0700107 IsSymlink(name string) (bool, error)
Colin Crossc64f2642018-09-20 21:48:44 -0700108
109 // Lstat returns info on a file without following symlinks.
Jeff Gastonaca42202017-08-23 17:30:05 -0700110 Lstat(name string) (os.FileInfo, error)
Colin Crossc64f2642018-09-20 21:48:44 -0700111
Colin Cross3316a5e2018-09-27 15:49:45 -0700112 // Lstat returns info on a file.
113 Stat(name string) (os.FileInfo, error)
114
Colin Crosse98d0822018-09-21 15:30:13 -0700115 // ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested.
116 ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error)
Colin Cross9e1ff742018-09-20 22:44:36 -0700117
118 // ReadDirNames returns a list of everything in a directory.
119 ReadDirNames(name string) ([]string, error)
Colin Crosse81b4322018-09-26 16:53:26 -0700120
121 // Readlink returns the destination of the named symbolic link.
122 Readlink(name string) (string, error)
Colin Crossb519a7e2017-02-01 13:21:35 -0800123}
124
125// osFs implements FileSystem using the local disk.
Colin Crossc5fa50e2019-12-17 13:12:35 -0800126type osFs struct {
127 srcDir string
128}
Colin Crossb519a7e2017-02-01 13:21:35 -0800129
Colin Crossc5fa50e2019-12-17 13:12:35 -0800130func NewOsFs(path string) FileSystem {
131 return &osFs{srcDir: path}
132}
133
Colin Cross2f95ec72020-01-10 13:47:35 -0800134func (fs *osFs) toAbs(path string) string {
135 if filepath.IsAbs(path) {
136 return path
137 }
138 return filepath.Join(fs.srcDir, path)
139}
140
Colin Crossc5fa50e2019-12-17 13:12:35 -0800141func (fs *osFs) removeSrcDirPrefix(path string) string {
142 if fs.srcDir == "" {
143 return path
144 }
145 rel, err := filepath.Rel(fs.srcDir, path)
146 if err != nil {
147 panic(fmt.Errorf("unexpected failure in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
148 fs.srcDir, path, err))
149 }
150 if strings.HasPrefix(rel, "../") {
151 panic(fmt.Errorf("unexpected relative path outside directory in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
152 fs.srcDir, path, rel))
153 }
154 return rel
155}
156
157func (fs *osFs) removeSrcDirPrefixes(paths []string) []string {
158 if fs.srcDir != "" {
159 for i, path := range paths {
160 paths[i] = fs.removeSrcDirPrefix(path)
161 }
162 }
163 return paths
164}
165
166func (fs *osFs) Open(name string) (ReaderAtSeekerCloser, error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800167 return os.Open(fs.toAbs(name))
Colin Crossc5fa50e2019-12-17 13:12:35 -0800168}
169
170func (fs *osFs) Exists(name string) (bool, bool, error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800171 stat, err := os.Stat(fs.toAbs(name))
Colin Crossb519a7e2017-02-01 13:21:35 -0800172 if err == nil {
173 return true, stat.IsDir(), nil
174 } else if os.IsNotExist(err) {
175 return false, false, nil
176 } else {
177 return false, false, err
178 }
179}
180
Colin Crossc5fa50e2019-12-17 13:12:35 -0800181func (fs *osFs) IsDir(name string) (bool, error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800182 info, err := os.Stat(fs.toAbs(name))
Colin Crossb519a7e2017-02-01 13:21:35 -0800183 if err != nil {
Colin Crosse3b7ec32018-09-20 14:36:10 -0700184 return false, err
Colin Crossb519a7e2017-02-01 13:21:35 -0800185 }
186 return info.IsDir(), nil
187}
188
Colin Crossc5fa50e2019-12-17 13:12:35 -0800189func (fs *osFs) IsSymlink(name string) (bool, error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800190 if info, err := os.Lstat(fs.toAbs(name)); err != nil {
Colin Crosse3b7ec32018-09-20 14:36:10 -0700191 return false, err
192 } else {
193 return info.Mode()&os.ModeSymlink != 0, nil
194 }
195}
196
Colin Crossc5fa50e2019-12-17 13:12:35 -0800197func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
Colin Crosse98d0822018-09-21 15:30:13 -0700198 return startGlob(fs, pattern, excludes, follow)
Colin Crossb519a7e2017-02-01 13:21:35 -0800199}
200
Colin Crossc5fa50e2019-12-17 13:12:35 -0800201func (fs *osFs) glob(pattern string) ([]string, error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800202 paths, err := filepath.Glob(fs.toAbs(pattern))
Colin Crossc5fa50e2019-12-17 13:12:35 -0800203 fs.removeSrcDirPrefixes(paths)
204 return paths, err
Colin Crossb519a7e2017-02-01 13:21:35 -0800205}
206
Colin Crossc5fa50e2019-12-17 13:12:35 -0800207func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800208 return os.Lstat(fs.toAbs(path))
Jeff Gastonaca42202017-08-23 17:30:05 -0700209}
210
Colin Crossc5fa50e2019-12-17 13:12:35 -0800211func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800212 return os.Stat(fs.toAbs(path))
Colin Cross3316a5e2018-09-27 15:49:45 -0700213}
214
Colin Crossa4fae902017-10-03 15:58:50 -0700215// Returns a list of all directories under dir
Colin Crossc5fa50e2019-12-17 13:12:35 -0800216func (fs *osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
217 return listDirsRecursive(fs, name, follow)
Colin Cross9e1ff742018-09-20 22:44:36 -0700218}
Colin Crossa4fae902017-10-03 15:58:50 -0700219
Colin Crossc5fa50e2019-12-17 13:12:35 -0800220func (fs *osFs) ReadDirNames(name string) ([]string, error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800221 dir, err := os.Open(fs.toAbs(name))
Colin Cross9e1ff742018-09-20 22:44:36 -0700222 if err != nil {
223 return nil, err
224 }
225 defer dir.Close()
Colin Crossa4fae902017-10-03 15:58:50 -0700226
Colin Cross9e1ff742018-09-20 22:44:36 -0700227 contents, err := dir.Readdirnames(-1)
228 if err != nil {
229 return nil, err
230 }
Colin Crossa4fae902017-10-03 15:58:50 -0700231
Colin Cross9e1ff742018-09-20 22:44:36 -0700232 sort.Strings(contents)
233 return contents, nil
Colin Crossa4fae902017-10-03 15:58:50 -0700234}
235
Colin Crossc5fa50e2019-12-17 13:12:35 -0800236func (fs *osFs) Readlink(name string) (string, error) {
Colin Cross2f95ec72020-01-10 13:47:35 -0800237 return os.Readlink(fs.toAbs(name))
Colin Crosse81b4322018-09-26 16:53:26 -0700238}
239
Colin Crossb519a7e2017-02-01 13:21:35 -0800240type mockFs struct {
Colin Crossc64f2642018-09-20 21:48:44 -0700241 files map[string][]byte
242 dirs map[string]bool
243 symlinks map[string]string
244 all []string
245}
246
247func (m *mockFs) followSymlinks(name string) string {
248 dir, file := saneSplit(name)
249 if dir != "." && dir != "/" {
250 dir = m.followSymlinks(dir)
251 }
252 name = filepath.Join(dir, file)
253
254 for i := 0; i < 255; i++ {
255 i++
256 if i > 255 {
257 panic("symlink loop")
258 }
259 to, exists := m.symlinks[name]
260 if !exists {
261 break
262 }
263 if filepath.IsAbs(to) {
264 name = to
265 } else {
266 name = filepath.Join(dir, to)
267 }
268 }
269 return name
Colin Crossb519a7e2017-02-01 13:21:35 -0800270}
271
Colin Cross192dbc52018-09-26 16:49:55 -0700272func (m *mockFs) Open(name string) (ReaderAtSeekerCloser, error) {
Colin Crossc64f2642018-09-20 21:48:44 -0700273 name = filepath.Clean(name)
274 name = m.followSymlinks(name)
Colin Crossb519a7e2017-02-01 13:21:35 -0800275 if f, ok := m.files[name]; ok {
276 return struct {
277 io.Closer
278 *bytes.Reader
279 }{
280 ioutil.NopCloser(nil),
281 bytes.NewReader(f),
282 }, nil
283 }
284
285 return nil, &os.PathError{
286 Op: "open",
287 Path: name,
288 Err: os.ErrNotExist,
289 }
290}
291
292func (m *mockFs) Exists(name string) (bool, bool, error) {
293 name = filepath.Clean(name)
Colin Crossc64f2642018-09-20 21:48:44 -0700294 name = m.followSymlinks(name)
Colin Crossb519a7e2017-02-01 13:21:35 -0800295 if _, ok := m.files[name]; ok {
296 return ok, false, nil
297 }
298 if _, ok := m.dirs[name]; ok {
299 return ok, true, nil
300 }
301 return false, false, nil
302}
303
304func (m *mockFs) IsDir(name string) (bool, error) {
Colin Crossc64f2642018-09-20 21:48:44 -0700305 dir := filepath.Dir(name)
306 if dir != "." && dir != "/" {
307 isDir, err := m.IsDir(dir)
308
309 if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR {
310 isDir = false
311 } else if err != nil {
312 return false, err
313 }
314
315 if !isDir {
316 return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR)
317 }
318 }
319
320 name = filepath.Clean(name)
321 name = m.followSymlinks(name)
322
323 if _, ok := m.dirs[name]; ok {
324 return true, nil
325 }
326 if _, ok := m.files[name]; ok {
327 return false, nil
328 }
329 return false, os.ErrNotExist
Colin Crossb519a7e2017-02-01 13:21:35 -0800330}
331
Colin Crosse3b7ec32018-09-20 14:36:10 -0700332func (m *mockFs) IsSymlink(name string) (bool, error) {
Colin Crossc64f2642018-09-20 21:48:44 -0700333 dir, file := saneSplit(name)
334 dir = m.followSymlinks(dir)
335 name = filepath.Join(dir, file)
336
337 if _, isSymlink := m.symlinks[name]; isSymlink {
338 return true, nil
339 }
340 if _, isDir := m.dirs[name]; isDir {
341 return false, nil
342 }
343 if _, isFile := m.files[name]; isFile {
344 return false, nil
345 }
346 return false, os.ErrNotExist
Colin Crosse3b7ec32018-09-20 14:36:10 -0700347}
348
Colin Crosse98d0822018-09-21 15:30:13 -0700349func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
350 return startGlob(m, pattern, excludes, follow)
Colin Crossb519a7e2017-02-01 13:21:35 -0800351}
352
Colin Crossc64f2642018-09-20 21:48:44 -0700353func unescapeGlob(s string) string {
354 i := 0
355 for i < len(s) {
356 if s[i] == '\\' {
357 s = s[:i] + s[i+1:]
358 } else {
359 i++
360 }
361 }
362 return s
363}
364
Colin Crossb519a7e2017-02-01 13:21:35 -0800365func (m *mockFs) glob(pattern string) ([]string, error) {
Colin Crossc64f2642018-09-20 21:48:44 -0700366 dir, file := saneSplit(pattern)
367
368 dir = unescapeGlob(dir)
369 toDir := m.followSymlinks(dir)
370
Colin Crossb519a7e2017-02-01 13:21:35 -0800371 var matches []string
372 for _, f := range m.all {
Colin Crossc64f2642018-09-20 21:48:44 -0700373 fDir, fFile := saneSplit(f)
374 if toDir == fDir {
375 match, err := filepath.Match(file, fFile)
376 if err != nil {
377 return nil, err
378 }
Colin Crossa4cc6832019-03-25 15:04:17 -0700379 if (f == "." || f == "/") && f != pattern {
380 // filepath.Glob won't return "." or "/" unless the pattern was "." or "/"
Colin Crossc64f2642018-09-20 21:48:44 -0700381 match = false
382 }
383 if match {
384 matches = append(matches, filepath.Join(dir, fFile))
385 }
Colin Crossb519a7e2017-02-01 13:21:35 -0800386 }
387 }
388 return matches, nil
389}
Jeff Gastonaca42202017-08-23 17:30:05 -0700390
Colin Crossc64f2642018-09-20 21:48:44 -0700391type mockStat struct {
392 name string
393 size int64
394 mode os.FileMode
395}
396
397func (ms *mockStat) Name() string { return ms.name }
398func (ms *mockStat) IsDir() bool { return ms.Mode().IsDir() }
399func (ms *mockStat) Size() int64 { return ms.size }
400func (ms *mockStat) Mode() os.FileMode { return ms.mode }
401func (ms *mockStat) ModTime() time.Time { return time.Time{} }
402func (ms *mockStat) Sys() interface{} { return nil }
403
404func (m *mockFs) Lstat(name string) (os.FileInfo, error) {
Colin Cross3316a5e2018-09-27 15:49:45 -0700405 dir, file := saneSplit(name)
406 dir = m.followSymlinks(dir)
407 name = filepath.Join(dir, file)
Colin Crossc64f2642018-09-20 21:48:44 -0700408
409 ms := mockStat{
Colin Cross3316a5e2018-09-27 15:49:45 -0700410 name: file,
411 }
412
413 if symlink, isSymlink := m.symlinks[name]; isSymlink {
414 ms.mode = os.ModeSymlink
415 ms.size = int64(len(symlink))
416 } else if _, isDir := m.dirs[name]; isDir {
417 ms.mode = os.ModeDir
418 } else if _, isFile := m.files[name]; isFile {
419 ms.mode = 0
420 ms.size = int64(len(m.files[name]))
421 } else {
422 return nil, os.ErrNotExist
423 }
424
425 return &ms, nil
426}
427
428func (m *mockFs) Stat(name string) (os.FileInfo, error) {
429 name = filepath.Clean(name)
430 origName := name
431 name = m.followSymlinks(name)
432
433 ms := mockStat{
434 name: filepath.Base(origName),
Colin Crossc64f2642018-09-20 21:48:44 -0700435 size: int64(len(m.files[name])),
436 }
437
Colin Cross3316a5e2018-09-27 15:49:45 -0700438 if _, isDir := m.dirs[name]; isDir {
Colin Crossc64f2642018-09-20 21:48:44 -0700439 ms.mode = os.ModeDir
Colin Cross3316a5e2018-09-27 15:49:45 -0700440 } else if _, isFile := m.files[name]; isFile {
Colin Crossc64f2642018-09-20 21:48:44 -0700441 ms.mode = 0
Colin Cross3316a5e2018-09-27 15:49:45 -0700442 ms.size = int64(len(m.files[name]))
443 } else {
444 return nil, os.ErrNotExist
Colin Crossc64f2642018-09-20 21:48:44 -0700445 }
446
447 return &ms, nil
Jeff Gastonaca42202017-08-23 17:30:05 -0700448}
Colin Crossa4fae902017-10-03 15:58:50 -0700449
Colin Cross9e1ff742018-09-20 22:44:36 -0700450func (m *mockFs) ReadDirNames(name string) ([]string, error) {
Colin Crossa4fae902017-10-03 15:58:50 -0700451 name = filepath.Clean(name)
Colin Cross9e1ff742018-09-20 22:44:36 -0700452 name = m.followSymlinks(name)
453
454 exists, isDir, err := m.Exists(name)
455 if err != nil {
456 return nil, err
Colin Crossa4fae902017-10-03 15:58:50 -0700457 }
Colin Cross9e1ff742018-09-20 22:44:36 -0700458 if !exists {
459 return nil, os.ErrNotExist
460 }
461 if !isDir {
462 return nil, os.NewSyscallError("readdir", syscall.ENOTDIR)
463 }
464
465 var ret []string
Colin Crossa4fae902017-10-03 15:58:50 -0700466 for _, f := range m.all {
Colin Cross9e1ff742018-09-20 22:44:36 -0700467 dir, file := saneSplit(f)
468 if dir == name && len(file) > 0 && file[0] != '.' {
469 ret = append(ret, file)
470 }
471 }
472 return ret, nil
473}
474
Colin Crosse98d0822018-09-21 15:30:13 -0700475func (m *mockFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) ([]string, error) {
476 return listDirsRecursive(m, name, follow)
Colin Cross9e1ff742018-09-20 22:44:36 -0700477}
478
Colin Crosse81b4322018-09-26 16:53:26 -0700479func (m *mockFs) Readlink(name string) (string, error) {
480 dir, file := saneSplit(name)
481 dir = m.followSymlinks(dir)
482
483 origName := name
484 name = filepath.Join(dir, file)
485
486 if dest, isSymlink := m.symlinks[name]; isSymlink {
487 return dest, nil
488 }
489
490 if exists, _, err := m.Exists(name); err != nil {
491 return "", err
492 } else if !exists {
493 return "", os.ErrNotExist
494 } else {
495 return "", os.NewSyscallError("readlink: "+origName, syscall.EINVAL)
496 }
497}
498
Colin Crosse98d0822018-09-21 15:30:13 -0700499func listDirsRecursive(fs FileSystem, name string, follow ShouldFollowSymlinks) ([]string, error) {
Colin Cross9e1ff742018-09-20 22:44:36 -0700500 name = filepath.Clean(name)
501
502 isDir, err := fs.IsDir(name)
503 if err != nil {
504 return nil, err
505 }
506
507 if !isDir {
508 return nil, nil
509 }
510
511 dirs := []string{name}
512
Colin Crosse98d0822018-09-21 15:30:13 -0700513 subDirs, err := listDirsRecursiveRelative(fs, name, follow, 0)
Colin Cross9e1ff742018-09-20 22:44:36 -0700514 if err != nil {
515 return nil, err
516 }
517
518 for _, d := range subDirs {
519 dirs = append(dirs, filepath.Join(name, d))
520 }
521
522 return dirs, nil
523}
524
Colin Crosse98d0822018-09-21 15:30:13 -0700525func listDirsRecursiveRelative(fs FileSystem, name string, follow ShouldFollowSymlinks, depth int) ([]string, error) {
Colin Cross9e1ff742018-09-20 22:44:36 -0700526 depth++
527 if depth > 255 {
528 return nil, fmt.Errorf("too many symlinks")
529 }
530 contents, err := fs.ReadDirNames(name)
531 if err != nil {
532 return nil, err
533 }
534
535 var dirs []string
536 for _, f := range contents {
537 if f[0] == '.' {
538 continue
539 }
540 f = filepath.Join(name, f)
Colin Crosse98d0822018-09-21 15:30:13 -0700541 if isSymlink, _ := fs.IsSymlink(f); isSymlink && follow == DontFollowSymlinks {
542 continue
543 }
Colin Cross9e1ff742018-09-20 22:44:36 -0700544 if isDir, _ := fs.IsDir(f); isDir {
545 dirs = append(dirs, f)
Colin Crosse98d0822018-09-21 15:30:13 -0700546 subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth)
Colin Cross9e1ff742018-09-20 22:44:36 -0700547 if err != nil {
548 return nil, err
549 }
550 for _, s := range subDirs {
551 dirs = append(dirs, filepath.Join(f, s))
Colin Crossa4fae902017-10-03 15:58:50 -0700552 }
553 }
554 }
555
Colin Cross9e1ff742018-09-20 22:44:36 -0700556 for i, d := range dirs {
557 rel, err := filepath.Rel(name, d)
558 if err != nil {
559 return nil, err
560 }
561 dirs[i] = rel
562 }
563
Colin Crossa4fae902017-10-03 15:58:50 -0700564 return dirs, nil
565}