10Sstevel@tonic-gate /* 20Sstevel@tonic-gate * CDDL HEADER START 30Sstevel@tonic-gate * 40Sstevel@tonic-gate * The contents of this file are subject to the terms of the 52720Sfrankho * Common Development and Distribution License (the "License"). 62720Sfrankho * You may not use this file except in compliance with the License. 70Sstevel@tonic-gate * 80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 100Sstevel@tonic-gate * See the License for the specific language governing permissions 110Sstevel@tonic-gate * and limitations under the License. 120Sstevel@tonic-gate * 130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 180Sstevel@tonic-gate * 190Sstevel@tonic-gate * CDDL HEADER END 200Sstevel@tonic-gate */ 210Sstevel@tonic-gate /* 224356Scasper * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 230Sstevel@tonic-gate * Use is subject to license terms. 240Sstevel@tonic-gate */ 250Sstevel@tonic-gate 260Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 270Sstevel@tonic-gate 280Sstevel@tonic-gate #include <sys/param.h> 290Sstevel@tonic-gate #include <sys/t_lock.h> 300Sstevel@tonic-gate #include <sys/errno.h> 310Sstevel@tonic-gate #include <sys/sysmacros.h> 320Sstevel@tonic-gate #include <sys/buf.h> 330Sstevel@tonic-gate #include <sys/systm.h> 340Sstevel@tonic-gate #include <sys/vfs.h> 350Sstevel@tonic-gate #include <sys/vnode.h> 360Sstevel@tonic-gate #include <sys/kmem.h> 370Sstevel@tonic-gate #include <sys/proc.h> 380Sstevel@tonic-gate #include <sys/cred.h> 390Sstevel@tonic-gate #include <sys/cmn_err.h> 400Sstevel@tonic-gate #include <sys/debug.h> 410Sstevel@tonic-gate #include <vm/pvn.h> 420Sstevel@tonic-gate #include <sys/fs/pc_label.h> 430Sstevel@tonic-gate #include <sys/fs/pc_fs.h> 440Sstevel@tonic-gate #include <sys/fs/pc_dir.h> 450Sstevel@tonic-gate #include <sys/fs/pc_node.h> 460Sstevel@tonic-gate #include <sys/dirent.h> 470Sstevel@tonic-gate #include <sys/fdio.h> 480Sstevel@tonic-gate #include <sys/file.h> 490Sstevel@tonic-gate #include <sys/conf.h> 500Sstevel@tonic-gate 510Sstevel@tonic-gate struct pchead pcfhead[NPCHASH]; 520Sstevel@tonic-gate struct pchead pcdhead[NPCHASH]; 530Sstevel@tonic-gate 540Sstevel@tonic-gate extern krwlock_t pcnodes_lock; 550Sstevel@tonic-gate 560Sstevel@tonic-gate static int pc_getentryblock(struct pcnode *, struct buf **); 570Sstevel@tonic-gate static int syncpcp(struct pcnode *, int); 580Sstevel@tonic-gate 590Sstevel@tonic-gate /* 600Sstevel@tonic-gate * fake entry for root directory, since this does not have a parent 610Sstevel@tonic-gate * pointing to it. 620Sstevel@tonic-gate */ 632720Sfrankho struct pcdir pcfs_rootdirentry = { 640Sstevel@tonic-gate "", 650Sstevel@tonic-gate "", 660Sstevel@tonic-gate PCA_DIR 670Sstevel@tonic-gate }; 680Sstevel@tonic-gate 690Sstevel@tonic-gate void 700Sstevel@tonic-gate pc_init(void) 710Sstevel@tonic-gate { 720Sstevel@tonic-gate struct pchead *hdp, *hfp; 730Sstevel@tonic-gate int i; 740Sstevel@tonic-gate for (i = 0; i < NPCHASH; i++) { 750Sstevel@tonic-gate hdp = &pcdhead[i]; 760Sstevel@tonic-gate hfp = &pcfhead[i]; 770Sstevel@tonic-gate hdp->pch_forw = (struct pcnode *)hdp; 780Sstevel@tonic-gate hdp->pch_back = (struct pcnode *)hdp; 790Sstevel@tonic-gate hfp->pch_forw = (struct pcnode *)hfp; 800Sstevel@tonic-gate hfp->pch_back = (struct pcnode *)hfp; 810Sstevel@tonic-gate } 820Sstevel@tonic-gate } 830Sstevel@tonic-gate 840Sstevel@tonic-gate struct pcnode * 850Sstevel@tonic-gate pc_getnode( 860Sstevel@tonic-gate struct pcfs *fsp, /* filsystem for node */ 870Sstevel@tonic-gate daddr_t blkno, /* phys block no of dir entry */ 880Sstevel@tonic-gate int offset, /* offset of dir entry in block */ 890Sstevel@tonic-gate struct pcdir *ep) /* node dir entry */ 900Sstevel@tonic-gate { 910Sstevel@tonic-gate struct pcnode *pcp; 920Sstevel@tonic-gate struct pchead *hp; 930Sstevel@tonic-gate struct vnode *vp; 940Sstevel@tonic-gate pc_cluster32_t scluster; 950Sstevel@tonic-gate 960Sstevel@tonic-gate ASSERT(fsp->pcfs_flags & PCFS_LOCKED); 970Sstevel@tonic-gate if (ep == (struct pcdir *)0) { 982720Sfrankho ep = &pcfs_rootdirentry; 990Sstevel@tonic-gate scluster = 0; 1000Sstevel@tonic-gate } else { 1010Sstevel@tonic-gate scluster = pc_getstartcluster(fsp, ep); 1020Sstevel@tonic-gate } 1030Sstevel@tonic-gate /* 1040Sstevel@tonic-gate * First look for active nodes. 1050Sstevel@tonic-gate * File nodes are identified by the location (blkno, offset) of 1060Sstevel@tonic-gate * its directory entry. 1070Sstevel@tonic-gate * Directory nodes are identified by the starting cluster number 1080Sstevel@tonic-gate * for the entries. 1090Sstevel@tonic-gate */ 1100Sstevel@tonic-gate if (ep->pcd_attr & PCA_DIR) { 1110Sstevel@tonic-gate hp = &pcdhead[PCDHASH(fsp, scluster)]; 1120Sstevel@tonic-gate rw_enter(&pcnodes_lock, RW_READER); 1130Sstevel@tonic-gate for (pcp = hp->pch_forw; 1140Sstevel@tonic-gate pcp != (struct pcnode *)hp; pcp = pcp->pc_forw) { 1150Sstevel@tonic-gate if ((fsp == VFSTOPCFS(PCTOV(pcp)->v_vfsp)) && 1160Sstevel@tonic-gate (scluster == pcp->pc_scluster)) { 1170Sstevel@tonic-gate VN_HOLD(PCTOV(pcp)); 1180Sstevel@tonic-gate rw_exit(&pcnodes_lock); 1190Sstevel@tonic-gate return (pcp); 1200Sstevel@tonic-gate } 1210Sstevel@tonic-gate } 1220Sstevel@tonic-gate rw_exit(&pcnodes_lock); 1230Sstevel@tonic-gate } else { 1240Sstevel@tonic-gate hp = &pcfhead[PCFHASH(fsp, blkno, offset)]; 1250Sstevel@tonic-gate rw_enter(&pcnodes_lock, RW_READER); 1260Sstevel@tonic-gate for (pcp = hp->pch_forw; 1270Sstevel@tonic-gate pcp != (struct pcnode *)hp; pcp = pcp->pc_forw) { 1280Sstevel@tonic-gate if ((fsp == VFSTOPCFS(PCTOV(pcp)->v_vfsp)) && 1290Sstevel@tonic-gate ((pcp->pc_flags & PC_INVAL) == 0) && 1300Sstevel@tonic-gate (blkno == pcp->pc_eblkno) && 1310Sstevel@tonic-gate (offset == pcp->pc_eoffset)) { 1320Sstevel@tonic-gate VN_HOLD(PCTOV(pcp)); 1330Sstevel@tonic-gate rw_exit(&pcnodes_lock); 1340Sstevel@tonic-gate return (pcp); 1350Sstevel@tonic-gate } 1360Sstevel@tonic-gate } 1370Sstevel@tonic-gate rw_exit(&pcnodes_lock); 1380Sstevel@tonic-gate } 1390Sstevel@tonic-gate /* 1400Sstevel@tonic-gate * Cannot find node in active list. Allocate memory for a new node 1410Sstevel@tonic-gate * initialize it, and put it on the active list. 1420Sstevel@tonic-gate */ 1430Sstevel@tonic-gate pcp = kmem_alloc(sizeof (struct pcnode), KM_SLEEP); 1440Sstevel@tonic-gate bzero(pcp, sizeof (struct pcnode)); 1450Sstevel@tonic-gate vp = vn_alloc(KM_SLEEP); 1460Sstevel@tonic-gate pcp->pc_vn = vp; 1470Sstevel@tonic-gate pcp->pc_entry = *ep; 1480Sstevel@tonic-gate pcp->pc_eblkno = blkno; 1490Sstevel@tonic-gate pcp->pc_eoffset = offset; 1500Sstevel@tonic-gate pcp->pc_scluster = scluster; 1510Sstevel@tonic-gate pcp->pc_lcluster = scluster; 1520Sstevel@tonic-gate pcp->pc_lindex = 0; 1530Sstevel@tonic-gate pcp->pc_flags = 0; 1540Sstevel@tonic-gate if (ep->pcd_attr & PCA_DIR) { 1550Sstevel@tonic-gate vn_setops(vp, pcfs_dvnodeops); 1560Sstevel@tonic-gate vp->v_type = VDIR; 1570Sstevel@tonic-gate if (scluster == 0) { 1580Sstevel@tonic-gate vp->v_flag = VROOT; 1590Sstevel@tonic-gate blkno = offset = 0; 1600Sstevel@tonic-gate if (IS_FAT32(fsp)) { 1612972Sfrankho pc_cluster32_t ncl = 0; 1622972Sfrankho 1632972Sfrankho scluster = fsp->pcfs_rdirstart; 1642972Sfrankho if (pc_fileclsize(fsp, scluster, &ncl)) { 1652972Sfrankho PC_DPRINTF1(2, "cluster chain " 1662972Sfrankho "corruption, scluster=%d\n", 1672972Sfrankho scluster); 1682972Sfrankho pcp->pc_flags |= PC_INVAL; 1692972Sfrankho } 1702972Sfrankho pcp->pc_size = fsp->pcfs_clsize * ncl; 1710Sstevel@tonic-gate } else { 1720Sstevel@tonic-gate pcp->pc_size = 1730Sstevel@tonic-gate fsp->pcfs_rdirsec * fsp->pcfs_secsize; 1740Sstevel@tonic-gate } 1752972Sfrankho } else { 1762972Sfrankho pc_cluster32_t ncl = 0; 1772972Sfrankho 1782972Sfrankho if (pc_fileclsize(fsp, scluster, &ncl)) { 1792972Sfrankho PC_DPRINTF1(2, "cluster chain corruption, " 1802972Sfrankho "scluster=%d\n", scluster); 1812972Sfrankho pcp->pc_flags |= PC_INVAL; 1822972Sfrankho } 1832972Sfrankho pcp->pc_size = fsp->pcfs_clsize * ncl; 1842972Sfrankho } 1850Sstevel@tonic-gate } else { 1860Sstevel@tonic-gate vn_setops(vp, pcfs_fvnodeops); 1870Sstevel@tonic-gate vp->v_type = VREG; 1880Sstevel@tonic-gate vp->v_flag = VNOSWAP; 1890Sstevel@tonic-gate fsp->pcfs_frefs++; 1900Sstevel@tonic-gate pcp->pc_size = ltohi(ep->pcd_size); 1910Sstevel@tonic-gate } 1920Sstevel@tonic-gate fsp->pcfs_nrefs++; 1932720Sfrankho VFS_HOLD(PCFSTOVFS(fsp)); 1940Sstevel@tonic-gate vp->v_data = (caddr_t)pcp; 1950Sstevel@tonic-gate vp->v_vfsp = PCFSTOVFS(fsp); 1960Sstevel@tonic-gate vn_exists(vp); 1970Sstevel@tonic-gate rw_enter(&pcnodes_lock, RW_WRITER); 1980Sstevel@tonic-gate insque(pcp, hp); 1990Sstevel@tonic-gate rw_exit(&pcnodes_lock); 2000Sstevel@tonic-gate return (pcp); 2010Sstevel@tonic-gate } 2020Sstevel@tonic-gate 2030Sstevel@tonic-gate int 2040Sstevel@tonic-gate syncpcp(struct pcnode *pcp, int flags) 2050Sstevel@tonic-gate { 2060Sstevel@tonic-gate int err; 2070Sstevel@tonic-gate if (!vn_has_cached_data(PCTOV(pcp))) 2080Sstevel@tonic-gate err = 0; 2090Sstevel@tonic-gate else 2104356Scasper err = VOP_PUTPAGE(PCTOV(pcp), 0, 0, flags, kcred); 2110Sstevel@tonic-gate 2120Sstevel@tonic-gate return (err); 2130Sstevel@tonic-gate } 2140Sstevel@tonic-gate 2150Sstevel@tonic-gate void 2160Sstevel@tonic-gate pc_rele(struct pcnode *pcp) 2170Sstevel@tonic-gate { 2180Sstevel@tonic-gate struct pcfs *fsp; 2190Sstevel@tonic-gate struct vnode *vp; 2200Sstevel@tonic-gate int err; 2210Sstevel@tonic-gate 2220Sstevel@tonic-gate vp = PCTOV(pcp); 2230Sstevel@tonic-gate PC_DPRINTF1(8, "pc_rele vp=0x%p\n", (void *)vp); 2240Sstevel@tonic-gate 2250Sstevel@tonic-gate fsp = VFSTOPCFS(vp->v_vfsp); 2260Sstevel@tonic-gate ASSERT(fsp->pcfs_flags & PCFS_LOCKED); 2270Sstevel@tonic-gate 2280Sstevel@tonic-gate rw_enter(&pcnodes_lock, RW_WRITER); 2290Sstevel@tonic-gate pcp->pc_flags |= PC_RELEHOLD; 2300Sstevel@tonic-gate 2310Sstevel@tonic-gate retry: 2320Sstevel@tonic-gate if (vp->v_type != VDIR && (pcp->pc_flags & PC_INVAL) == 0) { 2330Sstevel@tonic-gate /* 2340Sstevel@tonic-gate * If the file was removed while active it may be safely 2350Sstevel@tonic-gate * truncated now. 2360Sstevel@tonic-gate */ 2370Sstevel@tonic-gate 2380Sstevel@tonic-gate if (pcp->pc_entry.pcd_filename[0] == PCD_ERASED) { 2390Sstevel@tonic-gate (void) pc_truncate(pcp, 0); 2400Sstevel@tonic-gate } else if (pcp->pc_flags & PC_CHG) { 2410Sstevel@tonic-gate (void) pc_nodeupdate(pcp); 2420Sstevel@tonic-gate } 2430Sstevel@tonic-gate err = syncpcp(pcp, B_INVAL); 2440Sstevel@tonic-gate if (err) { 245*5121Sfrankho (void) syncpcp(pcp, B_INVAL | B_FORCE); 2460Sstevel@tonic-gate } 2470Sstevel@tonic-gate } 2480Sstevel@tonic-gate if (vn_has_cached_data(vp)) { 2490Sstevel@tonic-gate /* 2500Sstevel@tonic-gate * pvn_vplist_dirty will abort all old pages 2510Sstevel@tonic-gate */ 2520Sstevel@tonic-gate (void) pvn_vplist_dirty(vp, (u_offset_t)0, 2530Sstevel@tonic-gate pcfs_putapage, B_INVAL, (struct cred *)NULL); 2540Sstevel@tonic-gate } 2550Sstevel@tonic-gate 2560Sstevel@tonic-gate (void) pc_syncfat(fsp); 2570Sstevel@tonic-gate mutex_enter(&vp->v_lock); 2580Sstevel@tonic-gate if (vn_has_cached_data(vp)) { 2590Sstevel@tonic-gate mutex_exit(&vp->v_lock); 2600Sstevel@tonic-gate goto retry; 2610Sstevel@tonic-gate } 2620Sstevel@tonic-gate ASSERT(!vn_has_cached_data(vp)); 2630Sstevel@tonic-gate 2640Sstevel@tonic-gate vp->v_count--; /* release our hold from vn_rele */ 2650Sstevel@tonic-gate if (vp->v_count > 0) { /* Is this check still needed? */ 2660Sstevel@tonic-gate PC_DPRINTF1(3, "pc_rele: pcp=0x%p HELD AGAIN!\n", (void *)pcp); 2670Sstevel@tonic-gate mutex_exit(&vp->v_lock); 2680Sstevel@tonic-gate pcp->pc_flags &= ~PC_RELEHOLD; 2690Sstevel@tonic-gate rw_exit(&pcnodes_lock); 2700Sstevel@tonic-gate return; 2710Sstevel@tonic-gate } 2720Sstevel@tonic-gate 2730Sstevel@tonic-gate remque(pcp); 2740Sstevel@tonic-gate rw_exit(&pcnodes_lock); 2752972Sfrankho /* 2762972Sfrankho * XXX - old code had a check for !(pcp->pc_flags & PC_INVAL) 2772972Sfrankho * here. Seems superfluous/incorrect, but then earlier on PC_INVAL 2782972Sfrankho * was never set anywhere in PCFS. Now it is, and we _have_ to drop 2792972Sfrankho * the file reference here. Else, we'd screw up umount/modunload. 2802972Sfrankho */ 2812972Sfrankho if ((vp->v_type == VREG)) { 2820Sstevel@tonic-gate fsp->pcfs_frefs--; 2830Sstevel@tonic-gate } 2840Sstevel@tonic-gate fsp->pcfs_nrefs--; 2852720Sfrankho VFS_RELE(vp->v_vfsp); 2860Sstevel@tonic-gate 2870Sstevel@tonic-gate if (fsp->pcfs_nrefs < 0) { 2880Sstevel@tonic-gate panic("pc_rele: nrefs count"); 2890Sstevel@tonic-gate } 2900Sstevel@tonic-gate if (fsp->pcfs_frefs < 0) { 2910Sstevel@tonic-gate panic("pc_rele: frefs count"); 2920Sstevel@tonic-gate } 2930Sstevel@tonic-gate 2940Sstevel@tonic-gate mutex_exit(&vp->v_lock); 2950Sstevel@tonic-gate vn_invalid(vp); 2960Sstevel@tonic-gate vn_free(vp); 2970Sstevel@tonic-gate kmem_free(pcp, sizeof (struct pcnode)); 2980Sstevel@tonic-gate } 2990Sstevel@tonic-gate 3000Sstevel@tonic-gate /* 3010Sstevel@tonic-gate * Mark a pcnode as modified with the current time. 3020Sstevel@tonic-gate */ 303*5121Sfrankho /* ARGSUSED */ 3040Sstevel@tonic-gate void 305*5121Sfrankho pc_mark_mod(struct pcfs *fsp, struct pcnode *pcp) 3060Sstevel@tonic-gate { 3070Sstevel@tonic-gate timestruc_t now; 3080Sstevel@tonic-gate 309*5121Sfrankho if (PCTOV(pcp)->v_type == VDIR) 310*5121Sfrankho return; 311*5121Sfrankho 312*5121Sfrankho ASSERT(PCTOV(pcp)->v_type == VREG); 313*5121Sfrankho 314*5121Sfrankho gethrestime(&now); 315*5121Sfrankho if (pc_tvtopct(&now, &pcp->pc_entry.pcd_mtime)) 316*5121Sfrankho PC_DPRINTF1(2, "pc_mark_mod failed timestamp " 317*5121Sfrankho "conversion, curtime = %lld\n", 318*5121Sfrankho (long long)now.tv_sec); 319*5121Sfrankho 320*5121Sfrankho pcp->pc_flags |= PC_CHG; 3210Sstevel@tonic-gate } 3220Sstevel@tonic-gate 3230Sstevel@tonic-gate /* 3240Sstevel@tonic-gate * Mark a pcnode as accessed with the current time. 3250Sstevel@tonic-gate */ 3260Sstevel@tonic-gate void 327*5121Sfrankho pc_mark_acc(struct pcfs *fsp, struct pcnode *pcp) 3280Sstevel@tonic-gate { 3292720Sfrankho struct pctime pt = { 0, 0 }; 3300Sstevel@tonic-gate timestruc_t now; 3310Sstevel@tonic-gate 332*5121Sfrankho if (fsp->pcfs_flags & PCFS_NOATIME || PCTOV(pcp)->v_type == VDIR) 333*5121Sfrankho return; 334*5121Sfrankho 335*5121Sfrankho ASSERT(PCTOV(pcp)->v_type == VREG); 336*5121Sfrankho 337*5121Sfrankho gethrestime(&now); 338*5121Sfrankho if (pc_tvtopct(&now, &pt)) { 339*5121Sfrankho PC_DPRINTF1(2, "pc_mark_acc failed timestamp " 340*5121Sfrankho "conversion, curtime = %lld\n", 341*5121Sfrankho (long long)now.tv_sec); 342*5121Sfrankho return; 343*5121Sfrankho } 344*5121Sfrankho 345*5121Sfrankho /* 346*5121Sfrankho * We don't really want to write the adate for every access 347*5121Sfrankho * on flash media; make sure it really changed ! 348*5121Sfrankho */ 349*5121Sfrankho if (pcp->pc_entry.pcd_ladate != pt.pct_date) { 3500Sstevel@tonic-gate pcp->pc_entry.pcd_ladate = pt.pct_date; 351*5121Sfrankho pcp->pc_flags |= (PC_CHG | PC_ACC); 3520Sstevel@tonic-gate } 3530Sstevel@tonic-gate } 3540Sstevel@tonic-gate 3550Sstevel@tonic-gate /* 3560Sstevel@tonic-gate * Truncate a file to a length. 3570Sstevel@tonic-gate * Node must be locked. 3580Sstevel@tonic-gate */ 3590Sstevel@tonic-gate int 3600Sstevel@tonic-gate pc_truncate(struct pcnode *pcp, uint_t length) 3610Sstevel@tonic-gate { 3620Sstevel@tonic-gate struct pcfs *fsp; 3630Sstevel@tonic-gate struct vnode *vp; 3640Sstevel@tonic-gate int error = 0; 3650Sstevel@tonic-gate 3660Sstevel@tonic-gate PC_DPRINTF3(4, "pc_truncate pcp=0x%p, len=%u, size=%u\n", 3670Sstevel@tonic-gate (void *)pcp, length, pcp->pc_size); 3680Sstevel@tonic-gate vp = PCTOV(pcp); 3690Sstevel@tonic-gate if (pcp->pc_flags & PC_INVAL) 3700Sstevel@tonic-gate return (EIO); 3710Sstevel@tonic-gate fsp = VFSTOPCFS(vp->v_vfsp); 3720Sstevel@tonic-gate /* 3730Sstevel@tonic-gate * directories are always truncated to zero and are not marked 3740Sstevel@tonic-gate */ 3750Sstevel@tonic-gate if (vp->v_type == VDIR) { 3760Sstevel@tonic-gate error = pc_bfree(pcp, 0); 3770Sstevel@tonic-gate return (error); 3780Sstevel@tonic-gate } 3790Sstevel@tonic-gate /* 3800Sstevel@tonic-gate * If length is the same as the current size 3810Sstevel@tonic-gate * just mark the pcnode and return. 3820Sstevel@tonic-gate */ 3830Sstevel@tonic-gate if (length > pcp->pc_size) { 3840Sstevel@tonic-gate daddr_t bno; 385*5121Sfrankho uint_t llcn = howmany((offset_t)length, fsp->pcfs_clsize); 3860Sstevel@tonic-gate 3870Sstevel@tonic-gate /* 3880Sstevel@tonic-gate * We are extending a file. 3890Sstevel@tonic-gate * Extend it with _one_ call to pc_balloc (no holes) 3900Sstevel@tonic-gate * since we don't need to use the block number(s). 3910Sstevel@tonic-gate */ 3920Sstevel@tonic-gate if ((daddr_t)howmany((offset_t)pcp->pc_size, fsp->pcfs_clsize) < 393*5121Sfrankho (daddr_t)llcn) { 3940Sstevel@tonic-gate error = pc_balloc(pcp, (daddr_t)(llcn - 1), 1, &bno); 3950Sstevel@tonic-gate } 3960Sstevel@tonic-gate if (error) { 3972972Sfrankho pc_cluster32_t ncl = 0; 3980Sstevel@tonic-gate PC_DPRINTF1(2, "pc_truncate: error=%d\n", error); 3990Sstevel@tonic-gate /* 4000Sstevel@tonic-gate * probably ran out disk space; 4010Sstevel@tonic-gate * determine current file size 4020Sstevel@tonic-gate */ 4032972Sfrankho if (pc_fileclsize(fsp, pcp->pc_scluster, &ncl)) { 4042972Sfrankho PC_DPRINTF1(2, "cluster chain corruption, " 4052972Sfrankho "scluster=%d\n", pcp->pc_scluster); 4062972Sfrankho pcp->pc_flags |= PC_INVAL; 4072972Sfrankho } 4082972Sfrankho pcp->pc_size = fsp->pcfs_clsize * ncl; 4090Sstevel@tonic-gate } else 4100Sstevel@tonic-gate pcp->pc_size = length; 4110Sstevel@tonic-gate 4120Sstevel@tonic-gate } else if (length < pcp->pc_size) { 4130Sstevel@tonic-gate /* 4140Sstevel@tonic-gate * We are shrinking a file. 4150Sstevel@tonic-gate * Free blocks after the block that length points to. 4160Sstevel@tonic-gate */ 4170Sstevel@tonic-gate if (pc_blkoff(fsp, length) == 0) { 4180Sstevel@tonic-gate /* 4190Sstevel@tonic-gate * Truncation to a block (cluster size) boundary only 4200Sstevel@tonic-gate * requires us to invalidate everything after the new 4210Sstevel@tonic-gate * end of the file. 4220Sstevel@tonic-gate */ 4230Sstevel@tonic-gate (void) pvn_vplist_dirty(PCTOV(pcp), (u_offset_t)length, 424*5121Sfrankho pcfs_putapage, B_INVAL | B_TRUNC, CRED()); 4250Sstevel@tonic-gate } else { 4260Sstevel@tonic-gate /* 4270Sstevel@tonic-gate * pvn_vpzero() cannot deal with more than MAXBSIZE 4280Sstevel@tonic-gate * chunks. Since the FAT clustersize can get larger 4290Sstevel@tonic-gate * than that, we'll zero from the new length to the 4300Sstevel@tonic-gate * end of the cluster for clustersizes smaller than 4310Sstevel@tonic-gate * MAXBSIZE - or the end of the MAXBSIZE block in 4320Sstevel@tonic-gate * case we've got a large clustersize. 4330Sstevel@tonic-gate */ 4340Sstevel@tonic-gate size_t nbytes = 4350Sstevel@tonic-gate roundup(length, MIN(fsp->pcfs_clsize, MAXBSIZE)) - 4360Sstevel@tonic-gate length; 4370Sstevel@tonic-gate 4380Sstevel@tonic-gate pvn_vpzero(PCTOV(pcp), (u_offset_t)length, nbytes); 4390Sstevel@tonic-gate (void) pvn_vplist_dirty(PCTOV(pcp), 4400Sstevel@tonic-gate (u_offset_t)length + nbytes, 4410Sstevel@tonic-gate pcfs_putapage, B_INVAL | B_TRUNC, CRED()); 4420Sstevel@tonic-gate } 443*5121Sfrankho error = pc_bfree(pcp, (pc_cluster32_t) 444*5121Sfrankho howmany((offset_t)length, fsp->pcfs_clsize)); 4450Sstevel@tonic-gate pcp->pc_size = length; 4460Sstevel@tonic-gate } 447*5121Sfrankho 448*5121Sfrankho /* 449*5121Sfrankho * This is the only place in PCFS code where pc_mark_mod() is called 450*5121Sfrankho * without setting PC_MOD. May be a historical artifact ... 451*5121Sfrankho */ 452*5121Sfrankho pc_mark_mod(fsp, pcp); 4530Sstevel@tonic-gate return (error); 4540Sstevel@tonic-gate } 4550Sstevel@tonic-gate 4560Sstevel@tonic-gate /* 4570Sstevel@tonic-gate * Get block for entry. 4580Sstevel@tonic-gate */ 4590Sstevel@tonic-gate static int 4600Sstevel@tonic-gate pc_getentryblock(struct pcnode *pcp, struct buf **bpp) 4610Sstevel@tonic-gate { 4620Sstevel@tonic-gate struct pcfs *fsp; 4630Sstevel@tonic-gate 4640Sstevel@tonic-gate fsp = VFSTOPCFS(PCTOV(pcp)->v_vfsp); 4650Sstevel@tonic-gate if (pcp->pc_eblkno >= fsp->pcfs_datastart || 4660Sstevel@tonic-gate (pcp->pc_eblkno - fsp->pcfs_rdirstart) < 4670Sstevel@tonic-gate (fsp->pcfs_rdirsec & ~(fsp->pcfs_spcl - 1))) { 4680Sstevel@tonic-gate *bpp = bread(fsp->pcfs_xdev, 4690Sstevel@tonic-gate pc_dbdaddr(fsp, pcp->pc_eblkno), fsp->pcfs_clsize); 4700Sstevel@tonic-gate } else { 4710Sstevel@tonic-gate *bpp = bread(fsp->pcfs_xdev, 4720Sstevel@tonic-gate pc_dbdaddr(fsp, pcp->pc_eblkno), 473*5121Sfrankho (int)(fsp->pcfs_datastart - pcp->pc_eblkno) * 4740Sstevel@tonic-gate fsp->pcfs_secsize); 4750Sstevel@tonic-gate } 4760Sstevel@tonic-gate if ((*bpp)->b_flags & B_ERROR) { 4770Sstevel@tonic-gate brelse(*bpp); 4780Sstevel@tonic-gate pc_mark_irrecov(fsp); 4790Sstevel@tonic-gate return (EIO); 4800Sstevel@tonic-gate } 4810Sstevel@tonic-gate return (0); 4820Sstevel@tonic-gate } 4830Sstevel@tonic-gate 4840Sstevel@tonic-gate /* 4850Sstevel@tonic-gate * Sync all data associated with a file. 4860Sstevel@tonic-gate * Flush all the blocks in the buffer cache out to disk, sync the FAT and 4870Sstevel@tonic-gate * update the directory entry. 4880Sstevel@tonic-gate */ 4890Sstevel@tonic-gate int 4900Sstevel@tonic-gate pc_nodesync(struct pcnode *pcp) 4910Sstevel@tonic-gate { 4920Sstevel@tonic-gate struct pcfs *fsp; 4930Sstevel@tonic-gate int err; 4940Sstevel@tonic-gate struct vnode *vp; 4950Sstevel@tonic-gate 4960Sstevel@tonic-gate vp = PCTOV(pcp); 4970Sstevel@tonic-gate fsp = VFSTOPCFS(vp->v_vfsp); 4980Sstevel@tonic-gate err = 0; 4990Sstevel@tonic-gate if (pcp->pc_flags & PC_MOD) { 5000Sstevel@tonic-gate /* 5010Sstevel@tonic-gate * Flush all data blocks from buffer cache and 5020Sstevel@tonic-gate * update the FAT which points to the data. 5030Sstevel@tonic-gate */ 5040Sstevel@tonic-gate if (err = syncpcp(pcp, 0)) { /* %% ?? how to handle error? */ 5050Sstevel@tonic-gate if (err == ENOMEM) 5060Sstevel@tonic-gate return (err); 5070Sstevel@tonic-gate else { 5080Sstevel@tonic-gate pc_mark_irrecov(fsp); 5090Sstevel@tonic-gate return (EIO); 5100Sstevel@tonic-gate } 5110Sstevel@tonic-gate } 5120Sstevel@tonic-gate pcp->pc_flags &= ~PC_MOD; 5130Sstevel@tonic-gate } 5140Sstevel@tonic-gate /* 5150Sstevel@tonic-gate * update the directory entry 5160Sstevel@tonic-gate */ 5170Sstevel@tonic-gate if (pcp->pc_flags & PC_CHG) 5180Sstevel@tonic-gate (void) pc_nodeupdate(pcp); 5190Sstevel@tonic-gate return (err); 5200Sstevel@tonic-gate } 5210Sstevel@tonic-gate 5220Sstevel@tonic-gate /* 5230Sstevel@tonic-gate * Update the node's directory entry. 5240Sstevel@tonic-gate */ 5250Sstevel@tonic-gate int 5260Sstevel@tonic-gate pc_nodeupdate(struct pcnode *pcp) 5270Sstevel@tonic-gate { 5280Sstevel@tonic-gate struct buf *bp; 5290Sstevel@tonic-gate int error; 5300Sstevel@tonic-gate struct vnode *vp; 5310Sstevel@tonic-gate struct pcfs *fsp; 5320Sstevel@tonic-gate 5330Sstevel@tonic-gate vp = PCTOV(pcp); 5340Sstevel@tonic-gate fsp = VFSTOPCFS(vp->v_vfsp); 5350Sstevel@tonic-gate if (IS_FAT32(fsp) && (vp->v_flag & VROOT)) { 5360Sstevel@tonic-gate /* no node to update */ 5370Sstevel@tonic-gate pcp->pc_flags &= ~(PC_CHG | PC_MOD | PC_ACC); 5380Sstevel@tonic-gate return (0); 5390Sstevel@tonic-gate } 5400Sstevel@tonic-gate if (vp->v_flag & VROOT) { 5410Sstevel@tonic-gate panic("pc_nodeupdate"); 5420Sstevel@tonic-gate } 5430Sstevel@tonic-gate if (pcp->pc_flags & PC_INVAL) 5440Sstevel@tonic-gate return (0); 5450Sstevel@tonic-gate PC_DPRINTF3(7, "pc_nodeupdate pcp=0x%p, bn=%ld, off=%d\n", (void *)pcp, 5460Sstevel@tonic-gate pcp->pc_eblkno, pcp->pc_eoffset); 5470Sstevel@tonic-gate 5480Sstevel@tonic-gate if (error = pc_getentryblock(pcp, &bp)) { 5490Sstevel@tonic-gate return (error); 5500Sstevel@tonic-gate } 5510Sstevel@tonic-gate if (vp->v_type == VREG) { 5520Sstevel@tonic-gate if (pcp->pc_flags & PC_CHG) 5530Sstevel@tonic-gate pcp->pc_entry.pcd_attr |= PCA_ARCH; 5540Sstevel@tonic-gate pcp->pc_entry.pcd_size = htoli(pcp->pc_size); 5550Sstevel@tonic-gate } 5560Sstevel@tonic-gate pc_setstartcluster(fsp, &pcp->pc_entry, pcp->pc_scluster); 5570Sstevel@tonic-gate *((struct pcdir *)(bp->b_un.b_addr + pcp->pc_eoffset)) = pcp->pc_entry; 5580Sstevel@tonic-gate bwrite2(bp); 5590Sstevel@tonic-gate error = geterror(bp); 5600Sstevel@tonic-gate brelse(bp); 5610Sstevel@tonic-gate if (error) { 562*5121Sfrankho error = EIO; 5630Sstevel@tonic-gate pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp)); 5640Sstevel@tonic-gate } 5650Sstevel@tonic-gate pcp->pc_flags &= ~(PC_CHG | PC_MOD | PC_ACC); 5660Sstevel@tonic-gate return (error); 5670Sstevel@tonic-gate } 5680Sstevel@tonic-gate 5690Sstevel@tonic-gate /* 5700Sstevel@tonic-gate * Verify that the disk in the drive is the same one that we 5710Sstevel@tonic-gate * got the pcnode from. 5720Sstevel@tonic-gate * MUST be called with node unlocked. 5730Sstevel@tonic-gate */ 5740Sstevel@tonic-gate int 5750Sstevel@tonic-gate pc_verify(struct pcfs *fsp) 5760Sstevel@tonic-gate { 5770Sstevel@tonic-gate int fdstatus = 0; 5780Sstevel@tonic-gate int error = 0; 5790Sstevel@tonic-gate 5800Sstevel@tonic-gate if (!fsp || fsp->pcfs_flags & PCFS_IRRECOV) 5810Sstevel@tonic-gate return (EIO); 5820Sstevel@tonic-gate 5830Sstevel@tonic-gate if (!(fsp->pcfs_flags & PCFS_NOCHK) && fsp->pcfs_fatp) { 584*5121Sfrankho /* 585*5121Sfrankho * This "has it been removed" check should better be 586*5121Sfrankho * modified for removeable media that are not floppies. 587*5121Sfrankho * dkio-managed devices such as USB/firewire external 588*5121Sfrankho * disks/memory sticks/floppies (gasp) do not understand 589*5121Sfrankho * this ioctl. 590*5121Sfrankho */ 5910Sstevel@tonic-gate PC_DPRINTF1(4, "pc_verify fsp=0x%p\n", (void *)fsp); 5920Sstevel@tonic-gate error = cdev_ioctl(fsp->pcfs_vfs->vfs_dev, 593*5121Sfrankho FDGETCHANGE, (intptr_t)&fdstatus, FNATIVE | FKIOCTL, 5940Sstevel@tonic-gate NULL, NULL); 5950Sstevel@tonic-gate 5960Sstevel@tonic-gate if (error) { 5970Sstevel@tonic-gate if (error == ENOTTY || error == ENXIO) { 598*5121Sfrankho /* 599*5121Sfrankho * See comment above. This is a workaround 600*5121Sfrankho * for removeable media that don't understand 601*5121Sfrankho * floppy ioctls. 602*5121Sfrankho */ 6030Sstevel@tonic-gate error = 0; 6040Sstevel@tonic-gate } else { 6050Sstevel@tonic-gate PC_DPRINTF1(1, 6060Sstevel@tonic-gate "pc_verify: FDGETCHANGE ioctl failed: %d\n", 6070Sstevel@tonic-gate error); 6080Sstevel@tonic-gate pc_mark_irrecov(fsp); 6090Sstevel@tonic-gate } 6100Sstevel@tonic-gate } else if (fsp->pcfs_fatjustread) { 6110Sstevel@tonic-gate /* 6120Sstevel@tonic-gate * Ignore the results of the ioctl if we just 6130Sstevel@tonic-gate * read the FAT. There is a good chance that 6140Sstevel@tonic-gate * the disk changed bit will be on, because 6150Sstevel@tonic-gate * we've just mounted and we don't want to 6160Sstevel@tonic-gate * give a false positive that the sky is falling. 6170Sstevel@tonic-gate */ 6180Sstevel@tonic-gate fsp->pcfs_fatjustread = 0; 6190Sstevel@tonic-gate } else { 6200Sstevel@tonic-gate /* 6210Sstevel@tonic-gate * Oddly enough we can't check just one flag here. The 6220Sstevel@tonic-gate * x86 floppy driver sets a different flag 6230Sstevel@tonic-gate * (FDGC_DETECTED) than the sparc driver does. 6240Sstevel@tonic-gate * I think this MAY be a bug, and I filed 4165938 6250Sstevel@tonic-gate * to get someone to look at the behavior 6260Sstevel@tonic-gate * a bit more closely. In the meantime, my testing and 6270Sstevel@tonic-gate * code examination seem to indicate it is safe to 6280Sstevel@tonic-gate * check for either bit being set. 6290Sstevel@tonic-gate */ 6300Sstevel@tonic-gate if (fdstatus & (FDGC_HISTORY | FDGC_DETECTED)) { 6310Sstevel@tonic-gate PC_DPRINTF0(1, "pc_verify: change detected\n"); 6320Sstevel@tonic-gate pc_mark_irrecov(fsp); 6330Sstevel@tonic-gate } 6340Sstevel@tonic-gate } 6350Sstevel@tonic-gate } 636*5121Sfrankho if (error == 0 && fsp->pcfs_fatp == NULL) { 6370Sstevel@tonic-gate error = pc_getfat(fsp); 6380Sstevel@tonic-gate } 6390Sstevel@tonic-gate 6400Sstevel@tonic-gate return (error); 6410Sstevel@tonic-gate } 6420Sstevel@tonic-gate 6430Sstevel@tonic-gate /* 6440Sstevel@tonic-gate * The disk has changed, pulling the rug out from beneath us. 6450Sstevel@tonic-gate * Mark the FS as being in an irrecoverable state. 6460Sstevel@tonic-gate * In a short while we'll clean up. 6470Sstevel@tonic-gate */ 6480Sstevel@tonic-gate void 6490Sstevel@tonic-gate pc_mark_irrecov(struct pcfs *fsp) 6500Sstevel@tonic-gate { 6510Sstevel@tonic-gate if (!(fsp->pcfs_flags & PCFS_NOCHK)) { 6520Sstevel@tonic-gate if (pc_lockfs(fsp, 1, 0)) { 6530Sstevel@tonic-gate /* 6540Sstevel@tonic-gate * Locking failed, which currently would 6550Sstevel@tonic-gate * only happen if the FS were already 6560Sstevel@tonic-gate * marked as hosed. If another reason for 6570Sstevel@tonic-gate * failure were to arise in the future, this 6580Sstevel@tonic-gate * routine would have to change. 6590Sstevel@tonic-gate */ 6600Sstevel@tonic-gate return; 6610Sstevel@tonic-gate } 6620Sstevel@tonic-gate 6630Sstevel@tonic-gate fsp->pcfs_flags |= PCFS_IRRECOV; 6640Sstevel@tonic-gate cmn_err(CE_WARN, 665*5121Sfrankho "Disk was changed during an update or\n" 666*5121Sfrankho "an irrecoverable error was encountered.\n" 667*5121Sfrankho "File damage is possible. To prevent further\n" 668*5121Sfrankho "damage, this pcfs instance will now be frozen.\n" 669*5121Sfrankho "Use umount(1M) to release the instance.\n"); 6700Sstevel@tonic-gate (void) pc_unlockfs(fsp); 6710Sstevel@tonic-gate } 6720Sstevel@tonic-gate } 6730Sstevel@tonic-gate 6740Sstevel@tonic-gate /* 6750Sstevel@tonic-gate * The disk has been changed! 6760Sstevel@tonic-gate */ 6770Sstevel@tonic-gate void 6780Sstevel@tonic-gate pc_diskchanged(struct pcfs *fsp) 6790Sstevel@tonic-gate { 6802720Sfrankho struct pcnode *pcp, *npcp = NULL; 6812720Sfrankho struct pchead *hp; 6822720Sfrankho struct vnode *vp; 6832720Sfrankho extern vfs_t EIO_vfs; 6842720Sfrankho struct vfs *vfsp; 6850Sstevel@tonic-gate 6860Sstevel@tonic-gate /* 6870Sstevel@tonic-gate * Eliminate all pcnodes (dir & file) associated with this fs. 6880Sstevel@tonic-gate * If the node is internal, ie, no references outside of 6890Sstevel@tonic-gate * pcfs itself, then release the associated vnode structure. 6900Sstevel@tonic-gate * Invalidate the in core FAT. 6910Sstevel@tonic-gate * Invalidate cached data blocks and blocks waiting for I/O. 6920Sstevel@tonic-gate */ 6930Sstevel@tonic-gate PC_DPRINTF1(1, "pc_diskchanged fsp=0x%p\n", (void *)fsp); 6940Sstevel@tonic-gate 6952720Sfrankho vfsp = PCFSTOVFS(fsp); 6962720Sfrankho 6970Sstevel@tonic-gate for (hp = pcdhead; hp < &pcdhead[NPCHASH]; hp++) { 6980Sstevel@tonic-gate for (pcp = hp->pch_forw; 6990Sstevel@tonic-gate pcp != (struct pcnode *)hp; pcp = npcp) { 7000Sstevel@tonic-gate npcp = pcp -> pc_forw; 7010Sstevel@tonic-gate vp = PCTOV(pcp); 7022720Sfrankho if ((vp->v_vfsp == vfsp) && 7030Sstevel@tonic-gate !(pcp->pc_flags & PC_RELEHOLD)) { 7040Sstevel@tonic-gate mutex_enter(&(vp)->v_lock); 7050Sstevel@tonic-gate if (vp->v_count > 0) { 7060Sstevel@tonic-gate mutex_exit(&(vp)->v_lock); 7070Sstevel@tonic-gate continue; 7080Sstevel@tonic-gate } 7090Sstevel@tonic-gate mutex_exit(&(vp)->v_lock); 7100Sstevel@tonic-gate VN_HOLD(vp); 7110Sstevel@tonic-gate remque(pcp); 7120Sstevel@tonic-gate vp->v_data = NULL; 7130Sstevel@tonic-gate vp->v_vfsp = &EIO_vfs; 7140Sstevel@tonic-gate vp->v_type = VBAD; 7150Sstevel@tonic-gate VN_RELE(vp); 7162720Sfrankho if (!(pcp->pc_flags & PC_EXTERNAL)) { 7172720Sfrankho (void) pvn_vplist_dirty(vp, 7182720Sfrankho (u_offset_t)0, pcfs_putapage, 7192720Sfrankho B_INVAL | B_TRUNC, 7202720Sfrankho (struct cred *)NULL); 7210Sstevel@tonic-gate vn_free(vp); 7222720Sfrankho } 7230Sstevel@tonic-gate kmem_free(pcp, sizeof (struct pcnode)); 7240Sstevel@tonic-gate fsp->pcfs_nrefs --; 7252720Sfrankho VFS_RELE(vfsp); 7260Sstevel@tonic-gate } 7270Sstevel@tonic-gate } 7280Sstevel@tonic-gate } 7290Sstevel@tonic-gate for (hp = pcfhead; fsp->pcfs_frefs && hp < &pcfhead[NPCHASH]; hp++) { 7300Sstevel@tonic-gate for (pcp = hp->pch_forw; fsp->pcfs_frefs && 7310Sstevel@tonic-gate pcp != (struct pcnode *)hp; pcp = npcp) { 7320Sstevel@tonic-gate npcp = pcp -> pc_forw; 7330Sstevel@tonic-gate vp = PCTOV(pcp); 7342720Sfrankho if ((vp->v_vfsp == vfsp) && 7350Sstevel@tonic-gate !(pcp->pc_flags & PC_RELEHOLD)) { 7360Sstevel@tonic-gate mutex_enter(&(vp)->v_lock); 7370Sstevel@tonic-gate if (vp->v_count > 0) { 7380Sstevel@tonic-gate mutex_exit(&(vp)->v_lock); 7390Sstevel@tonic-gate continue; 7400Sstevel@tonic-gate } 7410Sstevel@tonic-gate mutex_exit(&(vp)->v_lock); 7420Sstevel@tonic-gate VN_HOLD(vp); 7430Sstevel@tonic-gate remque(pcp); 7440Sstevel@tonic-gate vp->v_data = NULL; 7450Sstevel@tonic-gate vp->v_vfsp = &EIO_vfs; 7460Sstevel@tonic-gate vp->v_type = VBAD; 7470Sstevel@tonic-gate VN_RELE(vp); 7482720Sfrankho if (!(pcp->pc_flags & PC_EXTERNAL)) { 7492720Sfrankho (void) pvn_vplist_dirty(vp, 7502720Sfrankho (u_offset_t)0, pcfs_putapage, 7512720Sfrankho B_INVAL | B_TRUNC, 7522720Sfrankho (struct cred *)NULL); 7530Sstevel@tonic-gate vn_free(vp); 7542720Sfrankho } 7550Sstevel@tonic-gate kmem_free(pcp, sizeof (struct pcnode)); 7562972Sfrankho fsp->pcfs_frefs--; 7572972Sfrankho fsp->pcfs_nrefs--; 7582720Sfrankho VFS_RELE(vfsp); 7590Sstevel@tonic-gate } 7600Sstevel@tonic-gate } 7610Sstevel@tonic-gate } 7620Sstevel@tonic-gate #ifdef undef 7630Sstevel@tonic-gate if (fsp->pcfs_frefs) { 7640Sstevel@tonic-gate rw_exit(&pcnodes_lock); 7650Sstevel@tonic-gate panic("pc_diskchanged: frefs"); 7660Sstevel@tonic-gate } 7670Sstevel@tonic-gate if (fsp->pcfs_nrefs) { 7680Sstevel@tonic-gate rw_exit(&pcnodes_lock); 7690Sstevel@tonic-gate panic("pc_diskchanged: nrefs"); 7700Sstevel@tonic-gate } 7710Sstevel@tonic-gate #endif 7722720Sfrankho if (!(vfsp->vfs_flag & VFS_UNMOUNTED) && 7732720Sfrankho fsp->pcfs_fatp != (uchar_t *)0) { 7740Sstevel@tonic-gate pc_invalfat(fsp); 7750Sstevel@tonic-gate } else { 7760Sstevel@tonic-gate binval(fsp->pcfs_xdev); 7770Sstevel@tonic-gate } 7780Sstevel@tonic-gate } 779