This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-core/detach/process.go

118 lines
2.3 KiB
Go

package detach
import (
"fmt"
"os"
"sync"
"syscall"
"time"
)
const RingBufSize = 64 * 1024 // 64KB ring buffer per detached process
// Process represents a process that the agent has detached from
// but which continues running. The user can view its output and signal it.
type Process struct {
PID int
Command string
Started time.Time
Exited bool
ExitCode int
Ring *RingBuffer
Cmd *os.Process
Done chan struct{}
Mu sync.Mutex
}
// Info returns a plain-data snapshot of this process for external consumers.
type InfoData struct {
PID int
Command string
Started int64 // unix timestamp
Exited bool
ExitCode int
}
func (p *Process) Info() InfoData {
p.Mu.Lock()
defer p.Mu.Unlock()
return InfoData{
PID: p.PID,
Command: p.Command,
Started: p.Started.Unix(),
Exited: p.Exited,
ExitCode: p.ExitCode,
}
}
// Output returns the current contents of the ring buffer.
func (p *Process) Output() string {
p.Mu.Lock()
defer p.Mu.Unlock()
return p.Ring.String()
}
// Signal sends a signal to the detached process.
func (p *Process) Signal(sig syscall.Signal) error {
p.Mu.Lock()
defer p.Mu.Unlock()
if p.Exited {
return fmt.Errorf("process %d already exited", p.PID)
}
if p.Cmd == nil {
return fmt.Errorf("process %d has no handle", p.PID)
}
// Send to process group
return syscall.Kill(-p.PID, sig)
}
// ringBuffer is a fixed-size circular byte buffer.
type RingBuffer struct {
buf []byte
size int
pos int
full bool
}
func NewRingBuffer(size int) *RingBuffer {
return &RingBuffer{buf: make([]byte, size), size: size}
}
// Write implements io.Writer.
func (r *RingBuffer) Write(p []byte) (int, error) {
n := len(p)
if n >= r.size {
// Data larger than buffer: just keep the tail
copy(r.buf, p[n-r.size:])
r.pos = 0
r.full = true
return n, nil
}
if r.pos+n <= r.size {
copy(r.buf[r.pos:], p)
} else {
first := r.size - r.pos
copy(r.buf[r.pos:], p[:first])
copy(r.buf, p[first:])
r.full = true
}
r.pos = (r.pos + n) % r.size
if r.pos == 0 && n > 0 {
r.full = true
}
return n, nil
}
// String returns the buffer contents in order.
func (r *RingBuffer) String() string {
if !r.full {
return string(r.buf[:r.pos])
}
// Buffer has wrapped: data from pos..end + 0..pos
out := make([]byte, r.size)
copy(out, r.buf[r.pos:])
copy(out[r.size-r.pos:], r.buf[:r.pos])
return string(out)
}