Source file src/os/root_unix.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 unix || wasip1
     6  
     7  package os
     8  
     9  import (
    10  	"errors"
    11  	"internal/syscall/unix"
    12  	"runtime"
    13  	"syscall"
    14  	"time"
    15  )
    16  
    17  // sysfdType is the native type of a file handle
    18  // (int on Unix, syscall.Handle on Windows),
    19  // permitting helper functions to be written portably.
    20  type sysfdType = int
    21  
    22  // openRootNolog is OpenRoot.
    23  func openRootNolog(name string) (*Root, error) {
    24  	var fd int
    25  	err := ignoringEINTR(func() error {
    26  		var err error
    27  		fd, _, err = open(name, syscall.O_CLOEXEC, 0)
    28  		return err
    29  	})
    30  	if err != nil {
    31  		return nil, &PathError{Op: "open", Path: name, Err: err}
    32  	}
    33  	return newRoot(fd, name)
    34  }
    35  
    36  // newRoot returns a new Root.
    37  // If fd is not a directory, it closes it and returns an error.
    38  func newRoot(fd int, name string) (*Root, error) {
    39  	var fs fileStat
    40  	err := ignoringEINTR(func() error {
    41  		return syscall.Fstat(fd, &fs.sys)
    42  	})
    43  	fillFileStatFromSys(&fs, name)
    44  	if err == nil && !fs.IsDir() {
    45  		syscall.Close(fd)
    46  		return nil, &PathError{Op: "open", Path: name, Err: errors.New("not a directory")}
    47  	}
    48  
    49  	// There's a race here with fork/exec, which we are
    50  	// content to live with. See ../syscall/exec_unix.go.
    51  	if !supportsCloseOnExec {
    52  		syscall.CloseOnExec(fd)
    53  	}
    54  
    55  	r := &Root{&root{
    56  		fd:   fd,
    57  		name: name,
    58  	}}
    59  	r.root.cleanup = runtime.AddCleanup(r, func(f *root) { f.Close() }, r.root)
    60  	return r, nil
    61  }
    62  
    63  // openRootInRoot is Root.OpenRoot.
    64  func openRootInRoot(r *Root, name string) (*Root, error) {
    65  	fd, err := doInRoot(r, name, nil, func(parent int, name string) (fd int, err error) {
    66  		ignoringEINTR(func() error {
    67  			fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
    68  			if isNoFollowErr(err) {
    69  				err = checkSymlink(parent, name, err)
    70  			}
    71  			return err
    72  		})
    73  		return fd, err
    74  	})
    75  	if err != nil {
    76  		return nil, &PathError{Op: "openat", Path: name, Err: err}
    77  	}
    78  	return newRoot(fd, name)
    79  }
    80  
    81  // rootOpenFileNolog is Root.OpenFile.
    82  func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, error) {
    83  	fd, err := doInRoot(root, name, nil, func(parent int, name string) (fd int, err error) {
    84  		ignoringEINTR(func() error {
    85  			fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC|flag, uint32(perm))
    86  			if err != nil {
    87  				// Never follow symlinks when O_CREATE|O_EXCL, no matter
    88  				// what error the OS returns.
    89  				isCreateExcl := flag&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL)
    90  				if !isCreateExcl && (isNoFollowErr(err) || err == syscall.ENOTDIR) {
    91  					err = checkSymlink(parent, name, err)
    92  				}
    93  				// AIX returns ELOOP instead of EEXIST for a dangling symlink.
    94  				// Convert this to EEXIST so it matches ErrExists.
    95  				if isCreateExcl && err == syscall.ELOOP {
    96  					err = syscall.EEXIST
    97  				}
    98  			}
    99  			return err
   100  		})
   101  		return fd, err
   102  	})
   103  	if err != nil {
   104  		return nil, &PathError{Op: "openat", Path: name, Err: err}
   105  	}
   106  	f := newFile(fd, joinPath(root.Name(), name), kindOpenFile, unix.HasNonblockFlag(flag))
   107  	return f, nil
   108  }
   109  
   110  func rootOpenDir(parent int, name string) (int, error) {
   111  	var (
   112  		fd  int
   113  		err error
   114  	)
   115  	ignoringEINTR(func() error {
   116  		fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC|syscall.O_DIRECTORY, 0)
   117  		if isNoFollowErr(err) || err == syscall.ENOTDIR {
   118  			err = checkSymlink(parent, name, err)
   119  		} else if err == syscall.ENOTSUP || err == syscall.EOPNOTSUPP {
   120  			// ENOTSUP and EOPNOTSUPP are often, but not always, the same errno.
   121  			// Translate both to ENOTDIR, since this indicates a non-terminal
   122  			// path component was not a directory.
   123  			err = syscall.ENOTDIR
   124  		}
   125  		return err
   126  	})
   127  	return fd, err
   128  }
   129  
   130  func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
   131  	fi, err := doInRoot(r, name, nil, func(parent sysfdType, n string) (FileInfo, error) {
   132  		var fs fileStat
   133  		if err := unix.Fstatat(parent, n, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil {
   134  			return nil, err
   135  		}
   136  		fillFileStatFromSys(&fs, name)
   137  		if !lstat && fs.Mode()&ModeSymlink != 0 {
   138  			return nil, checkSymlink(parent, n, syscall.ELOOP)
   139  		}
   140  		return &fs, nil
   141  	})
   142  	if err != nil {
   143  		return nil, &PathError{Op: "statat", Path: name, Err: err}
   144  	}
   145  	return fi, nil
   146  }
   147  
   148  func rootSymlink(r *Root, oldname, newname string) error {
   149  	_, err := doInRoot(r, newname, nil, func(parent sysfdType, name string) (struct{}, error) {
   150  		return struct{}{}, symlinkat(oldname, parent, name)
   151  	})
   152  	if err != nil {
   153  		return &LinkError{"symlinkat", oldname, newname, err}
   154  	}
   155  	return nil
   156  }
   157  
   158  // On systems which use fchmodat, fchownat, etc., we have a race condition:
   159  // When "name" is a symlink, Root.Chmod("name") should act on the target of that link.
   160  // However, fchmodat doesn't allow us to chmod a file only if it is not a symlink;
   161  // the AT_SYMLINK_NOFOLLOW parameter causes the operation to act on the symlink itself.
   162  //
   163  // We do the best we can by first checking to see if the target of the operation is a symlink,
   164  // and only attempting the fchmodat if it is not. If the target is replaced between the check
   165  // and the fchmodat, we will chmod the symlink rather than following it.
   166  //
   167  // This race condition is unfortunate, but does not permit escaping a root:
   168  // We may act on the wrong file, but that file will be contained within the root.
   169  func afterResolvingSymlink(parent int, name string, f func() error) error {
   170  	if err := checkSymlink(parent, name, nil); err != nil {
   171  		return err
   172  	}
   173  	return f()
   174  }
   175  
   176  func chmodat(parent int, name string, mode FileMode) error {
   177  	return afterResolvingSymlink(parent, name, func() error {
   178  		return ignoringEINTR(func() error {
   179  			return unix.Fchmodat(parent, name, syscallMode(mode), unix.AT_SYMLINK_NOFOLLOW)
   180  		})
   181  	})
   182  }
   183  
   184  func chownat(parent int, name string, uid, gid int) error {
   185  	return afterResolvingSymlink(parent, name, func() error {
   186  		return ignoringEINTR(func() error {
   187  			return unix.Fchownat(parent, name, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
   188  		})
   189  	})
   190  }
   191  
   192  func lchownat(parent int, name string, uid, gid int) error {
   193  	return ignoringEINTR(func() error {
   194  		return unix.Fchownat(parent, name, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
   195  	})
   196  }
   197  
   198  func chtimesat(parent int, name string, atime time.Time, mtime time.Time) error {
   199  	return afterResolvingSymlink(parent, name, func() error {
   200  		return ignoringEINTR(func() error {
   201  			utimes := chtimesUtimes(atime, mtime)
   202  			return unix.Utimensat(parent, name, &utimes, unix.AT_SYMLINK_NOFOLLOW)
   203  		})
   204  	})
   205  }
   206  
   207  func mkdirat(fd int, name string, perm FileMode) error {
   208  	return ignoringEINTR(func() error {
   209  		return unix.Mkdirat(fd, name, syscallMode(perm))
   210  	})
   211  }
   212  
   213  func removeat(fd int, name string) error {
   214  	// The system call interface forces us to know whether
   215  	// we are removing a file or directory. Try both.
   216  	e := ignoringEINTR(func() error {
   217  		return unix.Unlinkat(fd, name, 0)
   218  	})
   219  	if e == nil {
   220  		return nil
   221  	}
   222  	e1 := ignoringEINTR(func() error {
   223  		return unix.Unlinkat(fd, name, unix.AT_REMOVEDIR)
   224  	})
   225  	if e1 == nil {
   226  		return nil
   227  	}
   228  	// Both failed. See comment in Remove for how we decide which error to return.
   229  	if e1 != syscall.ENOTDIR {
   230  		return e1
   231  	}
   232  	return e
   233  }
   234  
   235  func removefileat(fd int, name string) error {
   236  	return ignoringEINTR(func() error {
   237  		return unix.Unlinkat(fd, name, 0)
   238  	})
   239  }
   240  
   241  func removedirat(fd int, name string) error {
   242  	return ignoringEINTR(func() error {
   243  		return unix.Unlinkat(fd, name, unix.AT_REMOVEDIR)
   244  	})
   245  }
   246  
   247  func renameat(oldfd int, oldname string, newfd int, newname string) error {
   248  	return unix.Renameat(oldfd, oldname, newfd, newname)
   249  }
   250  
   251  func linkat(oldfd int, oldname string, newfd int, newname string) error {
   252  	return unix.Linkat(oldfd, oldname, newfd, newname, 0)
   253  }
   254  
   255  func symlinkat(oldname string, newfd int, newname string) error {
   256  	return unix.Symlinkat(oldname, newfd, newname)
   257  }
   258  
   259  func modeAt(parent int, name string) (FileMode, error) {
   260  	var fs fileStat
   261  	if err := unix.Fstatat(parent, name, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil {
   262  		return 0, err
   263  	}
   264  	fillFileStatFromSys(&fs, name)
   265  	return fs.mode, nil
   266  }
   267  
   268  // checkSymlink resolves the symlink name in parent,
   269  // and returns errSymlink with the link contents.
   270  //
   271  // If name is not a symlink, return origError.
   272  func checkSymlink(parent int, name string, origError error) error {
   273  	link, err := readlinkat(parent, name)
   274  	if err != nil {
   275  		return origError
   276  	}
   277  	return errSymlink(link)
   278  }
   279  
   280  func readlinkat(fd int, name string) (string, error) {
   281  	for len := 128; ; len *= 2 {
   282  		b := make([]byte, len)
   283  		var (
   284  			n int
   285  			e error
   286  		)
   287  		ignoringEINTR(func() error {
   288  			n, e = unix.Readlinkat(fd, name, b)
   289  			return e
   290  		})
   291  		if e == syscall.ERANGE {
   292  			continue
   293  		}
   294  		if e != nil {
   295  			return "", e
   296  		}
   297  		n = max(n, 0)
   298  		if n < len {
   299  			return string(b[0:n]), nil
   300  		}
   301  	}
   302  }
   303  

View as plain text