kernel
Loading...
Searching...
No Matches
fat32.c
Go to the documentation of this file.
1/*
2 * PROJECT: MatanelOS Kernel
3 * LICENSE: NONE
4 * PURPOSE: FAT32 FileSystem Implementation.
5 */
6
7#include "fat32.h"
9#include "../../assert.h"
10#include "../../time.h"
12#include "../../includes/mg.h"
13#include "../../includes/mm.h"
14#include "../../includes/fs.h"
15#include "../../includes/ob.h"
16
17#define WRITE_MODE_APPEND_EXISTING 0
18#define WRITE_MODE_CREATE_OR_REPLACE 1
19
20#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
21#define le32toh(x) __builtin_bswap32(x)
22#else
23#define le32toh(x) (x)
24#endif
25
26static FAT32_BPB bpb;
27static FAT32_FSINFO fs;
28static BLOCK_DEVICE* disk;
30
31static SPINLOCK fat32_read_fat_lock = { 0 };
32static SPINLOCK fat32_write_fat_lock = { 0 };
33static void* fat_cache_buf = NULL;
34void* fat_cache_buf2 = NULL;
35static uint32_t fat_cache_sector = UINT32_MAX;
36
37#define MAX_LFN_ENTRIES 20 // Allows up to 260 chars (20*13)
38#define MAX_LFN_LEN 260
39
40volatile int32_t fat32_called_from_scanner = 0;
41
42// Internal Error Constants
43#define FAT32_READ_ERROR 0xFFFFFFFFu
44typedef struct {
45 uint16_t name_chars[13]; // UTF-16 characters from one LFN entry
47
48// Read sector into the buffer.
49static MTSTATUS read_sector(uint32_t lba, void* buf) {
50
51 size_t NumberOfBytes = fs.bytes_per_sector;
52 if (!NumberOfBytes) NumberOfBytes = 512;
53
54 if (NumberOfBytes % 512 != 0) {
55 // NumberOfBytes must be in multiples of 512.
56 return MT_INVALID_PARAM;
57 }
58
59 return disk->read_sector(disk, lba, buf, NumberOfBytes);
60}
61
62// Write to sector from buffer
63static MTSTATUS write_sector(uint32_t lba, const void* buf) {
64
65 size_t NumberOfBytes = fs.bytes_per_sector;
66 if (!NumberOfBytes) NumberOfBytes = 512;
67
68 if (NumberOfBytes % 512 != 0) {
69 // NumberOfBytes must be in multiples of 512.
70 return MT_INVALID_PARAM;
71 }
72
73 return disk->write_sector(disk, lba, buf, NumberOfBytes);
74}
75
76// Compute checksum of 8.3 name (from specification)
77static uint8_t lfn_checksum(const uint8_t short_name[11]) {
78 uint8_t sum = 0;
79 for (int i = 0; i < 11; i++) {
80 sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1) + short_name[i];
81 }
82 return sum;
83}
84
85// Convert to uppercase.
86static inline int toupper(int c) {
87 if (c >= 'a' && c <= 'z') {
88 return c - ('a' - 'A'); // Convert lowercase to uppercase
89 }
90 return c; // Return unchanged if not lowercase letter
91}
92
93// Compare short name
94static bool cmp_name(const char* str_1, const char* str_2) {
95 char t[12] = { 0 };
96 for (int i = 0; i < 11; i++) { t[i] = str_1[i]; }
97 for (int i = 0; i < 11; i++) {
98 if (toupper(t[i]) != toupper(str_2[i])) {
99 return false;
100 }
101 }
102 return true;
103}
104
105
106// Helper: convert "NAME.EXT" or "NAMEEXT" to 11-byte FAT short-name (uppercased, space-padded).
107static void format_short_name(const char* input, char out[11]) {
108 // Fill with spaces
109 for (int i = 0; i < 11; ++i) out[i] = ' ';
110 // Copy name (up to 8 chars)
111 int ni = 0;
112 const unsigned char* p = (const unsigned char*)input;
113 while (*p && *p != '.' && ni < 8) {
114 out[ni++] = (char)toupper(*p++);
115 }
116 // If '.' found, copy extension (up to 3)
117 if (*p == '.') {
118 ++p;
119 int ei = 0;
120 while (*p && ei < 3) {
121 out[8 + ei++] = (char)toupper(*p++);
122 }
123 }
124}
125
133static FAT32_DIR_ENTRY* read_lfn(FAT32_DIR_ENTRY* cur, uint32_t remaining, char* out_name, uint32_t* out_consumed) {
134 if (!cur || remaining == 0) return NULL;
135 *out_consumed = 0;
136
137 // Collect LFN pointers (they appear immediately before the 8.3 entry).
139 uint32_t lfn_count = 0;
140 uint32_t i = 0;
141
142 // Walk forward while entries are LFN (0x0F). Stop when we hit a non-LFN or end.
143 while (i < remaining && ((uint8_t)cur[i].name[0] != 0x00) && (cur[i].attr == ATTR_LONG_NAME)) {
144 if (lfn_count < MAX_LFN_ENTRIES) lfn_list[lfn_count++] = &cur[i];
145 i++;
146 }
147
148 // i now points to the candidate 8.3 entry (must exist and not be end marker).
149 if (i >= remaining) return NULL;
150 FAT32_DIR_ENTRY* short_entry = &cur[i];
151 if ((uint8_t)short_entry->name[0] == 0x00 || (uint8_t)short_entry->name[0] == 0xE5) {
152 // no valid 8.3 here
153 return NULL;
154 }
155
156 // If no LFN entries collected, just copy short name into out_name and return short_entry.
157 if (lfn_count == 0) {
158 // Convert 11-byte SFN to human readable name "NAME.EXT"
159 const unsigned char* s = (const unsigned char*)short_entry->name;
160 int pos = 0;
161
162 // name part (0..7)
163 for (int n = 0; n < 8; ++n) {
164 if (s[n] == ' ') break;
165 if (pos < (int)MAX_LFN_LEN - 1) out_name[pos++] = s[n];
166 }
167
168 // extension (8..10)
169 bool has_ext = false;
170 for (int n = 8; n < 11; ++n) if (s[n] != ' ') { has_ext = true; break; }
171 if (has_ext) {
172 if (pos < (int)MAX_LFN_LEN - 1) out_name[pos++] = '.';
173 for (int n = 8; n < 11; ++n) {
174 if (s[n] == ' ') break;
175 if (pos < (int)MAX_LFN_LEN - 1) out_name[pos++] = s[n];
176 }
177 }
178
179 out_name[pos] = '\0';
180 *out_consumed = 1;
181 return short_entry;
182 }
183
184 // Validate checksum of short name against each LFN entry's checksum field (offset 13)
185 uint8_t cs = lfn_checksum((uint8_t*)short_entry->name);
186 for (uint32_t j = 0; j < lfn_count; ++j) {
187 uint8_t entry_checksum = *((uint8_t*)lfn_list[j] + 13);
188 if (entry_checksum != cs) return NULL; // mismatch -> invalid chain
189 }
190
191 // Reconstruct name: iterate lfn_list in reverse (last chunk -> first chunk)
192 uint32_t pos = 0;
193 for (int j = (int)lfn_count - 1; j >= 0; --j) {
194 uint8_t* ebytes = (uint8_t*)lfn_list[j];
195
196 // Name1 at offset 1, 5 UTF-16 chars
197 uint16_t* name1 = (uint16_t*)(ebytes + 1);
198 for (int c = 0; c < 5; ++c) {
199 uint16_t ch = name1[c];
200 if (ch == 0x0000) { out_name[pos] = '\0'; goto done; }
201 if (pos >= MAX_LFN_LEN - 1) { // Check BEFORE writing
202 goto done;
203 }
204 if (ch <= 0x7F) out_name[pos++] = (char)ch; else out_name[pos++] = '?';
205 }
206
207 // Name2 at offset 14, 6 UTF-16 chars
208 uint16_t* name2 = (uint16_t*)(ebytes + 14);
209 for (int c = 0; c < 6; ++c) {
210 uint16_t ch = name2[c];
211 if (ch == 0x0000) { out_name[pos] = '\0'; goto done; }
212 if (pos >= MAX_LFN_LEN - 1) {
213 goto done;
214 }
215 if (ch <= 0x7F) out_name[pos++] = (char)ch; else out_name[pos++] = '?';
216 }
217
218 // Name3 at offset 28, 2 UTF-16 chars
219 uint16_t* name3 = (uint16_t*)(ebytes + 28);
220 for (int c = 0; c < 2; ++c) {
221 uint16_t ch = name3[c];
222 if (ch == 0x0000) { out_name[pos] = '\0'; goto done; }
223 if (pos >= MAX_LFN_LEN - 1) {
224 goto done;
225 }
226 if (ch <= 0x7F) out_name[pos++] = (char)ch; else out_name[pos++] = '?';
227 }
228 }
229
230done:
231 out_name[pos] = '\0';
232 // consumed entries = number of LFN entries + the 8.3 entry
233 *out_consumed = (uint32_t)lfn_count + 1;
234 return short_entry;
235}
236
237static inline uint32_t fat32_total_clusters(void) {
238 return (bpb.total_sectors_32 - fs.first_data_sector) / fs.sectors_per_cluster;
239}
240
241// Read the FAT for the given cluster, to inspect data about this specific cluster, like which sectors are free, used, what's the next sector, and which sector are EOF (end of file = 0x0FFFFFFF)
242static uint32_t fat32_read_fat(uint32_t cluster) {
244
245 // Do not treat reserved clusters as "free" returned to callers that iterate the chain.
246 if (cluster < 2) {
247 if (isScanner) {
248 return FAT32_READ_ERROR;
249 }
250 return FAT32_EOC_MIN;
251 }
252
253 IRQL oldIrql;
254 MsAcquireSpinlock(&fat32_read_fat_lock, &oldIrql);
255
256 // allocate cache buffer onceW
257 if (!fat_cache_buf) {
258 fat_cache_buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, '1TAF');
259 if (!fat_cache_buf) {
260 gop_printf(0xFFFF0000, "fat32_read_fat: couldn't alloc cache buf\n");
261 MsReleaseSpinlock(&fat32_read_fat_lock, oldIrql);
262 if (isScanner) {
263 return FAT32_READ_ERROR;
264 }
265 return FAT32_EOC_MIN;
266 }
267 }
268
269 uint32_t fat_offset = cluster * 4;
270 uint32_t fat_sector = fs.fat_start + (fat_offset / fs.bytes_per_sector);
271 uint32_t ent_offset = fat_offset % fs.bytes_per_sector;
272 uint32_t bps = fs.bytes_per_sector;
273
274 // read sector only if different from cached one
275 if (fat_cache_sector != fat_sector) {
276 MTSTATUS st = read_sector(fat_sector, fat_cache_buf);
277 if (MT_FAILURE(st)) {
278 gop_printf(0xFFFF0000, "fat32_read_fat: read_sector fail for sector %u\n", fat_sector);
279 MsReleaseSpinlock(&fat32_read_fat_lock, oldIrql);
280 if (isScanner) {
281 return FAT32_READ_ERROR;
282 }
283 return FAT32_EOC_MIN;
284 }
285 fat_cache_sector = fat_sector;
286 }
287
288 uint32_t raw = 0;
289 uint32_t val = 0;
290
291 if (ent_offset <= bps - 4) {
292 /* entirely inside cached sector */
293 kmemcpy(&raw, (uint8_t*)fat_cache_buf + ent_offset, sizeof(raw));
294 raw = le32toh(raw);
295 val = raw & 0x0FFFFFFF;
296 }
297 else {
298 /* entry spans to next sector */
299 if (!fat_cache_buf2) {
301 if (!fat_cache_buf2) {
302 gop_printf(0xFFFF0000, "fat32_read_fat: couldn't alloc secondary cache buf\n");
303 MsReleaseSpinlock(&fat32_read_fat_lock, oldIrql);
304 if (isScanner) {
305 return FAT32_READ_ERROR;
306 }
307 return FAT32_EOC_MIN;
308 }
309 }
310
311 MTSTATUS st2 = read_sector(fat_sector + 1, fat_cache_buf2);
312 if (MT_FAILURE(st2)) {
313 gop_printf(0xFFFF0000, "fat32_read_fat: read_sector fail for next sector %u\n", fat_sector + 1);
314 MsReleaseSpinlock(&fat32_read_fat_lock, oldIrql);
315 if (isScanner) {
316 return FAT32_READ_ERROR;
317 }
318 return FAT32_EOC_MIN;
319 }
320
321 uint8_t tmp[4];
322 size_t first = bps - ent_offset; // bytes available in current sector
323 kmemcpy(tmp, (uint8_t*)fat_cache_buf + ent_offset, first);
324 kmemcpy(tmp + first, (uint8_t*)fat_cache_buf2, 4 - first);
325 kmemcpy(&raw, tmp, sizeof(raw));
326 raw = le32toh(raw);
327 val = raw & 0x0FFFFFFF;
328 }
329
330 /* diagnostic: use the computed raw (not a fresh read from cache which might be wrong if entry spanned) */
331 if (val == cluster) {
332 if (raw == 0) {
333 gop_printf(0xFFFF0000, "FAT suspicious: cluster=%u -> raw=0x%08x (ent_off=%u, fat_sector=%u, total=%u)\n",
334 cluster, raw, ent_offset, fat_sector, fat32_total_clusters());
335 MsReleaseSpinlock(&fat32_read_fat_lock, oldIrql);
336 if (isScanner) {
337 return FAT32_READ_ERROR;
338 }
339 return FAT32_EOC_MIN;
340 }
341 }
342
343 MsReleaseSpinlock(&fat32_read_fat_lock, oldIrql);
344 return val;
345}
346
347static inline uint32_t first_sector_of_cluster(uint32_t cluster) {
348 return fs.first_data_sector + (cluster - 2) * fs.sectors_per_cluster;
349}
350
351
352static bool fat32_write_fat(uint32_t cluster, uint32_t value) {
353 IRQL oldIrql;
354 MsAcquireSpinlock(&fat32_write_fat_lock, &oldIrql);
355 uint32_t fat_offset = cluster * 4;
356 uint32_t sec_index = fat_offset / fs.bytes_per_sector;
357 uint32_t ent_offset = fat_offset % fs.bytes_per_sector;
358 uint32_t bps = fs.bytes_per_sector;
359 if (bps == 0) { gop_printf(0xFFFF0000, "fat32_write_fat: bps==0!\n"); MsReleaseSpinlock(&fat32_write_fat_lock, oldIrql); return false; }
360 // We may need up to two buffers if the entry spans sectors.
361 void* buf1 = MmAllocatePoolWithTag(NonPagedPool, bps, '1FUB');
362 if (!buf1) {
363 MsReleaseSpinlock(&fat32_write_fat_lock, oldIrql);
364 return false;
365 }
366 gop_printf(0x00FF00FF, "fat32_write_fat: alloc buf1=%p bps=%u ent_off=%u sec=%u\n", buf1, bps, ent_offset, sec_index);
367 void* buf2 = NULL; // Allocate only if needed
368
369 bool spans = (ent_offset > bps - 4);
370 if (spans) {
371 buf2 = MmAllocatePoolWithTag(NonPagedPool, bps, 'fat');
372 if (!buf2) {
373 MmFreePool(buf1);
374 MsReleaseSpinlock(&fat32_write_fat_lock, oldIrql);
375 return false;
376 }
377 }
378
379 bool ok = true;
380 for (uint32_t fat_i = 0; fat_i < bpb.num_fats; ++fat_i) {
381 uint32_t current_fat_base = fs.fat_start + (fat_i * fs.sectors_per_fat);
382 uint32_t sector1_lba = current_fat_base + sec_index;
383 uint32_t sector2_lba = sector1_lba + 1;
384
385 if (spans) {
386 // Read both affected sectors
387 if (MT_FAILURE(read_sector(sector1_lba, buf1)) || MT_FAILURE(read_sector(sector2_lba, buf2))) {
388 ok = false;
389 break;
390 }
391
392 // Modify the two buffers
393 uint8_t value_bytes[4];
394 kmemcpy(value_bytes, &value, 4);
395
396 size_t first_part_size = bps - ent_offset;
397 size_t second_part_size = 4 - first_part_size;
398
399 kmemcpy((uint8_t*)buf1 + ent_offset, value_bytes, first_part_size);
400 kmemcpy(buf2, value_bytes + first_part_size, second_part_size);
401
402 // Write both sectors back
403 if (MT_FAILURE(write_sector(sector1_lba, buf1)) || MT_FAILURE(write_sector(sector2_lba, buf2))) {
404 ok = false;
405 break;
406 }
407 }
408 else {
409 // Entry is fully contained in one sector
410 if (MT_FAILURE(read_sector(sector1_lba, buf1))) { ok = false; break; }
411
412 // Read existing 4-byte raw entry safely (avoid unaligned direct deref)
413 uint32_t raw_le = 0;
414 kmemcpy(&raw_le, (uint8_t*)buf1 + ent_offset, sizeof(raw_le));
415 uint32_t raw = le32toh(raw_le);
416
417 // Modify only the low 28 bits per FAT32
418 raw = (raw & 0xF0000000) | (value & 0x0FFFFFFF);
419
420 // Write back in little-endian form
421 uint32_t new_le = le32toh(raw); // on little-endian this is a no-op; or define htole32 properly
422 kmemcpy((uint8_t*)buf1 + ent_offset, &new_le, sizeof(new_le));
423
424 if (MT_FAILURE(write_sector(sector1_lba, buf1))) { ok = false; break; }
425 }
426 }
427
428 MmFreePool(buf1);
429 if (buf2) {
430 MmFreePool(buf2);
431 }
432 MsReleaseSpinlock(&fat32_write_fat_lock, oldIrql);
433 return ok;
434}
435
436
437static inline uint32_t get_dir_cluster(FAT32_DIR_ENTRY* entry) {
438 return ((uint32_t)entry->fst_clus_hi << 16) | entry->fst_clus_lo;
439}
440
441// Free a cluster chain starting at start_cluster (set each entry to FREE)
442static bool fat32_free_cluster_chain(uint32_t start_cluster) {
443 if (start_cluster < 2 || start_cluster >= FAT32_EOC_MIN) return false;
444
445 uint32_t cur = start_cluster;
446 while (cur < FAT32_EOC_MIN) {
447 uint32_t next = fat32_read_fat(cur);
448 if (next == cur || next == 0) {
449 gop_printf(0xFFFF0000, "Detected FAT self-loop/zero at %u -> %u | %s\n", cur, next, __func__);
450 break; // fail gracefully
451 }
452 // mark current as free
453 if (!fat32_write_fat(cur, FAT32_FREE_CLUSTER)) return false;
454 // protect against pathological loops
455 if (next == cur) break;
456 cur = next;
457 }
458 return true;
459}
460
461static uint32_t fat32_find_free_cluster(void) {
462 // Atomically update.
464 // Start searching from cluster 2 (the first usable cluster)
465 // In a more advanced implementation, we would use the FSInfo sector to find a hint. But even then that hint can be misleading (read osdev on FAT)
466 uint32_t total_clusters = fat32_total_clusters();
467 uint32_t retval = 0;
468 for (uint32_t i = 2; i < total_clusters; i++) {
469 retval = fat32_read_fat(i);
470 if (retval == FAT32_FREE_CLUSTER) {
473 return i;
474 }
475 else if (retval == FAT32_READ_ERROR) {
477 continue;
478 }
479 }
482 return 0; // no free clusters found..
483}
484
485static bool zero_cluster(uint32_t cluster) {
486 void* buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'FUBF');
487 bool success = true;
488 if (!buf) return false;
489 kmemset(buf, 0, fs.bytes_per_sector);
490 MTSTATUS status;
491 uint32_t sector = first_sector_of_cluster(cluster);
492 for (uint32_t i = 0; i < fs.sectors_per_cluster; i++) {
493 status = write_sector(sector + i, buf);
494 if (MT_FAILURE(status)) {
495 success = false;
496 break;
497 }
498 }
499
500 MmFreePool(buf);
501 return success;
502}
503
504// Simple, strict compare: dir_name is on-disk 11 bytes, short_name is formatted 11 bytes
505static bool cmp_short_name(const char* dir_name, const char short_name[11]) {
506 for (int i = 0; i < 11; ++i) {
507 if ((unsigned char)dir_name[i] != (unsigned char)short_name[i]) return false;
508 }
509 return true;
510}
511
512// ASCII case-insensitive compare
513static inline bool ci_equal(const char* a, const char* b) {
514 size_t la = kstrlen(a);
515 size_t lb = kstrlen(b);
516 if (la != lb) return false;
517 for (size_t i = 0; i < la; ++i) {
518 if ((char)toupper((int)a[i]) != (char)toupper((int)b[i])) return false;
519 }
520 return true;
521}
522
524static uint32_t fat32_create_lfn_entries(FAT32_LFN_ENTRY* entry_buffer, const char* long_name, uint8_t sfn_checksum) {
525 uint32_t len = kstrlen(long_name);
526 uint32_t num_lfn_entries = (len + 12) / 13; // 13 chars per entry
527 uint32_t char_idx = 0;
528
529 for (int i = (int)num_lfn_entries - 1; i >= 0; --i) {
530 FAT32_LFN_ENTRY* lfn = &entry_buffer[i];
531
532 // Clear the entry
533 kmemset(lfn, 0, sizeof(*lfn));
534
535 uint8_t seq = (uint8_t)(num_lfn_entries - i);
536 if (i == (int)num_lfn_entries - 1)
537 seq |= 0x40; // last entry marker
538
539 lfn->LDIR_Ord = seq;
540 lfn->LDIR_Attr = 0x0F;
541 lfn->LDIR_Type = 0;
542 lfn->LDIR_Chksum = sfn_checksum;
543 lfn->LDIR_FstClusLO = 0;
544
545 // Fill name fields safely
546 for (int k = 0; k < 13; ++k) {
547 uint16_t uch = 0xFFFF;
548 if (char_idx < len)
549 uch = (uint8_t)long_name[char_idx];
550 else if (char_idx == len)
551 uch = 0x0000; // null terminator
552
553 if (k < 5)
554 lfn->LDIR_Name1[k] = uch;
555 else if (k < 11)
556 lfn->LDIR_Name2[k - 5] = uch;
557 else
558 lfn->LDIR_Name3[k - 11] = uch;
559
560 if (char_idx <= len)
561 ++char_idx;
562 }
563 }
564
565 return num_lfn_entries;
566}
567
568
576static bool fat32_find_entry(const char* path, FAT32_DIR_ENTRY* out_entry, uint32_t* out_parent_cluster) {
577 char path_copy[260];
578 kstrncpy(path_copy, path, sizeof(path_copy));
579
580 uint32_t current_cluster = fs.root_cluster;
581 uint32_t parent_cluster_of_last_found = fs.root_cluster;
582
583 if (kstrcmp(path_copy, "/") == 0 || path_copy[0] == '\0') {
584 if (out_entry) {
585 kmemset(out_entry, 0, sizeof(FAT32_DIR_ENTRY));
586 out_entry->attr = ATTR_DIRECTORY;
587 out_entry->fst_clus_lo = (uint16_t)(fs.root_cluster & 0xFFFF);
588 out_entry->fst_clus_hi = (uint16_t)(fs.root_cluster >> 16);
589 }
590 if (out_parent_cluster) *out_parent_cluster = fs.root_cluster;
591 return true;
592 }
593
594 FAT32_DIR_ENTRY last_found_entry;
595 kmemset(&last_found_entry, 0, sizeof(FAT32_DIR_ENTRY));
596 bool any_token_found = false;
597
598 char* save_ptr = NULL;
599 char* token = kstrtok_r(path_copy, "/", &save_ptr);
600
601 void* sector_buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'tecs');
602 if (!sector_buf) return false;
603
604 while (token != NULL) {
605 bool found_this_token = false;
606 parent_cluster_of_last_found = current_cluster;
607
608 uint32_t search_cluster = current_cluster;
609 do {
610 uint32_t sector = first_sector_of_cluster(search_cluster);
611 for (uint32_t i = 0; i < fs.sectors_per_cluster; i++) {
612 MTSTATUS status = read_sector(sector + i, sector_buf);
613 if (MT_FAILURE(status)) { MmFreePool(sector_buf); return false; }
614
615 FAT32_DIR_ENTRY* entries = (FAT32_DIR_ENTRY*)sector_buf;
616 uint32_t num_entries = fs.bytes_per_sector / sizeof(FAT32_DIR_ENTRY);
617
618 for (uint32_t j = 0; j < num_entries; ) {
619 if (entries[j].name[0] == END_OF_DIRECTORY) {
620 goto next_cluster; // Break inner loop, continue with next cluster
621 }
622 if ((uint8_t)entries[j].name[0] == DELETED_DIR_ENTRY) { j++; continue; }
623
624 char lfn_buf[MAX_LFN_LEN];
625 uint32_t consumed = 0;
626 FAT32_DIR_ENTRY* sfn = read_lfn(&entries[j], num_entries - j, lfn_buf, &consumed);
627
628 if (sfn) {
629 // Using ci_equal for simplicity, assuming it does case-insensitive compare
630 if (ci_equal(lfn_buf, token)) {
631 kmemcpy(&last_found_entry, sfn, sizeof(FAT32_DIR_ENTRY));
632 found_this_token = true;
633 current_cluster = (sfn->fst_clus_hi << 16) | sfn->fst_clus_lo;
634 goto token_search_done; // Break all search loops for this token
635 }
636 }
637 j += (consumed > 0) ? consumed : 1;
638 }
639 }
640 next_cluster:
641 search_cluster = fat32_read_fat(search_cluster);
642 } while (search_cluster < FAT32_EOC_MIN);
643
644 token_search_done:
645 MmFreePool(sector_buf); // free buffer
646
647 if (!found_this_token) {
648 return false; // Path component not found
649 }
650 any_token_found = true;
651 token = kstrtok_r(NULL, "/", &save_ptr);
652
653 if (token != NULL && !(last_found_entry.attr & ATTR_DIRECTORY)) {
654 return false; // Trying to traverse into a file
655 }
656 }
657
658 if (any_token_found) {
659 if (out_entry) kmemcpy(out_entry, &last_found_entry, sizeof(FAT32_DIR_ENTRY));
660 if (out_parent_cluster) *out_parent_cluster = parent_cluster_of_last_found;
661 return true;
662 }
663
664 return false;
665}
666
667static bool fat32_extend_directory(uint32_t dir_cluster) {
668 uint32_t new_cluster = fat32_find_free_cluster();
669 if (new_cluster == 0) return false;
670
671 // Zero out the new cluster
672 if (!zero_cluster(new_cluster)) {
673 fat32_write_fat(new_cluster, FAT32_FREE_CLUSTER); // Free it back
674 return false;
675 }
676
677 fat32_write_fat(new_cluster, FAT32_EOC_MAX); // Mark as new end of chain
678
679 // Find the last cluster in the original chain and link it to the new one
680 uint32_t current = dir_cluster;
681 uint32_t next = 0;
682 while ((next = fat32_read_fat(current)) < FAT32_EOC_MIN) {
683 current = next;
684 }
685
686 return fat32_write_fat(current, new_cluster);
687}
688
689static bool fat32_find_free_dir_slots(uint32_t dir_cluster, uint32_t count, uint32_t* out_sector, uint32_t* out_entry_index) {
690 uint32_t current_cluster = dir_cluster;
691 void* sector_buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'tecs');
692 if (!sector_buf) return false;
693 MTSTATUS status;
694
695 do {
696 uint32_t sector_lba = first_sector_of_cluster(current_cluster);
697 for (uint32_t i = 0; i < fs.sectors_per_cluster; i++) {
698 status = read_sector(sector_lba + i, sector_buf);
699 if (MT_FAILURE(status)) { MmFreePool(sector_buf); return false; }
700
701 FAT32_DIR_ENTRY* entries = (FAT32_DIR_ENTRY*)sector_buf;
702 uint32_t num_entries = fs.bytes_per_sector / sizeof(FAT32_DIR_ENTRY);
703
704 // --- CRITICAL FIX: Reset counter for each new sector ---
705 uint32_t consecutive_free = 0;
706
707 for (uint32_t j = 0; j < num_entries; j++) {
708 uint8_t first_byte = (uint8_t)entries[j].name[0];
709 if (first_byte == END_OF_DIRECTORY || first_byte == DELETED_DIR_ENTRY) {
710 if (consecutive_free == 0) {
711 // Mark the start of a potential block
712 *out_sector = sector_lba + i;
713 *out_entry_index = j;
714 }
715 consecutive_free++;
716 if (consecutive_free == count) {
717 MmFreePool(sector_buf);
718 return true;
719 }
720 }
721 else {
722 consecutive_free = 0;
723 }
724 }
725 }
726
727 uint32_t next_cluster = fat32_read_fat(current_cluster);
728 if (next_cluster >= FAT32_EOC_MIN) {
729 MmFreePool(sector_buf);
730 if (fat32_extend_directory(dir_cluster)) {
731 return fat32_find_free_dir_slots(dir_cluster, count, out_sector, out_entry_index);
732 }
733 else {
734 return false;
735 }
736 }
737 current_cluster = next_cluster;
738 } while (true);
739
740 MmFreePool(sector_buf);
741 return false;
742}
743
744static MTSTATUS fat32_update_file_entry(const char* path, uint32_t start_cluster, uint32_t new_size, bool update_cluster, bool update_size) {
745 char path_copy[260];
746 kstrncpy(path_copy, path, sizeof(path_copy));
747
748 if (kstrcmp(path_copy, "/") == 0 || path_copy[0] == '\0') return MT_INVALID_PARAM;
749
750 uint32_t current_cluster = fs.root_cluster;
751 char* save_ptr = NULL;
752 char* token = kstrtok_r(path_copy, "/", &save_ptr);
753
754 // Buffer for 2 sectors
755 uint32_t buf_size = fs.bytes_per_sector * 2;
756 void* big_buf = MmAllocatePoolWithTag(NonPagedPool, buf_size, 'UPDT');
757 if (!big_buf) return MT_NO_MEMORY;
758
759 while (token != NULL) {
760 char* next_token = kstrtok_r(NULL, "/", &save_ptr);
761 bool is_target_file = (next_token == NULL);
762 bool found_component = false;
763 uint32_t search_cluster = current_cluster;
764
765 do {
766 uint32_t sector_lba = first_sector_of_cluster(search_cluster);
767
768 for (uint32_t i = 0; i < fs.sectors_per_cluster; i++) {
769 // Read Current Sector
770 MTSTATUS st = read_sector(sector_lba + i, big_buf);
771 if (MT_FAILURE(st)) { MmFreePool(big_buf); return st; }
772
773 // Read Next Sector for spanning LFNs
774 if (i < fs.sectors_per_cluster - 1) {
775 read_sector(sector_lba + i + 1, (uint8_t*)big_buf + fs.bytes_per_sector);
776 }
777 else {
778 kmemset((uint8_t*)big_buf + fs.bytes_per_sector, 0, fs.bytes_per_sector);
779 }
780
781 FAT32_DIR_ENTRY* entries = (FAT32_DIR_ENTRY*)big_buf;
782 uint32_t num_entries_to_scan = fs.bytes_per_sector / sizeof(FAT32_DIR_ENTRY);
783 uint32_t total_entries_in_buf = buf_size / sizeof(FAT32_DIR_ENTRY);
784
785 for (uint32_t j = 0; j < num_entries_to_scan; ) {
786 if (entries[j].name[0] == END_OF_DIRECTORY) goto cluster_done;
787 if ((uint8_t)entries[j].name[0] == DELETED_DIR_ENTRY) { j++; continue; }
788
789 char lfn_buf[MAX_LFN_LEN];
790 uint32_t consumed = 0;
791 FAT32_DIR_ENTRY* sfn = read_lfn(&entries[j], total_entries_in_buf - j, lfn_buf, &consumed);
792
793 if (sfn) {
794 if (ci_equal(lfn_buf, token)) {
795 if (is_target_file) {
796 // --- FOUND THE ENTRY, UPDATE IT ---
797
798 // 1. Update Cluster if requested
799 if (update_cluster) {
800 sfn->fst_clus_hi = (uint16_t)((start_cluster >> 16) & 0xFFFF);
801 sfn->fst_clus_lo = (uint16_t)(start_cluster & 0xFFFF);
802 }
803
804 // 2. Update Size if requested
805 if (update_size) {
806 sfn->file_size = new_size;
807 }
808
809 // Calculate offset and write back
810 uintptr_t offset = (uintptr_t)sfn - (uintptr_t)big_buf;
811 uint32_t target_sector = sector_lba + i;
812 void* write_source = big_buf;
813
814 if (offset >= fs.bytes_per_sector) {
815 target_sector = sector_lba + i + 1;
816 write_source = (uint8_t*)big_buf + fs.bytes_per_sector;
817 }
818
819 MTSTATUS wr_st = write_sector(target_sector, write_source);
820 MmFreePool(big_buf);
821 return wr_st;
822 }
823 else {
824 current_cluster = ((uint32_t)sfn->fst_clus_hi << 16) | sfn->fst_clus_lo;
825 found_component = true;
826 goto token_next;
827 }
828 }
829 }
830 j += (consumed > 0) ? consumed : 1;
831 }
832 }
833 search_cluster = fat32_read_fat(search_cluster);
834 } while (search_cluster < FAT32_EOC_MIN);
835
836 cluster_done:
837 if (!found_component) {
838 MmFreePool(big_buf);
839 return MT_NOT_FOUND;
840 }
841
842 token_next:
843 token = next_token;
844 }
845
846 MmFreePool(big_buf);
847 return MT_NOT_FOUND;
848}
849
850#define BPB_SECTOR_START 2048
851
852// Read BPB (Bios Parameter Block) and initialize.
853MTSTATUS fat32_init(int disk_index) {
854 MTSTATUS status;
855 disk = get_block_device(disk_index);
856 if (!disk) { return MT_GENERAL_FAILURE; }
857
858 void* buf = MmAllocatePoolWithTag(NonPagedPool, 512, 'TAF');
859 if (!buf) return MT_NO_MEMORY;
860 status = read_sector(BPB_SECTOR_START, buf);
861 if (MT_FAILURE(status)) { return status; } // First sector contains the BPB for FAT.
862 kmemcpy(&bpb, buf, sizeof(bpb)); // So copy that first sector into our local BPB structure.
863
864 // Then initialize it.
865 fs.bytes_per_sector = bpb.bytes_per_sector;
866 fs.sectors_per_cluster = bpb.sectors_per_cluster;
867 fs.reserved_sector_count = bpb.reserved_sector_count;
868 fs.sectors_per_fat = bpb.fat_size_32;
869 fs.root_cluster = bpb.root_cluster;
870 fs.fat_start = BPB_SECTOR_START + bpb.reserved_sector_count; // technically also reserved_sector_count of fs. holds it as well.
871 fs.first_data_sector = fs.fat_start + bpb.num_fats * fs.sectors_per_fat;
872 MmFreePool(buf);
873 return MT_SUCCESS;
874}
875
876// Walk cluster chain and read directory entries.
877void fat32_list_root(void) {
878 uint32_t cluster = fs.root_cluster;
879
880 void* buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'fatb');
881 if (!buf) return;
882
883 // Temp buffer to accumulate LFN entries (and eventually the 8.3 entry).
884 FAT32_DIR_ENTRY temp_entries[MAX_LFN_ENTRIES + 1];
885 uint32_t lfn_accum = 0;
886 MTSTATUS status;
887 do {
888 uint32_t sector = first_sector_of_cluster(cluster);
889 for (uint32_t i = 0; i < fs.sectors_per_cluster; ++i) {
890 status = read_sector(sector + i, buf);
891 if (MT_FAILURE(status)) {
892 MmFreePool(buf);
893 return;
894 }
895 FAT32_DIR_ENTRY* dir = (FAT32_DIR_ENTRY*)buf;
896 uint32_t entries = fs.bytes_per_sector / sizeof(*dir);
897
898 for (uint32_t j = 0; j < entries; ++j, ++dir) {
899 uint8_t first = (uint8_t)dir->name[0];
900
901 // End of directory: stop everything
902 if (first == 0x00) {
903 MmFreePool(buf);
904 return;
905 }
906
907 // Deleted entry: if we were accumulating an LFN chain, drop it.
908 if (first == 0xE5) {
909 lfn_accum = 0;
910 continue;
911 }
912
913 // If it's an LFN entry, copy it into the temp accumulator (preserve order read-on-disk)
914 if (dir->attr == ATTR_LONG_NAME) {
915 if (lfn_accum < MAX_LFN_ENTRIES) {
916 kmemcpy(&temp_entries[lfn_accum], dir, sizeof(FAT32_DIR_ENTRY));
917 lfn_accum++;
918 }
919 else {
920 // too many parts: drop accumulator to avoid overflow
921 lfn_accum = 0;
922 }
923 continue;
924 }
925 // Non-LFN entry: this is the 8.3 entry that ends any preceding LFN chain (if present).
926 // If we have accumulated LFN entries, build a contiguous array: [LFN...][8.3]
927 char bufferLfn[MAX_LFN_LEN];
928 uint32_t consumed = 0;
929 FAT32_DIR_ENTRY* real = NULL;
930
931 if (lfn_accum > 0) {
932 // copy the 8.3 entry as the last element
933 kmemcpy(&temp_entries[lfn_accum], dir, sizeof(FAT32_DIR_ENTRY));
934 // call read_lfn on our temp buffer (which starts with LFN entries)
935 real = read_lfn(temp_entries, lfn_accum + 1, bufferLfn, &consumed);
936 // reset accumulator regardless (we've handled or attempted to)
937 lfn_accum = 0;
938 }
939 else {
940 // No accumulated LFN entries: handle short-name-only entry
941 // We can call read_lfn directly on the sector buffer at current position
942 uint32_t remaining = entries - j;
943 real = read_lfn(&dir[0], remaining, bufferLfn, &consumed);
944 // note: consumed will be at least 1 if successful
945 }
946
947 // If read_lfn returned a real 8.3 entry (it should), print the name
948 if (real) {
949 gop_printf(0xFF00FFFF, "Found: %s\n", bufferLfn);
950 }
951 else {
952 // Fallback: if read_lfn failed for some reason, print raw 8.3 as a last resort
953 char fallback[12];
954 for (int k = 0; k < 11; ++k) fallback[k] = dir->name[k];
955 fallback[11] = '\0';
956 gop_printf(0xFF00FFFF, "Found (raw): %s\n", fallback);
957 }
958
959 // continue to next entry (we already advanced j by loop)
960 } // for each dir entry in sector
961 } // for each sector in cluster
962
963 cluster = fat32_read_fat(cluster);
964 } while (cluster < FAT32_EOC_MIN);
965}
966
967// Helper to detect if a filename has a slash in it (/), and so the filename is in a directory
968static bool is_filename_in_dir(const char* filename) {
969 if (!filename) return false;
970
971 while (*filename) {
972 if (*filename == '/') return true;
973 filename++;
974 }
975
976 return false;
977}
978
979static uint32_t extract_dir_cluster(const char* filename) {
980
981 if (!filename || filename[0] == '\0') return fs.root_cluster;
982
983 // Make a mutable copy
984 char path_copy[260];
985 kstrncpy(path_copy, filename, sizeof(path_copy));
986
987 // Remove trailing slashes (keep a single leading '/' if path is "/")
988 int len = (int)kstrlen(path_copy);
989 while (len > 1 && path_copy[len - 1] == '/') {
990 path_copy[len - 1] = '\0';
991 len--;
992 }
993
994 // Find last slash
995 int last_slash = -1;
996 for (int i = len - 1; i >= 0; --i) {
997 if (path_copy[i] == '/') { last_slash = i; break; }
998 }
999
1000 // No slash -> file is in root
1001 if (last_slash == -1) {
1002 return fs.root_cluster;
1003 }
1004
1005 // Parent path is "/" if last_slash == 0, otherwise substring [0..last_slash-1]
1006 char parent[260];
1007 if (last_slash == 0) {
1008 parent[0] = '/';
1009 parent[1] = '\0';
1010 }
1011 else {
1012 // copy up to last_slash
1013 for (int i = 0; i < last_slash; ++i) parent[i] = path_copy[i];
1014 parent[last_slash] = '\0';
1015 }
1016
1017 // Resolve the parent path to a directory entry
1018 FAT32_DIR_ENTRY parent_entry;
1019 if (!fat32_find_entry(parent, &parent_entry, NULL)) return 0;
1020
1021 // Ensure it's a directory
1022 if (!(parent_entry.attr & ATTR_DIRECTORY)) return 0;
1023
1024 uint32_t cluster = ((uint32_t)parent_entry.fst_clus_hi << 16) | parent_entry.fst_clus_lo;
1025 if (cluster == 0) cluster = fs.root_cluster; // safety fallback
1026 return cluster;
1027}
1028
1030 IN PFILE_OBJECT FileObject,
1031 IN uint64_t FileOffset,
1032 OUT void* Buffer,
1033 IN size_t BufferSize,
1034 _Out_Opt size_t* BytesRead
1035)
1036{
1037 if (BytesRead) *BytesRead = 0;
1038 if (BufferSize == 0) return MT_SUCCESS;
1039
1040 uint32_t bytes_per_sector = fs.bytes_per_sector;
1041 uint32_t sectors_per_cluster = fs.sectors_per_cluster;
1042 uint32_t cluster_size = bytes_per_sector * sectors_per_cluster;
1043
1044 // Walk the FAT chain until we reach thee desired cluster of the file offset.
1045 uint32_t current_cluster = (uint32_t)(uintptr_t)FileObject->FsContext;
1046 uint32_t clusters_to_skip = FileOffset / cluster_size;
1047
1048 for (uint32_t i = 0; i < clusters_to_skip; i++) {
1049 current_cluster = fat32_read_fat(current_cluster);
1050 if (current_cluster >= FAT32_EOC_MIN) {
1051 return MT_FAT32_EOF;
1052 }
1053 }
1054
1055 // FileOffset is good, we can set it in the file object.
1056 FileObject->CurrentOffset = FileOffset;
1057
1058 // We will need an intermediate buffer ONLY IF the buffer given is less than the sector size (which is what DMA reads, we have to make it sector aligned in DMA reads)
1059 // to avoid buffer overflows.
1060 void* IntermediateBuffer = MmAllocatePoolWithTag(NonPagedPool, bytes_per_sector, 'BTAF');
1061 if (!IntermediateBuffer) {
1062 return MT_NO_MEMORY;
1063 }
1064
1065 size_t total_bytes_read = 0;
1066 size_t bytes_left = BufferSize;
1067 uint32_t current_file_offset = FileOffset;
1068 uint8_t* current_buffer_ptr = (uint8_t*)Buffer;
1069 MTSTATUS status = MT_SUCCESS;
1070
1071 while (bytes_left > 0) {
1072 // Calculate the LBA for the current cluster position.
1073 uint32_t offset_in_cluster = current_file_offset % cluster_size;
1074 uint32_t sector_index_in_cluster = offset_in_cluster / bytes_per_sector;
1075
1076 uint32_t lba = fs.first_data_sector + ((current_cluster - 2) * sectors_per_cluster) + sector_index_in_cluster;
1077
1078 // Determine offsets within this specific sector
1079 uint32_t offset_in_sector = current_file_offset % bytes_per_sector;
1080 uint32_t bytes_available_in_sector = bytes_per_sector - offset_in_sector;
1081
1082 // We can only read as much as fits in the sector OR as much as the caller asked for
1083 size_t bytes_to_copy = (bytes_left < bytes_available_in_sector) ? bytes_left : bytes_available_in_sector;
1084
1085 // If its an unaligned read (more bytes than we can fit), we use the intermediate buffer for this.
1086 bool direct_read = (offset_in_sector == 0) && (bytes_left >= bytes_per_sector);
1087
1088 void* target_buf = direct_read ? current_buffer_ptr : IntermediateBuffer;
1089
1090 status = read_sector(lba, target_buf);
1091
1092 if (MT_FAILURE(status)) {
1093 // Read failed
1094 break;
1095 }
1096
1097 // Copy data if this was to the intermediate buffer. (not a direct read to caller buffer)
1098 if (!direct_read) {
1099 kmemcpy(current_buffer_ptr, (uint8_t*)IntermediateBuffer + offset_in_sector, bytes_to_copy);
1100 }
1101
1102 // Advance Pointers.
1103 total_bytes_read += bytes_to_copy;
1104 bytes_left -= bytes_to_copy;
1105 current_buffer_ptr += bytes_to_copy;
1106 current_file_offset += bytes_to_copy;
1107
1108 // if the new offset is directly at a cluster boundary (end of cluster) we cannot read it since it would go to a different cluster..
1109 // We need to read the next cluster and use it.
1110 if (bytes_left > 0 && (current_file_offset % cluster_size) == 0) {
1111 current_cluster = fat32_read_fat(current_cluster);
1112
1113 // Check for EOF (End of Chain)
1114 if (current_cluster >= FAT32_EOC_MIN) {
1115 // Technically an error if we expected more data but hit EOF
1116 status = MT_FAT32_EOF;
1117 break;
1118 }
1119 }
1120 }
1121
1122 // 4. Cleanup and Return
1123 if (IntermediateBuffer) {
1124 MmFreePool(IntermediateBuffer);
1125 }
1126
1127 if (BytesRead) {
1128 *BytesRead = total_bytes_read;
1129 }
1130
1131 // If we read some bytes but hit EOF/Error later, we usually return success with partial count,
1132 // or the specific error. Here we return the last status.
1133 return status;
1134}
1135
1137 // Check if an entry already exists at this path
1138 if (fat32_find_entry(path, NULL, NULL)) {
1139#ifdef DEBUG
1140 gop_printf(0xFFFF0000, "Error: Path '%s' already exists.\n", path);
1141#endif
1143 }
1145 // Separate parent path and new directory name
1146 char path_copy[260];
1147 kstrncpy(path_copy, path, sizeof(path_copy));
1148
1149 char* new_dir_name = NULL;
1150 char* parent_path = "/";
1151
1152 // Remove trailing slashes (except if path is just "/")
1153 int len = kstrlen(path_copy);
1154 while (len > 1 && path_copy[len - 1] == '/') {
1155 path_copy[len - 1] = '\0';
1156 len--;
1157 }
1158
1159 // Find last slash
1160 int last_slash = -1;
1161 for (int i = 0; path_copy[i] != '\0'; i++) {
1162 if (path_copy[i] == '/') last_slash = i;
1163 }
1164
1165 // Split parent path and new directory name
1166 if (last_slash != -1) {
1167 new_dir_name = &path_copy[last_slash + 1]; // name after last slash
1168 if (last_slash > 0) {
1169 path_copy[last_slash] = '\0'; // terminate parent path
1170 parent_path = path_copy;
1171 }
1172 // If last_slash == 0, parent_path stays "/"
1173 }
1174 else {
1175 // No slashes at all: directory is in root
1176 new_dir_name = path_copy;
1177 }
1178
1179
1180 // Find the parent directory cluster
1181 FAT32_DIR_ENTRY parent_entry;
1182 uint32_t parent_cluster;
1183 if (!fat32_find_entry(parent_path, &parent_entry, NULL)) {
1184#ifdef DEBUG
1185 gop_printf(0xFFFF0000, "Error: Parent path '%s' not found.\n", parent_path);
1186#endif
1188 }
1189 if (!(parent_entry.attr & ATTR_DIRECTORY)) {
1190#ifdef DEBUG
1191 gop_printf(0xFFFF0000, "Error: Parent path is not a directory. PATH: %s\n", parent_path);
1192#endif
1194 }
1195 parent_cluster = (parent_entry.fst_clus_hi << 16) | parent_entry.fst_clus_lo;
1196
1197 // Allocate a new cluster for this directory's contents
1198 uint32_t new_cluster = fat32_find_free_cluster();
1199 if (new_cluster == 0) return MT_FAT32_CLUSTERS_FULL;
1200
1201 fat32_write_fat(new_cluster, FAT32_EOC_MAX);
1202 zero_cluster(new_cluster);
1203
1204 // Create '.' and '..' entries in the new cluster
1205 void* sector_buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'fat');
1206 if (!sector_buf) { /* handle error */ return MT_MEMORY_LIMIT; }
1207 kmemset(sector_buf, 0, fs.bytes_per_sector);
1208 FAT32_DIR_ENTRY* dot_entries = (FAT32_DIR_ENTRY*)sector_buf;
1209
1210 kmemcpy(dot_entries[0].name, ". ", 11);
1211 dot_entries[0].attr = ATTR_DIRECTORY;
1212 dot_entries[0].fst_clus_lo = (uint16_t)new_cluster;
1213 dot_entries[0].fst_clus_hi = (uint16_t)(new_cluster >> 16);
1214
1215 kmemcpy(dot_entries[1].name, ".. ", 11);
1216 dot_entries[1].attr = ATTR_DIRECTORY;
1217 dot_entries[1].fst_clus_lo = (uint16_t)parent_cluster;
1218 dot_entries[1].fst_clus_hi = (uint16_t)(parent_cluster >> 16);
1219
1220 write_sector(first_sector_of_cluster(new_cluster), sector_buf);
1221
1222 // Create the entry in the parent directory
1223 // For simplicity, we'll use a simple SFN.
1224 char sfn[11];
1225 format_short_name(new_dir_name, sfn);
1226
1227 // Decide whether we need LFN entries:
1228 int name_len = kstrlen(new_dir_name);
1229 int need_lfn = 0;
1230 if (name_len > 11) need_lfn = 1;
1231 else {
1232 for (int i = 0; i < name_len; i++) {
1233 char c = new_dir_name[i];
1234 if (c >= 'a' && c <= 'z') { need_lfn = 1; break; }
1235 }
1236 }
1237
1238 uint32_t entry_sector = 0, entry_index = 0;
1239
1240 if (need_lfn) {
1241 // Create LFN + SFN (allocate contiguous slots)
1242 uint8_t checksum = lfn_checksum((uint8_t*)sfn);
1243 uint32_t num_lfn_entries = (name_len + 12) / 13;
1244 uint32_t total_slots = num_lfn_entries + 1; // LFN entries + SFN
1245
1246 if (!fat32_find_free_dir_slots(parent_cluster, total_slots, &entry_sector, &entry_index)) {
1247 // free sector_buf, free cluster...
1248 MmFreePool(sector_buf);
1249 fat32_write_fat(new_cluster, FAT32_FREE_CLUSTER);
1250 return MT_FAT32_DIR_FULL;
1251 }
1252
1253 // Prepare temporary buffer with LFN entries followed by SFN
1254 FAT32_LFN_ENTRY* temp_entries = (FAT32_LFN_ENTRY*)MmAllocatePoolWithTag(NonPagedPool, total_slots * sizeof(FAT32_LFN_ENTRY), 'fat');
1255 if (!temp_entries) {
1256 MmFreePool(sector_buf);
1257 fat32_write_fat(new_cluster, FAT32_FREE_CLUSTER);
1258 return MT_MEMORY_LIMIT;
1259 }
1260
1261 kmemset(temp_entries, 0, total_slots * sizeof(FAT32_LFN_ENTRY));
1262
1263 // Fill LFN entries into temp_entries[0 .. num_lfn_entries-1]
1264 fat32_create_lfn_entries(temp_entries, new_dir_name, checksum);
1265
1266 // Fill SFN at the end
1267 FAT32_DIR_ENTRY* sfn_entry = (FAT32_DIR_ENTRY*)&temp_entries[num_lfn_entries];
1268 kmemset(sfn_entry, 0, sizeof(FAT32_DIR_ENTRY));
1269 kmemcpy(sfn_entry->name, sfn, 11);
1270 sfn_entry->attr = ATTR_DIRECTORY;
1271 sfn_entry->fst_clus_lo = (uint16_t)new_cluster;
1272 sfn_entry->fst_clus_hi = (uint16_t)(new_cluster >> 16);
1273
1274 // Write temp_entries sequentially into parent directory slots (may span sectors)
1275 const int entries_per_sector = fs.bytes_per_sector / sizeof(FAT32_DIR_ENTRY);
1276 uint32_t cur_sector = entry_sector;
1277 int cur_index = (int)entry_index;
1278 uint32_t remaining = total_slots;
1279 uint32_t temp_idx = 0;
1280
1281 while (remaining > 0) {
1282 status = read_sector(cur_sector, sector_buf);
1283 if (MT_FAILURE(status)) {
1284 // cleanup: free temp, free sector_buf, free cluster
1285 MmFreePool(temp_entries);
1286 MmFreePool(sector_buf);
1287 fat32_write_fat(new_cluster, FAT32_FREE_CLUSTER);
1288 return status;
1289 }
1290
1291 int can = entries_per_sector - cur_index;
1292 int to_write = (remaining < (uint32_t)can) ? remaining : (uint32_t)can;
1293
1294 for (int j = 0; j < to_write; j++) {
1295 FAT32_DIR_ENTRY* dst = &((FAT32_DIR_ENTRY*)sector_buf)[cur_index + j];
1296 FAT32_DIR_ENTRY* src = (FAT32_DIR_ENTRY*)&temp_entries[temp_idx + j];
1297 kmemcpy(dst, src, sizeof(FAT32_DIR_ENTRY));
1298 }
1299
1300 status = write_sector(cur_sector, sector_buf);
1301 if (MT_FAILURE(status)) {
1302 MmFreePool(temp_entries);
1303 MmFreePool(sector_buf);
1304 fat32_write_fat(new_cluster, FAT32_FREE_CLUSTER);
1305 return status;
1306 }
1307
1308 remaining -= to_write;
1309 temp_idx += to_write;
1310 cur_sector++;
1311 cur_index = 0;
1312 }
1313
1314 // cleanup temp buffer
1315 MmFreePool(temp_entries);
1316 // free sector_buf and return last write status
1317 MmFreePool(sector_buf);
1318 return status;
1319 }
1320 else {
1321 // No LFN needed: simple single-slot SFN write (original behaviour)
1322 if (!fat32_find_free_dir_slots(parent_cluster, 1, &entry_sector, &entry_index)) {
1323 // free sector_buf, free cluster...
1324 MmFreePool(sector_buf);
1325 fat32_write_fat(new_cluster, FAT32_FREE_CLUSTER);
1326 return MT_FAT32_DIR_FULL;
1327 }
1328
1329 // Read the target sector, modify it, write it back
1330 status = read_sector(entry_sector, sector_buf);
1331 if (MT_FAILURE(status)) { MmFreePool(sector_buf); return status; }
1332
1333 FAT32_DIR_ENTRY* new_entry = &((FAT32_DIR_ENTRY*)sector_buf)[entry_index];
1334 kmemset(new_entry, 0, sizeof(FAT32_DIR_ENTRY));
1335 kmemcpy(new_entry->name, sfn, 11);
1336 new_entry->attr = ATTR_DIRECTORY;
1337 new_entry->fst_clus_lo = (uint16_t)new_cluster;
1338 new_entry->fst_clus_hi = (uint16_t)(new_cluster >> 16);
1339
1340 status = write_sector(entry_sector, sector_buf);
1341
1342 // free sector_buf
1343 MmFreePool(sector_buf);
1344 return status;
1345 }
1346}
1347
1348static TIME_ENTRY convertFat32ToRealtime(uint16_t fat32Time, uint16_t fat32Date) {
1349 TIME_ENTRY time;
1350 uint8_t h, m, s;
1351 uint8_t mon, day;
1352 uint16_t y;
1353 fat32_decode_date(fat32Date, &y, &mon, &day);
1354 fat32_decode_time(fat32Time, &h, &m, &s);
1355 time.hour = h;
1356 time.minute = m;
1357 time.second = s;
1358 time.month = mon;
1359 time.day = day;
1360 time.year = y;
1361 return time;
1362}
1363
1365 IN PFILE_OBJECT FileObject,
1366 IN uint64_t FileOffset,
1367 IN void* Buffer,
1368 IN size_t BufferSize,
1369 _Out_Opt size_t* BytesWritten
1370)
1371{
1372 if (BytesWritten) *BytesWritten = 0;
1373 if (BufferSize == 0) return MT_SUCCESS;
1374
1375 uint32_t bytes_per_sector = fs.bytes_per_sector;
1376 uint32_t sectors_per_cluster = fs.sectors_per_cluster;
1377 uint32_t cluster_size = bytes_per_sector * sectors_per_cluster;
1378 MTSTATUS status = MT_SUCCESS;
1379
1380 // If we do not have a cluster for the file, we allocate it.
1381 if (FileObject->FsContext == 0) {
1382 // Allocate a new cluster
1383 uint32_t first_cluster = fat32_find_free_cluster();
1384 if (first_cluster == 0) {
1386 }
1387
1388 // Mark it as End-of-Chain (EOC) immediately
1389 fat32_write_fat(first_cluster, FAT32_EOC_MAX);
1390
1391 // Zero the cluster to avoid leaking old data
1392 zero_cluster(first_cluster);
1393
1394 // Update entry on disk so it points to new cluster.
1395 MTSTATUS update_st = fat32_update_file_entry(FileObject->FileName, first_cluster, 0, true, false);
1396
1397 if (MT_FAILURE(update_st)) {
1398 // Cleanup: If we couldn't update the directory, we should free the allocated cluster
1399 fat32_write_fat(first_cluster, FAT32_FREE_CLUSTER);
1400 return update_st;
1401 }
1402
1403 // 5. Update the File Object in memory
1404 FileObject->FsContext = (void*)(uintptr_t)first_cluster;
1405 }
1406
1407 // Seek to the correct cluster based on FileOffset
1408 uint32_t current_cluster = (uint32_t)(uintptr_t)FileObject->FsContext;
1409 uint32_t clusters_to_skip = FileOffset / cluster_size;
1410
1411 for (uint32_t i = 0; i < clusters_to_skip; i++) {
1412 current_cluster = fat32_read_fat(current_cluster);
1413
1414 // Looks like we hit EOF.
1415 if (current_cluster >= FAT32_EOC_MIN) {
1416 return MT_FAT32_EOF;
1417 }
1418 }
1419
1420 // Intermediate buffer use exactly like in fat32_read_file
1421 void* IntermediateBuffer = MmAllocatePoolWithTag(NonPagedPool, bytes_per_sector, 'BTAF');
1422 if (!IntermediateBuffer) {
1423 return MT_NO_MEMORY;
1424 }
1425
1426 size_t total_bytes_written = 0;
1427 size_t bytes_left = BufferSize;
1428 uint32_t current_file_offset = FileOffset;
1429 const uint8_t* src_buffer_ptr = (const uint8_t*)Buffer;
1430
1431 // Write loop
1432 while (bytes_left > 0) {
1433 // Calculate LBA for current position
1434 uint32_t offset_in_cluster = current_file_offset % cluster_size;
1435 uint32_t sector_index_in_cluster = offset_in_cluster / bytes_per_sector;
1436 uint32_t lba = fs.first_data_sector + ((current_cluster - 2) * sectors_per_cluster) + sector_index_in_cluster;
1437
1438 // Determine offsets within this specific sector
1439 uint32_t offset_in_sector = current_file_offset % bytes_per_sector;
1440 uint32_t bytes_available_in_sector = bytes_per_sector - offset_in_sector;
1441 size_t bytes_to_write = (bytes_left < bytes_available_in_sector) ? bytes_left : bytes_available_in_sector;
1442
1443 // If we are overwriting the ENTIRE sector, we don't need to read it first.
1444 // Otherwise (partial write), we must read the existing sector data to preserve surrounding bytes.
1445 bool full_sector_overwrite = (offset_in_sector == 0) && (bytes_to_write == bytes_per_sector);
1446
1447 if (full_sector_overwrite) {
1448 // Looks like we can write directly from the user buffer!
1449 status = write_sector(lba, (void*)src_buffer_ptr);
1450 }
1451 else {
1452 // We have to read the sector and then modify it and write it back, since it is smaller than the user buffer.
1453 status = read_sector(lba, IntermediateBuffer);
1454 if (MT_FAILURE(status)) break;
1455
1456 // Modify buffer
1457 kmemcpy((uint8_t*)IntermediateBuffer + offset_in_sector, src_buffer_ptr, bytes_to_write);
1458
1459 // Write back
1460 status = write_sector(lba, IntermediateBuffer);
1461 }
1462
1463 if (MT_FAILURE(status)) break;
1464
1465 // Advance pointers
1466 total_bytes_written += bytes_to_write;
1467 bytes_left -= bytes_to_write;
1468 src_buffer_ptr += bytes_to_write;
1469 current_file_offset += bytes_to_write;
1470
1471 // If we finished a cluster and still have data, we move to the next one.
1472 if (bytes_left > 0 && (current_file_offset % cluster_size) == 0) {
1473 uint32_t next_cluster = fat32_read_fat(current_cluster);
1474
1475 if (next_cluster >= FAT32_EOC_MIN) {
1476 // We reached the end of the chain but still have data to write.
1477 // Allocate a new cluster.
1478 uint32_t new_c = fat32_find_free_cluster();
1479 if (new_c == 0) {
1480 status = MT_FAT32_CLUSTERS_FULL;
1481 break;
1482 }
1483
1484 zero_cluster(new_c);
1485 fat32_write_fat(current_cluster, new_c); // Current = new
1486 fat32_write_fat(new_c, FAT32_EOC_MAX); // Mark new as EOC
1487
1488 current_cluster = new_c;
1489 }
1490 else {
1491 // Just follow the existing chain (overwriting existing file data)
1492 current_cluster = next_cluster;
1493 }
1494 }
1495 }
1496
1497 // Update the file object state before return
1498 FileObject->CurrentOffset = current_file_offset;
1499
1500 // If we extended the file we update the size in the object
1501 if (current_file_offset > FileObject->FileSize) {
1502 FileObject->FileSize = current_file_offset;
1503
1504 // Update the cluster info as well (we cast to uint32_t since this is FAT32)
1505 fat32_update_file_entry(FileObject->FileName, 0, (uint32_t)FileObject->FileSize, false, true);
1506 }
1507
1508 if (IntermediateBuffer) {
1509 MmFreePool(IntermediateBuffer);
1510 }
1511
1512 if (BytesWritten) {
1513 *BytesWritten = total_bytes_written;
1514 }
1515
1516 return status;
1517}
1518
1519// forward
1520static MTSTATUS fat32_open_file(
1521 IN const char* path,
1522 OUT PFILE_OBJECT* FileObjectOut
1523);
1524
1526 IN const char* path,
1527 OUT PFILE_OBJECT* FileObjectOut
1528)
1529{
1530 // First of all, we check if the file already exists
1531 FAT32_DIR_ENTRY existing_entry;
1532 uint32_t parent_cluster_check;
1533
1534 if (fat32_find_entry(path, &existing_entry, &parent_cluster_check)) {
1535 // If its a directory we return.
1536 if (existing_entry.attr & ATTR_DIRECTORY) {
1538 }
1539
1540 // It already exists, return fat32_open_file.
1541 return fat32_open_file(path, FileObjectOut);
1542 }
1543
1544 // Split the path into directory and filename
1545 char parent_path[260];
1546 char filename[260];
1547 int len = kstrlen(path);
1548 int last_slash = -1;
1549
1550 // Manual split logic
1551 for (int i = len - 1; i >= 0; i--) {
1552 if (path[i] == '/') {
1553 last_slash = i;
1554 break;
1555 }
1556 }
1557
1558 if (last_slash == -1) {
1559 // No slash? It's in the root
1560 kstrncpy(filename, path, 260);
1561 parent_path[0] = '/';
1562 parent_path[1] = '\0';
1563 }
1564 else {
1565 // Copy parent path
1566 int p_len = (last_slash == 0) ? 1 : last_slash; // Handle "/file.txt" vs "/A/file.txt"
1567 for (int i = 0; i < p_len; i++) parent_path[i] = path[i];
1568 parent_path[p_len] = '\0';
1569
1570 // Copy filename
1571 kstrncpy(filename, &path[last_slash + 1], 260);
1572 }
1573
1574 // Find parent directory cluster
1575 FAT32_DIR_ENTRY parent_entry;
1576 uint32_t parent_dir_cluster;
1577
1578 // Check if root
1579 if (kstrcmp(parent_path, "/") == 0) {
1580 parent_dir_cluster = fs.root_cluster;
1581 }
1582 else {
1583 if (!fat32_find_entry(parent_path, &parent_entry, &parent_dir_cluster)) {
1584 return MT_NOT_FOUND; // Parent folder doesn't exist
1585 }
1586 parent_dir_cluster = get_dir_cluster(&parent_entry);
1587 }
1588
1589 // Prepare SFN for the file.
1590 char sfn[11];
1591 format_short_name(filename, sfn);
1592
1593 // Append ~ to file.
1594 if (sfn[6] == ' ') { sfn[6] = '~'; sfn[7] = '1'; }
1595 else { sfn[6] = '~'; sfn[7] = '1'; } // Force overwrite for safety if name was long
1596
1597 uint8_t checksum = lfn_checksum((uint8_t*)sfn);
1598
1599 // Prepare LFN entries for file.
1600 uint32_t lfn_count = (kstrlen(filename) + 12) / 13;
1601 uint32_t total_entries = lfn_count + 1; // LFNs + 1 SFN
1602
1603 FAT32_LFN_ENTRY* entry_buf = (FAT32_LFN_ENTRY*)MmAllocatePoolWithTag(NonPagedPool, total_entries * sizeof(FAT32_LFN_ENTRY), 'LFN');
1604 if (!entry_buf) return MT_NO_MEMORY;
1605
1606 fat32_create_lfn_entries(entry_buf, filename, checksum);
1607
1608 // Configure the SFN entry (the last in the entry buffer)
1609 FAT32_DIR_ENTRY* sfn_entry = (FAT32_DIR_ENTRY*)&entry_buf[lfn_count];
1610 kmemset(sfn_entry, 0, sizeof(FAT32_DIR_ENTRY));
1611
1612 kmemcpy(sfn_entry->name, sfn, 11);
1613 sfn_entry->attr = ATTR_ARCHIVE;
1614 sfn_entry->file_size = 0; // New file
1615 sfn_entry->fst_clus_hi = 0;
1616 sfn_entry->fst_clus_lo = 0;
1617 // TODO Timestamps
1618
1619 // Allocate slots in parent directory to place the file there.
1620 uint32_t sector_lba;
1621 uint32_t entry_index;
1622
1623 if (!fat32_find_free_dir_slots(parent_dir_cluster, total_entries, &sector_lba, &entry_index)) {
1624 // Directory is full.
1625 MmFreePool(entry_buf);
1626 return MT_FAT32_DIR_FULL;
1627 }
1628
1629 // Write the entries to disk now.
1630 void* sector_buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'wrte');
1631 if (!sector_buf) {
1632 MmFreePool(entry_buf);
1633 return MT_NO_MEMORY;
1634 }
1635
1636 // FIX: Read the first sector where entries begin
1637 MTSTATUS status = read_sector(sector_lba, sector_buf);
1638 if (MT_FAILURE(status)) {
1639 MmFreePool(sector_buf);
1640 MmFreePool(entry_buf);
1641 return status;
1642 }
1643
1644 // FIX: Calculate offsets and check for sector boundary crossing
1645 uint32_t entry_size = sizeof(FAT32_DIR_ENTRY);
1646 uint32_t total_bytes_to_write = total_entries * entry_size;
1647 uint32_t offset_in_sector = entry_index * entry_size;
1648 uint32_t bytes_available_in_sector = fs.bytes_per_sector - offset_in_sector;
1649
1650 if (total_bytes_to_write <= bytes_available_in_sector) {
1651 // All entries fit in the current sector ---
1652 // Copy all entries to the correct offset
1653 kmemcpy((uint8_t*)sector_buf + offset_in_sector, entry_buf, total_bytes_to_write);
1654
1655 // Write the single sector back
1656 status = write_sector(sector_lba, sector_buf);
1657 }
1658 else {
1659 // Write the first part to the current sector
1660 kmemcpy((uint8_t*)sector_buf + offset_in_sector, entry_buf, bytes_available_in_sector);
1661 status = write_sector(sector_lba, sector_buf);
1662
1663 if (MT_SUCCEEDED(status)) {
1664 // We read the NEXT sector first to preserve any existing data in it
1665 // This assumes sectors are contiguous.
1666
1667 uint32_t next_sector_lba = sector_lba + 1;
1668
1669 status = read_sector(next_sector_lba, sector_buf);
1670 if (MT_SUCCEEDED(status)) {
1671 // Calculate remaining bytes
1672 uint32_t bytes_remaining = total_bytes_to_write - bytes_available_in_sector;
1673
1674 // Copy the rest of the entries to the START of the new sector buffer
1675 kmemcpy(sector_buf, (uint8_t*)entry_buf + bytes_available_in_sector, bytes_remaining);
1676
1677 // Write the second sector
1678 status = write_sector(next_sector_lba, sector_buf);
1679 }
1680 }
1681 }
1682
1683 MmFreePool(sector_buf);
1684 MmFreePool(entry_buf);
1685
1686 if (MT_FAILURE(status)) {
1687 return status;
1688 }
1689
1690 // Open the file now.
1691 return fat32_open_file(path, FileObjectOut);
1692}
1693
1694MTSTATUS fat32_list_directory(const char* path, char* listings, size_t max_len) {
1695 MTSTATUS status;
1696 // Find the directory entry for the given path to get its starting cluster.
1697 FAT32_DIR_ENTRY dir_entry;
1698 if (!fat32_find_entry(path, &dir_entry, NULL) || !(dir_entry.attr & ATTR_DIRECTORY)) {
1699 gop_printf(0xFFFF0000, "Error: Directory not found or path is not a directory: %s\n", path);
1701 }
1702
1703 uint32_t cluster = (uint32_t)((dir_entry.fst_clus_hi << 16) | dir_entry.fst_clus_lo);
1704 if (cluster == 0) { // Root directory special case on some FAT16/12, but for FAT32 it should be root_cluster.
1705 cluster = fs.root_cluster;
1706 }
1707
1708 void* buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'fat');
1709 if (!buf) return MT_NO_MEMORY;
1710
1711 if (max_len > 0) listings[0] = '\0';
1712 size_t used = 0;
1713
1714 do {
1715 uint32_t sector = first_sector_of_cluster(cluster);
1716 bool end_of_dir = false;
1717
1718 for (uint32_t i = 0; i < fs.sectors_per_cluster; ++i) {
1719 status = read_sector(sector + i, buf);
1720 if (MT_FAILURE(status)) { MmFreePool(buf); return status; }
1721
1722 FAT32_DIR_ENTRY* dir = (FAT32_DIR_ENTRY*)buf;
1723 uint32_t entries = fs.bytes_per_sector / sizeof(*dir);
1724
1725 for (uint32_t j = 0; j < entries; ) {
1726 FAT32_DIR_ENTRY* current_entry = &dir[j];
1727
1728 if (current_entry->name[0] == END_OF_DIRECTORY) {
1729 end_of_dir = true;
1730 break; // stop scanning entries in this sector -> will break outer loops below
1731 }
1732
1733 if ((uint8_t)current_entry->name[0] == DELETED_DIR_ENTRY ||
1734 (current_entry->name[0] == '.' && (current_entry->name[1] == '\0' || current_entry->name[1] == '.'))) {
1735 j++;
1736 continue;
1737 }
1738
1739 char lfn_name[MAX_LFN_LEN];
1740 uint32_t consumed = 0;
1741 FAT32_DIR_ENTRY* sfn_entry = read_lfn(current_entry, entries - j, lfn_name, &consumed);
1742 char line_buf[256];
1743 if (sfn_entry) {
1744 if (sfn_entry->attr & ATTR_DIRECTORY) {
1745 ksnprintf(line_buf, sizeof(line_buf), "<DIR> %s\n", lfn_name);
1746 }
1747 else {
1748 ksnprintf(line_buf, sizeof(line_buf), "%s (%u bytes)\n", lfn_name, sfn_entry->file_size);
1749 }
1750 // safe append: compute remaining and append up to remaining-1
1751 size_t avail = (used < max_len) ? (max_len - used) : 0;
1752 if (avail > 1) {
1753 // write directly into listings+used
1754 ksnprintf(listings + used, avail, "%s", line_buf);
1755 used = kstrlen(listings);
1756 }
1757 j += consumed;
1758 }
1759 else {
1760 j++;
1761 }
1762 }
1763
1764 if (end_of_dir) break;
1765 }
1766
1767 if (end_of_dir) break;
1768
1769 cluster = fat32_read_fat(cluster);
1770 } while (cluster < FAT32_EOC_MIN);
1771
1772 MmFreePool(buf);
1773 return MT_SUCCESS;
1774}
1775
1776// Check that a directory cluster contains only '.' and '..' (and deleted entries).
1777// Returns true if empty ,false if non-empty or error.
1778bool fat32_directory_is_empty(const char* path) {
1779
1780 FAT32_DIR_ENTRY entry;
1781 uint32_t parent_cluster = 0;
1782 fat32_find_entry(path, &entry, &parent_cluster);
1783
1784 uint32_t dir_cluster = get_dir_cluster(&entry);
1785 if (dir_cluster == 0) return false;
1786
1787 void* buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'fat');
1788 if (!buf) return false;
1789
1790 uint32_t cluster = dir_cluster;
1791 MTSTATUS status;
1792 do {
1793 uint32_t sector_lba = first_sector_of_cluster(cluster);
1794 for (uint32_t s = 0; s < fs.sectors_per_cluster; ++s) {
1795 status = read_sector(sector_lba + s, buf);
1796 if (MT_FAILURE(status)) { MmFreePool(buf); return false; }
1797
1798 FAT32_DIR_ENTRY* entries = (FAT32_DIR_ENTRY*)buf;
1799 uint32_t entries_per_sector = fs.bytes_per_sector / sizeof(FAT32_DIR_ENTRY);
1800
1801 for (uint32_t j = 0; j < entries_per_sector; ) {
1802 uint8_t first = (uint8_t)entries[j].name[0];
1803
1804 if (first == END_OF_DIRECTORY) { MmFreePool(buf); return true; } // no more entries
1805 if (first == DELETED_DIR_ENTRY) { j++; continue; }
1806
1807 // Build full name (LFN or SFN)
1808 char lfn_buf[MAX_LFN_LEN];
1809 uint32_t consumed = 0;
1810 FAT32_DIR_ENTRY* sfn = read_lfn(&entries[j], entries_per_sector - j, lfn_buf, &consumed);
1811 if (!sfn) { j++; continue; }
1812
1813 // skip '.' and '..'
1814 if ((unsigned char)sfn->name[0] == '.') {
1815 j += consumed;
1816 continue;
1817 }
1818
1819 // There is a non-deleted entry that is not '.'/'..' -> directory not empty
1820 MmFreePool(buf);
1821 return false;
1822 }
1823 }
1824 cluster = fat32_read_fat(cluster);
1825 } while (cluster < FAT32_EOC_MIN);
1826
1827 MmFreePool(buf);
1828 return true;
1829}
1830
1831// Mark the SFN and all preceding LFN entries for `filename` in parent_cluster as deleted.
1832// `path` is the full path. parent_cluster is cluster of parent directory.
1833// Returns true on success (sector written), false otherwise.
1834static bool mark_entry_and_lfns_deleted(const char* path, uint32_t parent_cluster) {
1835 // extract filename (last component)
1836 char path_copy[260];
1837 kstrncpy(path_copy, path, sizeof(path_copy));
1838 int len = (int)kstrlen(path_copy);
1839 // strip trailing slashes
1840 while (len > 1 && path_copy[len - 1] == '/') { path_copy[--len] = '\0'; }
1841
1842 int last_slash = -1;
1843 for (int i = len - 1; i >= 0; --i) {
1844 if (path_copy[i] == '/') { last_slash = i; break; }
1845 }
1846
1847 const char* filename = (last_slash == -1) ? path_copy : &path_copy[last_slash + 1];
1848
1849 // Prepare SFN format for short-name comparison
1850 char sfn_formatted[11];
1851 format_short_name(filename, sfn_formatted);
1852
1853 void* buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'fat');
1854 if (!buf) return false;
1855
1856 uint32_t cluster = parent_cluster;
1857 MTSTATUS status;
1858 do {
1859 uint32_t sector_lba = first_sector_of_cluster(cluster);
1860 for (uint32_t s = 0; s < fs.sectors_per_cluster; ++s) {
1861 status = read_sector(sector_lba + s, buf);
1862 if (MT_FAILURE(status)) { MmFreePool(buf); return false; }
1863
1864 FAT32_DIR_ENTRY* entries = (FAT32_DIR_ENTRY*)buf;
1865 uint32_t entries_per_sector = fs.bytes_per_sector / sizeof(FAT32_DIR_ENTRY);
1866
1867 for (uint32_t j = 0; j < entries_per_sector; ) {
1868 uint8_t first = (uint8_t)entries[j].name[0];
1869
1870 if (first == END_OF_DIRECTORY) { MmFreePool(buf); return false; } // not found in parent
1871 if (first == DELETED_DIR_ENTRY) { j++; continue; }
1872
1873 char lfn_buf[MAX_LFN_LEN];
1874 uint32_t consumed = 0;
1875 FAT32_DIR_ENTRY* sfn = read_lfn(&entries[j], entries_per_sector - j, lfn_buf, &consumed);
1876
1877 if (sfn) {
1878 // Match by LFN (exact), LFN (case-insensitive), or SFN bytes
1879 bool match = false;
1880
1881 // 1) exact LFN match
1882 if (kstrcmp(lfn_buf, filename) == 0) {
1883 match = true;
1884 }
1885
1886 // 2) case-insensitive LFN match
1887 if (!match && ci_equal(lfn_buf, filename)) {
1888 match = true;
1889 }
1890
1891 // 3) SFN byte-wise compare (token formatted)
1892 if (!match && cmp_short_name(sfn->name, sfn_formatted)) {
1893 match = true;
1894 }
1895
1896 if (match) {
1897 // Mark all consumed entries (LFN...SFN) as deleted (0xE5)
1898 for (uint32_t k = 0; k < consumed; ++k) {
1899 ((uint8_t*)entries[j + k].name)[0] = DELETED_DIR_ENTRY;
1900 }
1901
1902 // Write sector back to disk
1903 bool ok = write_sector(sector_lba + s, buf);
1904 MmFreePool(buf);
1905 return ok;
1906 }
1907
1908 j += consumed;
1909 continue;
1910 }
1911 else {
1912 // read_lfn failed (corrupted LFN chain?), skip this entry
1913 j++;
1914 }
1915 }
1916 }
1917 cluster = fat32_read_fat(cluster);
1918 } while (cluster < FAT32_EOC_MIN);
1919
1920 MmFreePool(buf);
1921 return false; // not found
1922}
1923
1924
1925// Recursively delete directory contents and free the directory's cluster chain.
1926// This function deletes all children (files & subdirs) found inside dir_cluster,
1927// marks their directory entries as DELETED on disk, and finally frees dir_cluster itself.
1928// Returns true on success, false on any error.
1929static bool fat32_rm_rf_dir(uint32_t dir_cluster) {
1930
1931 if (dir_cluster == 0 || dir_cluster == fs.root_cluster) return false; // never delete root here
1932
1933 void* buf = MmAllocatePoolWithTag(NonPagedPool, fs.bytes_per_sector, 'fat');
1934 if (!buf) return false;
1935
1936 uint32_t cluster = dir_cluster;
1937 // Iterate cluster chain
1938 MTSTATUS status;
1939 while (cluster < FAT32_EOC_MIN) {
1940 uint32_t sector_lba = first_sector_of_cluster(cluster);
1941
1942 for (uint32_t s = 0; s < fs.sectors_per_cluster; ++s) {
1943 status = read_sector(sector_lba + s, buf);
1944 if (MT_FAILURE(status)) { MmFreePool(buf); return false; }
1945
1946 FAT32_DIR_ENTRY* entries = (FAT32_DIR_ENTRY*)buf;
1947 uint32_t entries_per_sector = fs.bytes_per_sector / sizeof(FAT32_DIR_ENTRY);
1948
1949 for (uint32_t j = 0; j < entries_per_sector; ) {
1950 uint8_t first = (uint8_t)entries[j].name[0];
1951
1952 // End of directory: nothing after this in this directory
1953 if (first == END_OF_DIRECTORY) {
1954 // we can stop scanning this directory entirely
1955 // free buffer and break out to free cluster chain
1956 MmFreePool(buf);
1957 goto free_and_return;
1958 }
1959
1960 // Deleted entry: skip
1961 if (first == DELETED_DIR_ENTRY) { j++; continue; }
1962
1963 // Attempt to read LFN + SFN at this position
1964 char lfn_name[MAX_LFN_LEN];
1965 uint32_t consumed = 0;
1966 FAT32_DIR_ENTRY* sfn = read_lfn(&entries[j], entries_per_sector - j, lfn_name, &consumed);
1967
1968 if (!sfn) {
1969 // corrupted chain or not an entry we can parse: skip single entry
1970 j++;
1971 continue;
1972 }
1973
1974 // Skip '.' and '..' entries
1975 if ((unsigned char)sfn->name[0] == '.') {
1976 j += consumed;
1977 continue;
1978 }
1979
1980 // If directory -> recurse
1981 if (sfn->attr & ATTR_DIRECTORY) {
1982 uint32_t child_cluster = get_dir_cluster(sfn);
1983 if (child_cluster != 0 && child_cluster != 1 && child_cluster != dir_cluster) {
1984 // Recursively delete child directory contents and free its clusters.
1985 if (!fat32_rm_rf_dir(child_cluster)) {
1986 // recursion failed � return false
1987 MmFreePool(buf);
1988 return false;
1989 }
1990 // At this point child's clusters are freed by the recursive call.
1991 }
1992 // After child deleted, mark child's LFN+SFN entries as deleted in this parent sector
1993 for (uint32_t k = 0; k < consumed; ++k) {
1994 ((uint8_t*)entries[j + k].name)[0] = DELETED_DIR_ENTRY;
1995 }
1996 // write this sector back
1997 status = write_sector(sector_lba + s, buf);
1998 if (MT_FAILURE(status)) { MmFreePool(buf); return false; }
1999 // advance past consumed entries
2000 j += consumed;
2001 continue;
2002 }
2003 else {
2004 // It's a file: free its cluster chain (if any) then mark entries deleted
2005 uint32_t file_cluster = get_dir_cluster(sfn);
2006 if (file_cluster >= 2) {
2007 if (!fat32_free_cluster_chain(file_cluster)) {
2008 MmFreePool(buf);
2009 return false;
2010 }
2011 }
2012 // mark the LFN+SFN entries as deleted
2013 for (uint32_t k = 0; k < consumed; ++k) {
2014 ((uint8_t*)entries[j + k].name)[0] = DELETED_DIR_ENTRY;
2015 }
2016 // write sector back
2017 status = write_sector(sector_lba + s, buf);
2018 if (MT_FAILURE(status)) { MmFreePool(buf); return false; }
2019 j += consumed;
2020 continue;
2021 }
2022 } // for each entry in sector
2023 } // for each sector in cluster
2024
2025 cluster = fat32_read_fat(cluster);
2026 } // while cluster chain
2027
2028free_and_return:
2029 // Free this directory's own cluster chain (we deleted contents)
2030 if (!fat32_free_cluster_chain(dir_cluster)) {
2031 // if freeing fails, we still consider it an error
2032 return false;
2033 }
2034 return true;
2035}
2036
2038
2039 // Find entry & its parent cluster
2040 FAT32_DIR_ENTRY entry;
2041 uint32_t parent_cluster;
2042 if (!fat32_find_entry(path, &entry, &parent_cluster)) return MT_FAT32_DIRECTORY_NOT_FOUND;
2043
2044 // Must be a directory
2045 if (!(entry.attr & ATTR_DIRECTORY)) return MT_FAT32_INVALID_FILENAME;
2046
2047 uint32_t dir_cluster = get_dir_cluster(&entry);
2048 if (dir_cluster == 0) dir_cluster = fs.root_cluster;
2049
2050 // Don't allow removing root via this function
2051 if (dir_cluster == fs.root_cluster) return MT_GENERAL_FAILURE;
2052
2053 // Recursively delete children and free the directory's clusters.
2054 if (!fat32_rm_rf_dir(dir_cluster)) return MT_GENERAL_FAILURE;
2055
2056 // Now mark this directory's entry (LFN+SFN) in parent as deleted.
2057 if (!mark_entry_and_lfns_deleted(path, parent_cluster)) return MT_GENERAL_FAILURE;
2058
2059 return MT_SUCCESS;
2060}
2061
2062static inline bool is_file(FAT32_DIR_ENTRY* entry) {
2063 uint8_t attr = entry->attr;
2064 if ((attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) return false; // skip LFN
2065 if (attr & ATTR_DIRECTORY) return false; // skip directories
2066 return true; // it's a regular file
2067}
2068
2069MTSTATUS fat32_delete_file(const char* path) {
2070
2071 // Find the file entry and its parent cluster
2072 FAT32_DIR_ENTRY entry;
2073 uint32_t parent_cluster;
2074 if (!fat32_find_entry(path, &entry, &parent_cluster)) {
2075 return MT_FAT32_DIRECTORY_NOT_FOUND; // File not found
2076 }
2077
2078 // Must be a file (not a directory)
2079 if (!is_file(&entry)) {
2080 return MT_FAT32_INVALID_FILENAME; // Not a file
2081 }
2082
2083 // Get the file's first cluster
2084 uint32_t file_cluster = get_dir_cluster(&entry);
2085
2086 // Free the file's cluster chain (if it has any clusters allocated)
2087 if (file_cluster >= 2 && file_cluster < FAT32_EOC_MIN) {
2088 if (!fat32_free_cluster_chain(file_cluster)) {
2089 return MT_GENERAL_FAILURE; // Failed to free cluster chain
2090 }
2091 }
2092
2093 // Mark the file's directory entry (LFN + SFN) as deleted in the parent directory
2094 if (!mark_entry_and_lfns_deleted(path, parent_cluster)) {
2095 return MT_GENERAL_FAILURE; // Failed to mark directory entries as deleted
2096 }
2097
2098 return MT_SUCCESS; // Success
2099}
2100
2101static MTSTATUS fat32_open_file(
2102 IN const char* path,
2103 OUT PFILE_OBJECT* FileObjectOut
2104)
2105
2106{
2107 // Find the file entry and its parent cluster
2108 FAT32_DIR_ENTRY entry;
2109 uint32_t parent_cluster;
2110 if (!fat32_find_entry(path, &entry, &parent_cluster)) {
2111 return MT_FAT32_FILE_NOT_FOUND; // File not found
2112 }
2113
2114 // Must be a file (not a directory)
2115 if (!is_file(&entry)) {
2116 return MT_FAT32_INVALID_FILENAME; // Not a file
2117 }
2118
2119 // Get the file's first cluster
2120 uint32_t file_cluster = get_dir_cluster(&entry);
2121
2122 // All passed, create the object.
2123 PFILE_OBJECT FileObject = NULL;
2124 MTSTATUS Status = ObCreateObject(FsFileType, sizeof(FILE_OBJECT), (void**)&FileObject);
2125 if (MT_FAILURE(Status)) return Status;
2126
2127 // Fill in fields.
2128 // File name is the path.
2129
2130 size_t length = kstrlen(path) + 1;
2131 FileObject->FileName = MmAllocatePoolWithTag(PagedPool, length, 'eman');
2132 kstrncpy(FileObject->FileName, path, length);
2133 // Offset starts at 0.
2134 FileObject->CurrentOffset = 0;
2135 // File size given from the entry.
2136 FileObject->FileSize = entry.file_size;
2137 // The initial cluster of the file.
2138 FileObject->FsContext = (void*)(uintptr_t)file_cluster;
2139 // Flags describing what the hell is this!
2140 // Currently, none, this also means its a file since the dir bit isnt set.
2141 FileObject->Flags = MT_FOF_NONE;
2142 *FileObjectOut = FileObject;
2143 return MT_SUCCESS;
2144}
2145
2146void fat32_deletion_routine(void* Object)
2147
2148{
2149 // We just delete the filename allocated.
2150 PFILE_OBJECT FileObject = (PFILE_OBJECT)Object;
2151 MmFreePool((void*)FileObject->FileName);
2152}
#define _Out_Opt
Definition annotations.h:11
#define IN
Definition annotations.h:8
#define OUT
Definition annotations.h:9
FORCEINLINE int32_t InterlockedCompareExchange32(volatile int32_t *target, int32_t value, int32_t comparand)
Definition atomic.h:60
FORCEINLINE int32_t InterlockedExchange32(volatile int32_t *target, int32_t value)
Definition atomic.h:36
BLOCK_DEVICE * get_block_device(int index)
Definition block.c:32
struct _BLOCK_DEVICE BLOCK_DEVICE
GOP_PARAMS gop_local
Definition gop.c:223
enum _IRQL IRQL
struct _GOP_PARAMS GOP_PARAMS
void * fat_cache_buf2
Definition fat32.c:34
MTSTATUS fat32_create_file(IN const char *path, OUT PFILE_OBJECT *FileObjectOut)
Definition fat32.c:1525
MTSTATUS fat32_init(int disk_index)
Definition fat32.c:853
#define BPB_SECTOR_START
Definition fat32.c:850
MTSTATUS fat32_create_directory(const char *path)
Creates a new directory (/testdir/ or /testdir are both allowed to create 'testdir' inside of 'root')
Definition fat32.c:1136
MTSTATUS fat32_list_directory(const char *path, char *listings, size_t max_len)
Lists the directory given.
Definition fat32.c:1694
MTSTATUS fat32_delete_directory(const char *path)
This function deletes the directory given to the function from the system.
Definition fat32.c:2037
#define MAX_LFN_ENTRIES
Definition fat32.c:37
#define FAT32_READ_ERROR
Definition fat32.c:43
MTSTATUS fat32_read_file(IN PFILE_OBJECT FileObject, IN uint64_t FileOffset, OUT void *Buffer, IN size_t BufferSize, _Out_Opt size_t *BytesRead)
Definition fat32.c:1029
MTSTATUS fat32_write_file(IN PFILE_OBJECT FileObject, IN uint64_t FileOffset, IN void *Buffer, IN size_t BufferSize, _Out_Opt size_t *BytesWritten)
Definition fat32.c:1364
#define MAX_LFN_LEN
Definition fat32.c:38
MTSTATUS fat32_delete_file(const char *path)
This function deletes the file given to the function from the system.
Definition fat32.c:2069
void fat32_deletion_routine(void *Object)
Definition fat32.c:2146
#define le32toh(x)
Definition fat32.c:23
void fat32_list_root(void)
Definition fat32.c:877
bool fat32_directory_is_empty(const char *path)
This function returns if the directory given to the function is empty (e.g, has only '....
Definition fat32.c:1778
volatile int32_t fat32_called_from_scanner
Definition fat32.c:40
#define END_OF_DIRECTORY
Definition fat32.h:17
#define DELETED_DIR_ENTRY
Definition fat32.h:18
FAT32_BPB
Definition fat32.h:55
@ ATTR_LONG_NAME
Definition fat32.h:122
@ ATTR_DIRECTORY
Definition fat32.h:119
@ ATTR_ARCHIVE
Definition fat32.h:120
struct _FAT32_FSINFO FAT32_FSINFO
FAT32_DIR_ENTRY
Definition fat32.h:78
#define FAT32_EOC_MIN
Definition fat32.h:24
#define FAT32_EOC_MAX
Definition fat32.h:25
#define FAT32_FREE_CLUSTER
Definition fat32.h:22
struct _FILE_OBJECT * PFILE_OBJECT
@ MT_FOF_NONE
Definition fs.h:55
struct _FILE_OBJECT FILE_OBJECT
size_t kstrlen(const char *str)
Definition gop.c:342
void gop_printf(uint32_t color, const char *fmt,...)
Definition gop.c:633
int ksnprintf(char *buf, size_t bufsize, const char *fmt,...)
Definition gop.c:482
char * kstrtok_r(char *str, const char *delim, char **save_ptr)
Definition gop.c:440
char * kstrncpy(char *dst, const char *src, size_t n)
Definition gop.c:365
int kstrcmp(const char *s1, const char *s2)
Definition gop.c:593
struct _ACPI_SDT_HEADER h
Definition mh.h:0
@ NonPagedPool
Definition mm.h:355
@ PagedPool
Definition mm.h:356
FORCEINLINE void * kmemcpy(void *dest, const void *src, size_t len)
Definition mm.h:669
FORCEINLINE void * kmemset(void *dest, int64_t val, uint64_t len)
Definition mm.h:655
struct _SPINLOCK SPINLOCK
#define MT_NO_MEMORY
Definition mtstatus.h:42
#define MT_SUCCESS
Definition mtstatus.h:22
#define MT_FAT32_DIRECTORY_NOT_FOUND
Definition mtstatus.h:79
#define MT_MEMORY_LIMIT
Definition mtstatus.h:43
#define MT_GENERAL_FAILURE
Definition mtstatus.h:31
#define MT_FAT32_PARENT_PATH_NOT_FOUND
Definition mtstatus.h:74
#define MT_FAT32_CLUSTERS_FULL
Definition mtstatus.h:66
#define MT_FAT32_DIRECTORY_ALREADY_EXISTS
Definition mtstatus.h:73
#define MT_FAILURE(Status)
Definition mtstatus.h:16
#define MT_INVALID_PARAM
Definition mtstatus.h:24
#define MT_FAT32_INVALID_FILENAME
Definition mtstatus.h:71
int32_t MTSTATUS
Definition mtstatus.h:12
#define MT_FAT32_FILE_NOT_FOUND
Definition mtstatus.h:69
#define MT_FAT32_PARENT_PATH_NOT_DIR
Definition mtstatus.h:75
#define MT_FAT32_EOF
Definition mtstatus.h:72
#define MT_NOT_FOUND
Definition mtstatus.h:30
#define MT_SUCCEEDED(Status)
Macros to test status.
Definition mtstatus.h:15
#define MT_FAT32_DIR_FULL
Definition mtstatus.h:68
MTSTATUS ObCreateObject(IN POBJECT_TYPE ObjectType, IN uint32_t ObjectSize, OUT void **ObjectCreated)
Definition ob.c:118
void MmFreePool(IN void *buf)
Definition pool.c:632
void * MmAllocatePoolWithTag(IN enum _POOL_TYPE PoolType, IN size_t NumberOfBytes, IN uint32_t Tag)
Definition pool.c:443
void MsAcquireSpinlock(IN PSPINLOCK lock, IN PIRQL OldIrql)
Definition spinlock.c:13
void MsReleaseSpinlock(IN PSPINLOCK lock, IN IRQL OldIrql)
Definition spinlock.c:45
uint64_t CurrentOffset
Definition fs.h:110
char * FileName
Definition fs.h:101
void * FsContext
Definition fs.h:104
uint64_t FileSize
Definition fs.h:107
uint32_t Flags
Definition fs.h:113
uint8_t LDIR_Type
Definition fat32.h:88
uint16_t LDIR_FstClusLO
Definition fat32.h:91
uint8_t LDIR_Chksum
Definition fat32.h:89
uint8_t LDIR_Attr
Definition fat32.h:87
uint16_t LDIR_Name1[5]
Definition fat32.h:86
uint8_t LDIR_Ord
Definition fat32.h:85
uint16_t LDIR_Name2[6]
Definition fat32.h:90
uint16_t LDIR_Name3[2]
Definition fat32.h:92
uint16_t name_chars[13]
Definition fat32.c:45
uint8_t month
Definition time.h:23
uint16_t year
Definition time.h:24
uint8_t day
Definition time.h:22
uint8_t second
Definition time.h:19
uint8_t minute
Definition time.h:20
uint8_t hour
Definition time.h:21
POBJECT_TYPE FsFileType
Definition vfs.c:26