Source file src/internal/syscall/unix/fchmodat_linux.go

     1  // Copyright 2026 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 linux
     6  
     7  package unix
     8  
     9  import (
    10  	"internal/strconv"
    11  	"syscall"
    12  )
    13  
    14  func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
    15  	// On Linux, the fchmodat syscall silently ignores the AT_SYMLINK_NOFOLLOW flag.
    16  	// We need to use fchmodat2 instead.
    17  	// syscall.Fchmodat handles this.
    18  	if err := syscall.Fchmodat(dirfd, path, mode, flags); err != syscall.EOPNOTSUPP {
    19  		return err
    20  	}
    21  
    22  	// This kernel doesn't appear to support fchmodat2 (added in Linux 6.6).
    23  	// We can't fall back to Fchmod, because it requires write permissions on the file.
    24  	// Instead, use the same workaround as GNU libc and musl, which is to open the file
    25  	// and then fchmodat the FD in /proc/self/fd.
    26  	// See: https://lwn.net/Articles/939217/
    27  	fd, err := Openat(dirfd, path, O_PATH|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
    28  	if err != nil {
    29  		return err
    30  	}
    31  	defer syscall.Close(fd)
    32  	procPath := "/proc/self/fd/" + strconv.Itoa(fd)
    33  
    34  	// Check to see if this file is a symlink.
    35  	// (We passed O_NOFOLLOW above, but O_PATH|O_NOFOLLOW will open a symlink.)
    36  	var st syscall.Stat_t
    37  	if err := syscall.Stat(procPath, &st); err != nil {
    38  		if err == syscall.ENOENT {
    39  			// /proc has probably not been mounted. Give up.
    40  			return syscall.EOPNOTSUPP
    41  		}
    42  		return err
    43  	}
    44  	if st.Mode&syscall.S_IFMT == syscall.S_IFLNK {
    45  		// fchmodat on the proc FD for a symlink apparently gives inconsistent
    46  		// results, so just refuse to try.
    47  		return syscall.EOPNOTSUPP
    48  	}
    49  
    50  	return syscall.Fchmodat(AT_FDCWD, procPath, mode, flags&^AT_SYMLINK_NOFOLLOW)
    51  }
    52  

View as plain text