141480Smckusick /* 241480Smckusick * Copyright (c) 1988 University of Utah. 363145Sbostic * Copyright (c) 1990, 1993 463145Sbostic * The Regents of the University of California. All rights reserved. 541480Smckusick * 641480Smckusick * This code is derived from software contributed to Berkeley by 741480Smckusick * the Systems Programming Group of the University of Utah Computer 841480Smckusick * Science Department. 941480Smckusick * 1041480Smckusick * %sccs.include.redist.c% 1141480Smckusick * 1266614Shibler * from: Utah $Hdr: vn.c 1.13 94/04/02$ 1341480Smckusick * 14*68158Scgd * @(#)vn.c 8.8 (Berkeley) 01/09/95 1541480Smckusick */ 1641480Smckusick 1741480Smckusick /* 1849299Shibler * Vnode disk driver. 1941480Smckusick * 2049299Shibler * Block/character interface to a vnode. Allows one to treat a file 2149299Shibler * as a disk (e.g. build a filesystem in it, mount it, etc.). 2241480Smckusick * 2349299Shibler * NOTE 1: This uses the VOP_BMAP/VOP_STRATEGY interface to the vnode 2449299Shibler * instead of a simple VOP_RDWR. We do this to avoid distorting the 2549299Shibler * local buffer cache. 2649299Shibler * 2749299Shibler * NOTE 2: There is a security issue involved with this driver. 2841480Smckusick * Once mounted all access to the contents of the "mapped" file via 2941480Smckusick * the special file is controlled by the permissions on the special 3041480Smckusick * file, the protection of the mapped file is ignored (effectively, 3141480Smckusick * by using root credentials in all transactions). 3257301Shibler * 3357301Shibler * NOTE 3: Doesn't interact with leases, should it? 3441480Smckusick */ 3549299Shibler #include "vn.h" 3649299Shibler #if NVN > 0 3741480Smckusick 3855159Spendry #include <sys/param.h> 3955159Spendry #include <sys/systm.h> 4055159Spendry #include <sys/namei.h> 4155159Spendry #include <sys/proc.h> 4255159Spendry #include <sys/errno.h> 4355159Spendry #include <sys/dkstat.h> 4455159Spendry #include <sys/buf.h> 4555159Spendry #include <sys/malloc.h> 4655159Spendry #include <sys/ioctl.h> 4755159Spendry #include <sys/mount.h> 4855159Spendry #include <sys/vnode.h> 4955159Spendry #include <sys/file.h> 5055159Spendry #include <sys/uio.h> 5141480Smckusick 5255159Spendry #include <miscfs/specfs/specdev.h> 5355159Spendry 5456503Sbostic #include <dev/vnioctl.h> 5541480Smckusick 5641480Smckusick #ifdef DEBUG 5766613Shibler int dovncluster = 1; 5849299Shibler int vndebug = 0x00; 5949299Shibler #define VDB_FOLLOW 0x01 6049299Shibler #define VDB_INIT 0x02 6149299Shibler #define VDB_IO 0x04 6241480Smckusick #endif 6341480Smckusick 6441480Smckusick #define b_cylin b_resid 6541480Smckusick 6649299Shibler #define vnunit(x) ((minor(x) >> 3) & 0x7) /* for consistency */ 6741480Smckusick 6849299Shibler #define getvnbuf() \ 6941480Smckusick ((struct buf *)malloc(sizeof(struct buf), M_DEVBUF, M_WAITOK)) 7049299Shibler #define putvnbuf(bp) \ 7141480Smckusick free((caddr_t)(bp), M_DEVBUF) 7241480Smckusick 7349299Shibler struct vn_softc { 7441480Smckusick int sc_flags; /* flags */ 7549299Shibler size_t sc_size; /* size of vn */ 7641480Smckusick struct vnode *sc_vp; /* vnode */ 7741480Smckusick struct ucred *sc_cred; /* credentials */ 7841480Smckusick int sc_maxactive; /* max # of active requests */ 7959342Shibler struct buf sc_tab; /* transfer queue */ 8059342Shibler }; 8141480Smckusick 8241480Smckusick /* sc_flags */ 8349299Shibler #define VNF_ALIVE 0x01 8449299Shibler #define VNF_INITED 0x02 8541480Smckusick 8659342Shibler #if 0 /* if you need static allocation */ 8759342Shibler struct vn_softc vn_softc[NVN]; 8859342Shibler int numvnd = NVN; 8959342Shibler #else 9059342Shibler struct vn_softc *vn_softc; 9159342Shibler int numvnd; 9259342Shibler #endif 9359342Shibler 9459342Shibler void 9559342Shibler vnattach(num) 9659342Shibler int num; 9759342Shibler { 9859342Shibler char *mem; 9959342Shibler register u_long size; 10059342Shibler 10159342Shibler if (num <= 0) 10259342Shibler return; 10359342Shibler size = num * sizeof(struct vn_softc); 10459342Shibler mem = malloc(size, M_DEVBUF, M_NOWAIT); 10559342Shibler if (mem == NULL) { 10659342Shibler printf("WARNING: no memory for vnode disks\n"); 10759342Shibler return; 10859342Shibler } 10959342Shibler bzero(mem, size); 11059342Shibler vn_softc = (struct vn_softc *)mem; 11159342Shibler numvnd = num; 11259342Shibler } 11359342Shibler 11449299Shibler int 11549299Shibler vnopen(dev, flags, mode, p) 11641480Smckusick dev_t dev; 11749299Shibler int flags, mode; 11849299Shibler struct proc *p; 11941480Smckusick { 12049299Shibler int unit = vnunit(dev); 12141480Smckusick 12241480Smckusick #ifdef DEBUG 12349299Shibler if (vndebug & VDB_FOLLOW) 12449299Shibler printf("vnopen(%x, %x, %x, %x)\n", dev, flags, mode, p); 12541480Smckusick #endif 12659342Shibler if (unit >= numvnd) 12741480Smckusick return(ENXIO); 12841480Smckusick return(0); 12941480Smckusick } 13041480Smckusick 13141480Smckusick /* 13241480Smckusick * Break the request into bsize pieces and submit using VOP_BMAP/VOP_STRATEGY. 13341480Smckusick * Note that this driver can only be used for swapping over NFS on the hp 13441480Smckusick * since nfs_strategy on the vax cannot handle u-areas and page tables. 13541480Smckusick */ 136*68158Scgd void 13749299Shibler vnstrategy(bp) 13841480Smckusick register struct buf *bp; 13941480Smckusick { 14049299Shibler int unit = vnunit(bp->b_dev); 14149299Shibler register struct vn_softc *vn = &vn_softc[unit]; 14241480Smckusick register struct buf *nbp; 14341480Smckusick register int bn, bsize, resid; 14441480Smckusick register caddr_t addr; 14566613Shibler int sz, flags, error; 14653921Shibler extern void vniodone(); 14741480Smckusick 14841480Smckusick #ifdef DEBUG 14949299Shibler if (vndebug & VDB_FOLLOW) 15049299Shibler printf("vnstrategy(%x): unit %d\n", bp, unit); 15141480Smckusick #endif 15249299Shibler if ((vn->sc_flags & VNF_INITED) == 0) { 15341480Smckusick bp->b_error = ENXIO; 15441480Smckusick bp->b_flags |= B_ERROR; 15549299Shibler biodone(bp); 15641480Smckusick return; 15741480Smckusick } 15841480Smckusick bn = bp->b_blkno; 15941480Smckusick sz = howmany(bp->b_bcount, DEV_BSIZE); 16041480Smckusick bp->b_resid = bp->b_bcount; 16149299Shibler if (bn < 0 || bn + sz > vn->sc_size) { 16249299Shibler if (bn != vn->sc_size) { 16341480Smckusick bp->b_error = EINVAL; 16441480Smckusick bp->b_flags |= B_ERROR; 16541480Smckusick } 16649299Shibler biodone(bp); 16741480Smckusick return; 16841480Smckusick } 16941480Smckusick bn = dbtob(bn); 17051945Smckusick bsize = vn->sc_vp->v_mount->mnt_stat.f_iosize; 17164902Shibler addr = bp->b_data; 17241480Smckusick flags = bp->b_flags | B_CALL; 17341480Smckusick for (resid = bp->b_resid; resid; resid -= sz) { 17441480Smckusick struct vnode *vp; 17541480Smckusick daddr_t nbn; 17666613Shibler int off, s, nra; 17741480Smckusick 17866613Shibler nra = 0; 17966613Shibler error = VOP_BMAP(vn->sc_vp, bn / bsize, &vp, &nbn, &nra); 18066613Shibler if (error == 0 && (long)nbn == -1) 18166613Shibler error = EIO; 18241480Smckusick #ifdef DEBUG 18366613Shibler if (!dovncluster) 18466613Shibler nra = 0; 18566613Shibler #endif 18666613Shibler 18766613Shibler if (off = bn % bsize) 18866613Shibler sz = bsize - off; 18966613Shibler else 19066613Shibler sz = (1 + nra) * bsize; 19166613Shibler if (resid < sz) 19266613Shibler sz = resid; 19366613Shibler #ifdef DEBUG 19449299Shibler if (vndebug & VDB_IO) 19566613Shibler printf("vnstrategy: vp %x/%x bn %x/%x sz %x\n", 19666613Shibler vn->sc_vp, vp, bn, nbn, sz); 19741480Smckusick #endif 19866613Shibler 19966613Shibler nbp = getvnbuf(); 20041480Smckusick nbp->b_flags = flags; 20141480Smckusick nbp->b_bcount = sz; 20241480Smckusick nbp->b_bufsize = bp->b_bufsize; 20341480Smckusick nbp->b_error = 0; 20449299Shibler if (vp->v_type == VBLK || vp->v_type == VCHR) 20549299Shibler nbp->b_dev = vp->v_rdev; 20649299Shibler else 20749299Shibler nbp->b_dev = NODEV; 20864902Shibler nbp->b_data = addr; 20941480Smckusick nbp->b_blkno = nbn + btodb(off); 21041480Smckusick nbp->b_proc = bp->b_proc; 21149299Shibler nbp->b_iodone = vniodone; 21241480Smckusick nbp->b_vp = vp; 21341480Smckusick nbp->b_pfcent = (int) bp; /* XXX */ 21457301Shibler nbp->b_rcred = vn->sc_cred; /* XXX crdup? */ 21557301Shibler nbp->b_wcred = vn->sc_cred; /* XXX crdup? */ 21657301Shibler nbp->b_dirtyoff = bp->b_dirtyoff; 21757301Shibler nbp->b_dirtyend = bp->b_dirtyend; 21857301Shibler nbp->b_validoff = bp->b_validoff; 21957301Shibler nbp->b_validend = bp->b_validend; 22041480Smckusick /* 22166613Shibler * If there was an error or a hole in the file...punt. 22264661Shibler * Note that we deal with this after the nbp allocation. 22364661Shibler * This ensures that we properly clean up any operations 22464661Shibler * that we have already fired off. 22564661Shibler * 22666613Shibler * XXX we could deal with holes here but it would be 22764661Shibler * a hassle (in the write case). 22864661Shibler */ 22966613Shibler if (error) { 23066613Shibler nbp->b_error = error; 23164661Shibler nbp->b_flags |= B_ERROR; 23264661Shibler bp->b_resid -= (resid - sz); 23364661Shibler biodone(nbp); 23464661Shibler return; 23564661Shibler } 23664661Shibler /* 23741480Smckusick * Just sort by block number 23841480Smckusick */ 23941480Smckusick nbp->b_cylin = nbp->b_blkno; 24041480Smckusick s = splbio(); 24159342Shibler disksort(&vn->sc_tab, nbp); 24259342Shibler if (vn->sc_tab.b_active < vn->sc_maxactive) { 24359342Shibler vn->sc_tab.b_active++; 24459342Shibler vnstart(vn); 24541480Smckusick } 24641480Smckusick splx(s); 24741480Smckusick bn += sz; 24841480Smckusick addr += sz; 24941480Smckusick } 25041480Smckusick } 25141480Smckusick 25241480Smckusick /* 25341480Smckusick * Feed requests sequentially. 25441480Smckusick * We do it this way to keep from flooding NFS servers if we are connected 25541480Smckusick * to an NFS file. This places the burden on the client rather than the 25641480Smckusick * server. 25741480Smckusick */ 25859342Shibler vnstart(vn) 25959342Shibler register struct vn_softc *vn; 26041480Smckusick { 26141480Smckusick register struct buf *bp; 26241480Smckusick 26341480Smckusick /* 26441480Smckusick * Dequeue now since lower level strategy routine might 26541480Smckusick * queue using same links 26641480Smckusick */ 26759342Shibler bp = vn->sc_tab.b_actf; 26859342Shibler vn->sc_tab.b_actf = bp->b_actf; 26941480Smckusick #ifdef DEBUG 27049299Shibler if (vndebug & VDB_IO) 27149299Shibler printf("vnstart(%d): bp %x vp %x blkno %x addr %x cnt %x\n", 27264902Shibler vn-vn_softc, bp, bp->b_vp, bp->b_blkno, bp->b_data, 27341480Smckusick bp->b_bcount); 27441480Smckusick #endif 27557301Shibler if ((bp->b_flags & B_READ) == 0) 27657301Shibler bp->b_vp->v_numoutput++; 27741480Smckusick VOP_STRATEGY(bp); 27841480Smckusick } 27941480Smckusick 28053921Shibler void 28149299Shibler vniodone(bp) 28241480Smckusick register struct buf *bp; 28341480Smckusick { 28441480Smckusick register struct buf *pbp = (struct buf *)bp->b_pfcent; /* XXX */ 28559342Shibler register struct vn_softc *vn = &vn_softc[vnunit(pbp->b_dev)]; 28641480Smckusick int s; 28741480Smckusick 28841480Smckusick s = splbio(); 28941480Smckusick #ifdef DEBUG 29049299Shibler if (vndebug & VDB_IO) 29149299Shibler printf("vniodone(%d): bp %x vp %x blkno %x addr %x cnt %x\n", 29264902Shibler vn-vn_softc, bp, bp->b_vp, bp->b_blkno, bp->b_data, 29341480Smckusick bp->b_bcount); 29441480Smckusick #endif 29541480Smckusick if (bp->b_error) { 29641480Smckusick #ifdef DEBUG 29749299Shibler if (vndebug & VDB_IO) 29849299Shibler printf("vniodone: bp %x error %d\n", bp, bp->b_error); 29941480Smckusick #endif 30041480Smckusick pbp->b_flags |= B_ERROR; 30149299Shibler pbp->b_error = biowait(bp); 30241480Smckusick } 30341480Smckusick pbp->b_resid -= bp->b_bcount; 30449299Shibler putvnbuf(bp); 30541480Smckusick if (pbp->b_resid == 0) { 30641480Smckusick #ifdef DEBUG 30749299Shibler if (vndebug & VDB_IO) 30849299Shibler printf("vniodone: pbp %x iodone\n", pbp); 30941480Smckusick #endif 31049299Shibler biodone(pbp); 31141480Smckusick } 31259342Shibler if (vn->sc_tab.b_actf) 31359342Shibler vnstart(vn); 31441480Smckusick else 31559342Shibler vn->sc_tab.b_active--; 31641480Smckusick splx(s); 31741480Smckusick } 31841480Smckusick 31949299Shibler vnread(dev, uio, flags, p) 32041480Smckusick dev_t dev; 32141480Smckusick struct uio *uio; 32249299Shibler int flags; 32349299Shibler struct proc *p; 32441480Smckusick { 32541480Smckusick 32641480Smckusick #ifdef DEBUG 32749299Shibler if (vndebug & VDB_FOLLOW) 32849299Shibler printf("vnread(%x, %x, %x, %x)\n", dev, uio, flags, p); 32941480Smckusick #endif 33059342Shibler return(physio(vnstrategy, NULL, dev, B_READ, minphys, uio)); 33141480Smckusick } 33241480Smckusick 33349299Shibler vnwrite(dev, uio, flags, p) 33441480Smckusick dev_t dev; 33541480Smckusick struct uio *uio; 33649299Shibler int flags; 33749299Shibler struct proc *p; 33841480Smckusick { 33941480Smckusick 34041480Smckusick #ifdef DEBUG 34149299Shibler if (vndebug & VDB_FOLLOW) 34249299Shibler printf("vnwrite(%x, %x, %x, %x)\n", dev, uio, flags, p); 34341480Smckusick #endif 34459342Shibler return(physio(vnstrategy, NULL, dev, B_WRITE, minphys, uio)); 34541480Smckusick } 34641480Smckusick 34741480Smckusick /* ARGSUSED */ 34849299Shibler vnioctl(dev, cmd, data, flag, p) 34941480Smckusick dev_t dev; 35041480Smckusick u_long cmd; 35141480Smckusick caddr_t data; 35241480Smckusick int flag; 35349299Shibler struct proc *p; 35441480Smckusick { 35549299Shibler int unit = vnunit(dev); 35649299Shibler register struct vn_softc *vn; 35749299Shibler struct vn_ioctl *vio; 35841480Smckusick struct vattr vattr; 35949299Shibler struct nameidata nd; 36041480Smckusick int error; 36141480Smckusick 36241480Smckusick #ifdef DEBUG 36349299Shibler if (vndebug & VDB_FOLLOW) 36449299Shibler printf("vnioctl(%x, %x, %x, %x, %x): unit %d\n", 36549299Shibler dev, cmd, data, flag, p, unit); 36641480Smckusick #endif 36749299Shibler error = suser(p->p_ucred, &p->p_acflag); 36841480Smckusick if (error) 36941480Smckusick return (error); 37059342Shibler if (unit >= numvnd) 37141480Smckusick return (ENXIO); 37241480Smckusick 37349299Shibler vn = &vn_softc[unit]; 37449299Shibler vio = (struct vn_ioctl *)data; 37541480Smckusick switch (cmd) { 37641480Smckusick 37749299Shibler case VNIOCSET: 37849299Shibler if (vn->sc_flags & VNF_INITED) 37941480Smckusick return(EBUSY); 38041480Smckusick /* 38141480Smckusick * Always open for read and write. 38241480Smckusick * This is probably bogus, but it lets vn_open() 38341480Smckusick * weed out directories, sockets, etc. so we don't 38441480Smckusick * have to worry about them. 38541480Smckusick */ 38652761Shibler NDINIT(&nd, LOOKUP, FOLLOW, UIO_USERSPACE, vio->vn_file, p); 38752761Shibler if (error = vn_open(&nd, FREAD|FWRITE, 0)) 38841480Smckusick return(error); 38950115Smckusick if (error = VOP_GETATTR(nd.ni_vp, &vattr, p->p_ucred, p)) { 39050115Smckusick VOP_UNLOCK(nd.ni_vp); 39150115Smckusick (void) vn_close(nd.ni_vp, FREAD|FWRITE, p->p_ucred, p); 39241480Smckusick return(error); 39341480Smckusick } 39450115Smckusick VOP_UNLOCK(nd.ni_vp); 39549299Shibler vn->sc_vp = nd.ni_vp; 39649299Shibler vn->sc_size = btodb(vattr.va_size); /* note truncation */ 39750115Smckusick if (error = vnsetcred(vn, p->p_ucred)) { 39866613Shibler (void) vn_close(nd.ni_vp, FREAD|FWRITE, p->p_ucred, p); 39941480Smckusick return(error); 40041480Smckusick } 40149299Shibler vnthrottle(vn, vn->sc_vp); 40249299Shibler vio->vn_size = dbtob(vn->sc_size); 40349299Shibler vn->sc_flags |= VNF_INITED; 40441480Smckusick #ifdef DEBUG 40549299Shibler if (vndebug & VDB_INIT) 40649299Shibler printf("vnioctl: SET vp %x size %x\n", 40749299Shibler vn->sc_vp, vn->sc_size); 40841480Smckusick #endif 40941480Smckusick break; 41041480Smckusick 41149299Shibler case VNIOCCLR: 41249299Shibler if ((vn->sc_flags & VNF_INITED) == 0) 41341480Smckusick return(ENXIO); 41449299Shibler vnclear(vn); 41541480Smckusick #ifdef DEBUG 41649299Shibler if (vndebug & VDB_INIT) 41749299Shibler printf("vnioctl: CLRed\n"); 41841480Smckusick #endif 41941480Smckusick break; 42041480Smckusick 42141480Smckusick default: 42267741Smckusick return(ENOTTY); 42341480Smckusick } 42441480Smckusick return(0); 42541480Smckusick } 42641480Smckusick 42741480Smckusick /* 42841480Smckusick * Duplicate the current processes' credentials. Since we are called only 42941480Smckusick * as the result of a SET ioctl and only root can do that, any future access 43041480Smckusick * to this "disk" is essentially as root. Note that credentials may change 43141480Smckusick * if some other uid can write directly to the mapped file (NFS). 43241480Smckusick */ 43349299Shibler vnsetcred(vn, cred) 43449299Shibler register struct vn_softc *vn; 43565266Smckusick struct ucred *cred; 43641480Smckusick { 43741480Smckusick struct uio auio; 43841480Smckusick struct iovec aiov; 43966613Shibler char *tmpbuf; 44066613Shibler int error; 44141480Smckusick 44249299Shibler vn->sc_cred = crdup(cred); 44366613Shibler tmpbuf = malloc(DEV_BSIZE, M_TEMP, M_WAITOK); 44466613Shibler 44541480Smckusick /* XXX: Horrible kludge to establish credentials for NFS */ 44641480Smckusick aiov.iov_base = tmpbuf; 44755159Spendry aiov.iov_len = min(DEV_BSIZE, dbtob(vn->sc_size)); 44841480Smckusick auio.uio_iov = &aiov; 44941480Smckusick auio.uio_iovcnt = 1; 45041480Smckusick auio.uio_offset = 0; 45141480Smckusick auio.uio_rw = UIO_READ; 45241480Smckusick auio.uio_segflg = UIO_SYSSPACE; 45341480Smckusick auio.uio_resid = aiov.iov_len; 45466613Shibler error = VOP_READ(vn->sc_vp, &auio, 0, vn->sc_cred); 45566613Shibler 45666613Shibler free(tmpbuf, M_TEMP); 45766613Shibler return (error); 45841480Smckusick } 45941480Smckusick 46041480Smckusick /* 46141480Smckusick * Set maxactive based on FS type 46241480Smckusick */ 46349299Shibler vnthrottle(vn, vp) 46449299Shibler register struct vn_softc *vn; 46541480Smckusick struct vnode *vp; 46641480Smckusick { 46753517Sheideman extern int (**nfsv2_vnodeop_p)(); 46841480Smckusick 46953517Sheideman if (vp->v_op == nfsv2_vnodeop_p) 47049299Shibler vn->sc_maxactive = 2; 47141480Smckusick else 47249299Shibler vn->sc_maxactive = 8; 47341480Smckusick 47449299Shibler if (vn->sc_maxactive < 1) 47549299Shibler vn->sc_maxactive = 1; 47641480Smckusick } 47741480Smckusick 47849299Shibler vnshutdown() 47941480Smckusick { 48049299Shibler register struct vn_softc *vn; 48141480Smckusick 48259342Shibler for (vn = &vn_softc[0]; vn < &vn_softc[numvnd]; vn++) 48349299Shibler if (vn->sc_flags & VNF_INITED) 48449299Shibler vnclear(vn); 48541480Smckusick } 48641480Smckusick 48749299Shibler vnclear(vn) 48849299Shibler register struct vn_softc *vn; 48941480Smckusick { 49049299Shibler register struct vnode *vp = vn->sc_vp; 49150115Smckusick struct proc *p = curproc; /* XXX */ 49241480Smckusick 49341480Smckusick #ifdef DEBUG 49449299Shibler if (vndebug & VDB_FOLLOW) 49549299Shibler printf("vnclear(%x): vp %x\n", vp); 49641480Smckusick #endif 49749299Shibler vn->sc_flags &= ~VNF_INITED; 49841480Smckusick if (vp == (struct vnode *)0) 49949299Shibler panic("vnioctl: null vp"); 50050115Smckusick (void) vn_close(vp, FREAD|FWRITE, vn->sc_cred, p); 50149299Shibler crfree(vn->sc_cred); 50249299Shibler vn->sc_vp = (struct vnode *)0; 50349299Shibler vn->sc_cred = (struct ucred *)0; 50449299Shibler vn->sc_size = 0; 50541480Smckusick } 50641480Smckusick 50749299Shibler vnsize(dev) 50841480Smckusick dev_t dev; 50941480Smckusick { 51049299Shibler int unit = vnunit(dev); 51149299Shibler register struct vn_softc *vn = &vn_softc[unit]; 51241480Smckusick 51359342Shibler if (unit >= numvnd || (vn->sc_flags & VNF_INITED) == 0) 51441480Smckusick return(-1); 51549299Shibler return(vn->sc_size); 51641480Smckusick } 51741480Smckusick 51849299Shibler vndump(dev) 51941480Smckusick { 52041480Smckusick return(ENXIO); 52141480Smckusick } 52241480Smckusick #endif 523