1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#include <sys/param.h>
29#include <sys/t_lock.h>
30#include <sys/errno.h>
31#include <sys/sysmacros.h>
32#include <sys/buf.h>
33#include <sys/systm.h>
34#include <sys/vfs.h>
35#include <sys/vnode.h>
36#include <sys/kmem.h>
37#include <sys/proc.h>
38#include <sys/cred.h>
39#include <sys/cmn_err.h>
40#include <sys/debug.h>
41#include <vm/pvn.h>
42#include <sys/fs/pc_label.h>
43#include <sys/fs/pc_fs.h>
44#include <sys/fs/pc_dir.h>
45#include <sys/fs/pc_node.h>
46#include <sys/dirent.h>
47#include <sys/fdio.h>
48#include <sys/file.h>
49#include <sys/conf.h>
50
51struct pchead pcfhead[NPCHASH];
52struct pchead pcdhead[NPCHASH];
53
54extern krwlock_t pcnodes_lock;
55
56static int	pc_getentryblock(struct pcnode *, struct buf **);
57static int	syncpcp(struct pcnode *, int);
58
59/*
60 * fake entry for root directory, since this does not have a parent
61 * pointing to it.
62 */
63struct pcdir pcfs_rootdirentry = {
64	"",
65	"",
66	PCA_DIR
67};
68
69void
70pc_init(void)
71{
72	struct pchead *hdp, *hfp;
73	int i;
74	for (i = 0; i < NPCHASH; i++) {
75		hdp = &pcdhead[i];
76		hfp = &pcfhead[i];
77		hdp->pch_forw =  (struct pcnode *)hdp;
78		hdp->pch_back =  (struct pcnode *)hdp;
79		hfp->pch_forw =  (struct pcnode *)hfp;
80		hfp->pch_back =  (struct pcnode *)hfp;
81	}
82}
83
84struct pcnode *
85pc_getnode(
86	struct pcfs *fsp,	/* filsystem for node */
87	daddr_t blkno,		/* phys block no of dir entry */
88	int offset,		/* offset of dir entry in block */
89	struct pcdir *ep)	/* node dir entry */
90{
91	struct pcnode *pcp;
92	struct pchead *hp;
93	struct vnode *vp;
94	pc_cluster32_t scluster;
95
96	ASSERT(fsp->pcfs_flags & PCFS_LOCKED);
97	if (ep == (struct pcdir *)0) {
98		ep = &pcfs_rootdirentry;
99		scluster = 0;
100	} else {
101		scluster = pc_getstartcluster(fsp, ep);
102	}
103	/*
104	 * First look for active nodes.
105	 * File nodes are identified by the location (blkno, offset) of
106	 * its directory entry.
107	 * Directory nodes are identified by the starting cluster number
108	 * for the entries.
109	 */
110	if (ep->pcd_attr & PCA_DIR) {
111		hp = &pcdhead[PCDHASH(fsp, scluster)];
112		rw_enter(&pcnodes_lock, RW_READER);
113		for (pcp = hp->pch_forw;
114		    pcp != (struct pcnode *)hp; pcp = pcp->pc_forw) {
115			if ((fsp == VFSTOPCFS(PCTOV(pcp)->v_vfsp)) &&
116			    (scluster == pcp->pc_scluster)) {
117				VN_HOLD(PCTOV(pcp));
118				rw_exit(&pcnodes_lock);
119				return (pcp);
120			}
121		}
122		rw_exit(&pcnodes_lock);
123	} else {
124		hp = &pcfhead[PCFHASH(fsp, blkno, offset)];
125		rw_enter(&pcnodes_lock, RW_READER);
126		for (pcp = hp->pch_forw;
127		    pcp != (struct pcnode *)hp; pcp = pcp->pc_forw) {
128			if ((fsp == VFSTOPCFS(PCTOV(pcp)->v_vfsp)) &&
129			    ((pcp->pc_flags & PC_INVAL) == 0) &&
130			    (blkno == pcp->pc_eblkno) &&
131			    (offset == pcp->pc_eoffset)) {
132				VN_HOLD(PCTOV(pcp));
133				rw_exit(&pcnodes_lock);
134				return (pcp);
135			}
136		}
137		rw_exit(&pcnodes_lock);
138	}
139	/*
140	 * Cannot find node in active list. Allocate memory for a new node
141	 * initialize it, and put it on the active list.
142	 */
143	pcp = kmem_alloc(sizeof (struct pcnode), KM_SLEEP);
144	bzero(pcp, sizeof (struct pcnode));
145	vp = vn_alloc(KM_SLEEP);
146	pcp->pc_vn = vp;
147	pcp->pc_entry = *ep;
148	pcp->pc_eblkno = blkno;
149	pcp->pc_eoffset = offset;
150	pcp->pc_scluster = scluster;
151	pcp->pc_lcluster = scluster;
152	pcp->pc_lindex = 0;
153	pcp->pc_flags = 0;
154	if (ep->pcd_attr & PCA_DIR) {
155		vn_setops(vp, pcfs_dvnodeops);
156		vp->v_type = VDIR;
157		if (scluster == 0) {
158			vp->v_flag = VROOT;
159			blkno = offset = 0;
160			if (IS_FAT32(fsp)) {
161				pc_cluster32_t ncl = 0;
162
163				scluster = fsp->pcfs_rdirstart;
164				if (pc_fileclsize(fsp, scluster, &ncl)) {
165					PC_DPRINTF1(2, "cluster chain "
166					    "corruption, scluster=%d\n",
167					    scluster);
168					pcp->pc_flags |= PC_INVAL;
169				}
170				pcp->pc_size = fsp->pcfs_clsize * ncl;
171			} else {
172				pcp->pc_size =
173				    fsp->pcfs_rdirsec * fsp->pcfs_secsize;
174			}
175		} else {
176			pc_cluster32_t ncl = 0;
177
178			if (pc_fileclsize(fsp, scluster, &ncl)) {
179				PC_DPRINTF1(2, "cluster chain corruption, "
180				    "scluster=%d\n", scluster);
181				pcp->pc_flags |= PC_INVAL;
182			}
183			pcp->pc_size = fsp->pcfs_clsize * ncl;
184		}
185	} else {
186		vn_setops(vp, pcfs_fvnodeops);
187		vp->v_type = VREG;
188		vp->v_flag = VNOSWAP;
189		fsp->pcfs_frefs++;
190		pcp->pc_size = ltohi(ep->pcd_size);
191	}
192	fsp->pcfs_nrefs++;
193	VFS_HOLD(PCFSTOVFS(fsp));
194	vp->v_data = (caddr_t)pcp;
195	vp->v_vfsp = PCFSTOVFS(fsp);
196	vn_exists(vp);
197	rw_enter(&pcnodes_lock, RW_WRITER);
198	insque(pcp, hp);
199	rw_exit(&pcnodes_lock);
200	return (pcp);
201}
202
203int
204syncpcp(struct pcnode *pcp, int flags)
205{
206	int err;
207	if (!vn_has_cached_data(PCTOV(pcp)))
208		err = 0;
209	else
210		err = VOP_PUTPAGE(PCTOV(pcp), 0, 0, flags,
211		    kcred, NULL);
212
213	return (err);
214}
215
216void
217pc_rele(struct pcnode *pcp)
218{
219	struct pcfs *fsp;
220	struct vnode *vp;
221	int err;
222
223	vp = PCTOV(pcp);
224	PC_DPRINTF1(8, "pc_rele vp=0x%p\n", (void *)vp);
225
226	fsp = VFSTOPCFS(vp->v_vfsp);
227	ASSERT(fsp->pcfs_flags & PCFS_LOCKED);
228
229	rw_enter(&pcnodes_lock, RW_WRITER);
230	pcp->pc_flags |= PC_RELEHOLD;
231
232retry:
233	if (vp->v_type != VDIR && (pcp->pc_flags & PC_INVAL) == 0) {
234		/*
235		 * If the file was removed while active it may be safely
236		 * truncated now.
237		 */
238
239		if (pcp->pc_entry.pcd_filename[0] == PCD_ERASED) {
240			(void) pc_truncate(pcp, 0);
241		} else if (pcp->pc_flags & PC_CHG) {
242			(void) pc_nodeupdate(pcp);
243		}
244		err = syncpcp(pcp, B_INVAL);
245		if (err) {
246			(void) syncpcp(pcp, B_INVAL | B_FORCE);
247		}
248	}
249	if (vn_has_cached_data(vp)) {
250		/*
251		 * pvn_vplist_dirty will abort all old pages
252		 */
253		(void) pvn_vplist_dirty(vp, (u_offset_t)0,
254		    pcfs_putapage, B_INVAL, (struct cred *)NULL);
255	}
256
257	(void) pc_syncfat(fsp);
258	mutex_enter(&vp->v_lock);
259	if (vn_has_cached_data(vp)) {
260		mutex_exit(&vp->v_lock);
261		goto retry;
262	}
263	ASSERT(!vn_has_cached_data(vp));
264
265	vp->v_count--;  /* release our hold from vn_rele */
266	if (vp->v_count > 0) { /* Is this check still needed? */
267		PC_DPRINTF1(3, "pc_rele: pcp=0x%p HELD AGAIN!\n", (void *)pcp);
268		mutex_exit(&vp->v_lock);
269		pcp->pc_flags &= ~PC_RELEHOLD;
270		rw_exit(&pcnodes_lock);
271		return;
272	}
273
274	remque(pcp);
275	rw_exit(&pcnodes_lock);
276	/*
277	 * XXX - old code had a check for !(pcp->pc_flags & PC_INVAL)
278	 * here. Seems superfluous/incorrect, but then earlier on PC_INVAL
279	 * was never set anywhere in PCFS. Now it is, and we _have_ to drop
280	 * the file reference here. Else, we'd screw up umount/modunload.
281	 */
282	if ((vp->v_type == VREG)) {
283		fsp->pcfs_frefs--;
284	}
285	fsp->pcfs_nrefs--;
286	VFS_RELE(vp->v_vfsp);
287
288	if (fsp->pcfs_nrefs < 0) {
289		panic("pc_rele: nrefs count");
290	}
291	if (fsp->pcfs_frefs < 0) {
292		panic("pc_rele: frefs count");
293	}
294
295	mutex_exit(&vp->v_lock);
296	vn_invalid(vp);
297	vn_free(vp);
298	kmem_free(pcp, sizeof (struct pcnode));
299}
300
301/*
302 * Mark a pcnode as modified with the current time.
303 */
304/* ARGSUSED */
305void
306pc_mark_mod(struct pcfs *fsp, struct pcnode *pcp)
307{
308	timestruc_t now;
309
310	if (PCTOV(pcp)->v_type == VDIR)
311		return;
312
313	ASSERT(PCTOV(pcp)->v_type == VREG);
314
315	gethrestime(&now);
316	if (pc_tvtopct(&now, &pcp->pc_entry.pcd_mtime))
317		PC_DPRINTF1(2, "pc_mark_mod failed timestamp "
318		    "conversion, curtime = %lld\n",
319		    (long long)now.tv_sec);
320
321	pcp->pc_flags |= PC_CHG;
322}
323
324/*
325 * Mark a pcnode as accessed with the current time.
326 */
327void
328pc_mark_acc(struct pcfs *fsp, struct pcnode *pcp)
329{
330	struct pctime pt = { 0, 0 };
331	timestruc_t now;
332
333	if (fsp->pcfs_flags & PCFS_NOATIME || PCTOV(pcp)->v_type == VDIR)
334		return;
335
336	ASSERT(PCTOV(pcp)->v_type == VREG);
337
338	gethrestime(&now);
339	if (pc_tvtopct(&now, &pt)) {
340		PC_DPRINTF1(2, "pc_mark_acc failed timestamp "
341		    "conversion, curtime = %lld\n",
342		    (long long)now.tv_sec);
343		return;
344	}
345
346	/*
347	 * We don't really want to write the adate for every access
348	 * on flash media; make sure it really changed !
349	 */
350	if (pcp->pc_entry.pcd_ladate != pt.pct_date) {
351		pcp->pc_entry.pcd_ladate = pt.pct_date;
352		pcp->pc_flags |= (PC_CHG | PC_ACC);
353	}
354}
355
356/*
357 * Truncate a file to a length.
358 * Node must be locked.
359 */
360int
361pc_truncate(struct pcnode *pcp, uint_t length)
362{
363	struct pcfs *fsp;
364	struct vnode *vp;
365	int error = 0;
366
367	PC_DPRINTF3(4, "pc_truncate pcp=0x%p, len=%u, size=%u\n",
368	    (void *)pcp, length, pcp->pc_size);
369	vp = PCTOV(pcp);
370	if (pcp->pc_flags & PC_INVAL)
371		return (EIO);
372	fsp = VFSTOPCFS(vp->v_vfsp);
373	/*
374	 * directories are always truncated to zero and are not marked
375	 */
376	if (vp->v_type == VDIR) {
377		error = pc_bfree(pcp, 0);
378		return (error);
379	}
380	/*
381	 * If length is the same as the current size
382	 * just mark the pcnode and return.
383	 */
384	if (length > pcp->pc_size) {
385		daddr_t bno;
386		uint_t llcn = howmany((offset_t)length, fsp->pcfs_clsize);
387
388		/*
389		 * We are extending a file.
390		 * Extend it with _one_ call to pc_balloc (no holes)
391		 * since we don't need to use the block number(s).
392		 */
393		if ((daddr_t)howmany((offset_t)pcp->pc_size, fsp->pcfs_clsize) <
394		    (daddr_t)llcn) {
395			error = pc_balloc(pcp, (daddr_t)(llcn - 1), 1, &bno);
396		}
397		if (error) {
398			pc_cluster32_t ncl = 0;
399			PC_DPRINTF1(2, "pc_truncate: error=%d\n", error);
400			/*
401			 * probably ran out disk space;
402			 * determine current file size
403			 */
404			if (pc_fileclsize(fsp, pcp->pc_scluster, &ncl)) {
405				PC_DPRINTF1(2, "cluster chain corruption, "
406				    "scluster=%d\n", pcp->pc_scluster);
407				pcp->pc_flags |= PC_INVAL;
408			}
409			pcp->pc_size = fsp->pcfs_clsize * ncl;
410		} else
411			pcp->pc_size = length;
412
413	} else if (length < pcp->pc_size) {
414		/*
415		 * We are shrinking a file.
416		 * Free blocks after the block that length points to.
417		 */
418		if (pc_blkoff(fsp, length) == 0) {
419			/*
420			 * Truncation to a block (cluster size) boundary only
421			 * requires us to invalidate everything after the new
422			 * end of the file.
423			 */
424			(void) pvn_vplist_dirty(PCTOV(pcp), (u_offset_t)length,
425			    pcfs_putapage, B_INVAL | B_TRUNC, CRED());
426		} else {
427			/*
428			 * pvn_vpzero() cannot deal with more than MAXBSIZE
429			 * chunks. Since the FAT clustersize can get larger
430			 * than that, we'll zero from the new length to the
431			 * end of the cluster for clustersizes smaller than
432			 * MAXBSIZE - or the end of the MAXBSIZE block in
433			 * case we've got a large clustersize.
434			 */
435			size_t nbytes =
436			    roundup(length, MIN(fsp->pcfs_clsize, MAXBSIZE)) -
437			    length;
438
439			pvn_vpzero(PCTOV(pcp), (u_offset_t)length, nbytes);
440			(void) pvn_vplist_dirty(PCTOV(pcp),
441			    (u_offset_t)length + nbytes,
442			    pcfs_putapage, B_INVAL | B_TRUNC, CRED());
443		}
444		error = pc_bfree(pcp, (pc_cluster32_t)
445		    howmany((offset_t)length, fsp->pcfs_clsize));
446		pcp->pc_size = length;
447	}
448
449	/*
450	 * This is the only place in PCFS code where pc_mark_mod() is called
451	 * without setting PC_MOD. May be a historical artifact ...
452	 */
453	pc_mark_mod(fsp, pcp);
454	return (error);
455}
456
457/*
458 * Get block for entry.
459 */
460static int
461pc_getentryblock(struct pcnode *pcp, struct buf **bpp)
462{
463	struct pcfs *fsp;
464
465	fsp = VFSTOPCFS(PCTOV(pcp)->v_vfsp);
466	if (pcp->pc_eblkno >= fsp->pcfs_datastart ||
467	    (pcp->pc_eblkno - fsp->pcfs_rdirstart) <
468	    (fsp->pcfs_rdirsec & ~(fsp->pcfs_spcl - 1))) {
469		*bpp = bread(fsp->pcfs_xdev,
470		    pc_dbdaddr(fsp, pcp->pc_eblkno), fsp->pcfs_clsize);
471	} else {
472		*bpp = bread(fsp->pcfs_xdev,
473		    pc_dbdaddr(fsp, pcp->pc_eblkno),
474		    (int)(fsp->pcfs_datastart - pcp->pc_eblkno) *
475		    fsp->pcfs_secsize);
476	}
477	if ((*bpp)->b_flags & B_ERROR) {
478		brelse(*bpp);
479		pc_mark_irrecov(fsp);
480		return (EIO);
481	}
482	return (0);
483}
484
485/*
486 * Sync all data associated with a file.
487 * Flush all the blocks in the buffer cache out to disk, sync the FAT and
488 * update the directory entry.
489 */
490int
491pc_nodesync(struct pcnode *pcp)
492{
493	struct pcfs *fsp;
494	int err;
495	struct vnode *vp;
496
497	vp = PCTOV(pcp);
498	fsp = VFSTOPCFS(vp->v_vfsp);
499	err = 0;
500	if (pcp->pc_flags & PC_MOD) {
501		/*
502		 * Flush all data blocks from buffer cache and
503		 * update the FAT which points to the data.
504		 */
505		if (err = syncpcp(pcp, 0)) { /* %% ?? how to handle error? */
506			if (err == ENOMEM)
507				return (err);
508			else {
509				pc_mark_irrecov(fsp);
510				return (EIO);
511			}
512		}
513		pcp->pc_flags &= ~PC_MOD;
514	}
515	/*
516	 * update the directory entry
517	 */
518	if (pcp->pc_flags & PC_CHG)
519		(void) pc_nodeupdate(pcp);
520	return (err);
521}
522
523/*
524 * Update the node's directory entry.
525 */
526int
527pc_nodeupdate(struct pcnode *pcp)
528{
529	struct buf *bp;
530	int error;
531	struct vnode *vp;
532	struct pcfs *fsp;
533
534	vp = PCTOV(pcp);
535	fsp = VFSTOPCFS(vp->v_vfsp);
536	if (IS_FAT32(fsp) && (vp->v_flag & VROOT)) {
537		/* no node to update */
538		pcp->pc_flags &= ~(PC_CHG | PC_MOD | PC_ACC);
539		return (0);
540	}
541	if (vp->v_flag & VROOT) {
542		panic("pc_nodeupdate");
543	}
544	if (pcp->pc_flags & PC_INVAL)
545		return (0);
546	PC_DPRINTF3(7, "pc_nodeupdate pcp=0x%p, bn=%ld, off=%d\n", (void *)pcp,
547	    pcp->pc_eblkno, pcp->pc_eoffset);
548
549	if (error = pc_getentryblock(pcp, &bp)) {
550		return (error);
551	}
552	if (vp->v_type == VREG) {
553		if (pcp->pc_flags & PC_CHG)
554			pcp->pc_entry.pcd_attr |= PCA_ARCH;
555		pcp->pc_entry.pcd_size = htoli(pcp->pc_size);
556	}
557	pc_setstartcluster(fsp, &pcp->pc_entry, pcp->pc_scluster);
558	*((struct pcdir *)(bp->b_un.b_addr + pcp->pc_eoffset)) = pcp->pc_entry;
559	bwrite2(bp);
560	error = geterror(bp);
561	brelse(bp);
562	if (error) {
563		error = EIO;
564		pc_mark_irrecov(VFSTOPCFS(vp->v_vfsp));
565	}
566	pcp->pc_flags &= ~(PC_CHG | PC_MOD | PC_ACC);
567	return (error);
568}
569
570/*
571 * Verify that the disk in the drive is the same one that we
572 * got the pcnode from.
573 * MUST be called with node unlocked.
574 */
575int
576pc_verify(struct pcfs *fsp)
577{
578	int fdstatus = 0;
579	int error = 0;
580
581	if (!fsp || fsp->pcfs_flags & PCFS_IRRECOV)
582		return (EIO);
583
584	if (!(fsp->pcfs_flags & PCFS_NOCHK) && fsp->pcfs_fatp) {
585		/*
586		 * This "has it been removed" check should better be
587		 * modified for removeable media that are not floppies.
588		 * dkio-managed devices such as USB/firewire external
589		 * disks/memory sticks/floppies (gasp) do not understand
590		 * this ioctl.
591		 */
592		PC_DPRINTF1(4, "pc_verify fsp=0x%p\n", (void *)fsp);
593		error = cdev_ioctl(fsp->pcfs_vfs->vfs_dev,
594		    FDGETCHANGE, (intptr_t)&fdstatus, FNATIVE | FKIOCTL,
595		    NULL, NULL);
596
597		if (error) {
598			if (error == ENOTTY || error == ENXIO) {
599				/*
600				 * See comment above. This is a workaround
601				 * for removeable media that don't understand
602				 * floppy ioctls.
603				 */
604				error = 0;
605			} else {
606				PC_DPRINTF1(1,
607				    "pc_verify: FDGETCHANGE ioctl failed: %d\n",
608				    error);
609				pc_mark_irrecov(fsp);
610			}
611		} else if (fsp->pcfs_fatjustread) {
612			/*
613			 * Ignore the results of the ioctl if we just
614			 * read the FAT.  There is a good chance that
615			 * the disk changed bit will be on, because
616			 * we've just mounted and we don't want to
617			 * give a false positive that the sky is falling.
618			 */
619			fsp->pcfs_fatjustread = 0;
620		} else {
621			/*
622			 * Oddly enough we can't check just one flag here. The
623			 * x86 floppy driver sets a different flag
624			 * (FDGC_DETECTED) than the sparc driver does.
625			 * I think this MAY be a bug, and I filed 4165938
626			 * to get someone to look at the behavior
627			 * a bit more closely.  In the meantime, my testing and
628			 * code examination seem to indicate it is safe to
629			 * check for either bit being set.
630			 */
631			if (fdstatus & (FDGC_HISTORY | FDGC_DETECTED)) {
632				PC_DPRINTF0(1, "pc_verify: change detected\n");
633				pc_mark_irrecov(fsp);
634			}
635		}
636	}
637	if (error == 0 && fsp->pcfs_fatp == NULL) {
638		error = pc_getfat(fsp);
639	}
640
641	return (error);
642}
643
644/*
645 * The disk has changed, pulling the rug out from beneath us.
646 * Mark the FS as being in an irrecoverable state.
647 * In a short while we'll clean up.
648 */
649void
650pc_mark_irrecov(struct pcfs *fsp)
651{
652	if (!(fsp->pcfs_flags & PCFS_NOCHK)) {
653		if (pc_lockfs(fsp, 1, 0)) {
654			/*
655			 * Locking failed, which currently would
656			 * only happen if the FS were already
657			 * marked as hosed.  If another reason for
658			 * failure were to arise in the future, this
659			 * routine would have to change.
660			 */
661			return;
662		}
663
664		fsp->pcfs_flags |= PCFS_IRRECOV;
665		cmn_err(CE_WARN,
666		    "Disk was changed during an update or\n"
667		    "an irrecoverable error was encountered.\n"
668		    "File damage is possible.  To prevent further\n"
669		    "damage, this pcfs instance will now be frozen.\n"
670		    "Use umount(1M) to release the instance.\n");
671		(void) pc_unlockfs(fsp);
672	}
673}
674
675/*
676 * The disk has been changed!
677 */
678void
679pc_diskchanged(struct pcfs *fsp)
680{
681	struct pcnode	*pcp, *npcp = NULL;
682	struct pchead	*hp;
683	struct vnode	*vp;
684	extern vfs_t	EIO_vfs;
685	struct vfs	*vfsp;
686
687	/*
688	 * Eliminate all pcnodes (dir & file) associated with this fs.
689	 * If the node is internal, ie, no references outside of
690	 * pcfs itself, then release the associated vnode structure.
691	 * Invalidate the in core FAT.
692	 * Invalidate cached data blocks and blocks waiting for I/O.
693	 */
694	PC_DPRINTF1(1, "pc_diskchanged fsp=0x%p\n", (void *)fsp);
695
696	vfsp = PCFSTOVFS(fsp);
697
698	for (hp = pcdhead; hp < &pcdhead[NPCHASH]; hp++) {
699		for (pcp = hp->pch_forw;
700		    pcp != (struct pcnode *)hp; pcp = npcp) {
701			npcp = pcp -> pc_forw;
702			vp = PCTOV(pcp);
703			if ((vp->v_vfsp == vfsp) &&
704			    !(pcp->pc_flags & PC_RELEHOLD)) {
705				mutex_enter(&(vp)->v_lock);
706				if (vp->v_count > 0) {
707					mutex_exit(&(vp)->v_lock);
708					continue;
709				}
710				mutex_exit(&(vp)->v_lock);
711				VN_HOLD(vp);
712				remque(pcp);
713				vp->v_data = NULL;
714				vp->v_vfsp = &EIO_vfs;
715				vp->v_type = VBAD;
716				VN_RELE(vp);
717				if (!(pcp->pc_flags & PC_EXTERNAL)) {
718					(void) pvn_vplist_dirty(vp,
719					    (u_offset_t)0, pcfs_putapage,
720					    B_INVAL | B_TRUNC,
721					    (struct cred *)NULL);
722					vn_free(vp);
723				}
724				kmem_free(pcp, sizeof (struct pcnode));
725				fsp->pcfs_nrefs --;
726				VFS_RELE(vfsp);
727			}
728		}
729	}
730	for (hp = pcfhead; fsp->pcfs_frefs && hp < &pcfhead[NPCHASH]; hp++) {
731		for (pcp = hp->pch_forw; fsp->pcfs_frefs &&
732		    pcp != (struct pcnode *)hp; pcp = npcp) {
733			npcp = pcp -> pc_forw;
734			vp = PCTOV(pcp);
735			if ((vp->v_vfsp == vfsp) &&
736			    !(pcp->pc_flags & PC_RELEHOLD)) {
737				mutex_enter(&(vp)->v_lock);
738				if (vp->v_count > 0) {
739					mutex_exit(&(vp)->v_lock);
740					continue;
741				}
742				mutex_exit(&(vp)->v_lock);
743				VN_HOLD(vp);
744				remque(pcp);
745				vp->v_data = NULL;
746				vp->v_vfsp = &EIO_vfs;
747				vp->v_type = VBAD;
748				VN_RELE(vp);
749				if (!(pcp->pc_flags & PC_EXTERNAL)) {
750					(void) pvn_vplist_dirty(vp,
751					    (u_offset_t)0, pcfs_putapage,
752					    B_INVAL | B_TRUNC,
753					    (struct cred *)NULL);
754					vn_free(vp);
755				}
756				kmem_free(pcp, sizeof (struct pcnode));
757				fsp->pcfs_frefs--;
758				fsp->pcfs_nrefs--;
759				VFS_RELE(vfsp);
760			}
761		}
762	}
763#ifdef undef
764	if (fsp->pcfs_frefs) {
765		rw_exit(&pcnodes_lock);
766		panic("pc_diskchanged: frefs");
767	}
768	if (fsp->pcfs_nrefs) {
769		rw_exit(&pcnodes_lock);
770		panic("pc_diskchanged: nrefs");
771	}
772#endif
773	if (!(vfsp->vfs_flag & VFS_UNMOUNTED) &&
774	    fsp->pcfs_fatp != (uchar_t *)0) {
775		pc_invalfat(fsp);
776	} else {
777		binval(fsp->pcfs_xdev);
778	}
779}
780