xref: /illumos-gate/usr/src/uts/common/fs/pcfs/pc_node.c (revision dd72704bd9e794056c558153663c739e2012d721)
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  * Copyright (c) 2017 by Delphix. All rights reserved.
27  */
28 
29 #include <sys/param.h>
30 #include <sys/t_lock.h>
31 #include <sys/errno.h>
32 #include <sys/sysmacros.h>
33 #include <sys/buf.h>
34 #include <sys/systm.h>
35 #include <sys/vfs.h>
36 #include <sys/vnode.h>
37 #include <sys/kmem.h>
38 #include <sys/proc.h>
39 #include <sys/cred.h>
40 #include <sys/cmn_err.h>
41 #include <sys/debug.h>
42 #include <vm/pvn.h>
43 #include <sys/fs/pc_label.h>
44 #include <sys/fs/pc_fs.h>
45 #include <sys/fs/pc_dir.h>
46 #include <sys/fs/pc_node.h>
47 #include <sys/dirent.h>
48 #include <sys/fdio.h>
49 #include <sys/file.h>
50 #include <sys/conf.h>
51 
52 struct pchead pcfhead[NPCHASH];
53 struct pchead pcdhead[NPCHASH];
54 
55 extern krwlock_t pcnodes_lock;
56 
57 static int	pc_getentryblock(struct pcnode *, struct buf **);
58 static int	syncpcp(struct pcnode *, int);
59 
60 /*
61  * fake entry for root directory, since this does not have a parent
62  * pointing to it.
63  */
64 struct pcdir pcfs_rootdirentry = {
65 	"",
66 	"",
67 	PCA_DIR
68 };
69 
70 void
71 pc_init(void)
72 {
73 	struct pchead *hdp, *hfp;
74 	int i;
75 	for (i = 0; i < NPCHASH; i++) {
76 		hdp = &pcdhead[i];
77 		hfp = &pcfhead[i];
78 		hdp->pch_forw =  (struct pcnode *)hdp;
79 		hdp->pch_back =  (struct pcnode *)hdp;
80 		hfp->pch_forw =  (struct pcnode *)hfp;
81 		hfp->pch_back =  (struct pcnode *)hfp;
82 	}
83 }
84 
85 struct pcnode *
86 pc_getnode(
87 	struct pcfs *fsp,	/* filsystem for node */
88 	daddr_t blkno,		/* phys block no of dir entry */
89 	int offset,		/* offset of dir entry in block */
90 	struct pcdir *ep)	/* node dir entry */
91 {
92 	struct pcnode *pcp;
93 	struct pchead *hp;
94 	struct vnode *vp;
95 	pc_cluster32_t scluster;
96 
97 	ASSERT(fsp->pcfs_flags & PCFS_LOCKED);
98 	if (ep == (struct pcdir *)0) {
99 		ep = &pcfs_rootdirentry;
100 		scluster = 0;
101 	} else {
102 		scluster = pc_getstartcluster(fsp, ep);
103 	}
104 	/*
105 	 * First look for active nodes.
106 	 * File nodes are identified by the location (blkno, offset) of
107 	 * its directory entry.
108 	 * Directory nodes are identified by the starting cluster number
109 	 * for the entries.
110 	 */
111 	if (ep->pcd_attr & PCA_DIR) {
112 		hp = &pcdhead[PCDHASH(fsp, scluster)];
113 		rw_enter(&pcnodes_lock, RW_READER);
114 		for (pcp = hp->pch_forw;
115 		    pcp != (struct pcnode *)hp; pcp = pcp->pc_forw) {
116 			if ((fsp == VFSTOPCFS(PCTOV(pcp)->v_vfsp)) &&
117 			    (scluster == pcp->pc_scluster)) {
118 				VN_HOLD(PCTOV(pcp));
119 				rw_exit(&pcnodes_lock);
120 				return (pcp);
121 			}
122 		}
123 		rw_exit(&pcnodes_lock);
124 	} else {
125 		hp = &pcfhead[PCFHASH(fsp, blkno, offset)];
126 		rw_enter(&pcnodes_lock, RW_READER);
127 		for (pcp = hp->pch_forw;
128 		    pcp != (struct pcnode *)hp; pcp = pcp->pc_forw) {
129 			if ((fsp == VFSTOPCFS(PCTOV(pcp)->v_vfsp)) &&
130 			    ((pcp->pc_flags & PC_INVAL) == 0) &&
131 			    (blkno == pcp->pc_eblkno) &&
132 			    (offset == pcp->pc_eoffset)) {
133 				VN_HOLD(PCTOV(pcp));
134 				rw_exit(&pcnodes_lock);
135 				return (pcp);
136 			}
137 		}
138 		rw_exit(&pcnodes_lock);
139 	}
140 	/*
141 	 * Cannot find node in active list. Allocate memory for a new node
142 	 * initialize it, and put it on the active list.
143 	 */
144 	pcp = kmem_zalloc(sizeof (struct pcnode), KM_SLEEP);
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 
203 int
204 syncpcp(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 
216 void
217 pc_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 
232 retry:
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 	VN_RELE_LOCKED(vp);
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 */
305 void
306 pc_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  */
327 void
328 pc_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  */
360 int
361 pc_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  */
460 static int
461 pc_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  */
490 int
491 pc_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  */
526 int
527 pc_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  */
575 int
576 pc_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  */
649 void
650 pc_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(8) to release the instance.\n");
671 		(void) pc_unlockfs(fsp);
672 	}
673 }
674 
675 /*
676  * The disk has been changed!
677  */
678 void
679 pc_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