/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef NFSCLIENT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * The majority of PC media use a 512 sector size, but * occasionally you will run across a 1k sector size. * For media with a 1k sector size, fd_strategy() requires * the I/O size to be a 1k multiple; so when the sector size * is not yet known, always read 1k. */ #define PC_SAFESECSIZE (PC_SECSIZE * 2) static int pcfs_pseudo_floppy(dev_t); static int pcfsinit(int, char *); static int pcfs_mount(struct vfs *, struct vnode *, struct mounta *, struct cred *); static int pcfs_unmount(struct vfs *, int, struct cred *); static int pcfs_root(struct vfs *, struct vnode **); static int pcfs_statvfs(struct vfs *, struct statvfs64 *); static int pc_syncfsnodes(struct pcfs *); static int pcfs_sync(struct vfs *, short, struct cred *); static int pcfs_vget(struct vfs *vfsp, struct vnode **vpp, struct fid *fidp); static int pc_getfattype(struct vnode *, int, daddr_t *, int *); static int pc_readfat(struct pcfs *fsp, uchar_t *fatp, daddr_t start, size_t fatsize); static int pc_writefat(struct pcfs *fsp, daddr_t start); /* * pcfs mount options table */ static char *nohidden_cancel[] = {MNTOPT_PCFS_HIDDEN, NULL}; static char *hidden_cancel[] = {MNTOPT_PCFS_NOHIDDEN, NULL}; static char *nofoldcase_cancel[] = {MNTOPT_PCFS_FOLDCASE, NULL}; static char *foldcase_cancel[] = {MNTOPT_PCFS_NOFOLDCASE, NULL}; static mntopt_t mntopts[] = { /* * option name cancel option default arg flags * opt data */ { MNTOPT_PCFS_NOHIDDEN, nohidden_cancel, NULL, MO_DEFAULT, NULL }, { MNTOPT_PCFS_HIDDEN, hidden_cancel, NULL, 0, NULL }, { MNTOPT_PCFS_NOFOLDCASE, nofoldcase_cancel, NULL, MO_DEFAULT, NULL }, { MNTOPT_PCFS_FOLDCASE, foldcase_cancel, NULL, 0, NULL } }; static mntopts_t pcfs_mntopts = { sizeof (mntopts) / sizeof (mntopt_t), mntopts }; int pcfsdebuglevel = 0; /* * pcfslock: protects the list of mounted pc filesystems "pc_mounttab. * pcfs_lock: (inside per filesystem structure "pcfs") * per filesystem lock. Most of the vfsops and vnodeops are * protected by this lock. * pcnodes_lock: protects the pcnode hash table "pcdhead", "pcfhead". * * Lock hierarchy: pcfslock > pcfs_lock > pcnodes_lock */ kmutex_t pcfslock; krwlock_t pcnodes_lock; /* protect the pcnode hash table "pcdhead", "pcfhead" */ static int pcfstype; static vfsdef_t vfw = { VFSDEF_VERSION, "pcfs", pcfsinit, VSW_HASPROTO|VSW_CANREMOUNT, &pcfs_mntopts }; extern struct mod_ops mod_fsops; static struct modlfs modlfs = { &mod_fsops, "PC filesystem v%I%", &vfw }; static struct modlinkage modlinkage = { MODREV_1, &modlfs, NULL }; int _init(void) { int error; #if !defined(lint) /* make sure the on-disk structures are sane */ ASSERT(sizeof (struct pcdir) == 32); ASSERT(sizeof (struct pcdir_lfn) == 32); #endif mutex_init(&pcfslock, NULL, MUTEX_DEFAULT, NULL); rw_init(&pcnodes_lock, NULL, RW_DEFAULT, NULL); error = mod_install(&modlinkage); if (error) { mutex_destroy(&pcfslock); rw_destroy(&pcnodes_lock); } return (error); } int _fini(void) { int error; error = mod_remove(&modlinkage); if (error) return (error); mutex_destroy(&pcfslock); rw_destroy(&pcnodes_lock); /* * Tear down the operations vectors */ (void) vfs_freevfsops_by_type(pcfstype); vn_freevnodeops(pcfs_fvnodeops); vn_freevnodeops(pcfs_dvnodeops); return (0); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } /* ARGSUSED1 */ static int pcfsinit(int fstype, char *name) { static const fs_operation_def_t pcfs_vfsops_template[] = { VFSNAME_MOUNT, pcfs_mount, VFSNAME_UNMOUNT, pcfs_unmount, VFSNAME_ROOT, pcfs_root, VFSNAME_STATVFS, pcfs_statvfs, VFSNAME_SYNC, (fs_generic_func_p) pcfs_sync, VFSNAME_VGET, pcfs_vget, NULL, NULL }; int error; error = vfs_setfsops(fstype, pcfs_vfsops_template, NULL); if (error != 0) { cmn_err(CE_WARN, "pcfsinit: bad vfs ops template"); return (error); } error = vn_make_ops("pcfs", pcfs_fvnodeops_template, &pcfs_fvnodeops); if (error != 0) { (void) vfs_freevfsops_by_type(fstype); cmn_err(CE_WARN, "pcfsinit: bad file vnode ops template"); return (error); } error = vn_make_ops("pcfsd", pcfs_dvnodeops_template, &pcfs_dvnodeops); if (error != 0) { (void) vfs_freevfsops_by_type(fstype); vn_freevnodeops(pcfs_fvnodeops); cmn_err(CE_WARN, "pcfsinit: bad dir vnode ops template"); return (error); } pcfstype = fstype; (void) pc_init(); return (0); } static struct pcfs *pc_mounttab = NULL; extern struct pcfs_args pc_tz; /* * Define some special logical drives we use internal to this file. */ #define BOOT_PARTITION_DRIVE 99 #define PRIMARY_DOS_DRIVE 1 /* * pc_mount system call */ static int pcfs_mount( struct vfs *vfsp, struct vnode *mvp, struct mounta *uap, struct cred *cr) { struct pcfs *fsp; struct vnode *bvp; struct vnode *devvp; struct pathname special; daddr_t dosstart; dev_t pseudodev; dev_t xdev; char *spnp; char *data = uap->dataptr; int datalen = uap->datalen; int dos_ldrive = 0; int error; int fattype; int spnlen; int wantbootpart = 0; struct vioc_info info; int rval; /* set but not used */ minor_t minor; int oflag, aflag; if ((error = secpolicy_fs_mount(cr, mvp, vfsp)) != 0) return (error); PC_DPRINTF0(4, "pcfs_mount\n"); if (mvp->v_type != VDIR) { return (ENOTDIR); } mutex_enter(&mvp->v_lock); if ((uap->flags & MS_REMOUNT) == 0 && (uap->flags & MS_OVERLAY) == 0 && (mvp->v_count != 1 || (mvp->v_flag & VROOT))) { mutex_exit(&mvp->v_lock); return (EBUSY); } mutex_exit(&mvp->v_lock); /* * The caller is responsible for making sure to always * pass in sizeof(struct pcfs_args) (or the old one). * Doing this is the only way to know an EINVAL return * from mount(2) is due to the "not a DOS filesystem" * EINVAL that pc_verify/pc_getfattype could return. */ if ((datalen != sizeof (struct pcfs_args)) && (datalen != sizeof (struct old_pcfs_args))) { return (EINVAL); } else { struct pcfs_args tmp_tz; int hidden = 0; int foldcase = 0; tmp_tz.flags = 0; if (copyin(data, &tmp_tz, datalen)) { return (EFAULT); } if (datalen == sizeof (struct pcfs_args)) { hidden = tmp_tz.flags & PCFS_MNT_HIDDEN; foldcase = tmp_tz.flags & PCFS_MNT_FOLDCASE; } if (hidden) vfs_setmntopt(vfsp, MNTOPT_PCFS_HIDDEN, NULL, 0); if (foldcase) vfs_setmntopt(vfsp, MNTOPT_PCFS_FOLDCASE, NULL, 0); /* * more than one pc filesystem can be mounted on x86 * so the pc_tz structure is now a critical region */ mutex_enter(&pcfslock); if (pc_mounttab == NULL) bcopy(&tmp_tz, &pc_tz, sizeof (struct pcfs_args)); mutex_exit(&pcfslock); } /* * Resolve path name of special file being mounted. */ if (error = pn_get(uap->spec, UIO_USERSPACE, &special)) { return (error); } if (error = lookupname(special.pn_path, UIO_SYSSPACE, FOLLOW, NULLVPP, &bvp)) { /* * look for suffix to special * which indicates a request to mount the solaris boot * partition, or a DOS logical drive on the hard disk */ spnlen = special.pn_pathlen; if (spnlen > 5) { spnp = special.pn_path + spnlen - 5; if (*spnp++ == ':' && *spnp++ == 'b' && *spnp++ == 'o' && *spnp++ == 'o' && *spnp++ == 't') { /* * Looks as if they want to mount * the Solaris boot partition */ wantbootpart = 1; dos_ldrive = BOOT_PARTITION_DRIVE; spnp = special.pn_path + spnlen - 5; *spnp = '\0'; error = lookupname(special.pn_path, UIO_SYSSPACE, FOLLOW, NULLVPP, &bvp); } } if (!wantbootpart) { spnp = special.pn_path + spnlen - 1; if (spnlen > 2 && *spnp >= 'c' && *spnp <= 'z') { spnlen--; dos_ldrive = *spnp-- - 'c' + 1; } else if (spnlen > 2 && *spnp >= '0' && *spnp <= '9') { spnlen--; dos_ldrive = *spnp-- - '0'; if (spnlen > 2 && *spnp >= '0' && *spnp <= '9') { spnlen--; dos_ldrive += 10 * (*spnp-- - '0'); } } if (spnlen > 1 && dos_ldrive && dos_ldrive <= 24 && *spnp == ':') { /* * remove suffix so that we have a real * device name */ *spnp = '\0'; error = lookupname(special.pn_path, UIO_SYSSPACE, FOLLOW, NULLVPP, &bvp); } } if (error) { pn_free(&special); return (error); } } pn_free(&special); if (bvp->v_type != VBLK) { VN_RELE(bvp); return (ENOTBLK); } xdev = bvp->v_rdev; /* * Verify caller's permission to open the device special file. */ if ((vfsp->vfs_flag & VFS_RDONLY) != 0 || ((uap->flags & MS_RDONLY) != 0)) { oflag = FREAD; aflag = VREAD; } else { oflag = FREAD | FWRITE; aflag = VREAD | VWRITE; } if ((error = VOP_ACCESS(bvp, aflag, 0, cr)) != 0 || (error = secpolicy_spec_open(cr, bvp, oflag)) != 0) { VN_RELE(bvp); return (error); } VN_RELE(bvp); if (getmajor(xdev) >= devcnt) { return (ENXIO); } /* * Ensure that this device (or logical drive) isn't already mounted, * unless this is a REMOUNT request */ if (dos_ldrive) { mutex_enter(&pcfslock); for (fsp = pc_mounttab; fsp; fsp = fsp->pcfs_nxt) if (fsp->pcfs_xdev == xdev && fsp->pcfs_ldrv == dos_ldrive) { mutex_exit(&pcfslock); if (uap->flags & MS_REMOUNT) { return (0); } else { return (EBUSY); } } /* * Assign a unique device number for the vfs * The old way (getudev() + a constantly incrementing * major number) was wrong because it changes vfs_dev * across mounts and reboots, which breaks nfs file handles. * UFS just uses the real dev_t. We can't do that because * of the way pcfs opens fdisk partitons (the :c and :d * partitions are on the same dev_t). Though that _might_ * actually be ok, since the file handle contains an * absolute block number, it's probably better to make them * different. So I think we should retain the original * dev_t, but come up with a different minor number based * on the logical drive that will _always_ come up the same. * For now, we steal the upper 6 bits. */ #ifdef notdef /* what should we do here? */ if (((getminor(xdev) >> 12) & 0x3F) != 0) printf("whoops - upper bits used!\n"); #endif minor = ((dos_ldrive << 12) | getminor(xdev)) & MAXMIN32; pseudodev = makedevice(getmajor(xdev), minor); if (vfs_devmounting(pseudodev, vfsp)) { mutex_exit(&pcfslock); return (EBUSY); } if (vfs_devismounted(pseudodev)) { mutex_exit(&pcfslock); if (uap->flags & MS_REMOUNT) { return (0); } else { return (EBUSY); } } mutex_exit(&pcfslock); } else { if (vfs_devmounting(xdev, vfsp)) { return (EBUSY); } if (vfs_devismounted(xdev)) if (uap->flags & MS_REMOUNT) { return (0); } else { return (EBUSY); } pseudodev = xdev; } if (uap->flags & MS_RDONLY) { vfsp->vfs_flag |= VFS_RDONLY; vfs_setmntopt(vfsp, MNTOPT_RO, NULL, 0); } /* * Mount the filesystem */ devvp = makespecvp(xdev, VBLK); if (IS_SWAPVP(devvp)) { VN_RELE(devvp); return (EBUSY); } /* * special handling for PCMCIA memory card * with pseudo floppies organization */ if (dos_ldrive == 0 && pcfs_pseudo_floppy(xdev)) { dosstart = (daddr_t)0; fattype = PCFS_PCMCIA_NO_CIS; } else { if (error = pc_getfattype(devvp, dos_ldrive, &dosstart, &fattype)) { VN_RELE(devvp); return (error); } } (void) VOP_PUTPAGE(devvp, (offset_t)0, (uint_t)0, B_INVAL, cr); fsp = kmem_zalloc((uint_t)sizeof (struct pcfs), KM_SLEEP); fsp->pcfs_vfs = vfsp; fsp->pcfs_flags = fattype; fsp->pcfs_devvp = devvp; fsp->pcfs_xdev = xdev; fsp->pcfs_ldrv = dos_ldrive; fsp->pcfs_dosstart = dosstart; mutex_init(&fsp->pcfs_lock, NULL, MUTEX_DEFAULT, NULL); /* set the "nocheck" flag if volmgt is managing this volume */ info.vii_pathlen = 0; info.vii_devpath = 0; error = cdev_ioctl(fsp->pcfs_xdev, VOLIOCINFO, (intptr_t)&info, FKIOCTL|FREAD, kcred, &rval); if (error == 0) { fsp->pcfs_flags |= PCFS_NOCHK; } if (vfs_optionisset(vfsp, MNTOPT_PCFS_HIDDEN, NULL)) fsp->pcfs_flags |= PCFS_HIDDEN; if (vfs_optionisset(vfsp, MNTOPT_PCFS_FOLDCASE, NULL)) fsp->pcfs_flags |= PCFS_FOLDCASE; vfsp->vfs_dev = pseudodev; vfsp->vfs_fstype = pcfstype; vfs_make_fsid(&vfsp->vfs_fsid, pseudodev, pcfstype); vfsp->vfs_data = (caddr_t)fsp; vfsp->vfs_bcount = 0; error = pc_verify(fsp); if (error) { VN_RELE(devvp); mutex_destroy(&fsp->pcfs_lock); kmem_free(fsp, (uint_t)sizeof (struct pcfs)); return (error); } vfsp->vfs_bsize = fsp->pcfs_clsize; mutex_enter(&pcfslock); fsp->pcfs_nxt = pc_mounttab; pc_mounttab = fsp; mutex_exit(&pcfslock); return (0); } /* * vfs operations */ /* ARGSUSED */ static int pcfs_unmount( struct vfs *vfsp, int flag, struct cred *cr) { struct pcfs *fsp, *fsp1; if (secpolicy_fs_unmount(cr, vfsp) != 0) return (EPERM); /* * forced unmount is not supported by this file system * and thus, ENOTSUP, is being returned. */ if (flag & MS_FORCE) return (ENOTSUP); PC_DPRINTF0(4, "pcfs_unmount\n"); fsp = VFSTOPCFS(vfsp); /* * We don't have to lock fsp because the VVFSLOCK in vfs layer will * prevent lookuppn from crossing the mount point. */ if (fsp->pcfs_nrefs) { return (EBUSY); } /* * Allow an unmount (regardless of state) if the fs instance has * been marked as beyond recovery. */ if (fsp->pcfs_flags & PCFS_IRRECOV) { mutex_enter(&pcfslock); rw_enter(&pcnodes_lock, RW_WRITER); pc_diskchanged(fsp); rw_exit(&pcnodes_lock); mutex_exit(&pcfslock); } /* now there should be no pcp node on pcfhead or pcdhead. */ mutex_enter(&pcfslock); if (fsp == pc_mounttab) { pc_mounttab = fsp->pcfs_nxt; } else { for (fsp1 = pc_mounttab; fsp1 != NULL; fsp1 = fsp1->pcfs_nxt) if (fsp1->pcfs_nxt == fsp) fsp1->pcfs_nxt = fsp->pcfs_nxt; } if (fsp->pcfs_fatp != (uchar_t *)0) { pc_invalfat(fsp); } mutex_exit(&pcfslock); VN_RELE(fsp->pcfs_devvp); mutex_destroy(&fsp->pcfs_lock); kmem_free(fsp, (uint_t)sizeof (struct pcfs)); return (0); } /* * find root of pcfs */ static int pcfs_root( struct vfs *vfsp, struct vnode **vpp) { struct pcfs *fsp; struct pcnode *pcp; int error; fsp = VFSTOPCFS(vfsp); if (error = pc_lockfs(fsp, 0, 0)) return (error); pcp = pc_getnode(fsp, (daddr_t)0, 0, (struct pcdir *)0); PC_DPRINTF2(9, "pcfs_root(0x%p) pcp= 0x%p\n", (void *)vfsp, (void *)pcp); pc_unlockfs(fsp); *vpp = PCTOV(pcp); pcp->pc_flags |= PC_EXTERNAL; return (0); } /* * Get file system statistics. */ static int pcfs_statvfs( struct vfs *vfsp, struct statvfs64 *sp) { struct pcfs *fsp; int error; dev32_t d32; fsp = VFSTOPCFS(vfsp); error = pc_getfat(fsp); if (error) return (error); bzero(sp, sizeof (*sp)); sp->f_bsize = sp->f_frsize = fsp->pcfs_clsize; sp->f_blocks = (fsblkcnt64_t)fsp->pcfs_ncluster; sp->f_bavail = sp->f_bfree = (fsblkcnt64_t)pc_freeclusters(fsp); sp->f_files = (fsfilcnt64_t)-1; sp->f_ffree = (fsfilcnt64_t)-1; sp->f_favail = (fsfilcnt64_t)-1; #ifdef notdef (void) cmpldev(&d32, fsp->pcfs_devvp->v_rdev); #endif /* notdef */ (void) cmpldev(&d32, vfsp->vfs_dev); sp->f_fsid = d32; (void) strcpy(sp->f_basetype, vfssw[vfsp->vfs_fstype].vsw_name); sp->f_flag = vf_to_stf(vfsp->vfs_flag); sp->f_namemax = PCFNAMESIZE; return (0); } static int pc_syncfsnodes(struct pcfs *fsp) { struct pchead *hp; struct pcnode *pcp; int error; PC_DPRINTF0(7, "pcfs_syncfsnodes\n"); if (error = pc_lockfs(fsp, 0, 0)) return (error); if (!(error = pc_syncfat(fsp))) { hp = pcfhead; while (hp < & pcfhead [ NPCHASH ]) { rw_enter(&pcnodes_lock, RW_READER); pcp = hp->pch_forw; while (pcp != (struct pcnode *)hp) { if (VFSTOPCFS(PCTOV(pcp) -> v_vfsp) == fsp) if (error = pc_nodesync(pcp)) break; pcp = pcp -> pc_forw; } rw_exit(&pcnodes_lock); if (error) break; hp++; } } pc_unlockfs(fsp); return (error); } /* * Flush any pending I/O. */ /*ARGSUSED*/ static int pcfs_sync( struct vfs *vfsp, short flag, struct cred *cr) { struct pcfs *fsp; int error = 0; /* this prevents the filesystem from being umounted. */ mutex_enter(&pcfslock); if (vfsp != NULL) { fsp = VFSTOPCFS(vfsp); if (!(fsp->pcfs_flags & PCFS_IRRECOV)) { error = pc_syncfsnodes(fsp); } else { rw_enter(&pcnodes_lock, RW_WRITER); pc_diskchanged(fsp); rw_exit(&pcnodes_lock); error = EIO; } } else { fsp = pc_mounttab; while (fsp != NULL) { if (fsp->pcfs_flags & PCFS_IRRECOV) { rw_enter(&pcnodes_lock, RW_WRITER); pc_diskchanged(fsp); rw_exit(&pcnodes_lock); error = EIO; break; } error = pc_syncfsnodes(fsp); if (error) break; fsp = fsp->pcfs_nxt; } } mutex_exit(&pcfslock); return (error); } int pc_lockfs(struct pcfs *fsp, int diskchanged, int releasing) { if ((fsp->pcfs_flags & PCFS_IRRECOV) && !releasing) return (EIO); if ((fsp->pcfs_flags & PCFS_LOCKED) && (fsp->pcfs_owner == curthread)) { fsp->pcfs_count++; } else { mutex_enter(&fsp->pcfs_lock); if (fsp->pcfs_flags & PCFS_LOCKED) panic("pc_lockfs"); /* * We check the IRRECOV bit again just in case somebody * snuck past the initial check but then got held up before * they could grab the lock. (And in the meantime someone * had grabbed the lock and set the bit) */ if (!diskchanged && !(fsp->pcfs_flags & PCFS_IRRECOV)) (void) pc_getfat(fsp); fsp->pcfs_flags |= PCFS_LOCKED; fsp->pcfs_owner = curthread; fsp->pcfs_count++; } return (0); } void pc_unlockfs(struct pcfs *fsp) { if ((fsp->pcfs_flags & PCFS_LOCKED) == 0) panic("pc_unlockfs"); if (--fsp->pcfs_count < 0) panic("pc_unlockfs: count"); if (fsp->pcfs_count == 0) { fsp->pcfs_flags &= ~PCFS_LOCKED; fsp->pcfs_owner = 0; mutex_exit(&fsp->pcfs_lock); } } /* * isDosDrive() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes a DOS drive. We * use systid values defined in sys/dktp/fdisk.h. */ static int isDosDrive(uchar_t checkMe) { return ((checkMe == DOSOS12) || (checkMe == DOSOS16) || (checkMe == DOSHUGE) || (checkMe == FDISK_WINDOWS) || (checkMe == FDISK_EXT_WIN) || (checkMe == FDISK_FAT95) || (checkMe == DIAGPART)); } /* * isDosExtended() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes an extended DOS * partition. */ static int isDosExtended(uchar_t checkMe) { return ((checkMe == EXTDOS) || (checkMe == FDISK_EXTLBA)); } /* * isBootPart() * Boolean function. Give it the systid field for an fdisk partition * and it decides if that's a systid that describes a Solaris boot * partition. */ static int isBootPart(uchar_t checkMe) { return (checkMe == X86BOOT); } /* * noLogicalDrive() * Display error message about not being able to find a logical * drive. */ static void noLogicalDrive(int requested) { if (requested == BOOT_PARTITION_DRIVE) { cmn_err(CE_NOTE, "!pcfs: no boot partition"); } else { cmn_err(CE_NOTE, "!pcfs: no such logical drive"); } } /* * findTheDrive() * Discover offset of the requested logical drive, and return * that offset (startSector), the systid of that drive (sysid), * and a buffer pointer (bp), with the buffer contents being * the first sector of the logical drive (i.e., the sector that * contains the BPB for that drive). */ static int findTheDrive(dev_t dev, int askedFor, int *error, buf_t **bp, daddr_t *startSector, uchar_t *sysid) { struct ipart dosp[FD_NUMPART]; /* incore fdisk partition structure */ struct mboot *dosp_ptr; /* boot structure pointer */ daddr_t lastseek = 0; /* Disk block we sought previously */ daddr_t diskblk = 0; /* Disk block to get */ daddr_t xstartsect; /* base of Extended DOS partition */ int logicalDriveCount = 0; /* Count of logical drives seen */ int extendedPart = -1; /* index of extended dos partition */ int primaryPart = -1; /* index of primary dos partition */ int bootPart = -1; /* index of a Solaris boot partition */ int xnumsect = -1; /* length of extended DOS partition */ int driveIndex; /* computed FDISK table index */ int i; /* * Count of drives in the current extended partition's * FDISK table, and indexes of the drives themselves. */ int extndDrives[FD_NUMPART]; int numDrives = 0; /* * Count of drives (beyond primary) in master boot record's * FDISK table, and indexes of the drives themselves. */ int extraDrives[FD_NUMPART]; int numExtraDrives = 0; /* * Copy from disk block into memory aligned structure for fdisk usage. */ dosp_ptr = (struct mboot *)(*bp)->b_un.b_addr; bcopy(dosp_ptr->parts, dosp, sizeof (struct ipart) * FD_NUMPART); /* * Get a summary of what is in the Master FDISK table. * Normally we expect to find one partition marked as a DOS drive. * This partition is the one Windows calls the primary dos partition. * If the machine has any logical drives then we also expect * to find a partition marked as an extended DOS partition. * * Sometimes we'll find multiple partitions marked as DOS drives. * The Solaris fdisk program allows these partitions * to be created, but Windows fdisk no longer does. We still need * to support these, though, since Windows does. We also need to fix * our fdisk to behave like the Windows version. * * It turns out that some off-the-shelf media have *only* an * Extended partition, so we need to deal with that case as well. * * Only a single (the first) Extended or Boot Partition will * be recognized. Any others will be ignored. */ for (i = 0; i < FD_NUMPART; i++) { if (isDosDrive(dosp[i].systid)) { if (primaryPart < 0) { logicalDriveCount++; primaryPart = i; } else { extraDrives[numExtraDrives++] = i; } continue; } if ((extendedPart < 0) && isDosExtended(dosp[i].systid)) { extendedPart = i; continue; } if ((bootPart < 0) && isBootPart(dosp[i].systid)) { bootPart = i; continue; } } if (askedFor == BOOT_PARTITION_DRIVE) { if (bootPart < 0) { noLogicalDrive(askedFor); *error = EINVAL; return (0); } *sysid = dosp[bootPart].systid; *startSector = ltohi(dosp[bootPart].relsect); return (1); } if (askedFor == PRIMARY_DOS_DRIVE && primaryPart >= 0) { *sysid = dosp[primaryPart].systid; *startSector = ltohi(dosp[primaryPart].relsect); return (1); } /* * We are not looking for the C: drive (or the primary drive * was not found), so we had better have an extended partition * or extra drives in the Master FDISK table. */ if ((extendedPart < 0) && (numExtraDrives == 0)) { cmn_err(CE_NOTE, "!pcfs: no extended dos partition"); noLogicalDrive(askedFor); *error = EINVAL; return (0); } if (extendedPart >= 0) { diskblk = xstartsect = ltohi(dosp[extendedPart].relsect); xnumsect = ltohi(dosp[extendedPart].numsect); do { /* * If the seek would not cause us to change * position on the drive, then we're out of * extended partitions to examine. */ if (diskblk == lastseek) break; logicalDriveCount += numDrives; /* * Seek the next extended partition, and find * logical drives within it. */ brelse(*bp); *bp = bread(dev, diskblk, PC_SAFESECSIZE); if ((*bp)->b_flags & B_ERROR) { PC_DPRINTF0(1, "pc_getfattype: read error\n"); *error = EIO; return (0); } lastseek = diskblk; dosp_ptr = (struct mboot *)(*bp)->b_un.b_addr; if (ltohs(dosp_ptr->signature) != MBB_MAGIC) { cmn_err(CE_NOTE, "!pcfs: " "extended partition signature err"); *error = EINVAL; return (0); } bcopy(dosp_ptr->parts, dosp, sizeof (struct ipart) * FD_NUMPART); /* * Count up drives, and track where the next * extended partition is in case we need it. We * are expecting only one extended partition. If * there is more than one we'll only go to the * first one we see, but warn about ignoring. */ numDrives = 0; for (i = 0; i < FD_NUMPART; i++) { if (isDosDrive(dosp[i].systid)) { extndDrives[numDrives++] = i; continue; } else if (isDosExtended(dosp[i].systid)) { if (diskblk != lastseek) { /* * Already found an extended * partition in this table. */ cmn_err(CE_NOTE, "!pcfs: ignoring unexpected" " additional extended" " partition"); continue; } diskblk = xstartsect + ltohi(dosp[i].relsect); continue; } } } while (askedFor > logicalDriveCount + numDrives); if (askedFor <= logicalDriveCount + numDrives) { /* * The number of logical drives we've found thus * far is enough to get us to the one we were * searching for. */ driveIndex = logicalDriveCount + numDrives - askedFor; *sysid = dosp[extndDrives[driveIndex]].systid; *startSector = ltohi(dosp[extndDrives[driveIndex]].relsect) + lastseek; if (*startSector > (xstartsect + xnumsect)) { cmn_err(CE_NOTE, "!pcfs: extended partition " "values bad"); *error = EINVAL; return (0); } return (1); } else { /* * We ran out of extended dos partition * drives. The only hope now is to go * back to extra drives defined in the master * fdisk table. But we overwrote that table * already, so we must load it in again. */ logicalDriveCount += numDrives; brelse(*bp); *bp = bread(dev, (daddr_t)0, PC_SAFESECSIZE); if ((*bp)->b_flags & B_ERROR) { PC_DPRINTF0(1, "pc_getfattype: read error\n"); *error = EIO; return (0); } dosp_ptr = (struct mboot *)(*bp)->b_un.b_addr; bcopy(dosp_ptr->parts, dosp, sizeof (struct ipart) * FD_NUMPART); } } /* * Still haven't found the drive, is it an extra * drive defined in the main FDISK table? */ if (askedFor <= logicalDriveCount + numExtraDrives) { driveIndex = logicalDriveCount + numExtraDrives - askedFor; *sysid = dosp[extraDrives[driveIndex]].systid; *startSector = ltohi(dosp[extraDrives[driveIndex]].relsect); return (1); } /* * Still haven't found the drive, and there is * nowhere else to look. */ noLogicalDrive(askedFor); *error = EINVAL; return (0); } /* * Get the FAT type for the DOS medium. * * We look at sector 0 and determine if we are looking at an FDISK table * or a BIOS Parameter Block (BPB). Most of the time, if its a floppy * we'll be looking at a BPB and if we're looking at a hard drive we'll be * examining an FDISK table. Those fun exceptions do happen, though. * * If we are looking at a BPB, we can calculate and verify the FAT size. * If we are looking at an FDISK partition table, we scan the partition * table for the requested logical volume. */ static int pc_getfattype( struct vnode *devvp, int ldrive, daddr_t *strtsectp, int *fattypep) { struct mboot *dosp_ptr; /* boot structure pointer */ struct bootsec *bootp, *bpbp; /* for detailed sector examination */ uchar_t *cp; /* for searching out FAT string */ uint_t overhead; /* sectors not part of file area */ uint_t numclusters; /* number of clusters in file area */ uint_t bytesoffat; /* computed number of bytes in a FAT */ int secsize; /* Sector size in bytes/sec */ buf_t *bp = NULL; /* Disk buffer pointer */ int rval = 0; uchar_t sysid = 0; /* System ID character */ dev_t dev = devvp->v_rdev; *strtsectp = (daddr_t)0; /* * Open the device so we can check out the BPB or FDISK table, * then read in the sector. */ PC_DPRINTF2(5, "pc_getfattype: dev=%x ldrive=%x ", (int)dev, ldrive); if (rval = VOP_OPEN(&devvp, FREAD, CRED())) { PC_DPRINTF1(1, "pc_getfattype: open error=%d\n", rval); return (rval); } /* * Read block 0 from device */ bp = bread(dev, (daddr_t)0, PC_SAFESECSIZE); if (bp->b_flags & B_ERROR) { PC_DPRINTF0(1, "pc_getfattype: read error\n"); rval = EIO; goto out; } /* * If this is a logical volume with a FAT file system, * then we expect to find a "file system boot sector" * (also called a BIOS Perameter Block -- or BPB) in * the first physical sector. */ /* * The word "FAT" is encoded into the BPB beginning at * PCFS_TYPESTRING_OFFSET16 in 12 and 16 bit FATs. It is * encoded at FS_TYPESTRING_OFFSET32 in 32 bit FATs. */ cp = (uchar_t *)bp->b_un.b_addr; if (*(cp + PCFS_TYPESTRING_OFFSET16) == 'F' && *(cp + PCFS_TYPESTRING_OFFSET16 + 1) == 'A' && *(cp + PCFS_TYPESTRING_OFFSET16 + 2) == 'T') { PC_DPRINTF0(5, "Found the FAT string at 12/16 location\n"); /* * Compute a bits/fat value */ bpbp = (struct bootsec *)bp->b_un.b_addr; secsize = (int)ltohs(bpbp->bps[0]); /* * Check for bogus sector size - * fat should be at least 1 sector * If anything looks weird, we have to bail and try looking * for an FDISK table instead. */ if (secsize < 512 || (int)ltohs(bpbp->fatsec) < 1 || bpbp->nfat < 1 || bpbp->spcl < 1) { PC_DPRINTF4(5, "One or more BPB fields bad\n" "bytes/sec = %d, sec/fat = %d, numfats = %d, " "sec/clust = %d\n", secsize, (int)ltohs(bpbp->fatsec), bpbp->nfat, bpbp->spcl); goto lookforfdisk; } overhead = bpbp->nfat * ltohs(bpbp->fatsec); overhead += ltohs(bpbp->res_sec[0]); overhead += (ltohs(bpbp->rdirents[0]) * sizeof (struct pcdir)) / secsize; numclusters = ((ltohs(bpbp->numsect[0]) ? ltohs(bpbp->numsect[0]) : ltohi(bpbp->totalsec)) - overhead) / bpbp->spcl; /* * If the number of clusters looks bad, go look for an * FDISK table. */ if (numclusters < 1) { PC_DPRINTF1(5, "num clusters is bad ( = %d )\n", numclusters); goto lookforfdisk; } /* * The number of bytes of FAT determines the maximum number * of entries of a given size that FAT can contain. * The FAT can only contain (bytes of FAT)*8/12 12-bit entries * and (bytes of FAT)*8/16 16-bit entries. */ bytesoffat = ltohs(bpbp->fatsec) * secsize; PC_DPRINTF1(5, "Computed bytes of fat = %u\n", bytesoffat); if ((bytesoffat * 2 / 3) >= numclusters && *(cp + PCFS_TYPESTRING_OFFSET16 + 4) == '2') { PC_DPRINTF0(4, "pc_getfattype: 12-bit FAT\n"); *fattypep = 0; rval = 0; goto out; } else if (*(cp + PCFS_TYPESTRING_OFFSET16 + 4) == '6') { /* * this check can result in a false positive, where * we believe a FAT12 filesystem to be a FAT16 one * (if the type recorded in the header block lies). * we recover from being lied to, in 'pc_getfat' by * Forcing fat12 over fat16, if * 'pcfs_fatsec <= 12' and * '(byteoffat * 2 / 3) >= numclusters' * the obvious check would have been: * else if ((bytesoffat / 2) >= numclusters && * *(cp + PCFS_TYPESTRING_OFFSET16 + 4) == '6') */ PC_DPRINTF0(4, "pc_getfattype: 16-bit FAT\n"); *fattypep = PCFS_FAT16; rval = 0; goto out; } goto lookforfdisk; } else if (*(cp + PCFS_TYPESTRING_OFFSET32) == 'F' && *(cp + PCFS_TYPESTRING_OFFSET32 + 1) == 'A' && *(cp + PCFS_TYPESTRING_OFFSET32 + 2) == 'T') { PC_DPRINTF0(5, "Found the FAT string at 32 location\n"); bpbp = (struct bootsec *)bp->b_un.b_addr; if ((int)ltohs(bpbp->fatsec) != 0) { /* * Not a good sign, we expect it to be zero if * this is really a FAT32 BPB. All we can do * is consider the string an anomaly, and go look * for an FDISK table. */ PC_DPRINTF0(5, "But secs/fat non-zero\n"); goto lookforfdisk; } PC_DPRINTF0(4, "pc_getfattype: 32-bit FAT\n"); *fattypep = PCFS_FAT32; rval = 0; goto out; } else { /* * We've got some legacy cases (like the stuff fdformat * produces!). Basically this is pre-MSDOS4.0 FATs. * * We'll declare a match if: * 1. Bytes/Sector and other fields seem reasonable * 2. The media byte matches a known one. * * If the media byte indicates a floppy we'll * assume FAT12, otherwise we'll assume FAT16. */ bpbp = (struct bootsec *)bp->b_un.b_addr; secsize = (int)ltohs(bpbp->bps[0]); if (secsize && secsize % 512 == 0 && ltohs(bpbp->fatsec) > 0 && bpbp->nfat > 0 && bpbp->spcl > 0 && ltohs(bpbp->res_sec[0]) >= 1 && ltohs(bpbp->numsect[0]) > 0) { switch (bpbp->mediadesriptor) { case SS8SPT: case DS8SPT: case SS9SPT: case DS9SPT: case DS18SPT: case DS9_15SPT: *fattypep = 0; rval = 0; goto out; case MD_FIXED: *fattypep = PCFS_FAT16; rval = 0; goto out; default: goto lookforfdisk; } } } lookforfdisk: /* * If we got here then we didn't find a BPB. * We now assume we are looking at the start of a hard drive, * where the first sector will be a Master Boot Record (MBR). * The MBR contains a partition table (also called an FDISK * table) and should end with a signature word (MBB_MAGIC). * * Check signature at end of boot block for good value. * If not then error with invalid request. */ dosp_ptr = (struct mboot *)bp->b_un.b_addr; if (ltohs(dosp_ptr->signature) != MBB_MAGIC) { cmn_err(CE_NOTE, "!pcfs: MBR signature error"); rval = EINVAL; goto out; } if (ldrive <= 0) { cmn_err(CE_NOTE, "!pcfs: no logical drive specified"); rval = EINVAL; goto out; } if (findTheDrive(dev, ldrive, &rval, &bp, strtsectp, &sysid) == 0) goto out; /* * Check the sysid value of the logical drive. * Return the correct value for the type of FAT found. * Else return a value of -1 for unknown FAT type. */ if ((sysid == DOS_FAT32) || (sysid == DOS_FAT32_LBA)) { *fattypep = PCFS_FAT32 | PCFS_NOCHK; PC_DPRINTF0(4, "pc_getfattype: 32-bit FAT\n"); } else if ((sysid == DOS_SYSFAT16) || (sysid == DOS_SYSHUGE) || (sysid == DIAGPART) || (sysid == DOS_FAT16P_LBA) || (sysid == DOS_FAT16_LBA)) { *fattypep = PCFS_FAT16 | PCFS_NOCHK; PC_DPRINTF0(4, "pc_getfattype: 16-bit FAT\n"); } else if (sysid == DOS_SYSFAT12) { *fattypep = PCFS_NOCHK; PC_DPRINTF0(4, "pc_getfattype: 12-bit FAT\n"); } else if (sysid == X86BOOT) { brelse(bp); bp = bread(dev, *strtsectp, PC_SAFESECSIZE); if (bp->b_flags & B_ERROR) { PC_DPRINTF0(1, "pc_getfattype: read error\n"); rval = EIO; goto out; } bootp = (struct bootsec *)bp->b_un.b_addr; /* get the sector size - may be more than 512 bytes */ secsize = (int)ltohs(bootp->bps[0]); /* * Check for bogus sector size - * fat should be at least 1 sector */ if (secsize < 512 || (int)ltohs(bootp->fatsec) < 1 || bootp->nfat < 1 || bootp->spcl < 1) { cmn_err(CE_NOTE, "!pcfs: FAT size error"); rval = EINVAL; goto out; } overhead = bootp->nfat * ltohs(bootp->fatsec); overhead += ltohs(bootp->res_sec[0]); overhead += (ltohs(bootp->rdirents[0]) * sizeof (struct pcdir)) / secsize; numclusters = ((ltohs(bootp->numsect[0]) ? ltohs(bootp->numsect[0]) : ltohi(bootp->totalsec)) - overhead) / bootp->spcl; if (numclusters > DOS_F12MAXC) { PC_DPRINTF0(4, "pc_getfattype: 16-bit FAT BOOTPART\n"); *fattypep = PCFS_FAT16 | PCFS_NOCHK | PCFS_BOOTPART; } else { PC_DPRINTF0(4, "pc_getfattype: 12-bit FAT BOOTPART\n"); *fattypep = PCFS_NOCHK | PCFS_BOOTPART; } } else { cmn_err(CE_NOTE, "!pcfs: unknown FAT type"); rval = EINVAL; } /* * Release the buffer used */ out: if (bp != NULL) brelse(bp); (void) VOP_CLOSE(devvp, FREAD, 1, (offset_t)0, CRED()); return (rval); } /* * Get the boot parameter block and file allocation table. * If there is an old FAT, invalidate it. */ int pc_getfat(struct pcfs *fsp) { struct vfs *vfsp = PCFSTOVFS(fsp); struct buf *tp = 0; struct buf *bp = 0; uchar_t *fatp = NULL; uchar_t *fat_changemap = NULL; struct bootsec *bootp; struct fat32_bootsec *f32b; struct vnode *devvp; int error; int fatsize; int fat_changemapsize; int flags = 0; int nfat; int secsize; int fatsec; PC_DPRINTF0(5, "pc_getfat\n"); devvp = fsp->pcfs_devvp; if (fsp->pcfs_fatp) { /* * There is a FAT in core. * If there are open file pcnodes or we have modified it or * it hasn't timed out yet use the in core FAT. * Otherwise invalidate it and get a new one */ #ifdef notdef if (fsp->pcfs_frefs || (fsp->pcfs_flags & PCFS_FATMOD) || (gethrestime_sec() < fsp->pcfs_fattime)) { return (0); } else { mutex_enter(&pcfslock); pc_invalfat(fsp); mutex_exit(&pcfslock); } #endif /* notdef */ return (0); } /* * Open block device mounted on. */ error = VOP_OPEN(&devvp, (vfsp->vfs_flag & VFS_RDONLY) ? FREAD : FREAD|FWRITE, CRED()); if (error) { PC_DPRINTF1(1, "pc_getfat: open error=%d\n", error); return (error); } /* * Get boot parameter block and check it for validity */ tp = bread(fsp->pcfs_xdev, fsp->pcfs_dosstart, PC_SAFESECSIZE); if (tp->b_flags & (B_ERROR | B_STALE)) { PC_DPRINTF0(1, "pc_getfat: boot block error\n"); flags = tp->b_flags & B_ERROR; error = EIO; goto out; } tp->b_flags |= B_STALE | B_AGE; bootp = (struct bootsec *)tp->b_un.b_addr; /* get the sector size - may be more than 512 bytes */ secsize = (int)ltohs(bootp->bps[0]); /* check for bogus sector size - fat should be at least 1 sector */ if (IS_FAT32(fsp)) { f32b = (struct fat32_bootsec *)bootp; fatsec = ltohi(f32b->f_fatlength); } else { fatsec = ltohs(bootp->fatsec); } if (secsize < 512 || fatsec < 1 || bootp->nfat < 1) { cmn_err(CE_NOTE, "!pcfs: FAT size error"); error = EINVAL; goto out; } switch (bootp->mediadesriptor) { default: cmn_err(CE_NOTE, "!pcfs: media-descriptor error, 0x%x", bootp->mediadesriptor); error = EINVAL; goto out; case MD_FIXED: /* * PCMCIA pseudo floppy is type MD_FIXED, * but is accessed like a floppy */ if (!(fsp->pcfs_flags & PCFS_PCMCIA_NO_CIS)) { fsp->pcfs_flags |= PCFS_NOCHK; } /* FALLTHRU */ case SS8SPT: case DS8SPT: case SS9SPT: case DS9SPT: case DS18SPT: case DS9_15SPT: fsp->pcfs_secsize = secsize; fsp->pcfs_sdshift = secsize / DEV_BSIZE - 1; fsp->pcfs_entps = secsize / sizeof (struct pcdir); fsp->pcfs_spcl = (int)bootp->spcl; fsp->pcfs_fatsec = fatsec; fsp->pcfs_spt = (int)ltohs(bootp->spt); fsp->pcfs_rdirsec = (int)ltohs(bootp->rdirents[0]) * sizeof (struct pcdir) / secsize; fsp->pcfs_clsize = fsp->pcfs_spcl * secsize; fsp->pcfs_fatstart = fsp->pcfs_dosstart + (daddr_t)ltohs(bootp->res_sec[0]); fsp->pcfs_rdirstart = fsp->pcfs_fatstart + (bootp->nfat * fsp->pcfs_fatsec); fsp->pcfs_datastart = fsp->pcfs_rdirstart + fsp->pcfs_rdirsec; if (IS_FAT32(fsp)) fsp->pcfs_rdirstart = ltohi(f32b->f_rootcluster); fsp->pcfs_ncluster = (((int)(ltohs(bootp->numsect[0]) ? ltohs(bootp->numsect[0]) : ltohi(bootp->totalsec))) - fsp->pcfs_datastart + fsp->pcfs_dosstart) / fsp->pcfs_spcl; fsp->pcfs_numfat = (int)bootp->nfat; fsp->pcfs_nxfrecls = PCF_FIRSTCLUSTER; break; } /* * Get FAT and check it for validity */ fatsize = fsp->pcfs_fatsec * fsp->pcfs_secsize; fatp = kmem_alloc(fatsize, KM_SLEEP); error = pc_readfat(fsp, fatp, fsp->pcfs_fatstart, fatsize); if (error) { flags = B_ERROR; goto out; } fat_changemapsize = (fatsize / fsp->pcfs_clsize) + 1; fat_changemap = kmem_zalloc(fat_changemapsize, KM_SLEEP); if (fatp[0] != bootp->mediadesriptor || fatp[1] != 0xFF || fatp[2] != 0xFF) { cmn_err(CE_NOTE, "!pcfs: FAT signature error"); error = EINVAL; goto out; } /* * Checking for fatsec and number of supported clusters, should * actually determine a FAT12/FAT media. See pc_getfattype(). * fatp[3] != 0xFF is necessary for FAT validity. */ if (fsp->pcfs_flags & PCFS_FAT16) { if ((fsp->pcfs_fatsec <= 12) && ((fatsize * 2 / 3) >= fsp->pcfs_ncluster)) { /* * We have a 12-bit FAT, rather than a 16-bit FAT. * Ignore what the fdisk table says. */ PC_DPRINTF0(2, "pc_getfattype: forcing 12-bit FAT\n"); fsp->pcfs_flags &= ~PCFS_FAT16; } else if (fatp[3] != 0xFF) { cmn_err(CE_NOTE, "!pcfs: FAT signature error"); error = EINVAL; goto out; } } /* * Sanity check our FAT is large enough for the * clusters we think we have. */ if ((fsp->pcfs_flags & PCFS_FAT16) && ((fatsize / 2) < fsp->pcfs_ncluster)) { cmn_err(CE_NOTE, "!pcfs: FAT too small for number of clusters"); error = EINVAL; goto out; } /* * Get alternate FATs and check for consistency * This is an inlined version of pc_readfat(). * Since we're only comparing FAT and alternate FAT, * there's no reason to let pc_readfat() copy data out * of the buf. Instead, compare in-situ, one cluster * at a time. */ for (nfat = 1; nfat < fsp->pcfs_numfat; nfat++) { size_t startsec; size_t off; startsec = fsp->pcfs_fatstart + nfat * fsp->pcfs_fatsec; for (off = 0; off < fatsize; off += fsp->pcfs_clsize) { bp = bread(fsp->pcfs_xdev, pc_dbdaddr(fsp, startsec + pc_cltodb(fsp, pc_lblkno(fsp, off))), MIN(fsp->pcfs_clsize, fatsize - off)); if (bp->b_flags & (B_ERROR | B_STALE)) { cmn_err(CE_NOTE, "!pcfs: alternate FAT #%d read error" " at byte %ld", nfat, off); flags = B_ERROR; error = EIO; goto out; } bp->b_flags |= B_STALE | B_AGE; if (bcmp(bp->b_un.b_addr, fatp + off, MIN(fsp->pcfs_clsize, fatsize - off))) { cmn_err(CE_NOTE, "!pcfs: alternate FAT #%d corrupted" " at byte %ld", nfat, off); flags = B_ERROR; } brelse(bp); bp = NULL; /* prevent double release */ } } fsp->pcfs_fatsize = fatsize; fsp->pcfs_fatp = fatp; fsp->pcfs_fat_changemapsize = fat_changemapsize; fsp->pcfs_fat_changemap = fat_changemap; fsp->pcfs_fattime = gethrestime_sec() + PCFS_DISKTIMEOUT; fsp->pcfs_fatjustread = 1; brelse(tp); tp = NULL; if (IS_FAT32(fsp)) { /* get fsinfo */ struct fat32_boot_fsinfo fsinfo_disk; fsp->f32fsinfo_sector = ltohs(f32b->f_infosector); tp = bread(fsp->pcfs_xdev, fsp->pcfs_dosstart + pc_dbdaddr(fsp, fsp->f32fsinfo_sector), PC_SAFESECSIZE); if (tp->b_flags & (B_ERROR | B_STALE)) { cmn_err(CE_NOTE, "!pcfs: error reading fat32 fsinfo"); flags = tp->b_flags & B_ERROR; brelse(tp); tp = NULL; error = EIO; goto out; } tp->b_flags |= B_STALE | B_AGE; bcopy((void *)(tp->b_un.b_addr + FAT32_BOOT_FSINFO_OFF), &fsinfo_disk, sizeof (struct fat32_boot_fsinfo)); brelse(tp); tp = NULL; /* translated fields */ fsp->fsinfo_native.fs_signature = ltohi(fsinfo_disk.fs_signature); fsp->fsinfo_native.fs_free_clusters = ltohi(fsinfo_disk.fs_free_clusters); if (fsp->fsinfo_native.fs_signature != FAT32_FS_SIGN) { cmn_err(CE_NOTE, "!pcfs: fat32 fsinfo signature mismatch."); error = EINVAL; goto out; } } return (0); out: cmn_err(CE_NOTE, "!pcfs: illegal disk format"); if (tp) brelse(tp); if (bp) brelse(bp); if (fatp) kmem_free(fatp, fatsize); if (fat_changemap) kmem_free(fat_changemap, fat_changemapsize); if (flags) { pc_mark_irrecov(fsp); } (void) VOP_CLOSE(devvp, (vfsp->vfs_flag & VFS_RDONLY) ? FREAD : FREAD|FWRITE, 1, (offset_t)0, CRED()); return (error); } int pc_syncfat(struct pcfs *fsp) { struct buf *bp; int nfat; int error; struct fat32_boot_fsinfo fsinfo_disk; PC_DPRINTF0(7, "pcfs_syncfat\n"); if ((fsp->pcfs_fatp == (uchar_t *)0) || !(fsp->pcfs_flags & PCFS_FATMOD)) return (0); /* * write out all copies of FATs */ fsp->pcfs_flags &= ~PCFS_FATMOD; fsp->pcfs_fattime = gethrestime_sec() + PCFS_DISKTIMEOUT; for (nfat = 0; nfat < fsp->pcfs_numfat; nfat++) { error = pc_writefat(fsp, fsp->pcfs_fatstart + nfat*fsp->pcfs_fatsec); if (error) { pc_mark_irrecov(fsp); return (EIO); } } pc_clear_fatchanges(fsp); PC_DPRINTF0(6, "pcfs_syncfat: wrote out FAT\n"); /* write out fsinfo */ if (IS_FAT32(fsp)) { bp = bread(fsp->pcfs_xdev, fsp->pcfs_dosstart + pc_dbdaddr(fsp, fsp->f32fsinfo_sector), PC_SAFESECSIZE); if (bp->b_flags & (B_ERROR | B_STALE)) { brelse(bp); return (EIO); } bcopy((void *)(bp->b_un.b_addr + FAT32_BOOT_FSINFO_OFF), &fsinfo_disk, sizeof (struct fat32_boot_fsinfo)); /* translate fields */ fsinfo_disk.fs_free_clusters = htoli(fsp->fsinfo_native.fs_free_clusters); fsinfo_disk.fs_next_cluster = (uint32_t)FSINFO_UNKNOWN; bcopy(&fsinfo_disk, (void *)(bp->b_un.b_addr + FAT32_BOOT_FSINFO_OFF), sizeof (struct fat32_boot_fsinfo)); bwrite2(bp); error = geterror(bp); brelse(bp); if (error) { pc_mark_irrecov(fsp); return (EIO); } } return (0); } void pc_invalfat(struct pcfs *fsp) { struct pcfs *xfsp; int mount_cnt = 0; PC_DPRINTF0(7, "pc_invalfat\n"); if (fsp->pcfs_fatp == (uchar_t *)0) panic("pc_invalfat"); /* * Release FAT */ kmem_free(fsp->pcfs_fatp, fsp->pcfs_fatsize); fsp->pcfs_fatp = NULL; kmem_free(fsp->pcfs_fat_changemap, fsp->pcfs_fat_changemapsize); fsp->pcfs_fat_changemap = NULL; /* * Invalidate all the blocks associated with the device. * Not needed if stateless. */ for (xfsp = pc_mounttab; xfsp; xfsp = xfsp->pcfs_nxt) if (xfsp != fsp && xfsp->pcfs_xdev == fsp->pcfs_xdev) mount_cnt++; if (!mount_cnt) binval(fsp->pcfs_xdev); /* * close mounted device */ (void) VOP_CLOSE(fsp->pcfs_devvp, (PCFSTOVFS(fsp)->vfs_flag & VFS_RDONLY) ? FREAD : FREAD|FWRITE, 1, (offset_t)0, CRED()); } void pc_badfs(struct pcfs *fsp) { cmn_err(CE_WARN, "corrupted PC file system on dev %x.%x\n", getmajor(fsp->pcfs_devvp->v_rdev), getminor(fsp->pcfs_devvp->v_rdev)); } /* * The problem with supporting NFS on the PCFS filesystem is that there * is no good place to keep the generation number. The only possible * place is inside a directory entry. There are a few words that we * don't use - they store NT & OS/2 attributes, and the creation/last access * time of the file - but it seems wrong to use them. In addition, directory * entries come and go. If a directory is removed completely, its directory * blocks are freed and the generation numbers are lost. Whereas in ufs, * inode blocks are dedicated for inodes, so the generation numbers are * permanently kept on the disk. */ static int pcfs_vget(struct vfs *vfsp, struct vnode **vpp, struct fid *fidp) { struct pcnode *pcp; struct pc_fid *pcfid; struct pcfs *fsp; struct pcdir *ep; daddr_t eblkno; int eoffset; struct buf *bp; int error; pc_cluster32_t cn; pcfid = (struct pc_fid *)fidp; fsp = VFSTOPCFS(vfsp); error = pc_lockfs(fsp, 0, 0); if (error) { *vpp = NULL; return (error); } if (pcfid->pcfid_block == 0) { pcp = pc_getnode(fsp, (daddr_t)0, 0, (struct pcdir *)0); pcp->pc_flags |= PC_EXTERNAL; *vpp = PCTOV(pcp); pc_unlockfs(fsp); return (0); } eblkno = pcfid->pcfid_block; eoffset = pcfid->pcfid_offset; if ((pc_dbtocl(fsp, eblkno - fsp->pcfs_dosstart) >= fsp->pcfs_ncluster) || (eoffset > fsp->pcfs_clsize)) { pc_unlockfs(fsp); *vpp = NULL; return (EINVAL); } if (eblkno >= fsp->pcfs_datastart || (eblkno-fsp->pcfs_rdirstart) < (fsp->pcfs_rdirsec & ~(fsp->pcfs_spcl - 1))) { bp = bread(fsp->pcfs_xdev, eblkno, fsp->pcfs_clsize); } else { bp = bread(fsp->pcfs_xdev, eblkno, (int)(fsp->pcfs_datastart - eblkno) * fsp->pcfs_secsize); } if (bp->b_flags & (B_ERROR | B_STALE)) { error = geterror(bp); brelse(bp); if (error) pc_mark_irrecov(fsp); *vpp = NULL; pc_unlockfs(fsp); return (error); } ep = (struct pcdir *)(bp->b_un.b_addr + eoffset); /* * Ok, if this is a valid file handle that we gave out, * then simply ensuring that the creation time matches, * the entry has not been deleted, and it has a valid first * character should be enough. * * Unfortunately, verifying that the _still_ * refers to a directory entry is not easy, since we'd have * to search _all_ directories starting from root to find it. * That's a high price to pay just in case somebody is forging * file handles. So instead we verify that as much of the * entry is valid as we can: * * 1. The starting cluster is 0 (unallocated) or valid * 2. It is not an LFN entry * 3. It is not hidden (unless mounted as such) * 4. It is not the label */ cn = pc_getstartcluster(fsp, ep); /* * if the starting cluster is valid, but not valid according * to pc_validcl(), force it to be to simplify the following if. */ if (cn == 0) cn = PCF_FIRSTCLUSTER; if (IS_FAT32(fsp)) { if (cn >= PCF_LASTCLUSTER32) cn = PCF_FIRSTCLUSTER; } else { if (cn >= PCF_LASTCLUSTER) cn = PCF_FIRSTCLUSTER; } if ((!pc_validcl(fsp, cn)) || (PCDL_IS_LFN(ep)) || (PCA_IS_HIDDEN(fsp, ep->pcd_attr)) || ((ep->pcd_attr & PCA_LABEL) == PCA_LABEL)) { bp->b_flags |= B_STALE | B_AGE; brelse(bp); pc_unlockfs(fsp); return (EINVAL); } if ((ep->pcd_crtime.pct_time == pcfid->pcfid_ctime) && (ep->pcd_filename[0] != PCD_ERASED) && (pc_validchar(ep->pcd_filename[0]) || (ep->pcd_filename[0] == '.' && ep->pcd_filename[1] == '.'))) { pcp = pc_getnode(fsp, eblkno, eoffset, ep); pcp->pc_flags |= PC_EXTERNAL; *vpp = PCTOV(pcp); } else { *vpp = NULL; } bp->b_flags |= B_STALE | B_AGE; brelse(bp); pc_unlockfs(fsp); return (0); } /* * if device is a PCMCIA pseudo floppy, return 1 * otherwise, return 0 */ static int pcfs_pseudo_floppy(dev_t rdev) { int error, err; struct dk_cinfo info; ldi_handle_t lh; ldi_ident_t li; err = ldi_ident_from_mod(&modlinkage, &li); if (err) { PC_DPRINTF1(1, "pcfs_pseudo_floppy: ldi_ident_from_mod err=%d\n", err); return (0); } err = ldi_open_by_dev(&rdev, OTYP_CHR, FREAD, CRED(), &lh, li); ldi_ident_release(li); if (err) { PC_DPRINTF1(1, "pcfs_pseudo_floppy: ldi_open err=%d\n", err); return (0); } /* return value stored in err is purposfully ignored */ error = ldi_ioctl(lh, DKIOCINFO, (intptr_t)&info, FKIOCTL, CRED(), &err); err = ldi_close(lh, FREAD, CRED()); if (err != 0) { PC_DPRINTF1(1, "pcfs_pseudo_floppy: ldi_close err=%d\n", err); return (0); } if ((error == 0) && (info.dki_ctype == DKC_PCMCIA_MEM) && (info.dki_flags & DKI_PCMCIA_PFD)) return (1); else return (0); } /* * Unfortunately, FAT32 fat's can be pretty big (On a 1 gig jaz drive, about * a meg), so we can't bread() it all in at once. This routine reads a * fat a chunk at a time. */ static int pc_readfat(struct pcfs *fsp, uchar_t *fatp, daddr_t start, size_t fatsize) { struct buf *bp; size_t off; size_t readsize; readsize = fsp->pcfs_clsize; for (off = 0; off < fatsize; off += readsize, fatp += readsize) { if (readsize > (fatsize - off)) readsize = fatsize - off; bp = bread(fsp->pcfs_xdev, pc_dbdaddr(fsp, start + pc_cltodb(fsp, pc_lblkno(fsp, off))), readsize); if (bp->b_flags & (B_ERROR | B_STALE)) { brelse(bp); return (EIO); } bp->b_flags |= B_STALE | B_AGE; bcopy(bp->b_un.b_addr, fatp, readsize); brelse(bp); } return (0); } /* * We write the FAT out a _lot_, in order to make sure that it * is up-to-date. But on a FAT32 system (large drive, small clusters) * the FAT might be a couple of megabytes, and writing it all out just * because we created or deleted a small file is painful (especially * since we do it for each alternate FAT too). So instead, for FAT16 and * FAT32 we only write out the bit that has changed. We don't clear * the 'updated' fields here because the caller might be writing out * several FATs, so the caller must use pc_clear_fatchanges() after * all FATs have been updated. */ static int pc_writefat(struct pcfs *fsp, daddr_t start) { struct buf *bp; size_t off; size_t writesize; int error; uchar_t *fatp = fsp->pcfs_fatp; size_t fatsize = fsp->pcfs_fatsize; writesize = fsp->pcfs_clsize; for (off = 0; off < fatsize; off += writesize, fatp += writesize) { if (writesize > (fatsize - off)) writesize = fatsize - off; if (!pc_fat_is_changed(fsp, pc_lblkno(fsp, off))) { continue; } bp = ngeteblk(writesize); bp->b_edev = fsp->pcfs_xdev; bp->b_dev = cmpdev(bp->b_edev); bp->b_blkno = pc_dbdaddr(fsp, start + pc_cltodb(fsp, pc_lblkno(fsp, off))); bcopy(fatp, bp->b_un.b_addr, writesize); bwrite2(bp); error = geterror(bp); brelse(bp); if (error) { return (error); } } return (0); } /* * Mark the FAT cluster that 'cn' is stored in as modified. */ void pc_mark_fat_updated(struct pcfs *fsp, pc_cluster32_t cn) { pc_cluster32_t bn; size_t size; /* which fat block is the cluster number stored in? */ if (IS_FAT32(fsp)) { size = sizeof (pc_cluster32_t); bn = pc_lblkno(fsp, cn * size); fsp->pcfs_fat_changemap[bn] = 1; } else if (IS_FAT16(fsp)) { size = sizeof (pc_cluster16_t); bn = pc_lblkno(fsp, cn * size); fsp->pcfs_fat_changemap[bn] = 1; } else { offset_t off; pc_cluster32_t nbn; ASSERT(IS_FAT12(fsp)); off = cn + (cn >> 1); bn = pc_lblkno(fsp, off); fsp->pcfs_fat_changemap[bn] = 1; /* does this field wrap into the next fat cluster? */ nbn = pc_lblkno(fsp, off + 1); if (nbn != bn) { fsp->pcfs_fat_changemap[nbn] = 1; } } } /* * return whether the FAT cluster 'bn' is updated and needs to * be written out. */ int pc_fat_is_changed(struct pcfs *fsp, pc_cluster32_t bn) { return (fsp->pcfs_fat_changemap[bn] == 1); }