Logind sessioins support

Didn't you notice that when PA runs in the default user mode, if you switched to another terminal "Ctrl+Alt+F2", the sound disappears?

More interestingly, if you login with your credentials, the sound returns back!

How is this achieved? It's done using a mix of facilities between logind and udev. This section inspects module-systemd-login for answers.

First, this module exists at src/modules/module-systemd-login.c. A small description exists about the module on top:

PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("Create a client for each login session of this user");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(true);

The first thing the module checks is whether systemd-logind is running correctly in the system:

    /* If we are not actually running logind become a NOP */
    if (access("/run/systemd/seats/", F_OK) < 0)
        return 0;

In the system I'm writing this manual on, the contents of this folder is:

$ ls /run/systemd/seats
seat0

$ cat /run/systemd/seats/seat0
# This is private data. Do not parse.
IS_SEAT0=1
CAN_MULTI_SESSION=1
CAN_TTY=1
CAN_GRAPHICAL=1
ACTIVE=c1
ACTIVE_UID=1000
SESSIONS=c2 c1
UIDS=1000 1000

Systemd logind is very useful for running rootless X servers (by passing file descriptors for system resources only for the active non-root session). It's also useful for organizing system seats and sessions in a sane manner, and insuring desktop programs clean state before shutdown using systemd inhibtion locks.

Both GNOME and KDE now use logind for session management. The Xorg server and Wayland implementations now use logind for rootless access to system devices using file descriptor passing. It has now become a core component of modern desktop Linux systems.

Summary of logind file descriptor passing

rootless sessions using systemd logind Image is courtesy of David Herrman.

Regarding audio, we have:

$ getfacl /dev/snd/controlC0

user::rw-
user:darwish:rw-
group::rw-
mask::rw-
other::---

PulseAudio detects the permission changes of the sound files and act accordingly:

D: [lt-pulseaudio] module-udev-detect.c: /dev/snd/controlC0 is accessible: yes
D: [lt-pulseaudio] module-udev-detect.c: /devices/pci0000:00/0000:00:14.2/sound/card0 is busy: no

# ... NOW, switch console using Ctrl+Alt+F3 ...

D: [lt-pulseaudio] module-udev-detect.c: /dev/snd/controlC0 is accessible: no
D: [lt-pulseaudio] module-udev-detect.c: Suspending all sinks and sources of card alsa_card.pci-0000_00_14.2.
D: [lt-pulseaudio] sink.c: Suspend cause of sink alsa_output.pci-0000_00_14.2.analog-stereo is 0x0008, suspending
I: [alsa-sink-VT1708S Analog] alsa-sink.c: Device suspended...

Back to code analysis

Now that the PA module is sure that a logind system is running, it asks logind for a notification handle:

    r = sd_login_monitor_new("session", &monitor);
    if (r < 0) {
        pa_log("Failed to create session monitor: %s", strerror(-r));
        goto fail;
    }

From the documentation of the sd_login_monitor_new API, we can that this PA module asks to be modified only about session changes. That is, when a session is being created, removed, or changed. It's not interested in any of other events that can be reported by logind, namely:

  • "seats", seats being added, removed, or changed
  • "uid", when a user changes login state, in or out
  • "machine", when a virtual machine is started or stopped

Tracking user sessions

Now that pulse has asked logind to be notified for any session created or freed, it prepares some book-keeping hashmap structures:

    u->sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session);
    u->previous_sessions = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) free_session);

This basically creates a HashMap containing current and previous sessions. A handler to free each hashmap entry is also registered.

Now the most important part of this module is integrating logind file descriptor events into PA event loop. This is done by:

    u->io = u->core->mainloop->io_new(u->core->mainloop, sd_login_monitor_get_fd(monitor), PA_IO_EVENT_INPUT, monitor_cb, u);

Per the documentation of the sd_login_monitor_get_fd API, this method call basically returns a file descriptor for the object to be monitored ("session" in our case). This file descriptor can then be integrated in the application event loop.

In the code snippet above, we can see PA actually integrating this file descriptor into its own event loop abstractions.

Logind session events handling

We can see from the PA event loop created in the earlier section that whenever logind sends a session event, PA calls the monitor_cb function. This function does the following:

static void monitor_cb(...) {
    struct userdata *u = userdata;
    pa_assert(u);

    sd_login_monitor_flush(u->monitor);
    get_session_list(u);
}

