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