[comp.sources.unix] v10i098: A /dev/fd device driver for 4.3 and NFS systems

rs@uunet.UU.NET (Rich Salz) (08/07/87)

Submitted-by: emory!arnold (Arnold D. Robbins {EUCC})
Posting-number: Volume 10, Issue 98
Archive-name: dev.fd

[  The code here implements /dev/fd/0, /dev/fd/12, etc., so that a program
   can treat its file descriptors as files; the README-FIRST describes
   some possible tricks that one can now play with, e.g., KSH, as a result
   of this.  --r$  ]

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README-FIRST
#	README
#	fd.4
#	dev_fd.c
#	PATCH
export PATH; PATH=/bin:$PATH
echo shar: extracting "'README-FIRST'" '(2045 characters)'
if test -f 'README-FIRST'
then
	echo shar: will not over-write existing file "'README-FIRST'"
else
cat << \SHAR_EOF > 'README-FIRST'
README-FIRST

	The files in this directory contain the source code for the "fd"
pseudo-device driver for 4.[23] BSD Unix. The idea originated with the Eighth
Edition Research Unix system.
	The original version for BSD Unix was done by Fred Blonder at the
University of Maryland. He simply had a bunch of devices, /dev/fd0 - /dev/fd19.
The Research Unix system uses a /dev/fd directory, with files 0 - 19.
	The latter version of the mechanism is used by the Korn shell (ksh)
for "process substitution", a feature whereby programs can read/write files
which are really pipes from/to other processes.  This mechanism allows one
to have pipelines that move data in more than one dimension. Once this
driver is installed, the ksh needs to be recompiled. Process substitution
is new in the version of the ksh known as "ksh-i". I don't know if the older
ksh had it as an undocumented feature.
	To allow the ksh to use the fd driver, and for compatibility with
Research Unix, I have changed the fd.4 man page to refer to the /dev/fd
directory.

	This version of the fd driver now includes code for NFS systems.
Unfortunately, for reasons detailed in the source code, the NFS version
requires a small patch to the stock system code. The file that is affected
is /sys/specfs/spec_vnodeops.c.  This patch is included in the file PATCH.
This patch should not affect any other system code (famous last words!).
Besides the NFS version, I have added some additional error checking, and
a bug fix to the inode version as well. This is commented, so if someone
wants the exact original code, they can change it back.

	The NFS version works without change on both Sun systems running
SunOS 3.3, and Mt. Xinu 4.3 + NFS 3.2. On the Sun, "make depend" will not
work because of where the inode.h file is included from if NFS is not
defined. This is not a very big deal, just comment out that line.  The
interaction with the file locking mechanisms is not clear. (i.e. I didn't
bother to investigate it.)

Arnold Robbins
Emory University Computing Center
July, 1987
SHAR_EOF
chmod +x 'README-FIRST'
fi # end of overwriting check
echo shar: extracting "'README'" '(1781 characters)'
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat << \SHAR_EOF > 'README'
From fred@gymble.UUCP (Fred Blonder) Fri Jun 21 00:34:53 1985
Relay-Version: version B 2.10.2 9/18/84; site gatech.CSNET
Posting-Version: version B 2.10.1 6/24/83; site gymble.UUCP
Path: gatech!akgua!whuxlm!whuxl!houxm!ihnp4!mhuxn!mhuxr!ulysses!allegra!mit-eddie!genrad!panda!talcott!harvard!seismo!umcp-cs!gymble!fred
From: fred@gymble.UUCP (Fred Blonder)
Newsgroups: net.sources
Subject: ``file descriptor'' pseudo-device driver for 4.2BSD
Message-ID: <173@gymble.UUCP>
Date: 21 Jun 85 04:34:53 GMT
Date-Received: 23 Jun 85 03:29:53 GMT
Distribution: net
Organization: U of Maryland, Laboratory for Parallel Computation, C.P., MD
Lines: 272

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

<**************************************************************>
SHAR_EOF
chmod +x 'README'
fi # end of overwriting check
echo shar: extracting "'fd.4'" '(3615 characters)'
if test -f 'fd.4'
then
	echo shar: will not over-write existing file "'fd.4'"
