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

package os

import (
	"errors"
	"internal/testlog"
	"runtime"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

// ErrProcessDone indicates a [Process] has finished.
var ErrProcessDone = errors.New("os: process already finished")

type processMode uint8

const (
	// modePID means that Process operations such use the raw PID from the
	// Pid field. handle is not used.
	//
	// This may be due to the host not supporting handles, or because
	// Process was created as a literal, leaving handle unset.
	//
	// This must be the zero value so Process literals get modePID.
	modePID processMode = iota

	// modeHandle means that Process operations use handle, which is
	// initialized with an OS process handle.
	//
	// Note that Release and Wait will deactivate and eventually close the
	// handle, so acquire may fail, indicating the reason.
	modeHandle
)

type processStatus uint64

const (
	// PID/handle OK to use.
	statusOK processStatus = 0

	// statusDone indicates that the PID/handle should not be used because
	// the process is done (has been successfully Wait'd on).
	statusDone processStatus = 1 << 62

	// statusReleased indicates that the PID/handle should not be used
	// because the process is released.
	statusReleased processStatus = 1 << 63

	processStatusMask = 0x3 << 62
)

// Process stores the information about a process created by [StartProcess].
type Process struct {
	Pid int

	mode processMode

	// State contains the atomic process state.
	//
	// In modePID, this consists only of the processStatus fields, which
	// indicate if the process is done/released.
	//
	// In modeHandle, the lower bits also contain a reference count for the
	// handle field.
	//
	// The Process itself initially holds 1 persistent reference. Any
	// operation that uses the handle with a system call temporarily holds
	// an additional transient reference. This prevents the handle from
	// being closed prematurely, which could result in the OS allocating a
	// different handle with the same value, leading to Process' methods
	// operating on the wrong process.
	//
	// Release and Wait both drop the Process' persistent reference, but
	// other concurrent references may delay actually closing the handle
	// because they hold a transient reference.
	//
	// Regardless, we want new method calls to immediately treat the handle
	// as unavailable after Release or Wait to avoid extending this delay.
	// This is achieved by setting either processStatus flag when the
	// Process' persistent reference is dropped. The only difference in the
	// flags is the reason the handle is unavailable, which affects the
	// errors returned by concurrent calls.
	state atomic.Uint64

	// Used only in modePID.
	sigMu sync.RWMutex // avoid race between wait and signal

	// handle is the OS handle for process actions, used only in
	// modeHandle.
	//
	// handle must be accessed only via the handleTransientAcquire method
	// (or during closeHandle), not directly! handle is immutable.
	//
	// On Windows, it is a handle from OpenProcess.
	// On Linux, it is a pidfd.
	// It is unused on other GOOSes.
	handle uintptr
}

func newPIDProcess(pid int) *Process {
	p := &Process{
		Pid:  pid,
		mode: modePID,
	}
	runtime.SetFinalizer(p, (*Process).Release)
	return p
}

func newHandleProcess(pid int, handle uintptr) *Process {
	p := &Process{
		Pid:    pid,
		mode:   modeHandle,
		handle: handle,
	}
	p.state.Store(1) // 1 persistent reference
	runtime.SetFinalizer(p, (*Process).Release)
	return p
}

func newDoneProcess(pid int) *Process {
	p := &Process{
		Pid:  pid,
		mode: modeHandle,
		// N.B Since we set statusDone, handle will never actually be
		// used, so its value doesn't matter.
	}
	p.state.Store(uint64(statusDone)) // No persistent reference, as there is no handle.
	runtime.SetFinalizer(p, (*Process).Release)
	return p
}

func (p *Process) handleTransientAcquire() (uintptr, processStatus) {
	if p.mode != modeHandle {
		panic("handleTransientAcquire called in invalid mode")
	}

	for {
		refs := p.state.Load()
		if refs&processStatusMask != 0 {
			return 0, processStatus(refs & processStatusMask)
		}
		new := refs + 1
		if !p.state.CompareAndSwap(refs, new) {
			continue
		}
		return p.handle, statusOK
	}
}

func (p *Process) handleTransientRelease() {
	if p.mode != modeHandle {
		panic("handleTransientRelease called in invalid mode")
	}

	for {
		state := p.state.Load()
		refs := state &^ processStatusMask
		status := processStatus(state & processStatusMask)
		if refs == 0 {
			// This should never happen because
			// handleTransientRelease is always paired with
			// handleTransientAcquire.
			panic("release of handle with refcount 0")
		}
		if refs == 1 && status == statusOK {
			// Process holds a persistent reference and always sets
			// a status when releasing that reference
			// (handlePersistentRelease). Thus something has gone
			// wrong if this is the last release but a status has
			// not always been set.
			panic("final release of handle without processStatus")
		}
		new := state - 1
		if !p.state.CompareAndSwap(state, new) {
			continue
		}
		if new&^processStatusMask == 0 {
			p.closeHandle()
		}
		return
	}
}

// Drop the Process' persistent reference on the handle, deactivating future
// Wait/Signal calls with the passed reason.
//
// Returns the status prior to this call. If this is not statusOK, then the
// reference was not dropped or status changed.
func (p *Process) handlePersistentRelease(reason processStatus) processStatus {
	if p.mode != modeHandle {
		panic("handlePersistentRelease called in invalid mode")
	}

	for {
		refs := p.state.Load()
		status := processStatus(refs & processStatusMask)
		if status != statusOK {
			// Both Release and successful Wait will drop the
			// Process' persistent reference on the handle. We
			// can't allow concurrent calls to drop the reference
			// twice, so we use the status as a guard to ensure the
			// reference is dropped exactly once.
			return status
		}
		if refs == 0 {
			// This should never happen because dropping the
			// persistent reference always sets a status.
			panic("release of handle with refcount 0")
		}
		new := (refs - 1) | uint64(reason)
		if !p.state.CompareAndSwap(refs, new) {
			continue
		}
		if new&^processStatusMask == 0 {
			p.closeHandle()
		}
		return status
	}
}

func (p *Process) pidStatus() processStatus {
	if p.mode != modePID {
		panic("pidStatus called in invalid mode")
	}

	return processStatus(p.state.Load())
}

func (p *Process) pidDeactivate(reason processStatus) {
	if p.mode != modePID {
		panic("pidDeactivate called in invalid mode")
	}

	// Both Release and successful Wait will deactivate the PID. Only one
	// of those should win, so nothing left to do here if the compare
	// fails.
	//
	// N.B. This means that results can be inconsistent. e.g., with a
	// racing Release and Wait, Wait may successfully wait on the process,
	// returning the wait status, while future calls error with "process
	// released" rather than "process done".
	p.state.CompareAndSwap(0, uint64(reason))
}

// ProcAttr holds the attributes that will be applied to a new process
// started by StartProcess.
type ProcAttr struct {
	// If Dir is non-empty, the child changes into the directory before
	// creating the process.
	Dir string
	// If Env is non-nil, it gives the environment variables for the
	// new process in the form returned by Environ.
	// If it is nil, the result of Environ will be used.
	Env []string
	// Files specifies the open files inherited by the new process. The
	// first three entries correspond to standard input, standard output, and
	// standard error. An implementation may support additional entries,
	// depending on the underlying operating system. A nil entry corresponds
	// to that file being closed when the process starts.
	// On Unix systems, StartProcess will change these File values
	// to blocking mode, which means that SetDeadline will stop working
	// and calling Close will not interrupt a Read or Write.
	Files []*File

	// Operating system-specific process creation attributes.
	// Note that setting this field means that your program
	// may not execute properly or even compile on some
	// operating systems.
	Sys *syscall.SysProcAttr
}

// A Signal represents an operating system signal.
// The usual underlying implementation is operating system-dependent:
// on Unix it is syscall.Signal.
type Signal interface {
	String() string
	Signal() // to distinguish from other Stringers
}

// Getpid returns the process id of the caller.
func Getpid() int { return syscall.Getpid() }

// Getppid returns the process id of the caller's parent.
func Getppid() int { return syscall.Getppid() }

// FindProcess looks for a running process by its pid.
//
// The [Process] it returns can be used to obtain information
// about the underlying operating system process.
//
// On Unix systems, FindProcess always succeeds and returns a Process
// for the given pid, regardless of whether the process exists. To test whether
// the process actually exists, see whether p.Signal(syscall.Signal(0)) reports
// an error.
func FindProcess(pid int) (*Process, error) {
	return findProcess(pid)
}

// StartProcess starts a new process with the program, arguments and attributes
// specified by name, argv and attr. The argv slice will become [os.Args] in the
// new process, so it normally starts with the program name.
//
// If the calling goroutine has locked the operating system thread
// with [runtime.LockOSThread] and modified any inheritable OS-level
// thread state (for example, Linux or Plan 9 name spaces), the new
// process will inherit the caller's thread state.
//
// StartProcess is a low-level interface. The [os/exec] package provides
// higher-level interfaces.
//
// If there is an error, it will be of type [*PathError].
func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
	testlog.Open(name)
	return startProcess(name, argv, attr)
}

