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