diff --git a/dbus/methods.go b/dbus/methods.go index a64f0b3e..c0d78290 100644 --- a/dbus/methods.go +++ b/dbus/methods.go @@ -870,6 +870,43 @@ func (c *Conn) ThawUnit(ctx context.Context, unit string) error { return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ThawUnit", 0, unit).Store() } +// Process models a process (PID) contained in a CGroup. +type Process struct { + // Path is where the process exists in the unit/cgroup hierarchy. + Path string + // PID is the numeric process ID. + PID uint64 + // Command is the process command and arguments. + Command string +} + +// GetUnitProcesses returns an array with all currently running processes in a unit, including its child units. +func (c *Conn) GetUnitProcesses(ctx context.Context, unit string) ([]Process, error) { + // switch to storeSlice[Process] once available + result := make([][]any, 0) + + if err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.GetUnitProcesses", 0, unit).Store(&result); err != nil { + return nil, err + } + + resultInterface := make([]any, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + process := make([]Process, len(result)) + processInterface := make([]any, len(process)) + for i := range process { + processInterface[i] = &process[i] + } + + if err := dbus.Store(resultInterface, processInterface...); err != nil { + return nil, err + } + + return process, nil +} + // AttachProcessesToUnit moves existing processes, identified by pids, into an existing systemd unit. func (c *Conn) AttachProcessesToUnit(ctx context.Context, unit, subcgroup string, pids []uint32) error { return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.AttachProcessesToUnit", 0, unit, subcgroup, pids).Store() diff --git a/dbus/methods_test.go b/dbus/methods_test.go index fd6d7854..39f5ef85 100644 --- a/dbus/methods_test.go +++ b/dbus/methods_test.go @@ -1749,3 +1749,48 @@ func TestAttachProcessesToUnit(t *testing.T) { func TestAttachProcessesToUnitWithSubcgroup(t *testing.T) { testAttachProcessesToUnit(t, "/test-subcgroup") } + +func TestListUnitProcesses(t *testing.T) { + ctx := context.Background() + target := "list-me.service" + + conn := setupConn(t) + defer conn.Close() + + setupUnit(target, conn, t) + linkUnit(target, conn, t) + + reschan := make(chan string) + _, err := conn.StartUnitContext(ctx, target, "replace", reschan) + if err != nil { + t.Fatal(err) + } + defer func() { + err := runStopUnit(t, conn, TrUnitProp{target, nil}) + if err != nil { + t.Error("StopUnit:", err) + } + }() + + job := <-reschan + if job != "done" { + t.Fatal("Job is not done, status:", job) + } + + processes, err := conn.GetUnitProcesses(ctx, target) + if err != nil { + e, ok := err.(dbus.Error) + if ok && (e.Name == "org.freedesktop.DBus.Error.UnknownMethod" || e.Name == "org.freedesktop.DBus.Error.NotSupported") { + t.SkipNow() + } + t.Fatalf("failed to list processes of %s: %s", target, err) + } + + for _, p := range processes { + t.Logf("Found %v.\n", p) + return + } + + t.Log("processes:", processes) + t.Errorf("Nothing was found in %s unit's process list.", target) +} diff --git a/fixtures/list-me.service b/fixtures/list-me.service new file mode 100644 index 00000000..7c9ead40 --- /dev/null +++ b/fixtures/list-me.service @@ -0,0 +1,5 @@ +[Unit] +Description=GetUnitProcesses test + +[Service] +ExecStart=/bin/sleep 400