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  	runtime.SetFinalizer(r.root, (*root).Close)
    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, joinPath(r.Name(), 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  	f.inRoot = true
   108  	return f, nil
   109  }
   110  
   111  func rootOpenDir(parent int, name string) (int, error) {
   112  	var (
   113  		fd  int
   114  		err error
   115  	)
   116  	ignoringEINTR(func() error {
   117  		fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC|syscall.O_DIRECTORY, 0)
   118  		if isNoFollowErr(err) || err == syscall.ENOTDIR {
   119  			err = checkSymlink(parent, name, err)
   120  		} else if err == syscall.ENOTSUP || err == syscall.EOPNOTSUPP {
   121  			// ENOTSUP and EOPNOTSUPP are often, but not always, the same errno.
   122  			// Translate both to ENOTDIR, since this indicates a non-terminal
   123  			// path component was not a directory.
   124  			err = syscall.ENOTDIR
   125  		}
   126  		return err
   127  	})
   128  	return fd, err
   129  }
   130  
   131  func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
   132  	fi, err := doInRoot(r, name, nil, func(parent sysfdType, n string) (FileInfo, error) {
   133  		var fs fileStat
   134  		if err := unix.Fstatat(parent, n, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil {
   135  			return nil, err
   136  		}
   137  		fillFileStatFromSys(&fs, name)
   138  		if !lstat && fs.Mode()&ModeSymlink != 0 {
   139  			return nil, checkSymlink(parent, n, syscall.ELOOP)
   140  		}
   141  		return &fs, nil
   142  	})
   143  	if err != nil {
   144  		return nil, &PathError{Op: "statat", Path: name, Err: err}
   145  	}
   146  	return fi, nil
   147  }
   148  
   149  func rootSymlink(r *Root, oldname, newname string) error {
   150  	_, err := doInRoot(r, newname, nil, func(parent sysfdType, name string) (struct{}, error) {
   151  		return struct{}{}, symlinkat(oldname, parent, name)
   152  	})
   153  	if err != nil {
   154  		return &LinkError{"symlinkat", oldname, newname, err}
   155  	}
   156  	return nil
   157  }
   158  
   159  // On systems which use fchmodat, fchownat, etc., we have a race condition:
   160  // When "name" is a symlink, Root.Chmod("name") should act on the target of that link.
   161  // However, fchmodat doesn't allow us to chmod a file only if it is not a symlink;
   162  // the AT_SYMLINK_NOFOLLOW parameter causes the operation to act on the symlink itself.
   163  //
   164  // We do the best we can by first checking to see if the target of the operation is a symlink,
   165  // and only attempting the fchmodat if it is not. If the target is replaced between the check
   166  // and the fchmodat, we will chmod the symlink rather than following it.
   167  //
   168  // This race condition is unfortunate, but does not permit escaping a root:
   169  // We may act on the wrong file, but that file will be contained within the root.
   170  func afterResolvingSymlink(parent int, name string, f func() error) error {
   171  	if err := checkSymlink(parent, name, nil); err != nil {
   172  		return err
   173  	}
   174  	return f()
   175  }
   176  
   177  func chmodat(parent int, name string, mode FileMode) error {
   178  	return afterResolvingSymlink(parent, name, func() error {
   179  		return ignoringEINTR(func() error {
   180  			return unix.Fchmodat(parent, name, syscallMode(mode), unix.AT_SYMLINK_NOFOLLOW)
   181  		})
   182  	})
   183  }
   184  
   185  func chownat(parent int, name string, uid, gid int) error {
   186  	return afterResolvingSymlink(parent, name, func() error {
   187  		return ignoringEINTR(func() error {
   188  			return unix.Fchownat(parent, name, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
   189  		})
   190  	})
   191  }
   192  
   193  func lchownat(parent int, name string, uid, gid int) error {
   194  	return ignoringEINTR(func() error {
   195  		return unix.Fchownat(parent, name, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
   196  	})
   197  }
   198  
   199  func chtimesat(parent int, name string, atime time.Time, mtime time.Time) error {
   200  	return afterResolvingSymlink(parent, name, func() error {
   201  		return ignoringEINTR(func() error {
   202  			utimes := chtimesUtimes(atime, mtime)
   203  			return unix.Utimensat(parent, name, &utimes, unix.AT_SYMLINK_NOFOLLOW)
   204  		})
   205  	})
   206  }
   207  
   208  func mkdirat(fd int, name string, perm FileMode) error {
   209  	return ignoringEINTR(func() error {
   210  		return unix.Mkdirat(fd, name, syscallMode(perm))
   211  	})
   212  }
   213  
   214  func removeat(fd int, name string) error {
   215  	// The system call interface forces us to know whether
   216  	// we are removing a file or directory. Try both.
   217  	e := ignoringEINTR(func() error {
   218  		return unix.Unlinkat(fd, name, 0)
   219  	})
   220  	if e == nil {
   221  		return nil
   222  	}
   223  	e1 := ignoringEINTR(func() error {
   224  		return unix.Unlinkat(fd, name, unix.AT_REMOVEDIR)
   225  	})
   226  	if e1 == nil {
   227  		return nil
   228  	}
   229  	// Both failed. See comment in Remove for how we decide which error to return.
   230  	if e1 != syscall.ENOTDIR {
   231  		return e1
   232  	}
   233  	return e
   234  }
   235  
   236  func removefileat(fd int, name string) error {
   237  	return ignoringEINTR(func() error {
   238  		return unix.Unlinkat(fd, name, 0)
   239  	})
   240  }
   241  
   242  func removedirat(fd int, name string) error {
   243  	return ignoringEINTR(func() error {
   244  		return unix.Unlinkat(fd, name, unix.AT_REMOVEDIR)
   245  	})
   246  }
   247  
   248  func renameat(oldfd int, oldname string, newfd int, newname string) error {
   249  	return unix.Renameat(oldfd, oldname, newfd, newname)
   250  }
   251  
   252  func linkat(oldfd int, oldname string, newfd int, newname string) error {
   253  	return unix.Linkat(oldfd, oldname, newfd, newname, 0)
   254  }
   255  
   256  func symlinkat(oldname string, newfd int, newname string) error {
   257  	return unix.Symlinkat(oldname, newfd, newname)
   258  }
   259  
   260  func modeAt(parent int, name string) (FileMode, error) {
   261  	var fs fileStat
   262  	if err := unix.Fstatat(parent, name, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil {
   263  		return 0, err
   264  	}
   265  	fillFileStatFromSys(&fs, name)
   266  	return fs.mode, nil
   267  }
   268  
   269  // checkSymlink resolves the symlink name in parent,
   270  // and returns errSymlink with the link contents.
   271  //
   272  // If name is not a symlink, return origError.
   273  func checkSymlink(parent int, name string, origError error) error {
   274  	link, err := readlinkat(parent, name)
   275  	if err != nil {
   276  		return origError
   277  	}
   278  	return errSymlink(link)
   279  }
   280  
   281  func readlinkat(fd int, name string) (string, error) {
   282  	for len := 128; ; len *= 2 {
   283  		b := make([]byte, len)
   284  		var (
   285  			n int
   286  			e error
   287  		)
   288  		ignoringEINTR(func() error {
   289  			n, e = unix.Readlinkat(fd, name, b)
   290  			return e
   291  		})
   292  		if e == syscall.ERANGE {
   293  			continue
   294  		}
   295  		if e != nil {
   296  			return "", e
   297  		}
   298  		n = max(n, 0)
   299  		if n < len {
   300  			return string(b[0:n]), nil
   301  		}
   302  	}
   303  }
   304  

View as plain text