// Release releases any resources associated with the [Process] p,
// rendering it unusable in the future.
// Release only needs to be called if [Process.Wait] is not.
func (p *Process) Release() error {
	// Note to future authors: the Release API is cursed.
	//
	// On Unix and Plan 9, Release sets p.Pid = -1. This is the only part of the
	// Process API that is not thread-safe, but it can't be changed now.
	//
	// On Windows, Release does _not_ modify p.Pid.
	//
	// On Windows, Wait calls Release after successfully waiting to
	// proactively clean up resources.
	//
	// On Unix and Plan 9, Wait also proactively cleans up resources, but
	// can not call Release, as Wait does not set p.Pid = -1.
	//
	// On Unix and Plan 9, calling Release a second time has no effect.
	//
	// On Windows, calling Release a second time returns EINVAL.
	return p.release()
}

// Kill causes the [Process] to exit immediately. Kill does not wait until
// the Process has actually exited. This only kills the Process itself,
// not any other processes it may have started.
func (p *Process) Kill() error {
	return p.kill()
}

// Wait waits for the [Process] to exit, and then returns a
// ProcessState describing its status and an error, if any.
// Wait releases any resources associated with the Process.
// On most operating systems, the Process must be a child
// of the current process or an error will be returned.
func (p *Process) Wait() (*ProcessState, error) {
	return p.wait()
}

