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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || wasip1
     6  
     7  package os
     8  
     9  import (
    10  	"errors"
    11  	"internal/syscall/unix"
    12  	"runtime"
    13  	"syscall"
    14  )
    15  
    16  type sysfdType = int
    17  
    18  // openRootNolog is OpenRoot.
    19  func openRootNolog(name string) (*Root, error) {
    20  	var fd int
    21  	err := ignoringEINTR(func() error {
    22  		var err error
    23  		fd, _, err = open(name, syscall.O_CLOEXEC, 0)
    24  		return err
    25  	})
    26  	if err != nil {
    27  		return nil, &PathError{Op: "open", Path: name, Err: err}
    28  	}
    29  	return newRoot(fd, name)
    30  }
    31  
    32  // newRoot returns a new Root.
    33  // If fd is not a directory, it closes it and returns an error.
    34  func newRoot(fd int, name string) (*Root, error) {
    35  	var fs fileStat
    36  	err := ignoringEINTR(func() error {
    37  		return syscall.Fstat(fd, &fs.sys)
    38  	})
    39  	fillFileStatFromSys(&fs, name)
    40  	if err == nil && !fs.IsDir() {
    41  		syscall.Close(fd)
    42  		return nil, &PathError{Op: "open", Path: name, Err: errors.New("not a directory")}
    43  	}
    44  
    45  	// There's a race here with fork/exec, which we are
    46  	// content to live with. See ../syscall/exec_unix.go.
    47  	if !supportsCloseOnExec {
    48  		syscall.CloseOnExec(fd)
    49  	}
    50  
    51  	r := &Root{root{
    52  		fd:   fd,
    53  		name: name,
    54  	}}
    55  	runtime.SetFinalizer(&r.root, (*root).Close)
    56  	return r, nil
    57  }
    58  
    59  // openRootInRoot is Root.OpenRoot.
    60  func openRootInRoot(r *Root, name string) (*Root, error) {
    61  	fd, err := doInRoot(r, name, func(parent int, name string) (fd int, err error) {
    62  		ignoringEINTR(func() error {
    63  			fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
    64  			if isNoFollowErr(err) {
    65  				err = checkSymlink(parent, name, err)
    66  			}
    67  			return err
    68  		})
    69  		return fd, err
    70  	})
    71  	if err != nil {
    72  		return nil, &PathError{Op: "openat", Path: name, Err: err}
    73  	}
    74  	return newRoot(fd, name)
    75  }
    76  
    77  // rootOpenFileNolog is Root.OpenFile.
    78  func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File, error) {
    79  	fd, err := doInRoot(root, name, func(parent int, name string) (fd int, err error) {
    80  		ignoringEINTR(func() error {
    81  			fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC|flag, uint32(perm))
    82  			if isNoFollowErr(err) || err == syscall.ENOTDIR {
    83  				err = checkSymlink(parent, name, err)
    84  			}
    85  			return err
    86  		})
    87  		return fd, err
    88  	})
    89  	if err != nil {
    90  		return nil, &PathError{Op: "openat", Path: name, Err: err}
    91  	}
    92  	f := newFile(fd, joinPath(root.Name(), name), kindOpenFile, unix.HasNonblockFlag(flag))
    93  	return f, nil
    94  }
    95  
    96  func rootOpenDir(parent int, name string) (int, error) {
    97  	var (
    98  		fd  int
    99  		err error
   100  	)
   101  	ignoringEINTR(func() error {
   102  		fd, err = unix.Openat(parent, name, syscall.O_NOFOLLOW|syscall.O_CLOEXEC|syscall.O_DIRECTORY, 0)
   103  		if isNoFollowErr(err) || err == syscall.ENOTDIR {
   104  			err = checkSymlink(parent, name, err)
   105  		} else if err == syscall.ENOTSUP || err == syscall.EOPNOTSUPP {
   106  			// ENOTSUP and EOPNOTSUPP are often, but not always, the same errno.
   107  			// Translate both to ENOTDIR, since this indicates a non-terminal
   108  			// path component was not a directory.
   109  			err = syscall.ENOTDIR
   110  		}
   111  		return err
   112  	})
   113  	return fd, err
   114  }
   115  
   116  func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
   117  	fi, err := doInRoot(r, name, func(parent sysfdType, n string) (FileInfo, error) {
   118  		var fs fileStat
   119  		if err := unix.Fstatat(parent, n, &fs.sys, unix.AT_SYMLINK_NOFOLLOW); err != nil {
   120  			return nil, err
   121  		}
   122  		fillFileStatFromSys(&fs, name)
   123  		if !lstat && fs.Mode()&ModeSymlink != 0 {
   124  			return nil, checkSymlink(parent, n, syscall.ELOOP)
   125  		}
   126  		return &fs, nil
   127  	})
   128  	if err != nil {
   129  		return nil, &PathError{Op: "statat", Path: name, Err: err}
   130  	}
   131  	return fi, nil
   132  }
   133  
   134  func mkdirat(fd int, name string, perm FileMode) error {
   135  	return ignoringEINTR(func() error {
   136  		return unix.Mkdirat(fd, name, syscallMode(perm))
   137  	})
   138  }
   139  
   140  func removeat(fd int, name string) error {
   141  	// The system call interface forces us to know whether
   142  	// we are removing a file or directory. Try both.
   143  	e := ignoringEINTR(func() error {
   144  		return unix.Unlinkat(fd, name, 0)
   145  	})
   146  	if e == nil {
   147  		return nil
   148  	}
   149  	e1 := ignoringEINTR(func() error {
   150  		return unix.Unlinkat(fd, name, unix.AT_REMOVEDIR)
   151  	})
   152  	if e1 == nil {
   153  		return nil
   154  	}
   155  	// Both failed. See comment in Remove for how we decide which error to return.
   156  	if e1 != syscall.ENOTDIR {
   157  		return e1
   158  	}
   159  	return e
   160  }
   161  
   162  // checkSymlink resolves the symlink name in parent,
   163  // and returns errSymlink with the link contents.
   164  //
   165  // If name is not a symlink, return origError.
   166  func checkSymlink(parent int, name string, origError error) error {
   167  	link, err := readlinkat(parent, name)
   168  	if err != nil {
   169  		return origError
   170  	}
   171  	return errSymlink(link)
   172  }
   173  
   174  func readlinkat(fd int, name string) (string, error) {
   175  	for len := 128; ; len *= 2 {
   176  		b := make([]byte, len)
   177  		var (
   178  			n int
   179  			e error
   180  		)
   181  		ignoringEINTR(func() error {
   182  			n, e = unix.Readlinkat(fd, name, b)
   183  			return e
   184  		})
   185  		if e == syscall.ERANGE {
   186  			continue
   187  		}
   188  		if e != nil {
   189  			return "", e
   190  		}
   191  		if n < 0 {
   192  			n = 0
   193  		}
   194  		if n < len {
   195  			return string(b[0:n]), nil
   196  		}
   197  	}
   198  }
   199  

View as plain text