lsvmtools/lsvmutils/vfat.c

1611 строки
36 KiB
C

/*
**==============================================================================
**
** LSVMTools
**
** MIT License
**
** Copyright (c) Microsoft Corporation. All rights reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
** SOFTWARE
**
**==============================================================================
*/
#include "vfat.h"
#include "strings.h"
#include "print.h"
#include "guid.h"
#include "alloc.h"
#include "dump.h"
#include "buf.h"
#include "print.h"
#include "chksum.h"
#define TRACE PRINTF("TRACE: %a(%d)\n", __FILE__, __LINE__)
#if 1
# define GOTO(LABEL) goto LABEL
#else
# define GOTO(LABEL) \
do \
{ \
PRINTF("GOTO: %a(%d)\n", __FILE__, __LINE__); \
goto LABEL; \
} \
while (0)
#endif
VFATType GetFATType(const VFATBPB* bpb)
{
UINT32 TotSec;
UINT32 FATSz;
UINT32 RootDirSectors;
UINT32 CountOfClusters;
UINT32 DataSec;
if (bpb->FATSz16 != 0)
FATSz = bpb->FATSz16;
else
FATSz = bpb->u.s32.FATSz32;
if (bpb->TotSec16 != 0)
TotSec = bpb->TotSec16;
else
TotSec = bpb->TotSec32;
RootDirSectors = ((bpb->RootEntCnt * 32) + (bpb->BytsPerSec - 1)) /
bpb->BytsPerSec;
DataSec = TotSec -
(bpb->ResvdSecCnt + (bpb->NumFATs * FATSz) + RootDirSectors);
CountOfClusters = DataSec / bpb->SecPerClus;
if (CountOfClusters < 4085)
return FAT12;
else if (CountOfClusters < 65525)
return FAT16;
else
return FAT32;
}
static __inline BOOLEAN __DumpCHAR16Str(const CHAR16* str, UINTN len)
{
UINTN i;
for (i = 0; i < len; i++)
{
if (str[i] == '\0')
return TRUE;
PRINTF("%c", (char)str[i]);
}
return FALSE;
}
static __inline UINT8 _VFATChkSum(const UINT8 name[11])
{
UINTN i;
UINT8 r = 0;
for (i = 0; i < 11; i++)
r = ((r & 1) ? 0x80 : 0) + (r >> 1) + name[i];
return r;
}
void DumpBPB(const VFATBPB* p)
{
PRINTF("=== VFATBPB (%u bytes)\n", (int)sizeof(VFATBPB));
PRINTF("jmpBoot[]=%02X%02X%02X\n",
p->jmpBoot[0], p->jmpBoot[1], p->jmpBoot[2]);
PRINTF("OEMName=%.*a\n", 8, p->OEMName);
PRINTF("BytsPerSec=%u\n", p->BytsPerSec);
PRINTF("SecPerClus=%u\n", p->SecPerClus);
PRINTF("ResvdSecCnt=%u\n", p->ResvdSecCnt);
PRINTF("NumFATs=%u\n", p->NumFATs);
PRINTF("RootEntCnt=%u\n", p->RootEntCnt);
PRINTF("TotSec16=%u\n", p->TotSec16);
PRINTF("Media=%u\n", p->Media);
PRINTF("FATSz16=%u\n", p->FATSz16);
PRINTF("SecPerTrk=%u\n", p->SecPerTrk);
PRINTF("NumHeads=%u\n", p->NumHeads);
PRINTF("HiddSec=%u\n", p->HiddSec);
PRINTF("TotSec32=%u\n", p->TotSec32);
if (GetFATType(p) == FAT12)
{
PRINTF("NumPHDrive=%u\n", p->u.s12.NumPHDrive);
PRINTF("Reserved0=%u\n", p->u.s12.Reserved0);
PRINTF("BootSig=%u\n", p->u.s12.BootSig);
PRINTF("NumSerial=%u\n", p->u.s12.NumSerial);
PRINTF("VolLab=%.*a\n", 11, p->u.s12.VolLab);
PRINTF("FilSysType[]=%.*a\n", 8, p->u.s12.FilSysType);
}
else if (GetFATType(p) == FAT16)
{
PRINTF("NumPHDrive=%u\n", p->u.s16.NumPHDrive);
PRINTF("Reserved0=%u\n", p->u.s16.Reserved0);
PRINTF("BootSig=%u\n", p->u.s16.BootSig);
PRINTF("NumSerial=%u\n", p->u.s16.NumSerial);
PRINTF("VolLab=%.*a\n", 11, p->u.s16.VolLab);
PRINTF("FilSysType[]=%.*a\n", 8, p->u.s16.FilSysType);
}
if (GetFATType(p) == FAT32)
{
PRINTF("FATSz32=%u\n", p->u.s32.FATSz32);
PRINTF("ExtFlags=%u\n", p->u.s32.ExtFlags);
PRINTF("FSVer=%u\n", p->u.s32.FSVer);
PRINTF("RootClus=%u\n", p->u.s32.RootClus);
PRINTF("FSInfo=%u\n", p->u.s32.FSInfo);
PRINTF("BkBootSec=%u\n", p->u.s32.BkBootSec);
PRINTF("DrvNum=%u\n", p->u.s32.DrvNum);
PRINTF("Reserved1=%u\n", p->u.s32.Reserved1);
PRINTF("BootSig=%u\n", p->u.s32.BootSig);
PRINTF("VolID=%u\n", p->u.s32.VolID);
PRINTF("VolLab=%.*a\n", 11, p->u.s32.VolLab);
PRINTF("FilSysType[]=%.*a\n", 8, p->u.s32.FilSysType);
}
}
void DumpFSInfo(const VFATFSInfo* p)
{
PRINTF("=== VFATFSInfo (%u bytes)\n", (int)sizeof(VFATFSInfo));
PRINTF("LeadSig=%08X\n", p->LeadSig);
PRINTF("StrucSig=%u\n", p->StrucSig);
PRINTF("Free_Count=%u\n", p->Free_Count);
PRINTF("Nxt_Free=%u\n", p->Nxt_Free);
PRINTF("TrailSig=%08X\n", p->TrailSig);
}
void VFATDumpDirectoryEntry(const VFATDirectoryEntry* ent)
{
PRINTF("=== VFATDirectoryEntry (%u bytes)\n",
(int)sizeof(VFATDirectoryEntry));
PRINTF("name={%.*a}\n", 11, ent->name);
PRINTF("attr=%u\n", ent->attr);
PRINTF("res=%u\n", ent->res);
PRINTF("crtTimeTenth=%u\n", ent->crtTimeTenth);
PRINTF("crtTime=%u\n", ent->crtTime);
PRINTF("crtDate=%u\n", ent->crtDate);
PRINTF("lstAccDate=%u\n", ent->lstAccDate);
PRINTF("fstClusHI=%u\n", ent->fstClusHI);
PRINTF("wrtTime=%u\n", ent->wrtTime);
PRINTF("wrtDate=%u\n", ent->wrtDate);
PRINTF("fstClusLO=%u\n", ent->fstClusLO);
PRINTF("fileSize=%u\n", ent->fileSize);
if (ent->attr & ATTR_READ_ONLY)
PRINTF("ATTR_READ_ONLY{%u}\n", ATTR_READ_ONLY);
if (ent->attr & ATTR_HIDDEN)
PRINTF("ATTR_HIDDEN{%u}\n", ATTR_HIDDEN);
if (ent->attr & ATTR_SYSTEM)
PRINTF("ATTR_SYSTEM{%u}\n", ATTR_SYSTEM);
if (ent->attr & ATTR_VOLUME_ID)
PRINTF("ATTR_VOLUME_ID{%u}\n", ATTR_VOLUME_ID);
if (ent->attr & ATTR_DIRECTORY)
PRINTF("ATTR_DIRECTORY{%u}\n", ATTR_DIRECTORY);
if (ent->attr & ATTR_ARCHIVE)
PRINTF("ATTR_ARCHIVE{%u}\n", ATTR_ARCHIVE);
}
void DumpLongDirectoryEntry(const VFATLongDirectoryEntry* ent)
{
BOOLEAN eos = FALSE;
PRINTF("=== VFATLongDirectoryEntry (%u bytes)\n",
(int)sizeof(VFATLongDirectoryEntry));
PRINTF("ord=%u (%u)\n", ent->ord, (ent->ord & ~0x40));
PRINTF0("name1={");
eos = __DumpCHAR16Str(ent->name1, ARRSIZE(ent->name1));
PRINTF0("}\n");
PRINTF("attr=%u\n", ent->attr);
PRINTF("type=%u\n", ent->type);
PRINTF("chksum=%u\n", ent->chksum);
PRINTF0("name2={");
if (!eos)
eos = __DumpCHAR16Str(ent->name2, ARRSIZE(ent->name2));
PRINTF0("}\n");
PRINTF("fstClusLO=%u\n", ent->fstClusLO);
PRINTF0("name3={");
if (!eos)
eos = __DumpCHAR16Str(ent->name3, ARRSIZE(ent->name3));
PRINTF0("}\n");
}
static __inline UINT32 _Max(UINT32 x, UINT32 y)
{
return x > y ? x : y;
}
static __inline UINT32 _FirstSectorOfCluster(const VFAT* vfat, UINT32 n)
{
return ((n - 2) * vfat->bpb.SecPerClus) + vfat->FirstDataSector;
}
static __inline UINT32 _GetFATOffset(const VFAT* vfat, UINT32 clustno)
{
UINT32 offset = 0;
if (vfat->FATType == FAT12)
offset = clustno + (clustno / 2);
else if (vfat->FATType == FAT16)
offset = clustno * 2;
else if (vfat->FATType == FAT32)
offset = clustno * 4;
return offset;
}
static __inline UINT32 GetFATEntry(const VFAT* vfat, UINT32 clustno)
{
UINT32 offset = _GetFATOffset(vfat, clustno);
const UINT8* p = (const UINT8*)vfat->fat.data + offset;
switch (vfat->FATType)
{
case FAT12:
{
UINT16 x = *((const UINT16*)p);
if (clustno & 0x0001)
x >>= 4;
else
x &= 0x0FFF;
return x;
}
case FAT16:
{
return *((const UINT16*)p);
}
case FAT32:
{
return *((const UINT32*)p) & 0x0FFFFFFF;
}
}
/* Unreachable */
return 0;
}
static void SetFATEntry(VFAT* vfat, UINT32 clustno, UINT32 x)
{
UINT32 offset = _GetFATOffset(vfat, clustno);
UINT8* p = (UINT8*)vfat->fat.data + offset;
switch (vfat->FATType)
{
case FAT12:
{
UINT16 r = *((UINT16*)p);
if (clustno & 0x0001)
{
x <<= 4;
r &= 0x000F;
}
else
{
x &= 0x0FFF;
r &= 0xF000;
}
*((UINT16*)p) = r | x;
break;
}
case FAT16:
{
*((UINT16*)p) = x;
break;
}
case FAT32:
{
/* Preserve top four bits of origninal entry */
UINT32 r = *((UINT32*)p);
*((UINT32*)p) = (r & 0xF0000000) | (x & 0x0FFFFFFF);
break;
}
}
}
static __inline UINT32 GetEOC(const VFAT* vfat)
{
switch (vfat->FATType)
{
case FAT12:
return 0x0FF8;
case FAT16:
return 0x0FF8;
case FAT32:
return 0x0FFFFFF8;
}
/* Unreachable! */
return 0;
}
static __inline BOOLEAN IsEOF(const VFAT* vfat, UINT32 x)
{
if (vfat->FATType == FAT12)
{
if (x >= 0x0FF8)
return TRUE;
}
else if (vfat->FATType == FAT16)
{
if (x >= 0xFFF8)
return TRUE;
}
else if (vfat->FATType == FAT32)
{
if (x >= 0x0FFFFFF8)
return TRUE;
}
return FALSE;
}
int ReadCluster(
const VFAT* vfat,
UINT32 clustno,
Buf* buf)
{
int rc = -1;
UINT32 sectno = _FirstSectorOfCluster(vfat, clustno);
if (BufReserve(buf, buf->size + vfat->ClusterSize) != 0)
GOTO(done);
if (BlkdevRead(
vfat->dev,
sectno,
(UINT8*)buf->data + buf->size,
vfat->ClusterSize) != 0)
{
GOTO(done);
}
buf->size += vfat->ClusterSize;
rc = 0;
done:
return rc;
}
int ReadClusters(
const VFAT* vfat,
UINT32 clustno,
Buf* buf)
{
int rc = -1;
while (clustno && !IsEOF(vfat, clustno))
{
/* Read this cluster */
if (ReadCluster(vfat, clustno, buf) != 0)
GOTO(done);
/* Advance to the next cluster */
clustno = GetFATEntry(vfat, clustno);
}
rc = 0;
done:
return rc;
}
int WriteCluster(
const VFAT* vfat,
UINT32 clustno,
const void* data)
{
int rc = -1;
UINT32 sectno = _FirstSectorOfCluster(vfat, clustno);
if (BlkdevWrite(
vfat->dev,
sectno,
data,
vfat->ClusterSize) != 0)
{
GOTO(done);
}
rc = 0;
done:
return rc;
}
int WriteClusters(
const VFAT* vfat,
UINT32 clustno,
const void* data,
UINTN size)
{
int rc = -1;
const UINT8* ptr = (const UINT8*)data;
const UINT8* end = ptr + size;
void* cluster = NULL;
while (clustno && !IsEOF(vfat, clustno))
{
/* If not enough data for an entire cluster */
if (end - ptr < vfat->ClusterSize)
{
if (!(cluster = Calloc(1, vfat->ClusterSize)))
goto done;
Memcpy(cluster, ptr, end - ptr);
if (WriteCluster(vfat, clustno, cluster) != 0)
GOTO(done);
ptr = end;
break;
}
else
{
if (WriteCluster(vfat, clustno, ptr) != 0)
GOTO(done);
ptr += vfat->ClusterSize;
}
/* Advance to the next cluster */
clustno = GetFATEntry(vfat, clustno);
}
if (ptr != end)
GOTO(done);
rc = 0;
done:
if (cluster)
Free(cluster);
return rc;
}
/* "XYZ.DAT" becomes "XYZ DAT" (not zero-terminated) */
int InternShortname(char buf[12], const char* shortname)
{
int rc = -1;
const char *dot;
UINTN i;
if ((dot = Strchr(shortname, '.')))
{
UINTN n1 = dot - shortname;
UINTN n2 = Strlen(dot + 1);
/* 8.3-format: "ABCDEFGH.IJK" */
if (n1 > 8 || n2 > 3)
GOTO(done);
Memcpy(buf, shortname, n1);
/* Pad name out to 8 characters */
for (i = n1; i < 8; i++)
buf[i] = ' ';
/* Append extension if any */
if (n2)
Memcpy(buf + 8, dot + 1, n2);
/* Pad name out to 11 characters */
for (i = 8 + n2; i < 11; i++)
buf[i] = ' ';
}
else
{
UINTN n = Strlen(shortname);
UINTN i;
if (n > 11)
GOTO(done);
Memcpy(buf, shortname, n);
for (i = n; i < 11; i++)
buf[i] = ' ';
}
/* Convert to upper case */
for (i = 0; i < 11; i++)
buf[i] = Toupper(buf[i]);
/* Null terminate */
buf[11] = '\0';
rc = 0;
done:
return rc;
}
void ExternShortname(char buf[13], const char shortname[11])
{
UINTN n = 0;
UINTN i;
BOOLEAN more = FALSE;
/* Copy over up to 8-byte filename */
for (i = 0; i < 8; i++)
{
if (shortname[i] != ' ')
buf[n++] = shortname[i];
}
/* If more non-space characters */
for (i = 8; i < 11; i++)
{
if (shortname[i] != ' ')
more = TRUE;
}
/* Copy over up to 3-byte extension */
if (more)
{
buf[n++] = '.';
for (i = 8; i < 11; i++)
{
if (shortname[i] != ' ')
buf[n++] = shortname[i];
}
}
/* Null terminate */
buf[n] = '\0';
}
int FindDirectoryEntry(
const VFAT* vfat,
const char* shortname,
const void* directoryData,
UINTN directorySize,
VFATDirectoryEntry* entry)
{
int rc = -1;
const VFATDirectoryEntry* de = (const VFATDirectoryEntry*)directoryData;
BOOLEAN found = FALSE;
char name[12];
if (!vfat || !shortname || !directoryData || !directoryData || !entry)
GOTO(done);
Memset(entry, 0, sizeof(VFATDirectoryEntry));
/* Fix up the name */
if (Strcmp(shortname, ".") == 0)
Strlcpy(name, ". ", sizeof(name));
else if (Strcmp(shortname, "..") == 0)
Strlcpy(name, ".. ", sizeof(name));
else if (InternShortname(name, shortname) != 0)
GOTO(done);
for (; de->name[0]; de++)
{
/* If exhausted data */
if ((void*)de >= (directoryData + directorySize))
GOTO(done);
if (de->name[0] != 0xE5)
{
if (de->attr != ATTR_LONG_NAME)
{
if (Memcmp(de->name, name, 11) == 0)
{
Memcpy(entry, de, sizeof(VFATDirectoryEntry));
found = TRUE;
break;
}
}
}
}
if (found)
rc = 0;
done:
return rc;
}
int VFATStatFile(
const VFAT* vfat,
const char* path,
VFATDirectoryEntry* entry)
{
int rc = -1;
char buf[VFAT_PATH_SIZE];
const char* elements[32];
const UINTN NELEMENTS = ARRSIZE(elements);
UINT8 nelements = 0;
UINT32 clustno = 0;
UINTN i;
BOOLEAN found = FALSE;
/* Check parameters */
if (!vfat || !path || !entry)
GOTO(done);
/* Reject non-absolute paths */
if (path[0] != '/')
GOTO(done);
/* Split the path into its individual elements */
{
char* p;
char* save = NULL;
/* Inject root directory */
elements[nelements++] = "/";
Strlcpy(buf, path, sizeof(buf));
/* For each element of the path */
for (p = Strtok(buf, "/", &save); p; p = Strtok(NULL, "/", &save))
{
if (nelements == NELEMENTS)
GOTO(done);
elements[nelements++] = p;
}
}
/* Load each cluster along the path until we find it */
for (i = 0; i < nelements; i++)
{
VFATDirectoryEntry ent;
if (Strcmp(elements[i], "/") == 0)
continue;
/* Search the root directory */
if (i == 1)
{
/* Search the root directory */
if (FindDirectoryEntry(
vfat,
elements[i],
vfat->rootdir.data,
vfat->rootdir.size,
&ent) != 0)
{
GOTO(done);
}
/* Convert cluster number to 32-bit */
clustno = ((UINT32)ent.fstClusHI << 16) | (UINT32)ent.fstClusLO;
}
else
{
Buf buf = BUF_INITIALIZER;
/* Read all the clusters for this directory file */
if (ReadClusters(vfat, clustno, &buf) != 0)
GOTO(done);
if (FindDirectoryEntry(
vfat,
elements[i],
buf.data,
buf.size,
&ent) != 0)
{
BufRelease(&buf);
GOTO(done);
}
/* Convert cluster number to 32-bit */
clustno = ((UINT32)ent.fstClusHI << 16) | (UINT32)ent.fstClusLO;
BufRelease(&buf);
}
/* If this was the final component, then succeess! */
if (i + 1 == nelements)
{
Memcpy(entry, &ent, sizeof(VFATDirectoryEntry));
found = TRUE;
}
}
if (found)
rc = 0;
done:
return rc;
}
int LoadFileFromDirectoryEntry(
const VFAT* vfat,
VFATDirectoryEntry* ent,
Buf* buf)
{
int rc = -1;
UINT32 clustno;
/* Check parameters */
if (!vfat || !ent || !buf)
GOTO(done);
/* Clear the buffer */
BufClear(buf);
/* Form the 32-bit cluster number */
clustno = ((UINT32)ent->fstClusHI << 16) | (UINT32)ent->fstClusLO;
/* For each cluster */
while (clustno && !IsEOF(vfat, clustno))
{
/* Read this cluster */
if (ReadCluster(vfat, clustno, buf) != 0)
GOTO(done);
/* Advance to the next cluster */
clustno = GetFATEntry(vfat, clustno);
}
/* If not able to read enough bytes */
if (buf->size < ent->fileSize)
GOTO(done);
/* Size down to file size */
if (!(ent->attr & ATTR_DIRECTORY))
buf->size = ent->fileSize;
rc = 0;
done:
return rc;
}
void DumpLayout(const VFAT* vfat)
{
UINT32 sectno = 0;
UINT32 i;
PRINTF0("=== Sector layout:\n");
PRINTF("Reserved: [offset=%u:count=%u] (includes BPB and FSI)\n",
sectno, vfat->bpb.ResvdSecCnt);
sectno += vfat->bpb.ResvdSecCnt;
for (i = 0; i < vfat->bpb.NumFATs; i++)
{
PRINTF("FAT%u: [offset=%u:count=%u]\n", i, sectno, vfat->FATSz);
sectno += vfat->FATSz;
}
PRINTF("RootDirSectors: [offset=%u:count=%u]\n",
sectno, vfat->RootDirSectors);
sectno += vfat->RootDirSectors;
PRINTF("DataSectors: [offset=%u:count=%u]\n", sectno, vfat->DataSec);
sectno += vfat->DataSec;
PRINTF("End: [offset=%u]\n", sectno);
}
int VFATDump(const VFAT* vfat)
{
int rc = -1;
if (!vfat)
GOTO(done);
DumpBPB(&vfat->bpb);
DumpFSInfo(&vfat->fsi);
PRINTF("RootDirSectors{%u}\n", vfat->RootDirSectors);
PRINTF("FATSz{%u}\n", vfat->FATSz);
PRINTF("TotSec{%u}\n", vfat->TotSec);
PRINTF("DataSec{%u}\n", vfat->DataSec);
PRINTF("CountOfClusters{%u}\n", vfat->CountOfClusters);
PRINTF("FirstDataSector{%u}\n", vfat->FirstDataSector);
PRINTF("FirstRootDirSecNum{%u}\n", vfat->FirstRootDirSecNum);
PRINTF("Type{%u}\n", vfat->FATType);
DumpLayout(vfat);
rc = 0;
done:
return rc;
}
static int _LoadDir(
VFAT* vfat,
const char* path,
void** data,
UINTN* size,
UINT32* clustno)
{
int rc = -1;
Buf buf = BUF_INITIALIZER;
/* Check parameters */
if (!vfat || !path || !data || !size || !clustno)
GOTO(done);
/* Initialize output buffer */
*data = NULL;
*size = 0;
/* Handle root path separately */
if (Strcmp(path, "/") == 0)
{
BufAppend(&buf, vfat->rootdir.data, vfat->rootdir.size);
*clustno = vfat->rootdirClustno;
}
else
{
VFATDirectoryEntry entry;
/* Stat the file */
if (VFATStatFile(vfat, path, &entry) != 0)
GOTO(done);
/* Be sure file is a directory */
if (!(entry.attr & ATTR_DIRECTORY))
GOTO(done);
/* Load directory file into memory */
if (LoadFileFromDirectoryEntry(vfat, &entry, &buf) != 0)
GOTO(done);
*clustno = ((UINT32)entry.fstClusHI << 16) | (UINT32)entry.fstClusLO;
}
*data = buf.data;
*size = buf.size;
rc = 0;
done:
if (rc != 0)
BufRelease(&buf);
return rc;
}
static int _AllocateFATChain(
VFAT* vfat,
UINTN numClusters, /* number of clusters in FAT chain */
UINT32* clustno) /* head cluster number of FAT chain */
{
int rc = -1;
UINT32 i;
BufU32 buf = BUF_U32_INITIALIZER;
/* Check parameters */
if (!clustno)
GOTO(done);
*clustno = 0;
/* Handle zero-sized chain */
if (numClusters == 0)
{
*clustno = 0;
rc = 0;
GOTO(done);
}
/* Find N free entries (where N=numClusters) */
for (i = 2; i < vfat->CountOfClusters + 2 && buf.size < numClusters; i++)
{
UINT32 c = GetFATEntry(vfat, i);
/* If available */
if (c == 0)
BufU32Append(&buf, &i, 1);
}
/* If not enough free clusters found */
if (buf.size != numClusters)
GOTO(done);
/* Reserve the entries */
{
/* Set the head of the chain */
*clustno = buf.data[0];
/* Set subsequent entries */
for (i = 0; i < buf.size; i++)
{
if (i + 1 == buf.size)
SetFATEntry(vfat, buf.data[i], GetEOC(vfat));
else
SetFATEntry(vfat, buf.data[i], buf.data[i+1]);
}
}
rc = 0;
done:
BufU32Release(&buf);
return rc;
}
int VFATInit(
Blkdev* dev,
VFAT** vfatOut)
{
int rc = -1;
VFAT* vfat = NULL;
if (!dev || !vfatOut)
GOTO(done);
/* Allocate the vfat object */
if (!(vfat = Calloc(1, sizeof(VFAT))))
GOTO(done);
/* Read the BPB */
if (BlkdevRead(dev, 0, &vfat->bpb, sizeof(vfat->bpb)) != 0)
GOTO(done);
/* Check signature of BPB */
{
const UINT8* p = vfat->bpb.jmpBoot;
if (!(p[0] == 0xEB && p[2] == 0x90) && !(p[0] == 0xE9))
GOTO(done);
}
/* See if there is an FSI (FAT32 only) */
if (GetFATType(&vfat->bpb) == FAT32)
{
/* Read the BPB */
if (BlkdevRead(
dev,
vfat->bpb.u.s32.FSInfo,
&vfat->fsi,
sizeof(vfat->fsi)) != 0)
{
GOTO(done);
}
/* Check signatures of FSI */
if (vfat->fsi.LeadSig != 0x41615252 || vfat->fsi.TrailSig != 0xAA550000)
GOTO(done);
}
/* Set the block device */
vfat->dev = dev;
/* Reject sector sizes that are not 512 */
if (vfat->bpb.BytsPerSec != VFAT_SECTOR_SIZE)
GOTO(done);
/* Precompute some useful values */
{
/* Compute VFAT.FATSz */
if (vfat->bpb.FATSz16 != 0)
vfat->FATSz = vfat->bpb.FATSz16;
else
vfat->FATSz = vfat->bpb.u.s32.FATSz32;
/* Compute VFAT.TotSec */
if (vfat->bpb.TotSec16 != 0)
vfat->TotSec = vfat->bpb.TotSec16;
else
vfat->TotSec = vfat->bpb.TotSec32;
/* Compute VFAT.RootDirSectors */
vfat->RootDirSectors =
((vfat->bpb.RootEntCnt * 32) + (vfat->bpb.BytsPerSec - 1)) /
vfat->bpb.BytsPerSec;
/* Compute VFAT.FirstRootDirSecNum */
vfat->FirstRootDirSecNum = vfat->bpb.ResvdSecCnt +
(vfat->bpb.NumFATs * vfat->FATSz);
/* Compute VFAT.FirstDataSector */
vfat->FirstDataSector = vfat->bpb.ResvdSecCnt +
(vfat->bpb.NumFATs * vfat->FATSz) + vfat->RootDirSectors;
/* Compute VFAT.DataSec */
vfat->DataSec = vfat->TotSec -
(vfat->bpb.ResvdSecCnt + (vfat->bpb.NumFATs * vfat->FATSz) +
vfat->RootDirSectors);
/* Compute VFAT.CountOfClusters */
vfat->CountOfClusters = vfat->DataSec / vfat->bpb.SecPerClus;
/* Compute cluster size in bytes */
vfat->ClusterSize = vfat->bpb.SecPerClus * vfat->bpb.BytsPerSec;
}
/* Determine Type of FAT */
if (vfat->CountOfClusters < 4085)
vfat->FATType = FAT12;
else if (vfat->CountOfClusters < 65525)
vfat->FATType = FAT16;
else
vfat->FATType = FAT32;
/* Load one of the FATs into memory */
{
UINTN nbytes = vfat->FATSz * vfat->bpb.BytsPerSec;
if (BufReserve(&vfat->fat, nbytes) != 0)
GOTO(done);
if (BlkdevRead(
vfat->dev,
vfat->bpb.ResvdSecCnt,
vfat->fat.data,
nbytes) != 0)
{
GOTO(done);
}
vfat->fat.size += nbytes;
/* If more than one FAT, read the second one in an compare */
if (vfat->bpb.NumFATs > 1)
{
Buf buf = BUF_INITIALIZER;
if (BufReserve(&buf, nbytes) != 0)
GOTO(done);
if (BlkdevRead(
vfat->dev,
vfat->bpb.ResvdSecCnt + vfat->FATSz,
buf.data,
nbytes) != 0)
{
BufRelease(&buf);
GOTO(done);
}
buf.size += nbytes;
if (buf.size != vfat->fat.size ||
Memcmp(buf.data, vfat->fat.data, buf.size) != 0)
{
BufRelease(&buf);
GOTO(done);
}
BufRelease(&buf);
}
#if 0
HexDump(vfat->fat.data, vfat->fat.size);
#endif
}
/* Load the root directory into memory */
if (vfat->FATType == FAT32)
{
/* Read the root directory file into memory */
if (ReadClusters(vfat, vfat->bpb.u.s32.RootClus, &vfat->rootdir) != 0)
GOTO(done);
vfat->rootdirClustno = vfat->bpb.u.s32.RootClus;
}
else
{
UINTN nbytes = vfat->RootDirSectors * vfat->bpb.BytsPerSec;
if (BufReserve(&vfat->rootdir, nbytes) != 0)
GOTO(done);
if (BlkdevRead(
vfat->dev,
vfat->FirstRootDirSecNum,
vfat->rootdir.data,
nbytes) != 0)
{
GOTO(done);
}
vfat->rootdir.size += nbytes;
vfat->rootdirClustno = 0;
}
*vfatOut = vfat;
rc = 0;
done:
if (rc != 0)
{
if (vfat)
{
/* Don't let VFATRelease() free the caller's device */
vfat->dev = NULL;
VFATRelease(vfat);
}
}
return rc;
}
void VFATRelease(VFAT* vfat)
{
if (!vfat)
return;
if (vfat->dev)
vfat->dev->Close(vfat->dev);
BufRelease(&vfat->fat);
BufRelease(&vfat->rootdir);
Free(vfat);
}
int VFATGetFile(
VFAT* vfat,
const char* path,
void** data,
UINTN* size)
{
int rc = -1;
VFATDirectoryEntry ent;
Buf buf = BUF_INITIALIZER;
/* Check parameters */
if (!vfat || !data || !size)
GOTO(done);
*data = NULL;
*size = 0;
if (VFATStatFile(vfat, path, &ent) != 0)
GOTO(done);
if (LoadFileFromDirectoryEntry(vfat, &ent, &buf) != 0)
GOTO(done);
*data = buf.data;
*size = buf.size;
rc = 0;
done:
if (rc != 0)
BufRelease(&buf);
return rc;
}
static char* _GetDirName(
char buf[VFAT_PATH_SIZE],
const char* path)
{
char* slash;
Strlcpy(buf, path, VFAT_PATH_SIZE);
if (!(slash = Strrchr(buf, '/')))
return NULL;
if (slash == buf)
buf[1] = '\0';
else
buf[slash - buf] = '\0';
return buf;
}
static char* _GetBaseName(
char buf[VFAT_PATH_SIZE],
const char* path)
{
char* slash;
if (!(slash = Strrchr(path, '/')))
return NULL;
if (slash[1] == '\0')
return NULL;
Strlcpy(buf, slash + 1, VFAT_PATH_SIZE);
return buf;
}
static int _FlushFAT(
VFAT* vfat)
{
int rc = -1;
UINTN i;
/* Check parameters */
if (!vfat)
GOTO(done);
/* Update each copy of the FAT on disk */
for (i = 0; i < vfat->bpb.NumFATs; i++)
{
/* Write FAT to disk */
if (BlkdevWrite(
vfat->dev,
vfat->bpb.ResvdSecCnt + (i * vfat->FATSz),
vfat->fat.data,
vfat->fat.size) != 0)
{
GOTO(done);
}
}
rc = 0;
done:
return rc;
}
static int _FlushFile(
VFAT* vfat,
const void* data,
UINTN size,
UINT32 clustno)
{
int rc = -1;
/* Check parameters */
if (!vfat || !data)
GOTO(done);
/* If no data to flush */
if (size == 0)
{
rc = 0;
GOTO(done);
}
/* If flushing root directory on a non-FAT32 file system */
if (clustno == 0)
{
UINT32 rootDirSize = vfat->RootDirSectors * vfat->bpb.BytsPerSec;
if (rootDirSize != size)
GOTO(done);
if (BlkdevWrite(
vfat->dev,
vfat->FirstRootDirSecNum,
data,
size) != 0)
{
GOTO(done);
}
}
else
{
if (WriteClusters(vfat, clustno, data, size) != 0)
GOTO(done);
}
rc = 0;
done:
return rc;
}
int _PutFile(
VFAT* vfat,
BOOLEAN isDir,
const char* path,
void* data,
UINTN size)
{
int rc = -1;
VFATDirectoryEntry ent;
char dirname[VFAT_PATH_SIZE];
char basename[VFAT_PATH_SIZE];
void* dirData = NULL;
UINTN dirSize;
UINT32 dirClustno;
UINT32 clustno = 0;
const VFATDirectoryEntry dot =
{
{ '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, /* name */
ATTR_ARCHIVE | ATTR_DIRECTORY, /* attr */
0, /* res */
0, /* crtTimeTenth */
1709, /* crtTime */
18706, /* crtDate */
18706, /* lstAccDate */
0, /* fstClusHI */
22131, /* wrtTime */
18706, /* wrtDate */
0, /* fstClusLO */
0, /* fileSize */
};
const VFATDirectoryEntry dotdot =
{
{ '.', '.', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, /* name */
ATTR_ARCHIVE | ATTR_DIRECTORY, /* attr */
0, /* res */
0, /* crtTimeTenth */
1709, /* crtTime */
18706, /* crtDate */
18706, /* lstAccDate */
0, /* fstClusHI */
22131, /* wrtTime */
18706, /* wrtDate */
0, /* fstClusLO */
0, /* fileSize */
};
/* Check parameters */
if (!vfat || !path || !data)
GOTO(done);
/* If a directory, then copy "." and ".." entries */
if (isDir)
{
Memset(data, 0, size);
Memcpy(data, &dot, sizeof(dot));
Memcpy((UINT8*)data + sizeof(dot), &dotdot, sizeof(dotdot));
}
/* Fail if file already exists */
if (VFATStatFile(vfat, path, &ent) == 0)
GOTO(done);
/* Reject non-absolute paths */
if (path[0] != '/')
GOTO(done);
/* Get the base name from the path (final component) */
if (_GetBaseName(basename, path) == NULL)
GOTO(done);
/* Get the directory name from the path (excludes final component) */
if (_GetDirName(dirname, path) == NULL)
GOTO(done);
/* Load directory into memory */
if (_LoadDir(vfat, dirname, &dirData, &dirSize, &dirClustno) != 0)
GOTO(done);
/* If creating directory file, update the parent directory cluster */
if (isDir)
{
VFATDirectoryEntry* p = (VFATDirectoryEntry*)data;
p[1].fstClusHI = (dirClustno & 0xFFFF0000) << 16;
p[1].fstClusLO = (dirClustno & 0x0000FFFF);
}
/* Inject directory entry */
{
/* Used hardcoded dates and times */
VFATDirectoryEntry entry =
{
{ '\0' }, /* name */
0, /* attr */
0, /* res */
100, /* crtTimeTenth */
1884, /* crtTime */
18706, /* crtDate */
18706, /* lstAccDate */
0, /* fstClusHI */
1884, /* wrtTime */
18706, /* wrtDate */
0, /* fstClusLO */
0, /* fileSize */
};
/* Set VFATDirectoryEntry.attr */
{
entry.attr = ATTR_ARCHIVE;
if (isDir)
entry.attr |= ATTR_DIRECTORY;
}
/* Set VFATDirectoryEntry.name */
{
char shortname[12];
InternShortname(shortname, basename);
Memcpy(entry.name, shortname, sizeof(entry.name));
}
/* Set VFATDirectoryEntry.fileSize */
if (!isDir)
entry.fileSize = size;
/* Allocate FAT chain for this new file (update in-memory FAT copy) */
{
UINTN r = (size + vfat->ClusterSize - 1) / vfat->ClusterSize;
if (_AllocateFATChain(vfat, r, &clustno) != 0)
GOTO(done);
}
/* If creating directory file, update the directory cluster */
if (isDir)
{
VFATDirectoryEntry* p = (VFATDirectoryEntry*)data;
p[0].fstClusHI = (clustno & 0xFFFF0000) << 16;
p[0].fstClusLO = (clustno & 0x0000FFFF);
}
/* Update directory entry cluster number */
entry.fstClusHI = (clustno & 0xFFFF0000) << 16;
entry.fstClusLO = (clustno & 0x0000FFFF);
/* Inject entry into directory file memory */
{
VFATDirectoryEntry* p = (VFATDirectoryEntry*)dirData;
for (; p->name[0]; p++)
{
/* Use this entry if deleted */
if (p->name[0] == 0xE5)
break;
/* Skip long-name entries */
if (p->attr == ATTR_LONG_NAME)
continue;
}
/* If no more room for a directory entry */
if ((void*)(p + 1) >= (dirData + dirSize))
GOTO(done);
/* Update the entry */
Memcpy(p, &entry, sizeof(*p));
}
}
/* Flush file to disk */
if (_FlushFile(vfat, data, size, clustno) != 0)
GOTO(done);
/* If this is the root directory, then refresh the cache */
if (dirClustno == vfat->rootdirClustno)
{
if (vfat->rootdir.size != dirSize)
goto done;
Memcpy(vfat->rootdir.data, dirData, dirSize);
}
/* Flush directory to disk */
if (_FlushFile(vfat, dirData, dirSize, dirClustno) != 0)
GOTO(done);
/* Flush FAT to disk */
if (_FlushFAT(vfat))
GOTO(done);
rc = 0;
done:
if (dirData)
Free(dirData);
return rc;
}
int VFATMkdir(
VFAT* vfat,
const char* path)
{
int rc = -1;
void* data = NULL;
UINTN size;
if (!vfat || !path)
goto done;
size = vfat->ClusterSize;
if (!(data = Calloc(1, vfat->ClusterSize)))
goto done;
if (_PutFile(vfat, TRUE, path, data, size) != 0)
goto done;
rc = 0;
done:
if (data)
Free(data);
return rc;
}
int VFATPutFile(
VFAT* vfat,
const char* path,
const void* data,
UINTN size)
{
return _PutFile(vfat, FALSE, path, (void*)data, size);
}
int VFATDir(
VFAT* vfat,
const char* path,
StrArr* paths)
{
int rc = -1;
void* data = NULL;
UINTN size = 0;
UINT32 clustno = 0;
/* Check parameters */
if (!vfat || !path || !paths)
GOTO(done);
/* Load the directory file into memory */
if (_LoadDir(vfat, path, &data, &size, &clustno) != 0)
GOTO(done);
/* Scan this directory and form list of files */
{
VFATDirectoryEntry* p = (VFATDirectoryEntry*)data;
for (; p->name[0]; p++)
{
/* Skip deleted entries */
if (p->name[0] == 0xE5)
continue;
/* Skip long-name entries */
if (p->attr == ATTR_LONG_NAME)
continue;
/* Format name */
{
char buf[13];
ExternShortname(buf, (const char*)p->name);
StrArrAppend(paths, buf);
}
}
}
rc = 0;
done:
if (data)
Free(data);
return rc;
}