else
cat << \SHAR_EOF > 'fd.4'
...
... $Header: fd.4,v 1.3 87/07/10 10:20:26 root Locked $
... 
... $Log:	fd.4,v $
... Revision 1.3  87/07/10  10:20:26  root
... Added ERRORS section. ADR.
... 
... Revision 1.2  87/07/05  12:05:49  root
... Added synopsis entry, changed for /dev/fd as a directory, added notes
... about ksh, /dev/std{in, out, err} and only having 20 entries. ADR.
... 
... Revision 1.1  87/07/02  15:28:01  root
... Initial revision
... 
...
.TH FD 4 EUCC
.UC 4
.SH NAME
/dev/fd/* \- ``file descriptor'' driver
.SH SYNOPSIS
.B "pseudo-device	fd"
.SH DESCRIPTION
.B /dev/fd/0
-
.B /dev/fd/19
are special files that reference the files associated with a process'
open file descriptors.
That is, opening the file: \fB/dev/fd/\fIN\fR is equivalent to opening whatever
file you opened to get the file descriptor
.IR N ,
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:
.sp
.ti +10
cat /dev/fd/0
.sp
produces the same result as:
.sp
.ti +10
cat -
.sp
which would be useful if
.IR cat (1)
didn't use the ``-'' convention.
.PP
This driver is particularly useful for enabling the ``process substitution''
mechanism in
.IR ksh (1).
.PP
If the open file descriptor references a socket, the driver fakes a
.IR dup (2)
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
.B /dev/fd/5
for reading.
.PP
As a notational convenience, the files
.BR /dev/stdin ,
.BR /dev/stdout ,
and
.B /dev/stderr
are provided as hard links to
.BR /dev/fd/0 ,
.BR /dev/fd/1 ,
and
.BR /dev/fd/2
respectively.
.SH ERRORS
The named file descriptor will be ``opened'' as described above unless
one of the following is true.
.PP
.TP 15
[ENXIO]
The file descriptor attempting to be reopened is larger than the
maximum number of open files that a process may have.
(E.g., attempting to open file descriptor 65 on a 4.3BSD system.)
.TP 15
[EBADF]
The file descriptor attempting to be reopened is not an open file.
.PP
Other errors as would be returned by
.IR access (2),
and
.IR open (2).
.SH SEE ALSO
.IR ksh (1),
.IR access (2),
.IR dup (2),
.IR open (2).
.SH AUTHOR
Fred Blonder <Fred@mimsy.umd.EDU>
.br
NFS version by Arnold Robbins <arnold@emory.EDU>
.SH FILES
.BR /dev/fd/* ,
.BR /dev/stdin ,
.BR /dev/stdout ,
.B /dev/stderr
.SH BUGS
Since, for sockets,
the driver fakes a
.I dup
system call rather than actually re-opening
the file, the new descriptor is a copy of the
.IR dup 'ed
descriptor, and shares
its properties. Specifically: it will have the same read/write mode as the
.IR dup 'ed
descriptor. If descriptor 0 is open for reading and writing, opening
.B /dev/fd/0
for reading will return a writable, as well as readable, file
descriptor.
Also: the descriptors share the same read/write pointer, so seeks, reads and
writes on one will affect the other.
.PP
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.
.PP
Having only 20 entries in the
.B /dev/fd
directory is an anachronism; modern Unix systems allow
a process to have many more open file descriptors.
SHAR_EOF
chmod +x 'fd.4'
fi # end of overwriting check
echo shar: extracting "'dev_fd.c'" '(8981 characters)'
if test -f 'dev_fd.c'
then
	echo shar: will not over-write existing file "'dev_fd.c'"
else
cat << \SHAR_EOF > 'dev_fd.c'
#ifndef lint
static char rcsid[] = "@(#)$Header: dev_fd.c,v 1.6 87/07/10 10:24:13 root Locked $";
#endif lint

/*
 * 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.
 *
 * NFS version by
 * Arnold Robbins -- Emory University Computing Center -- Summer 87
 */

/*
 * $Log:	dev_fd.c,v $
 * Revision 1.6  87/07/10  10:24:13  root
 * Removed debugging printfs. ADR.
 * 
 * Revision 1.5  87/07/10  10:20:12  root
 * Added ENXIO check to inode version. ADR.
 * 
 * Revision 1.4  87/07/05  14:19:41  root
 * Added NOFILE/ENXIO check, some minor cleanup. ADR.
 * 
 * Revision 1.3  87/07/05  10:46:48  root
 * Brought the comments into sync with reality. Bug fix to inode version. ADR.
 * 
 * Revision 1.2  87/07/03  16:53:46  root
 * NFS version of the driver. Works just fine on a sun. ADR.
 * 
 * Revision 1.1  84/12/01  21:38:17  chris
 * Initial revision
 * 
 */

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

#include "../h/param.h"
#ifdef NFS
#include "../h/time.h"
#include "../h/vnode.h"
#else
#include "../h/inode.h"
#endif
#include "../h/file.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/errno.h"

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

/*
 * The NFS mods are so extensive that I have decided to provide two whole
 * copies of the routine, one for NFS and one for regular BSD, instead
 * of mixing them up with ifdefs. The non-NFS code is the original
 * version from UMD.  A.D.R.
 */

