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;
    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
	} 
	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 back-ticks 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 back-tick 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{ALRM} = 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
    } 

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