singrdk/base/boot/Singldr/fatdevice.cpp

591 lines
19 KiB
C++
Raw Normal View History

2008-03-05 09:52:00 -05:00
//////////////////////////////////////////////////////////////////////////////
//
// fatdevice.cpp - Access Fat16/32 volumes from SINGLDR
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#include "fatdevice.h"
#include "fnames.h"
#pragma warning(disable: 4505) // Compiler generated constructor unreferenced
//////////////////////////////////////////////////////////////////////////////
//
// Private Functions for reading the disk
// read the FAT to find the cluster that follows currentCluster
uint32 FatDevice::CalcNextCluster(uint32 currentCluster) __far
{
uint32 SectorToRead, OffsetInSector;
uint32 result32;
uint16 result16;
OffsetInSector = (currentCluster*FatOffsetMultiplier) % (BytesPerSec);
SectorToRead = ((currentCluster*FatOffsetMultiplier) / (BytesPerSec)) + (RsvdSecs) + (LBAStart);
uint8 __far * buffer = FatBuffer;
BiosDiskRead(buffer, SectorToRead, 1, BootDrive);
if (FatType == 32) {
result32 = *((uint32 __far *)(buffer+OffsetInSector));
result32 = result32 & 0x0FFFFFFF; // make it 28-bits
}
else {
result16 = *((uint16 __far *)(buffer+OffsetInSector));
result32 = result16; // implicit cast
}
return result32;
}
// read cluster # ClusterNum into a (pre-allocated) buffer
void FatDevice::ReadCluster(uint32 ClusterNum, uint8 __far * buffer) __far
{
// figure out the first sector:
uint32 startingSector = ((ClusterNum - 2) * SecsPerClus) + FirstDataSec + LBAStart;
// do the read
BiosDiskRead(buffer, startingSector, SecsPerClus, BootDrive);
}
//////////////////////////////////////////////////////////////////////////////
//
// Private Functions for matching filenames
// match a short filename (8.3, dot implicit)
int FatShortNameMatch(uint8 __far * buffer, LPCHAR filename)
{
int counter = 0;
// check up to the first dot or 8 chars, whatever comes first
while (counter < 8) {
if (*filename == '.' || *filename == 0) {
break;
}
if (UCase(buffer[counter]) != UCase(*filename)) {
return 0;
}
counter++;
filename++;
}
// pad out to 8 chars with spaces
while (counter < 8) {
if (UCase(buffer[counter++]) != ' ') {
return 0;
}
}
if (*filename == '.') {
filename++;
}
// check the file extension
while (counter < 11) {
if (*filename == 0) {
break;
}
if (UCase(buffer[counter]) != UCase(*filename)) {
return 0;
}
filename++;
counter++;
}
// pad out to 11 chars with spaces
while (counter < 11) {
if (UCase(buffer[counter++]) != ' ') {
return 0;
}
}
return 1;
}
// match a piece of a long filename. This is case insensitive,
// and assumes ascii (high byte of 2-byte strings=0)
int FatLongNamePartialMatch(uint8 __far * buffer,
LPCHAR filename,
uint8 signature,
uint8 checksum)
{
// We don't really check 13 consecutive positions.
// These are the places to check in the dir entry.
int positions[] = {1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30};
int counter = 0;
// check entry signature in position 0
if (buffer[0] != signature) {
return 0;
}
// check FAT LFN signature in positions 11, 26, 27
if (buffer[11] != 0x0F || buffer[26] != 0 || buffer[27] != 0) {
return 0;
}
// verify checksum
if (buffer[13] != checksum) {
return 0;
}
// advance the filename to the current "segment" as indicated by signature
filename += (13 * ((signature & ~0x40)-1));
// compare the characters of the filename, and pad it out to 13
// with 0x0000 and 0xFFFF's
while (*filename != 0 && counter < 13) {
// if this char doesn't match, fail instantly
if ((UCase(buffer[positions[counter]]) != UCase(*filename)) ||
(buffer[positions[counter]+1] != 0))
{
return 0;
}
counter++;
filename++;
}
// all that's left is to check the padding
if (counter < 13) {
if ((buffer[positions[counter]] != 0) ||
buffer[positions[counter]+1] != 0)
{
return 0;
}
counter++;
while (counter < 13) {
if ((buffer[positions[counter]] != 0xFF) ||
buffer[positions[counter]+1] != 0xFF)
{
return 0;
}
counter++;
}
}
return signature;
}
// this is straight out of the Fat docs: compute the checksum for
// an 8.3 filename
uint8 FatChecksum(uint8 __far * buffer)
{
uint8 checksum = 0;
for (int i = 0; i < 11; i++) {
checksum = (uint8)
(((checksum & 1) ? 0x80 : 0) + (checksum>>1) + buffer[i]);
}
return checksum;
}
// Search from the first cluster of a directory entry to find the Fat
// encoding of a filename. We accomplish this through a FSM. Fat16RootDir
// is a special flag because Fat16 and Fat32 differ on the representation
// of a root directory. The Fat32 code treats the root directory like any
// other. The Fat16 code does not.
int FatDevice::DirLookup(LPCHAR filename,
uint8 /* len */,
FilePtr Directory,
FilePtr File,
int Fat16RootDir) __far
{
// points into the data we've loaded from disk:
uint8 __far * buffer;
// we will use the generic term "block" to refer to a readable
// entity on the disk, since the Fat16 root dir is in sectors while
// the rest of Fat is in clusters
uint32 totalBlocks, blockCounter = 0;
uint32 nextBlock, entriesPerBlock, entryCounter = 0;
if (Fat16RootDir) {
totalBlocks = RootDirSecs;
entriesPerBlock = BytesPerSec / 32;
nextBlock = RootStartSec + LBAStart;
}
else {
totalBlocks = 2; // keep this less than blockCounter
entriesPerBlock = DirEntriesPerClus;
nextBlock = Directory->FirstBlock;
}
// "segment" will refer to the portion of the LongFileName
// about which we care, since it will be in 13-char chunks
uint8 totalSegs, segCounter, segsMatched = 0;
totalSegs = FullFNameLength(filename);
totalSegs = (uint8) ((totalSegs + 12) / 13);
segCounter = totalSegs;
// long file name verification requires a checksum, as well
// as matching a special "entry signature" in byte[0] of the
// directory entry
uint8 checksum = 0, signature = 0;
// the primary loop fetches data to check
while (blockCounter < totalBlocks) {
// read some data and set a counter for when we need more data
if (Fat16RootDir) {
BiosDiskRead(FatBuffer, nextBlock, 1, BootDrive);
entryCounter = BytesPerSec / 32;
}
else {
ReadCluster(nextBlock, FatBuffer);
entryCounter = DirEntriesPerClus;
}
buffer = FatBuffer;
// the inner loop checks entries
while (entryCounter > 0) {
// failure condition #1: if the first bit is zero,
// there are no more directory entries.
if (buffer[0] == 0) {
return -1;
}
// success condition #1: do we match on 8.3?
// success condition #2: if segCounter == 0, does checksum match?
if ((FatShortNameMatch(buffer, filename) == 1) ||
(segCounter == 0 && FatChecksum(buffer) == checksum))
{
File->FirstBlock = (*((uint16 __far *)(buffer+20))<<16) +
*((uint16 __far *)(buffer+26));
File->Size = *((uint32 __far *)(buffer+28));
return 0;
}
// progress condition: do we match on the current
// segment of long file name?
// first calc checksum and the signature
if (segCounter == totalSegs) {
signature = (uint8) (segCounter | 0x40);
checksum = buffer[13];
}
else {
signature = segCounter;
}
// now do the check
if (signature == FatLongNamePartialMatch(buffer, filename, signature, checksum)) {
// advance to next directory entry and earlier
// segment in the filename
segCounter--;
buffer += 32;
entryCounter--;
}
else {
// if we were checking the first LFN entry then it's
// time to advance the counter
if (segCounter == totalSegs) {
buffer += 32;
entryCounter--;
}
else {
// otherwise this might just be the start of the
// correct entry, so don't advance the counter,
// just reset to the first segment of the filename
segCounter = totalSegs;
}
}
}
// calculate next data block
if (Fat16RootDir == 1) {
nextBlock++;
blockCounter++;
}
else {
nextBlock = CalcNextCluster(nextBlock);
if (nextBlock >= (EndOfClusterMarker))
blockCounter = 2;
}
}
return -1;
}
//////////////////////////////////////////////////////////////////////////////
//
// Public Functions
int FatDevice::OpenDevice() __far
{
uint8 expectedType = FatType;
int entrycounter = 4; // up to 4 entries in partition table
// allocate memory for reading a sector (assume 512-byte sector size)
MbrBuffer = (uint8 __far *) alloc(512, 0);
// Read the MBR
BiosDiskRead(MbrBuffer, 0, 1, BootDrive);
// temp to make using far pointers easier
uint8 __far * buffer = MbrBuffer;
// search the partition table for a partition whose
// type matches our desired type
buffer += 446; // first entry is at offset 446
while (entrycounter > 0) {
// does the partition type match expectedtype
if (buffer[4] == 0x0c && expectedType == 32) {
FatType = 32;
break;
}
if (buffer[4] == 0x0e && expectedType == 16) {
FatType = 16;
break;
}// try the next entry
entrycounter--;
buffer += 16;
}
// exceptional condition, should never happen...
if (entrycounter == 0) {
printf("USB: Valid Fat partition not found\n");
return -1;
}
// now get the first sector of the active partition, and the
// partition size
LBAStart = *((uint32 __far *)(buffer+8));
LBASize = *((uint32 __far *)(buffer+12));
// we are done with the MBR. now we need to read the boot sector
// to get the FAT parameters
BiosDiskRead(MbrBuffer, LBAStart, 1, BootDrive);
buffer = MbrBuffer;
// Verify the FileSystem type:
// the Microsoft Fat32 documentation is very specific about how this ought to be done.
// However, this is rather ugly, because it requires information that may not be in the bootsector.
// For example, suppose that the volume is Fat16, and the volume is in very bad shape.
// we may read 0 for a 16-bit value, and then look in an invalid portion of the boot sector for a 32-bit value.
// UGH! Well, we'll do it nonetheless:
// read data that should always be in the same place, no matter what
BytesPerSec = *((uint16 __far *)(buffer+11));
SecsPerClus = buffer[13];
RsvdSecs = *((uint16 __far *)(buffer+14));
NumFats = buffer[16];
RootDirEntries = *((uint16 __far *)(buffer+17));
HiddenSecs = *((uint32 __far *)(buffer+28));
// compute the NumFatSecs and TotalSecs fields
// (i.e. read one value, if it is zero, read another)
uint16 tmp16;
tmp16 = *((uint16 __far *)(buffer+19));
if (tmp16 == 0) {
TotalSecs = *((uint32 __far *)(buffer+32));
}
else {
TotalSecs = tmp16;
}
tmp16 = *((uint16 __far *)(buffer+22));
if (tmp16 == 0) {
NumFatSecs = *((uint32 __far *)(buffer+36));
}
else {
NumFatSecs = tmp16;
}
// now we may compute RootDirSectors =
// ((BPB_RootEntCnt*32) + (BPB_BytsPerSec-1))/BPB_BytesPerSec
RootDirSecs = ((RootDirEntries*32) + (BytesPerSec-1))/BytesPerSec;
// Next compute the total data sectors in the volume:
TotalDataSecs = TotalSecs - (RsvdSecs + (NumFats*NumFatSecs) + RootDirSecs);
// Finally, compute the # of clusters in the volume:
ClusterCount = TotalDataSecs / SecsPerClus;
// Do the verification:
if (!(ClusterCount >= 4085 && ClusterCount < 65525 && FatType == 16) && !(ClusterCount >= 65525 && FatType == 32)) {
printf("USB: File System Type does not match Partition Table\n");
return -1; // the partition table did not match the boot sector.
// we can't trust this filesystem.
}
// now we can set fields that are FAT-type specific:
if (FatType == 16) {
RootStartClus = 0;
EndOfClusterMarker = 0xFFF8;
BadClusterMarker = 0xFFF7;
FatOffsetMultiplier = 2;
RootStartSec = (RsvdSecs + (NumFats*NumFatSecs));
}
else {
RootStartClus = *((uint32 __far *)(buffer+44));
EndOfClusterMarker = 0x0FFFFFF8;
BadClusterMarker = 0x0FFFFFF7;
FatOffsetMultiplier = 4;
}
// final computations:
FirstDataSec = (NumFats*NumFatSecs) + RsvdSecs + RootDirSecs;
BytesPerClus = SecsPerClus * BytesPerSec;
DirEntriesPerClus = BytesPerClus/32;
// allocate data for remaining fs buffers:
FatBuffer = (uint8 __far *) alloc((uint16)BytesPerClus, 0); // exactly the size of a cluster
FileBuffer = (uint8 __far *) alloc(0x7FFF, 0); // 32KB
// display results
#if 0
printf("\nDisk Configuration\n");
printf("------------------------------\n");
printf("Boot Drive = %2xh\n", BootDrive);
printf("First Sector of Active Partition = %8lxh\n", LBAStart);
printf("Sectors in Partition = %8lxh\n", LBASize);
printf("Bytes/Sector = %4xh\n", BytesPerSec);
printf("\nFileSystem Configuration\n");
printf("------------------------------\n");
printf("Fat Type = FAT%d\n", FatType);
printf("Sectors/Cluster = %2xh\n", SecsPerClus);
printf("Reserved Sectors = %4xh\n", RsvdSecs);
printf("Number of Fats = %2xh\n", NumFats);
printf("Hidden Sectors = %8lxh\n", HiddenSecs);
printf("Sectors per Fat = %8lxh\n", NumFatSecs);
printf("First Cluster of Root Dir = %8lxh\n", RootStartClus);
printf("First Data Sector in Partition = %8lxh\n", FirstDataSec);
printf("Bytes/Cluster = %8lxh\n", BytesPerClus);
printf("Dir Entries/Cluster = %8lxh\n", DirEntriesPerClus);
#endif
return 0;
}
int FatDevice::CloseDevice() __far
{
return 0;
}
int FatDevice::GetFileProperties(LPCHAR filename,
FilePtr file,
FilePtr directory) __far
{
LPCHAR fname = filename;
uint8 len;
int result;
char c;
int Fat16RootDir = (FatType == 16); // Fat16 Root Dir is special.
// Flag 1st Fat16 read
// we'll continually recycle the File and Directory structs
file->Size = 0;
file->FirstBlock = 0;
directory->FirstBlock = RootStartClus;
// traverse through the filename, identifying tokens and
// looking them up in the current context
bool done = 0;
while (!done) {
// consume the leading '/'
while (fname[0] == '/') {
fname++;
}
// failure condition: whitespace
if (IsEndToken(fname[0])) {
printf("FAT: Invalid filename: ");
PutFName(filename);
printf("\n");
return -1;
}
// find the next separator in the filename ('/' or whitespace),
// store it, and replace it with 0
len = ShortFNameLength(fname);
c = fname[len];
fname[len] = 0;
// look it up and put the results into File
result = DirLookup(fname, len, directory, file, Fat16RootDir);
// undo the change we made to the filename:
fname[len] = c;
// now shut off the Fat16RootDir flag for subsequent directory scans
Fat16RootDir = 0;
if (result == -1) {
printf("FAT: File not found: ");
PutFName(filename);
printf("\n");
return -1;
}
// check loop termination condition
if (c != '/') {
done = 1;
}
else {
fname += len;
// transfer results from File into Directory
directory->FirstBlock = file->FirstBlock;
directory->Size = file->Size;
file->Size = 0;
file->FirstBlock = 0;
}
}
return 0;
}
INT16 FatDevice::ReadFileLow(LPCHAR /* filename */,
FilePtr file,
uint8 __far * buffer) __far
{
uint32 bytesread = 0;
uint8 __far * destination = buffer;
uint32 currentcluster = file->FirstBlock;
while (bytesread < file->Size) {
// read the current cluster
if (currentcluster == BadClusterMarker) {
printf("FAT: Bad Cluster encountered\n");
return -1;
}
ReadCluster(currentcluster, destination);
currentcluster = CalcNextCluster(currentcluster);
bytesread += BytesPerClus;
destination = (uint8 __far *) _MK_FP(_FP_SEG(destination), (_FP_OFF(destination)+BytesPerClus));
}
return 0;
}
UINT32 FatDevice::ReadFileHigh(LPCHAR /* filename */,
FilePtr file,
uint32 destinationAddress,
uint32 /* cbDestination */) __far
{
uint32 bytesread = 0;
uint32 nextdestination = destinationAddress;
uint32 currentcluster = file->FirstBlock;
uint32 sector;
uint32 bufferaddress = PointerToUint32(FileBuffer);
while (bytesread < file->Size) {
// ensure cluster is valid
if (currentcluster == BadClusterMarker) {
printf("FAT: Bad Cluster encountered\n");
return bytesread;
}
// calc true sector for this cluster
sector = ((currentcluster-2) * SecsPerClus)+ FirstDataSec + LBAStart;
// do the read
BiosDiskRead(FileBuffer, sector, SecsPerClus, BootDrive);
// move the data into extended memory
PModeTransfer(bufferaddress, nextdestination, BytesPerClus);
// get next cluster number
currentcluster = CalcNextCluster(currentcluster);
// update the count of bytes read
bytesread += BytesPerClus;
// and update the destination address
nextdestination += BytesPerClus;
}
// since we read full sectors at the bios level,
// we should trim our count back down a bit here
if (bytesread > file->Size)
bytesread = file->Size;
return bytesread;
}