
![]() | ![]() |
16.11. Making a Process Look Like a File with Named Pipes
16.11.1. Problem
You want a process to intercept all
access to a file. For instance, you want to make your
~/.plan file a program that returns a random
quote.
16.11.2. Solution
Use named pipes. First create one, probably from your shell:% mkfifo /path/to/named.pipe
Here's a reader for it:open($fifo, "<", "/path/to/named.pipe") or die $!;
while (<$fifo>) {
print "Got: $_";
}
close $fifo;
Here's a writer for it:open($fifo, ">", "/path/to/named.pipe") or die $!;
print $fifo "Smoke this.\n";
close $fifo;
16.11.3. Discussion
A named pipe, or FIFO as they are also known, is a special file that
acts as a buffer to connect processes on the same machine. Ordinary
pipes also allow processes to communicate, but those processes must
have inherited the filehandles from their parents. To use a named
pipe, a process need know only the named pipe's filename. In most
cases, processes don't even need to be aware that they're reading
from a FIFO.Named pipes can be read from and written to just as though they were
ordinary files (unlike Unix-domain sockets as discussed in Chapter 17). Data written into the FIFO is buffered up by
the operating system, then read back in the order it was written in.
Because a FIFO acts as a buffer to connect processes, opening one for
reading will block until another process opens it for writing, and
vice versa. If you open for read and write using
the +< mode to open, you won't block (on most
systems), because your process could be both reader and writer.Let's examine how to use a named pipe so people will get a different
file each time they finger you. To create a
named pipe, use mkfifo or
mknod to create a named pipe called
.plan in your home directory:% mkfifo ~/.plan # isn't this everywhere yet?
% mknod ~/.plan p # in case you don't have mkfifo
On some systems, you must use mknod(8). The
location and names of these programs aren't uniform or necessarily
obvious, so consult your system documentation to find out where these
programs are.The next step is to create a program to feed data to the programs
that read from your ~/.plan file. We'll just
print the date and time, as shown in Example 16-9.
Example 16-9. dateplan
#!/usr/bin/perl -w
# dateplan - place current date and time in .plan file
while (1) {
open($fifo, "> $ENV{HOME}/.plan")
or die "Couldn't open $ENV{HOME}/.plan for writing: $!\n";
print $fifo "The current time is ", scalar(localtime), "\n";
close $fifo;
sleep 1;
}
Unfortunately, this won't always work, because some
finger programs and their attendant daemons
check the size of the .plan file before trying
to read it. Because named pipes appear as special files of zero size
on the filesystem, such clients and servers will not try to open or
read from our named pipe, and the trick will fail.In our .plan example, the writer was a daemon.
It's not uncommon for readers to be daemons as well. Take, for
instance, the use of a named pipe to centralize logging from many
processes. The log server reads log messages from the named pipe and
can send them to a database or file. Clients write their messages to
the named pipe. This removes the distribution logic from the clients,
making changes to message distribution easy to implement.Example 16-10 is a simple program to read two-line
messages where the first line is the name of the service and the
second line is the message being logged. All messages from
httpd are ignored, while all messages from
login are saved to
/var/log/login.
Example 16-10. fifolog
#!/usr/bin/perl -w
# fifolog - read and record log msgs from fifo
$SIG{ALRM} = sub { close(FIFO) }; # move on to the next queued process
while (1) {
alarm(0); # turn off alarm for blocking open
open($fifo, "</tmp/log") or die "Can't open /tmp/log : $!\n";
alarm(1); # you have 1 second to log
$service = <$fifo>;
next unless defined $service; # interrupted or nothing logged
chomp $service;
$message = <$fifo>;
next unless defined $message; # interrupted or nothing logged
chomp $message;
alarm(0); # turn off alarms for message processing
if ($service eq "http") {
# ignoring
} elsif ($service eq "login") {
# log to /var/log/login
if ( open($log, ">> /tmp/login") ) {
print $log scalar(localtime), " $service $message\n";
close $log;
} else {
warn "Couldn't log $service $message to /var/log/login : $!\n";
}
}
}
This program is more complicated than the .plan
program for several reasons. First and foremost, we don't want our
logging server to block would-be writers for long. It's easy to
imagine a situation where an attacker or misbehaving writer opens the
named pipe for writing, but doesn't send a complete message. To
prevent this, we use alarm and
SIGALRM to signal us if we get stuck reading.Only two exceptional conditions can happen when using named pipes: a
writer can have its reader disappear, or vice versa. If a process is
reading from a named pipe and the writer closes its end, the reading
process will get an end-of-file (<> returns
undef). If the reader closes the connection,
though, the writer will get a SIGPIPE when it next
tries to write there. If you disregard broken pipe signals with
$SIG{PIPE} =
'IGNORE', your print will
return a false value and $! will be set to
EPIPE:use POSIX qw(:errno_h);
$SIG{PIPE} = 'IGNORE';
# ...
$status = print $fifo "Are you there?\n";
if (!$status && $! = = EPIPE) {
warn "My reader has forsaken me!\n";
next;
}
You may be asking "If I have 100 processes all trying simultaneously
to write to this server, how can I be sure that I'll get 100 separate
entries and not a jumbled mishmash with characters or lines from
different processes?" That's a good question. The POSIX standard says
that writes of less than PIPE_BUF bytes in size
will be delivered atomically, i.e., not jumbled. You can get the
PIPE_BUF constant from POSIX:use POSIX;
print PIPE_BUF, "\n";
Fortunately, the POSIX standard also requires
PIPE_BUF to be at least 512
bytes. This means that all we have to do is ensure that our clients
don't try to log more than 512 bytes at a time.What if you want to log more than 512 bytes at a time? Then you split
each large message into several smaller (fewer than 512 bytes)
messages, preface each with the unique client identifier (process ID,
say), and have the server reassemble them. This is similar to the
processing involved in TCP/IP message fragmentation and reassembly.Because a single named pipe doesn't allow bidirectional access
between writer and reader, authentication and similar ways of
preventing forged messages are hard to do (if not impossible). Rather
than struggle to force such things on top of a model that doesn't
accommodate them, you are better off using the filesystem's access
control to restrict access to the file through the owner and group
permissions on the named pipe.
16.11.4. See Also
mkfifo(8) or mknod(8) (if
you have them); Recipe 17.6
![]() | ![]() | ![]() |
16.10. Communicating Between Related Processes | ![]() | 16.12. Sharing Variables in Different Processes |

Copyright © 2003 O'Reilly & Associates. All rights reserved.