-
Notifications
You must be signed in to change notification settings - Fork 18k
/
Copy pathroot.go
391 lines (354 loc) · 11.7 KB
/
root.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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// Copyright 2024 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 os
import (
"errors"
"internal/bytealg"
"internal/stringslite"
"internal/testlog"
"io/fs"
"runtime"
"slices"
"time"
)
// OpenInRoot opens the file name in the directory dir.
// It is equivalent to OpenRoot(dir) followed by opening the file in the root.
//
// OpenInRoot returns an error if any component of the name
// references a location outside of dir.
//
// See [Root] for details and limitations.
func OpenInRoot(dir, name string) (*File, error) {
r, err := OpenRoot(dir)
if err != nil {
return nil, err
}
defer r.Close()
return r.Open(name)
}
// Root may be used to only access files within a single directory tree.
//
// Methods on Root can only access files and directories beneath a root directory.
// If any component of a file name passed to a method of Root references a location
// outside the root, the method returns an error.
// File names may reference the directory itself (.).
//
// Methods on Root will follow symbolic links, but symbolic links may not
// reference a location outside the root.
// Symbolic links must not be absolute.
//
// Methods on Root do not prohibit traversal of filesystem boundaries,
// Linux bind mounts, /proc special files, or access to Unix device files.
//
// Methods on Root are safe to be used from multiple goroutines simultaneously.
//
// On most platforms, creating a Root opens a file descriptor or handle referencing
// the directory. If the directory is moved, methods on Root reference the original
// directory in its new location.
//
// Root's behavior differs on some platforms:
//
// - When GOOS=windows, file names may not reference Windows reserved device names
// such as NUL and COM1.
// - On Unix, [Root.Chmod], [Root.Chown], and [Root.Chtimes] are vulnerable to a race condition.
// If the target of the operation is changed from a regular file to a symlink
// while the operation is in progress, the operation may be performed on the link
// rather than the link target.
// - When GOOS=js, Root is vulnerable to TOCTOU (time-of-check-time-of-use)
// attacks in symlink validation, and cannot ensure that operations will not
// escape the root.
// - When GOOS=plan9 or GOOS=js, Root does not track directories across renames.
// On these platforms, a Root references a directory name, not a file descriptor.
// - WASI preview 1 (GOOS=wasip1) does not support [Root.Chmod].
type Root struct {
root *root
}
const (
// Maximum number of symbolic links we will follow when resolving a file in a root.
// 8 is __POSIX_SYMLOOP_MAX (the minimum allowed value for SYMLOOP_MAX),
// and a common limit.
rootMaxSymlinks = 8
)
// OpenRoot opens the named directory.
// It follows symbolic links in the directory name.
// If there is an error, it will be of type [*PathError].
func OpenRoot(name string) (*Root, error) {
testlog.Open(name)
return openRootNolog(name)
}
// Name returns the name of the directory presented to OpenRoot.
//
// It is safe to call Name after [Close].
func (r *Root) Name() string {
return r.root.Name()
}
// Close closes the Root.
// After Close is called, methods on Root return errors.
func (r *Root) Close() error {
return r.root.Close()
}
// Open opens the named file in the root for reading.
// See [Open] for more details.
func (r *Root) Open(name string) (*File, error) {
return r.OpenFile(name, O_RDONLY, 0)
}
// Create creates or truncates the named file in the root.
// See [Create] for more details.
func (r *Root) Create(name string) (*File, error) {
return r.OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
// OpenFile opens the named file in the root.
// See [OpenFile] for more details.
//
// If perm contains bits other than the nine least-significant bits (0o777),
// OpenFile returns an error.
func (r *Root) OpenFile(name string, flag int, perm FileMode) (*File, error) {
if perm&0o777 != perm {
return nil, &PathError{Op: "openat", Path: name, Err: errors.New("unsupported file mode")}
}
r.logOpen(name)
rf, err := rootOpenFileNolog(r, name, flag, perm)
if err != nil {
return nil, err
}
rf.appendMode = flag&O_APPEND != 0
return rf, nil
}
// OpenRoot opens the named directory in the root.
// If there is an error, it will be of type [*PathError].
func (r *Root) OpenRoot(name string) (*Root, error) {
r.logOpen(name)
return openRootInRoot(r, name)
}
// Chmod changes the mode of the named file in the root to mode.
// See [Chmod] for more details.
func (r *Root) Chmod(name string, mode FileMode) error {
return rootChmod(r, name, mode)
}
// Mkdir creates a new directory in the root
// with the specified name and permission bits (before umask).
// See [Mkdir] for more details.
//
// If perm contains bits other than the nine least-significant bits (0o777),
// OpenFile returns an error.
func (r *Root) Mkdir(name string, perm FileMode) error {
if perm&0o777 != perm {
return &PathError{Op: "mkdirat", Path: name, Err: errors.New("unsupported file mode")}
}
return rootMkdir(r, name, perm)
}
// Chown changes the numeric uid and gid of the named file in the root.
// See [Chown] for more details.
func (r *Root) Chown(name string, uid, gid int) error {
return rootChown(r, name, uid, gid)
}
// Lchown changes the numeric uid and gid of the named file in the root.
// See [Lchown] for more details.
func (r *Root) Lchown(name string, uid, gid int) error {
return rootLchown(r, name, uid, gid)
}
// Chtimes changes the access and modification times of the named file in the root.
// See [Chtimes] for more details.
func (r *Root) Chtimes(name string, atime time.Time, mtime time.Time) error {
return rootChtimes(r, name, atime, mtime)
}
// Remove removes the named file or (empty) directory in the root.
// See [Remove] for more details.
func (r *Root) Remove(name string) error {
return rootRemove(r, name)
}
// Stat returns a [FileInfo] describing the named file in the root.
// See [Stat] for more details.
func (r *Root) Stat(name string) (FileInfo, error) {
r.logStat(name)
return rootStat(r, name, false)
}
// Lstat returns a [FileInfo] describing the named file in the root.
// If the file is a symbolic link, the returned FileInfo
// describes the symbolic link.
// See [Lstat] for more details.
func (r *Root) Lstat(name string) (FileInfo, error) {
r.logStat(name)
return rootStat(r, name, true)
}
// Readlink returns the destination of the named symbolic link in the root.
// See [Readlink] for more details.
func (r *Root) Readlink(name string) (string, error) {
return rootReadlink(r, name)
}
// Rename renames (moves) oldname to newname.
// Both paths are relative to the root.
// See [Rename] for more details.
func (r *Root) Rename(oldname, newname string) error {
return rootRename(r, oldname, newname)
}
// Link creates newname as a hard link to the oldname file.
// Both paths are relative to the root.
// See [Link] for more details.
//
// If oldname is a symbolic link, Link creates new link to oldname and not its target.
// This behavior may differ from that of [Link] on some platforms.
//
// When GOOS=js, Link returns an error if oldname is a symbolic link.
func (r *Root) Link(oldname, newname string) error {
return rootLink(r, oldname, newname)
}
// Symlink creates newname as a symbolic link to oldname.
// See [Symlink] for more details.
//
// Symlink does not validate oldname,
// which may reference a location outside the root.
//
// On Windows, a directory link is created if oldname references
// a directory within the root. Otherwise a file link is created.
func (r *Root) Symlink(oldname, newname string) error {
return rootSymlink(r, oldname, newname)
}
func (r *Root) logOpen(name string) {
if log := testlog.Logger(); log != nil {
// This won't be right if r's name has changed since it was opened,
// but it's the best we can do.
log.Open(joinPath(r.Name(), name))
}
}
func (r *Root) logStat(name string) {
if log := testlog.Logger(); log != nil {
// This won't be right if r's name has changed since it was opened,
// but it's the best we can do.
log.Stat(joinPath(r.Name(), name))
}
}
// splitPathInRoot splits a path into components
// and joins it with the given prefix and suffix.
//
// The path is relative to a Root, and must not be
// absolute, volume-relative, or "".
//
// "." components are removed, except in the last component.
//
// Path separators following the last component are preserved.
func splitPathInRoot(s string, prefix, suffix []string) (_ []string, err error) {
if len(s) == 0 {
return nil, errors.New("empty path")
}
if IsPathSeparator(s[0]) {
return nil, errPathEscapes
}
if runtime.GOOS == "windows" {
// Windows cleans paths before opening them.
s, err = rootCleanPath(s, prefix, suffix)
if err != nil {
return nil, err
}
prefix = nil
suffix = nil
}
parts := slices.Clone(prefix)
i, j := 0, 1
for {
if j < len(s) && !IsPathSeparator(s[j]) {
// Keep looking for the end of this component.
j++
continue
}
parts = append(parts, s[i:j])
// Advance to the next component, or end of the path.
for j < len(s) && IsPathSeparator(s[j]) {
j++
}
if j == len(s) {
// If this is the last path component,
// preserve any trailing path separators.
parts[len(parts)-1] = s[i:]
break
}
if parts[len(parts)-1] == "." {
// Remove "." components, except at the end.
parts = parts[:len(parts)-1]
}
i = j
}
if len(suffix) > 0 && len(parts) > 0 && parts[len(parts)-1] == "." {
// Remove a trailing "." component if we're joining to a suffix.
parts = parts[:len(parts)-1]
}
parts = append(parts, suffix...)
return parts, nil
}
// FS returns a file system (an fs.FS) for the tree of files in the root.
//
// The result implements [io/fs.StatFS], [io/fs.ReadFileFS] and
// [io/fs.ReadDirFS].
func (r *Root) FS() fs.FS {
return (*rootFS)(r)
}
type rootFS Root
func (rfs *rootFS) Open(name string) (fs.File, error) {
r := (*Root)(rfs)
if !isValidRootFSPath(name) {
return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid}
}
f, err := r.Open(name)
if err != nil {
return nil, err
}
return f, nil
}
func (rfs *rootFS) ReadDir(name string) ([]DirEntry, error) {
r := (*Root)(rfs)
if !isValidRootFSPath(name) {
return nil, &PathError{Op: "readdir", Path: name, Err: ErrInvalid}
}
// This isn't efficient: We just open a regular file and ReadDir it.
// Ideally, we would skip creating a *File entirely and operate directly
// on the file descriptor, but that will require some extensive reworking
// of directory reading in general.
//
// This suffices for the moment.
f, err := r.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
dirs, err := f.ReadDir(-1)
slices.SortFunc(dirs, func(a, b DirEntry) int {
return bytealg.CompareString(a.Name(), b.Name())
})
return dirs, err
}
func (rfs *rootFS) ReadFile(name string) ([]byte, error) {
r := (*Root)(rfs)
if !isValidRootFSPath(name) {
return nil, &PathError{Op: "readfile", Path: name, Err: ErrInvalid}
}
f, err := r.Open(name)
if err != nil {
return nil, err
}
defer f.Close()
return readFileContents(statOrZero(f), f.Read)
}
func (rfs *rootFS) Stat(name string) (FileInfo, error) {
r := (*Root)(rfs)
if !isValidRootFSPath(name) {
return nil, &PathError{Op: "stat", Path: name, Err: ErrInvalid}
}
return r.Stat(name)
}
// isValidRootFSPath reports whether name is a valid filename to pass a Root.FS method.
func isValidRootFSPath(name string) bool {
if !fs.ValidPath(name) {
return false
}
if runtime.GOOS == "windows" {
// fs.FS paths are /-separated.
// On Windows, reject the path if it contains any \ separators.
// Other forms of invalid path (for example, "NUL") are handled by
// Root's usual file lookup mechanisms.
if stringslite.IndexByte(name, '\\') >= 0 {
return false
}
}
return true
}