Skip to content

List users

Source · io.systemd.UserDatabase · systemd v257+ · Unprivileged

userdbctl user in Go. systemd-userdbd's multiplexer merges every user source on the system (/etc/passwd, systemd-homed, NSS, ...) and, with Varlink's more flag, streams one reply per user. There is no D-Bus equivalent.

Run it

go run ./examples/list-users
go run ./examples/list-users -user root
Output (illustrative)
NAME             UID    DISPOSITION  HOME               REAL NAME
root             0      intrinsic    /root              Super User
nobody           65534  intrinsic    /                  Kernel Overflow User
systemd-resolve  991    system       /                  systemd Resolver
ross             1000   regular      /home/ross         Ross Edman

The code

examples/list-users/main.go
// Command list-users streams every user record from systemd's userdb
// multiplexer, like `userdbctl user`.
//
//  go run ./examples/list-users
//  go run ./examples/list-users -user root
package main

import (
    "context"
    "encoding/json"
    "flag"
    "fmt"
    "log"
    "os"
    "text/tabwriter"

    "github.com/rossedman/sdvarlink/systemd/v257/userdatabase"
    "github.com/rossedman/sdvarlink/varlink"
)

// userRecord picks a few fields from the JSON user record format
// (https://systemd.io/USER_RECORD/).
type userRecord struct {
    UserName      string `json:"userName"`
    UID           int    `json:"uid"`
    RealName      string `json:"realName"`
    HomeDirectory string `json:"homeDirectory"`
    Disposition   string `json:"disposition"`
}

func main() {
    user := flag.String("user", "", "look up a single user")
    flag.Parse()
    ctx := context.Background()

    c, err := userdatabase.Dial(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()

    in := userdatabase.GetUserRecordIn{Service: "io.systemd.Multiplexer"}
    w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
    fmt.Fprintln(w, "NAME\tUID\tDISPOSITION\tHOME\tREAL NAME")
    defer w.Flush()

    if *user != "" {
        in.UserName = varlink.Ptr(*user)
        out, err := c.GetUserRecord(ctx, in)
        if err != nil {
            log.Fatal(err)
        }
        print(w, out.Record)
        return
    }

    // With "more", the multiplexer streams one reply per user.
    for out, err := range c.GetUserRecordMore(ctx, in) {
        if err != nil {
            log.Fatal(err)
        }
        print(w, out.Record)
    }
}

func print(w *tabwriter.Writer, raw json.RawMessage) {
    var u userRecord
    if err := json.Unmarshal(raw, &u); err != nil {
        log.Fatal(err)
    }
    fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", u.UserName, u.UID, u.Disposition, u.HomeDirectory, u.RealName)
}

How it works

  • Supports-more. GetUserRecord is annotated [Supports 'more' flag], so the package has both GetUserRecord (one reply, used with -user) and GetUserRecordMore (an iter.Seq2 of every record).
  • Service: "io.systemd.Multiplexer" is required: it tells userdbd to query all sources.
  • The record is JSON. Record is json.RawMessage holding a full JSON user record; the example decodes a few fields into its own struct.
  • varlink.Ptr builds the optional UserName filter.

See also