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