118 lines
2.3 KiB
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)
|
|
}
|