// 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.//go:build unix || windows || wasip1package osimport ()// root implementation for platforms with a function to open a file// relative to a directory.typerootstruct {namestring// refs is incremented while an operation is using fd. // closed is set when Close is called. // fd is closed when closed is true and refs is 0.musync.MutexfdsysfdTyperefsint// number of active operationsclosedbool// set when closed}func ( *root) () error { .mu.Lock()defer .mu.Unlock()if !.closed && .refs == 0 {syscall.Close(.fd) } .closed = trueruntime.SetFinalizer(, nil) // no need for a finalizer any morereturnnil}func ( *root) () error { .mu.Lock()defer .mu.Unlock()if .closed {returnErrClosed } .refs++returnnil}func ( *root) () { .mu.Lock()defer .mu.Unlock()if .refs <= 0 {panic("bad Root refcount") } .refs--if .closed && .refs == 0 {syscall.Close(.fd) }}func ( *root) () string {return .name}func ( *Root, string, FileMode) error { , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (struct{}, error) {returnstruct{}{}, chmodat(, , ) })if != nil {return &PathError{Op: "chmodat", Path: , Err: } }returnnil}func ( *Root, string, , int) error { , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (struct{}, error) {returnstruct{}{}, chownat(, , , ) })if != nil {return &PathError{Op: "chownat", Path: , Err: } }returnnil}func ( *Root, string, , int) error { , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (struct{}, error) {returnstruct{}{}, lchownat(, , , ) })if != nil {return &PathError{Op: "lchownat", Path: , Err: } }returnnil}func ( *Root, string, time.Time, time.Time) error { , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (struct{}, error) {returnstruct{}{}, chtimesat(, , , ) })if != nil {return &PathError{Op: "chtimesat", Path: , Err: } }returnnil}func ( *Root, string, FileMode) error { := uint(doInRootCreatingDirectory)switchruntime.GOOS {case"linux", "windows":// These platforms do not follow "symlink" on "mkdir symlink/". // (POSIX.1-2024 4.16 says that the trailing slash should cause // resolution to follow the symlink, but we're trying to match // platform semantics, not implement POSIX.) = doInRootNoHandleTerminalSlash } , := doInRoot(, , , nil, func( sysfdType, string, bool) (struct{}, error) {returnstruct{}{}, mkdirat(, , ) })if != nil {return &PathError{Op: "mkdirat", Path: , Err: } }returnnil}func ( *Root, string, FileMode) error {// doInRoot opens each path element in turn. // // openDirFunc opens all but the last path component. // The usual default openDirFunc just opens directories with O_DIRECTORY. // We replace it here with one that creates missing directories along the way. := func( sysfdType, string) (sysfdType, error) {for := range2 { , := rootOpenDir(, )switch .(type) {casenil, errSymlink:return , }if > 0 || !IsNotExist() {return0, &PathError{Op: "openat", Err: } }// Try again on EEXIST, because the directory may have been created // by another process or thread between the rootOpenDir and mkdirat calls.if := mkdirat(, , ); != nil && != syscall.EEXIST {return0, &PathError{Op: "mkdirat", Err: } } }panic("unreachable") }// openLastComponentFunc opens the last path component. := func( sysfdType, string, bool) (struct{}, error) { := mkdirat(, , )if == syscall.EEXIST { , := modeAt(, )if == nil {if .IsDir() {// The target of MkdirAll is an existing directory. = nil } elseif &ModeSymlink != 0 {// The target of MkdirAll is a symlink. // For consistency with os.MkdirAll, // succeed if the link resolves to a directory. // We don't return errSymlink here, because we don't // want to create the link target if it doesn't exist. , := .Stat()if == nil && .Mode().IsDir() { = nil } } } }switch .(type) {casenil, errSymlink:returnstruct{}{}, }returnstruct{}{}, &PathError{Op: "mkdirat", Err: } } , := doInRoot(, , 0, , )if != nil {if , := .(*PathError); ! { = &PathError{Op: "mkdirat", Path: , Err: } } }return}func ( *Root, string) (string, error) { , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (string, error) {returnreadlinkat(, ) })if != nil {return"", &PathError{Op: "readlinkat", Path: , Err: } }return , nil}func ( *Root, string) error { , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (struct{}, error) {returnstruct{}{}, removeat(, ) })if != nil {return &PathError{Op: "removeat", Path: , Err: } }returnnil}func ( *Root, string) error {// Consistency with os.RemoveAll: Strip trailing /s from the name, // so RemoveAll("not_a_directory/") succeeds.forlen() > 0 && IsPathSeparator([len()-1]) { = [:len()-1] }ifendsWithDot() {// Consistency with os.RemoveAll: Return EINVAL when trying to remove .return &PathError{Op: "RemoveAll", Path: , Err: syscall.EINVAL} } , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (struct{}, error) {returnstruct{}{}, removeAllFrom(, ) })ifIsNotExist() {returnnil }if != nil {return &PathError{Op: "RemoveAll", Path: , Err: underlyingError()} }return}func ( *Root, , string) error { , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (struct{}, error) { := uint(doInRootCreatingDirectory)ifruntime.GOOS == "windows" { = doInRootNoHandleTerminalSlash } , := doInRoot(, , , nil, func( sysfdType, string, bool) (struct{}, error) {ifruntime.GOOS != "windows" && { , := modeAt(, )if != nil {returnstruct{}{}, }if .Type() != fs.ModeDir {returnstruct{}{}, syscall.ENOTDIR } }// Same checks as applied by rename (in file_unix.go): , := lstatat(, )if == nil && .IsDir() {if , := lstatat(, ); != nil {returnstruct{}{}, } elseif == || !SameFile(, ) {returnstruct{}{}, syscall.EEXIST } }returnstruct{}{}, renameat(, , , ) })returnstruct{}{}, })if != nil {return &LinkError{"renameat", , , } }return}func ( *Root, , string) error { , := doInRoot(, , 0, nil, func( sysfdType, string, bool) (struct{}, error) { := uint(0)ifruntime.GOOS == "windows" {// Windows doesn't pay attention to trailing slashes in the link target. = doInRootNoHandleTerminalSlash } , := doInRoot(, , , nil, func( sysfdType, string, bool) (struct{}, error) {returnstruct{}{}, linkat(, , , ) })returnstruct{}{}, })if != nil {return &LinkError{"linkat", , , } }return}// Flags for doInRoot.const (// doInRootNoHandleTerminalSlash prevents doInRoot from applying special handling // for paths which end in one or more slashes.doInRootNoHandleTerminalSlash = 1 << iota// doInRootCreatingDirectory indicates that the operation is creating a directory. // When a path ends in /, the last path component may name a file which does not exist.doInRootCreatingDirectory// doInRootAlwaysResolveTerminalSlash causes doInRoot to resolve symlinks in the last // path component when a path ends in /, even on Windows. For example, this causes // doInRoot to resolve "symlink/" as the link target of "symlink". // // POSIX path operations resolve symlinks in this case. // Most Windows operations do not. // This flag enforces the POSIX behavior.doInRootAlwaysResolveTerminalSlash)// doInRoot performs an operation on a path in a Root.//// It calls f with the FD or handle for the directory containing the last// path element, the name of the last path element (not including slashes),// and a boolean indicating whether the original path ended in one or more slashes.//// For example, given the path a/b/c it calls f with the FD for a/b and the name "c".//// It applies special handling for paths ending in a slash: When a path ends in a slash// (for example "a/b/"), doInRoot will check the final component ("b") before calling f.// If the final component is a symlink, doInRoot will resolve it.// If the final component is neither a symlink nor a directory, doInRoot will return ENOTDIR.// This behavior may be disabled by passing the doInRootNoHandleTerminalSlash flag.//// If openDirFunc is non-nil, it is called to open intermediate path elements.// For example, given the path a/b/c openDirFunc will be called to open a and a/b in turn.//// f or openDirFunc may return errSymlink to indicate that the path element is a symlink// which should be followed. Note that this can result in f being called multiple times// with different names. For example, given the path "link" which is a symlink to "target",// f is called with the path "link", returns errSymlink("target"), and is called again with// the path "target".//// If f or openDirFunc return a *PathError, doInRoot will set PathError.Path to the// full path which caused the error.func [ any]( *Root, string, uint, func( sysfdType, string) (sysfdType, error), func( sysfdType, string, bool) (, error)) ( , error) {if := .root.incref(); != nil {return , }defer .root.decref() , , := splitPathInRoot(, nil, nil)if != nil {return , }if == nil { = rootOpenDir } := .root.fd := deferfunc() {if != {syscall.Close() } }()// When resolving .. path components, we restart path resolution from the root. // (We can't openat(dir, "..") to move up to the parent directory, // because dir may have moved since we opened it.) // To limit how many opens a malicious path can cause us to perform, we set // a limit on the total number of path steps and the total number of restarts // caused by .. components. If *both* limits are exceeded, we halt the operation.const = 255const = 8 := 0 := 0 := 0 := 0:for { ++if > && > {return , syscall.ENAMETOOLONG }if [] == ".." {// Resolve one or more parent ("..") path components. // // Rewrite the original path, // removing the elements eliminated by ".." components, // and start over from the beginning. ++ := + 1for < len() && [] == ".." { ++ } := - if > {return , errPathEscapes } = slices.Delete(, -, )iflen() == 0 { = []string{"."} } = 0if != {syscall.Close() } = continue }if == len()-1 { = nilif && &doInRootNoHandleTerminalSlash == 0 {varFileInfo , = lstatat(, [])switch {caseIsNotExist() && &doInRootCreatingDirectory != 0:// The path ends in a slash, the last path component // does not exist, and we creating a directory. // This is fine. = nilcase != nil:returncase .Mode().Type() == fs.ModeDir:case .Mode().Type() == fs.ModeSymlink:ifruntime.GOOS != "windows" || &doInRootAlwaysResolveTerminalSlash != 0 { = checkSymlink(, [], syscall.ENOTDIR) } else {if !isDirectoryLink() { = syscall.ENOTDIR } }default: = syscall.ENOTDIRreturn } }// This is the last path element. // Call f to decide what to do with it. // If f returns errSymlink, this element is a symlink // which should be followed. // suffixSep contains any trailing separator characters.if == nil { , = (, [], )if == nil {return } } } else {varsysfdType , = (, [])if == nil {if != {syscall.Close() } = } }switch e := .(type) {casenil:caseerrSymlink: ++if > rootMaxSymlinks {return , syscall.ELOOP } := == len()-1 , , := splitPathInRoot(string(), [:], [+1:])if != nil {return , }if && {// If a link target in the final path component ends in a slash, // then the path now ends in a slash. = true }iflen() < || !slices.Equal([:], [:]) {// Some component in the path which we have already traversed // has changed. We need to restart parsing from the root. = 0if != {syscall.Close() } = } = continuecase *PathError:// This is strings.Join(parts[:i+1], PathSeparator). .Path = [0]for , := range [1 : +1] { .Path += string(PathSeparator) + }return , default:return , } ++ }}func ( sysfdType, string) (FileMode, error) { , := lstatat(, )if != nil {return0, }return .Mode(), nil}// errSymlink reports that a file being operated on is actually a symlink,// and the target of that symlink.typeerrSymlinkstringfunc (errSymlink) () string { panic("errSymlink is not user-visible") }
The pages are generated with Goldsv0.8.4. (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.