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