#include <sys/param.h>
#include <sys/types.h>
#include <sys/mntent.h>
#include <errno.h>
#include <sys/fs/pc_fs.h>
#include <sys/fs/pc_label.h>
#include <sys/fs/pc_dir.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <sys/dkio.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/mnttab.h>
#include <locale.h>
#include <libfstyp_module.h>
#define PC_LABEL_SIZE 11
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];
ulong_t FATSz;
ulong_t TotSec;
ulong_t RootDirSectors;
ulong_t FirstDataSector;
ulong_t DataSec;
ulong_t CountOfClusters;
} fstyp_pcfs_t;
#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)
#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));
}
static int
read_bootsec(fstyp_pcfs_t *h)
{
struct dk_minfo dkminfo;
char *buf;
size_t size = PC_SECSIZE;
if (ioctl(h->fd, DKIOCGMEDIAINFO, &dkminfo) != -1) {
if (dkminfo.dki_lbsize != 0)
size = dkminfo.dki_lbsize;
}
buf = malloc(size);
if (buf == NULL)
return (FSTYP_ERR_NOMEM);
(void) lseek(h->fd, h->offset, SEEK_SET);
if (read(h->fd, buf, size) != (ssize_t)size) {
free(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));
free(buf);
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 == 0) {
h->fattype = 0;
return;
}
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)
{
(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);
}
}
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;
}
}
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);
}
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);
}
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);
}
static int
dumpfs(fstyp_pcfs_t *h, FILE *fout, FILE *ferr __unused)
{
if (h->fattype == 0)
return (FSTYP_ERR_NO_MATCH);
(void) fprintf(fout, "Filesystem type: FAT%d\n", h->fattype);
(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), h->TotSec);
(void) fprintf(fout,
"Sectors Per FAT %d\t\tSectors Per Track %d\n",
h->FATSz, 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);
}