Pclose似乎会使过程失败

时间:2018-07-03 14:49:18

标签: c linux daemon popen pclose

此问题是该问题的后续内容:Controlling a C daemon from another program

我的目标是从另一个程序控制守护进程的执行。
守护程序的代码非常简单。

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. 使用“开始”参数启动脚本
  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会在行为良好时不调用而使程序失败?


编辑:

以下是有关错误情况的更多信息

errno是Success
WIFEXITED宏返回true
WEXITSTATUS宏返回141

通过进一步进行调试,我重新确定了修改init脚本以将输出记录到文件可以使其工作... 为什么?

1 个答案:

答案 0 :(得分:1)

您使用popen(DAEMON_START_CMD, "r")。这意味着您的“守护程序监视程序”正在读取“守护程序启动程序”脚本的标准输出。如果您pclose()使用该管道,则脚本将写入标准输出并获得SIGPIPE,因为管道的读取端已关闭。是否在实际的守护程序启动之前发生是有争议的,而且还有时间问题。

在您知道守护程序启动程序已通过某种方式退出之前,请勿pclose()进行管道传输。就我个人而言,我会直接使用pipe()fork()execv()(或exec函数家族的其他一些变体。我不认为popen()是完成这项工作的正确工具。但是,如果您要使用popen(),请先阅读输入内容,直到没有更多(EOF),然后再安全地使用pclose()。打印阅读的内容,尽管这是常规且明智的做法-“ daemon starter”脚本会告诉您有用的信息。

检查进程ID是否仍在运行的经典方法是使用kill(daemon_pid, 0)。如果执行的进程具有适当的特权(与该进程相同的UID或root特权),则此方法有效。如果您无法向PID发送有效信号,将无济于事。

(我假设start-stop-daemon是一个程序,可能是C程序而不是Shell脚本,它可以将另一个程序作为守护程序启动。我有一个类似的程序,我称之为daemonize —并且它也用于将不是专门设计为守护程序的程序转换为以守护程序运行的程序,许多程序不能作为守护程序正常工作,请考虑将lsgreppssort的意思是。其他程序可以更合理地作为守护程序运行。)