1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
package sys
import (
"fmt"
"os"
"runtime"
"strconv"
"golang.org/x/sys/unix"
"github.com/opencontainers/runc/internal/pathrs"
)
// FchmodFile is a wrapper around fchmodat2(AT_EMPTY_PATH) with fallbacks for
// older kernels. This is distinct from [File.Chmod] and [unix.Fchmod] in that
// it works on O_PATH file descriptors.
func FchmodFile(f *os.File, mode uint32) error {
err := unix.Fchmodat(int(f.Fd()), "", mode, unix.AT_EMPTY_PATH)
// If fchmodat2(2) is not available at all, golang.org/x/unix (probably
// in order to mirror glibc) returns EOPNOTSUPP rather than EINVAL
// (what the kernel actually returns for invalid flags, which is being
// emulated) or ENOSYS (which is what glibc actually sees).
if err != unix.EINVAL && err != unix.EOPNOTSUPP { //nolint:errorlint // unix errors are bare
// err == nil is implicitly handled
return os.NewSyscallError("fchmodat2 AT_EMPTY_PATH", err)
}
// AT_EMPTY_PATH support was added to fchmodat2 in Linux 6.6
// (5daeb41a6fc9d0d81cb2291884b7410e062d8fa1). The alternative for
// older kernels is to go through /proc.
fdDir, closer, err2 := pathrs.ProcThreadSelfOpen("fd/", unix.O_DIRECTORY)
if err2 != nil {
return fmt.Errorf("fchmodat2 AT_EMPTY_PATH fallback: %w", err2)
}
defer closer()
defer fdDir.Close()
err = unix.Fchmodat(int(fdDir.Fd()), strconv.Itoa(int(f.Fd())), mode, 0)
if err != nil {
err = fmt.Errorf("fchmodat /proc/self/fd/%d: %w", f.Fd(), err)
}
runtime.KeepAlive(f)
return err
}
// FchownFile is a wrapper around fchownat(AT_EMPTY_PATH). This is distinct
// from [File.Chown] and [unix.Fchown] in that it works on O_PATH file
// descriptors.
func FchownFile(f *os.File, uid, gid int) error {
err := unix.Fchownat(int(f.Fd()), "", uid, gid, unix.AT_EMPTY_PATH)
runtime.KeepAlive(f)
return os.NewSyscallError("fchownat AT_EMPTY_PATH", err)
}
|