The first thing the event handler does is to call sd_login_monitor_flush(). This is mandated by the systemd-logind APIs and is used to reset the file descriptor wakeup state. Otherwise, we will wakeup again and again and again.

The next action is directly to query logind about the available user sessions in the system:

static int get_session_list(struct userdata *u) {
    r = sd_uid_get_sessions(getuid(), 0, &sessions);

    /* We copy all sessions that still exist from one hashmap to the
     * other and then flush the remaining ones */

    h = u->previous_sessions;
    u->previous_sessions = u->sessions;
    u->sessions = h;

    if (sessions) {
        char **s;
        for (s = sessions; *s; s++) {
            o = pa_hashmap_remove(u->previous_sessions, *s);
            if (o)
                pa_hashmap_put(u->sessions, o->id, o);
            else
                add_session(u, *s);

            free(*s);
        }
        free(sessions);
    }
    pa_hashmap_remove_all(u->previous_sessions);

    return 0;
}

What the above code basically does is:

  • Free any session that was previously tracked, but is now closed
  • Keep the currently-tracked sessions which are still on as is
  • Add PA clients for newly-discovered sessions

For us the most important is the third point. What does PA do for "adding" a session? This is answered by the following method:

static int add_session(struct userdata *u, const char *id) {
    struct session *session;
    pa_client_new_data data;

    session = pa_xnew(struct session, 1);
    session->id = pa_xstrdup(id);

    pa_client_new_data_init(&data);
    data.module = u->module;
    data.driver = __FILE__;
    pa_proplist_setf(data.proplist, PA_PROP_APPLICATION_NAME, "Login Session %s", id);
    pa_proplist_sets(data.proplist, "systemd-login.session", id);
    session->client = pa_client_new(u->core, &data);
    pa_client_new_data_done(&data);

    if (!session->client) {
        pa_xfree(session->id);
        pa_xfree(session);
        return -1;
    }

    pa_hashmap_put(u->sessions, session->id, session);

    pa_log_debug("Added new session %s", id);
    return 0;
}

The above basically creates a session object, and initializes it with the session id. It also initializes a data object containing the name of the application, and the name of the systemd login session in the abstract property of "systemd-login.session" and this module name.

After all of this comes the central part of this module, what is being done after knowing that a session has been created. After all the initializations are done above, the code simple calls:

session->client = pa_client_new(u->core, &data);

The above code simply creates a new PulseAudio "client". This is why we have in the PulseAudio log files statements like:

I: [lt-pulseaudio] client.c: Created 0 "Login Session c1"
...
I: [lt-pulseaudio] client.c: Created 34 "Login Session c2"

PulseAudio clients

As you can see from above, and in summary, PA creates a new client fore each added sessioin.

You can actually list all of PA clients in the system using the simple command:

$ pactl list clients

Client #0
    Driver: module-systemd-login.c
    Owner Module: 21
    Properties:
        application.name = "Login Session c1"
        systemd-login.session = "c1"

Client #1
    Driver: protocol-native.c
    Owner Module: 13
    Properties:
        application.name = "GNOME Shell Volume Control"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "30"
        application.id = "org.gnome.VolumeControl"
        application.icon_name = "multimedia-volume-control"
        application.version = "3.16.3"
        application.process.id = "408"
        application.process.user = "darwish"
        application.process.host = "darwish-pc"
        application.process.binary = "gnome-shell"
        application.language = "en_US.UTF-8"
        window.x11.display = ":0"
        application.process.machine_id = "35d8dc0072e14deb8664fd04562b1faa"
        application.process.session_id = "c1"

Client #2
    Driver: protocol-native.c
    Owner Module: 13
    Properties:
        application.name = "GNOME Volume Control Media Keys"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "30"
        application.id = "org.gnome.VolumeControl"
        application.icon_name = "multimedia-volume-control"
        application.version = "3.16.3"
        application.process.id = "350"
        application.process.user = "darwish"
        application.process.host = "darwish-pc"
        application.process.binary = "gnome-settings-daemon"
        application.language = "en_US.UTF-8"
        window.x11.display = ":0"
        application.process.machine_id = "35d8dc0072e14deb8664fd04562b1faa"
        application.process.session_id = "c1"

