// Copyright 2023 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.

// Support for pidfd was added during the course of a few Linux releases:
//  v5.1: pidfd_send_signal syscall;
//  v5.2: CLONE_PIDFD flag for clone syscall;
//  v5.3: pidfd_open syscall, clone3 syscall;
//  v5.4: P_PIDFD idtype support for waitid syscall;
//  v5.6: pidfd_getfd syscall.

package os

import (
	
	
	
	
	
)

func ensurePidfd( *syscall.SysProcAttr) *syscall.SysProcAttr {
	if !pidfdWorks() {
		return 
	}

	var  int

	if  == nil {
		return &syscall.SysProcAttr{
			PidFD: &,
		}
	}
	if .PidFD == nil {
		 := * // copy
		.PidFD = &
		return &
	}

	return 
}

func getPidfd( *syscall.SysProcAttr) (uintptr, bool) {
	if !pidfdWorks() {
		return 0, false
	}

	return uintptr(*.PidFD), true
}

func pidfdFind( int) (uintptr, error) {
	if !pidfdWorks() {
		return 0, syscall.ENOSYS
	}

	,  := unix.PidFDOpen(, 0)
	if  != nil {
		return 0, convertESRCH()
	}
	return , nil
}

// _P_PIDFD is used as idtype argument to waitid syscall.
const _P_PIDFD = 3

func ( *Process) () (*ProcessState, error) {
	// When pidfd is used, there is no wait/kill race (described in CL 23967)
	// because the PID recycle issue doesn't exist (IOW, pidfd, unlike PID,
	// is guaranteed to refer to one particular process). Thus, there is no
	// need for the workaround (blockUntilWaitable + sigMu) from pidWait.
	//
	// We _do_ need to be careful about reuse of the pidfd FD number when
	// closing the pidfd. See handle for more details.
	,  := .handleTransientAcquire()
	switch  {
	case statusDone:
		// Process already completed Wait, or was not found by
		// pidfdFind. Return ECHILD for consistency with what the wait
		// syscall would return.
		return nil, NewSyscallError("wait", syscall.ECHILD)
	case statusReleased:
		return nil, syscall.EINVAL
	}
	defer .handleTransientRelease()

	var (
		   unix.SiginfoChild
		 syscall.Rusage
		      syscall.Errno
	)
	for {
		_, _,  = syscall.Syscall6(syscall.SYS_WAITID, _P_PIDFD, , uintptr(unsafe.Pointer(&)), syscall.WEXITED, uintptr(unsafe.Pointer(&)), 0)
		if  != syscall.EINTR {
			break
		}
	}
	if  != 0 {
		return nil, NewSyscallError("waitid", )
	}
	// Release the Process' handle reference, in addition to the reference
	// we took above.
	.handlePersistentRelease(statusDone)
	return &ProcessState{
		pid:    int(.Pid),
		status: .WaitStatus(),
		rusage: &,
	}, nil
}

func ( *Process) ( syscall.Signal) error {
	,  := .handleTransientAcquire()
	switch  {
	case statusDone:
		return ErrProcessDone
	case statusReleased:
		return errors.New("os: process already released")
	}
	defer .handleTransientRelease()

	return convertESRCH(unix.PidFDSendSignal(, ))
}

func pidfdWorks() bool {
	return checkPidfdOnce() == nil
}

var checkPidfdOnce = sync.OnceValue(checkPidfd)

// checkPidfd checks whether all required pidfd-related syscalls work.
// This consists of pidfd_open and pidfd_send_signal syscalls, and waitid
// syscall with idtype of P_PIDFD.
//
// Reasons for non-working pidfd syscalls include an older kernel and an
// execution environment in which the above system calls are restricted by
// seccomp or a similar technology.
func checkPidfd() error {
	// Get a pidfd of the current process (opening of "/proc/self" won't
	// work for waitid).
	,  := unix.PidFDOpen(syscall.Getpid(), 0)
	if  != nil {
		return NewSyscallError("pidfd_open", )
	}
	defer syscall.Close(int())

	// Check waitid(P_PIDFD) works.
	for {
		_, _,  = syscall.Syscall6(syscall.SYS_WAITID, _P_PIDFD, , 0, syscall.WEXITED, 0, 0)
		if  != syscall.EINTR {
			break
		}
	}
	// Expect ECHILD from waitid since we're not our own parent.
	if  != syscall.ECHILD {
		return NewSyscallError("pidfd_wait", )
	}

	// Check pidfd_send_signal works (should be able to send 0 to itself).
	if  := unix.PidFDSendSignal(, 0);  != nil {
		return NewSyscallError("pidfd_send_signal", )
	}

	return nil
}