blob: 786ec29df2482457f1fc007c8ee8dc80ec84fc41 [file]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux
package unix
import (
"internal/strconv"
"syscall"
)
func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
// On Linux, the fchmodat syscall silently ignores the AT_SYMLINK_NOFOLLOW flag.
// We need to use fchmodat2 instead.
// syscall.Fchmodat handles this.
if err := syscall.Fchmodat(dirfd, path, mode, flags); err != syscall.EOPNOTSUPP {
return err
}
// This kernel doesn't appear to support fchmodat2 (added in Linux 6.6).
// We can't fall back to Fchmod, because it requires write permissions on the file.
// Instead, use the same workaround as GNU libc and musl, which is to open the file
// and then fchmodat the FD in /proc/self/fd.
// See: https://lwn.net/Articles/939217/
fd, err := Openat(dirfd, path, O_PATH|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
if err != nil {
return err
}
defer syscall.Close(fd)
procPath := "/proc/self/fd/" + strconv.Itoa(fd)
// Check to see if this file is a symlink.
// (We passed O_NOFOLLOW above, but O_PATH|O_NOFOLLOW will open a symlink.)
var st syscall.Stat_t
if err := syscall.Stat(procPath, &st); err != nil {
if err == syscall.ENOENT {
// /proc has probably not been mounted. Give up.
return syscall.EOPNOTSUPP
}
return err
}
if st.Mode&syscall.S_IFMT == syscall.S_IFLNK {
// fchmodat on the proc FD for a symlink apparently gives inconsistent
// results, so just refuse to try.
return syscall.EOPNOTSUPP
}
return syscall.Fchmodat(AT_FDCWD, procPath, mode, flags&^AT_SYMLINK_NOFOLLOW)
}