pc_vfsops.c revision 18c2aff776a775d34a4c9893a4c72e0434d68e36
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/pathname.h>
#include <sys/sysmacros.h>
/*
* 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.
*/
static int pcfs_pseudo_floppy(dev_t);
static int pcfsinit(int, char *);
struct cred *);
static int pc_syncfsnodes(struct pcfs *);
/*
* pcfs mount options table
*/
/*
* option name cancel option default arg flags opt data
*/
};
static mntopts_t pcfs_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
*
* pcfs_mountcount: used to prevent module unloads while there is still
* pcfs state from a former mount hanging around. With
* forced umount support, the filesystem module must not
* be allowed to go away before the last VFS_FREEVFS()
* call has been made.
* Since this is just an atomic counter, there's no need
* for locking.
*/
static int pcfstype;
"pcfs",
};
"PC filesystem v%I%",
&vfw
};
static struct modlinkage modlinkage = {
&modlfs,
};
int
_init(void)
{
int error;
#if !defined(lint)
/* make sure the on-disk structures are sane */
#endif
if (error) {
}
return (error);
}
int
_fini(void)
{
int error;
/*
* If a forcedly unmounted instance is still hanging around,
* we cannot allow the module to be unloaded because that would
* cause panics once the VFS framework decides it's time to call
* into VFS_FREEVFS().
*/
if (pcfs_mountcount)
return (EBUSY);
if (error)
return (error);
/*
* Tear down the operations vectors
*/
(void) vfs_freevfsops_by_type(pcfstype);
return (0);
}
int
{
}
/* ARGSUSED1 */
static int
{
static const fs_operation_def_t pcfs_vfsops_template[] = {
};
int error;
if (error != 0) {
return (error);
}
if (error != 0) {
(void) vfs_freevfsops_by_type(fstype);
return (error);
}
if (error != 0) {
(void) vfs_freevfsops_by_type(fstype);
return (error);
}
(void) pc_init();
pcfs_mountcount = 0;
return (0);
}
/*
* 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
{
char *spnp;
int dos_ldrive = 0;
int error;
int fattype;
int spnlen;
int wantbootpart = 0;
return (error);
return (ENOTDIR);
}
return (EBUSY);
}
/*
* 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.
*/
(datalen != sizeof (struct old_pcfs_args))) {
return (EINVAL);
} else {
int hidden = 0;
int foldcase = 0;
int noclamptime = 0;
return (EFAULT);
}
}
if (hidden)
if (foldcase)
if (noclamptime)
/*
* more than one pc filesystem can be mounted on x86
* so the pc_tz structure is now a critical region
*/
if (pc_mounttab == NULL)
}
/*
* Resolve path name of special file being mounted.
*/
return (error);
}
if (error =
/*
* look for suffix to special
* which indicates a request to mount the solaris boot
* partition, or a DOS logical drive on the hard disk
*/
if (spnlen > 5) {
*spnp++ == 't') {
/*
* Looks as if they want to mount
* the Solaris boot partition
*/
wantbootpart = 1;
*spnp = '\0';
}
}
if (!wantbootpart) {
spnlen--;
spnlen--;
*spnp <= '9') {
spnlen--;
}
}
*spnp == ':') {
/*
* remove suffix so that we have a real
* device name
*/
*spnp = '\0';
}
}
if (error) {
return (error);
}
}
return (ENOTBLK);
}
/*
* Verify caller's permission to open the device special file.
*/
} else {
}
return (error);
}
return (ENXIO);
}
/*
* Ensure that this device (or logical drive) isn't already mounted,
* unless this is a REMOUNT request
*/
if (dos_ldrive) {
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? */
printf("whoops - upper bits used!\n");
#endif
return (EBUSY);
}
if (vfs_devismounted(pseudodev)) {
return (0);
} else {
return (EBUSY);
}
}
} else {
return (EBUSY);
}
if (vfs_devismounted(xdev))
return (0);
} else {
return (EBUSY);
}
}
}
/*
* Mount the filesystem
*/
return (EBUSY);
}
/*
* special handling for PCMCIA memory card
* with pseudo floppies organization
*/
} else {
&fattype)) {
return (error);
}
}
vfsp->vfs_bcount = 0;
if (error) {
return (error);
}
pc_mounttab = fsp;
return (0);
}
/*
* vfs operations
*/
/* ARGSUSED */
static int
int flag,
{
return (EPERM);
/*
* We don't have to lock fsp because the VVFSLOCK in vfs layer will
* prevent lookuppn from crossing the mount point.
* If this is not a forced umount request and there's ongoing I/O,
* don't allow the mount to proceed.
*/
else if (fsp->pcfs_nrefs)
return (EBUSY);
/*
* If this is a forced umount request or if the fs instance has
* been marked as beyond recovery, allow the umount to proceed
* regardless of state. pc_diskchanged() forcibly releases all
*/
}
/* now there should be no pcp node on pcfhead or pcdhead. */
if (fsp == pc_mounttab) {
} else {
}
/*
* Since we support VFS_FREEVFS(), there's no need to
* free the fsp right now. The framework will tell us
* when the right time to do so has arrived by calling
* into pcfs_freevfs.
*/
return (0);
}
/*
* find root of pcfs
*/
static int
{
int error;
return (error);
return (0);
}
/*
* Get file system statistics.
*/
static int
{
int error;
if (error)
return (error);
#ifdef notdef
#endif /* notdef */
return (0);
}
static int
{
int error;
return (error);
break;
}
if (error)
break;
hp++;
}
}
return (error);
}
/*
* Flush any pending I/O.
*/
/*ARGSUSED*/
static int
short flag,
{
int error = 0;
/* this prevents the filesystem from being umounted. */
} else {
}
} else {
fsp = pc_mounttab;
break;
}
if (error) break;
}
}
return (error);
}
int
{
int err;
return (EIO);
fsp->pcfs_count++;
} else {
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)
*/
return (err);
}
}
fsp->pcfs_count++;
}
return (0);
}
void
{
panic("pc_unlockfs");
if (--fsp->pcfs_count < 0)
panic("pc_unlockfs: count");
if (fsp->pcfs_count == 0) {
fsp->pcfs_owner = 0;
}
}
/*
* 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
*/
static int
{
}
/*
* 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
{
}
/*
* 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
{
}
/*
* noLogicalDrive()
* Display error message about not being able to find a logical
* drive.
*/
static void
noLogicalDrive(int requested)
{
if (requested == BOOT_PARTITION_DRIVE) {
} else {
}
}
/*
* 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
{
int logicalDriveCount = 0; /* Count of logical drives seen */
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.
*/
/*
* 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 (primaryPart < 0) {
primaryPart = i;
} else {
extraDrives[numExtraDrives++] = i;
}
continue;
}
extendedPart = i;
continue;
}
bootPart = i;
continue;
}
}
if (askedFor == BOOT_PARTITION_DRIVE) {
if (bootPart < 0) {
return (0);
}
return (1);
}
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)) {
return (0);
}
if (extendedPart >= 0) {
do {
/*
* If the seek would not cause us to change
* position on the drive, then we're out of
* extended partitions to examine.
*/
break;
/*
* Seek the next extended partition, and find
* logical drives within it.
*/
return (0);
}
"extended partition signature err");
return (0);
}
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++) {
extndDrives[numDrives++] = i;
continue;
/*
* Already found an extended
* partition in this table.
*/
"!pcfs: ignoring unexpected"
" additional extended"
" partition");
continue;
}
diskblk = xstartsect +
continue;
}
}
/*
* The number of logical drives we've found thus
* far is enough to get us to the one we were
* searching for.
*/
*startSector =
"values bad");
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.
*/
return (0);
}
sizeof (struct ipart) * FD_NUMPART);
}
}
/*
* Still haven't found the drive, is it an extra
* drive defined in the main FDISK table?
*/
return (1);
}
/*
* Still haven't found the drive, and there is
* nowhere else to look.
*/
return (0);
}
/*
*/
static int
{
if (pcfsdebuglevel >= 5) {
}
}
/*
* FAT32 specific consistency checks.
*/
static int
{
if (pcfsdebuglevel >= 5) {
}
}
/*
* Calculate the number of clusters in order to determine
* the type of FAT we are looking at. This is the only
* recommended way of determining FAT type, though there
* are other hints in the data, this is the best way.
*/
static ulong_t
{
struct fat32_bootsec *bpb;
char FileSysType[9];
/*
* Cast it to FAT32 bpb. If it turns out to be FAT12/16, its
* OK, we won't try accessing the data beyond the FAT16 header
* boundary.
*/
if (pcfsdebuglevel >= 5) {
FileSysType[8] = 0;
"%s", FileSysType);
}
}
else
else
return (CountOfClusters);
}
static int
{
/*
* From Microsoft:
* In the following example, when it says <, it does not mean <=.
* Note also that the numbers are correct. The first number for
* FAT12 is 4085; the second number for FAT16 is 65525. These numbers
* and the '<' signs are not wrong.
*/
/* Watch for edge cases */
return (-1); /* Cannot be determined yet */
} else if (CountOfClusters < 4085) {
/* Volume is FAT12 */
return (0);
} else if (CountOfClusters < 65525) {
/* Volume is FAT16 */
return (PCFS_FAT16);
} else {
/* Volume is FAT32 */
return (PCFS_FAT32);
}
}
s == 32 || s == 64 || s == 128)
static int
{
/*
* Perform secondary checks to try and determine what sort
* of FAT partition we have based on other, less reliable,
* data in the BPB header.
*/
/*
* Must be FAT12 or FAT16, check the
* FilSysType string (not 100% reliable).
*/
return (0); /* FAT12 */
5)) {
return (PCFS_FAT16);
} else {
/*
* Try to use the BPB_Media byte
*
* If the media byte indicates a floppy we'll
* assume FAT12, otherwise we'll assume FAT16.
*/
switch (bpb->mediadesriptor) {
case SS8SPT:
case DS8SPT:
case SS9SPT:
case DS9SPT:
case DS18SPT:
case DS9_15SPT:
PC_DPRINTF0(5,
"secondaryBPBCheck says: FAT12");
return (0); /* FAT12 */
case MD_FIXED:
PC_DPRINTF0(5,
"secondaryBPBCheck says: FAT16");
return (PCFS_FAT16);
default:
"!pcfs: unknown FAT type");
return (-1);
}
}
return (PCFS_FAT32);
} else {
/* We don't know */
return (-1);
}
}
/*
* Check to see if the BPB we found is correct.
*
* First, look for obvious, tell-tale signs of trouble:
* The NumFATs value should always be 2. Sometimes it can be a '1'
* on FLASH memory cards and other non-disk-based media, so we
* will allow that as well.
*
* We also look at the Media byte, the valid range is 0xF0, or
* 0xF8 thru 0xFF, anything else means this is probably not a good
* BPB.
*
* Finally, check the BPB Magic number at the end of the 512 byte
* block, it must be 0xAA55.
*
* If that all is good, calculate the number of clusters and
* do some final verification steps.
*
* If all is well, return success (1) and set the fattypep
* value to the correct FAT value.
*/
static int
{
if (pcfsdebuglevel >= 3) {
if (!VALID_SECSIZE(secsize))
secsize);
}
!VALID_SECSIZE(secsize) ||
return (0);
/*
* Basic sanity checks passed so far, now try to determine which
* FAT format to use.
*/
/* Do some final sanity checks for each specific type of FAT */
switch (*fattypep) {
case 0: /* FAT12 */
case PCFS_FAT16:
return (0);
break;
case PCFS_FAT32:
return (0);
break;
default: /* not sure yet */
if (*fattypep == -1) {
/* Still nothing, give it up. */
return (0);
}
break;
}
return (1);
}
/*
* Get the FAT type for the DOS medium.
*
* -------------------------
* According to Microsoft:
* The FAT type one of FAT12, FAT16, or FAT32 is determined by the
* count of clusters on the volume and nothing else.
* -------------------------
*
*/
static int
int ldrive,
int *fattypep)
{
int rval = 0;
/*
* Open the device so we can check out the BPB or FDISK table,
* then read in the sector.
*/
return (rval);
}
/*
* Read block 0 from device
*/
goto out;
}
/*
* If the first block is not a valid BPB, look for the
* through the FDISK table.
*/
/* find the partition table and get 512 bytes from it. */
goto out;
goto out;
}
/* If this one is still no good, give it up. */
goto out;
}
}
out:
/*
* Release the buffer used
*/
return (rval);
}
/*
* Get the boot parameter block and file allocation table.
* If there is an old FAT, invalidate it.
*/
int
{
struct fat32_bootsec *f32b;
int error;
int fatsize;
int fat_changemapsize;
int flags = 0;
int nfat;
int secsize;
int fatsec;
/*
* 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 ||
return (0);
} else {
}
#endif /* notdef */
return (0);
}
/*
* Open block device mounted on.
*/
CRED());
if (error) {
return (error);
}
/*
* Get boot parameter block and check it for validity
*/
goto out;
}
/* get the sector size - may be more than 512 bytes */
/* check for bogus sector size - fat should be at least 1 sector */
} else {
}
goto out;
}
switch (bootp->mediadesriptor) {
default:
goto out;
case MD_FIXED:
/*
* PCMCIA pseudo floppy is type MD_FIXED,
* but is accessed like a floppy
*/
}
/* FALLTHRU */
case SS8SPT:
case DS8SPT:
case SS9SPT:
case DS9SPT:
case DS18SPT:
case DS9_15SPT:
break;
}
/*
* Get FAT and check it for validity
*/
if (error) {
goto out;
}
/*
* The only definite signature check is that the
* media descriptor byte should match the first byte
* of the FAT block.
*/
goto out;
}
/*
* Checking for fatsec and number of supported clusters, should
*/
/*
* We have a 12-bit FAT, rather than a 16-bit FAT.
* Ignore what the fdisk table says.
*/
}
}
/*
* Sanity check our FAT is large enough for the
* clusters we think we have.
*/
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.
*/
startsec +
"!pcfs: alternate FAT #%d read error"
goto out;
}
"!pcfs: alternate FAT #%d corrupted"
}
}
}
/* get fsinfo */
struct fat32_boot_fsinfo fsinfo_disk;
goto out;
}
&fsinfo_disk, sizeof (struct fat32_boot_fsinfo));
/* translated fields */
"!pcfs: fat32 fsinfo signature mismatch.");
goto out;
}
}
return (0);
out:
if (tp)
if (bp)
if (fatp)
if (fat_changemap)
if (flags) {
}
return (error);
}
int
{
int nfat;
int error;
struct fat32_boot_fsinfo fsinfo_disk;
return (0);
/*
* write out all copies of FATs
*/
if (error) {
return (EIO);
}
}
/* write out fsinfo */
return (EIO);
}
&fsinfo_disk, sizeof (struct fat32_boot_fsinfo));
/* translate fields */
sizeof (struct fat32_boot_fsinfo));
if (error) {
return (EIO);
}
}
return (0);
}
void
{
int mount_cnt = 0;
panic("pc_invalfat");
/*
* Release FAT
*/
/*
* Invalidate all the blocks associated with the device.
* Not needed if stateless.
*/
mount_cnt++;
if (!mount_cnt)
/*
* close mounted device
*/
}
void
{
}
/*
* 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
* 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
{
int eoffset;
int error;
if (error) {
return (error);
}
if (pcfid->pcfid_block == 0) {
return (0);
}
return (EINVAL);
}
} else {
}
if (error)
return (error);
}
/*
* 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 <blkno, offset> _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
*/
/*
* 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)
if (cn >= PCF_LASTCLUSTER32)
} else {
if (cn >= PCF_LASTCLUSTER)
}
(PCDL_IS_LFN(ep)) ||
return (EINVAL);
}
} else {
}
return (0);
}
/*
* if device is a PCMCIA pseudo floppy, return 1
* otherwise, return 0
*/
static int
{
if (err) {
PC_DPRINTF1(1,
"pcfs_pseudo_floppy: ldi_ident_from_mod err=%d\n", err);
return (0);
}
if (err) {
PC_DPRINTF1(1,
"pcfs_pseudo_floppy: ldi_open err=%d\n", err);
return (0);
}
/* return value stored in err is purposfully ignored */
if (err != 0) {
PC_DPRINTF1(1,
"pcfs_pseudo_floppy: ldi_close err=%d\n", err);
return (0);
}
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
{
readsize);
return (EIO);
}
}
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
{
int error;
continue;
}
if (error) {
return (error);
}
}
return (0);
}
/*
* Mark the FAT cluster that 'cn' is stored in as modified.
*/
void
{
/* which fat block is the cluster number stored in? */
size = sizeof (pc_cluster32_t);
size = sizeof (pc_cluster16_t);
} else {
/* does this field wrap into the next fat cluster? */
}
}
}
/*
* return whether the FAT cluster 'bn' is updated and needs to
* be written out.
*/
int
{
}
/*
* Implementation of VFS_FREEVFS() to support forced umounts.
* This is called by the vfs framework after umount, to trigger
* the release of any resources still associated with the given
* vfs_t once the need to keep them has gone away.
*/
void
{
/*
* Allow _fini() to succeed now, if so desired.
*/
}