Как отметил Тадман, проще использовать командную структуру для передачи вещей.
Мы не можем [ну, мы могли бы , но не должны ] сделать wait
[в родительском] во время конвейера конструкция .Это должен быть отдельный цикл позже.Мы повесим родителя после того, как будет создан первый дочерний элемент.
Если первый дочерний элемент имеет большой объем выходных данных, канальные буферы kernel могут заполниться, а первый дочерний элемент заблокируется.Но, поскольку второму дочернему элементу не был создан, нечего читать / истощать вывод первого дочернего элемента и разблокировать его.
Кроме того, важно закрыть блоки каналов после выполненияdup2
и убедитесь, что предыдущие блоки этапа конвейера закрыты в родительском элементе.
Вот реорганизованная версия, которая делает все это.
Что касается вашей первоначальной проблемы с утечкой дескриптора файла, I думаю Я исправил это, добавив еще несколько close
вызовов.Программа имеет некоторый код самопроверки:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <sys/wait.h>
#define FREEME(ptr_) \
do { \
if (ptr_ == NULL) \
break; \
free(ptr_); \
ptr_ = NULL; \
} while (0)
#define CLOSEME(fd_) \
do { \
if (fd_ < 0) \
break; \
close(fd_); \
fd_ = -1; \
} while (0)
// command control
typedef struct {
unsigned int cmd_opt; // options
int cmd_cldno; // child number
char *cmd_buf; // command buffer
int cmd_argc; // argument count
char **cmd_argv; // arguments
int cmd_pipe[2]; // pipe units
pid_t cmd_pid; // child pid number
int cmd_status; // child status
} cmd_t;
#define CMD_FIRST (1u << 0)
#define CMD_LAST (1u << 1)
char linebuf[1000];
int cmdcount;
cmd_t *cmdlist;
int opt_d;
int opt_l;
#define dbg(fmt_...) \
do { \
if (opt_d) \
printf(fmt_); \
} while (0)
// show open fd's
void
fdshow1(int cldid)
{
char buf[100];
fprintf(stderr,"CLD: %d\n",cldid);
sprintf(buf,"ls -l /proc/%d/fd 1>&2",getpid());
system(buf);
}
// show open fd's
void
fdshow2(int cldid)
{
char dir[100];
char lnkfm[1000];
char lnkto[1000];
int len;
DIR *xf;
struct dirent *ent;
char *bp;
char obuf[1000];
sprintf(dir,"/proc/%d/fd",getpid());
xf = opendir(dir);
bp = obuf;
bp += sprintf(bp,"%d:",cldid);
while (1) {
ent = readdir(xf);
if (ent == NULL)
break;
if (strcmp(ent->d_name,".") == 0)
continue;
if (strcmp(ent->d_name,"..") == 0)
continue;
sprintf(lnkfm,"%s/%s",dir,ent->d_name);
len = readlink(lnkfm,lnkto,sizeof(lnkto));
lnkto[len] = 0;
if (strstr(lnkto,"pipe") != 0)
bp += sprintf(bp," %s-->%s",ent->d_name,lnkto);
switch (ent->d_type) {
case DT_FIFO:
break;
}
}
bp += sprintf(bp,"\n");
fputs(obuf,stderr);
fflush(stderr);
closedir(xf);
}
// show open fd's
void
fdshow(int cldid)
{
fdshow2(cldid);
}
// pipeadd -- add single command to pipe
void
pipeadd(char *buf)
{
char *cp;
char *bp;
char *sv;
cmd_t *cmd;
dbg("pipeadd: buf='%s'\n",buf);
cmdlist = realloc(cmdlist,(cmdcount + 1) * sizeof(cmd_t));
cmd = &cmdlist[cmdcount];
memset(cmd,0,sizeof(cmd_t));
cmd->cmd_pipe[0] = -1;
cmd->cmd_pipe[1] = -1;
cmd->cmd_cldno = cmdcount;
++cmdcount;
bp = buf;
while (1) {
cp = strtok_r(bp," \t",&sv);
bp = NULL;
if (cp == NULL)
break;
cmd->cmd_argv = realloc(cmd->cmd_argv,
(cmd->cmd_argc + 2) * sizeof(char **));
cmd->cmd_argv[cmd->cmd_argc + 0] = cp;
cmd->cmd_argv[cmd->cmd_argc + 1] = NULL;
cmd->cmd_argc += 1;
}
}
// pipesplit -- read in and split up command
void
pipesplit(void)
{
char *cp;
char *bp;
char *sv;
cmd_t *cmd;
printf("> ");
fflush(stdout);
fgets(linebuf,sizeof(linebuf),stdin);
cp = strchr(linebuf,'\n');
if (cp != NULL)
*cp = 0;
bp = linebuf;
while (1) {
cp = strtok_r(bp,"|",&sv);
bp = NULL;
if (cp == NULL)
break;
pipeadd(cp);
}
cmd = &cmdlist[0];
cmd->cmd_opt |= CMD_FIRST;
cmd = &cmdlist[cmdcount - 1];
cmd->cmd_opt |= CMD_LAST;
if (opt_d) {
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
dbg("%d:",cmd->cmd_cldno);
for (int argc = 0; argc < cmd->cmd_argc; ++argc)
dbg(" '%s'",cmd->cmd_argv[argc]);
dbg("\n");
}
}
}
// pipefork -- fork elements of pipe
void
pipefork(void)
{
cmd_t *cmd;
int fdprev = -1;
int fdpipe[2] = { -1, -1 };
for (cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
// both parent and child should close output side of previous pipe
CLOSEME(fdpipe[1]);
// create a new pipe for the output of the current child
if (cmd->cmd_opt & CMD_LAST) {
fdpipe[0] = -1;
fdpipe[1] = -1;
}
else
pipe(fdpipe);
cmd->cmd_pid = fork();
if (cmd->cmd_pid < 0) {
printf("pipefork: fork fail -- %s\n",strerror(errno));
exit(1);
}
// parent the input side for the next pipe stage
if (cmd->cmd_pid != 0) {
CLOSEME(fdprev);
fdprev = fdpipe[0];
continue;
}
// connect up our input to previous pipe stage's output
if (fdprev >= 0) {
dup2(fdprev,0);
CLOSEME(fdprev);
}
// connect output side of our pipe to stdout
if (fdpipe[1] >= 0) {
dup2(fdpipe[1],1);
CLOSEME(fdpipe[1]);
}
// child doesn't care about reading its own output
CLOSEME(fdpipe[0]);
if (opt_l)
fdshow(cmd->cmd_cldno);
// off we go ...
execvp(cmd->cmd_argv[0],cmd->cmd_argv);
}
CLOSEME(fdpipe[0]);
CLOSEME(fdpipe[1]);
if (opt_l)
fdshow(-1);
}
// pipewait -- wait for pipe stages to complete
void
pipewait(void)
{
pid_t pid;
int status;
int donecnt = 0;
while (donecnt < cmdcount) {
pid = waitpid(0,&status,0);
if (pid < 0)
break;
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
if (pid == cmd->cmd_pid) {
cmd->cmd_status = status;
++donecnt;
break;
}
}
}
}
// pipeclean -- free all storage
void
pipeclean(void)
{
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd)
FREEME(cmd->cmd_argv);
FREEME(cmdlist);
cmdcount = 0;
}
// main -- main program
int
main(int argc,char **argv)
{
char *cp;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'd':
opt_d = ! opt_d;
break;
case 'l':
opt_l = ! opt_l;
break;
default:
break;
}
}
while (1) {
pipesplit();
pipefork();
pipewait();
pipeclean();
}
return 0;
}