Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No access to xserver when started by udev #81

Closed
timokau opened this issue Oct 13, 2017 · 12 comments
Closed

No access to xserver when started by udev #81

timokau opened this issue Oct 13, 2017 · 12 comments

Comments

@timokau
Copy link
Contributor

timokau commented Oct 13, 2017

When autorandr gets triggered by udev, I get the following logs:

systemd[1]: Starting autorandr execution hook...
autorandr[1776]: No protocol specified
autorandr[1776]: Can't open display :0
autorandr[1776]: Failed to run xrandr (line 439)
autorandr[1776]: Running autorandr as timo for display :0

Looking through the autorandr source, Failed to run xrandr seems to be a misleading error message that is caused by xrandrs stdout being empty (but not it's stdr, Can't open display :0).

When I disable x access control with xhost +, everything works fine.
I'm assuming the problem is that autorandr doesn't get the XAUTHORITY variable via udev.
However, I tried to add Environment=XAUTHORITY=/home/timo/.Xauthority to the service file and `ENV{XAUTHORITY}="/home/timo/.Xauthority" to the udev rule, without success.

As an aside, why does autorandr use an system wide service file instead of systemd-user?

@blueyed
Copy link
Contributor

blueyed commented Oct 13, 2017

why does autorandr use an system wide service file instead of systemd-user?

Is it possible to have something for this by default per user then?

@timokau
Copy link
Contributor Author

timokau commented Oct 14, 2017

I'm not sure what you mean. I'm talking about systemd user units.

@phillipberndt
Copy link
Owner

When autorandr gets triggered by udev, I get the following logs:

This seems to be correct. No need to worry about the first two errors, they mean that the instance run as root does not have access to the display. The last line is the important one: It states that the global (batch) instance of autorandr forks a new instance of autorandr, running as your user, and that copy will have XAUTHORITY set properly (if it didn't, you'd see another error message from that copy).

..or do you have reason to believe that autorandr doesn't work?

As an aside, why does autorandr use an system wide service file instead of systemd-user?

For two reasons, (1) I don't know whether user instances of systemd trigger sleep.target, (2) it's much easier to invoke a system service from udev than to invoke any existing user instance that might have a unit named autorandr.service.

@timokau
Copy link
Contributor Author

timokau commented Oct 14, 2017

the global (batch) instance of autorandr forks a new instance of autorandr
Why does it just do this after first triggering the error?

..or do you have reason to believe that autorandr doesn't work?

Yes, it doesn't apply the profile after those logs are shown. If I manually run autorandr -c however, it does.

(1) I don't know whether user instances of systemd trigger sleep.target

I'm pretty sure systemd-user works with the same targets as the global systemd.

(2) it's much easier to invoke a system service from udev than to invoke any existing user instance that might have a unit named autorandr.service.

That's true. I did a little research how you could acomplish this, and I came up with a kind-of-but-not-quite working solution:

  • Having this autorandr.service file as a systemd-user service:
[Service]
Type=oneshot
ExecStart=/usr/bin/autorandr -c
  • Having this udev rule, using the horribly underdocumented SYSTEMD_USER_WANTS (see man systemd.device):
ACTION=="change", SUBSYSTEM=="drm", TAG+="systemd", ENV{SYSTEMD_USER_WANTS}="autorandr.service"

Now for some reason that works the first time I plug in the monitor after a reboot, but not after that.

It might have something to do with the following part of the documentation:

Note that systemd will only act on Wants dependencies when a device first becomes active. It will not act on them if they are added to devices that are already active. Use SYSTEMD_READY= (see below) to influence on which udev event to trigger the dependencies.

But I'm not quite sure what that means. I tried asking in the systemd IRC, but didn't get an answer yet.

@phillipberndt
Copy link
Owner

phillipberndt commented Oct 14, 2017

Yes, it doesn't apply the profile after those logs are shown.

Hmm ok. Please add some debug output to autorandr:

  • After
    print("Running autorandr as %s for display %s" % (pwent.pw_name, display))
    add
    print(process_environ)
    and check whether XAUTHORITY/DISPLAY are set correctly
  • Before
    if "--batch" in options:
    in the main function, add
    print("autorandr started as uid %d" % os.getuid())
  • Also, add --debug to the command line in the systemd unit

This should generate enough info to see what's going on, hopefully.

I'm pretty sure systemd-user works with the same targets as the global systemd.

Just found this as the first result on google. Might have changed in the meantime, of course.

.. SYSTEMD_USER_WANTS ..

That's a really nice feature, and it would be a great solution, but - alas - it won't work. See here, where we found exactly what you did find, too - this only works once.

(A workaround that might work would be to make the systemd unit a service instead of a OneShot script, but write it such that it cleanly stops itself shortly after invocation. Maybe systemd is intelligent enough to see "oh, it's a service, I should restart it because this new device needs it"?!)

@timokau
Copy link
Contributor Author

timokau commented Oct 14, 2017

Hmm ok. Please add some debug output to autorandr:

I did that, and for some inexplicable reason it just works now. After switching back to the "vanilla" version, it still works.

I'm sorry for bothering you with this, it was probably not working for some other reason. Thanks for the support.

Just found this as the first result on google. Might have changed in the meantime, of course.

Looks like you're right, and thats still the case:

That's a really nice feature, and it would be a great solution, but - alas - it won't work.

We could ask on the systemd mailing list. But if the missing sleep.target is a blocker for systemd-user anyways, that may not be neccessary.

@timokau
Copy link
Contributor Author

timokau commented Oct 14, 2017

Okay, while I was fiddeling with postswitch scripts it stopped working again.
Here's the output:

-- Logs begin at Mon 2017-08-28 22:21:00 CDT. --
Oct 14 16:02:49 pad systemd[1]: Started autorandr execution hook.
Oct 14 16:03:33 pad systemd[1]: Starting autorandr execution hook...
Oct 14 16:03:34 pad autorandr[26554]: No protocol specified
Oct 14 16:03:34 pad autorandr[26554]: Can't open display :0
Oct 14 16:03:34 pad autorandr[26554]: Failed to run xrandr (line 440)
Oct 14 16:03:34 pad autorandr[26554]: autorandr started as uid 1000
Oct 14 16:03:34 pad autorandr[26554]: autorandr started as uid 0
Oct 14 16:03:34 pad autorandr[26554]: Running autorandr as timo for display :0
Oct 14 16:03:34 pad autorandr[26554]: {'DESKTOP_SESSION': '/nix/store/j93sjhrv8axb4f0nw2ilwcpwagk0s6gi-desktops/none+herbstluftwm', 'DISPLAY': ':0', 'PATH': '/bin:/usr/bin:/usr/local/bin', 'XDG_CURRENT_DESKTOP': '', 'XDG_SEAT': 'seat0', 'XDG_SEAT_PATH': '/org/freedesktop/DisplayManager/Seat0', 'XDG_SESSION_CLASS': 'user', 'XDG_SESSION_DESKTOP': '', 'XDG_SESSION_PATH': '/org/freedesktop/DisplayManager/Session1', 'XDG_SESSION_TYPE': 'x11', 'XDG_VTNR': '7', 'LANG': 'en_US.UTF-8', 'LD_LIBRARY_PATH': '/run/opengl-driver/lib', 'LOCALE_ARCHIVE': '/run/current-system/sw/lib/locale/locale-archive', 'NIX_CONF_DIR': '/etc/nix', 'NIX_OTHER_STORES': '/run/nix/remote-stores/*/nix', 'NIX_PATH': 'nixpkgs=/tmp/:nixos-config=/etc/nixos/configuration.nix:unstable=http://nixos.org/channels/nixos-unstable/nixexprs.tar.xz', 'TZDIR': '/etc/zoneinfo', 'XDG_SESSION_ID': '2', 'XDG_RUNTIME_DIR': '/run/user/1000', 'PAM_KWALLET5_LOGIN': '/run/user/1000/kwallet5.socket', 'AUTORANDR_BATCH_PID': '26554'}
Oct 14 16:03:34 pad systemd[1]: Started autorandr execution hook.

@phillipberndt
Copy link
Owner

Okay, so the user instance is executed but XAUTHORITY is missing. Interesting: autorandr obtains its environment by scanning through any applications run by your user that have the DISPLAY environment variable set and cloning its environment. Apparently, your user runs a process that has DISPLAY set, but doesn't actually use X11 (It can't without having access..)

To see which process this is, you can add before

print("Running autorandr as %s for display %s" % (pwent.pw_name, display))

the line

print("Copied environment from %s" % " ".join(open(os.path.join(directory, "cmdline")).read().split("\0")))

And to resolve the issue, try adding after

display = process_environ["DISPLAY"] if "DISPLAY" in process_environ else None

a line

if "XAUTHORITY" not in process_environ:
    display = None

@timokau
Copy link
Contributor Author

timokau commented Oct 15, 2017

"Unfortunately" right now it works again (which makes sense if your explanation is correct: it depends on which process autorandr semi-randomly selects).

Instead I ran the following script to test:

import os

for directory in os.listdir("/proc"):
    directory = os.path.join("/proc/", directory)
    if not os.path.isdir(directory):
        continue
    environ_file = os.path.join(directory, "environ")
    if not os.path.isfile(environ_file):
        continue
    uid = os.stat(environ_file).st_uid

    if uid < 1000:
        continue

    process_environ = {}
    try:
        for environ_entry in open(environ_file).read().split("\0"):
            if "=" in environ_entry:
                name, value = environ_entry.split("=", 1)
                if name == "DISPLAY" and "." in value:
                    value = value[:value.find(".")]
                process_environ[name] = value
        if "DISPLAY" in process_environ and "XAUTHORITY" not in process_environ:
            print("Found %s" % " ".join(open(os.path.join(directory, "cmdline")).read().split("\0")))
    except IOError:
        pass

And got the following results:

Found /nix/store/shq2an5pmbjmgmrjwzn8ixjaxnfspnxj-kwallet-5.37.0-bin/bin/kwalletd5 --
Found /nix/store/rr1vxmwdy7risbsiwcz3iawabnv8w8jn-redshift-1.11/bin/redshift -l xx.yy

I don't know anything about kwalletd5, but redshift is running as a systemd-user service. Maybe that has something to do with it.

Note that I ignored all IOErrors. That's because I got an Permisison denied for /proc/2411/environ. It might be a good idea to handle that case in the real autorandr code.

@phillipberndt
Copy link
Owner

Interesting that redshift still works - it also uses XRandr to set the gamma curve (e.g.) so it should be restricted by the same ACL as every other process.

However, there should be a simple fix: Prefer processes with XAUTHORITY set as template environments for forking user instances of autorandr. I've made a commit that does this just now.

@timokau
Copy link
Contributor Author

timokau commented Oct 21, 2017

Yes, that is weird. Maybe it does some hacks to discover XAUTHORITY at runtime.

Your "environment stealing" is a very clever workaround by the way. It might even be worthwhile to extract that to its own script:

for auth in $( detect_running_xsesions ); do
    XAUTHORITY="$auth" autorandr --change
done

or something like that. I could have used that on multiple occasions already (like when trying to notify-send in a cronjob or something).

Thank you for fixing it!

@phillipberndt
Copy link
Owner

Your "environment stealing" is a very clever workaround by the way. It might even be worthwhile to
extract that to its own script

I'll leave it in place here, because I've made the promise that autorandr would always remain a contained, single-file script without dependencies. But feel free to extract it into its own tool anyway if you feel you'd find some use for it :-) (Might be an idea to use psutil to be cross-platform compatible in that case.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants