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, которая:
- Запустите скрипт с аргументом start
- Дождитесь создания pid файла
- Чтение pid демона из pid файла
- Периодически проверять, что демон работает, проверяя наличие файла
/proc/<daemon_pid>/exec
- Если демон убит, перезапустите его
И вот проблема, с которой я сталкиваюсь. Программа работает хорошо, только если я не звоню 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
будет означать. Другие программы могут быть более разумно запущены как демоны.)