Source file src/os/root_noopenat.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build (js && wasm) || plan9
     6  
     7  package os
     8  
     9  import (
    10  	"errors"
    11  	"internal/filepathlite"
    12  	"internal/stringslite"
    13  	"sync/atomic"
    14  	"syscall"
    15  	"time"
    16  )
    17  
    18  // root implementation for platforms with no openat.
    19  // Currently plan9 and js.
    20  type root struct {
    21  	name   string
    22  	closed atomic.Bool
    23  }
    24  
    25  // openRootNolog is OpenRoot.
    26  func openRootNolog(name string) (*Root, error) {
    27  	r, err := newRoot(name)
    28  	if err != nil {
    29  		return nil, &PathError{Op: "open", Path: name, Err: err}
    30  	}
    31  	return r, nil
    32  }
    33  
    34  // openRootInRoot is Root.OpenRoot.
    35  func openRootInRoot(r *Root, name string) (*Root, error) {
    36  	if err := checkPathEscapes(r, name); err != nil {
    37  		return nil, &PathError{Op: "openat", Path: name, Err: err}
    38  	}
    39  	r, err := newRoot(joinPath(r.root.name, name))
    40  	if err != nil {
    41  		return nil, &PathError{Op: "openat", Path: name, Err: err}
    42  	}
    43  	return r, nil
    44  }
    45  
    46  // newRoot returns a new Root.
    47  // If fd is not a directory, it closes it and returns an error.
    48  func newRoot(name string) (*Root, error) {
    49  	fi, err := Stat(name)
    50  	if err != nil {
    51  		return nil, err.(*PathError).Err
    52  	}
    53  	if !fi.IsDir() {
    54  		return nil, errors.New("not a directory")
    55  	}
    56  	return &Root{&root{name: name}}, nil
    57  }
    58  
    59  func (r *root) Close() error {
    60  	// For consistency with platforms where Root.Close closes a handle,
    61  	// mark the Root as closed and return errors from future calls.
    62  	r.closed.Store(true)
    63  	return nil
    64  }
    65  
    66  func (r *root) Name() string {
    67  	return r.name
    68  }
    69  
    70  // rootOpenFileNolog is Root.OpenFile.
    71  func rootOpenFileNolog(r *Root, name string, flag int, perm FileMode) (*File, error) {
    72  	if err := checkPathEscapes(r, name); err != nil {
    73  		return nil, &PathError{Op: "openat", Path: name, Err: err}
    74  	}
    75  	f, err := openFileNolog(joinPath(r.root.name, name), flag, perm)
    76  	if err != nil {
    77  		return nil, &PathError{Op: "openat", Path: name, Err: underlyingError(err)}
    78  	}
    79  	return f, nil
    80  }
    81  
    82  func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
    83  	var fi FileInfo
    84  	var err error
    85  	if lstat {
    86  		err = checkPathEscapesLstat(r, name)
    87  		if err == nil {
    88  			fi, err = Lstat(joinPath(r.root.name, name))
    89  		}
    90  	} else {
    91  		err = checkPathEscapes(r, name)
    92  		if err == nil {
    93  			fi, err = Stat(joinPath(r.root.name, name))
    94  		}
    95  	}
    96  	if err != nil {
    97  		return nil, &PathError{Op: "statat", Path: name, Err: underlyingError(err)}
    98  	}
    99  	return fi, nil
   100  }
   101  
   102  func rootChmod(r *Root, name string, mode FileMode) error {
   103  	if err := checkPathEscapes(r, name); err != nil {
   104  		return &PathError{Op: "chmodat", Path: name, Err: err}
   105  	}
   106  	if err := Chmod(joinPath(r.root.name, name), mode); err != nil {
   107  		return &PathError{Op: "chmodat", Path: name, Err: underlyingError(err)}
   108  	}
   109  	return nil
   110  }
   111  
   112  func rootChown(r *Root, name string, uid, gid int) error {
   113  	if err := checkPathEscapes(r, name); err != nil {
   114  		return &PathError{Op: "chownat", Path: name, Err: err}
   115  	}
   116  	if err := Chown(joinPath(r.root.name, name), uid, gid); err != nil {
   117  		return &PathError{Op: "chownat", Path: name, Err: underlyingError(err)}
   118  	}
   119  	return nil
   120  }
   121  
   122  func rootLchown(r *Root, name string, uid, gid int) error {
   123  	if err := checkPathEscapesLstat(r, name); err != nil {
   124  		return &PathError{Op: "lchownat", Path: name, Err: err}
   125  	}
   126  	if err := Lchown(joinPath(r.root.name, name), uid, gid); err != nil {
   127  		return &PathError{Op: "lchownat", Path: name, Err: underlyingError(err)}
   128  	}
   129  	return nil
   130  }
   131  
   132  func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
   133  	if err := checkPathEscapes(r, name); err != nil {
   134  		return &PathError{Op: "chtimesat", Path: name, Err: err}
   135  	}
   136  	if err := Chtimes(joinPath(r.root.name, name), atime, mtime); err != nil {
   137  		return &PathError{Op: "chtimesat", Path: name, Err: underlyingError(err)}
   138  	}
   139  	return nil
   140  }
   141  
   142  func rootMkdir(r *Root, name string, perm FileMode) error {
   143  	if err := checkPathEscapes(r, name); err != nil {
   144  		return &PathError{Op: "mkdirat", Path: name, Err: err}
   145  	}
   146  	if err := Mkdir(joinPath(r.root.name, name), perm); err != nil {
   147  		return &PathError{Op: "mkdirat", Path: name, Err: underlyingError(err)}
   148  	}
   149  	return nil
   150  }
   151  
   152  func rootMkdirAll(r *Root, name string, perm FileMode) error {
   153  	// We only check for errPathEscapes here.
   154  	// For errors such as ENOTDIR (a non-directory file appeared somewhere along the path),
   155  	// we let MkdirAll generate the error.
   156  	// MkdirAll will return a PathError referencing the exact location of the error,
   157  	// and we want to preserve that property.
   158  	if err := checkPathEscapes(r, name); err == errPathEscapes {
   159  		return &PathError{Op: "mkdirat", Path: name, Err: err}
   160  	}
   161  	prefix := r.root.name + string(PathSeparator)
   162  	if err := MkdirAll(prefix+name, perm); err != nil {
   163  		if pe, ok := err.(*PathError); ok {
   164  			pe.Op = "mkdirat"
   165  			pe.Path = stringslite.TrimPrefix(pe.Path, prefix)
   166  			return pe
   167  		}
   168  		return &PathError{Op: "mkdirat", Path: name, Err: underlyingError(err)}
   169  	}
   170  	return nil
   171  }
   172  
   173  func rootRemove(r *Root, name string) error {
   174  	if err := checkPathEscapesLstat(r, name); err != nil {
   175  		return &PathError{Op: "removeat", Path: name, Err: err}
   176  	}
   177  	if endsWithDot(name) {
   178  		// We don't want to permit removing the root itself, so check for that.
   179  		if filepathlite.Clean(name) == "." {
   180  			return &PathError{Op: "removeat", Path: name, Err: errPathEscapes}
   181  		}
   182  	}
   183  	if err := Remove(joinPath(r.root.name, name)); err != nil {
   184  		return &PathError{Op: "removeat", Path: name, Err: underlyingError(err)}
   185  	}
   186  	return nil
   187  }
   188  
   189  func rootRemoveAll(r *Root, name string) error {
   190  	if endsWithDot(name) {
   191  		// Consistency with os.RemoveAll: Return EINVAL when trying to remove .
   192  		return &PathError{Op: "RemoveAll", Path: name, Err: syscall.EINVAL}
   193  	}
   194  	if err := checkPathEscapesLstat(r, name); err != nil {
   195  		if err == syscall.ENOTDIR {
   196  			// Some intermediate path component is not a directory.
   197  			// RemoveAll treats this as success (since the target doesn't exist).
   198  			return nil
   199  		}
   200  		return &PathError{Op: "RemoveAll", Path: name, Err: err}
   201  	}
   202  	if err := RemoveAll(joinPath(r.root.name, name)); err != nil {
   203  		return &PathError{Op: "RemoveAll", Path: name, Err: underlyingError(err)}
   204  	}
   205  	return nil
   206  }
   207  
   208  func rootReadlink(r *Root, name string) (string, error) {
   209  	if err := checkPathEscapesLstat(r, name); err != nil {
   210  		return "", &PathError{Op: "readlinkat", Path: name, Err: err}
   211  	}
   212  	name, err := Readlink(joinPath(r.root.name, name))
   213  	if err != nil {
   214  		return "", &PathError{Op: "readlinkat", Path: name, Err: underlyingError(err)}
   215  	}
   216  	return name, nil
   217  }
   218  
   219  func rootRename(r *Root, oldname, newname string) error {
   220  	if err := checkPathEscapesLstat(r, oldname); err != nil {
   221  		return &PathError{Op: "renameat", Path: oldname, Err: err}
   222  	}
   223  	if err := checkPathEscapesLstat(r, newname); err != nil {
   224  		return &PathError{Op: "renameat", Path: newname, Err: err}
   225  	}
   226  	err := Rename(joinPath(r.root.name, oldname), joinPath(r.root.name, newname))
   227  	if err != nil {
   228  		return &LinkError{"renameat", oldname, newname, underlyingError(err)}
   229  	}
   230  	return nil
   231  }
   232  
   233  func rootLink(r *Root, oldname, newname string) error {
   234  	if err := checkPathEscapesLstat(r, oldname); err != nil {
   235  		return &PathError{Op: "linkat", Path: oldname, Err: err}
   236  	}
   237  	fullOldName := joinPath(r.root.name, oldname)
   238  	if fs, err := Lstat(fullOldName); err == nil && fs.Mode()&ModeSymlink != 0 {
   239  		return &PathError{Op: "linkat", Path: oldname, Err: errors.New("cannot create a hard link to a symlink")}
   240  	}
   241  	if err := checkPathEscapesLstat(r, newname); err != nil {
   242  		return &PathError{Op: "linkat", Path: newname, Err: err}
   243  	}
   244  	err := Link(fullOldName, joinPath(r.root.name, newname))
   245  	if err != nil {
   246  		return &LinkError{"linkat", oldname, newname, underlyingError(err)}
   247  	}
   248  	return nil
   249  }
   250  
   251  func rootSymlink(r *Root, oldname, newname string) error {
   252  	if err := checkPathEscapesLstat(r, newname); err != nil {
   253  		return &PathError{Op: "symlinkat", Path: newname, Err: err}
   254  	}
   255  	err := Symlink(oldname, joinPath(r.root.name, newname))
   256  	if err != nil {
   257  		return &LinkError{"symlinkat", oldname, newname, underlyingError(err)}
   258  	}
   259  	return nil
   260  }
   261  

View as plain text