Spaces:
Paused
Paused
| //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) | |
| } | |
| } | |
| } | |