child processes and filehandles


Note: I may have stolen this idea from Gnat and Tom’s Perl Cookbook or some other high profile source. However, this tidbit is useful to publicize. The code below is my own.

Here’s a random Perl tip that you may find helpful. In certain flavors of UNIX, including Linux and Solaris, the ownership of open filehandles is inherited by child processes. Although this doesn’t seem all that useful at first, consider the case in which you have a forking server that writes to a log file. You’d like all the child processes to write to that log file too, but the children run as a different user. One solution is to open a filehandle in the parent as a globally visible variable (both lexically scoped scalars and package-global filehandles work). After the parent forks and changes real and effective UIDs, the child process has that same open filehandle. Take a look at the code sample below. There is no tricky Perl here (except for setting the autoflush to prevent a weird extra copy of sample string from appearing in the outfile when the process forks [I don’t know why I had to do this, but it seems to be an stdio buffering issue]).

!/usr/bin/env perl


unless ($> == 0) { print “need to be root to run this (child process changes UID)\n”; exit; }

$< = $> = 0; unlink ‘testing’;

print “Current real ID: $>\n”, “Opening filehandle\n”; open my $in, “>testing” or die “can’t open ‘testing’ $!\n”; select((select($in), $|++)[0]);

print $in localtime() . “: $< says ‘hello’ from $$\n”;

my ($nobody_uid) = (getpwnam(‘nobody’))[2]; print “ID of ‘nobody’: $nobody_uid\n”;

print “Forking and changing real and effective ID to $nobody_uid\n”;

$SIG{CHLD} = sub { wait }; my $pid = fork;

if ($pid) { sleep(1); print $in localtime() . “: $< says ‘hello’ from $$\n”; close $in; exit; }

sleep(3); $< = $> = $nobody_uid;

print $in localtime() . “: $< says ‘hello’ from $$\n”; ; close $in;

try appending to file as this user

open my $try, “>>testing” or die “can’t open ‘testing’: $!\n”; print $try “I can write to this file and I’m $<\n”; close $try;

If you run this code on a compatible system, your ‘testing’ outfile should have two lines in it from different processes and different UIDs. Of course, you need to be aware of the lurking dangers of multiple processes trying to write to the same file at the same time. This resource contention issue may be solved with a variety of IPC synchronization techniques, such as semaphores and lock files. I like to use lock files, although I find flock a bit cumbersome. Unfortunately, semaphores are even more troublesome, as a quick look at the perlipc manpage reveals. Conceptually simple, semaphors require some weird pack and unpack formats and IPC::SysV constants to work.


Now, you have one less excuse to run your perl daemons as root.

[Original post and comments.]