forked from golang/dep
/
dirwalk.go
139 lines (123 loc) · 4.76 KB
/
dirwalk.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pkgtree
import (
"os"
"path/filepath"
"sort"
"strings"
"github.com/pkg/errors"
)
// DirWalkFunc is the type of the function called for each file system node
// visited by DirWalk. The path argument contains the argument to DirWalk as a
// prefix; that is, if DirWalk is called with "dir", which is a directory
// containing the file "a", the walk function will be called with the argument
// "dir/a", using the correct os.PathSeparator for the Go Operating System
// architecture, GOOS. The info argument is the os.FileInfo for the named path.
//
// If there was a problem walking to the file or directory named by path, the
// incoming error will describe the problem and the function can decide how to
// handle that error (and DirWalk will not descend into that directory). If an
// error is returned, processing stops. The sole exception is when the function
// returns the special value filepath.SkipDir. If the function returns
// filepath.SkipDir when invoked on a directory, DirWalk skips the directory's
// contents entirely. If the function returns filepath.SkipDir when invoked on a
// non-directory file system node, DirWalk skips the remaining files in the
// containing directory.
type DirWalkFunc func(osPathname string, info os.FileInfo, err error) error
// DirWalk walks the file tree rooted at osDirname, calling for each file system
// node in the tree, including root. All errors that arise visiting nodes are
// filtered by walkFn. The nodes are walked in lexical order, which makes the
// output deterministic but means that for very large directories DirWalk can be
// inefficient. Unlike filepath.Walk, DirWalk does follow symbolic links.
func DirWalk(osDirname string, walkFn DirWalkFunc) error {
osDirname = filepath.Clean(osDirname)
// Ensure parameter is a directory
fi, err := os.Stat(osDirname)
if err != nil {
return errors.Wrap(err, "cannot read node")
}
if !fi.IsDir() {
return errors.Errorf("cannot walk non directory: %q", osDirname)
}
// Initialize a work queue with the empty string, which signifies the
// starting directory itself.
queue := []string{""}
var osRelative string // os-specific relative pathname under directory name
// As we enumerate over the queue and encounter a directory, its children
// will be added to the work queue.
for len(queue) > 0 {
// Unshift a pathname from the queue (breadth-first traversal of
// hierarchy)
osRelative, queue = queue[0], queue[1:]
osPathname := filepath.Join(osDirname, osRelative)
// walkFn needs to choose how to handle symbolic links, therefore obtain
// lstat rather than stat.
fi, err = os.Lstat(osPathname)
if err == nil {
err = walkFn(osPathname, fi, nil)
} else {
err = walkFn(osPathname, nil, errors.Wrap(err, "cannot read node"))
}
if err != nil {
if err == filepath.SkipDir {
if fi.Mode()&os.ModeSymlink > 0 {
// Resolve symbolic link referent to determine whether node
// is directory or not.
fi, err = os.Stat(osPathname)
if err != nil {
return errors.Wrap(err, "cannot visit node")
}
}
// If current node is directory, then skip this
// directory. Otherwise, skip all nodes in the same parent
// directory.
if !fi.IsDir() {
// Consume nodes from queue while they have the same parent
// as the current node.
osParent := filepath.Dir(osPathname) + osPathSeparator
for len(queue) > 0 && strings.HasPrefix(queue[0], osParent) {
queue = queue[1:] // drop sibling from queue
}
}
continue
}
return errors.Wrap(err, "DirWalkFunction") // wrap error returned by walkFn
}
if fi.IsDir() {
osChildrenNames, err := sortedChildrenFromDirname(osPathname)
if err != nil {
return errors.Wrap(err, "cannot get list of directory children")
}
for _, osChildName := range osChildrenNames {
switch osChildName {
case ".", "..":
// skip
default:
queue = append(queue, filepath.Join(osRelative, osChildName))
}
}
}
}
return nil
}
// sortedChildrenFromDirname returns a lexicographically sorted list of child
// nodes for the specified directory.
func sortedChildrenFromDirname(osDirname string) ([]string, error) {
fh, err := os.Open(osDirname)
if err != nil {
return nil, errors.Wrap(err, "cannot Open")
}
osChildrenNames, err := fh.Readdirnames(0) // 0: read names of all children
if err != nil {
return nil, errors.Wrap(err, "cannot Readdirnames")
}
sort.Strings(osChildrenNames)
// Close the file handle to the open directory without masking possible
// previous error value.
if er := fh.Close(); err == nil {
err = errors.Wrap(er, "cannot Close")
}
return osChildrenNames, err
}