/ run / user / $ UID não montado quando o daemon inicia

2

Quando eu reinicio meu Raspberry Pi (Stretch), um daemon falha ao iniciar porque /run/user/1000 não existe. Este é o meu arquivo de unidade:

[Unit]
Description=SBFspot Upload Daemon

[Service]
User=pi
Type=forking
TimeoutStopSec=60
ExecStart=/usr/local/bin/sbfspot.3/SBFspotUploadDaemon -p /run/user/1000/sbfspotupload.pid 2>&1> /dev/null
PIDFile=/run/user/1000/sbfspotupload.pid
Restart=no
RestartSec=15
SuccessExitStatus=SIGKILL

[Install]
WantedBy=default.target

Quando configuro para Restart=on-failure , tudo corre bem depois de algumas tentativas, mas não é exatamente isso que desejo. Eu quero que o daemon espere que /run/user/1000 seja montado. Eu tentei com After=run-user-1000.mount , mas ainda falha.

Isso é possível ou tenho que ficar com o Restart=on-failure ?

    
por SBF 25.06.2018 / 23:14

1 resposta

1

/run/user/1000 , que obviamente não existe até que o usuário # 1000 efetue login ou inicie explicitamente o xyr por gerenciamento de serviço por usuário, é um arenque vermelho. Todo o mecanismo que o usa não deveria estar lá.

O bug # 215 deste programa é executado muito mais profundamente do que você imagina. Este arquivo de unidade de serviço está muito errado, como é a operação do próprio programa . Há muita programação de cultos de carga, baseada na não compreensão dos fundamentos das unidades de serviço do systemd.

  • Unidades de serviço não são shell script. O manual do systemd faz explicar isso. A configuração ExecStart faz com que o programa de serviço seja executado com alguns argumentos extras, 2>&1> e /dev/null .
  • O gerente de serviços já garante que apenas um serviço seja executado. Todo esse código adicionado aqui é lixo desnecessário.
  • O mecanismo de arquivos PID frágil e perigoso deve não ser usado. Não tem lugar no gerenciamento de serviço adequado.
  • O gerenciador de serviço também lida com a chamada do serviço em um contexto de daemon. Muitos dos outros códigos em main() são também lixo desnecessário, baseado na falácia da demonização.
    • O programa não deve ser fork() ing, e o mecanismo de prontidão do serviço não deve ser especificado como Type=forking . Como muitos programas no mundo real, este programa não está falando o protocolo de prontidão de bifurcação em primeiro lugar.
    • O programa já está em execução como superusuário. User=root é desnecessário e, de fato, o serviço deve ser reprojetado para que não precise ser executado com privilégios de superusuário, mas executado sob a égide de uma conta de serviço dedicada sem privilégios.
    • O gerenciador de serviços já está registrando a saída e o erro padrão, e fazendo um trabalho melhor do que este programa. Este sistema de registro caseiro apenas desenvolve um arquivo de log até que ele preencha todo um sistema de arquivos, consumindo todo o espaço de emergência reservado para o superusuário.
    • Seu log é simplesmente o erro padrão, acessível a partir do C ++ como std::clog .
    • Na verdade, todo o código do fork() para o redirecionamento do padrão erro não deve ser usado. O gerenciamento de serviços lida com todos , desde a liderança da sessão até o diretório de trabalho e umask até a E / S padrão, e faz isso corretamente. Este programa não, e não deve estar tentando fazer qualquer deste para si quando usado sob um gerenciador de serviços.

      Tudo o que você tirou do Boost estava errado.

  • Três unidades de serviço são despesas desnecessárias de manutenção. Elas diferem apenas nas configurações de After , e elas podem ser mescladas em uma só.
  • Terminação sem graça não é sucesso. Dado que já havia um problema com a limpeza de arquivos após o término, SuccessExitStatus=SIGKILL está equivocado. A finalização normal deve ser graciosa, via SIGTERM , e SIGKILL deve ser considerado anormal. (Obviamente, todo o mecanismo de arquivos output é um mecanismo de registro caseiro desenvolvido de forma incorreta que não deve ser usado no gerenciamento de serviços, conforme já explicado.) Esse é o padrão systemd.
  • Destrutores dos objetos de banco de dados e outras coisas devem ser executados. Não deixe main() com exit() .

Um programa do daemon implementado adequadamente para rodar sob um gerenciador de serviço, seja daemontools, runit, s6, nosh, systemd ou qualquer outra coisa, é muito mais curto:

// the same until this point
void pvo_upload(void)
{
    std::clog << "Starting Daemon..." << std::endl;

    CommonServiceCode();

    std::clog << "Stopping Daemon..." << std::endl;
}

int main(int argc, char *argv[])
{
    int c;
    const char *config_file = "";

    /* parse commandline */
    while(1)
    {
        static struct option long_options[] =
        {
            { "config-file", required_argument, 0, 'c' },
            { 0, 0, 0, 0 }
        };

        int option_index = 0;
        c = getopt_long (argc, argv, "c:", long_options, &option_index);

        if (c == -1) break;

        switch (c)
        {
            case 'c':
                config_file = optarg;
                break;
            default:
                return EXIT_FAILURE;
                break;
        }
    }

    if (cfg.readSettings(argv[0], config_file) != Configuration::CFG_OK)
        return EXIT_FAILURE;

    std::clog << "Starting SBFspotUploadDaemon Version " << VERSION << std::endl;

    // Check if DB is accessible
    db_SQL_Base db = db_SQL_Base();
    db.open(cfg.getSqlHostname(), cfg.getSqlUsername(), cfg.getSqlPassword(), cfg.getSqlDatabase());
    if (!db.isopen())
    {
        std::clog << "Unable to open database. Check configuration." << std::endl;
        return EXIT_FAILURE;
    }

    // Check DB Version
    int schema_version = 0;
    db.get_config(SQL_SCHEMAVERSION, schema_version);
    db.close();

    if (schema_version < SQL_MINIMUM_SCHEMA_VERSION)
    {
        std::clog << "Upgrade your database to version " << SQL_MINIMUM_SCHEMA_VERSION << std::endl;
        return EXIT_FAILURE;
    }

    // Install our signal handler.
    // This responds to the service manager signalling the service to stop.
    signal(SIGTERM, handler);

    // Start daemon loop
    pvo_upload();

    return EXIT_SUCCESS;
}

E a unidade de serviço também é mais curta:

[Unit]
Description=SBFspot upload daemon
After=mysql.service mariadb.service network.target

[Service]
Type=simple
TimeoutStopSec=10
ExecStart=/usr/local/bin/sbfspot.3/SBFspotUploadDaemon
Restart=on-success

[Install]
WantedBy=multi-user.target

A saída do log é visível com systemctl status e journalctl (com a opção -u e o nome do serviço, se desejado).

Leitura adicional

por 26.06.2018 / 09:15