20#include "FatDirectory.h"
21#include "pedigree/kernel/LockGuard.h"
22#include "pedigree/kernel/Log.h"
23#include "pedigree/kernel/syscallError.h"
24#include "pedigree/kernel/time/Time.h"
25#include "pedigree/kernel/utilities/PointerGuard.h"
26#include "pedigree/kernel/utilities/utility.h"
29#include "FatFilesystem.h"
30#include "FatSymlink.h"
32#include "modules/system/vfs/File.h"
37constexpr size_t MaxLongFilenameEntries = 20;
38constexpr size_t LongFilenameCharactersPerEntry = 13;
39constexpr size_t LongFilenameStorageCharacters =
40 MaxLongFilenameEntries * LongFilenameCharactersPerEntry;
41constexpr size_t MaxLongFilenameCharacters = 255;
43uint8_t shortFilenameChecksum(
const uint8_t* name) {
45 for (
size_t i = 0; i < 11; ++i)
46 checksum =
static_cast<uint8_t
>(((checksum & 1) ? 0x80 : 0) + (checksum >> 1) + name[i]);
50void writeLongFilenameCharacter(uint8_t* entry,
size_t character, uint16_t value) {
51 static const uint8_t offsets[LongFilenameCharactersPerEntry] = {1, 3, 5, 7, 9, 14, 16,
52 18, 20, 22, 24, 28, 30};
53 const size_t offset = offsets[character];
54 entry[offset] = value & 0xFF;
55 entry[offset + 1] = value >> 8;
58bool encodeLongFilename(
const String& filename, uint16_t* characters,
size_t& characterCount,
62 for (
size_t i = 0; i < filename.length();) {
63 const uint8_t first =
static_cast<uint8_t
>(filename[i]);
64 uint32_t character = 0;
65 size_t sequenceLength = 0;
69 }
else if (first >= 0xC2 && first <= 0xDF) {
70 character = first & 0x1F;
72 }
else if (first >= 0xE0 && first <= 0xEF) {
73 character = first & 0x0F;
75 }
else if (first >= 0xF0 && first <= 0xF4) {
76 character = first & 0x07;
82 if ((i + sequenceLength) > filename.length())
84 for (
size_t continuation = 1; continuation < sequenceLength; ++continuation) {
85 const uint8_t value =
static_cast<uint8_t
>(filename[i + continuation]);
86 if ((value & 0xC0) != 0x80)
88 character = (character << 6) | (value & 0x3F);
91 if ((sequenceLength == 3 && character < 0x800) ||
92 (sequenceLength == 4 && character < 0x10000) || character > 0x10FFFF ||
93 (character >= 0xD800 && character <= 0xDFFF)) {
97 if (character <= 0xFFFF) {
98 if (characterCount >= MaxLongFilenameCharacters) {
102 characters[characterCount++] = character;
104 if ((characterCount + 2) > MaxLongFilenameCharacters) {
108 character -= 0x10000;
109 characters[characterCount++] = 0xD800 | (character >> 10);
110 characters[characterCount++] = 0xDC00 | (character & 0x3FF);
119 FatFileInfo& info, uint32_t dirClus, uint32_t dirOffset)
120 :
Directory(name, info.accessedTime, info.modifiedTime, info.creationTime, inode_num,
123 m_DirOffset(dirOffset),
130 uint32_t permissions = 0777;
140 pFs->registerNode(
this);
143bool FatDirectory::encodeEntrySet(
const String& filename,
const Dir& metadata,
145 const bool special = filename ==
"." || filename ==
"..";
146 uint16_t characters[LongFilenameStorageCharacters];
148 bool tooLong =
false;
149 for (
size_t i = 0; i < filename.length(); ++i) {
150 const uint8_t character = filename[i];
151 if (character < 0x20 || character ==
'"' || character ==
'*' || character ==
'/' ||
152 character ==
':' || character ==
'<' || character ==
'>' || character ==
'?' ||
153 character ==
'\\' || character ==
'|') {
154 SYSCALL_ERROR(InvalidArgument);
159 (!filename.length() || !encodeLongFilename(filename, characters, count, tooLong))) {
160 syscallError(tooLong ? Error::NameTooLong : Error::InvalidArgument);
163 const size_t longCount =
164 special ? 0 : (count + LongFilenameCharactersPerEntry - 1) / LongFilenameCharactersPerEntry;
167 if (shortName.length() < 11) {
168 SYSCALL_ERROR(InvalidArgument);
171 const uint8_t checksum =
172 shortFilenameChecksum(
reinterpret_cast<const uint8_t*
>(shortName.cstr()));
173 for (
size_t i = 0; i < longCount; ++i) {
175 ByteSet(&raw, 0xFF,
sizeof(raw));
177 const size_t ordinal = longCount - i;
178 entry->LDIR_Ord = ordinal | (i == 0 ? 0x40 : 0);
179 entry->LDIR_Attr = ATTR_LONG_NAME;
180 entry->LDIR_Type = 0;
181 entry->LDIR_Chksum = checksum;
182 entry->LDIR_FstClusLO = 0;
183 for (
size_t character = 0; character < LongFilenameCharactersPerEntry; ++character) {
184 const size_t index = (ordinal - 1) * LongFilenameCharactersPerEntry + character;
185 writeLongFilenameCharacter(
reinterpret_cast<uint8_t*
>(entry), character,
186 index < count ? characters[index]
192 Dir shortEntry = metadata;
193 MemoryCopy(shortEntry.DIR_Name, shortName.cstr(), 11);
199 static_cast<FatFilesystem*
>(m_pFilesystem)->releaseNode(
this);
205 Attributes attributes = File::getAttributes();
206 attributes.blocks = filesystem->allocatedBlocks(
const_cast<FatDirectory*
>(
this));
213 uintptr_t clus = m_Inode;
218 if (clus == 0 && m_Type != FAT32) {
221 }
else if (pFs->
m_Type == FAT32)
222 if (clus == pFs->m_Superblock32.BPB_RootClus)
227struct LongFilenameState {
228 uint16_t characters[LongFilenameStorageCharacters];
229 uint8_t expectedOrdinal;
235void resetLongFilename(LongFilenameState& state) {
236 ByteSet(state.characters, 0xFF,
sizeof(state.characters));
237 state.expectedOrdinal = 0;
238 state.entryCount = 0;
243uint16_t readLongFilenameCharacter(
const uint8_t* p) {
244 return static_cast<uint16_t
>(p[0]) | (
static_cast<uint16_t
>(p[1]) << 8);
247bool consumeLongFilenameEntry(LongFilenameState& state,
const DirLongFilename& entry) {
248 const uint8_t ordinal = entry.LDIR_Ord & 0x1F;
249 const bool last = (entry.LDIR_Ord & 0x40) != 0;
250 if (entry.LDIR_Ord & 0xA0 || !ordinal || ordinal > MaxLongFilenameEntries || entry.LDIR_Type ||
251 entry.LDIR_FstClusLO) {
252 resetLongFilename(state);
257 resetLongFilename(state);
259 state.expectedOrdinal = ordinal;
260 state.entryCount = ordinal;
261 state.checksum = entry.LDIR_Chksum;
264 if (!state.valid || state.expectedOrdinal != ordinal || state.checksum != entry.LDIR_Chksum) {
265 resetLongFilename(state);
269 const uint8_t* raw =
reinterpret_cast<const uint8_t*
>(&entry);
270 size_t character = (ordinal - 1) * LongFilenameCharactersPerEntry;
271 for (
size_t offset = 1; offset < 11; offset += 2)
272 state.characters[character++] = readLongFilenameCharacter(raw + offset);
273 for (
size_t offset = 14; offset < 26; offset += 2)
274 state.characters[character++] = readLongFilenameCharacter(raw + offset);
275 for (
size_t offset = 28; offset < 32; offset += 2)
276 state.characters[character++] = readLongFilenameCharacter(raw + offset);
278 state.expectedOrdinal = ordinal - 1;
282String longFilename(
const LongFilenameState& state) {
284 result.reserve((state.entryCount * LongFilenameCharactersPerEntry * 3) + 1);
285 const size_t characterCount =
286 pedigree_std::min(
static_cast<size_t>(state.entryCount) * LongFilenameCharactersPerEntry,
287 MaxLongFilenameCharacters);
288 for (
size_t i = 0; i < characterCount; ++i) {
289 uint32_t character = state.characters[i];
290 if (!character || character == 0xFFFF)
293 if (character >= 0xD800 && character <= 0xDBFF) {
294 if ((i + 1) < characterCount && state.characters[i + 1] >= 0xDC00 &&
295 state.characters[i + 1] <= 0xDFFF) {
296 character = 0x10000 + ((character - 0xD800) << 10) + (state.characters[++i] - 0xDC00);
300 }
else if (character >= 0xDC00 && character <= 0xDFFF) {
310 result +=
String(utf8, length,
true);
315uint64_t directoryCookie(uint32_t cluster, uint32_t offset) {
316 return ((
static_cast<uint64_t
>(cluster) << 32) | offset) + 1;
322Directory::ReadStatus FatDirectory::scanDirectory(uint64_t& cookie, ScanEmitter emitter,
325 return ReadStatus::IoError;
328 const uint64_t startingCookie = cookie;
329 uint32_t requestedCluster =
static_cast<uint32_t
>(m_Inode);
332 const uint64_t packed = cookie - 1;
333 requestedCluster =
static_cast<uint32_t
>(packed >> 32);
334 offset =
static_cast<uint32_t
>(packed);
337 const uint32_t portionSize = m_bRootDir && m_Type != FAT32 ?
m_DirBlockSize : m_BlockSize;
338 if (!portionSize || (portionSize %
sizeof(
Dir)) || (offset %
sizeof(
Dir)) || offset > portionSize)
339 return ReadStatus::IoError;
341 uint32_t cluster =
static_cast<uint32_t
>(m_Inode);
342 size_t visitedClusters = 1;
343 if (m_bRootDir && m_Type != FAT32) {
344 if (requestedCluster)
345 return ReadStatus::IoError;
347 if (cluster < 2 || requestedCluster < 2 || cluster >= (pFs->
m_ClusterCount + 2) ||
349 return ReadStatus::IoError;
352 while (cluster != requestedCluster) {
354 return ReadStatus::IoError;
356 if (!cluster || pFs->
isEof(cluster) || cluster < 2 || cluster >= (pFs->
m_ClusterCount + 2)) {
357 return ReadStatus::IoError;
362 uint8_t* buffer =
new uint8_t[portionSize];
365 return ReadStatus::IoError;
367 LongFilenameState lfn;
368 resetLongFilename(lfn);
369 uint64_t recordCookie = startingCookie;
370 bool initialSlot =
true;
373 if (offset == portionSize) {
374 if (m_bRootDir && m_Type != FAT32)
375 return ReadStatus::Complete;
379 return ReadStatus::IoError;
380 if (pFs->
isEof(nextCluster))
381 return ReadStatus::Complete;
382 if (nextCluster < 2 || nextCluster >= (pFs->
m_ClusterCount + 2) ||
384 return ReadStatus::IoError;
387 cluster = nextCluster;
389 if (!pFs->
readCluster(cluster,
reinterpret_cast<uintptr_t
>(buffer)))
390 return ReadStatus::IoError;
392 recordCookie = directoryCookie(cluster, 0);
396 const uint64_t slotCookie =
397 initialSlot && !startingCookie ? 0 : directoryCookie(cluster, offset);
399 const uint64_t nextCookie = directoryCookie(cluster, offset +
sizeof(
Dir));
400 const Dir* entry =
reinterpret_cast<const Dir*
>(buffer + offset);
401 offset +=
sizeof(
Dir);
403 const uint8_t firstCharacter = entry->DIR_Name[0];
405 return ReadStatus::Complete;
407 if (firstCharacter == 0xE5) {
408 resetLongFilename(lfn);
409 recordCookie = nextCookie;
413 if ((entry->DIR_Attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) {
414 if (entry->DIR_Name[0] & 0x40)
415 recordCookie = slotCookie;
416 if (!consumeLongFilenameEntry(lfn, *
reinterpret_cast<const DirLongFilename*
>(entry)))
417 recordCookie = nextCookie;
422 if (lfn.valid && !lfn.expectedOrdinal &&
423 lfn.checksum == shortFilenameChecksum(entry->DIR_Name)) {
424 filename = longFilename(lfn);
426 if (!filename.length()) {
427 uint8_t shortNameBytes[11];
428 MemoryCopy(shortNameBytes, entry->DIR_Name,
sizeof(shortNameBytes));
429 if (shortNameBytes[0] == 0x05)
430 shortNameBytes[0] = 0xE5;
431 const String shortName(
reinterpret_cast<const char*
>(shortNameBytes), 11,
true);
435 const uint64_t currentCookie = recordCookie;
436 resetLongFilename(lfn);
437 recordCookie = nextCookie;
439 if ((entry->DIR_Attr & ATTR_VOLUME_ID) || filename.
compare(
".", 1) ||
444 EntryType type = EntryType::Regular;
445 if (entry->DIR_Attr & ATTR_DIRECTORY) {
446 type = EntryType::Directory;
447 }
else if (filename.
endswith(symlinkSuffix())) {
448 filename.
rtrim(symlinkSuffix().length());
449 type = EntryType::Symlink;
452 ScannedEntry scanned;
453 scanned.name = pedigree_std::move(filename);
454 MemoryCopy(&scanned.entry, entry,
sizeof(
Dir));
455 scanned.directoryCluster = cluster;
456 scanned.directoryOffset = offset -
sizeof(
Dir);
459 if (!emitter(context, scanned, currentCookie, nextCookie))
460 return ReadStatus::Stopped;
465File* FatDirectory::materialize(
const ScannedEntry& scanned) {
467 const Dir& entry = scanned.entry;
468 const uint32_t fileCluster = LITTLE_TO_HOST16(entry.DIR_FstClusLO) |
469 (
static_cast<uint32_t
>(LITTLE_TO_HOST16(entry.DIR_FstClusHI)) << 16);
470 const Time::Timestamp writeTime = pFs->getUnixTimestamp(entry.DIR_WrtTime, entry.DIR_WrtDate);
471 const Time::Timestamp accessTime = pFs->getUnixTimestamp(0, entry.DIR_LstAccDate);
472 Time::Timestamp creationTime = pFs->getUnixTimestamp(entry.DIR_CrtTime, entry.DIR_CrtDate);
473 if (creationTime && entry.DIR_CrtTimeTenth >= 100 && entry.DIR_CrtTimeTenth < 200)
476 if (scanned.type == EntryType::Directory) {
478 info.accessedTime = accessTime;
479 info.modifiedTime = writeTime;
480 info.creationTime = creationTime;
481 return new FatDirectory(scanned.name, fileCluster, pFs,
this, info, scanned.directoryCluster,
482 scanned.directoryOffset);
485 const uint32_t size = LITTLE_TO_HOST32(entry.DIR_FileSize);
486 if (scanned.type == EntryType::Symlink) {
487 return new FatSymlink(scanned.name, accessTime, writeTime, creationTime, fileCluster, pFs, size,
488 scanned.directoryCluster, scanned.directoryOffset,
this);
490 return new FatFile(scanned.name, accessTime, writeTime, creationTime, fileCluster, pFs, size,
491 scanned.directoryCluster, scanned.directoryOffset,
this);
502 auto emitter = [](
void* opaque,
const ScannedEntry& entry, uint64_t, uint64_t) ->
bool {
503 Context* context =
reinterpret_cast<Context*
>(opaque);
504 if (entry.name == context->name) {
505 context->entry = entry;
506 context->found =
true;
518 return LookupStatus::NotFound;
519 status = scanDirectory(cookie, emitter, &context);
521 child = materialize(context.entry);
525 return child ? LookupStatus::Found : LookupStatus::IoError;
526 return status == ReadStatus::IoError ? LookupStatus::IoError : LookupStatus::NotFound;
538 auto emitter = [](
void* opaque,
const ScannedEntry& entry, uint64_t, uint64_t) ->
bool {
539 Context* context =
reinterpret_cast<Context*
>(opaque);
540 if (entry.name == context->name) {
541 context->entry = entry;
542 context->found =
true;
551 return LookupStatus::NotFound;
552 scanDirectory(cookie, emitter, &context);
554 child = materialize(context.entry);
558 return child ? LookupStatus::Found : LookupStatus::IoError;
567 return ReadStatus::IoError;
570 DirectoryEntryEmitter emitter;
573 } adapter = {emitter, context,
static_cast<FatFilesystem*
>(m_pFilesystem)};
575 auto scanEmitter = [](
void* opaque,
const ScannedEntry& entry, uint64_t currentCookie,
576 uint64_t nextCookie) ->
bool {
577 Context* context =
reinterpret_cast<Context*
>(opaque);
578 const uintptr_t inode =
579 entry.type == EntryType::Regular
580 ? context->filesystem->fileIdentifier(entry.directoryCluster, entry.directoryOffset)
581 : LITTLE_TO_HOST16(entry.entry.DIR_FstClusLO) |
582 (
static_cast<uint32_t
>(LITTLE_TO_HOST16(entry.entry.DIR_FstClusHI)) << 16);
583 DirectoryEntryView view = {entry.name.view(), inode, entry.type, currentCookie, nextCookie};
584 return context->emitter(context->context, view);
589 return ReadStatus::Complete;
590 return scanDirectory(cookie, scanEmitter, &adapter);
LookupStatus resolveChildAt(uint64_t cookie, const StringView &name, File *&child) override
FatDirectory(const FatDirectory &file)
ReadStatus readDirectory(uint64_t &cookie, DirectoryEntryEmitter emitter, void *context) override
void setInode(uintptr_t inode) override
void cacheDirectoryContents() override
LookupStatus resolveChild(const StringView &name, File *&child) override
String convertFilenameTo(String filename) const
void * readDirectoryPortion(uint32_t clus) const
String convertFilenameFrom(String filename) const
bool isEof(uint32_t cluster) const
uint32_t getClusterEntry(uint32_t cluster, bool bLock=true)
bool readCluster(uint32_t block, uintptr_t buffer) const
void setGidOnly(size_t gid)
void setPermissionsOnly(uint32_t perms)
void setUidOnly(size_t uid)
bool compare(const char *s, size_t len) const
bool endswith(const char c) const
static size_t Utf32ToUtf8(uint32_t utf32, char *utf8)
A vector / dynamic array.
void pushBack(const T &value)