Separe el proceso bifurcado del servicio de Linux usando dbus

Resuelto StillWaters77 asked hace 10 meses • 0 respuestas

Tengo un servicio en Linux. Este servicio necesita iniciar diferentes procesos. Por ejemplo, en alguna condición, por ejemplo un evento de red, el servicio debería iniciar un proceso que tenga una GUI. Para esto utilicé fork() y execvp(). Además, eliminé los privilegios de root configurando el uid y el gid según el usuario para el cual quiero ejecutar el proceso. Luego procedí configurando las variables de entorno DISPLAY y XAUTHORITY en consecuencia.

Esto funcionó un poco. De hecho, mi proceso se estaba ejecutando y se mostró la GUI. Sin embargo, se nota que el proceso no está realmente desvinculado del servicio. Por ejemplo: pkexec no funciona. Siempre intenta iniciar la versión del terminal y falla. Además, no quiero que mi proceso finalice tan pronto como se detenga el servicio.

Leí que para esto debería usar el dbus y usarlo para comunicarme con systemd y 1) iniciar el proceso a través de systemd o 2) dejar que systemd de alguna manera separe mi proceso del servicio.

Ahora no tengo idea de cómo funciona eso. Puedo abrir el bus y también debería poder llamar a métodos, sin embargo, no sé qué método ni qué estoy intentando hacer.

¿Alguien puede guiarme en la dirección correcta, cuál método debo llamar al systemd para iniciar un proceso en una sesión de usuario o desconectar un proceso de mi servicio?

Descargo de responsabilidad: configurar KillMode=none resolvería el problema de finalización del proceso; sin embargo, hasta donde yo sé, esto no se recomienda y tampoco resuelve el problema de pkexec.

StillWaters77 avatar Feb 17 '24 01:02 StillWaters77
Aceptado

Precedido por mis comentarios principales...

  1. hacer el doble fork
  2. hacer setsiden nieto
  3. hacer setpgrpen nieto
  4. Cerrar todas las unidades estándar en nieto

He creado un programa de diagnóstico que parece funcionar. No lo he probado exhaustivamente . No establece ningún entorno, así que no sé cómo afectará eso a las cosas.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

void
launch_top(int argc,char **argv)
{
    pid_t top = getpid();

    // parent launching child
    pid_t pcld = fork();
    if (pcld != 0) {
        waitpid(pcld,NULL,0);
        sleep(2);
        printf("launch_top: killing self %d\n",top);
        kill(top,SIGTERM);
        exit(0);
    }

    // child launching grandchild
    pid_t gcld = fork();
    if (gcld != 0)
        exit(0);

    // grandchild launching target
    setsid();
    setpgrp();

    fclose(stdin);
    fclose(stdout);
    fclose(stderr);

    char *av[3];

    av[0] = argv[0];
    av[1] = "forever";
    av[2] = NULL;

    execvp(av[0],av);
    perror("launch_top/execvp");
    exit(9);
}

void
stay_alive(int argc,char **argv)
{

    FILE *xflog = fopen("stay.log","w");
    setlinebuf(xflog);

    for (int loop = 0;  loop < 10;  ++loop) {
        fprintf(xflog,"stay_alive: %d\n",loop);
        sleep(1);
    }

    fprintf(xflog,"stay_alive: success\n");
    fclose(xflog);

    exit(0);
}

int
main(int argc,char **argv)
{

    setlinebuf(stdout);

    // ultimate program that should stay alive
    if (argc > 1)
        stay_alive(argc,argv);

    // launcher service
    else
        launch_top(argc,argv);

    return 0;
}

Aquí está el stdoutresultado para el padre:

launch_top: killing self 2564383

Aquí está el registro de salida de muestra stay.logpara el programa de destino que debería permanecer vivo y sobrevivir a la muerte del original/padre:

stay_alive: 0
stay_alive: 1
stay_alive: 2
stay_alive: 3
stay_alive: 4
stay_alive: 5
stay_alive: 6
stay_alive: 7
stay_alive: 8
stay_alive: 9
stay_alive: success
Craig Estey avatar Feb 16 '2024 19:02 Craig Estey

Tengo un servicio en Linux. Este servicio necesita iniciar diferentes procesos. Por ejemplo, en alguna condición, por ejemplo, un evento de red, el servicio debe iniciar un proceso que tenga una GUI.

No hagas eso. Es una arquitectura realmente mala, como habrás descubierto. (Incluso si configura DISPLAY y XAUTHORITY, ¿cómo sabe cuáles son los valores correctos para eso? ¿Qué garantiza que siempre haya exactamente un usuario? ¿Qué garantiza que su XAUTHORITY sea siempre la misma?)

En su lugar, deje que la propia GUI inicie un proceso de "escucha" para el usuario que ha iniciado sesión, que espera los eventos (ya sea del sistema en general o de su propio servicio del sistema) y simplemente inicia los programas necesarios porque ya se está ejecutando como el usuario correcto en el cgroup correcto. Este patrón de "agente" se usa comúnmente en entornos de escritorio; /etc/xdg/autostart se utiliza para iniciar automáticamente el proceso del agente al iniciar sesión.

Leí que para esto debería usar el dbus y usarlo para comunicarme con systemd y 1) iniciar el proceso a través de systemd o 2) dejar que systemd de alguna manera separe mi proceso del servicio.

Ahora no tengo idea de cómo funciona eso. Puedo abrir el bus y también debería poder llamar a métodos, sin embargo, no sé qué método ni qué estoy intentando hacer.

Así es como funcionaría el enfoque systemd:

  1. Puede conectarse al administrador de servicios del sistema y pedirle que inicie un servicio transitorio con los parámetros y el entorno que especifique a través de la llamada D-Bus. Estará completamente desvinculado de su servicio principal.

  2. O puede conectarse a la instancia systemd personal del usuario y solicitarle que inicie un servicio transitorio. La ventaja aquí es que automáticamente tendrá todo el entorno necesario heredado del proceso systemd por usuario; en realidad, así es como algunos entornos de escritorio inician aplicaciones hoy en día.

    A diferencia de generar el proceso directamente, esto solo requiere conocer el UID del usuario (y XDG_RUNTIME_DIR, pero afortunadamente se garantiza que será en una ruta fija por UID).

  3. O bien, puede generar el proceso usted mismo (con el entorno correcto) y luego pedirle a systemd que cree un alcance transitorio que corresponderá a un nuevo cgroup.

En todos los casos, la llamada al método que desea es StartTransientUnit(), con la única diferencia de la dirección del bus al que se conecta. Para la opción n.° 1, es el bus del sistema; para la opción n.° 2, debe cambiar al UID correcto y luego conectarse al "bus de usuario" en /run/user/<uid>/bus.

Empiece por experimentar con el systemd-runcomando; puedes usar dbus-monitor para ver qué llamadas usa. (O, de hecho, puedes simplemente generar systemd-run desde tu servicio). Por ejemplo:

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
user1686 avatar Feb 16 '2024 21:02 user1686