Source File
root.go
Belonging Package
os
// 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 (
)
// 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 (, string) (*File, error) {
, := OpenRoot()
if != nil {
return nil,
}
defer .Close()
return .Open()
}
// 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 ( string) (*Root, error) {
testlog.Open()
return openRootNolog()
}
// Name returns the name of the directory presented to OpenRoot.
//
// It is safe to call Name after [Close].
func ( *Root) () string {
return .root.Name()
}
// Close closes the Root.
// After Close is called, methods on Root return errors.
func ( *Root) () error {
return .root.Close()
}
// Open opens the named file in the root for reading.
// See [Open] for more details.
func ( *Root) ( string) (*File, error) {
return .OpenFile(, O_RDONLY, 0)
}
// Create creates or truncates the named file in the root.
// See [Create] for more details.
func ( *Root) ( string) (*File, error) {
return .OpenFile(, 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 ( *Root) ( string, int, FileMode) (*File, error) {
if &0o777 != {
return nil, &PathError{Op: "openat", Path: , Err: errors.New("unsupported file mode")}
}
.logOpen()
, := rootOpenFileNolog(, , , )
if != nil {
return nil,
}
.appendMode = &O_APPEND != 0
return , nil
}
// OpenRoot opens the named directory in the root.
// If there is an error, it will be of type [*PathError].
func ( *Root) ( string) (*Root, error) {
.logOpen()
return openRootInRoot(, )
}
// Chmod changes the mode of the named file in the root to mode.
// See [Chmod] for more details.
func ( *Root) ( string, FileMode) error {
return rootChmod(, , )
}
// 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),
// Mkdir returns an error.
func ( *Root) ( string, FileMode) error {
if &0o777 != {
return &PathError{Op: "mkdirat", Path: , Err: errors.New("unsupported file mode")}
}
return rootMkdir(, , )
}
// MkdirAll creates a new directory in the root, along with any necessary parents.
// See [MkdirAll] for more details.
//
// If perm contains bits other than the nine least-significant bits (0o777),
// MkdirAll returns an error.
func ( *Root) ( string, FileMode) error {
if &0o777 != {
return &PathError{Op: "mkdirat", Path: , Err: errors.New("unsupported file mode")}
}
return rootMkdirAll(, , )
}
// Chown changes the numeric uid and gid of the named file in the root.
// See [Chown] for more details.
func ( *Root) ( string, , int) error {
return rootChown(, , , )
}
// Lchown changes the numeric uid and gid of the named file in the root.
// See [Lchown] for more details.
func ( *Root) ( string, , int) error {
return rootLchown(, , , )
}
// Chtimes changes the access and modification times of the named file in the root.
// See [Chtimes] for more details.
func ( *Root) ( string, time.Time, time.Time) error {
return rootChtimes(, , , )
}
// Remove removes the named file or (empty) directory in the root.
// See [Remove] for more details.
func ( *Root) ( string) error {
return rootRemove(, )
}
// RemoveAll removes the named file or directory and any children that it contains.
// See [RemoveAll] for more details.
func ( *Root) ( string) error {
return rootRemoveAll(, )
}
// Stat returns a [FileInfo] describing the named file in the root.
// See [Stat] for more details.
func ( *Root) ( string) (FileInfo, error) {
.logStat()
return rootStat(, , 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 ( *Root) ( string) (FileInfo, error) {
.logStat()
return rootStat(, , true)
}
// Readlink returns the destination of the named symbolic link in the root.
// See [Readlink] for more details.
func ( *Root) ( string) (string, error) {
return rootReadlink(, )
}
// Rename renames (moves) oldname to newname.
// Both paths are relative to the root.
// See [Rename] for more details.
func ( *Root) (, string) error {
return rootRename(, , )
}
// 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 ( *Root) (, string) error {
return rootLink(, , )
}
// 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 ( *Root) (, string) error {
return rootSymlink(, , )
}
// ReadFile reads the named file in the root and returns its contents.
// See [ReadFile] for more details.
func ( *Root) ( string) ([]byte, error) {
, := .Open()
if != nil {
return nil,
}
defer .Close()
return readFileContents(statOrZero(), .Read)
}
// WriteFile writes data to the named file in the root, creating it if necessary.
// See [WriteFile] for more details.
func ( *Root) ( string, []byte, FileMode) error {
, := .OpenFile(, O_WRONLY|O_CREATE|O_TRUNC, )
if != nil {
return
}
_, = .Write()
if := .Close(); == nil {
=
}
return
}
func ( *Root) ( string) {
if := testlog.Logger(); != nil {
// This won't be right if r's name has changed since it was opened,
// but it's the best we can do.
.Open(joinPath(.Name(), ))
}
}
func ( *Root) ( string) {
if := testlog.Logger(); != nil {
// This won't be right if r's name has changed since it was opened,
// but it's the best we can do.
.Stat(joinPath(.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 returned in suffixSep.
func splitPathInRoot( string, , []string) ( []string, string, error) {
if len() == 0 {
return nil, "", errors.New("empty path")
}
if IsPathSeparator([0]) {
return nil, "", errPathEscapes
}
if runtime.GOOS == "windows" {
// Windows cleans paths before opening them.
, = rootCleanPath(, , )
if != nil {
return nil, "",
}
= nil
= nil
}
:= slices.Clone()
, := 0, 1
for {
if < len() && !IsPathSeparator([]) {
// Keep looking for the end of this component.
++
continue
}
= append(, [:])
// Advance to the next component, or end of the path.
:=
for < len() && IsPathSeparator([]) {
++
}
if == len() {
// If this is the last path component,
// preserve any trailing path separators.
= [:]
break
}
if [len()-1] == "." {
// Remove "." components, except at the end.
= [:len()-1]
}
=
}
if len() > 0 && len() > 0 && [len()-1] == "." {
// Remove a trailing "." component if we're joining to a suffix.
= [:len()-1]
}
= append(, ...)
return , , 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],
// [io/fs.ReadDirFS], and [io/fs.ReadLinkFS].
func ( *Root) () fs.FS {
return (*rootFS)()
}
type rootFS Root
func ( *rootFS) ( string) (fs.File, error) {
:= (*Root)()
if !isValidRootFSPath() {
return nil, &PathError{Op: "open", Path: , Err: ErrInvalid}
}
, := .Open()
if != nil {
return nil,
}
return , nil
}
func ( *rootFS) ( string) ([]DirEntry, error) {
:= (*Root)()
if !isValidRootFSPath() {
return nil, &PathError{Op: "readdir", Path: , 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.
, := .Open()
if != nil {
return nil,
}
defer .Close()
, := .ReadDir(-1)
slices.SortFunc(, func(, DirEntry) int {
return bytealg.CompareString(.Name(), .Name())
})
return ,
}
func ( *rootFS) ( string) ([]byte, error) {
:= (*Root)()
if !isValidRootFSPath() {
return nil, &PathError{Op: "readfile", Path: , Err: ErrInvalid}
}
, := .Open()
if != nil {
return nil,
}
defer .Close()
return readFileContents(statOrZero(), .Read)
}
func ( *rootFS) ( string) (string, error) {
:= (*Root)()
if !isValidRootFSPath() {
return "", &PathError{Op: "readlink", Path: , Err: ErrInvalid}
}
return .Readlink()
}
func ( *rootFS) ( string) (FileInfo, error) {
:= (*Root)()
if !isValidRootFSPath() {
return nil, &PathError{Op: "stat", Path: , Err: ErrInvalid}
}
return .Stat()
}
func ( *rootFS) ( string) (FileInfo, error) {
:= (*Root)()
if !isValidRootFSPath() {
return nil, &PathError{Op: "lstat", Path: , Err: ErrInvalid}
}
return .Lstat()
}
// isValidRootFSPath reports whether name is a valid filename to pass a Root.FS method.
func isValidRootFSPath( string) bool {
if !fs.ValidPath() {
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(, '\\') >= 0 {
return false
}
}
return true
}
![]() |
The pages are generated with Golds v0.7.7-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |