// 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.type root struct { name string// 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. mu sync.Mutex fd sysfdType refs int// number of active operations closed bool// set when closed cleanup runtime.Cleanup// cleanup closes the file when no longer referenced}func ( *root) () error { .mu.Lock()defer .mu.Unlock()if !.closed && .refs == 0 {syscall.Close(.fd) } .closed = true// There is no need for a cleanup at this point. Root must be alive at the point // where cleanup.stop is called. .cleanup.Stop()returnnil}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 rootChmod( *Root, string, FileMode) error { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, chmodat(, , ) })if != nil {return &PathError{Op: "chmodat", Path: , Err: } }returnnil}func rootChown( *Root, string, , int) error { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, chownat(, , , ) })if != nil {return &PathError{Op: "chownat", Path: , Err: } }returnnil}func rootLchown( *Root, string, , int) error { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, lchownat(, , , ) })if != nil {return &PathError{Op: "lchownat", Path: , Err: } }return}func rootChtimes( *Root, string, time.Time, time.Time) error { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, chtimesat(, , , ) })if != nil {return &PathError{Op: "chtimesat", Path: , Err: } }return}func rootMkdir( *Root, string, FileMode) error { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, mkdirat(, , ) })if != nil {return &PathError{Op: "mkdirat", Path: , Err: } }returnnil}func rootMkdirAll( *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: } }if := mkdirat(, , ); != nil {return0, &PathError{Op: "mkdirat", Err: } } }panic("unreachable") }// openLastComponentFunc opens the last path component. := func( sysfdType, string) (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(, , , )if != nil {if , := .(*PathError); ! { = &PathError{Op: "mkdirat", Path: , Err: } } }return}func rootReadlink( *Root, string) (string, error) { , := doInRoot(, , nil, func( sysfdType, string) (string, error) {returnreadlinkat(, ) })if != nil {return"", &PathError{Op: "readlinkat", Path: , Err: } }return , nil}func rootRemove( *Root, string) error { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, removeat(, ) })if != nil {return &PathError{Op: "removeat", Path: , Err: } }returnnil}func rootRemoveAll( *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(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, removeAllFrom(, ) })ifIsNotExist() {returnnil }if != nil {return &PathError{Op: "RemoveAll", Path: , Err: underlyingError()} }return}func rootRename( *Root, , string) error { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, renameat(, , , ) })returnstruct{}{}, })if != nil {return &LinkError{"renameat", , , } }return}func rootLink( *Root, , string) error { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) { , := doInRoot(, , nil, func( sysfdType, string) (struct{}, error) {returnstruct{}{}, linkat(, , , ) })returnstruct{}{}, })if != nil {return &LinkError{"linkat", , , } }return}// 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, and the name of the last path element.//// For example, given the path a/b/c it calls f with the FD for a/b and the name "c".//// 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, give 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 doInRoot[ any]( *Root, string, func( sysfdType, string) (sysfdType, error), func( sysfdType, string) (, 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 {// 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 // which we rejoin to the final part at this time. , = (, []+)if == nil {return } } else {varsysfdType , = (, [])if == nil {if != {syscall.Close() } = } }switch e := .(type) {casenil:caseerrSymlink: ++if > rootMaxSymlinks {return , syscall.ELOOP } , , := splitPathInRoot(string(), [:], [+1:])if != nil {return , }if == len()-1 {// suffixSep contains any trailing path separator characters // in the link target. // If we are replacing the remainder of the path, retain these. // If we're replacing some intermediate component of the path, // ignore them, since intermediate components must always be // directories. = }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 , } ++ }}// errSymlink reports that a file being operated on is actually a symlink,// and the target of that symlink.type errSymlink stringfunc (errSymlink) () string { panic("errSymlink is not user-visible") }
The pages are generated with Goldsv0.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.