| 9120e874 Andy McCall |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
#include "sdcard_fat.h" |
| 16 |
#include "support/harness.h" |
| 17 |
#include "files.h" |
| 18 |
|
| 19 |
#include <stdio.h> |
| 20 |
#include <stdlib.h> |
| 21 |
#include <string.h> |
| 22 |
#include <stdint.h> |
| 23 |
#include <stdbool.h> |
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
struct x16file { |
| 28 |
FILE *fp; |
| 29 |
}; |
| 30 |
|
| 31 |
struct x16file * |
| 32 |
x16open(const char *path, const char *attribs) |
| 33 |
{ |
| 34 |
FILE *fp = fopen(path, attribs); |
| 35 |
if (fp == NULL) { |
| 36 |
return NULL; |
| 37 |
} |
| 38 |
struct x16file *f = (struct x16file *)malloc(sizeof(struct x16file)); |
| 39 |
if (f == NULL) { |
| 40 |
fclose(fp); |
| 41 |
return NULL; |
| 42 |
} |
| 43 |
f->fp = fp; |
| 44 |
return f; |
| 45 |
} |
| 46 |
|
| 47 |
void |
| 48 |
x16close(struct x16file *f) |
| 49 |
{ |
| 50 |
if (f == NULL) { |
| 51 |
return; |
| 52 |
} |
| 53 |
fclose(f->fp); |
| 54 |
free(f); |
| 55 |
} |
| 56 |
|
| 57 |
int64_t |
| 58 |
x16size(struct x16file *f) |
| 59 |
{ |
| 60 |
long here = ftell(f->fp); |
| 61 |
fseek(f->fp, 0, SEEK_END); |
| 62 |
long end = ftell(f->fp); |
| 63 |
fseek(f->fp, here, SEEK_SET); |
| 64 |
return (int64_t)end; |
| 65 |
} |
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
|
| 70 |
|
| 71 |
int |
| 72 |
x16seek(struct x16file *f, int64_t pos, int origin) |
| 73 |
{ |
| 74 |
int whence = (origin == XSEEK_END) ? SEEK_END |
| 75 |
: (origin == XSEEK_CUR) ? SEEK_CUR |
| 76 |
: SEEK_SET; |
| 77 |
if (fseek(f->fp, (long)pos, whence) != 0) { |
| 78 |
return -1; |
| 79 |
} |
| 80 |
long p = ftell(f->fp); |
| 81 |
return (p < 0) ? -1 : (int)p; |
| 82 |
} |
| 83 |
|
| 84 |
int64_t |
| 85 |
x16tell(struct x16file *f) |
| 86 |
{ |
| 87 |
long p = ftell(f->fp); |
| 88 |
return (p < 0) ? -1 : (int64_t)p; |
| 89 |
} |
| 90 |
|
| 91 |
uint64_t |
| 92 |
x16read(struct x16file *f, uint8_t *data, uint64_t data_size, uint64_t data_count) |
| 93 |
{ |
| 94 |
return (uint64_t)fread(data, (size_t)data_size, (size_t)data_count, f->fp); |
| 95 |
} |
| 96 |
|
| 97 |
|
| 98 |
|
| 99 |
#define SECTOR_SIZE 512u |
| 100 |
|
| 101 |
typedef struct { |
| 102 |
uint8_t *data; |
| 103 |
uint32_t sector_count; |
| 104 |
} disk_t; |
| 105 |
|
| 106 |
static disk_t |
| 107 |
disk_alloc(uint32_t sectors) |
| 108 |
{ |
| 109 |
disk_t d; |
| 110 |
d.sector_count = sectors; |
| 111 |
d.data = (uint8_t *)calloc((size_t)sectors, SECTOR_SIZE); |
| 112 |
return d; |
| 113 |
} |
| 114 |
|
| 115 |
static void |
| 116 |
disk_free(disk_t *d) |
| 117 |
{ |
| 118 |
free(d->data); |
| 119 |
d->data = NULL; |
| 120 |
d->sector_count = 0; |
| 121 |
} |
| 122 |
|
| 123 |
static uint8_t * |
| 124 |
disk_sector(disk_t *d, uint32_t lba) |
| 125 |
{ |
| 126 |
return d->data + (size_t)lba * SECTOR_SIZE; |
| 127 |
} |
| 128 |
|
| 129 |
static void wle16(uint8_t *p, uint16_t v) |
| 130 |
{ |
| 131 |
p[0] = (uint8_t)(v & 0xFFu); |
| 132 |
p[1] = (uint8_t)((v >> 8) & 0xFFu); |
| 133 |
} |
| 134 |
|
| 135 |
static void wle32(uint8_t *p, uint32_t v) |
| 136 |
{ |
| 137 |
p[0] = (uint8_t)(v & 0xFFu); |
| 138 |
p[1] = (uint8_t)((v >> 8) & 0xFFu); |
| 139 |
p[2] = (uint8_t)((v >> 16) & 0xFFu); |
| 140 |
p[3] = (uint8_t)((v >> 24) & 0xFFu); |
| 141 |
} |
| 142 |
|
| 143 |
static void |
| 144 |
fat32_set(disk_t *d, uint32_t fat_lba, uint32_t cluster, uint32_t val) |
| 145 |
{ |
| 146 |
uint32_t byte_off = cluster * 4u; |
| 147 |
uint32_t s = fat_lba + byte_off / SECTOR_SIZE; |
| 148 |
uint32_t i = byte_off % SECTOR_SIZE; |
| 149 |
wle32(disk_sector(d, s) + i, val & 0x0FFFFFFFu); |
| 150 |
} |
| 151 |
|
| 152 |
static void |
| 153 |
fat16_set(disk_t *d, uint32_t fat_lba, uint32_t cluster, uint16_t val) |
| 154 |
{ |
| 155 |
uint32_t byte_off = cluster * 2u; |
| 156 |
uint32_t s = fat_lba + byte_off / SECTOR_SIZE; |
| 157 |
uint32_t i = byte_off % SECTOR_SIZE; |
| 158 |
wle16(disk_sector(d, s) + i, val); |
| 159 |
} |
| 160 |
|
| 161 |
static void |
| 162 |
write_dirent_83(uint8_t *de, const char *name83, uint8_t attr, |
| 163 |
uint32_t first_cluster, uint32_t size) |
| 164 |
{ |
| 165 |
memset(de, 0, 32); |
| 166 |
memcpy(de, name83, 11); |
| 167 |
de[11] = attr; |
| 168 |
wle16(de + 20, (uint16_t)(first_cluster >> 16)); |
| 169 |
wle16(de + 26, (uint16_t)(first_cluster & 0xFFFFu)); |
| 170 |
wle32(de + 28, size); |
| 171 |
} |
| 172 |
|
| 173 |
static uint8_t |
| 174 |
lfn_checksum(const uint8_t *name83) |
| 175 |
{ |
| 176 |
uint8_t sum = 0; |
| 177 |
for (int i = 0; i < 11; i++) { |
| 178 |
sum = (uint8_t)(((sum & 1u) ? 0x80u : 0x00u) + (sum >> 1) + name83[i]); |
| 179 |
} |
| 180 |
return sum; |
| 181 |
} |
| 182 |
|
| 183 |
static void |
| 184 |
write_lfn_entry(uint8_t *de, uint8_t seq, bool is_last, |
| 185 |
uint8_t checksum, const uint16_t ucs2[13]) |
| 186 |
{ |
| 187 |
static const int off[13] = {1,3,5,7,9, 14,16,18,20,22,24, 28,30}; |
| 188 |
memset(de, 0, 32); |
| 189 |
de[0] = seq | (is_last ? 0x40u : 0x00u); |
| 190 |
de[11] = 0x0F; |
| 191 |
de[13] = checksum; |
| 192 |
for (int i = 0; i < 13; i++) { |
| 193 |
wle16(de + off[i], ucs2[i]); |
| 194 |
} |
| 195 |
} |
| 196 |
|
| 197 |
static const char * |
| 198 |
write_temp_disk(const disk_t *d, const char *leaf) |
| 199 |
{ |
| 200 |
static char path[512]; |
| 201 |
snprintf(path, sizeof(path), "test_sdcard_fat_%s", leaf); |
| 202 |
FILE *f = fopen(path, "wb"); |
| 203 |
if (!f) { |
| 204 |
return NULL; |
| 205 |
} |
| 206 |
fwrite(d->data, SECTOR_SIZE, (size_t)d->sector_count, f); |
| 207 |
fclose(f); |
| 208 |
return path; |
| 209 |
} |
| 210 |
|
| 211 |
|
| 212 |
|
| 213 |
|
| 214 |
|
| 215 |
|
| 216 |
|
| 217 |
|
| 218 |
|
| 219 |
|
| 220 |
|
| 221 |
|
| 222 |
|
| 223 |
|
| 224 |
|
| 225 |
|
| 226 |
|
| 227 |
|
| 228 |
|
| 229 |
|
| 230 |
#define P_LBA 2048u |
| 231 |
#define RSVD_SEC 6u |
| 232 |
#define FAT32_SZ 513u |
| 233 |
#define NUM_FATS 2u |
| 234 |
#define F32_SPC 1u |
| 235 |
#define F32_TOT_SEC 66696u |
| 236 |
#define FAT1_LBA (P_LBA + RSVD_SEC) |
| 237 |
#define FAT2_LBA (FAT1_LBA + FAT32_SZ) |
| 238 |
#define F32_DATA_LBA (FAT2_LBA + FAT32_SZ) |
| 239 |
#define F32_DISK_SECTORS (F32_DATA_LBA + 6u + 1u) |
| 240 |
|
| 241 |
static disk_t |
| 242 |
build_fat32_image(void) |
| 243 |
{ |
| 244 |
disk_t d = disk_alloc(F32_DISK_SECTORS); |
| 245 |
|
| 246 |
|
| 247 |
uint8_t *mbr = disk_sector(&d, 0); |
| 248 |
mbr[0] = 0xEB; mbr[1] = 0x58; mbr[2] = 0x90; |
| 249 |
uint8_t *pe = mbr + 0x1BE; |
| 250 |
pe[4] = 0x0C; |
| 251 |
wle32(pe + 8, P_LBA); |
| 252 |
wle32(pe + 12, F32_TOT_SEC); |
| 253 |
mbr[510] = 0x55; mbr[511] = 0xAA; |
| 254 |
|
| 255 |
|
| 256 |
uint8_t *vbr = disk_sector(&d, P_LBA); |
| 257 |
vbr[0] = 0xEB; vbr[1] = 0x58; vbr[2] = 0x90; |
| 258 |
memcpy(vbr + 3, "MSDOS5.0", 8); |
| 259 |
wle16(vbr + 11, 512); |
| 260 |
vbr[13] = F32_SPC; |
| 261 |
wle16(vbr + 14, RSVD_SEC); |
| 262 |
vbr[16] = NUM_FATS; |
| 263 |
wle16(vbr + 17, 0); |
| 264 |
wle16(vbr + 19, 0); |
| 265 |
vbr[21] = 0xF8; |
| 266 |
wle16(vbr + 22, 0); |
| 267 |
wle16(vbr + 24, 63); |
| 268 |
wle16(vbr + 26, 255); |
| 269 |
wle32(vbr + 28, 0); |
| 270 |
wle32(vbr + 32, F32_TOT_SEC); |
| 271 |
wle32(vbr + 36, FAT32_SZ); |
| 272 |
wle16(vbr + 40, 0); |
| 273 |
wle16(vbr + 42, 0); |
| 274 |
wle32(vbr + 44, 2); |
| 275 |
wle16(vbr + 48, 1); |
| 276 |
wle16(vbr + 50, 6); |
| 277 |
vbr[510] = 0x55; vbr[511] = 0xAA; |
| 278 |
|
| 279 |
|
| 280 |
uint32_t fat_lbas[2] = { FAT1_LBA, FAT2_LBA }; |
| 281 |
for (int fi = 0; fi < 2; fi++) { |
| 282 |
uint32_t fl = fat_lbas[fi]; |
| 283 |
fat32_set(&d, fl, 0, 0x0FFFFFF8u); |
| 284 |
fat32_set(&d, fl, 1, 0x0FFFFFFFu); |
| 285 |
fat32_set(&d, fl, 2, 0x0FFFFFFFu); |
| 286 |
fat32_set(&d, fl, 3, 0x0FFFFFFFu); |
| 287 |
fat32_set(&d, fl, 4, 0x0FFFFFFFu); |
| 288 |
fat32_set(&d, fl, 5, 6u); |
| 289 |
fat32_set(&d, fl, 6, 0x0FFFFFFFu); |
| 290 |
fat32_set(&d, fl, 7, 0x0FFFFFFFu); |
| 291 |
} |
| 292 |
|
| 293 |
|
| 294 |
uint8_t *root = disk_sector(&d, F32_DATA_LBA + 0); |
| 295 |
|
| 296 |
write_dirent_83(root + 0*32, "FILE1 PRG", 0x20, 4, 100); |
| 297 |
write_dirent_83(root + 1*32, "SUBDIR ", 0x10, 3, 0); |
| 298 |
|
| 299 |
|
| 300 |
uint8_t short83[11]; |
| 301 |
memcpy(short83, "ALONGN~1PRG", 11); |
| 302 |
uint8_t ck = lfn_checksum(short83); |
| 303 |
|
| 304 |
const char *lfn_name = "A LONG NAME.PRG"; |
| 305 |
uint16_t frag1[13], frag2[13]; |
| 306 |
for (int i = 0; i < 13; i++) { |
| 307 |
frag1[i] = (uint16_t)(unsigned char)lfn_name[i]; |
| 308 |
} |
| 309 |
frag2[0] = (uint16_t)(unsigned char)lfn_name[13]; |
| 310 |
frag2[1] = (uint16_t)(unsigned char)lfn_name[14]; |
| 311 |
frag2[2] = 0x0000u; |
| 312 |
for (int i = 3; i < 13; i++) { frag2[i] = 0xFFFFu; } |
| 313 |
|
| 314 |
write_lfn_entry(root + 2*32, 2, true, ck, frag2); |
| 315 |
write_lfn_entry(root + 3*32, 1, false, ck, frag1); |
| 316 |
write_dirent_83(root + 4*32, (const char *)short83, 0x20, 5, 50); |
| 317 |
|
| 318 |
|
| 319 |
uint8_t *sub = disk_sector(&d, F32_DATA_LBA + 1); |
| 320 |
write_dirent_83(sub + 0*32, ". ", 0x10, 3, 0); |
| 321 |
write_dirent_83(sub + 1*32, ".. ", 0x10, 2, 0); |
| 322 |
write_dirent_83(sub + 2*32, "DEEP PRG", 0x20, 7, 200); |
| 323 |
|
| 324 |
return d; |
| 325 |
} |
| 326 |
|
| 327 |
|
| 328 |
|
| 329 |
|
| 330 |
|
| 331 |
|
| 332 |
|
| 333 |
|
| 334 |
|
| 335 |
|
| 336 |
#define F16_RSVD 4u |
| 337 |
#define F16_FATSZ 16u |
| 338 |
#define F16_NFATS 2u |
| 339 |
#define F16_ROOTENT 32u |
| 340 |
#define F16_ROOTSEC 2u |
| 341 |
#define F16_SPC 1u |
| 342 |
#define F16_TOT 4200u |
| 343 |
#define F16_FAT1_LBA F16_RSVD |
| 344 |
#define F16_FAT2_LBA (F16_FAT1_LBA + F16_FATSZ) |
| 345 |
#define F16_ROOT_LBA (F16_FAT2_LBA + F16_FATSZ) |
| 346 |
#define F16_DATA_LBA (F16_ROOT_LBA + F16_ROOTSEC) |
| 347 |
#define F16_DISK_SECTORS (F16_DATA_LBA + 2u) |
| 348 |
|
| 349 |
static disk_t |
| 350 |
build_fat16_image(void) |
| 351 |
{ |
| 352 |
disk_t d = disk_alloc(F16_DISK_SECTORS); |
| 353 |
|
| 354 |
uint8_t *vbr = disk_sector(&d, 0); |
| 355 |
vbr[0] = 0xEB; vbr[1] = 0x3C; vbr[2] = 0x90; |
| 356 |
memcpy(vbr + 3, "MSDOS5.0", 8); |
| 357 |
wle16(vbr + 11, 512); |
| 358 |
vbr[13] = F16_SPC; |
| 359 |
wle16(vbr + 14, F16_RSVD); |
| 360 |
vbr[16] = F16_NFATS; |
| 361 |
wle16(vbr + 17, F16_ROOTENT); |
| 362 |
wle16(vbr + 19, (uint16_t)F16_TOT); |
| 363 |
vbr[21] = 0xF8; |
| 364 |
wle16(vbr + 22, (uint16_t)F16_FATSZ); |
| 365 |
wle16(vbr + 24, 63); |
| 366 |
wle16(vbr + 26, 255); |
| 367 |
wle32(vbr + 28, 0); |
| 368 |
wle32(vbr + 32, 0); |
| 369 |
vbr[510] = 0x55; vbr[511] = 0xAA; |
| 370 |
|
| 371 |
uint32_t fat_lbas[2] = { F16_FAT1_LBA, F16_FAT2_LBA }; |
| 372 |
for (int fi = 0; fi < 2; fi++) { |
| 373 |
uint32_t fl = fat_lbas[fi]; |
| 374 |
fat16_set(&d, fl, 0, 0xFFF8u); |
| 375 |
fat16_set(&d, fl, 1, 0xFFFFu); |
| 376 |
fat16_set(&d, fl, 2, 0xFFFFu); |
| 377 |
} |
| 378 |
|
| 379 |
uint8_t *rdir = disk_sector(&d, F16_ROOT_LBA); |
| 380 |
write_dirent_83(rdir + 0*32, "ONE TXT", 0x20, 2, 42); |
| 381 |
|
| 382 |
return d; |
| 383 |
} |
| 384 |
|
| 385 |
|
| 386 |
|
| 387 |
static const sdcard_fat_file_t * |
| 388 |
find_file(const char *path) |
| 389 |
{ |
| 390 |
int n = sdcard_fat_file_count(); |
| 391 |
for (int i = 0; i < n; i++) { |
| 392 |
const sdcard_fat_file_t *f = sdcard_fat_file_at(i); |
| 393 |
if (f && strcmp(f->path, path) == 0) { |
| 394 |
return f; |
| 395 |
} |
| 396 |
} |
| 397 |
return NULL; |
| 398 |
} |
| 399 |
|
| 400 |
|
| 401 |
|
| 402 |
static struct x16file * |
| 403 |
open_and_build(const char *path, bool expect_ok, const char *label) |
| 404 |
{ |
| 405 |
struct x16file *img = x16open(path, "rb"); |
| 406 |
check(img != NULL, label); |
| 407 |
if (!img) { |
| 408 |
return NULL; |
| 409 |
} |
| 410 |
bool ok = sdcard_fat_build(img); |
| 411 |
if (expect_ok) { |
| 412 |
check(ok, "sdcard_fat_build succeeds"); |
| 413 |
} else { |
| 414 |
check(!ok, "sdcard_fat_build returns false (expected)"); |
| 415 |
} |
| 416 |
return img; |
| 417 |
} |
| 418 |
|
| 419 |
|
| 420 |
|
| 421 |
static void |
| 422 |
test_fat32(void) |
| 423 |
{ |
| 424 |
printf("\n--- FAT32 image tests ---\n"); |
| 425 |
|
| 426 |
disk_t d = build_fat32_image(); |
| 427 |
const char *path = write_temp_disk(&d, "fat32.img"); |
| 428 |
disk_free(&d); |
| 429 |
|
| 430 |
check(path != NULL, "FAT32 image written to disk"); |
| 431 |
if (!path) { return; } |
| 432 |
|
| 433 |
struct x16file *img = open_and_build(path, true, "FAT32 image opened"); |
| 434 |
if (!img) { remove(path); return; } |
| 435 |
|
| 436 |
check(sdcard_fat_ready(), "sdcard_fat_ready() is true"); |
| 437 |
check(strcmp(sdcard_fat_type_name(), "FAT32") == 0, "type name is FAT32"); |
| 438 |
|
| 439 |
|
| 440 |
check(sdcard_fat_file_count() >= 5, "file count >= 5 (root + 4 files)"); |
| 441 |
printf(" file count: %d\n", sdcard_fat_file_count()); |
| 442 |
|
| 443 |
check(find_file("/") != NULL, "found / (FAT32 root dir)"); |
| 444 |
check(find_file("/FILE1.PRG") != NULL, "found /FILE1.PRG"); |
| 445 |
check(find_file("/SUBDIR") != NULL, "found /SUBDIR"); |
| 446 |
check(find_file("/A LONG NAME.PRG") != NULL, "found /A LONG NAME.PRG via LFN"); |
| 447 |
check(find_file("/SUBDIR/DEEP.PRG") != NULL, "found /SUBDIR/DEEP.PRG"); |
| 448 |
|
| 449 |
const sdcard_fat_file_t *f1 = find_file("/FILE1.PRG"); |
| 450 |
if (f1) { check_eq(f1->size, 100u, "/FILE1.PRG size == 100"); } |
| 451 |
|
| 452 |
|
| 453 |
|
| 454 |
|
| 455 |
{ |
| 456 |
sdcard_fat_region_t r = sdcard_fat_lookup(0, NULL, NULL); |
| 457 |
check(r == SDCARD_FAT_REGION_MBR, "LBA 0 is REGION_MBR"); |
| 458 |
} |
| 459 |
|
| 460 |
|
| 461 |
{ |
| 462 |
sdcard_fat_region_t r = sdcard_fat_lookup(FAT1_LBA, NULL, NULL); |
| 463 |
check(r == SDCARD_FAT_REGION_FAT, "LBA FAT1_LBA is REGION_FAT"); |
| 464 |
} |
| 465 |
|
| 466 |
|
| 467 |
{ |
| 468 |
const sdcard_fat_file_t *hf = NULL; |
| 469 |
uint64_t off = 999u; |
| 470 |
sdcard_fat_region_t r = sdcard_fat_lookup(F32_DATA_LBA + 2u, &hf, &off); |
| 471 |
check(r == SDCARD_FAT_REGION_FILE, "cluster 4 is REGION_FILE"); |
| 472 |
check(hf != NULL && strcmp(hf->path, "/FILE1.PRG") == 0, " ...names /FILE1.PRG"); |
| 473 |
check_eq((uint32_t)off, 0u, " ...offset 0"); |
| 474 |
} |
| 475 |
|
| 476 |
|
| 477 |
{ |
| 478 |
const sdcard_fat_file_t *hf = NULL; |
| 479 |
uint64_t off = 0u; |
| 480 |
sdcard_fat_region_t r = sdcard_fat_lookup(F32_DATA_LBA + 4u, &hf, &off); |
| 481 |
check(r == SDCARD_FAT_REGION_FILE, "cluster 6 is REGION_FILE"); |
| 482 |
check(hf != NULL && strcmp(hf->path, "/A LONG NAME.PRG") == 0, " ...names /A LONG NAME.PRG"); |
| 483 |
check_eq((uint32_t)off, 512u, " ...offset 512 (second cluster)"); |
| 484 |
} |
| 485 |
|
| 486 |
|
| 487 |
|
| 488 |
{ |
| 489 |
const sdcard_fat_file_t *hf = NULL; |
| 490 |
uint64_t off = 999u; |
| 491 |
sdcard_fat_region_t r = sdcard_fat_lookup(F32_DATA_LBA + 0u, &hf, &off); |
| 492 |
check(r == SDCARD_FAT_REGION_DIR, |
| 493 |
"FAT32 root dir LBA is REGION_DIR (not FREE)"); |
| 494 |
check(hf != NULL && strcmp(hf->path, "/") == 0, |
| 495 |
" ...attributed to '/'"); |
| 496 |
check_eq((uint32_t)off, 0u, " ...at offset 0"); |
| 497 |
} |
| 498 |
|
| 499 |
|
| 500 |
{ |
| 501 |
check(!sdcard_fat_is_stale(), "not stale before root-dir write"); |
| 502 |
sdcard_fat_note_access(F32_DATA_LBA + 0u, true, 512); |
| 503 |
check(sdcard_fat_is_stale(), "stale after write to FAT32 root dir sector"); |
| 504 |
} |
| 505 |
|
| 506 |
|
| 507 |
{ |
| 508 |
sdcard_fat_region_t r = sdcard_fat_lookup(F32_DATA_LBA + 100u, NULL, NULL); |
| 509 |
check(r == SDCARD_FAT_REGION_FREE || r == SDCARD_FAT_REGION_UNKNOWN, |
| 510 |
"unallocated LBA returns FREE or UNKNOWN (no crash)"); |
| 511 |
} |
| 512 |
|
| 513 |
sdcard_fat_free(); |
| 514 |
check(!sdcard_fat_ready(), "sdcard_fat_ready() is false after free"); |
| 515 |
|
| 516 |
x16close(img); |
| 517 |
remove(path); |
| 518 |
} |
| 519 |
|
| 520 |
static void |
| 521 |
test_fat16(void) |
| 522 |
{ |
| 523 |
printf("\n--- FAT16 image tests ---\n"); |
| 524 |
|
| 525 |
disk_t d = build_fat16_image(); |
| 526 |
const char *path = write_temp_disk(&d, "fat16.img"); |
| 527 |
disk_free(&d); |
| 528 |
|
| 529 |
check(path != NULL, "FAT16 image written to disk"); |
| 530 |
if (!path) { return; } |
| 531 |
|
| 532 |
struct x16file *img = open_and_build(path, true, "FAT16 image opened"); |
| 533 |
if (!img) { remove(path); return; } |
| 534 |
|
| 535 |
check(sdcard_fat_ready(), "sdcard_fat_ready() is true"); |
| 536 |
check(strcmp(sdcard_fat_type_name(), "FAT16") == 0, "type name is FAT16"); |
| 537 |
|
| 538 |
check(find_file("/ONE.TXT") != NULL, "found /ONE.TXT"); |
| 539 |
const sdcard_fat_file_t *ft = find_file("/ONE.TXT"); |
| 540 |
if (ft) { check_eq(ft->size, 42u, "/ONE.TXT size == 42"); } |
| 541 |
|
| 542 |
|
| 543 |
{ |
| 544 |
sdcard_fat_region_t r = sdcard_fat_lookup(F16_FAT1_LBA, NULL, NULL); |
| 545 |
check(r == SDCARD_FAT_REGION_FAT, "LBA 4 is REGION_FAT"); |
| 546 |
} |
| 547 |
|
| 548 |
|
| 549 |
{ |
| 550 |
sdcard_fat_region_t r = sdcard_fat_lookup(F16_ROOT_LBA, NULL, NULL); |
| 551 |
check(r == SDCARD_FAT_REGION_ROOTDIR, "LBA 36 is REGION_ROOTDIR (FAT16 fixed)"); |
| 552 |
} |
| 553 |
|
| 554 |
|
| 555 |
{ |
| 556 |
const sdcard_fat_file_t *hf = NULL; |
| 557 |
uint64_t off = 999u; |
| 558 |
sdcard_fat_region_t r = sdcard_fat_lookup(F16_DATA_LBA, &hf, &off); |
| 559 |
check(r == SDCARD_FAT_REGION_FILE, "cluster 2 is REGION_FILE"); |
| 560 |
check(hf != NULL && strcmp(hf->path, "/ONE.TXT") == 0, " ...names /ONE.TXT"); |
| 561 |
check_eq((uint32_t)off, 0u, " ...offset 0"); |
| 562 |
} |
| 563 |
|
| 564 |
|
| 565 |
{ |
| 566 |
sdcard_fat_region_t r = sdcard_fat_lookup(0, NULL, NULL); |
| 567 |
check(r == SDCARD_FAT_REGION_RESERVED, "FAT16 superfloppy LBA 0 is RESERVED (VBR)"); |
| 568 |
} |
| 569 |
|
| 570 |
sdcard_fat_free(); |
| 571 |
x16close(img); |
| 572 |
remove(path); |
| 573 |
} |
| 574 |
|
| 575 |
static void |
| 576 |
test_garbage_image(void) |
| 577 |
{ |
| 578 |
printf("\n--- Garbage image tests ---\n"); |
| 579 |
|
| 580 |
disk_t d = disk_alloc(2u); |
| 581 |
const char *path = write_temp_disk(&d, "garbage.img"); |
| 582 |
disk_free(&d); |
| 583 |
|
| 584 |
check(path != NULL, "garbage image written"); |
| 585 |
if (!path) { return; } |
| 586 |
|
| 587 |
struct x16file *img = x16open(path, "rb"); |
| 588 |
check(img != NULL, "garbage image opened"); |
| 589 |
if (!img) { remove(path); return; } |
| 590 |
|
| 591 |
bool ok = sdcard_fat_build(img); |
| 592 |
check(!ok, "sdcard_fat_build returns false for zero image"); |
| 593 |
check(!sdcard_fat_ready(), "sdcard_fat_ready() is false after failed build"); |
| 594 |
|
| 595 |
sdcard_fat_region_t r = sdcard_fat_lookup(0, NULL, NULL); |
| 596 |
check(r == SDCARD_FAT_REGION_UNKNOWN, "lookup on unbuilt index returns UNKNOWN"); |
| 597 |
|
| 598 |
x16close(img); |
| 599 |
remove(path); |
| 600 |
} |
| 601 |
|
| 602 |
static void |
| 603 |
test_stale(void) |
| 604 |
{ |
| 605 |
printf("\n--- Stale tracking tests ---\n"); |
| 606 |
|
| 607 |
disk_t d = build_fat32_image(); |
| 608 |
const char *path = write_temp_disk(&d, "stale.img"); |
| 609 |
disk_free(&d); |
| 610 |
|
| 611 |
if (!path) { return; } |
| 612 |
|
| 613 |
struct x16file *img = x16open(path, "rb"); |
| 614 |
check(img != NULL, "stale test: image opened"); |
| 615 |
if (!img) { remove(path); return; } |
| 616 |
|
| 617 |
bool ok = sdcard_fat_build(img); |
| 618 |
check(ok, "stale test: build succeeds"); |
| 619 |
check(!sdcard_fat_is_stale(), "not stale immediately after build"); |
| 620 |
|
| 621 |
|
| 622 |
sdcard_fat_note_access(FAT1_LBA, true, 512); |
| 623 |
check(sdcard_fat_is_stale(), "stale after write to a FAT sector"); |
| 624 |
|
| 625 |
|
| 626 |
sdcard_fat_free(); |
| 627 |
sdcard_fat_build(img); |
| 628 |
check(!sdcard_fat_is_stale(), "not stale after rebuild"); |
| 629 |
sdcard_fat_note_access(F32_DATA_LBA + 2u, false, 512); |
| 630 |
check(!sdcard_fat_is_stale(), "read of file data does not mark stale"); |
| 631 |
|
| 632 |
sdcard_fat_free(); |
| 633 |
x16close(img); |
| 634 |
remove(path); |
| 635 |
} |
| 636 |
|
| 637 |
|
| 638 |
|
| 639 |
int |
| 640 |
main(void) |
| 641 |
{ |
| 642 |
test_fat32(); |
| 643 |
test_fat16(); |
| 644 |
test_garbage_image(); |
| 645 |
test_stale(); |
| 646 |
|
| 647 |
return x16_test_summary("sdcard_fat"); |
| 648 |
} |