mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
A tty inode was kept alive by a single ownership chain: the stdio file description's reference on the import-time dentry. Everything else that used the inode referred to it uncounted: a file description opened via /dev/tty pins only the devtmpfs dentry, and the controlling-terminal association (ThreadGroup.tty) is a bare pointer. PiperOrigin-RevId: 962479941
1119 lines
33 KiB
Go
1119 lines
33 KiB
Go
// Copyright 2018 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package linux
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/fspath"
|
|
"gvisor.dev/gvisor/pkg/hostarch"
|
|
"gvisor.dev/gvisor/pkg/marshal/primitive"
|
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/sched"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
"gvisor.dev/gvisor/pkg/usermem"
|
|
)
|
|
|
|
var (
|
|
// ExecMaxTotalSize is the maximum length of all argv and envv entries.
|
|
//
|
|
// N.B. The behavior here is different than Linux. Linux provides a limit on
|
|
// individual arguments of 32 pages, and an aggregate limit of at least 32 pages
|
|
// but otherwise bounded by min(stack size / 4, 8 MB * 3 / 4). We don't implement
|
|
// any behavior based on the stack size, and instead provide a fixed hard-limit of
|
|
// 2 MB (which should work well given that 8 MB stack limits are common).
|
|
ExecMaxTotalSize = 2 * 1024 * 1024
|
|
|
|
// ExecMaxElemSize is the maximum length of a single argv or envv entry.
|
|
ExecMaxElemSize = 32 * hostarch.PageSize
|
|
)
|
|
|
|
// Getppid implements linux syscall getppid(2).
|
|
func Getppid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
parent := t.Parent()
|
|
if parent == nil {
|
|
return 0, nil, nil
|
|
}
|
|
return uintptr(t.PIDNamespace().IDOfThreadGroup(parent.ThreadGroup())), nil, nil
|
|
}
|
|
|
|
// Getpid implements linux syscall getpid(2).
|
|
func Getpid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
return uintptr(t.ThreadGroup().ID()), nil, nil
|
|
}
|
|
|
|
// Gettid implements linux syscall gettid(2).
|
|
func Gettid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
return uintptr(t.ThreadID()), nil, nil
|
|
}
|
|
|
|
// Execve implements linux syscall execve(2).
|
|
func Execve(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
pathnameAddr := args[0].Pointer()
|
|
argvAddr := args[1].Pointer()
|
|
envvAddr := args[2].Pointer()
|
|
return execveat(t, linux.AT_FDCWD, pathnameAddr, argvAddr, envvAddr, 0 /* flags */)
|
|
}
|
|
|
|
// Execveat implements linux syscall execveat(2).
|
|
func Execveat(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
dirfd := args[0].Int()
|
|
pathnameAddr := args[1].Pointer()
|
|
argvAddr := args[2].Pointer()
|
|
envvAddr := args[3].Pointer()
|
|
flags := args[4].Int()
|
|
return execveat(t, dirfd, pathnameAddr, argvAddr, envvAddr, flags)
|
|
}
|
|
|
|
func execveat(t *kernel.Task, dirfd int32, pathnameAddr, argvAddr, envvAddr hostarch.Addr, flags int32) (uintptr, *kernel.SyscallControl, error) {
|
|
if flags&^(linux.AT_EMPTY_PATH|linux.AT_SYMLINK_NOFOLLOW) != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
pathname, err := t.CopyInString(pathnameAddr, linux.PATH_MAX)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
var argv, envv []string
|
|
if argvAddr != 0 {
|
|
var err error
|
|
argv, err = t.CopyInVector(argvAddr, ExecMaxElemSize, ExecMaxTotalSize)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
}
|
|
if envvAddr != 0 {
|
|
var err error
|
|
envv, err = t.CopyInVector(envvAddr, ExecMaxElemSize, ExecMaxTotalSize)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
}
|
|
|
|
root := t.FSContext().RootDirectory()
|
|
defer root.DecRef(t)
|
|
var executable *vfs.FileDescription // DecRef deferred to to Task.Execve
|
|
closeOnExec := false
|
|
if path := fspath.Parse(pathname); dirfd != linux.AT_FDCWD && !path.Absolute {
|
|
// We must open the executable ourselves since dirfd is used as the
|
|
// starting point while resolving path, but the task working directory
|
|
// is used as the starting point while resolving interpreters (Linux:
|
|
// fs/binfmt_script.c:load_script() => fs/exec.c:open_exec() =>
|
|
// do_open_execat(fd=AT_FDCWD)), and the loader package is currently
|
|
// incapable of handling this correctly.
|
|
if !path.HasComponents() && flags&linux.AT_EMPTY_PATH == 0 {
|
|
return 0, nil, linuxerr.ENOENT
|
|
}
|
|
dirfile, dirfileFlags := t.FDTable().Get(dirfd)
|
|
if dirfile == nil {
|
|
return 0, nil, linuxerr.EBADF
|
|
}
|
|
start := dirfile.VirtualDentry()
|
|
start.IncRef()
|
|
dirfile.DecRef(t)
|
|
closeOnExec = dirfileFlags.CloseOnExec
|
|
file, err := t.Kernel().VFS().OpenAt(t, t.Credentials(), &vfs.PathOperation{
|
|
Root: root,
|
|
Start: start,
|
|
Path: path,
|
|
FollowFinalSymlink: flags&linux.AT_SYMLINK_NOFOLLOW == 0,
|
|
}, &vfs.OpenOptions{
|
|
Flags: linux.O_RDONLY,
|
|
FileExec: true,
|
|
})
|
|
start.DecRef(t)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
executable = file
|
|
if path.HasComponents() {
|
|
pathname = fmt.Sprintf("/dev/fd/%d/%s", dirfd, pathname)
|
|
} else {
|
|
pathname = fmt.Sprintf("/dev/fd/%d", dirfd)
|
|
}
|
|
}
|
|
|
|
// Execve takes ownership of `executable`.
|
|
ctrl := t.Execve(argv, envv, flags, pathname, executable, closeOnExec)
|
|
return 0, ctrl, nil
|
|
}
|
|
|
|
// Exit implements linux syscall exit(2).
|
|
func Exit(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
status := args[0].Int()
|
|
t.PrepareExit(linux.WaitStatusExit(status & 0xff))
|
|
return 0, kernel.CtrlDoExit, nil
|
|
}
|
|
|
|
// ExitGroup implements linux syscall exit_group(2).
|
|
func ExitGroup(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
status := args[0].Int()
|
|
t.PrepareGroupExit(linux.WaitStatusExit(status & 0xff))
|
|
return 0, kernel.CtrlDoExit, nil
|
|
}
|
|
|
|
// clone is used by Clone, Fork, and VFork.
|
|
func clone(t *kernel.Task, flags int, stack hostarch.Addr, parentTID hostarch.Addr, childTID hostarch.Addr, tls hostarch.Addr) (uintptr, *kernel.SyscallControl, error) {
|
|
if flags&linux.CLONE_PIDFD != 0 {
|
|
if flags&linux.CLONE_PARENT_SETTID != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
if flags&linux.CLONE_DETACHED != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
}
|
|
|
|
args := linux.CloneArgs{
|
|
Flags: uint64(uint32(flags) &^ linux.CSIGNAL),
|
|
ChildTID: uint64(childTID),
|
|
ParentTID: uint64(parentTID),
|
|
ExitSignal: uint64(flags & linux.CSIGNAL),
|
|
Stack: uint64(stack),
|
|
TLS: uint64(tls),
|
|
}
|
|
if flags&linux.CLONE_PIDFD != 0 {
|
|
args.Pidfd = uint64(parentTID)
|
|
}
|
|
|
|
ntid, ctrl, err := t.Clone(&args)
|
|
return uintptr(ntid), ctrl, err
|
|
}
|
|
|
|
// Fork implements Linux syscall fork(2).
|
|
func Fork(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
// "A call to fork() is equivalent to a call to clone(2) specifying flags
|
|
// as just SIGCHLD." - fork(2)
|
|
return clone(t, int(linux.SIGCHLD), 0, 0, 0, 0)
|
|
}
|
|
|
|
// Vfork implements Linux syscall vfork(2).
|
|
func Vfork(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
// """
|
|
// A call to vfork() is equivalent to calling clone(2) with flags specified as:
|
|
//
|
|
// CLONE_VM | CLONE_VFORK | SIGCHLD
|
|
// """ - vfork(2)
|
|
return clone(t, linux.CLONE_VM|linux.CLONE_VFORK|int(linux.SIGCHLD), 0, 0, 0, 0)
|
|
}
|
|
|
|
// Clone3 implements linux syscall clone3(2).
|
|
func Clone3(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
cloneArgsPointer := args[0].Pointer()
|
|
size := args[1].SizeT()
|
|
|
|
if int(size) < linux.CLONE_ARGS_SIZE_VER0 || int(size) > linux.CLONE_ARGS_SIZE_VER2 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
var cloneArgs linux.CloneArgs
|
|
if cloneArgsPointer != 0 {
|
|
if _, err := cloneArgs.CopyInN(t, cloneArgsPointer, int(size)); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
}
|
|
|
|
if cloneArgs.Flags&linux.CLONE_DETACHED != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
if cloneArgs.Flags&(linux.CLONE_THREAD|linux.CLONE_PARENT) != 0 && cloneArgs.ExitSignal != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
ntid, ctrl, err := t.Clone(&cloneArgs)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
return uintptr(ntid), ctrl, err
|
|
}
|
|
|
|
// parseCommonWaitOptions applies the options common to wait4 and waitid to
|
|
// wopts.
|
|
func parseCommonWaitOptions(wopts *kernel.WaitOptions, options int) error {
|
|
switch options & (linux.WCLONE | linux.WALL) {
|
|
case 0:
|
|
wopts.NonCloneTasks = true
|
|
case linux.WCLONE:
|
|
wopts.CloneTasks = true
|
|
case linux.WALL:
|
|
wopts.NonCloneTasks = true
|
|
wopts.CloneTasks = true
|
|
default:
|
|
return linuxerr.EINVAL
|
|
}
|
|
if options&linux.WCONTINUED != 0 {
|
|
wopts.Events |= kernel.EventGroupContinue
|
|
}
|
|
if options&linux.WNOHANG == 0 {
|
|
wopts.BlockInterruptErr = linuxerr.ERESTARTSYS
|
|
}
|
|
if options&linux.WNOTHREAD == 0 {
|
|
wopts.SiblingChildren = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// wait4 waits for the given child process to exit.
|
|
func wait4(t *kernel.Task, pid int, statusAddr hostarch.Addr, options int, rusageAddr hostarch.Addr) (uintptr, error) {
|
|
if options&^(linux.WNOHANG|linux.WUNTRACED|linux.WCONTINUED|linux.WNOTHREAD|linux.WALL|linux.WCLONE) != 0 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
wopts := kernel.WaitOptions{
|
|
Events: kernel.EventExit | kernel.EventTraceeStop,
|
|
ConsumeEvent: true,
|
|
}
|
|
// There are four cases to consider:
|
|
//
|
|
// pid < -1 any child process whose process group ID is equal to the absolute value of pid
|
|
// pid == -1 any child process
|
|
// pid == 0 any child process whose process group ID is equal to that of the calling process
|
|
// pid > 0 the child whose process ID is equal to the value of pid
|
|
switch {
|
|
case pid < -1:
|
|
wopts.SpecificPGID = kernel.ProcessGroupID(-pid)
|
|
case pid == -1:
|
|
// Any process is the default.
|
|
case pid == 0:
|
|
wopts.SpecificPGID = t.PIDNamespace().IDOfProcessGroup(t.ThreadGroup().ProcessGroup())
|
|
default:
|
|
wopts.SpecificTID = kernel.ThreadID(pid)
|
|
}
|
|
|
|
if err := parseCommonWaitOptions(&wopts, options); err != nil {
|
|
return 0, err
|
|
}
|
|
if options&linux.WUNTRACED != 0 {
|
|
wopts.Events |= kernel.EventChildGroupStop
|
|
}
|
|
|
|
wr, err := t.Wait(&wopts)
|
|
if err != nil {
|
|
if err == kernel.ErrNoWaitableEvent {
|
|
return 0, nil
|
|
}
|
|
return 0, err
|
|
}
|
|
if statusAddr != 0 {
|
|
if _, err := primitive.CopyUint32Out(t, statusAddr, uint32(wr.Status)); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
if rusageAddr != 0 {
|
|
ru := getrusage(wr.Task, linux.RUSAGE_BOTH)
|
|
if _, err := ru.CopyOut(t, rusageAddr); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return uintptr(wr.TID), nil
|
|
}
|
|
|
|
// Wait4 implements linux syscall wait4(2).
|
|
func Wait4(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
pid := int(args[0].Int())
|
|
statusAddr := args[1].Pointer()
|
|
options := int(args[2].Uint())
|
|
rusageAddr := args[3].Pointer()
|
|
|
|
n, err := wait4(t, pid, statusAddr, options, rusageAddr)
|
|
return n, nil, err
|
|
}
|
|
|
|
// WaitPid implements linux syscall waitpid(2).
|
|
func WaitPid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
pid := int(args[0].Int())
|
|
statusAddr := args[1].Pointer()
|
|
options := int(args[2].Uint())
|
|
|
|
n, err := wait4(t, pid, statusAddr, options, 0)
|
|
return n, nil, err
|
|
}
|
|
|
|
// Waitid implements linux syscall waitid(2).
|
|
func Waitid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
idtype := args[0].Int()
|
|
id := args[1].Int()
|
|
infop := args[2].Pointer()
|
|
options := int(args[3].Uint())
|
|
rusageAddr := args[4].Pointer()
|
|
|
|
if options&^(linux.WNOHANG|linux.WEXITED|linux.WSTOPPED|linux.WCONTINUED|linux.WNOWAIT|linux.WNOTHREAD|linux.WALL|linux.WCLONE) != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
if options&(linux.WEXITED|linux.WSTOPPED|linux.WCONTINUED) == 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
wopts := kernel.WaitOptions{
|
|
Events: kernel.EventTraceeStop,
|
|
ConsumeEvent: options&linux.WNOWAIT == 0,
|
|
}
|
|
switch idtype {
|
|
case linux.P_ALL:
|
|
case linux.P_PID:
|
|
wopts.SpecificTID = kernel.ThreadID(id)
|
|
case linux.P_PGID:
|
|
wopts.SpecificPGID = kernel.ProcessGroupID(id)
|
|
case linux.P_PIDFD:
|
|
if id < 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
tg, nonblock, err := t.ThreadGroupFromPIDFD(id)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
wopts.SpecificThreadGroup = tg
|
|
if nonblock {
|
|
wopts.NonBlock = true
|
|
}
|
|
default:
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
if err := parseCommonWaitOptions(&wopts, options); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if options&linux.WEXITED != 0 {
|
|
wopts.Events |= kernel.EventExit
|
|
}
|
|
if options&linux.WSTOPPED != 0 {
|
|
wopts.Events |= kernel.EventChildGroupStop
|
|
}
|
|
|
|
wr, err := t.Wait(&wopts)
|
|
if err != nil {
|
|
if err == kernel.ErrNoWaitableEvent {
|
|
if wopts.NonBlock && options&linux.WNOHANG == 0 {
|
|
return 0, nil, linuxerr.EAGAIN
|
|
}
|
|
err = nil
|
|
// "If WNOHANG was specified in options and there were no children
|
|
// in a waitable state, then waitid() returns 0 immediately and the
|
|
// state of the siginfo_t structure pointed to by infop is
|
|
// unspecified." - waitid(2). But Linux's waitid actually zeroes
|
|
// out the fields it would set for a successful waitid in this case
|
|
// as well.
|
|
if infop != 0 {
|
|
var si linux.SignalInfo
|
|
_, err2 := si.CopyOut(t, infop)
|
|
if err2 != nil {
|
|
return 0, nil, err2
|
|
}
|
|
}
|
|
}
|
|
return 0, nil, err
|
|
}
|
|
if rusageAddr != 0 {
|
|
ru := getrusage(wr.Task, linux.RUSAGE_BOTH)
|
|
if _, err := ru.CopyOut(t, rusageAddr); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
}
|
|
if infop == 0 {
|
|
return 0, nil, nil
|
|
}
|
|
si := linux.SignalInfo{
|
|
Signo: int32(linux.SIGCHLD),
|
|
}
|
|
si.SetPID(int32(wr.TID))
|
|
si.SetUID(int32(wr.UID))
|
|
s := wr.Status
|
|
switch {
|
|
case s.Exited():
|
|
si.Code = linux.CLD_EXITED
|
|
si.SetStatus(int32(s.ExitStatus()))
|
|
case s.Signaled():
|
|
if s.CoreDumped() {
|
|
si.Code = linux.CLD_DUMPED
|
|
} else {
|
|
si.Code = linux.CLD_KILLED
|
|
}
|
|
si.SetStatus(int32(s.TerminationSignal()))
|
|
case s.Stopped():
|
|
if wr.Event == kernel.EventTraceeStop {
|
|
si.Code = linux.CLD_TRAPPED
|
|
si.SetStatus(int32(s.PtraceEvent()))
|
|
} else {
|
|
si.Code = linux.CLD_STOPPED
|
|
si.SetStatus(int32(s.StopSignal()))
|
|
}
|
|
case s.Continued():
|
|
si.Code = linux.CLD_CONTINUED
|
|
si.SetStatus(int32(linux.SIGCONT))
|
|
default:
|
|
t.Warningf("waitid got incomprehensible wait status %d", s)
|
|
}
|
|
_, err = si.CopyOut(t, infop)
|
|
return 0, nil, err
|
|
}
|
|
|
|
// SetTidAddress implements linux syscall set_tid_address(2).
|
|
func SetTidAddress(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
addr := args[0].Pointer()
|
|
|
|
// Always succeed, return caller's tid.
|
|
t.SetClearTID(addr)
|
|
return uintptr(t.ThreadID()), nil, nil
|
|
}
|
|
|
|
// Setns implements linux syscall setns(2).
|
|
func Setns(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
fd := args[0].Int()
|
|
|
|
file := t.GetFile(fd)
|
|
if file == nil {
|
|
return 0, nil, linuxerr.EBADF
|
|
}
|
|
defer file.DecRef(t)
|
|
|
|
flags := args[1].Int()
|
|
return 0, nil, t.Setns(file, flags)
|
|
}
|
|
|
|
// Unshare implements linux syscall unshare(2).
|
|
func Unshare(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
flags := args[0].Int()
|
|
// "CLONE_NEWPID automatically implies CLONE_THREAD as well." - unshare(2)
|
|
if flags&linux.CLONE_NEWPID != 0 {
|
|
flags |= linux.CLONE_THREAD
|
|
}
|
|
// "... specifying CLONE_NEWUSER automatically implies CLONE_THREAD. Since
|
|
// Linux 3.9, CLONE_NEWUSER also automatically implies CLONE_FS."
|
|
if flags&linux.CLONE_NEWUSER != 0 {
|
|
flags |= linux.CLONE_THREAD | linux.CLONE_FS
|
|
}
|
|
return 0, nil, t.Unshare(flags)
|
|
}
|
|
|
|
// SchedYield implements linux syscall sched_yield(2).
|
|
func SchedYield(t *kernel.Task, sysno uintptr, _ arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
t.Yield()
|
|
return 0, nil, nil
|
|
}
|
|
|
|
// SchedSetaffinity implements linux syscall sched_setaffinity(2).
|
|
func SchedSetaffinity(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
tid := args[0].Int()
|
|
size := args[1].SizeT()
|
|
maskAddr := args[2].Pointer()
|
|
|
|
var task *kernel.Task
|
|
if tid == 0 {
|
|
task = t
|
|
} else {
|
|
task = t.PIDNamespace().TaskWithID(kernel.ThreadID(tid))
|
|
if task == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
// See kernel/sched/syscalls.c:sched_setaffinity().
|
|
if !canSetTaskNice(t, task) {
|
|
return 0, nil, linuxerr.EPERM
|
|
}
|
|
}
|
|
|
|
mask := sched.NewCPUSet(t.Kernel().ApplicationCores())
|
|
if size > mask.Size() {
|
|
size = mask.Size()
|
|
}
|
|
if _, err := t.CopyInBytes(maskAddr, mask[:size]); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
return 0, nil, task.SetCPUMask(mask)
|
|
}
|
|
|
|
// SchedGetaffinity implements linux syscall sched_getaffinity(2).
|
|
func SchedGetaffinity(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
tid := args[0].Int()
|
|
size := args[1].SizeT()
|
|
maskAddr := args[2].Pointer()
|
|
|
|
// This limitation is because linux stores the cpumask
|
|
// in an array of "unsigned long" so the buffer needs to
|
|
// be a multiple of the word size.
|
|
if size&(t.Arch().Width()-1) > 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
var task *kernel.Task
|
|
if tid == 0 {
|
|
task = t
|
|
} else {
|
|
task = t.PIDNamespace().TaskWithID(kernel.ThreadID(tid))
|
|
if task == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
}
|
|
|
|
mask := task.CPUMask()
|
|
// The buffer needs to be big enough to hold a cpumask with
|
|
// all possible cpus.
|
|
if size < mask.Size() {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
_, err := t.CopyOutBytes(maskAddr, mask)
|
|
|
|
// NOTE: The syscall interface is slightly different than the glibc
|
|
// interface. The raw sched_getaffinity syscall returns the number of
|
|
// bytes used to represent a cpu mask.
|
|
return uintptr(mask.Size()), nil, err
|
|
}
|
|
|
|
func canSetTaskNice(tSrc *kernel.Task, tDst *kernel.Task) bool {
|
|
// Linux requires the caller's EUID to match the target's
|
|
// real or effective UID, or CAP_SYS_NICE in the target's
|
|
// user namespace.
|
|
srcCreds := tSrc.Credentials()
|
|
dstCreds := tDst.Credentials()
|
|
return srcCreds.EffectiveKUID == dstCreds.RealKUID ||
|
|
srcCreds.EffectiveKUID == dstCreds.EffectiveKUID ||
|
|
srcCreds.HasCapabilityIn(linux.CAP_SYS_NICE, dstCreds.UserNamespace)
|
|
}
|
|
|
|
const supportedSchedFlags = 0
|
|
|
|
// SchedSetattr implements linux syscall sched_setattr(2).
|
|
func SchedSetattr(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
tid := kernel.ThreadID(args[0].Int())
|
|
schedAttrAddr := args[1].Pointer()
|
|
flags := args[2].Uint()
|
|
|
|
if flags != 0 || tid < 0 || schedAttrAddr == 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
schedAttr, err := copyInSchedAttr(t, schedAttrAddr)
|
|
if err == linuxerr.E2BIG {
|
|
// On E2BIG, indicate the "correct" struct size to userspace
|
|
kSize := uint32(linux.SCHED_ATTR_SIZE_LATEST)
|
|
if _, err := primitive.CopyUint32Out(t, schedAttrAddr, kSize); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
return 0, nil, linuxerr.E2BIG
|
|
}
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
var task *kernel.Task
|
|
if tid == 0 {
|
|
task = t
|
|
} else {
|
|
task = t.PIDNamespace().TaskWithID(tid)
|
|
if task == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
|
|
// See kernel/sched/syscalls.c:user_check_sched_setscheduler().
|
|
if !canSetTaskNice(t, task) {
|
|
return 0, nil, linuxerr.EPERM
|
|
}
|
|
|
|
// Note that we do not enforce CAP_SYS_NICE for increasing one's *own* nice priority.
|
|
// This matches Setpriority(), and doesn't matter since niceness has no effect.
|
|
}
|
|
|
|
if schedAttr.SchedFlags&^supportedSchedFlags != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
policy := uint(schedAttr.SchedPolicy)
|
|
if !isSchedulerSupported(policy) {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
if schedAttr.SchedPriority != 0 {
|
|
// SchedPriority only used for rt scheduling policies, which we don't support.
|
|
// If we ever add it, SchedPriority should be checked against RLIMIT_RTPRIO.
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
if schedAttr.SchedRuntime != 0 || schedAttr.SchedDeadline != 0 || schedAttr.SchedPeriod != 0 {
|
|
// SCHED_DEADLINE fields (we don't support).
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
if schedAttr.SchedUtilMin != 0 || schedAttr.SchedUtilMax != 0 {
|
|
// Utilization hints are not supported.
|
|
// In the future, we may support them for set/query.
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
task.SetScheduler(policy)
|
|
|
|
// Niceness is only set for SCHED_NORMAL and SCHED_BATCH
|
|
if policy == linux.SCHED_NORMAL || policy == linux.SCHED_BATCH {
|
|
nice := int(schedAttr.SchedNice)
|
|
task.SetNiceness(nice)
|
|
}
|
|
|
|
return 0, nil, nil
|
|
}
|
|
|
|
func copyInSchedAttr(t *kernel.Task, addr hostarch.Addr) (linux.SchedAttr, error) {
|
|
var schedAttr linux.SchedAttr
|
|
|
|
// Read the struct's size
|
|
var size uint32
|
|
if _, err := primitive.CopyUint32In(t, addr, &size); err != nil {
|
|
return linux.SchedAttr{}, err
|
|
}
|
|
|
|
if size == 0 {
|
|
size = linux.SCHED_ATTR_SIZE_VER0
|
|
}
|
|
if size < linux.SCHED_ATTR_SIZE_VER0 || size > hostarch.PageSize {
|
|
return linux.SchedAttr{}, linuxerr.E2BIG
|
|
}
|
|
|
|
if _, err := schedAttr.CopyInN(t, addr, min(int(size), linux.SCHED_ATTR_SIZE_LATEST)); err != nil {
|
|
return linux.SchedAttr{}, err
|
|
}
|
|
|
|
if size > linux.SCHED_ATTR_SIZE_LATEST {
|
|
// Userspace has a newer struct version. Check that the fields we don't
|
|
// know about are zeroed out.
|
|
buf := make([]byte, size-linux.SCHED_ATTR_SIZE_LATEST)
|
|
if _, err := t.CopyInBytes(addr+linux.SCHED_ATTR_SIZE_LATEST, buf); err != nil {
|
|
return linux.SchedAttr{}, err
|
|
}
|
|
|
|
for _, b := range buf {
|
|
if b != 0 {
|
|
return linux.SchedAttr{}, linuxerr.E2BIG
|
|
}
|
|
}
|
|
}
|
|
|
|
if schedAttr.SchedFlags&(linux.SCHED_FLAG_UTIL_CLAMP_MIN|linux.SCHED_FLAG_UTIL_CLAMP_MAX) != 0 && size < linux.SCHED_ATTR_SIZE_VER1 {
|
|
// Cannot specify schedUtilMin/schedUtilMax fields if userspace struct has
|
|
// no space for those fields
|
|
return linux.SchedAttr{}, linuxerr.EINVAL
|
|
}
|
|
|
|
return schedAttr, nil
|
|
}
|
|
|
|
// SchedGetattr implements linux syscall sched_getattr(2).
|
|
func SchedGetattr(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
tid := kernel.ThreadID(args[0].Int())
|
|
schedAttrAddr := args[1].Pointer()
|
|
schedAttrSize := args[2].Uint()
|
|
flags := args[3].Uint()
|
|
|
|
if flags != 0 || tid < 0 || schedAttrAddr == 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
if schedAttrSize < linux.SCHED_ATTR_SIZE_VER0 || schedAttrSize > hostarch.PageSize {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
// Lookup the target process
|
|
var task *kernel.Task
|
|
if tid == 0 {
|
|
task = t
|
|
} else {
|
|
task = t.PIDNamespace().TaskWithID(tid)
|
|
if task == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
}
|
|
|
|
policy := uint32(task.GetScheduler())
|
|
|
|
ret := linux.SchedAttr{
|
|
SchedPolicy: policy,
|
|
SchedNice: int32(task.Niceness()),
|
|
}
|
|
|
|
if err := copyOutSchedAttr(t, schedAttrAddr, schedAttrSize, &ret); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
return 0, nil, nil
|
|
}
|
|
|
|
func copyOutSchedAttr(t *kernel.Task, addr hostarch.Addr, size uint32, schedAttr *linux.SchedAttr) error {
|
|
size = min(size, linux.SCHED_ATTR_SIZE_LATEST)
|
|
schedAttr.Size = size
|
|
|
|
if _, err := schedAttr.CopyOutN(t, addr, int(size)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// IOPrioset implements linux syscall ioprio_set(2).
|
|
func IOPrioset(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
which := args[0].Int()
|
|
who := args[1].Int()
|
|
ioprio := args[2].Int()
|
|
|
|
// Sanity checks on ioprio
|
|
ioclass, _ := linux.UnwrapIOPrio(int(ioprio))
|
|
switch ioclass {
|
|
case linux.IOPRIO_CLASS_RT:
|
|
// Real-time class requires CAP_SYS_ADMIN or CAP_SYS_NICE
|
|
if !t.HasRootCapability(linux.CAP_SYS_ADMIN) && !t.HasRootCapability(linux.CAP_SYS_NICE) {
|
|
return 0, nil, linuxerr.EPERM
|
|
}
|
|
case linux.IOPRIO_CLASS_BE:
|
|
case linux.IOPRIO_CLASS_IDLE:
|
|
case linux.IOPRIO_CLASS_NONE:
|
|
if ioprio != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
default:
|
|
// Unknown class
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
switch which {
|
|
case linux.IOPRIO_WHO_PROCESS:
|
|
var task *kernel.Task
|
|
if who == 0 {
|
|
task = t
|
|
} else {
|
|
task = t.PIDNamespace().TaskWithID(kernel.ThreadID(who))
|
|
if task == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
|
|
if !canSetTaskNice(t, task) {
|
|
return 0, nil, linuxerr.EPERM
|
|
}
|
|
}
|
|
|
|
task.SetIOPrio(int(ioprio))
|
|
|
|
case linux.IOPRIO_WHO_PGRP:
|
|
fallthrough
|
|
case linux.IOPRIO_WHO_USER:
|
|
// IOPRIO_WHO_PGRP and IOPRIO_WHO_USER have no further implementation yet.
|
|
return 0, nil, nil
|
|
default:
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
return 0, nil, nil
|
|
}
|
|
|
|
// IOPrioget implements linux syscall ioprio_get(2).
|
|
func IOPrioget(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
which := args[0].Int()
|
|
who := args[1].Int()
|
|
|
|
switch which {
|
|
case linux.IOPRIO_WHO_PROCESS:
|
|
var task *kernel.Task
|
|
if who == 0 {
|
|
task = t
|
|
} else {
|
|
task = t.PIDNamespace().TaskWithID(kernel.ThreadID(who))
|
|
if task == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
|
|
if !canSetTaskNice(t, task) {
|
|
return 0, nil, linuxerr.EPERM
|
|
}
|
|
}
|
|
|
|
ioprio := task.GetIOPrio()
|
|
|
|
return uintptr(ioprio), nil, nil
|
|
|
|
case linux.IOPRIO_WHO_PGRP:
|
|
fallthrough
|
|
case linux.IOPRIO_WHO_USER:
|
|
// IOPRIO_WHO_PGRP and IOPRIO_WHO_USER have no further implementation yet.
|
|
return 0, nil, nil
|
|
default:
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
}
|
|
|
|
// Getcpu implements linux syscall getcpu(2).
|
|
func Getcpu(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
cpu := args[0].Pointer()
|
|
node := args[1].Pointer()
|
|
// third argument to this system call is nowadays unused.
|
|
|
|
if cpu != 0 {
|
|
if _, err := primitive.CopyInt32Out(t, cpu, t.CPU()); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
}
|
|
// We always return node 0.
|
|
if node != 0 {
|
|
if _, err := t.MemoryManager().ZeroOut(t, node, 4, usermem.IOOpts{}); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
}
|
|
return 0, nil, nil
|
|
}
|
|
|
|
// Setpgid implements the linux syscall setpgid(2).
|
|
func Setpgid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
// Note that throughout this function, pgid is interpreted with respect
|
|
// to t's namespace, not with respect to the selected ThreadGroup's
|
|
// namespace (which may be different).
|
|
pid := kernel.ThreadID(args[0].Int())
|
|
pgid := kernel.ProcessGroupID(args[1].Int())
|
|
|
|
// "If pid is zero, then the process ID of the calling process is used."
|
|
tg := t.ThreadGroup()
|
|
if pid != 0 {
|
|
ot := t.PIDNamespace().TaskWithID(pid)
|
|
if ot == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
tg = ot.ThreadGroup()
|
|
if tg.Leader() != ot {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
// Setpgid only operates on child threadgroups.
|
|
if tg != t.ThreadGroup() && (tg.Leader().Parent() == nil || tg.Leader().Parent().ThreadGroup() != t.ThreadGroup()) {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
}
|
|
|
|
// "If pgid is zero, then the PGID of the process specified by pid is made
|
|
// the same as its process ID."
|
|
defaultPGID := kernel.ProcessGroupID(t.PIDNamespace().IDOfThreadGroup(tg))
|
|
if pgid == 0 {
|
|
pgid = defaultPGID
|
|
} else if pgid < 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
// Return EACCES if an attempt was made to change the process group ID of one
|
|
// of the children of the calling process and the child had
|
|
// already performed an execve(2)
|
|
if tg != t.ThreadGroup() && tg.Execed() {
|
|
return 0, nil, linuxerr.EACCES
|
|
}
|
|
|
|
// If the pgid is the same as the group, then create a new one. Otherwise,
|
|
// we attempt to join an existing process group.
|
|
if pgid == defaultPGID {
|
|
// For convenience, errors line up with Linux syscall API.
|
|
if err := tg.CreateProcessGroup(); err != nil {
|
|
// Is the process group already as expected? If so,
|
|
// just return success. This is the same behavior as
|
|
// Linux.
|
|
if t.PIDNamespace().IDOfProcessGroup(tg.ProcessGroup()) == defaultPGID {
|
|
return 0, nil, nil
|
|
}
|
|
return 0, nil, err
|
|
}
|
|
} else {
|
|
// Same as CreateProcessGroup, above.
|
|
if err := tg.JoinProcessGroup(t.PIDNamespace(), pgid); err != nil {
|
|
// See above.
|
|
if t.PIDNamespace().IDOfProcessGroup(tg.ProcessGroup()) == pgid {
|
|
return 0, nil, nil
|
|
}
|
|
return 0, nil, err
|
|
}
|
|
}
|
|
|
|
// Success.
|
|
return 0, nil, nil
|
|
}
|
|
|
|
// Getpgrp implements the linux syscall getpgrp(2).
|
|
func Getpgrp(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
return uintptr(t.PIDNamespace().IDOfProcessGroup(t.ThreadGroup().ProcessGroup())), nil, nil
|
|
}
|
|
|
|
// Getpgid implements the linux syscall getpgid(2).
|
|
func Getpgid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
tid := kernel.ThreadID(args[0].Int())
|
|
if tid == 0 {
|
|
return Getpgrp(t, sysno, args)
|
|
}
|
|
|
|
target := t.PIDNamespace().TaskWithID(tid)
|
|
if target == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
|
|
return uintptr(t.PIDNamespace().IDOfProcessGroup(target.ThreadGroup().ProcessGroup())), nil, nil
|
|
}
|
|
|
|
// Setsid implements the linux syscall setsid(2).
|
|
func Setsid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
sid, err := t.ThreadGroup().CreateSession(t)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
return uintptr(sid), nil, nil
|
|
}
|
|
|
|
// Getsid implements the linux syscall getsid(2).
|
|
func Getsid(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
tid := kernel.ThreadID(args[0].Int())
|
|
if tid == 0 {
|
|
return uintptr(t.PIDNamespace().IDOfSession(t.ThreadGroup().Session())), nil, nil
|
|
}
|
|
|
|
target := t.PIDNamespace().TaskWithID(tid)
|
|
if target == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
|
|
return uintptr(t.PIDNamespace().IDOfSession(target.ThreadGroup().Session())), nil, nil
|
|
}
|
|
|
|
// Getpriority pretends to implement the linux syscall getpriority(2).
|
|
//
|
|
// This is a stub; real priorities require a full scheduler.
|
|
func Getpriority(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
which := args[0].Int()
|
|
who := kernel.ThreadID(args[1].Int())
|
|
|
|
switch which {
|
|
case linux.PRIO_PROCESS:
|
|
// Look for who, return ESRCH if not found.
|
|
var task *kernel.Task
|
|
if who == 0 {
|
|
task = t
|
|
} else {
|
|
task = t.PIDNamespace().TaskWithID(who)
|
|
}
|
|
|
|
if task == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
|
|
// From kernel/sys.c:getpriority:
|
|
// "To avoid negative return values, 'getpriority()'
|
|
// will not return the normal nice-value, but a negated
|
|
// value that has been offset by 20"
|
|
return uintptr(20 - task.Niceness()), nil, nil
|
|
case linux.PRIO_USER:
|
|
fallthrough
|
|
case linux.PRIO_PGRP:
|
|
// PRIO_USER and PRIO_PGRP have no further implementation yet.
|
|
return 0, nil, nil
|
|
default:
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
}
|
|
|
|
// Setpriority pretends to implement the linux syscall setpriority(2).
|
|
//
|
|
// This is a stub; real priorities require a full scheduler.
|
|
func Setpriority(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
which := args[0].Int()
|
|
who := kernel.ThreadID(args[1].Int())
|
|
niceval := int(args[2].Int())
|
|
|
|
switch which {
|
|
case linux.PRIO_PROCESS:
|
|
// Look for who, return ESRCH if not found.
|
|
var task *kernel.Task
|
|
if who == 0 {
|
|
task = t
|
|
} else {
|
|
task = t.PIDNamespace().TaskWithID(who)
|
|
if task == nil {
|
|
return 0, nil, linuxerr.ESRCH
|
|
}
|
|
// See kernel/sys.c:set_one_prio_perm().
|
|
if !canSetTaskNice(t, task) {
|
|
return 0, nil, linuxerr.EPERM
|
|
}
|
|
|
|
// Note that we do not enforce CAP_SYS_NICE for increasing one's *own* nice priority.
|
|
// This doesn't matter since niceness has no effect.
|
|
}
|
|
|
|
task.SetNiceness(niceval)
|
|
case linux.PRIO_USER:
|
|
fallthrough
|
|
case linux.PRIO_PGRP:
|
|
// PRIO_USER and PRIO_PGRP have no further implementation yet.
|
|
return 0, nil, nil
|
|
default:
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
return 0, nil, nil
|
|
}
|
|
|
|
// Ptrace implements linux system call ptrace(2).
|
|
func Ptrace(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
req := args[0].Int64()
|
|
pid := kernel.ThreadID(args[1].Int())
|
|
addr := args[2].Pointer()
|
|
data := args[3].Pointer()
|
|
|
|
if req == linux.PTRACE_ATTACH || req == linux.PTRACE_SEIZE {
|
|
ctrl, err := t.PtraceAttach(req, pid, addr, data)
|
|
return 0, ctrl, err
|
|
}
|
|
return 0, nil, t.Ptrace(req, pid, addr, data)
|
|
}
|
|
|
|
// PIDFDOpen implements linux system call pidfd_open(2).
|
|
func PIDFDOpen(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
pid := kernel.ThreadID(args[0].Int())
|
|
flags := args[1].Uint()
|
|
|
|
if flags&^(linux.PIDFD_NONBLOCK|linux.PIDFD_THREAD) != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
if pid <= 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
|
|
isThread := flags&linux.PIDFD_THREAD != 0
|
|
nonblock := flags&linux.PIDFD_NONBLOCK != 0
|
|
vfsfd, err := t.PIDFDOpen(pid, isThread, nonblock)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
defer vfsfd.DecRef(t)
|
|
|
|
fd, err := t.NewFDFrom(0, vfsfd, kernel.FDFlags{
|
|
CloseOnExec: true, // Pidfd is always O_CLOEXEC.
|
|
})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
return uintptr(fd), nil, nil
|
|
}
|
|
|
|
// PIDFDGetFD implements linux system call pidfd_getfd(2).
|
|
func PIDFDGetFD(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
pidFD := args[0].Int()
|
|
targetFD := args[1].Int()
|
|
flags := args[2].Uint()
|
|
|
|
if flags != 0 {
|
|
return 0, nil, linuxerr.EINVAL
|
|
}
|
|
fd, err := t.PIDFDGetFD(pidFD, targetFD, flags)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
return fd, nil, nil
|
|
}
|