Pclose, кажется, делает процесс неудачным - PullRequest
0 голосов
/ 03 июля 2018

Этот вопрос является продолжением этого вопроса: Управление демоном C из другой программы

Моя цель - контролировать выполнение процесса демона из другой программы.
Код демона действительно прост.

int main()
{
  printf("Daemon starting ...\n");
  openlog("daemon-test", LOG_PID, LOG_DAEMON);

  syslog(LOG_INFO, "Daemon started !\n");

  while(1)
  {
    syslog(LOG_INFO, "Daemon alive - pid=%d, pgid=%d\n", getpid(), getpgrp());
    sleep(1);
  }

  return EXIT_SUCCESS;
}

Я реализовал сценарий инициализации SystemV для этого демона следующим образом

#!/bin/sh

NAME=daemon-test
DAEMON=/usr/bin/${NAME}
SCRIPTNAME=/etc/init.d/${NAME}
USER=root
RUN_LEVEL=99
PID_FILE=/var/run/${NAME}.pid
RETRY=3

start_daemon()
{
    start-stop-daemon --start --background --name ${NAME} --chuid ${USER} --nicelevel ${RUN_LEVEL} --make-pidfile --pidfile ${PID_FILE} --exec ${DAEMON}
    ret=$?

    if [ "$ret" -eq 0 ]; then
        echo "'${NAME}' started"
    elif [ "$ret" -eq 1 ]; then
        echo "'${NAME}' is already running"
    else
        echo "An error occured starting '${NAME}'"
    fi
    return ${ret}
}

stop_daemon()
{
    start-stop-daemon --stop --retry ${RETRY} --remove-pidfile --pidfile ${PID_FILE} --name ${NAME} --signal 9
    ret=$?

    if [ "$ret" -eq 0 ]; then
        echo "'${NAME}' stopped"
    elif [ "$ret" -eq 1 ]; then
        echo "'${NAME}' is already stopped"
    elif [ "$ret" -eq 2 ]; then
        echo "'${NAME}' not stopped after ${RETRY} tries"
    else
        echo "An error occured stopping '${NAME}'"
    fi
    return ${ret}
}

status_daemon()
{
    start-stop-daemon --status --pidfile ${PID_FILE} --name ${NAME}
    ret=$?

    if [ "$ret" -eq 0 ]; then
        echo "'${NAME}' is running"
    elif [ "$ret" -eq 1 ]; then
        echo "'${NAME}' stopped but pid file exits"
    elif [ "$ret" -eq 3 ]; then
        echo "'${NAME}' stopped"
    elif [ "$ret" -eq 4 ]; then
        echo "Unable to get '${NAME}' status"
    else
        echo "Unknown status : ${ret}"
    fi
    return ${ret}
}

case "$1" in
  start)
    echo "Starting '${NAME}' deamon :"
    start_daemon
    ;;
  stop)
    echo "Stopping '${NAME}' deamon :"
    stop_daemon
    ;;
  status)
    echo "Getting '${NAME}' deamon status :"
    status_daemon
    ;;
  restart|reload)
    "$0" stop
    "$0" start
    ;;
  *)
    echo "Usage: $0 {start|stop|status|restart}"
    exit 1
esac

exit $?

Использование этого скрипта из командной строки для управления выполнением демона работает хорошо.


Таким образом, сейчас цель состоит в том, чтобы использовать этот скрипт из другой программы на c для запуска демона и управления его выполнением из этой программы.

Я реализовал простую программу на C, которая:

  1. Запустить скрипт с аргументом start
  2. Ожидание создания файла pid
  3. Чтение pid демона из файла pid
  4. Периодически проверять, жив ли демон, проверяя наличие файла /proc/<daemon_pid>/exec
  5. Если демон убит, перезапустите его

И вот проблема, с которой я сталкиваюсь. Программа работает хорошо, только если я не звоню pclose.

Вот код программы

#define DAEMON_NAME       "daemon-test"
#define DAEMON_START_CMD  "/etc/init.d/" DAEMON_NAME " start"
#define DAEMON_STOP_CMD   "/etc/init.d/" DAEMON_NAME " stop"
#define DAEMON_PID_FILE   "/var/run/" DAEMON_NAME ".pid"

