74 lines
2.3 KiB
Go
74 lines
2.3 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
package lib
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os/exec"
|
|
"time"
|
|
|
|
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
|
|
"github.com/godbus/dbus/v5"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const cgroupCallTimeout = 1 * time.Second
|
|
|
|
func MaybeIsolateCgroup(cmd *exec.Cmd) (deleteFn func()) {
|
|
noop := func() {}
|
|
pid := cmd.Process.Pid
|
|
|
|
// 1. Connect to the user manager (no prompt on typical distros).
|
|
ctx, _ := context.WithTimeout(context.Background(), cgroupCallTimeout)
|
|
|
|
conn, err := systemdDbus.NewUserConnectionContext(ctx)
|
|
if err != nil {
|
|
log.Printf("⚠️ Could not connect to user systemd manager. No cgroup isolation for PID %d. Error: %v", pid, err)
|
|
return noop
|
|
}
|
|
// We'll keep 'conn' open while scope is active. The scope isn't strictly tied
|
|
// to the connection's lifetime, but it's nice to keep it in case we want to stop the unit.
|
|
|
|
scopeName := fmt.Sprintf("plandex-%s.scope", uuid.New().String())
|
|
|
|
props := []systemdDbus.Property{
|
|
systemdDbus.PropDescription("Plandex user-scope isolation"),
|
|
// Under system manager, user.slice means “treat it as a user process.”
|
|
// If this is truly in the user manager, it may ignore the slice or map it differently.
|
|
systemdDbus.PropSlice("user.slice"),
|
|
|
|
// KillMode=control-group: stopping the scope kills all processes in cgroup.
|
|
systemdDbus.Property{Name: "KillMode", Value: dbus.MakeVariant("control-group")},
|
|
|
|
// Attach the existing process by PID
|
|
systemdDbus.PropPids(uint32(pid)),
|
|
|
|
// Optional: auto-remove the scope once no processes remain.
|
|
systemdDbus.Property{Name: "CollectMode", Value: dbus.MakeVariant("inactive-or-failed")},
|
|
}
|
|
|
|
_, err = conn.StartTransientUnitContext(ctx, scopeName, "replace", props, nil)
|
|
if err != nil {
|
|
// Fallback, no isolation
|
|
log.Printf("⚠️ Failed to start transient scope for PID %d: %v", pid, err)
|
|
return noop
|
|
}
|
|
|
|
return func() {
|
|
// Close the connection to the user manager.
|
|
defer conn.Close()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), cgroupCallTimeout)
|
|
defer cancel()
|
|
|
|
// Attempt to stop the scope (killing all processes if any remain).
|
|
_, stopErr := conn.StopUnitContext(ctx, scopeName, "replace", nil)
|
|
if stopErr != nil {
|
|
log.Printf("⚠️ Failed to stop scope %s: %v", scopeName, stopErr)
|
|
}
|
|
}
|
|
}
|