diff --git a/dbus/methods.go b/dbus/methods.go index 6a0aa656..99abdbcc 100644 --- a/dbus/methods.go +++ b/dbus/methods.go @@ -288,6 +288,16 @@ func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) { return status, nil } +// GetUnitByPID returns the object path of the unit a process ID belongs to. +// The pid must refer to an existing process on the system. +func (c *Conn) GetUnitByPID(pid uint32) (dbus.ObjectPath, error) { + var op dbus.ObjectPath + if err := c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnitByPID", 0, uint(pid)).Store(&op); err != nil { + return dbus.ObjectPath(""), err + } + return op, nil +} + // ListUnits returns an array with all currently loaded units. Note that // units may be known by multiple names at the same time, and hence there might // be more unit names loaded than actual units behind them. diff --git a/dbus/methods_test.go b/dbus/methods_test.go index 061605bf..aae7aeb1 100644 --- a/dbus/methods_test.go +++ b/dbus/methods_test.go @@ -21,6 +21,7 @@ import ( "path" "path/filepath" "reflect" + "strings" "syscall" "testing" "time" @@ -1539,3 +1540,55 @@ func TestUnitName(t *testing.T) { } } } + +func assertNoError(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } +} + +func assertEqualStr(t *testing.T, shouldBe, target string) { + if target != shouldBe { + t.Fatalf("expected %q to equal %q", target, shouldBe) + } +} + +func TestGetUnitByPID(t *testing.T) { + target := "get-unit-pid.service" + conn := setupConn(t) + defer conn.Close() + + setupUnit(target, conn, t) + linkUnit(target, conn, t) + + reschan := make(chan string) + _, err := conn.StartUnit(target, "replace", reschan) + assertNoError(t, err) + + job := <-reschan + assertEqualStr(t, "done", job) + prop, err := conn.GetServiceProperty("get-unit-pid.service", "MainPID") + assertNoError(t, err) + + pid, ok := prop.Value.Value().(uint32) + if !ok { + t.Fatalf("expected MainPID to be uint32, got value %v of type %T", prop.Value, prop.Value) + } + if pid == 0 { + t.Fatal("expected MainPID to be greater than 0") + } + + objectPath, err := conn.GetUnitByPID(pid) + assertNoError(t, err) + if strings.HasSuffix(string(objectPath), "_2eslice") { + // in ubuntu:18.04 the top-level root slice container is the unit that gets returned + assertEqualStr(t, "/org/freedesktop/systemd1/unit/_2d_2eslice", string(objectPath)) + } else { + // otherwise, the service itself is returned + assertEqualStr(t, "/org/freedesktop/systemd1/unit/get_2dunit_2dpid_2eservice", string(objectPath)) + } + + _, err = conn.StopUnit(target, "replace", reschan) + assertNoError(t, err) + <-reschan +} diff --git a/fixtures/get-unit-pid.service b/fixtures/get-unit-pid.service new file mode 100644 index 00000000..63200947 --- /dev/null +++ b/fixtures/get-unit-pid.service @@ -0,0 +1,5 @@ +[Unit] +Description=get unit pid + +[Service] +ExecStart=/bin/sleep 400