So basically, I set up a pipe and fork, and then call execl. This all
works perfectly, except for one annoying exception. It doesn't work
properly if the shell command to be executed is a daemon. In that
case, it just hangs. I can't figure out why. My understanding is
that when a daemon starts, it typically forks and then kills its
parent. Since my application has an open pipe to the parent, the call
to read() should fail when the daemon kills its parent. But instead
the application just hangs.
Here is some bare bones code that reproduces the problem:
int main(int argc, char** argv)
{
if (argc < 2) {
printf("Please specify a shell command\n");
return 0;
}
// Create a pipe and fork
//
int fd[2];
int p = pipe(fd);
pid_t pid = fork();
if (pid > 0)
{
// Read from the pipe and output the result
//
close(fd[1]);
char buf[1024] = { 0 };
read(fd[0], buf, sizeof(buf));
printf("%s\n", buf);
// Wait for child to terminate
int status;
wait(&status);
}
else if (pid == 0)
{
// Redirect stdout and stderr to the pipe and execute
the shell
// command
//
dup2(fd[1], STDOUT_FILENO);
dup2(fd[1], STDERR_FILENO);
close(fd[0]);
execl("/bin/sh", "sh", "-c", argv[1], 0);
}
}
The code works fine if you use it with a normal shell command. But if
you try to run a daemon, it just hangs instead of returning to the
prompt as it should.
> if (pid > 0)
> {
> // Read from the pipe and output the result
> //
> close(fd[1]);
> char buf[1024] = { 0 };
> read(fd[0], buf, sizeof(buf));
> printf("%s\n", buf);
>
> // Wait for child to terminate
> int status;
> wait(&status);
> }
This can deadlock. The child may be waiting for you to read from its
output before it terminates and you are waiting for it to terminate.
> else if (pid == 0)
> {
> // Redirect stdout and stderr to the pipe and execute
> the shell
> // command
> //
> dup2(fd[1], STDOUT_FILENO);
> dup2(fd[1], STDERR_FILENO);
> close(fd[0]);
> execl("/bin/sh", "sh", "-c", argv[1], 0);
> }
Why don't you close fd(1)?
DS
No, typically it forks and the parent *exits*. Unless it's trying
to write data down a pipe that the application isn't reading.
>Since my application has an open pipe to the parent, the call
>to read() should fail when the daemon kills its parent. But instead
>the application just hangs.
A read() on a pipe will return with end-of-file when *all* of the
write ends of the pipe have been closed. The parent may pass on
the pipe to its child. If it's a daemon, it will typically close
stdin, stdout, and stderr and re-open them as /dev/null, but if the
pipe got passed as fd 3 or higher, it might still be hanging.
Another possible problem is that the application reading the pipe
has failed to close its write end, which would cause the deadlock
you are seeing.
You really need to close fd[1] here.
Not related to the problem, which David and Gordon have pointed out, but
you should cast the last argument so that it is passed as a pointer to
char, as expected by execl, and not an int:
execl("/bin/sh", "sh", "-c", argv[1], (char *)0);
(Because execl takes variable arguments, there is no way to tell the
compiler what the types should be; the rules are such that a bare "0"
will be passed as an int.)
If you don't cast, bad things may happen on a system with a different
size and/or representation for the integer 0 and a null pointer to char.
Alex
Good call. I have spent a substantial amount of time and energy fixing
this bug in various programs. It causes them to break on my amd64
machine.
By the way, if you use gcc and your system headers are properly
instrumented, -Wformat (enabled by -Wall) will warn about this.