diff --git a/login1/dbus.go b/login1/dbus.go index 1fe5e26..613db0d 100644 --- a/login1/dbus.go +++ b/login1/dbus.go @@ -26,9 +26,11 @@ import ( ) const ( - dbusDest = "org.freedesktop.login1" - dbusInterface = "org.freedesktop.login1.Manager" - dbusPath = "/org/freedesktop/login1" + dbusDest = "org.freedesktop.login1" + dbusManagerInterface = "org.freedesktop.login1.Manager" + dbusSessionInterface = "org.freedesktop.login1.Session" + dbusUserInterface = "org.freedesktop.login1.User" + dbusPath = "/org/freedesktop/login1" ) // Conn is a connection to systemds dbus endpoint. @@ -166,7 +168,7 @@ func userFromInterfaces(user []interface{}) (*User, error) { // GetActiveSession may be used to get the session object path for the current active session func (c *Conn) GetActiveSession() (dbus.ObjectPath, error) { var seat0Path dbus.ObjectPath - if err := c.object.Call(dbusInterface+".GetSeat", 0, "seat0").Store(&seat0Path); err != nil { + if err := c.object.Call(dbusManagerInterface+".GetSeat", 0, "seat0").Store(&seat0Path); err != nil { return "", err } @@ -242,7 +244,7 @@ func (c *Conn) GetSessionDisplay(sessionPath dbus.ObjectPath) (string, error) { // GetSession may be used to get the session object path for the session with the specified ID. func (c *Conn) GetSession(id string) (dbus.ObjectPath, error) { var out interface{} - if err := c.object.Call(dbusInterface+".GetSession", 0, id).Store(&out); err != nil { + if err := c.object.Call(dbusManagerInterface+".GetSession", 0, id).Store(&out); err != nil { return "", err } @@ -262,7 +264,7 @@ func (c *Conn) ListSessions() ([]Session, error) { // ListSessionsContext returns an array with all current sessions. func (c *Conn) ListSessionsContext(ctx context.Context) ([]Session, error) { out := [][]interface{}{} - if err := c.object.CallWithContext(ctx, dbusInterface+".ListSessions", 0).Store(&out); err != nil { + if err := c.object.CallWithContext(ctx, dbusManagerInterface+".ListSessions", 0).Store(&out); err != nil { return nil, err } @@ -285,7 +287,7 @@ func (c *Conn) ListUsers() ([]User, error) { // ListUsersContext returns an array with all currently logged-in users. func (c *Conn) ListUsersContext(ctx context.Context) ([]User, error) { out := [][]interface{}{} - if err := c.object.CallWithContext(ctx, dbusInterface+".ListUsers", 0).Store(&out); err != nil { + if err := c.object.CallWithContext(ctx, dbusManagerInterface+".ListUsers", 0).Store(&out); err != nil { return nil, err } @@ -300,36 +302,56 @@ func (c *Conn) ListUsersContext(ctx context.Context) ([]User, error) { return ret, nil } +// GetSessionPropertiesContext takes a session path and returns all of its dbus object properties. +func (c *Conn) GetSessionPropertiesContext(ctx context.Context, sessionPath dbus.ObjectPath) (map[string]dbus.Variant, error) { + return c.getProperties(ctx, sessionPath, dbusSessionInterface) +} + +// GetSessionPropertyContext takes a session path and a property name and returns the property value. +func (c *Conn) GetSessionPropertyContext(ctx context.Context, sessionPath dbus.ObjectPath, property string) (*dbus.Variant, error) { + return c.getProperty(ctx, sessionPath, dbusSessionInterface, property) +} + +// GetUserPropertiesContext takes a user path and returns all of its dbus object properties. +func (c *Conn) GetUserPropertiesContext(ctx context.Context, userPath dbus.ObjectPath) (map[string]dbus.Variant, error) { + return c.getProperties(ctx, userPath, dbusUserInterface) +} + +// GetUserPropertyContext takes a user path and a property name and returns the property value. +func (c *Conn) GetUserPropertyContext(ctx context.Context, userPath dbus.ObjectPath, property string) (*dbus.Variant, error) { + return c.getProperty(ctx, userPath, dbusUserInterface, property) +} + // LockSession asks the session with the specified ID to activate the screen lock. func (c *Conn) LockSession(id string) { - c.object.Call(dbusInterface+".LockSession", 0, id) + c.object.Call(dbusManagerInterface+".LockSession", 0, id) } // LockSessions asks all sessions to activate the screen locks. This may be used to lock any access to the machine in one action. func (c *Conn) LockSessions() { - c.object.Call(dbusInterface+".LockSessions", 0) + c.object.Call(dbusManagerInterface+".LockSessions", 0) } // TerminateSession forcibly terminate one specific session. func (c *Conn) TerminateSession(id string) { - c.object.Call(dbusInterface+".TerminateSession", 0, id) + c.object.Call(dbusManagerInterface+".TerminateSession", 0, id) } // TerminateUser forcibly terminates all processes of a user. func (c *Conn) TerminateUser(uid uint32) { - c.object.Call(dbusInterface+".TerminateUser", 0, uid) + c.object.Call(dbusManagerInterface+".TerminateUser", 0, uid) } // Reboot asks logind for a reboot optionally asking for auth. func (c *Conn) Reboot(askForAuth bool) { - c.object.Call(dbusInterface+".Reboot", 0, askForAuth) + c.object.Call(dbusManagerInterface+".Reboot", 0, askForAuth) } // Inhibit takes inhibition lock in logind. func (c *Conn) Inhibit(what, who, why, mode string) (*os.File, error) { var fd dbus.UnixFD - err := c.object.Call(dbusInterface+".Inhibit", 0, what, who, why, mode).Store(&fd) + err := c.object.Call(dbusManagerInterface+".Inhibit", 0, what, who, why, mode).Store(&fd) if err != nil { return nil, err } @@ -350,5 +372,37 @@ func (c *Conn) Subscribe(members ...string) chan *dbus.Signal { // PowerOff asks logind for a power off optionally asking for auth. func (c *Conn) PowerOff(askForAuth bool) { - c.object.Call(dbusInterface+".PowerOff", 0, askForAuth) + c.object.Call(dbusManagerInterface+".PowerOff", 0, askForAuth) +} + +func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]dbus.Variant, error) { + if !path.IsValid() { + return nil, fmt.Errorf("invalid object path (%s)", path) + } + + obj := c.conn.Object(dbusDest, path) + + var props map[string]dbus.Variant + err := obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props) + if err != nil { + return nil, err + } + + return props, nil +} + +func (c *Conn) getProperty(ctx context.Context, path dbus.ObjectPath, dbusInterface, property string) (*dbus.Variant, error) { + if !path.IsValid() { + return nil, fmt.Errorf("invalid object path (%s)", path) + } + + obj := c.conn.Object(dbusDest, path) + + var prop dbus.Variant + err := obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, dbusInterface, property).Store(&prop) + if err != nil { + return nil, err + } + + return &prop, nil } diff --git a/login1/dbus_test.go b/login1/dbus_test.go index b570c92..0c1ffe7 100644 --- a/login1/dbus_test.go +++ b/login1/dbus_test.go @@ -15,10 +15,12 @@ package login1 import ( + "context" "fmt" "os/user" "regexp" "testing" + "time" ) // TestNew ensures that New() works without errors. @@ -87,3 +89,105 @@ func TestListUsers(t *testing.T) { } } } + +func TestConn_GetSessionPropertiesContext(t *testing.T) { + c, err := New() + if err != nil { + t.Fatal(err) + } + + sessions, err := c.ListSessions() + if err != nil { + t.Fatal(err) + } + + for _, s := range sessions { + func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + props, err := c.GetSessionPropertiesContext(ctx, s.Path) + if err != nil { + t.Fatal(err) + } + if len(props) == 0 { + t.Fatal("no properties returned") + } + }() + } +} + +func TestConn_GetSessionPropertyContext(t *testing.T) { + c, err := New() + if err != nil { + t.Fatal(err) + } + + sessions, err := c.ListSessions() + if err != nil { + t.Fatal(err) + } + + for _, s := range sessions { + func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + _, err := c.GetSessionPropertyContext(ctx, s.Path, "Remote") + if err != nil { + t.Fatal(err) + } + }() + } +} + +func TestConn_GetUserPropertiesContext(t *testing.T) { + c, err := New() + if err != nil { + t.Fatal(err) + } + + users, err := c.ListUsers() + if err != nil { + t.Fatal(err) + } + + for _, u := range users { + func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + props, err := c.GetUserPropertiesContext(ctx, u.Path) + if err != nil { + t.Fatal(err) + } + if len(props) == 0 { + t.Fatal("no properties returned") + } + }() + } +} + +func TestConn_GetUserPropertyContext(t *testing.T) { + c, err := New() + if err != nil { + t.Fatal(err) + } + + users, err := c.ListUsers() + if err != nil { + t.Fatal(err) + } + + for _, u := range users { + func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + _, err := c.GetUserPropertyContext(ctx, u.Path, "State") + if err != nil { + t.Fatal(err) + } + }() + } +}