// 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 osimport ()// 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 {returnnil, }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].typeRootstruct { 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()returnopenRootNolog()}// 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 != {returnnil, &PathError{Op: "openat", Path: , Err: errors.New("unsupported file mode")} } .logOpen() , := rootOpenFileNolog(, , , )if != nil {returnnil, } .appendMode = &O_APPEND != 0return , 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()returnopenRootInRoot(, )}// Chmod changes the mode of the named file in the root to mode.// See [Chmod] for more details.func ( *Root) ( string, FileMode) error {returnrootChmod(, , )}// 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")} }returnrootMkdir(, , )}// 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")} }returnrootMkdirAll(, , )}// Chown changes the numeric uid and gid of the named file in the root.// See [Chown] for more details.func ( *Root) ( string, , int) error {returnrootChown(, , , )}// Lchown changes the numeric uid and gid of the named file in the root.// See [Lchown] for more details.func ( *Root) ( string, , int) error {returnrootLchown(, , , )}// 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 {returnrootChtimes(, , , )}// Remove removes the named file or (empty) directory in the root.// See [Remove] for more details.func ( *Root) ( string) error {returnrootRemove(, )}// RemoveAll removes the named file or directory and any children that it contains.// See [RemoveAll] for more details.func ( *Root) ( string) error {returnrootRemoveAll(, )}// Stat returns a [FileInfo] describing the named file in the root.// See [Stat] for more details.func ( *Root) ( string) (FileInfo, error) { .logStat()returnrootStat(, , 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()returnrootStat(, , true)}// Readlink returns the destination of the named symbolic link in the root.// See [Readlink] for more details.func ( *Root) ( string) (string, error) {returnrootReadlink(, )}// Rename renames (moves) oldname to newname.// Both paths are relative to the root.// See [Rename] for more details.func ( *Root) (, string) error {returnrootRename(, , )}// 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 {returnrootLink(, , )}// 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 {returnrootSymlink(, , )}// 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 {returnnil, }defer .Close()returnreadFileContents(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) {iflen() == 0 {returnnil, "", errors.New("empty path") }ifIsPathSeparator([0]) {returnnil, "", errPathEscapes }ifruntime.GOOS == "windows" {// Windows cleans paths before opening them. , = rootCleanPath(, , )if != nil {returnnil, "", } = nil = nil } := slices.Clone() , := 0, 1for {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] } = }iflen() > 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 Rootfunc ( *rootFS) ( string) (fs.File, error) { := (*Root)()if !isValidRootFSPath() {returnnil, &PathError{Op: "open", Path: , Err: ErrInvalid} } , := .Open()if != nil {returnnil, }return , nil}func ( *rootFS) ( string) ([]DirEntry, error) { := (*Root)()if !isValidRootFSPath() {returnnil, &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 {returnnil, }defer .Close() , := .ReadDir(-1)slices.SortFunc(, func(, DirEntry) int {returnbytealg.CompareString(.Name(), .Name()) })return , }func ( *rootFS) ( string) ([]byte, error) { := (*Root)()if !isValidRootFSPath() {returnnil, &PathError{Op: "readfile", Path: , Err: ErrInvalid} } , := .Open()if != nil {returnnil, }defer .Close()returnreadFileContents(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() {returnnil, &PathError{Op: "stat", Path: , Err: ErrInvalid} }return .Stat()}func ( *rootFS) ( string) (FileInfo, error) { := (*Root)()if !isValidRootFSPath() {returnnil, &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() {returnfalse }ifruntime.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.ifstringslite.IndexByte(, '\\') >= 0 {returnfalse } }returntrue}
The pages are generated with Goldsv0.7.9-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.