forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
walker.go
179 lines (145 loc) · 4.05 KB
/
walker.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package recycle
import (
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
)
type walkFunc func(path string, info os.FileInfo) error
// Walker visits a directory tree, depth-first, calling walkFn for every file/directory.
// It calls setfsuid with the owning UID of each directory before calling walkFn for the direct children of that directory.
type walker struct {
walkFn walkFunc
// fsuid holds our current fsuid
fsuid int64
// lstat is for testing, defaults to os.Lstat
lstat func(path string) (os.FileInfo, error)
// getuid is for testing, defaults to fileinfo.Sys().(*syscall.Stat_t).Uid
getuid func(info os.FileInfo) (int64, error)
// setfsuid is for testing, defaults to syscall.Setfsuid
setfsuid func(uid int) error
// readDirNames is for testing, defaults to readDirNames
readDirNames func(dirname string) ([]string, error)
}
type walkError struct {
path string
info os.FileInfo
operation string
err error
}
func (w walkError) Error() string {
var mode interface{} = "unknown"
if w.info != nil {
mode = w.info.Mode()
}
return fmt.Sprintf("%s (%s), %s: %s", w.path, mode, w.operation, w.err)
}
func makeWalkError(path string, info os.FileInfo, err error, operation string) error {
if _, isWalkError := err.(walkError); isWalkError {
return err
}
return walkError{path, info, operation, err}
}
func newWalker(walkFn walkFunc) *walker {
return &walker{
walkFn: walkFn,
fsuid: int64(os.Getuid()), // default to the uid of the process
lstat: os.Lstat,
getuid: getuid,
setfsuid: setfsuid,
readDirNames: readDirNames,
}
}
func (w *walker) Walk(root string) error {
// Lock threads, so our Setfsuid calls always apply to the same thread
runtime.LockOSThread()
defer runtime.UnlockOSThread()
// The launching process must have the ability to stat the root dir
info, err := w.lstat(root)
if err != nil {
return makeWalkError(root, info, err, "lstat root dir")
}
// become the root dir's owner to begin
err = w.becomeOwner(info)
if err != nil {
return makeWalkError(root, info, err, "becoming root dir owner")
}
return w.walk(root, info)
}
func (w *walker) walk(path string, info os.FileInfo) error {
var err error
// Descend first
if info.IsDir() {
// Remember our current fsuid
previousFSuid := w.fsuid
// become the dir's owner, in order to list/rmdir/unlink child files
err = w.becomeOwner(info)
if err != nil {
return makeWalkError(path, info, err, "becoming dir owner")
}
// read dir info
names, err := w.readDirNames(path)
if err != nil {
return makeWalkError(path, info, err, fmt.Sprintf("reading dir names as %d", w.fsuid))
}
// visit files
for _, name := range names {
filename := filepath.Join(path, name)
fileInfo, err := w.lstat(filename)
if err != nil {
return makeWalkError(path, info, err, fmt.Sprintf("lstat child as %d", w.fsuid))
}
err = w.walk(filename, fileInfo)
if err != nil {
return err
}
}
// Return to our previous fsuid, in order to rmdir the current directory
err = w.becomeUid(previousFSuid)
if err != nil {
return makeWalkError(path, info, err, "returning to previous uid")
}
}
// visit the current file
err = w.walkFn(path, info)
if err != nil {
return makeWalkError(path, info, err, "calling walkFn")
}
return nil
}
func (w *walker) becomeOwner(info os.FileInfo) error {
// get the UID
uid, err := w.getuid(info)
if err != nil {
return err
}
return w.becomeUid(uid)
}
func (w *walker) becomeUid(uid int64) error {
// if we already were the UID, no-op
if w.fsuid == uid {
return nil
}
// become the UID
if err := w.setfsuid(int(uid)); err != nil {
return err
}
// remember the last UID we became
w.fsuid = uid
return nil
}
// readDirNames reads the directory named by dirname and returns a sorted list of directory entries.
func readDirNames(dirname string) ([]string, error) {
f, err := os.Open(dirname)
if err != nil {
return nil, err
}
defer f.Close()
names, err := f.Readdirnames(-1)
if err != nil {
return nil, err
}
sort.Strings(names)
return names, nil
}