1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 Google LLC 5 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank 6 * Copyright (c) 1995 Martin Husemann 7 * Some structure declaration borrowed from Paul Popelka 8 * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include <assert.h> 32 #include <inttypes.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <ctype.h> 37 #include <unistd.h> 38 #include <time.h> 39 40 #include <sys/param.h> 41 42 #include "ext.h" 43 #include "fsutil.h" 44 45 #define SLOT_EMPTY 0x00 /* slot has never been used */ 46 #define SLOT_E5 0x05 /* the real value is 0xe5 */ 47 #define SLOT_DELETED 0xe5 /* file in this slot deleted */ 48 49 #define ATTR_NORMAL 0x00 /* normal file */ 50 #define ATTR_READONLY 0x01 /* file is readonly */ 51 #define ATTR_HIDDEN 0x02 /* file is hidden */ 52 #define ATTR_SYSTEM 0x04 /* file is a system file */ 53 #define ATTR_VOLUME 0x08 /* entry is a volume label */ 54 #define ATTR_DIRECTORY 0x10 /* entry is a directory name */ 55 #define ATTR_ARCHIVE 0x20 /* file is new or modified */ 56 57 #define ATTR_WIN95 0x0f /* long name record */ 58 59 /* 60 * This is the format of the contents of the deTime field in the direntry 61 * structure. 62 * We don't use bitfields because we don't know how compilers for 63 * arbitrary machines will lay them out. 64 */ 65 #define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */ 66 #define DT_2SECONDS_SHIFT 0 67 #define DT_MINUTES_MASK 0x7E0 /* minutes */ 68 #define DT_MINUTES_SHIFT 5 69 #define DT_HOURS_MASK 0xF800 /* hours */ 70 #define DT_HOURS_SHIFT 11 71 72 /* 73 * This is the format of the contents of the deDate field in the direntry 74 * structure. 75 */ 76 #define DD_DAY_MASK 0x1F /* day of month */ 77 #define DD_DAY_SHIFT 0 78 #define DD_MONTH_MASK 0x1E0 /* month */ 79 #define DD_MONTH_SHIFT 5 80 #define DD_YEAR_MASK 0xFE00 /* year - 1980 */ 81 #define DD_YEAR_SHIFT 9 82 83 84 /* dir.c */ 85 static struct dosDirEntry *newDosDirEntry(void); 86 static void freeDosDirEntry(struct dosDirEntry *); 87 static struct dirTodoNode *newDirTodo(void); 88 static void freeDirTodo(struct dirTodoNode *); 89 static char *fullpath(struct dosDirEntry *); 90 static u_char calcShortSum(u_char *); 91 static int delete(struct fat_descriptor *, cl_t, int, cl_t, int, int); 92 static int removede(struct fat_descriptor *, u_char *, u_char *, 93 cl_t, cl_t, cl_t, char *, int); 94 static int checksize(struct fat_descriptor *, u_char *, struct dosDirEntry *); 95 static int readDosDirSection(struct fat_descriptor *, struct dosDirEntry *); 96 97 /* 98 * Manage free dosDirEntry structures. 99 */ 100 static struct dosDirEntry *freede; 101 102 static struct dosDirEntry * 103 newDosDirEntry(void) 104 { 105 struct dosDirEntry *de; 106 107 if (!(de = freede)) { 108 if (!(de = malloc(sizeof(*de)))) 109 return (NULL); 110 } else 111 freede = de->next; 112 return de; 113 } 114 115 static void 116 freeDosDirEntry(struct dosDirEntry *de) 117 { 118 de->next = freede; 119 freede = de; 120 } 121 122 /* 123 * The same for dirTodoNode structures. 124 */ 125 static struct dirTodoNode *freedt; 126 127 static struct dirTodoNode * 128 newDirTodo(void) 129 { 130 struct dirTodoNode *dt; 131 132 if (!(dt = freedt)) { 133 if (!(dt = malloc(sizeof(*dt)))) 134 return 0; 135 } else 136 freedt = dt->next; 137 return dt; 138 } 139 140 static void 141 freeDirTodo(struct dirTodoNode *dt) 142 { 143 dt->next = freedt; 144 freedt = dt; 145 } 146 147 /* 148 * The stack of unread directories 149 */ 150 static struct dirTodoNode *pendingDirectories = NULL; 151 152 /* 153 * Return the full pathname for a directory entry. 154 */ 155 static char * 156 fullpath(struct dosDirEntry *dir) 157 { 158 static char namebuf[MAXPATHLEN + 1]; 159 char *cp, *np; 160 int nl; 161 162 cp = namebuf + sizeof namebuf; 163 *--cp = '\0'; 164 165 for(;;) { 166 np = dir->lname[0] ? dir->lname : dir->name; 167 nl = strlen(np); 168 if (cp <= namebuf + 1 + nl) { 169 *--cp = '?'; 170 break; 171 } 172 cp -= nl; 173 memcpy(cp, np, nl); 174 dir = dir->parent; 175 if (!dir) 176 break; 177 *--cp = '/'; 178 } 179 180 return cp; 181 } 182 183 /* 184 * Calculate a checksum over an 8.3 alias name 185 */ 186 static inline u_char 187 calcShortSum(u_char *p) 188 { 189 u_char sum = 0; 190 int i; 191 192 for (i = 0; i < 11; i++) { 193 sum = (sum << 7)|(sum >> 1); /* rotate right */ 194 sum += p[i]; 195 } 196 197 return sum; 198 } 199 200 /* 201 * Global variables temporarily used during a directory scan 202 */ 203 static char longName[DOSLONGNAMELEN] = ""; 204 static u_char *buffer = NULL; 205 static u_char *delbuf = NULL; 206 207 static struct dosDirEntry *rootDir; 208 static struct dosDirEntry *lostDir; 209 210 /* 211 * Init internal state for a new directory scan. 212 */ 213 int 214 resetDosDirSection(struct fat_descriptor *fat) 215 { 216 int rootdir_size, cluster_size; 217 int ret = FSOK; 218 size_t len; 219 struct bootblock *boot; 220 221 boot = fat_get_boot(fat); 222 223 rootdir_size = boot->bpbRootDirEnts * 32; 224 cluster_size = boot->bpbSecPerClust * boot->bpbBytesPerSec; 225 226 if ((buffer = malloc(len = MAX(rootdir_size, cluster_size))) == NULL) { 227 perr("No space for directory buffer (%zu)", len); 228 return FSFATAL; 229 } 230 231 if ((delbuf = malloc(len = cluster_size)) == NULL) { 232 free(buffer); 233 perr("No space for directory delbuf (%zu)", len); 234 return FSFATAL; 235 } 236 237 if ((rootDir = newDosDirEntry()) == NULL) { 238 free(buffer); 239 free(delbuf); 240 perr("No space for directory entry"); 241 return FSFATAL; 242 } 243 244 memset(rootDir, 0, sizeof *rootDir); 245 if (boot->flags & FAT32) { 246 if (!fat_is_cl_head(fat, boot->bpbRootClust)) { 247 free(buffer); 248 free(delbuf); 249 pfatal("Root directory doesn't start a cluster chain"); 250 return FSFATAL; 251 } 252 rootDir->head = boot->bpbRootClust; 253 } 254 255 return ret; 256 } 257 258 /* 259 * Cleanup after a directory scan 260 */ 261 void 262 finishDosDirSection(void) 263 { 264 struct dirTodoNode *p, *np; 265 struct dosDirEntry *d, *nd; 266 267 for (p = pendingDirectories; p; p = np) { 268 np = p->next; 269 freeDirTodo(p); 270 } 271 pendingDirectories = NULL; 272 for (d = rootDir; d; d = nd) { 273 if ((nd = d->child) != NULL) { 274 d->child = 0; 275 continue; 276 } 277 if (!(nd = d->next)) 278 nd = d->parent; 279 freeDosDirEntry(d); 280 } 281 rootDir = lostDir = NULL; 282 free(buffer); 283 free(delbuf); 284 buffer = NULL; 285 delbuf = NULL; 286 } 287 288 /* 289 * Delete directory entries between startcl, startoff and endcl, endoff. 290 */ 291 static int 292 delete(struct fat_descriptor *fat, cl_t startcl, 293 int startoff, cl_t endcl, int endoff, int notlast) 294 { 295 u_char *s, *e; 296 off_t off; 297 int clsz, fd; 298 struct bootblock *boot; 299 300 boot = fat_get_boot(fat); 301 fd = fat_get_fd(fat); 302 clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec; 303 304 s = delbuf + startoff; 305 e = delbuf + clsz; 306 while (fat_is_valid_cl(fat, startcl)) { 307 if (startcl == endcl) { 308 if (notlast) 309 break; 310 e = delbuf + endoff; 311 } 312 off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; 313 314 off *= boot->bpbBytesPerSec; 315 if (lseek(fd, off, SEEK_SET) != off) { 316 perr("Unable to lseek to %" PRId64, off); 317 return FSFATAL; 318 } 319 if (read(fd, delbuf, clsz) != clsz) { 320 perr("Unable to read directory"); 321 return FSFATAL; 322 } 323 while (s < e) { 324 *s = SLOT_DELETED; 325 s += 32; 326 } 327 if (lseek(fd, off, SEEK_SET) != off) { 328 perr("Unable to lseek to %" PRId64, off); 329 return FSFATAL; 330 } 331 if (write(fd, delbuf, clsz) != clsz) { 332 perr("Unable to write directory"); 333 return FSFATAL; 334 } 335 if (startcl == endcl) 336 break; 337 startcl = fat_get_cl_next(fat, startcl); 338 s = delbuf; 339 } 340 return FSOK; 341 } 342 343 static int 344 removede(struct fat_descriptor *fat, u_char *start, 345 u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, 346 char *path, int type) 347 { 348 switch (type) { 349 case 0: 350 pwarn("Invalid long filename entry for %s\n", path); 351 break; 352 case 1: 353 pwarn("Invalid long filename entry at end of directory %s\n", 354 path); 355 break; 356 case 2: 357 pwarn("Invalid long filename entry for volume label\n"); 358 break; 359 } 360 if (ask(0, "Remove")) { 361 if (startcl != curcl) { 362 if (delete(fat, 363 startcl, start - buffer, 364 endcl, end - buffer, 365 endcl == curcl) == FSFATAL) 366 return FSFATAL; 367 start = buffer; 368 } 369 /* startcl is < CLUST_FIRST for !FAT32 root */ 370 if ((endcl == curcl) || (startcl < CLUST_FIRST)) 371 for (; start < end; start += 32) 372 *start = SLOT_DELETED; 373 return FSDIRMOD; 374 } 375 return FSERROR; 376 } 377 378 /* 379 * Check an in-memory file entry 380 */ 381 static int 382 checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir) 383 { 384 int ret = FSOK; 385 size_t chainsize; 386 u_int64_t physicalSize; 387 struct bootblock *boot; 388 389 boot = fat_get_boot(fat); 390 391 /* 392 * Check size on ordinary files 393 */ 394 if (dir->head == CLUST_FREE) { 395 physicalSize = 0; 396 } else { 397 if (!fat_is_valid_cl(fat, dir->head) || !fat_is_cl_head(fat, dir->head)) { 398 pwarn("Directory entry %s of size %u referencing invalid cluster %u\n", 399 fullpath(dir), dir->size, dir->head); 400 if (ask(1, "Truncate")) { 401 p[28] = p[29] = p[30] = p[31] = 0; 402 p[26] = p[27] = 0; 403 if (boot->ClustMask == CLUST32_MASK) 404 p[20] = p[21] = 0; 405 dir->size = 0; 406 dir->head = CLUST_FREE; 407 return FSDIRMOD; 408 } else { 409 return FSERROR; 410 } 411 } 412 ret = checkchain(fat, dir->head, &chainsize); 413 /* 414 * Upon return, chainsize would hold the chain length 415 * that checkchain() was able to validate, but if the user 416 * refused the proposed repair, it would be unsafe to 417 * proceed with directory entry fix, so bail out in that 418 * case. 419 */ 420 if (ret == FSERROR) { 421 return (FSERROR); 422 } 423 /* 424 * The maximum file size on FAT32 is 4GiB - 1, which 425 * will occupy a cluster chain of exactly 4GiB in 426 * size. On 32-bit platforms, since size_t is 32-bit, 427 * it would wrap back to 0. 428 */ 429 physicalSize = (u_int64_t)chainsize * boot->ClusterSize; 430 } 431 if (physicalSize < dir->size) { 432 pwarn("size of %s is %u, should at most be %ju\n", 433 fullpath(dir), dir->size, (uintmax_t)physicalSize); 434 if (ask(1, "Truncate")) { 435 dir->size = physicalSize; 436 p[28] = (u_char)physicalSize; 437 p[29] = (u_char)(physicalSize >> 8); 438 p[30] = (u_char)(physicalSize >> 16); 439 p[31] = (u_char)(physicalSize >> 24); 440 return FSDIRMOD; 441 } else 442 return FSERROR; 443 } else if (physicalSize - dir->size >= boot->ClusterSize) { 444 pwarn("%s has too many clusters allocated\n", 445 fullpath(dir)); 446 if (ask(1, "Drop superfluous clusters")) { 447 cl_t cl; 448 uint32_t sz, len; 449 450 for (cl = dir->head, len = sz = 0; 451 (sz += boot->ClusterSize) < dir->size; len++) 452 cl = fat_get_cl_next(fat, cl); 453 clearchain(fat, fat_get_cl_next(fat, cl)); 454 ret = fat_set_cl_next(fat, cl, CLUST_EOF); 455 return (FSFATMOD | ret); 456 } else 457 return FSERROR; 458 } 459 return FSOK; 460 } 461 462 static const u_char dot_name[11] = ". "; 463 static const u_char dotdot_name[11] = ".. "; 464 465 /* 466 * Basic sanity check if the subdirectory have good '.' and '..' entries, 467 * and they are directory entries. Further sanity checks are performed 468 * when we traverse into it. 469 */ 470 static int 471 check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir) 472 { 473 u_char *buf, *cp; 474 off_t off; 475 cl_t cl; 476 int retval = FSOK; 477 int fd; 478 struct bootblock *boot; 479 480 boot = fat_get_boot(fat); 481 fd = fat_get_fd(fat); 482 483 cl = dir->head; 484 if (dir->parent && !fat_is_valid_cl(fat, cl)) { 485 return FSERROR; 486 } 487 488 if (!(boot->flags & FAT32) && !dir->parent) { 489 off = boot->bpbResSectors + boot->bpbFATs * 490 boot->FATsecs; 491 } else { 492 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; 493 } 494 495 /* 496 * We only need to check the first two entries of the directory, 497 * which is found in the first sector of the directory entry, 498 * so read in only the first sector. 499 */ 500 buf = malloc(boot->bpbBytesPerSec); 501 if (buf == NULL) { 502 perr("No space for directory buffer (%u)", 503 boot->bpbBytesPerSec); 504 return FSFATAL; 505 } 506 507 off *= boot->bpbBytesPerSec; 508 if (lseek(fd, off, SEEK_SET) != off || 509 read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) { 510 perr("Unable to read directory"); 511 free(buf); 512 return FSFATAL; 513 } 514 515 /* 516 * Both `.' and `..' must be present and be the first two entries 517 * and be ATTR_DIRECTORY of a valid subdirectory. 518 */ 519 cp = buf; 520 if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 || 521 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) { 522 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name); 523 retval |= FSERROR; 524 } 525 cp += 32; 526 if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 || 527 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) { 528 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name); 529 retval |= FSERROR; 530 } 531 532 free(buf); 533 return retval; 534 } 535 536 /* 537 * Read a directory and 538 * - resolve long name records 539 * - enter file and directory records into the parent's list 540 * - push directories onto the todo-stack 541 */ 542 static int 543 readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir) 544 { 545 struct bootblock *boot; 546 struct dosDirEntry dirent, *d; 547 u_char *p, *vallfn, *invlfn, *empty; 548 off_t off; 549 int fd, i, j, k, iosize, entries; 550 bool is_legacyroot; 551 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; 552 char *t; 553 u_int lidx = 0; 554 int shortSum; 555 int mod = FSOK; 556 size_t dirclusters; 557 #define THISMOD 0x8000 /* Only used within this routine */ 558 559 boot = fat_get_boot(fat); 560 fd = fat_get_fd(fat); 561 562 cl = dir->head; 563 if (dir->parent && (!fat_is_valid_cl(fat, cl))) { 564 /* 565 * Already handled somewhere else. 566 */ 567 return FSOK; 568 } 569 shortSum = -1; 570 vallfn = invlfn = empty = NULL; 571 572 /* 573 * If we are checking the legacy root (for FAT12/FAT16), 574 * we will operate on the whole directory; otherwise, we 575 * will operate on one cluster at a time, and also take 576 * this opportunity to examine the chain. 577 * 578 * Derive how many entries we are going to encounter from 579 * the I/O size. 580 */ 581 is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32)); 582 if (is_legacyroot) { 583 iosize = boot->bpbRootDirEnts * 32; 584 entries = boot->bpbRootDirEnts; 585 } else { 586 iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec; 587 entries = iosize / 32; 588 mod |= checkchain(fat, dir->head, &dirclusters); 589 } 590 591 do { 592 if (is_legacyroot) { 593 /* 594 * Special case for FAT12/FAT16 root -- read 595 * in the whole root directory. 596 */ 597 off = boot->bpbResSectors + boot->bpbFATs * 598 boot->FATsecs; 599 } else { 600 /* 601 * Otherwise, read in a cluster of the 602 * directory. 603 */ 604 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; 605 } 606 607 off *= boot->bpbBytesPerSec; 608 if (lseek(fd, off, SEEK_SET) != off || 609 read(fd, buffer, iosize) != iosize) { 610 perr("Unable to read directory"); 611 return FSFATAL; 612 } 613 614 for (p = buffer, i = 0; i < entries; i++, p += 32) { 615 if (dir->fsckflags & DIREMPWARN) { 616 *p = SLOT_EMPTY; 617 continue; 618 } 619 620 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { 621 if (*p == SLOT_EMPTY) { 622 dir->fsckflags |= DIREMPTY; 623 empty = p; 624 empcl = cl; 625 } 626 continue; 627 } 628 629 if (dir->fsckflags & DIREMPTY) { 630 if (!(dir->fsckflags & DIREMPWARN)) { 631 pwarn("%s has entries after end of directory\n", 632 fullpath(dir)); 633 if (ask(1, "Extend")) { 634 u_char *q; 635 636 dir->fsckflags &= ~DIREMPTY; 637 if (delete(fat, 638 empcl, empty - buffer, 639 cl, p - buffer, 1) == FSFATAL) 640 return FSFATAL; 641 q = ((empcl == cl) ? empty : buffer); 642 assert(q != NULL); 643 for (; q < p; q += 32) 644 *q = SLOT_DELETED; 645 mod |= THISMOD|FSDIRMOD; 646 } else if (ask(0, "Truncate")) 647 dir->fsckflags |= DIREMPWARN; 648 } 649 if (dir->fsckflags & DIREMPWARN) { 650 *p = SLOT_DELETED; 651 mod |= THISMOD|FSDIRMOD; 652 continue; 653 } else if (dir->fsckflags & DIREMPTY) 654 mod |= FSERROR; 655 empty = NULL; 656 } 657 658 if (p[11] == ATTR_WIN95) { 659 if (*p & LRFIRST) { 660 if (shortSum != -1) { 661 if (!invlfn) { 662 invlfn = vallfn; 663 invcl = valcl; 664 } 665 } 666 memset(longName, 0, sizeof longName); 667 shortSum = p[13]; 668 vallfn = p; 669 valcl = cl; 670 } else if (shortSum != p[13] 671 || lidx != (*p & LRNOMASK)) { 672 if (!invlfn) { 673 invlfn = vallfn; 674 invcl = valcl; 675 } 676 if (!invlfn) { 677 invlfn = p; 678 invcl = cl; 679 } 680 vallfn = NULL; 681 } 682 lidx = *p & LRNOMASK; 683 if (lidx == 0) { 684 pwarn("invalid long name\n"); 685 if (!invlfn) { 686 invlfn = vallfn; 687 invcl = valcl; 688 } 689 vallfn = NULL; 690 continue; 691 } 692 t = longName + --lidx * 13; 693 for (k = 1; k < 11 && t < longName + 694 sizeof(longName); k += 2) { 695 if (!p[k] && !p[k + 1]) 696 break; 697 *t++ = p[k]; 698 /* 699 * Warn about those unusable chars in msdosfs here? XXX 700 */ 701 if (p[k + 1]) 702 t[-1] = '?'; 703 } 704 if (k >= 11) 705 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) { 706 if (!p[k] && !p[k + 1]) 707 break; 708 *t++ = p[k]; 709 if (p[k + 1]) 710 t[-1] = '?'; 711 } 712 if (k >= 26) 713 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) { 714 if (!p[k] && !p[k + 1]) 715 break; 716 *t++ = p[k]; 717 if (p[k + 1]) 718 t[-1] = '?'; 719 } 720 if (t >= longName + sizeof(longName)) { 721 pwarn("long filename too long\n"); 722 if (!invlfn) { 723 invlfn = vallfn; 724 invcl = valcl; 725 } 726 vallfn = NULL; 727 } 728 if (p[26] | (p[27] << 8)) { 729 pwarn("long filename record cluster start != 0\n"); 730 if (!invlfn) { 731 invlfn = vallfn; 732 invcl = cl; 733 } 734 vallfn = NULL; 735 } 736 continue; /* long records don't carry further 737 * information */ 738 } 739 740 /* 741 * This is a standard msdosfs directory entry. 742 */ 743 memset(&dirent, 0, sizeof dirent); 744 745 /* 746 * it's a short name record, but we need to know 747 * more, so get the flags first. 748 */ 749 dirent.flags = p[11]; 750 751 /* 752 * Translate from 850 to ISO here XXX 753 */ 754 for (j = 0; j < 8; j++) 755 dirent.name[j] = p[j]; 756 dirent.name[8] = '\0'; 757 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--) 758 dirent.name[k] = '\0'; 759 if (k < 0 || dirent.name[k] != '\0') 760 k++; 761 if (dirent.name[0] == SLOT_E5) 762 dirent.name[0] = 0xe5; 763 764 if (dirent.flags & ATTR_VOLUME) { 765 if (vallfn || invlfn) { 766 mod |= removede(fat, 767 invlfn ? invlfn : vallfn, p, 768 invlfn ? invcl : valcl, -1, 0, 769 fullpath(dir), 2); 770 vallfn = NULL; 771 invlfn = NULL; 772 } 773 continue; 774 } 775 776 if (p[8] != ' ') 777 dirent.name[k++] = '.'; 778 for (j = 0; j < 3; j++) 779 dirent.name[k++] = p[j+8]; 780 dirent.name[k] = '\0'; 781 for (k--; k >= 0 && dirent.name[k] == ' '; k--) 782 dirent.name[k] = '\0'; 783 784 if (vallfn && shortSum != calcShortSum(p)) { 785 if (!invlfn) { 786 invlfn = vallfn; 787 invcl = valcl; 788 } 789 vallfn = NULL; 790 } 791 dirent.head = p[26] | (p[27] << 8); 792 if (boot->ClustMask == CLUST32_MASK) 793 dirent.head |= (p[20] << 16) | (p[21] << 24); 794 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24); 795 if (vallfn) { 796 strlcpy(dirent.lname, longName, 797 sizeof(dirent.lname)); 798 longName[0] = '\0'; 799 shortSum = -1; 800 } 801 802 dirent.parent = dir; 803 dirent.next = dir->child; 804 805 if (invlfn) { 806 mod |= k = removede(fat, 807 invlfn, vallfn ? vallfn : p, 808 invcl, vallfn ? valcl : cl, cl, 809 fullpath(&dirent), 0); 810 if (mod & FSFATAL) 811 return FSFATAL; 812 if (vallfn 813 ? (valcl == cl && vallfn != buffer) 814 : p != buffer) 815 if (k & FSDIRMOD) 816 mod |= THISMOD; 817 } 818 819 vallfn = NULL; /* not used any longer */ 820 invlfn = NULL; 821 822 /* 823 * Check if the directory entry is sane. 824 * 825 * '.' and '..' are skipped, their sanity is 826 * checked somewhere else. 827 * 828 * For everything else, check if we have a new, 829 * valid cluster chain (beginning of a file or 830 * directory that was never previously claimed 831 * by another file) when it's a non-empty file 832 * or a directory. The sanity of the cluster 833 * chain is checked at a later time when we 834 * traverse into the directory, or examine the 835 * file's directory entry. 836 * 837 * The only possible fix is to delete the entry 838 * if it's a directory; for file, we have to 839 * truncate the size to 0. 840 */ 841 if (!(dirent.flags & ATTR_DIRECTORY) || 842 (strcmp(dirent.name, ".") != 0 && 843 strcmp(dirent.name, "..") != 0)) { 844 if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) && 845 ((!fat_is_valid_cl(fat, dirent.head) || 846 !fat_is_cl_head(fat, dirent.head)))) { 847 if (!fat_is_valid_cl(fat, dirent.head)) { 848 pwarn("%s starts with cluster out of range(%u)\n", 849 fullpath(&dirent), 850 dirent.head); 851 } else { 852 pwarn("%s doesn't start a new cluster chain\n", 853 fullpath(&dirent)); 854 } 855 856 if (dirent.flags & ATTR_DIRECTORY) { 857 if (ask(0, "Remove")) { 858 *p = SLOT_DELETED; 859 mod |= THISMOD|FSDIRMOD; 860 } else 861 mod |= FSERROR; 862 continue; 863 } else { 864 if (ask(1, "Truncate")) { 865 p[28] = p[29] = p[30] = p[31] = 0; 866 p[26] = p[27] = 0; 867 if (boot->ClustMask == CLUST32_MASK) 868 p[20] = p[21] = 0; 869 dirent.size = 0; 870 dirent.head = 0; 871 mod |= THISMOD|FSDIRMOD; 872 } else 873 mod |= FSERROR; 874 } 875 } 876 } 877 if (dirent.flags & ATTR_DIRECTORY) { 878 /* 879 * gather more info for directories 880 */ 881 struct dirTodoNode *n; 882 883 if (dirent.size) { 884 pwarn("Directory %s has size != 0\n", 885 fullpath(&dirent)); 886 if (ask(1, "Correct")) { 887 p[28] = p[29] = p[30] = p[31] = 0; 888 dirent.size = 0; 889 mod |= THISMOD|FSDIRMOD; 890 } else 891 mod |= FSERROR; 892 } 893 /* 894 * handle `.' and `..' specially 895 */ 896 if (strcmp(dirent.name, ".") == 0) { 897 if (dirent.head != dir->head) { 898 pwarn("`.' entry in %s has incorrect start cluster\n", 899 fullpath(dir)); 900 if (ask(1, "Correct")) { 901 dirent.head = dir->head; 902 p[26] = (u_char)dirent.head; 903 p[27] = (u_char)(dirent.head >> 8); 904 if (boot->ClustMask == CLUST32_MASK) { 905 p[20] = (u_char)(dirent.head >> 16); 906 p[21] = (u_char)(dirent.head >> 24); 907 } 908 mod |= THISMOD|FSDIRMOD; 909 } else 910 mod |= FSERROR; 911 } 912 continue; 913 } else if (strcmp(dirent.name, "..") == 0) { 914 if (dir->parent) { /* XXX */ 915 if (!dir->parent->parent) { 916 if (dirent.head) { 917 pwarn("`..' entry in %s has non-zero start cluster\n", 918 fullpath(dir)); 919 if (ask(1, "Correct")) { 920 dirent.head = 0; 921 p[26] = p[27] = 0; 922 if (boot->ClustMask == CLUST32_MASK) 923 p[20] = p[21] = 0; 924 mod |= THISMOD|FSDIRMOD; 925 } else 926 mod |= FSERROR; 927 } 928 } else if (dirent.head != dir->parent->head) { 929 pwarn("`..' entry in %s has incorrect start cluster\n", 930 fullpath(dir)); 931 if (ask(1, "Correct")) { 932 dirent.head = dir->parent->head; 933 p[26] = (u_char)dirent.head; 934 p[27] = (u_char)(dirent.head >> 8); 935 if (boot->ClustMask == CLUST32_MASK) { 936 p[20] = (u_char)(dirent.head >> 16); 937 p[21] = (u_char)(dirent.head >> 24); 938 } 939 mod |= THISMOD|FSDIRMOD; 940 } else 941 mod |= FSERROR; 942 } 943 } 944 continue; 945 } else { 946 /* 947 * Only one directory entry can point 948 * to dir->head, it's '.'. 949 */ 950 if (dirent.head == dir->head) { 951 pwarn("%s entry in %s has incorrect start cluster\n", 952 dirent.name, fullpath(dir)); 953 if (ask(1, "Remove")) { 954 *p = SLOT_DELETED; 955 mod |= THISMOD|FSDIRMOD; 956 } else 957 mod |= FSERROR; 958 continue; 959 } else if ((check_subdirectory(fat, 960 &dirent) & FSERROR) == FSERROR) { 961 /* 962 * A subdirectory should have 963 * a dot (.) entry and a dot-dot 964 * (..) entry of ATTR_DIRECTORY, 965 * we will inspect further when 966 * traversing into it. 967 */ 968 if (ask(1, "Remove")) { 969 *p = SLOT_DELETED; 970 mod |= THISMOD|FSDIRMOD; 971 } else 972 mod |= FSERROR; 973 continue; 974 } 975 } 976 977 /* create directory tree node */ 978 if (!(d = newDosDirEntry())) { 979 perr("No space for directory"); 980 return FSFATAL; 981 } 982 memcpy(d, &dirent, sizeof(struct dosDirEntry)); 983 /* link it into the tree */ 984 dir->child = d; 985 986 /* Enter this directory into the todo list */ 987 if (!(n = newDirTodo())) { 988 perr("No space for todo list"); 989 return FSFATAL; 990 } 991 n->next = pendingDirectories; 992 n->dir = d; 993 pendingDirectories = n; 994 } else { 995 mod |= k = checksize(fat, p, &dirent); 996 if (k & FSDIRMOD) 997 mod |= THISMOD; 998 } 999 boot->NumFiles++; 1000 } 1001 1002 if (is_legacyroot) { 1003 /* 1004 * Don't bother to write back right now because 1005 * we may continue to make modification to the 1006 * non-FAT32 root directory below. 1007 */ 1008 break; 1009 } else if (mod & THISMOD) { 1010 if (lseek(fd, off, SEEK_SET) != off 1011 || write(fd, buffer, iosize) != iosize) { 1012 perr("Unable to write directory"); 1013 return FSFATAL; 1014 } 1015 mod &= ~THISMOD; 1016 } 1017 } while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl)))); 1018 if (invlfn || vallfn) 1019 mod |= removede(fat, 1020 invlfn ? invlfn : vallfn, p, 1021 invlfn ? invcl : valcl, -1, 0, 1022 fullpath(dir), 1); 1023 1024 /* 1025 * The root directory of non-FAT32 filesystems is in a special 1026 * area and may have been modified above removede() without 1027 * being written out. 1028 */ 1029 if ((mod & FSDIRMOD) && is_legacyroot) { 1030 if (lseek(fd, off, SEEK_SET) != off 1031 || write(fd, buffer, iosize) != iosize) { 1032 perr("Unable to write directory"); 1033 return FSFATAL; 1034 } 1035 mod &= ~THISMOD; 1036 } 1037 return mod & ~THISMOD; 1038 } 1039 1040 int 1041 handleDirTree(struct fat_descriptor *fat) 1042 { 1043 int mod; 1044 1045 mod = readDosDirSection(fat, rootDir); 1046 if (mod & FSFATAL) 1047 return FSFATAL; 1048 1049 /* 1050 * process the directory todo list 1051 */ 1052 while (pendingDirectories) { 1053 struct dosDirEntry *dir = pendingDirectories->dir; 1054 struct dirTodoNode *n = pendingDirectories->next; 1055 1056 /* 1057 * remove TODO entry now, the list might change during 1058 * directory reads 1059 */ 1060 freeDirTodo(pendingDirectories); 1061 pendingDirectories = n; 1062 1063 /* 1064 * handle subdirectory 1065 */ 1066 mod |= readDosDirSection(fat, dir); 1067 if (mod & FSFATAL) 1068 return FSFATAL; 1069 } 1070 1071 return mod; 1072 } 1073 1074 /* 1075 * Try to reconnect a FAT chain into dir 1076 */ 1077 static u_char *lfbuf; 1078 static cl_t lfcl; 1079 static off_t lfoff; 1080 1081 int 1082 reconnect(struct fat_descriptor *fat, cl_t head, size_t length) 1083 { 1084 struct bootblock *boot = fat_get_boot(fat); 1085 struct dosDirEntry d; 1086 int len, dosfs; 1087 u_char *p; 1088 1089 dosfs = fat_get_fd(fat); 1090 1091 if (!ask(1, "Reconnect")) 1092 return FSERROR; 1093 1094 if (!lostDir) { 1095 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) { 1096 if (!strcmp(lostDir->name, LOSTDIR)) 1097 break; 1098 } 1099 if (!lostDir) { /* Create LOSTDIR? XXX */ 1100 pwarn("No %s directory\n", LOSTDIR); 1101 return FSERROR; 1102 } 1103 } 1104 if (!lfbuf) { 1105 lfbuf = malloc(boot->ClusterSize); 1106 if (!lfbuf) { 1107 perr("No space for buffer"); 1108 return FSFATAL; 1109 } 1110 p = NULL; 1111 } else 1112 p = lfbuf; 1113 while (1) { 1114 if (p) 1115 for (; p < lfbuf + boot->ClusterSize; p += 32) 1116 if (*p == SLOT_EMPTY 1117 || *p == SLOT_DELETED) 1118 break; 1119 if (p && p < lfbuf + boot->ClusterSize) 1120 break; 1121 lfcl = p ? fat_get_cl_next(fat, lfcl) : lostDir->head; 1122 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) { 1123 /* Extend LOSTDIR? XXX */ 1124 pwarn("No space in %s\n", LOSTDIR); 1125 lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0; 1126 return FSERROR; 1127 } 1128 lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize 1129 + boot->FirstCluster * boot->bpbBytesPerSec; 1130 1131 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 1132 || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 1133 perr("could not read LOST.DIR"); 1134 return FSFATAL; 1135 } 1136 p = lfbuf; 1137 } 1138 1139 boot->NumFiles++; 1140 /* Ensure uniqueness of entry here! XXX */ 1141 memset(&d, 0, sizeof d); 1142 /* worst case -1 = 4294967295, 10 digits */ 1143 len = snprintf(d.name, sizeof(d.name), "%u", head); 1144 d.flags = 0; 1145 d.head = head; 1146 d.size = length * boot->ClusterSize; 1147 1148 memcpy(p, d.name, len); 1149 memset(p + len, ' ', 11 - len); 1150 memset(p + 11, 0, 32 - 11); 1151 p[26] = (u_char)d.head; 1152 p[27] = (u_char)(d.head >> 8); 1153 if (boot->ClustMask == CLUST32_MASK) { 1154 p[20] = (u_char)(d.head >> 16); 1155 p[21] = (u_char)(d.head >> 24); 1156 } 1157 p[28] = (u_char)d.size; 1158 p[29] = (u_char)(d.size >> 8); 1159 p[30] = (u_char)(d.size >> 16); 1160 p[31] = (u_char)(d.size >> 24); 1161 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 1162 || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 1163 perr("could not write LOST.DIR"); 1164 return FSFATAL; 1165 } 1166 return FSDIRMOD; 1167 } 1168 1169 void 1170 finishlf(void) 1171 { 1172 if (lfbuf) 1173 free(lfbuf); 1174 lfbuf = NULL; 1175 } 1176