// Signal sends a signal to the [Process].
// Sending [Interrupt] on Windows is not implemented.
func (p *Process) Signal(sig Signal) error {
	return p.signal(sig)
}

// UserTime returns the user CPU time of the exited process and its children.
func (p *ProcessState) UserTime() time.Duration {
	return p.userTime()
}

// SystemTime returns the system CPU time of the exited process and its children.
func (p *ProcessState) SystemTime() time.Duration {
	return p.systemTime()
}

// Exited reports whether the program has exited.
// On Unix systems this reports true if the program exited due to calling exit,
// but false if the program terminated due to a signal.
func (p *ProcessState) Exited() bool {
	return p.exited()
}

// Success reports whether the program exited successfully,
// such as with exit status 0 on Unix.
func (p *ProcessState) Success() bool {
	return p.success()
}

// Sys returns system-dependent exit information about
// the process. Convert it to the appropriate underlying
// type, such as [syscall.WaitStatus] on Unix, to access its contents.
func (p *ProcessState) Sys() any {
	return p.sys()
}

// SysUsage returns system-dependent resource usage information about
// the exited process. Convert it to the appropriate underlying
// type, such as [*syscall.Rusage] on Unix, to access its contents.
// (On Unix, *syscall.Rusage matches struct rusage as defined in the
// getrusage(2) manual page.)
func (p *ProcessState) SysUsage() any {
	return p.sysUsage()
}
