mirror of
https://github.com/wiiu-env/libfat.git
synced 2024-11-25 19:36:52 +01:00
456 lines
13 KiB
C
456 lines
13 KiB
C
/*
|
|
partition.c
|
|
Functions for mounting and dismounting partitions
|
|
on various block devices.
|
|
|
|
Copyright (c) 2006 Michael "Chishm" Chisholm
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation and/or
|
|
other materials provided with the distribution.
|
|
3. The name of the author may not be used to endorse or promote products derived
|
|
from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
2006-07-11 - Chishm
|
|
* Original release
|
|
*/
|
|
|
|
|
|
#include "partition.h"
|
|
#include "bit_ops.h"
|
|
#include "file_allocation_table.h"
|
|
#include "directory.h"
|
|
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "mem_allocate.h"
|
|
|
|
/*
|
|
This device name, as known by DevKitPro
|
|
*/
|
|
const char* DEVICE_NAME = "fat";
|
|
|
|
/*
|
|
Data offsets
|
|
*/
|
|
|
|
// BIOS Parameter Block offsets
|
|
enum BPB {
|
|
BPB_jmpBoot = 0x00,
|
|
BPB_OEMName = 0x03,
|
|
// BIOS Parameter Block
|
|
BPB_bytesPerSector = 0x0B,
|
|
BPB_sectorsPerCluster = 0x0D,
|
|
BPB_reservedSectors = 0x0E,
|
|
BPB_numFATs = 0x10,
|
|
BPB_rootEntries = 0x11,
|
|
BPB_numSectorsSmall = 0x13,
|
|
BPB_mediaDesc = 0x15,
|
|
BPB_sectorsPerFAT = 0x16,
|
|
BPB_sectorsPerTrk = 0x18,
|
|
BPB_numHeads = 0x1A,
|
|
BPB_numHiddenSectors = 0x1C,
|
|
BPB_numSectors = 0x20,
|
|
// Ext BIOS Parameter Block for FAT16
|
|
BPB_FAT16_driveNumber = 0x24,
|
|
BPB_FAT16_reserved1 = 0x25,
|
|
BPB_FAT16_extBootSig = 0x26,
|
|
BPB_FAT16_volumeID = 0x27,
|
|
BPB_FAT16_volumeLabel = 0x2B,
|
|
BPB_FAT16_fileSysType = 0x36,
|
|
// Bootcode
|
|
BPB_FAT16_bootCode = 0x3E,
|
|
// FAT32 extended block
|
|
BPB_FAT32_sectorsPerFAT32 = 0x24,
|
|
BPB_FAT32_extFlags = 0x28,
|
|
BPB_FAT32_fsVer = 0x2A,
|
|
BPB_FAT32_rootClus = 0x2C,
|
|
BPB_FAT32_fsInfo = 0x30,
|
|
BPB_FAT32_bkBootSec = 0x32,
|
|
// Ext BIOS Parameter Block for FAT32
|
|
BPB_FAT32_driveNumber = 0x40,
|
|
BPB_FAT32_reserved1 = 0x41,
|
|
BPB_FAT32_extBootSig = 0x42,
|
|
BPB_FAT32_volumeID = 0x43,
|
|
BPB_FAT32_volumeLabel = 0x47,
|
|
BPB_FAT32_fileSysType = 0x52,
|
|
// Bootcode
|
|
BPB_FAT32_bootCode = 0x5A,
|
|
BPB_bootSig_55 = 0x1FE,
|
|
BPB_bootSig_AA = 0x1FF
|
|
};
|
|
|
|
|
|
#ifdef NDS
|
|
#define MAXIMUM_PARTITIONS 4
|
|
PARTITION* _partitions[MAXIMUM_PARTITIONS] = {NULL};
|
|
#else // not defined NDS
|
|
#define MAXIMUM_PARTITIONS 1
|
|
PARTITION* _partitions[MAXIMUM_PARTITIONS] = {NULL};
|
|
#endif // defined NDS
|
|
|
|
// Use a single static buffer for the partitions
|
|
|
|
|
|
static PARTITION* _FAT_partition_constructor ( const IO_INTERFACE* disc, u32 cacheSize) {
|
|
PARTITION* partition;
|
|
int i;
|
|
u32 bootSector;
|
|
u8 sectorBuffer[BYTES_PER_READ] = {0};
|
|
|
|
// Read first sector of disc
|
|
if ( !_FAT_disc_readSectors (disc, 0, 1, sectorBuffer)) {
|
|
return NULL;
|
|
}
|
|
|
|
// Make sure it is a valid MBR or boot sector
|
|
if ( (sectorBuffer[BPB_bootSig_55] != 0x55) || (sectorBuffer[BPB_bootSig_AA] != 0xAA)) {
|
|
return NULL;
|
|
}
|
|
|
|
// Check if there is a FAT string, which indicates this is a boot sector
|
|
if ((sectorBuffer[0x36] == 'F') && (sectorBuffer[0x37] == 'A') && (sectorBuffer[0x38] == 'T')) {
|
|
bootSector = 0;
|
|
} else if ((sectorBuffer[0x52] == 'F') && (sectorBuffer[0x53] == 'A') && (sectorBuffer[0x54] == 'T')) {
|
|
// Check for FAT32
|
|
bootSector = 0;
|
|
} else {
|
|
// This is an MBR
|
|
// Find first valid partition from MBR
|
|
// First check for an active partition
|
|
for (i=0x1BE; (i < 0x1FE) && (sectorBuffer[i] != 0x80); i+= 0x10);
|
|
// If it didn't find an active partition, search for any valid partition
|
|
if (i == 0x1FE) {
|
|
for (i=0x1BE; (i < 0x1FE) && (sectorBuffer[i+0x04] == 0x00); i+= 0x10);
|
|
}
|
|
|
|
// Go to first valid partition
|
|
if ( i != 0x1FE) {
|
|
// Make sure it found a partition
|
|
bootSector = u8array_to_u32(sectorBuffer, 0x8 + i);
|
|
} else {
|
|
bootSector = 0; // No partition found, assume this is a MBR free disk
|
|
}
|
|
}
|
|
|
|
// Read in boot sector
|
|
if ( !_FAT_disc_readSectors (disc, bootSector, 1, sectorBuffer)) {
|
|
return NULL;
|
|
}
|
|
|
|
partition = (PARTITION*) _FAT_mem_allocate (sizeof(PARTITION));
|
|
if (partition == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
// Set partition's disc interface
|
|
partition->disc = disc;
|
|
|
|
// Store required information about the file system
|
|
partition->fat.sectorsPerFat = u8array_to_u16(sectorBuffer, BPB_sectorsPerFAT);
|
|
if (partition->fat.sectorsPerFat == 0) {
|
|
partition->fat.sectorsPerFat = u8array_to_u32( sectorBuffer, BPB_FAT32_sectorsPerFAT32);
|
|
}
|
|
|
|
partition->numberOfSectors = u8array_to_u16( sectorBuffer, BPB_numSectorsSmall);
|
|
if (partition->numberOfSectors == 0) {
|
|
partition->numberOfSectors = u8array_to_u32( sectorBuffer, BPB_numSectors);
|
|
}
|
|
|
|
partition->bytesPerSector = BYTES_PER_READ; // Sector size is redefined to be 512 bytes
|
|
partition->sectorsPerCluster = sectorBuffer[BPB_sectorsPerCluster] * u8array_to_u16(sectorBuffer, BPB_bytesPerSector) / BYTES_PER_READ;
|
|
partition->bytesPerCluster = partition->bytesPerSector * partition->sectorsPerCluster;
|
|
partition->fat.fatStart = bootSector + u8array_to_u16(sectorBuffer, BPB_reservedSectors);
|
|
|
|
partition->rootDirStart = partition->fat.fatStart + (sectorBuffer[BPB_numFATs] * partition->fat.sectorsPerFat);
|
|
partition->dataStart = partition->rootDirStart + (( u8array_to_u16(sectorBuffer, BPB_rootEntries) * DIR_ENTRY_DATA_SIZE) / partition->bytesPerSector);
|
|
|
|
partition->totalSize = (partition->numberOfSectors - partition->dataStart) * partition->bytesPerSector;
|
|
|
|
// Store info about FAT
|
|
partition->fat.lastCluster = (partition->numberOfSectors - partition->dataStart) / partition->sectorsPerCluster;
|
|
partition->fat.firstFree = CLUSTER_FIRST;
|
|
|
|
if (partition->fat.lastCluster < CLUSTERS_PER_FAT12) {
|
|
partition->filesysType = FS_FAT12; // FAT12 volume
|
|
} else if (partition->fat.lastCluster < CLUSTERS_PER_FAT16) {
|
|
partition->filesysType = FS_FAT16; // FAT16 volume
|
|
} else {
|
|
partition->filesysType = FS_FAT32; // FAT32 volume
|
|
}
|
|
|
|
if (partition->filesysType != FS_FAT32) {
|
|
partition->rootDirCluster = FAT16_ROOT_DIR_CLUSTER;
|
|
} else {
|
|
// Set up for the FAT32 way
|
|
partition->rootDirCluster = u8array_to_u32(sectorBuffer, BPB_FAT32_rootClus);
|
|
// Check if FAT mirroring is enabled
|
|
if (!(sectorBuffer[BPB_FAT32_extFlags] & 0x80)) {
|
|
// Use the active FAT
|
|
partition->fat.fatStart = partition->fat.fatStart + ( partition->fat.sectorsPerFat * (sectorBuffer[BPB_FAT32_extFlags] & 0x0F));
|
|
}
|
|
}
|
|
|
|
// Create a cache to use
|
|
partition->cache = _FAT_cache_constructor (cacheSize, partition->disc);
|
|
|
|
// Set current directory to the root
|
|
partition->cwdCluster = partition->rootDirCluster;
|
|
|
|
// Check if this disc is writable, and set the readOnly property appropriately
|
|
partition->readOnly = !(_FAT_disc_features(disc) & FEATURE_MEDIUM_CANWRITE);
|
|
|
|
// There are currently no open files on this partition
|
|
partition->openFileCount = 0;
|
|
|
|
return partition;
|
|
}
|
|
|
|
static void _FAT_partition_destructor (PARTITION* partition) {
|
|
_FAT_cache_destructor (partition->cache);
|
|
_FAT_disc_shutdown (partition->disc);
|
|
_FAT_mem_free (partition);
|
|
}
|
|
|
|
bool _FAT_partition_mount (PARTITION_INTERFACE partitionNumber, u32 cacheSize) {
|
|
#ifdef NDS
|
|
int i;
|
|
const IO_INTERFACE* disc = NULL;
|
|
|
|
if (_partitions[partitionNumber] != NULL) {
|
|
return false;
|
|
}
|
|
|
|
switch (partitionNumber) {
|
|
case PI_SLOT_1:
|
|
// Mount the disc in slot 1
|
|
disc = _FAT_disc_dsSlotFindInterface ();
|
|
break;
|
|
case PI_SLOT_2:
|
|
// Mount the disc in slot 2
|
|
disc = _FAT_disc_gbaSlotFindInterface ();
|
|
break;
|
|
case PI_DEFAULT:
|
|
case PI_CUSTOM:
|
|
default:
|
|
// Anything else has to be handled specially
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
if (disc == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// See if that disc is already in use, if so, then just copy the partition pointer
|
|
for (i = 0; i < MAXIMUM_PARTITIONS; i++) {
|
|
if ((_partitions[i] != NULL) && (_partitions[i]->disc == disc)) {
|
|
_partitions[partitionNumber] = _partitions[i];
|
|
return true;
|
|
}
|
|
}
|
|
|
|
_partitions[partitionNumber] = _FAT_partition_constructor (disc, cacheSize);
|
|
|
|
if (_partitions[partitionNumber] == NULL) {
|
|
return false;
|
|
}
|
|
|
|
#else // not defined NDS
|
|
const IO_INTERFACE* disc = NULL;
|
|
|
|
if (_partitions[partitionNumber] != NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Only ever one partition on GBA
|
|
disc = _FAT_disc_gbaSlotFindInterface ();
|
|
_partitions[partitionNumber] = _FAT_partition_constructor (disc, cacheSize);
|
|
|
|
#endif // defined NDS
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _FAT_partition_mountCustomInterface (const IO_INTERFACE* device, u32 cacheSize) {
|
|
#ifdef NDS
|
|
int i;
|
|
|
|
if (_partitions[PI_CUSTOM] != NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (device == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// See if that disc is already in use, if so, then just copy the partition pointer
|
|
for (i = 0; i < MAXIMUM_PARTITIONS; i++) {
|
|
if ((_partitions[i] != NULL) && (_partitions[i]->disc == device)) {
|
|
_partitions[PI_CUSTOM] = _partitions[i];
|
|
return true;
|
|
}
|
|
}
|
|
|
|
_partitions[PI_CUSTOM] = _FAT_partition_constructor (device, cacheSize);
|
|
|
|
if (_partitions[PI_CUSTOM] == NULL) {
|
|
return false;
|
|
}
|
|
|
|
#else // not defined NDS
|
|
if (_partitions[PI_CART_SLOT] != NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (device == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Only ever one partition on GBA
|
|
_partitions[PI_CART_SLOT] = _FAT_partition_constructor (device, cacheSize);
|
|
|
|
#endif // defined NDS
|
|
|
|
return true;
|
|
}
|
|
|
|
bool _FAT_partition_setDefaultInterface (PARTITION_INTERFACE partitionNumber) {
|
|
#ifdef NDS // Can only set the default partition when there is more than 1, so doesn't apply to GBA
|
|
if ((partitionNumber < 1) || (partitionNumber >= MAXIMUM_PARTITIONS)) {
|
|
return false;
|
|
}
|
|
|
|
if (_partitions[partitionNumber] == NULL) {
|
|
return false;
|
|
}
|
|
|
|
_partitions[PI_DEFAULT] = _partitions[partitionNumber];
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool _FAT_partition_setDefaultPartition (PARTITION* partition) {
|
|
#ifdef NDS // Can only set the default partition when there is more than 1, so doesn't apply to GBA
|
|
int i;
|
|
|
|
if (partition == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Ensure that this device is already in the list
|
|
for (i = 1; i < MAXIMUM_PARTITIONS; i++) {
|
|
if (_partitions[i] == partition) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// It wasn't in the list, so fail
|
|
if (i == MAXIMUM_PARTITIONS) {
|
|
return false;
|
|
}
|
|
|
|
// Change the default partition / device to this one
|
|
_partitions[PI_DEFAULT] = partition;
|
|
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool _FAT_partition_unmount (PARTITION_INTERFACE partitionNumber) {
|
|
int i;
|
|
PARTITION* partition = _partitions[partitionNumber];
|
|
|
|
if (partition == NULL) {
|
|
return false;
|
|
}
|
|
|
|
if (partition->openFileCount > 0) {
|
|
// There are still open files that need closing
|
|
return false;
|
|
}
|
|
|
|
// Remove all references to this partition
|
|
for (i = 0; i < MAXIMUM_PARTITIONS; i++) {
|
|
if (_partitions[i] == partition) {
|
|
_partitions[i] = NULL;
|
|
}
|
|
}
|
|
|
|
_FAT_partition_destructor (partition);
|
|
return true;
|
|
}
|
|
|
|
bool _FAT_partition_unsafeUnmount (PARTITION_INTERFACE partitionNumber) {
|
|
int i;
|
|
PARTITION* partition = _partitions[partitionNumber];
|
|
|
|
if (partition == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Remove all references to this partition
|
|
for (i = 0; i < MAXIMUM_PARTITIONS; i++) {
|
|
if (_partitions[i] == partition) {
|
|
_partitions[i] = NULL;
|
|
}
|
|
}
|
|
|
|
_FAT_cache_invalidate (partition->cache);
|
|
_FAT_partition_destructor (partition);
|
|
return true;
|
|
}
|
|
|
|
PARTITION* _FAT_partition_getPartitionFromPath (const char* path) {
|
|
#ifdef NDS
|
|
int namelen;
|
|
int partitionNumber;
|
|
|
|
// Device name extraction code taken from DevKitPro
|
|
namelen = strlen(DEVICE_NAME);
|
|
if( strncmp(DEVICE_NAME, path, namelen) == 0 ) {
|
|
if ( path[namelen] == ':' ) {
|
|
// Only the device name is specified
|
|
partitionNumber = PI_DEFAULT;
|
|
} else if (isdigit(path[namelen]) && path[namelen+1] ==':' ) {
|
|
// Device name and number specified
|
|
partitionNumber = path[namelen] - '0';
|
|
} else {
|
|
// Incorrect device name
|
|
return NULL;
|
|
}
|
|
} else if (strchr (path, ':') == NULL) {
|
|
// No device specified
|
|
partitionNumber = PI_DEFAULT;
|
|
} else {
|
|
// Incorrect device name
|
|
return NULL;
|
|
}
|
|
|
|
if ((partitionNumber < 0) || (partitionNumber >= MAXIMUM_PARTITIONS)) {
|
|
return NULL;
|
|
}
|
|
|
|
return _partitions[partitionNumber];
|
|
#else // not defined NDS
|
|
// Only one possible partition on GBA
|
|
return _partitions[PI_CART_SLOT];
|
|
#endif // defined NDS
|
|
}
|