Exit signal received with output pending

David Fox (fox@graphics.cs.nyu.edu)
Sat, 9 Mar 1996 11:49:26 -0500


The program below demonstrates what I believe is a Linux bug. It
starts a child process and starts reading the child's standard output.
It exits when it receives a SIGCHLD signal indicating that the child
has exited. It seems to me that this signal should not be received
until end of file is received on the child's standard output.
--
David Fox	   http://found.cs.nyu.edu/fox   	xoF divaD
NYU Media Research Lab			   baL hcraeseR aideM UYN

----------------- Cut Here --------------------- // procbug.c - demonstrate a Linux kernel bug? // David Fox, NYU Media Research Lab, 9 Mar 1996. // // Run the following program with arguments like // // % ./procbug "/bin/sh" "-c" "yes | cat -n | head -2000" // // About half the time I get only 1820 lines of output. How about // you?

#include <unistd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <minmax.h> #include <sys/time.h> #include <sys/resource.h> #include <sys/wait.h>

// These macros define which end of the pipes we read from and which // we write to.

#define READEND 0 #define WRITEEND 1

class ChildProcess { public: ChildProcess(const char** argv, int infd=-1, int outfd=-1, int errfd=-1); ~ChildProcess();

// Input(), output(), and error() return the file descriptors through // which you can communicate with the process. Getpid() returns the // child's process id.

int input() {return infd_;} int output() {return outfd_;} int error() {return errfd_;} int getpid() {return pid_;} private: void Init(const char* path, const char** argv, int infd, int outfd, int errfd);

int infd_; int outfd_; int errfd_; int pid_; };

inline void doclose(int fd) { if (close(fd) == -1) { perror("close"); exit(1); } }

inline void dodup(int fd1, int fd2) { if (fd1 != fd2) {if (dup2(fd1, fd2) == -1) {perror("dup"); exit(1);}} }

ChildProcess::ChildProcess(const char** argv, int infd, int outfd, int errfd) { infd_ = infd; outfd_ = outfd; errfd_ = errfd;

int pfdin[2]; int pfdout[2]; int pfderr[2];

// Thank God I never have to write this again!

if (infd_ == -1) {if (pipe(pfdin) == -1) {perror("pipe"); exit(1);}} if (outfd_ == -1) {if (pipe(pfdout) == -1) {perror("pipe"); exit(1);}} if (errfd_ == -1) {if (pipe(pfderr) == -1) {perror("pipe"); exit(1);}}

switch (pid_ = fork()) { case -1: perror("fork"); exit(1); case 0:

// This section is executed by the child process. We close the // three standard file descriptors and dup the child end of the // three pipes so the child process standard descriptors and the // pipes are the same file.

dodup(infd_ == -1 ? pfdin[READEND] : infd_, STDIN_FILENO); dodup(outfd_ == -1 ? pfdout[WRITEEND] : outfd_, STDOUT_FILENO); dodup(errfd_ == -1 ? pfderr[WRITEEND] : errfd_, STDERR_FILENO);

// Here we close all the pipe descriptors, either because they // are the parent end of the pipe or because we have duped them // above as file descriptors 0, 1, and 2.

if (infd_ == -1) {doclose(pfdin[WRITEEND]); doclose(pfdin[READEND]);} else if (infd_ != 0) {doclose(infd_);}

if (outfd_ == -1) {doclose(pfdout[READEND]); doclose(pfdout[WRITEEND]);} else if (outfd_ != 1) {doclose(outfd_);}

if (errfd_ == -1){doclose(pfderr[READEND]); doclose(pfderr[WRITEEND]);} else if (errfd_ != 2) {doclose(errfd_);}

// Launch the child program.

execvp(argv[0], (char**)argv); perror("execvp"); // Not reached. exit(1); }

// This section is executed by the parent process.

// Close the child end of each pipe. If we opened a pipe for this // fd then assign the parent's fd. If an fd was supplied no pipe // was opened so no fd is available for the parent, set it to -1.

if (infd_ == -1) {doclose(pfdin[READEND]); infd_ = pfdin[WRITEEND];} else infd_ = -1; if (outfd_ == -1) {doclose(pfdout[WRITEEND]); outfd_ = pfdout[READEND];} else outfd_ = -1; if (errfd_ == -1) {doclose(pfderr[WRITEEND]); errfd_ = pfderr[READEND];} else errfd_ = -1; }

ChildProcess::~ChildProcess() { close(infd_); close(outfd_); close(errfd_); ::wait(0); }

void sigchld_handler(int signo) { //printf("sigchld_handler\n"); int status; int pid = wait(&status); if (WIFEXITED(status)) exit(0); }

main(int ac, char *av[]) { char buf[512];

if (av[ac] != 0) { fprintf(stderr, "Sorry, av[%d] is not NULL, I thought it would be.\n", ac); exit(1); }

ChildProcess p((const char **)&av[1]);

signal(SIGCHLD, sigchld_handler);

fd_set readfds; FD_ZERO(&readfds); FD_SET(p.output(), &readfds); FD_SET(p.error(), &readfds); while (1) { int count = 0; select(getdtablesize(), &readfds, 0, 0, 0); if (FD_ISSET(p.output(), &readfds)) { count = read(p.output(), buf, 512); if (count) write(STDOUT_FILENO, buf, count); else exit(0); } if (FD_ISSET(p.error(), &readfds)) { count = read(p.error(), buf, 512); if (count) write(STDERR_FILENO, buf, count); else exit(0); } } }