#ifdef NFS
/*
 * XXX
 *
 * WARNING!!!!! This piece of code requires that a patch be made
 * to the stock NFS 3.2 code in specfs/spec_vnodeops.c$spec_open().
 * The modification is to pass a pointer to the vnode for this file
 * into this routine in the call to (*cdewsw[major(dev)])(), as a fourth
 * argument.
 *
 * Why? you ask. When this device is opened, open() calls copen() which calls
 * vn_open(). Now, the whole idea behind this "device" is to substitute an
 * already open file for this one. The way to do this is to hand back up a vnode
 * for said open file. The regular inode version of this code has it easy.
 * The file structure for this device points at the device's inode. Chuck
 * that inode and substitute the inode of the already open file. It is not
 * so easy in the NFS case, because *the file structure does not yet
 * point at a vnode*. That is only done in copen() after the vn_open()
 * completes. Right now, we're still in the middle of the open. So we have
 * no way of getting at the original vnode unless it is passed in to us.
 * So that is why spec_open() has to pass a pointer to the vnode (pointer)
 * to us, so we can switch it around.
 */

fdopen (dev, mode, newdev, vpp)
dev_t dev;
int mode;
dev_t *newdev;
struct vnode **vpp;	/* vnode for this device */
{
	struct file *fp, *wfp;
	struct vnode *vp, *wvp;
	int vmode = 0;
	int rwmode, error;

	if (minor(dev) >= NOFILE)	/* sanity check */
		return (ENXIO);

	*newdev = dev;	/* XXX - force loop termination in spec_open() */

	/*
	 * 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);

	vp = *vpp;

	/*
	 * Fake a ``dup()'' sys call if it isn't a vnode.
	 */
	if (wfp->f_type != DTYPE_VNODE) {
		/*
		 * 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 ((wfp->f_flag & rwmode) != rwmode)
			return (EACCES);

		/* Delete references to this pseudo-device. */
		VN_RELE(vp);		/* Chuck the vnode. */
		fp->f_count = 0;	/* Chuck the file structure. */
		crfree(fp->f_cred);
		/* Dup the file descriptor. */
		dupit(u.u_r.r_val1, wfp, u.u_pofile[minor(dev)]);
		*vpp = (struct vnode *)wfp->f_data;	/* needed? */
		return (0);
	}

	/*
	 * now have a regular vnode.
	 */
	error = 0;
	wvp = (struct vnode *)wfp->f_data;

	/*
	 * 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 && (error = VOP_ACCESS(wvp, VREAD, u.u_cred)))
		goto bad;
	if (mode & (FWRITE|FTRUNC)) {
		if (vp->v_type == VDIR) {
			error = EISDIR;
			goto bad;
		}
		if ((error = VOP_ACCESS(wvp, VWRITE, u.u_cred)))
			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. */
	vmode = mode & ~(FCREAT | FEXCL);
	if (wvp->v_type != VREG &&
			(error = VOP_OPEN(&wvp, vmode, u.u_cred)) != 0)
		goto bad;

	/*
	 * Made it this far, now return the other vnode back up the
	 * call chain for insertion into the file table entry.
	 */
	VN_RELE(vp);		/* We don't need this anymore. */
	wvp->v_count++;
	*vpp = wvp;
	return (0);

bad:
	return (error);
}

#else	/* ! NFS */

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

	/* this check added by ADR */
	if (minor(dev) >= NOFILE)	/* sanity check */
		return (ENXIO);

	/*
	 * 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);
		/* ADR: Bug fix: wfp below was originally fp */
		if ((wfp->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 /* NFS */
#endif /* NFD > 0 */
SHAR_EOF
chmod +x 'dev_fd.c'
fi # end of overwriting check
echo shar: extracting "'PATCH'" '(544 characters)'
if test -f 'PATCH'
then
	echo shar: will not over-write existing file "'PATCH'"
else
cat << \SHAR_EOF > 'PATCH'
*** /tmp/,RCSt1013388	Fri Jul 10 10:31:15 1987
--- spec_vnodeops.c	Sun Jul  5 17:27:24 1987
***************
*** 129,135 ****
  			if ((u_int)major(dev) >= nchrdev)
  				return (ENXIO);
  
! 			error = (*cdevsw[major(dev)].d_open)(dev,flag, &newdev);
  
  			/*
  			 * If this is an indirect device we need to do the
--- 132,139 ----
  			if ((u_int)major(dev) >= nchrdev)
  				return (ENXIO);
  
! 			error = (*cdevsw[major(dev)].d_open)(dev,flag, &newdev,
! 									vpp);
  
  			/*
  			 * If this is an indirect device we need to do the
SHAR_EOF
chmod +x 'PATCH'
fi # end of overwriting check
#	End of shell archive
exit 0
-- 
Arnold Robbins
ARPA, CSNET:	arnold@emory.ARPA	BITNET: arnold@emory
UUCP:	{ decvax, gatech, sun!sunatl }!emory!arnold
ONE-OF-THESE-DAYS:	arnold@emory.mathcs.emory.edu

-- 

Rich $alz			"Anger is an energy"
Cronus Project, BBN Labs	rsalz@bbn.com
Moderator, comp.sources.unix	sources@uunet.uu.net