/* * 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 * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * libfstyp module for pcfs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PC_LABEL_SIZE 11 /* for the PCDL_IS_LFN macro */ int enable_long_filenames = 1; struct fstyp_fat16_bs { uint8_t f_drvnum; uint8_t f_reserved1; uint8_t f_bootsig; uint8_t f_volid[4]; uint8_t f_label[11]; uint8_t f_typestring[8]; }; struct fstyp_fat32_bs { uint32_t f_fatlength; uint16_t f_flags; uint8_t f_major; uint8_t f_minor; uint32_t f_rootcluster; uint16_t f_infosector; uint16_t f_backupboot; uint8_t f_reserved2[12]; uint8_t f_drvnum; uint8_t f_reserved1; uint8_t f_bootsig; uint8_t f_volid[4]; uint8_t f_label[11]; uint8_t f_typestring[8]; }; typedef struct fstyp_pcfs { int fd; off_t offset; nvlist_t *attr; struct bootsec bs; struct fstyp_fat16_bs bs16; struct fstyp_fat32_bs bs32; ushort_t bps; int fattype; char volume_label[PC_LABEL_SIZE + 1]; /* parameters derived or calculated per FAT spec */ ulong_t FATSz; ulong_t TotSec; ulong_t RootDirSectors; ulong_t FirstDataSector; ulong_t DataSec; ulong_t CountOfClusters; } fstyp_pcfs_t; /* We should eventually make the structs "packed" so these won't be needed */ #define PC_BPSEC(h) ltohs((h)->bs.bps[0]) #define PC_RESSEC(h) ltohs((h)->bs.res_sec[0]) #define PC_NROOTENT(h) ltohs((h)->bs.rdirents[0]) #define PC_NSEC(h) ltohs((h)->bs.numsect[0]) #define PC_DRVNUM(h) (FSTYP_IS_32(h) ? (h)->bs32.f_drvnum : \ (h)->bs16.f_drvnum) #define PC_VOLID(a) (FSTYP_IS_32(h) ? ltohi((h)->bs32.f_volid[0]) : \ ltohi((h)->bs16.f_volid[0])) #define PC_LABEL_ADDR(a) (FSTYP_IS_32(h) ? \ &((h)->bs32.f_label[0]) : &((h)->bs16.f_label[0])) #define FSTYP_IS_32(h) ((h)->fattype == 32) #define FSTYP_MAX_CLUSTER_SIZE (64 * 1024) /* though officially 32K */ #define FSTYP_MAX_DIR_SIZE (65536 * 32) static int read_bootsec(fstyp_pcfs_t *h); static int valid_media(fstyp_pcfs_t *h); static int well_formed(fstyp_pcfs_t *h); static void calculate_parameters(fstyp_pcfs_t *h); static void determine_fattype(fstyp_pcfs_t *h); static void get_label(fstyp_pcfs_t *h); static void get_label_16(fstyp_pcfs_t *h); static void get_label_32(fstyp_pcfs_t *h); static int next_cluster_32(fstyp_pcfs_t *h, int n); static boolean_t dir_find_label(fstyp_pcfs_t *h, struct pcdir *d, int nent); static int is_pcfs(fstyp_pcfs_t *h); static int dumpfs(fstyp_pcfs_t *h, FILE *fout, FILE *ferr); static int get_attr(fstyp_pcfs_t *h); int fstyp_mod_init(int fd, off_t offset, fstyp_mod_handle_t *handle); void fstyp_mod_fini(fstyp_mod_handle_t handle); int fstyp_mod_ident(fstyp_mod_handle_t handle); int fstyp_mod_get_attr(fstyp_mod_handle_t handle, nvlist_t **attrp); int fstyp_mod_dump(fstyp_mod_handle_t handle, FILE *fout, FILE *ferr); int fstyp_mod_init(int fd, off_t offset, fstyp_mod_handle_t *handle) { struct fstyp_pcfs *h; if ((h = calloc(1, sizeof (struct fstyp_pcfs))) == NULL) { return (FSTYP_ERR_NOMEM); } h->fd = fd; h->offset = offset; *handle = (fstyp_mod_handle_t)h; return (0); } void fstyp_mod_fini(fstyp_mod_handle_t handle) { struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle; if (h->attr == NULL) { nvlist_free(h->attr); h->attr = NULL; } free(h); } int fstyp_mod_ident(fstyp_mod_handle_t handle) { struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle; return (is_pcfs(h)); } int fstyp_mod_get_attr(fstyp_mod_handle_t handle, nvlist_t **attrp) { struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle; int error; if (h->attr == NULL) { if (nvlist_alloc(&h->attr, NV_UNIQUE_NAME_TYPE, 0)) { return (FSTYP_ERR_NOMEM); } if ((error = get_attr(h)) != 0) { nvlist_free(h->attr); h->attr = NULL; return (error); } } *attrp = h->attr; return (0); } int fstyp_mod_dump(fstyp_mod_handle_t handle, FILE *fout, FILE *ferr) { struct fstyp_pcfs *h = (struct fstyp_pcfs *)handle; return (dumpfs(h, fout, ferr)); } /* * Read in boot sector. Convert into host endianness where possible. */ static int read_bootsec(fstyp_pcfs_t *h) { char buf[PC_SECSIZE]; (void) lseek(h->fd, h->offset, SEEK_SET); if (read(h->fd, buf, sizeof (buf)) != sizeof (buf)) { return (FSTYP_ERR_IO); } bcopy(buf, &h->bs, sizeof (h->bs)); bcopy(buf + sizeof (struct bootsec), &h->bs16, sizeof (h->bs16)); bcopy(buf + sizeof (struct bootsec), &h->bs32, sizeof (h->bs32)); h->bs.fatsec = ltohs(h->bs.fatsec); h->bs.spt = ltohs(h->bs.spt); h->bs.nhead = ltohs(h->bs.nhead); h->bs.hiddensec = ltohi(h->bs.hiddensec); h->bs.totalsec = ltohi(h->bs.totalsec); h->bs32.f_fatlength = ltohi(h->bs32.f_fatlength); h->bs32.f_flags = ltohs(h->bs32.f_flags); h->bs32.f_rootcluster = ltohi(h->bs32.f_rootcluster); h->bs32.f_infosector = ltohs(h->bs32.f_infosector); h->bs32.f_backupboot = ltohs(h->bs32.f_backupboot); h->bps = PC_BPSEC(h); return (0); } static int valid_media(fstyp_pcfs_t *h) { switch (h->bs.mediadesriptor) { case MD_FIXED: case SS8SPT: case DS8SPT: case SS9SPT: case DS9SPT: case DS18SPT: case DS9_15SPT: return (1); default: return (0); } } static int well_formed(fstyp_pcfs_t *h) { int fatmatch; if (h->bs16.f_bootsig == 0x29) { fatmatch = ((h->bs16.f_typestring[0] == 'F' && h->bs16.f_typestring[1] == 'A' && h->bs16.f_typestring[2] == 'T') && (h->bs.fatsec > 0) && ((PC_NSEC(h) == 0 && h->bs.totalsec > 0) || PC_NSEC(h) > 0)); } else if (h->bs32.f_bootsig == 0x29) { fatmatch = ((h->bs32.f_typestring[0] == 'F' && h->bs32.f_typestring[1] == 'A' && h->bs32.f_typestring[2] == 'T') && (h->bs.fatsec == 0 && h->bs32.f_fatlength > 0) && ((PC_NSEC(h) == 0 && h->bs.totalsec > 0) || PC_NSEC(h) > 0)); } else { fatmatch = (PC_NSEC(h) > 0 && h->bs.fatsec > 0); } return (fatmatch && h->bps > 0 && h->bps % 512 == 0 && h->bs.spcl > 0 && PC_RESSEC(h) >= 1 && h->bs.nfat > 0); } static void calculate_parameters(fstyp_pcfs_t *h) { if (PC_NSEC(h) != 0) { h->TotSec = PC_NSEC(h); } else { h->TotSec = h->bs.totalsec; } if (h->bs.fatsec != 0) { h->FATSz = h->bs.fatsec; } else { h->FATSz = h->bs32.f_fatlength; } if ((h->bps == 0) || (h->bs.spcl == 0)) { return; } h->RootDirSectors = ((PC_NROOTENT(h) * 32) + (h->bps - 1)) / h->bps; h->FirstDataSector = PC_RESSEC(h) + h->bs.nfat * h->FATSz + h->RootDirSectors; h->DataSec = h->TotSec - h->FirstDataSector; h->CountOfClusters = h->DataSec / h->bs.spcl; } static void determine_fattype(fstyp_pcfs_t *h) { if ((h->CountOfClusters >= 4085 && h->CountOfClusters <= 4095) || (h->CountOfClusters >= 65525 && h->CountOfClusters <= 65535)) { h->fattype = 0; } else if (h->CountOfClusters < 4085) { h->fattype = 12; } else if (h->CountOfClusters < 65525) { h->fattype = 16; } else { h->fattype = 32; } } static void get_label(fstyp_pcfs_t *h) { /* * Use label from the boot sector by default. * Can overwrite later with the one from root directory. */ (void) memcpy(h->volume_label, PC_LABEL_ADDR(h), PC_LABEL_SIZE); h->volume_label[PC_LABEL_SIZE] = '\0'; if (h->fattype == 0) { return; } else if (FSTYP_IS_32(h)) { get_label_32(h); } else { get_label_16(h); } } /* * Get volume label from the root directory entry. * In FAT12/16 the root directory is of fixed size. * It immediately follows the FATs */ static void get_label_16(fstyp_pcfs_t *h) { ulong_t FirstRootDirSecNum; int secsize; off_t offset; uint8_t buf[PC_SECSIZE * 4]; int i; int nent, resid; if ((secsize = h->bps) > sizeof (buf)) { return; } FirstRootDirSecNum = PC_RESSEC(h) + h->bs.nfat * h->bs.fatsec; offset = h->offset + FirstRootDirSecNum * secsize; resid = PC_NROOTENT(h); for (i = 0; i < h->RootDirSectors; i++) { (void) lseek(h->fd, offset, SEEK_SET); if (read(h->fd, buf, secsize) != secsize) { return; } nent = secsize / sizeof (struct pcdir); if (nent > resid) { nent = resid; } if (dir_find_label(h, (struct pcdir *)buf, nent)) { return; } resid -= nent; offset += PC_SECSIZE; } } /* * Get volume label from the root directory entry. * In FAT32 root is a usual directory, a cluster chain. * It starts at BPB_RootClus. */ static void get_label_32(fstyp_pcfs_t *h) { off_t offset; int clustersize; int n; ulong_t FirstSectorofCluster; uint8_t *buf; int nent; int cnt = 0; clustersize = h->bs.spcl * h->bps; if ((clustersize == 0) || (clustersize > FSTYP_MAX_CLUSTER_SIZE) || ((buf = calloc(1, clustersize)) == NULL)) { return; } for (n = h->bs32.f_rootcluster; n != 0; n = next_cluster_32(h, n)) { FirstSectorofCluster = (n - 2) * h->bs.spcl + h->FirstDataSector; offset = h->offset + FirstSectorofCluster * h->bps; (void) lseek(h->fd, offset, SEEK_SET); if (read(h->fd, buf, clustersize) != clustersize) { break; } nent = clustersize / sizeof (struct pcdir); if (dir_find_label(h, (struct pcdir *)buf, nent)) { break; } if (++cnt > FSTYP_MAX_DIR_SIZE / clustersize) { break; } } free(buf); } /* * Get a FAT entry pointing to the next file cluster */ int next_cluster_32(fstyp_pcfs_t *h, int n) { uint8_t buf[PC_SECSIZE]; ulong_t ThisFATSecNum; ulong_t ThisFATEntOffset; off_t offset; uint32_t val; int next = 0; ThisFATSecNum = PC_RESSEC(h) + (n * 4) / h->bps; ThisFATEntOffset = (n * 4) % h->bps; offset = h->offset + ThisFATSecNum * h->bps; (void) lseek(h->fd, offset, SEEK_SET); if (read(h->fd, buf, sizeof (buf)) == sizeof (buf)) { val = buf[ThisFATEntOffset] & 0x0fffffff; next = ltohi(val); } return (next); } /* * Given an array of pcdir structs, find one containing volume label. */ static boolean_t dir_find_label(fstyp_pcfs_t *h, struct pcdir *d, int nent) { int i; for (i = 0; i < nent; i++, d++) { if (PCDL_IS_LFN(d)) continue; if ((d->pcd_filename[0] != PCD_UNUSED) && (d->pcd_filename[0] != PCD_ERASED) && ((d->pcd_attr & (PCA_LABEL | PCA_DIR)) == PCA_LABEL) && (d->un.pcd_scluster_hi == 0) && (d->pcd_scluster_lo == 0)) { (void) memcpy(h->volume_label, d->pcd_filename, PC_LABEL_SIZE); h->volume_label[PC_LABEL_SIZE] = '\0'; return (B_TRUE); } } return (B_FALSE); } static int is_pcfs(fstyp_pcfs_t *h) { int error; if ((error = read_bootsec(h)) != 0) { return (error); } if (!valid_media(h)) { return (FSTYP_ERR_NO_MATCH); } if (!well_formed(h)) { return (FSTYP_ERR_NO_MATCH); } calculate_parameters(h); determine_fattype(h); get_label(h); return (0); } /* ARGSUSED */ static int dumpfs(fstyp_pcfs_t *h, FILE *fout, FILE *ferr) { (void) fprintf(fout, "Bytes Per Sector %d\t\tSectors Per Cluster %d\n", h->bps, h->bs.spcl); (void) fprintf(fout, "Reserved Sectors %d\t\tNumber of FATs %d\n", (unsigned short)PC_RESSEC(h), h->bs.nfat); (void) fprintf(fout, "Root Dir Entries %d\t\tNumber of Sectors %d\n", (unsigned short)PC_NROOTENT(h), (unsigned short)PC_NSEC(h)); (void) fprintf(fout, "Sectors Per FAT %d\t\tSectors Per Track %d\n", h->bs.fatsec, h->bs.spt); (void) fprintf(fout, "Number of Heads %d\t\tNumber Hidden Sectors %d\n", h->bs.nhead, h->bs.hiddensec); (void) fprintf(fout, "Volume ID: 0x%x\n", PC_VOLID(h)); (void) fprintf(fout, "Volume Label: %s\n", h->volume_label); (void) fprintf(fout, "Drive Number: 0x%x\n", PC_DRVNUM(h)); (void) fprintf(fout, "Media Type: 0x%x ", h->bs.mediadesriptor); switch (h->bs.mediadesriptor) { case MD_FIXED: (void) fprintf(fout, "\"Fixed\" Disk\n"); break; case SS8SPT: (void) fprintf(fout, "Single Sided, 8 Sectors Per Track\n"); break; case DS8SPT: (void) fprintf(fout, "Double Sided, 8 Sectors Per Track\n"); break; case SS9SPT: (void) fprintf(fout, "Single Sided, 9 Sectors Per Track\n"); break; case DS9SPT: (void) fprintf(fout, "Double Sided, 9 Sectors Per Track\n"); break; case DS18SPT: (void) fprintf(fout, "Double Sided, 18 Sectors Per Track\n"); break; case DS9_15SPT: (void) fprintf(fout, "Double Sided, 9-15 Sectors Per Track\n"); break; default: (void) fprintf(fout, "Unknown Media Type\n"); } return (0); } #define ADD_STRING(h, name, value) \ if (nvlist_add_string(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } #define ADD_UINT32(h, name, value) \ if (nvlist_add_uint32(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } #define ADD_UINT64(h, name, value) \ if (nvlist_add_uint64(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } #define ADD_BOOL(h, name, value) \ if (nvlist_add_boolean_value(h->attr, name, value) != 0) { \ return (FSTYP_ERR_NOMEM); \ } static int get_attr(fstyp_pcfs_t *h) { char s[64]; ADD_UINT32(h, "bytes_per_sector", h->bps); ADD_UINT32(h, "sectors_per_cluster", h->bs.spcl); ADD_UINT32(h, "reserved_sectors", PC_RESSEC(h)); ADD_UINT32(h, "fats", h->bs.nfat); ADD_UINT32(h, "root_entry_count", PC_NROOTENT(h)); ADD_UINT32(h, "total_sectors_16", PC_NSEC(h)); ADD_UINT32(h, "media", h->bs.mediadesriptor); ADD_UINT32(h, "fat_size_16", h->bs.fatsec); ADD_UINT32(h, "sectors_per_track", h->bs.spt); ADD_UINT32(h, "heads", h->bs.nhead); ADD_UINT32(h, "hidden_sectors", h->bs.hiddensec); ADD_UINT32(h, "total_sectors_32", h->bs.totalsec); ADD_UINT32(h, "drive_number", PC_DRVNUM(h)); ADD_UINT32(h, "volume_id", PC_VOLID(h)); ADD_STRING(h, "volume_label", h->volume_label); if (FSTYP_IS_32(h)) { ADD_UINT32(h, "fat_size_32", h->bs32.f_fatlength); } ADD_UINT32(h, "total_sectors", h->TotSec); ADD_UINT32(h, "fat_size", h->FATSz); ADD_UINT32(h, "count_of_clusters", h->CountOfClusters); ADD_UINT32(h, "fat_entry_size", h->fattype); ADD_BOOL(h, "gen_clean", B_TRUE); if (PC_VOLID(a) != 0) { (void) snprintf(s, sizeof (s), "%08x", PC_VOLID(a)); ADD_STRING(h, "gen_guid", s); } (void) snprintf(s, sizeof (s), "%d", h->fattype); ADD_STRING(h, "gen_version", s); ADD_STRING(h, "gen_volume_label", h->volume_label); return (0); }