Pclose, кажется, делает процесс неудачным

Этот вопрос является продолжением этого вопроса: Управление демоном 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 делает программу неудачной, когда поведение хорошее, не вызывая его?


РЕДАКТИРОВАТЬ:

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

эррно Success
WIFEXITED макрос возвращает true
Макрос WEXITSTATUS возвращает 141

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

1 ответ

Решение

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

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

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

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

Другие вопросы по тегам