Wayland interface, $XDG_RUNTIME_DIR and connecting clients to server

The context

Wayland has been specified with the assumption that clients and servers share the $XDG_RUNTIME_DIR belonging to the user.

The way Wayland uses $XDG_RUNTIME_DIR is for the server to place a file there that the client opens. This file could have any name, but by default it is wayland-0 (if that is in use it is wayland-1 etc.) or it can be specified by setting $WAYLAND_DISPLAY.

This use of environment variables doesn’t map well to the world of snaps as the client and server run in different snaps environments each with its own $XDG_RUNTIME_DIR. Specifically, $XDG_RUNTIME_DIR is set to a subdirectory of the “normal” location for it named after the snap.

“Speed bumps”

Making this work for Wayland client snaps is a poor experience. There (at least) are four scenarios:

  1. A kiosk or signage solution with the server and client snaps running as daemons
  2. A “desktop” client snap with a non-snap server both running as a user
  3. A “desktop” client snap with a snapped server both running as a user
  4. A “desktop” non-snap client with a snapped server both running as a user

Most of the work I’ve done falls into the first scenario, so I’ll describe how that works in detail.

1. server and client snaps running as daemons

We have adopted the convention that the server simply changes its $XDG_RUNTIME_DIR:

export XDG_RUNTIME_DIR=$(dirname $XDG_RUNTIME_DIR)

As it is running as a daemon this is actually /run/user/0 (which may not exist, so we create it). This works on Ubuntu Core (but currently only work on Ubuntu Classic by installing the server with --devmode as it cannot create this directory).

The client snaps need to do a corresponding “dance”. Either by changing its $XDG_RUNTIME_DIR or (recommended) by creating a link-file in its own $XDG_RUNTIME_DIR:

ln $(dirname $XDG_RUNTIME_DIR)/wayland-0 $XDG_RUNTIME_DIR

(This oversimplifies as it omits error handling.)

The need to get this right complicates the story for creating a client snap. We want this to be as simple as possible.

2. client snap with a non-snap server both running as a user

Because of the way that $XDG_RUNTIME_DIR is set, the same incantation should work (but the directory will be different - something like /run/user/1000).

3. A client snap with a snapped server both running as a user

Again, the “dance” works.

4. A non-snap client with a snapped server both running as a user

Again, the “dance” works.

The problem

Having to repeat this “dance” in every server snap and every client snap is tedious and error prone. It seems something where the “developer story” could be improved.

I’ve just tried this when running on Ubuntu Classic. It doesn’t work:

while ! ln -f $(dirname $XDG_RUNTIME_DIR)/wayland-0 $XDG_RUNTIME_DIR; do sleep 4; done
...
2019-05-14T17:24:00Z mir-kiosk-apps.mir-kiosk-app-daemon[17342]: ln: failed to create hard link '/run/user/0/snap.mir-kiosk-apps/wayland-0' => '/run/user/0/wayland-0': Permission denied
2019-05-14T17:24:04Z mir-kiosk-apps.mir-kiosk-app-daemon[17342]: ln: failed to create hard link '/run/user/0/snap.mir-kiosk-apps/wayland-0' => '/run/user/0/wayland-0': Permission denied

AppAmour:
[ 7329.880376] audit: type=1400 audit(1557902257.804:4895): apparmor=“DENIED” operation=“link” profile=“snap.mir-test-tools.bash” name="/run/user/0/snap.mir-test-tools/wayland-0" pid=24191 comm=“ln” requested_mask=“xm” denied_mask=“xm” fsuid=0 ouid=0 target="/run/user/0/wayland-0"

Random (but related) thought:

If, when starting the client, we could /1/ pass a file descriptor from the server, and /2/ set WAYLAND_SOCKET to the FD, then we wouldn’t need to mess with the file system.

And from snapd’s wayland interface:

const waylandConnectedPlugAppArmor = `
# Allow access to the Wayland compositor server socket
owner /run/user/[0-9]*/wayland-[0-9]* rw,

I think we need “l” in there.

Although, if instead I use:

    while ! ln -sf $(dirname $XDG_RUNTIME_DIR)/wayland-0 $XDG_RUNTIME_DIR; do sleep 4; done

the link succeeds, but I still get AppAmour denials:

[ 8505.872224] audit: type=1400 audit(1557904044.870:4946): apparmor=“DENIED” operation=“connect” profile=“snap.mir-kiosk-apps.mir-kiosk-app-daemon” name="/run/user/0/wayland-0" pid=26846 comm=“rssnews” requested_mask=“wr” denied_mask=“wr” fsuid=0 ouid=0

[edit]

Weirdly, if I sudo into mir-test-tools.bash I can use the symbolic link. But running as a daemon in mir-kiosk-apps doesn’t work. Not sure what’s going on here.

@jdstrand, naively, what if the wayland interface itself on the plug side created the symlink (based on the slot if the server is a snap too), and the appropriate apparmor hole?

Just for completeness, the client side “dance steps” are as follows:

#!/bin/sh

mkdir -p "$XDG_RUNTIME_DIR" -m 700

if [ -z "${WAYLAND_DISPLAY}" ]
then WAYLAND_DISPLAY=wayland-0
fi

real_wayland=$(dirname "$XDG_RUNTIME_DIR")/${WAYLAND_DISPLAY}
while [ ! -O "${real_wayland}" ]; do echo waiting for Wayland socket; sleep 4; done

ln -sf "${real_wayland}" "$XDG_RUNTIME_DIR"

...

(As seen in https://github.com/MirServer/mir-kiosk-kodi/blob/master/glue/bin/kodi-launch.sh)

And this “dance” has been incorporated with other conveniences: