[net.sources] ``file descriptor'' pseudo-device driver for 4.2BSD

fred@gymble.UUCP (Fred Blonder) (06/21/85)

I'm re-posting this due to popular demand:

This is a pseudo-device driver which implements a
series of devices: /dev/fd0 - /dev/fd19 which are
surreptitiously equivalenced to whatever file (if
any) is associated with the corresponding file
descriptor in the process which is attempting to
open that file. In other words: opening the file
/dev/fd<n> is almost the same as doing the system
call: dup(n). It's different in that if the file
is a device, the driver's open routine is called.
If the corresponding file descriptor is a pipe
(socket) a dup() sys call is faked.

This can be used to force programs to read from
their standard input, or write to their standard
output, even if they haven't been designed to do
so.  For instance:

	ln -s /dev/fd0 xx.c
	cc xx.c
	{ Type a C program here. }
	^D
	a.out

Or, to see if a certain source file compiled into a
particular object file:

	cc source.c -o /dev/fd1 | cmp - objectfile

Be sure to make these mods in your conf directory.

In the config file:
	pseudo-device	fd

In the ``files'' file:
	sys/dev_fd.c		optional fd

<**************************************************************>

: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting dev_fd.c'
sed 's/^X//' <<'//go.sysin dd *' >dev_fd.c
#ifndef lint
static char rcsid[] = "@(#)$Header: /usr/sys/sys/RCS/dev_fd.c,v 1.1 84/12/01 21:38:17 chris Exp $";
#endif lint

X/*
 * fd.c		Fred Blonder - U of Maryland	11-Sep-1984
 *
 * ``File Descriptor'' pseudo-device driver, rewritten for Berkeley 4.2.
 *
 * Opening minor device N opens the file (if any) connected to file-descriptor
 * N belonging to the calling process. Note that this driver consists of only
 * the ``open()'' routine, because all subsequent references to this file will
 * be direct to the other driver.
 */

#include "fd.h"
#if NFD > 0

#include "../h/param.h"
#include "../h/inode.h"
#include "../h/file.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/errno.h"

X/*
 * THIS CODE NEEDS CLEANING AS SOON AS ASSIGNMENTS TO u.u_* GO AWAY
 */

fdopen(dev, mode)
dev_t dev;
int mode;
{
	struct file *fp, *wfp;
	struct inode *ip, *wip;
	int rwmode, error;

	/*
	 * Note the horrid kludge here: u.u_r.r_val1 contains the value
	 * of the new file descriptor, which has not been disturbed since
	 * it was allocated.
	 */
	if ((fp = getf(u.u_r.r_val1)) == NULL)
		return (u.u_error);

	if ((wfp = getf(minor(dev))) == NULL)
		return (u.u_error);

	/*
	 * We must explicitly test for this case because ufalloc() may
	 * have allocated us the same file desriptor we are referring
	 * to, if the proccess referred to an invalid (closed) descriptor.
	 * Ordinarily this would be caught by getf(), but by the time we
	 * reach this routine u_pofile[minor(dev)] could already be set
	 * to point to our file struct.
	 */
	if (fp == wfp)
		return (EBADF);

	ip = (struct inode *)fp->f_data;

	/*
	 * Fake a ``dup()'' sys call if it isn't an inode.
	 */
	if (wfp->f_type != DTYPE_INODE) {
		/*
		 * Check that the mode the file is being opened
		 * for is consistent with the mode of the existing
		 * descriptor. This isn't as clean as it should be,
		 * but this entire driver is a real kludge anyway.
		 */
		rwmode = mode & (FREAD|FWRITE);
		if ((fp->f_flag & rwmode) != rwmode)
			return (EACCES);

		/* Delete references to this pseudo-device. */
		irele(ip);		/* Chuck the inode. */
		fp->f_count = 0;	/* Chuck the file structure. */
		/* Dup the file descriptor. */
		dupit(u.u_r.r_val1, wfp, u.u_pofile[minor(dev)]);
		return (0);
	}

	error = 0;
	wip = (struct inode *)wfp->f_data;

	/*
	 * I'm not sure that we really need to lock the inode here,
	 * but why not be paranoid?
	 */
	ilock(wip);

	/*
	 * Since we're opening a file again, we run through all the
	 * permission checks so this can't be used as a loophole to
	 * get access to a file we shouldn't have.  (GROT)
	 */
	if (mode & FREAD && access(wip, IREAD))
		goto bad;
	if (mode & (FWRITE|FTRUNC)) {
		if ((ip->i_mode&IFMT) == IFDIR) {
			error = EISDIR;
			goto bad;
		}
		if (access(wip, IWRITE))
			goto bad;
	}

	/*
	 * The file must always exist, so we don't even bother testing
	 * for its presence.
	 */
	if ((mode & (FCREAT|FEXCL)) == (FCREAT|FEXCL)) {
		error = EEXIST;
		goto bad;
	}

	/*
	 * This may not make any sense, but I'm paranoid and figure that
	 * it's probably an error.
	 */
	if (mode & FTRUNC) {
		error = EBUSY;
		goto bad;
	}

	/* Call the device-specific open routine, if any. */
	if ((error = openi(wip, mode)) != 0)
		goto bad;

	/*
	 * Made it this far, now switch the inode pointers in the
	 * file descriptors around, to make this file open refer
	 * to the other file.
	 */
	irele(ip);		/* We don't need this anymore. */
	fp->f_data = (caddr_t)wip;
	wip->i_count++;
	iunlock(wip);
	return (0);

bad:
	iunlock(wip);
	return (error);
}

#endif NFD > 0
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 dev_fd.c
	/bin/echo -n '	'; /bin/ls -ld dev_fd.c
fi
/bin/echo 'Extracting fd.4'
sed 's/^X//' <<'//go.sysin dd *' >fd.4
X.TH FD 4l "13-Oct-1984 (U of Maryland)"
X.UC 4
X.SH NAME
X/dev/fd* \- ``file descriptor'' driver
X.SH DESCRIPTION
X.lg
X.I /dev/fd0
-
X.I /dev/fd19
are special files that reference the files associated with a process'
open file descriptors.
That is, opening the file: /dev/fd\fIN\fP is equivalent to opening whatever
file you opened to get the file descriptor \fIN\fP, even though you may not
know the file's true name (or even if it has one).
This can be used to force a program which opens files by name, to connect
itself to open file descriptors (which you have thoughtfully provided) in
weird and wonderful ways. A simple use would be to force a program which
only accepts input from files, to read from its standard input.
For instance:
X.sp
X.ti +10
cat /dev/fd0
X.sp
produces the same result as:
X.sp
X.ti +10
cat -
X.sp
which would be useful if \fIcat\fP didn't use the ``-'' convention.
X.sp 2
If the open file descriptor references a socket, the driver fakes a ``dup()''
system call instead of actually opening a file. In this case it
checks to see that the read/write mode you are attempting to open
the file with, is compatible with the mode of the existing file descriptor.
That is: if descriptor 5 refers to a socket and
is open for writing, you cannot open ``/dev/fd5''
for reading.
X.SH AUTHOR
Fred Blonder <Fred@Maryland>
X.SH FILES
X/dev/fd*
X.SH BUGS
Since, for sockets,
the driver fakes a ``dup'' system call rather than actually re-opening
the file, the new descriptor is a copy of the dup-ed descriptor, and shares
its properties. Specifically: it will have the same read/write mode as the
dup-ed descriptor. If descriptor 0 is open for reading and writing, opening
``/dev/fd0'' for reading will return a writable, as well as readable, file
descriptor.
Also: the descriptors share the same read/write pointer, so seeks, reads &
writes on one will affect the other.
X.sp
While not a bug in the driver specifically, use of these files can produce odd
interactions with programs which don't expect to have their file
descriptors surreptitiously aliased.
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 fd.4
	/bin/echo -n '	'; /bin/ls -ld fd.4
fi
-- 
All characters mentioned herein are fictitious. Any similarity to
actual characters, ASCII or EBCDIC is purely coincidental.

						Fred Blonder (301) 454-7690
						Fred@Maryland.{ARPA,CSNet}
						harpo!seismo!umcp-cs!fred

smb@ulysses.UUCP (Steven Bellovin) (06/22/85)

The 8th Edition convention is to use /dev/fd/N, not /dev/fdN.  Some versions
of the Korn shell and the Eighth edition shell know about this naming convention;
they support things like

	diff (rsh a cat /etc/passwd) (rsh b cat /etc/passwd)
	
/dev/fd/0, /dev/fd/1, and /dev/fd/2 are also known as /dev/stdin, /dev/stdout, and
/dev/stderr.