int main()
{
    char daemon_proc_path[256];
    FILE* daemon_pipe = NULL;
    int daemon_pid = 0;
    FILE* fp = NULL;
    int ret = 0;
    int i = 0;

    printf("Launching '%s' program\n", DAEMON_NAME);
    if(NULL == (daemon_pipe = popen(DAEMON_START_CMD, "r")))
    {
        printf("An error occured launching '%s': %m\n", DAEMON_START_CMD);
        return EXIT_FAILURE;
    }
    #ifdef USE_PCLOSE
    else if(-1 == (ret = pclose(daemon_pipe)))
    {
        printf("An error occured waiting for '%s': %m\n", DAEMON_START_CMD);
        return EXIT_FAILURE;
    }
    #endif
    else
    {
        printf("Script exit status : %d\n", ret);

        while(0 != access(DAEMON_PID_FILE, F_OK))
        {
            printf("Waiting for pid file creation\n");
            sleep(1);
        }
        if(NULL == (fp = fopen(DAEMON_PID_FILE, "r")))
        {
            printf("Unable to open '%s'\n", DAEMON_PID_FILE);
            return EXIT_FAILURE;
        }
        fscanf(fp, "%d", &daemon_pid);
        fclose(fp);
        printf("Daemon has pid=%d\n", daemon_pid);
        sprintf(daemon_proc_path, "/proc/%d/exe", daemon_pid);
    }

    while(1)
    {
        if(0 != access(daemon_proc_path, F_OK))
        {
            printf("\n--- Daemon (pid=%d) has been killed ---\n", daemon_pid);
            printf("Relaunching new daemon instance...\n");
            if(NULL == (daemon_pipe = popen(DAEMON_START_CMD, "r")))
            {
                printf("An error occured launching '%s': %m\n", DAEMON_START_CMD);
                return EXIT_FAILURE;
            }
            #ifdef USE_PCLOSE
            else if(-1 == (ret = pclose(daemon_pipe)))
            {
                printf("An error occured waiting for '%s': %m\n", DAEMON_START_CMD);
                return EXIT_FAILURE;
            }
            #endif
            else
            {
                printf("Script exit status : %d\n", ret);

                while(0 != access(DAEMON_PID_FILE, F_OK))
                {
                    printf("Waiting for pid file creation\n");
                    sleep(1);
                }
                if(NULL == (fp = fopen(DAEMON_PID_FILE, "r")))
                {
                    printf("Unable to open '%s'\n", DAEMON_PID_FILE);
                    return EXIT_FAILURE;
                }
                fscanf(fp, "%d", &daemon_pid);
                fclose(fp);
                printf("Daemon has pid=%d\n", daemon_pid);
                sprintf(daemon_proc_path, "/proc/%d/exe", daemon_pid);
            }
        }
        else
        {
            printf("Daemon alive (pid=%d)\n", daemon_pid);
        }
        sleep(1);
    }

    return EXIT_SUCCESS;
}

Из того, что я понял, pclose должен ожидать завершения дочернего процесса, и только когда дочерний процесс вернулся, он закрывает канал.

Так что я не понимаю, почему моя реализация с pclose не работает, когда работает без вызова.

Вот журналы с и без блока pclose с комментариями

Без pclose звонки:

# ./popenTest 
Launching 'daemon-test' program
Script exit status : 0
Waiting for pid file creation
Daemon has pid=435
Daemon alive (pid=435)
Daemon alive (pid=435)
Daemon alive (pid=435)
Daemon alive (pid=435)

С pclose вызов:

# ./popenTest 
Launching 'daemon-test' program
Script exit status : 36096
Waiting for pid file creation
Waiting for pid file creation
Waiting for pid file creation
Waiting for pid file creation

Как видите, демон никогда не запускается, и файл pid никогда не создается.

Даже если моя программа работает без pclose Я бы хотел понять основную проблему с вызовом pclose.

Почему использование pclose приводит к сбою программы, если поведение не вызывает ее?


EDIT:

Вот еще немного информации для случая ошибки

errno is Success
Макрос WIFEXITED возвращает true
Макрос WEXITSTATUS возвращает 141

Идя дальше к отладке, я заметил, что изменение сценария инициализации для записи вывода в файл делает его работоспособным ... почему?

1 Ответ

0 голосов
/ 03 июля 2018

Вы используете popen(DAEMON_START_CMD, "r"). Это означает, что ваш 'daemon Watcher' читает стандартный вывод вашего скрипта 'daemon Starter'. Если вы pclose() этот канал, сценарий записывает в стандартный вывод и получает SIGPIPE, потому что конец чтения канала закрыт. Происходит ли это до запуска фактического демона или нет, остается открытым для обсуждения - и вопросов о сроках.

Не pclose() труба, пока вы не узнаете, что демон-стартер каким-то образом или другим образом вышел. Лично я бы использовал pipe(), fork() и execv() (или какой-либо другой вариант семейства функций exec напрямую. Я не думаю, что popen() является правильным инструментом для работы. Но если вы собираетесь использовать popen(), затем читать ввод, пока не получите больше (EOF), затем безопасно использовать * 1012. * Вам не нужно печатать то, что вы читаете, хотя это было бы обычным и разумным сделать Итак, скрипт 'daemon Starter' сообщает вам полезную информацию.

Классический способ проверить, запущен ли еще идентификатор процесса, - использовать kill(daemon_pid, 0). Если процесс, выполняющий это, имеет соответствующие привилегии (тот же UID, что и у процесса, или root привилегии), это работает. Это не поможет, если вы не можете отправить активный сигнал на PID.

(Полагаю, start-stop-daemon - это программа, скорее всего, программа на С, а не скрипт оболочки, которая запускает другую программу в качестве демона. У меня есть похожая программа, которую я называю daemonize - и она тоже предназначена для конвертации программы, специально не разработанные как демоны, в программу, выполняемую как демон. Многие программы работают не так хорошо, как демоны - подумайте, что означают демонизации ls, grep, ps или sort. Другие программы могут более разумно бегать как демоны.)

...