Client #6
    Driver: protocol-native.c
    Owner Module: 13
    Properties:
        application.name = "GNOME Shell"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "30"
        application.id = "org.gnome.Shell"
        application.icon_name = "start-here"
        application.language = "en_US.UTF-8"
        window.x11.screen = "0"
        window.x11.display = ":0"
        application.process.id = "408"
        application.process.user = "darwish"
        application.process.host = "darwish-pc"
        application.process.binary = "gnome-shell"
        application.process.machine_id = "35d8dc0072e14deb8664fd04562b1faa"
        application.process.session_id = "c1"

Client #9
    Driver: protocol-native.c
    Owner Module: 13
    Properties:
        application.name = "Firefox"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "30"
        application.icon_name = "firefox"
        application.version = "39.0.3"
        application.process.id = "884"
        application.process.user = "darwish"
        application.process.host = "darwish-pc"
        application.process.binary = "firefox"
        window.x11.display = ":0"
        application.language = "en_US.UTF-8"
        application.process.machine_id = "35d8dc0072e14deb8664fd04562b1faa"
        application.process.session_id = "c1"

Client #34
    Driver: module-systemd-login.c
    Owner Module: 21
    Properties:
        application.name = "Login Session c2"
        systemd-login.session = "c2"

Client #90
    Driver: protocol-native.c
    Owner Module: 13
    Properties:
        application.name = "gnome-clocks"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "30"
        application.id = "org.gnome.clocks"
        application.process.id = "974"
        application.process.user = "darwish"
        application.process.host = "darwish-pc"
        application.process.binary = "gnome-clocks"
        application.language = "en_US.UTF-8"
        window.x11.display = ":0"
        application.process.machine_id = "35d8dc0072e14deb8664fd04562b1faa"
        application.process.session_id = "c1"

Client #564
    Driver: protocol-native.c
    Owner Module: 13
    Properties:
        application.name = "pactl"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "30"
        application.process.id = "6690"
        application.process.user = "darwish"
        application.process.host = "darwish-pc"
        application.process.binary = "pactl"
        application.language = "en_US.UTF-8"
        window.x11.display = ":0"
        application.process.machine_id = "35d8dc0072e14deb8664fd04562b1faa"
        application.process.session_id = "c1"

Client #572
    Driver: protocol-native.c
    Owner Module: 13
    Properties:
        application.name = "Music"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "30"
        application.process.id = "6981"
        application.process.user = "darwish"
        application.process.host = "darwish-pc"
        application.process.binary = "python3.4"
        application.language = "en_US.UTF-8"
        window.x11.display = ":0"
        application.process.machine_id = "35d8dc0072e14deb8664fd04562b1faa"
        application.process.session_id = "c1"

Client #574
    Driver: protocol-native.c
    Owner Module: 13
    Properties:
        application.name = "pactl"
        native-protocol.peer = "UNIX socket client"
        native-protocol.version = "30"
        application.process.id = "7031"
        application.process.user = "darwish"
        application.process.host = "darwish-pc"
        application.process.binary = "pactl"
        application.language = "en_US.UTF-8"
        window.x11.display = ":0"
        application.process.machine_id = "35d8dc0072e14deb8664fd04562b1faa"
        application.process.session_id = "c1"

You can see from above that Client #0 and Client #34 are created for the sole purposes of currently active sessions in the system. Gnome shell have several clients registered, Firefox, gnome-clocks (for timer sounds), and GNOME official Music application. It's interesting that pactl also acts as a client.

Edit: Does pactl acts as systemd systemctl? In the case of systemd, systemctl connects with the systemd daemon over D-Bus. I guess in the case of pactl, the tool connects with PulseAudio using basic Unix sockets interface.

Question: If we transformed PA to kdbus, will we strip out the Unix domain transport and make all communication over kdbus?

Triggered actions upon session close

Upon session close, logind sends an event over the logind monitor file descriptor. This fd is integrated within PA event loop, so the event is detected and handled. PA queries logind of all session, puts all the now-stopped in their own hashmaap, and frees the hashmp. Freeing the hashmap triggers below method for each node:

static void free_session(struct session *session) {
    pa_assert(session);

    pa_log_debug("Removing session %s", session->id);

    pa_client_free(session->client);
    pa_xfree(session->id);
    pa_xfree(session);
}

As seen from above, this basically removes the client which PA has created for the now-nonexistent dessions. This is why when we logout from session c2, we get the following from PA logs:

D: [lt-pulseaudio] module-systemd-login.c: Removing session c2
I: [lt-pulseaudio] client.c: Freed 34 "Login Session c2"

And now we can see that Client #34, created for login session c2, has been freed.

References

results matching ""

    No results matching ""