- publishing free software manuals
Perl Language Reference Manual
by Larry Wall and others
Paperback (6"x9"), 724 pages
ISBN 9781906966027
RRP £29.95 ($39.95)

Sales of this book support The Perl Foundation! Get a printed copy>>>

20.3.4 Safe Pipe Opens

Another interesting approach to IPC is making your single program go multiprocess and communicate between (or even amongst) yourselves. The open() function will accept a file argument of either "-|" or "|-" to do a very interesting thing: it forks a child connected to the filehandle you've opened. The child is running the same program as the parent. This is useful for safely opening a file when running under an assumed UID or GID, for example. If you open a pipe to minus, you can write to the filehandle you opened and your kid will find it in his STDIN. If you open a pipe from minus, you can read from the filehandle you opened whatever your kid writes to his STDOUT.

use English '-no_match_vars';
my $sleep_count = 0;
do {
    $pid = open(KID_TO_WRITE, "|-");
    unless (defined $pid) {
        warn "cannot fork: $!";
        die "bailing out" if $sleep_count++ > 6;
        sleep 10;
    }
} until defined $pid;
if ($pid) {  # parent
    print KID_TO_WRITE @some_data;
    close(KID_TO_WRITE) || warn "kid exited $?";
} else {     # child
    ($EUID, $EGID) = ($UID, $GID); # suid progs only
    open (FILE, "> /safe/file")
        || die "can't open /safe/file: $!";
    while (<STDIN>) {
        print FILE; # child's STDIN is parent's KID_TO_WRITE
    }
    exit;  # don't forget this
}

Another common use for this construct is when you need to execute something without the shell's interference. With system(), it's straightforward, but you can't use a pipe open or backticks safely. That's because there's no way to stop the shell from getting its hands on your arguments. Instead, use lower-level control to call exec() directly.

Here's a safe backtick or pipe open for read:

# add error processing as above
$pid = open(KID_TO_READ, "-|");
if ($pid) {   # parent
    while (<KID_TO_READ>) {
        # do something interesting
    }
    close(KID_TO_READ) || warn "kid exited $?";
} else {      # child
    ($EUID, $EGID) = ($UID, $GID); # suid only
    exec($program, @options, @args)
        || die "can't exec program: $!";
    # NOTREACHED
}

And here's a safe pipe open for writing:

# add error processing as above
$pid = open(KID_TO_WRITE, "|-");
$SIG{PIPE} = sub { die "whoops, $program pipe broke" };
if ($pid) {  # parent
    for (@data) {
        print KID_TO_WRITE;
    }
    close(KID_TO_WRITE) || warn "kid exited $?";
} else {     # child
    ($EUID, $EGID) = ($UID, $GID);
    exec($program, @options, @args)
        || die "can't exec program: $!";
    # NOTREACHED
}

It is very easy to dead-lock a process using this form of open(), or indeed any use of pipe() and multiple sub-processes. The above example is 'safe' because it is simple and calls exec(). See 20.3.5 for general safety principles, but there are extra gotchas with Safe Pipe Opens.

In particular, if you opened the pipe using open FH, "|-", then you cannot simply use close() in the parent process to close an unwanted writer. Consider this code:

$pid = open WRITER, "|-";
defined $pid or die "fork failed; $!";
if ($pid) {
    if (my $sub_pid = fork()) {
        close WRITER;
        # do something else...
    }
    else {
        # write to WRITER...
        exit;
    }
}
else {
    # do something with STDIN...
    exit;
}

In the above, the true parent does not want to write to the WRITER filehandle, so it closes it. However, because WRITER was opened using open FH, "|-", it has a special behaviour: closing it will call waitpid() (see ), which waits for the sub-process to exit. If the child process ends up waiting for something happening in the section marked "do something else", then you have a deadlock.

This can also be a problem with intermediate sub-processes in more complicated code, which will call waitpid() on all open filehandles during global destruction; in no predictable order.

To solve this, you must manually use pipe(), fork(), and the form of open() which sets one file descriptor to another, as below:

pipe(READER, WRITER);
$pid = fork();
defined $pid or die "fork failed; $!";
if ($pid) {
    close READER;
    if (my $sub_pid = fork()) {
        close WRITER;
    }
    else {
        # write to WRITER...
        exit;
    }
    # write to WRITER...
}
else {
    open STDIN, "<&READER";
    close WRITER;
    # do something...
    exit;
}

Since Perl 5.8.0, you can also use the list form of open for pipes : the syntax

open KID_PS, "-|", "ps", "aux" or die $!;

forks the ps(1) command (without spawning a shell, as there are more than three arguments to open()), and reads its standard output via the KID_PS filehandle. The corresponding syntax to write to command pipes (with "|-" in place of "-|") is also implemented.

Note that these operations are full Unix forks, which means they may not be correctly implemented on alien systems. Additionally, these are not true multithreading. If you'd like to learn more about threading, see the modules file mentioned below in the SEE ALSO section.

ISBN 9781906966027Perl Language Reference ManualSee the print edition