为此,您需要使用低级设施。您需要创建一个额外的管道 close-on-exec,当 exec 失败时,子进程使用它来编写错误代码。
由于管道是 close-on-exec,它将在新二进制文件开始执行时被内核关闭。 (我们实际上不知道它是否正在运行;我们只知道 exec 没有失败。因此,不要假设关闭的管道意味着命令已经在运行。它只意味着它还没有失败。)
父进程关闭不必要的管道端,并从控制管道中读取。如果读取成功,则子进程执行命令失败,读取的数据描述了错误。如果管道关闭(读取返回 0),命令将开始执行(没有错误阻止其执行)。
之后,我们可以像往常一样继续从管道中读取。当子进程关闭管道时,我们应该使用waitpid()来获取它。
考虑以下示例程序。它执行命令行中指定的命令——如果你想要与system() 和popen() 相同的行为,请使用sh -c 'command'。 (即pathname == "sh" 和argv == { "sh", "-c", "command", NULL }。)
它逐个字符地读取命令的输出,对它们进行计数,直到子进程结束(通过关闭管道)。之后,我们获取子进程,并报告状态。
如果命令无法执行,也会报告原因。 (由于非可执行文件被报告为ENOENT(“没有这样的文件或目录”),exec_subproc() 将该情况修改为EACCESS(“权限被拒绝”)。)
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
int reap_subproc(pid_t pid)
{
int status;
pid_t p;
if (pid < 1) {
errno = EINVAL;
return -1;
}
do {
status = 0;
p = waitpid(pid, &status, 0);
} while (p == -1 && errno == EINTR);
if (p == -1)
return -1;
errno = 0;
return status;
}
FILE *exec_subproc(pid_t *pidptr, const char *pathname, char **argv)
{
char buffer[1];
int datafd[2], controlfd[2], result;
FILE *out;
ssize_t n;
pid_t pid, p;
if (pidptr)
*pidptr = (pid_t)0;
if (!pidptr || !pathname || !*pathname || !argv || !argv[0]) {
errno = EINVAL;
return NULL;
}
if (pipe(datafd) == -1)
return NULL;
if (pipe(controlfd) == -1) {
const int saved_errno = errno;
close(datafd[0]);
close(datafd[1]);
errno = saved_errno;
return NULL;
}
if (fcntl(datafd[0], F_SETFD, FD_CLOEXEC) == -1 ||
fcntl(controlfd[1], F_SETFD, FD_CLOEXEC) == -1) {
const int saved_errno = errno;
close(datafd[0]);
close(datafd[1]);
close(controlfd[0]);
close(controlfd[1]);
errno = saved_errno;
return NULL;
}
pid = fork();
if (pid == (pid_t)-1) {
const int saved_errno = errno;
close(datafd[0]);
close(datafd[1]);
close(controlfd[0]);
close(controlfd[1]);
errno = saved_errno;
return NULL;
}
if (!pid) {
/* Child process. */
close(datafd[0]);
close(controlfd[0]);
if (datafd[1] != STDOUT_FILENO) {
do {
result = dup2(datafd[1], STDOUT_FILENO);
} while (result == -1 && errno == EINTR);
if (result == -1) {
buffer[0] = errno;
close(datafd[1]);
do {
n = write(controlfd[1], buffer, 1);
} while (n == -1 && errno == EINTR);
exit(127);
}
close(datafd[1]);
}
if (pathname[0] == '/')
execv(pathname, argv);
else
execvp(pathname, argv);
buffer[0] = errno;
close(datafd[1]);
/* In case it exists, we return EACCES instead of ENOENT. */
if (buffer[0] == ENOENT)
if (access(pathname, R_OK) == 0)
buffer[0] = EACCES;
do {
n = write(controlfd[1], buffer, 1);
} while (n == -1 && errno == EINTR);
exit(127);
}
*pidptr = pid;
close(datafd[1]);
close(controlfd[1]);
do {
n = read(controlfd[0], buffer, 1);
} while (n == -1 && errno == EINTR);
if (n == -1) {
close(datafd[0]);
close(controlfd[0]);
kill(pid, SIGKILL);
do {
p = waitpid(pid, NULL, 0);
} while (p == (pid_t)-1 && errno == EINTR);
errno = EIO;
return NULL;
} else
if (n == 1) {
close(datafd[0]);
close(controlfd[0]);
do {
p = waitpid(pid, NULL, 0);
} while (p == (pid_t)-1 && errno == EINTR);
errno = (int)buffer[0];
return NULL;
}
close(controlfd[0]);
out = fdopen(datafd[0], "r");
if (!out) {
close(datafd[0]);
kill(pid, SIGKILL);
do {
p = waitpid(pid, NULL, 0);
} while (p == (pid_t)-1 && errno == EINTR);
errno = EIO;
return NULL;
}
errno = 0;
return out;
}
int main(int argc, char *argv[])
{
FILE *cmd;
pid_t pid;
int c;
unsigned long bytes = 0UL;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s command [ arguments ... ]\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
cmd = exec_subproc(&pid, argv[1], argv + 1);
if (!cmd) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
while ((c = getc(cmd)) != EOF) {
bytes++;
putchar(c);
}
fflush(stdout);
fclose(cmd);
c = reap_subproc(pid);
if (errno) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
if (WIFEXITED(c) && !WEXITSTATUS(c)) {
fprintf(stderr, "%s: %lu bytes; success.\n", argv[1], bytes);
return 0;
}
if (WIFEXITED(c)) {
fprintf(stderr, "%s: %lu bytes; failed (exit status %d)\n", argv[1], bytes, WEXITSTATUS(c));
return WEXITSTATUS(c);
}
if (WIFSIGNALED(c)) {
fprintf(stderr, "%s: %lu bytes; killed by signal %d (%s)\n", argv[1], bytes, WTERMSIG(c), strsignal(WTERMSIG(c)));
return 128 + WTERMSIG(c);
}
fprintf(stderr, "%s: %lu bytes; child lost.\n", argv[1], bytes);
return EXIT_FAILURE;
}
使用例如编译
gcc -Wall -Wextra -O2 example.c -o example
然后运行例如
./example date
./example ./example date -u
./example /bin/sh -c 'date | tr A-Za-z a-zA-Z'