📄 dosfs.c
字号:
/*
DOSFS Embedded FAT-Compatible Filesystem
(C) 2005 Lewin A.R.W. Edwards (sysadm@zws.com)
You are permitted to modify and/or use this code in your own projects without
payment of royalty, regardless of the license(s) you choose for those projects.
You cannot re-copyright or restrict use of the code as released by Lewin Edwards.
*/
#include <string.h>
#include <stdlib.h>
#include "dosfs.h"
/*
Get starting sector# of specified partition on drive #unit
NOTE: This code ASSUMES an MBR on the disk.
scratchsector should point to a SECTOR_SIZE scratch area
Returns 0xffffffff for any error.
If pactive is non-NULL, this function also returns the partition active flag.
If pptype is non-NULL, this function also returns the partition type.
If psize is non-NULL, this function also returns the partition size.
*/
uint32_t DFS_GetPtnStart(uint8_t unit, uint8_t *scratchsector, uint8_t pnum, uint8_t *pactive, uint8_t *pptype, uint32_t *psize)
{
uint32_t result;
PMBR mbr = (PMBR) scratchsector;
// DOS ptable supports maximum 4 partitions
if (pnum > 3)
return DFS_ERRMISC;
// Read MBR from target media
if (DFS_ReadSector(unit,scratchsector,0,1)) {
return DFS_ERRMISC;
}
result = (uint32_t) mbr->ptable[pnum].start_0 |
(((uint32_t) mbr->ptable[pnum].start_1) << 8) |
(((uint32_t) mbr->ptable[pnum].start_2) << 16) |
(((uint32_t) mbr->ptable[pnum].start_3) << 24);
if (pactive)
*pactive = mbr->ptable[pnum].active;
if (pptype)
*pptype = mbr->ptable[pnum].type;
if (psize)
*psize = (uint32_t) mbr->ptable[pnum].size_0 |
(((uint32_t) mbr->ptable[pnum].size_1) << 8) |
(((uint32_t) mbr->ptable[pnum].size_2) << 16) |
(((uint32_t) mbr->ptable[pnum].size_3) << 24);
return result;
}
/*
Retrieve volume info from BPB and store it in a VOLINFO structure
You must provide the unit and starting sector of the filesystem, and
a pointer to a sector buffer for scratch
Attempts to read BPB and glean information about the FS from that.
Returns 0 OK, nonzero for any error.
*/
uint32_t DFS_GetVolInfo(uint8_t unit, uint8_t *scratchsector, uint32_t startsector, PVOLINFO volinfo)
{
PLBR lbr = (PLBR) scratchsector;
volinfo->unit = unit;
volinfo->startsector = startsector;
if(DFS_ReadSector(unit,scratchsector,startsector,1))
return DFS_ERRMISC;
// tag: OEMID, refer dosfs.h
// strncpy(volinfo->oemid, lbr->oemid, 8);
// volinfo->oemid[8] = 0;
volinfo->secperclus = lbr->bpb.secperclus;
volinfo->reservedsecs = (uint16_t) lbr->bpb.reserved_l |
(((uint16_t) lbr->bpb.reserved_h) << 8);
volinfo->numsecs = (uint16_t) lbr->bpb.sectors_s_l |
(((uint16_t) lbr->bpb.sectors_s_h) << 8);
if (!volinfo->numsecs)
volinfo->numsecs = (uint32_t) lbr->bpb.sectors_l_0 |
(((uint32_t) lbr->bpb.sectors_l_1) << 8) |
(((uint32_t) lbr->bpb.sectors_l_2) << 16) |
(((uint32_t) lbr->bpb.sectors_l_3) << 24);
// If secperfat is 0, we must be in a FAT32 volume; get secperfat
// from the FAT32 EBPB. The volume label and system ID string are also
// in different locations for FAT12/16 vs FAT32.
volinfo->secperfat = (uint16_t) lbr->bpb.secperfat_l |
(((uint16_t) lbr->bpb.secperfat_h) << 8);
if (!volinfo->secperfat) {
volinfo->secperfat = (uint32_t) lbr->ebpb.ebpb32.fatsize_0 |
(((uint32_t) lbr->ebpb.ebpb32.fatsize_1) << 8) |
(((uint32_t) lbr->ebpb.ebpb32.fatsize_2) << 16) |
(((uint32_t) lbr->ebpb.ebpb32.fatsize_3) << 24);
memcpy(volinfo->label, lbr->ebpb.ebpb32.label, 11);
volinfo->label[11] = 0;
// tag: OEMID, refer dosfs.h
// memcpy(volinfo->system, lbr->ebpb.ebpb32.system, 8);
// volinfo->system[8] = 0;
}
else {
memcpy(volinfo->label, lbr->ebpb.ebpb.label, 11);
volinfo->label[11] = 0;
// tag: OEMID, refer dosfs.h
// memcpy(volinfo->system, lbr->ebpb.ebpb.system, 8);
// volinfo->system[8] = 0;
}
// note: if rootentries is 0, we must be in a FAT32 volume.
volinfo->rootentries = (uint16_t) lbr->bpb.rootentries_l |
(((uint16_t) lbr->bpb.rootentries_h) << 8);
// after extracting raw info we perform some useful precalculations
volinfo->fat1 = startsector + volinfo->reservedsecs;
// The calculation below is designed to round up the root directory size for FAT12/16
// and to simply ignore the root directory for FAT32, since it's a normal, expandable
// file in that situation.
if (volinfo->rootentries) {
volinfo->rootdir = volinfo->fat1 + (volinfo->secperfat * 2);
volinfo->dataarea = volinfo->rootdir + (((volinfo->rootentries * 32) + (SECTOR_SIZE - 1)) / SECTOR_SIZE);
}
else {
volinfo->dataarea = volinfo->fat1 + (volinfo->secperfat * 2);
volinfo->rootdir = (uint32_t) lbr->ebpb.ebpb32.root_0 |
(((uint32_t) lbr->ebpb.ebpb32.root_1) << 8) |
(((uint32_t) lbr->ebpb.ebpb32.root_2) << 16) |
(((uint32_t) lbr->ebpb.ebpb32.root_3) << 24);
}
// Calculate number of clusters in data area and infer FAT type from this information.
volinfo->numclusters = (volinfo->numsecs - volinfo->dataarea) / volinfo->secperclus;
if (volinfo->numclusters < 4085)
volinfo->filesystem = FAT12;
else if (volinfo->numclusters < 65525)
volinfo->filesystem = FAT16;
else
volinfo->filesystem = FAT32;
return DFS_OK;
}
/*
Fetch FAT entry for specified cluster number
You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO
Returns a FAT32 BAD_CLUSTER value for any error, otherwise the contents of the desired
FAT entry.
scratchcache should point to a UINT32. This variable caches the physical sector number
last read into the scratch buffer for performance enhancement reasons.
*/
uint32_t DFS_GetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster)
{
uint32_t offset, sector, result;
if (volinfo->filesystem == FAT12) {
offset = cluster + (cluster / 2);
}
else if (volinfo->filesystem == FAT16) {
offset = cluster * 2;
}
else if (volinfo->filesystem == FAT32) {
offset = cluster * 4;
}
else
return 0x0ffffff7; // FAT32 bad cluster
// at this point, offset is the BYTE offset of the desired sector from the start
// of the FAT. Calculate the physical sector containing this FAT entry.
sector = ldiv(offset, SECTOR_SIZE).quot + volinfo->fat1;
// If this is not the same sector we last read, then read it into RAM
if (sector != *scratchcache) {
if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
// avoid anyone assuming that this cache value is still valid, which
// might cause disk corruption
*scratchcache = 0;
return 0x0ffffff7; // FAT32 bad cluster
}
*scratchcache = sector;
}
// At this point, we "merely" need to extract the relevant entry.
// This is easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
// may span a sector boundary. The normal way around this is always to read two
// FAT sectors, but that luxury is (by design intent) unavailable to DOSFS.
offset = ldiv(offset, SECTOR_SIZE).rem;
if (volinfo->filesystem == FAT12) {
// Special case for sector boundary - Store last byte of current sector.
// Then read in the next sector and put the first byte of that sector into
// the high byte of result.
if (offset == SECTOR_SIZE - 1) {
result = (uint32_t) scratch[offset];
sector++;
if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
// avoid anyone assuming that this cache value is still valid, which
// might cause disk corruption
*scratchcache = 0;
return 0x0ffffff7; // FAT32 bad cluster
}
*scratchcache = sector;
// Thanks to Claudio Leonel for pointing out this missing line.
result |= ((uint32_t) scratch[0]) << 8;
}
else {
result = (uint32_t) scratch[offset] |
((uint32_t) scratch[offset+1]) << 8;
}
if (cluster & 1)
result = result >> 4;
else
result = result & 0xfff;
}
else if (volinfo->filesystem == FAT16) {
result = (uint32_t) scratch[offset] |
((uint32_t) scratch[offset+1]) << 8;
}
else if (volinfo->filesystem == FAT32) {
result = ((uint32_t) scratch[offset] |
((uint32_t) scratch[offset+1]) << 8 |
((uint32_t) scratch[offset+2]) << 16 |
((uint32_t) scratch[offset+3]) << 24) & 0x0fffffff;
}
else
result = 0x0ffffff7; // FAT32 bad cluster
return result;
}
/*
Set FAT entry for specified cluster number
You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO
Returns DFS_ERRMISC for any error, otherwise DFS_OK
scratchcache should point to a UINT32. This variable caches the physical sector number
last read into the scratch buffer for performance enhancement reasons.
NOTE: This code is HIGHLY WRITE-INEFFICIENT, particularly for flash media. Considerable
performance gains can be realized by caching the sector. However this is difficult to
achieve on FAT12 without requiring 2 sector buffers of scratch space, and it is a design
requirement of this code to operate on a single 512-byte scratch.
If you are operating DOSFS over flash, you are strongly advised to implement a writeback
cache in your physical I/O driver. This will speed up your code significantly and will
also conserve power and flash write life.
*/
uint32_t DFS_SetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster, uint32_t new_contents)
{
uint32_t offset, sector, result;
if (volinfo->filesystem == FAT12) {
offset = cluster + (cluster / 2);
new_contents &=0xfff;
}
else if (volinfo->filesystem == FAT16) {
offset = cluster * 2;
new_contents &=0xffff;
}
else if (volinfo->filesystem == FAT32) {
offset = cluster * 4;
new_contents &=0x0fffffff; // FAT32 is really "FAT28"
}
else
return DFS_ERRMISC;
// at this point, offset is the BYTE offset of the desired sector from the start
// of the FAT. Calculate the physical sector containing this FAT entry.
sector = ldiv(offset, SECTOR_SIZE).quot + volinfo->fat1;
// If this is not the same sector we last read, then read it into RAM
if (sector != *scratchcache) {
if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
// avoid anyone assuming that this cache value is still valid, which
// might cause disk corruption
*scratchcache = 0;
return DFS_ERRMISC;
}
*scratchcache = sector;
}
// At this point, we "merely" need to extract the relevant entry.
// This is easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
// may span a sector boundary. The normal way around this is always to read two
// FAT sectors, but that luxury is (by design intent) unavailable to DOSFS.
offset = ldiv(offset, SECTOR_SIZE).rem;
if (volinfo->filesystem == FAT12) {
// If this is an odd cluster, pre-shift the desired new contents 4 bits to
// make the calculations below simpler
if (cluster & 1)
new_contents = new_contents << 4;
// Special case for sector boundary
if (offset == SECTOR_SIZE - 1) {
// Odd cluster: High 12 bits being set
if (cluster & 1) {
scratch[offset] = (scratch[offset] & 0x0f) | new_contents & 0xf0;
}
// Even cluster: Low 12 bits being set
else {
scratch[offset] = new_contents & 0xff;
}
result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
// mirror the FAT into copy 2
if (DFS_OK == result)
result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
// If we wrote that sector OK, then read in the subsequent sector
// and poke the first byte with the remainder of this FAT entry.
if (DFS_OK == result) {
*scratchcache++;
result = DFS_ReadSector(volinfo->unit, scratch, *scratchcache, 1);
if (DFS_OK == result) {
// Odd cluster: High 12 bits being set
if (cluster & 1) {
scratch[0] = new_contents & 0xff00;
}
// Even cluster: Low 12 bits being set
else {
scratch[0] = (scratch[0] & 0xf0) | new_contents & 0x0f;
}
result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
// mirror the FAT into copy 2
if (DFS_OK == result)
result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
}
else {
// avoid anyone assuming that this cache value is still valid, which
// might cause disk corruption
*scratchcache = 0;
}
}
} // if (offset == SECTOR_SIZE - 1)
// Not a sector boundary. But we still have to worry about if it's an odd
// or even cluster number.
else {
// Odd cluster: High 12 bits being set
if (cluster & 1) {
scratch[offset] = (scratch[offset] & 0x0f) | new_contents & 0xf0;
scratch[offset+1] = new_contents & 0xff00;
}
// Even cluster: Low 12 bits being set
else {
scratch[offset] = new_contents & 0xff;
scratch[offset+1] = (scratch[offset+1] & 0xf0) | new_contents & 0x0f;
}
result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
// mirror the FAT into copy 2
if (DFS_OK == result)
result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
}
}
else if (volinfo->filesystem == FAT16) {
scratch[offset] = (new_contents & 0xff);
scratch[offset+1] = (new_contents & 0xff00) >> 8;
result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
// mirror the FAT into copy 2
if (DFS_OK == result)
result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
}
else if (volinfo->filesystem == FAT32) {
scratch[offset] = (new_contents & 0xff);
scratch[offset+1] = (new_contents & 0xff00) >> 8;
scratch[offset+2] = (new_contents & 0xff0000) >> 16;
scratch[offset+3] = (scratch[offset+3] & 0xf0) | ((new_contents & 0x0f000000) >> 24);
// Note well from the above: Per Microsoft's guidelines we preserve the upper
// 4 bits of the FAT32 cluster value. It's unclear what these bits will be used
// for; in every example I've encountered they are always zero.
result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
// mirror the FAT into copy 2
if (DFS_OK == result)
result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
}
else
result = DFS_ERRMISC;
return result;
}
/*
Convert a filename element from canonical (8.3) to directory entry (11) form
src must point to the first non-separator character.
dest must point to a 12-byte buffer.
*/
uint8_t *DFS_CanonicalToDir(uint8_t *dest, uint8_t *src)
{
uint8_t *destptr = dest;
memset(dest, ' ', 11);
dest[11] = 0;
while (*src && (*src != DIR_SEPARATOR) && (destptr - dest < 11)) {
if (*src >= 'a' && *src <='z') {
*destptr++ = (*src - 'a') + 'A';
src++;
}
else if (*src == '.') {
src++;
destptr = dest + 8;
}
else {
*destptr++ = *src++;
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -