go/src/os/root.go
Damien Neil 49d24d469e os: add Root.Remove
For #67002

Change-Id: Ibbf44c0bf62f53695a7399ba0dae5b84d5efd374
Reviewed-on: https://go-review.googlesource.com/c/go/+/627076
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2024-11-20 23:21:14 +00:00

192 lines
5.7 KiB
Go

// 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/testlog"
"runtime"
)
// 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.
// - 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.
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.
// 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)
}
// 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)
}
// 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)
}
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))
}
}
// 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 := append([]string{}, 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
}
parts = append(parts, suffix...)
return parts, nil
}