Search code examples
clinuxservicedbus

Detach forked process from linux service using dbus


I have a service on linux. This service needs to start different processes. For example, on some condition e.g. a network event, the service should start a process that has a GUI. For this i used fork() and execvp(). I furthermore dropped root privileges by setting the uid and gid according to the user, for which i want to run the process. I then perceded by setting the DISPLAY and XAUTHORITY environment variables accordingly.

This worked kinda. My process was indeed running and the GUI was displayed. However it is noticable that the process is not really detatched from the service. For example: pkexec does not work. It always tries to start the terminal version and fails. Also, i dont want my process to be killed, as soon as the service stops.

I read that for this i should use the dbus and use it to communicate with the systemd and either 1) start the process via systemd or 2) let systemd somehow detatch my process from the service.

Now i dont have any idea how that works. I can open the bus and i should also be able to call methods, however i dont know which method and what i am even trying to do.

Can someone guide me in the right direction, of which method i should call on the systemd to either start a process in a user session or detatch a process from my service.

Disclaimer: setting KillMode=none would solve the process killing issue, however this is, as far as i know, not recommended and also does not solve the pkexec problem.


Solution

  • I have a service on linux. This service needs to start different processes. For example, on some condition e.g. a network event, the service should start a process that has a GUI

    Don't do that. It's a really bad architecture, as you've found out. (Even if you do set DISPLAY and XAUTHORITY, how do you know what are the correct values for that? What guarantees that there's always exactly one user? What guarantees their XAUTHORITY is always the same?)

    Instead, let the GUI itself start a "listener" process for the logged-in user, which waits for the events (either from system in general, or from your own system service) and simply starts the necessary programs because it's already running as the correct user in the correct cgroup. This "agent" pattern is commonly used in desktop environments; /etc/xdg/autostart is used to automatically start the agent process upon login.

    I read that for this i should use the dbus and use it to communicate with the systemd and either 1) start the process via systemd or 2) let systemd somehow detatch my process from the service.

    Now i dont have any idea how that works. I can open the bus and i should also be able to call methods, however i dont know which method and what i am even trying to do.

    This is how the systemd approach would work:

    1. You can connect to the system service manager and ask it to start a transient service with parameters and environment that you specify through the D-Bus call. It will be completely detached from your main service.

    2. Or, you can connect to the user's personal systemd instance and ask that to start a transient service. The advantage here is that it will automatically have all the necessary environment inherited from the per-user systemd process – it's actually how some desktop environments launch apps nowadays.

      Unlike spawning the process directly, this only requires knowing the user's UID (and XDG_RUNTIME_DIR, but fortunately that is guaranteed to be at a fixed per-UID path).

    3. Or, you could spawn the process yourself (with the correct environment), then ask systemd to create a transient scope which will correspond to a new cgroup.

    In all cases, the method call you want is StartTransientUnit(), with the only difference being the bus address you connect to. For option #1 it's the system bus; for option #2 you have to switch to the correct UID, then connect to the "user bus" at /run/user/<uid>/bus.

    Start by experimenting with the systemd-run command; you can use dbus-monitor to see what calls it uses. (Or, in fact, you can just spawn systemd-run from your service.) For example:

    runuser -u $username -- \
        env XDG_RUNTIME_DIR=/run/user/$uid \
        systemd-run --user --collect /usr/bin/xterm
    
    systemd-run -M [email protected] --user --collect /usr/bin/xterm