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