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 */ 91static struct dosDirEntry *newDosDirEntry(void); 92static void freeDosDirEntry(struct dosDirEntry *); 93static struct dirTodoNode *newDirTodo(void); 94static void freeDirTodo(struct dirTodoNode *); 95static char *fullpath(struct dosDirEntry *); 96static u_char calcShortSum(u_char *); 97static int delete(struct fat_descriptor *, cl_t, int, cl_t, int, int); 98static int removede(struct fat_descriptor *, u_char *, u_char *, 99 cl_t, cl_t, cl_t, char *, int); 100static int checksize(struct fat_descriptor *, u_char *, struct dosDirEntry *); 101static int readDosDirSection(struct fat_descriptor *, struct dosDirEntry *); 102 103/* 104 * Manage free dosDirEntry structures. 105 */ 106static struct dosDirEntry *freede; 107 108static struct dosDirEntry * 109newDosDirEntry(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 121static void 122freeDosDirEntry(struct dosDirEntry *de) 123{ 124 de->next = freede; 125 freede = de; 126} 127 128/* 129 * The same for dirTodoNode structures. 130 */ 131static struct dirTodoNode *freedt; 132 133static struct dirTodoNode * 134newDirTodo(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 146static void 147freeDirTodo(struct dirTodoNode *dt) 148{ 149 dt->next = freedt; 150 freedt = dt; 151} 152 153/* 154 * The stack of unread directories 155 */ 156static struct dirTodoNode *pendingDirectories = NULL; 157 158/* 159 * Return the full pathname for a directory entry. 160 */ 161static char * 162fullpath(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 */ 192static inline u_char 193calcShortSum(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 */ 209static char longName[DOSLONGNAMELEN] = ""; 210static u_char *buffer = NULL; 211static u_char *delbuf = NULL; 212 213static struct dosDirEntry *rootDir; 214static struct dosDirEntry *lostDir; 215 216/* 217 * Init internal state for a new directory scan. 218 */ 219int 220resetDosDirSection(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 */ 265void 266finishDosDirSection(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 */ 295static int 296delete(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 347static int 348removede(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 */ 385static int 386checksize(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)) 402 return FSERROR; 403 ret = checkchain(fat, dir->head, &chainsize); 404 /* 405 * Upon return, chainsize would hold the chain length 406 * that checkchain() was able to validate, but if the user 407 * refused the proposed repair, it would be unsafe to 408 * proceed with directory entry fix, so bail out in that 409 * case. 410 */ 411 if (ret == FSERROR) { 412 return (FSERROR); 413 } 414 /* 415 * The maximum file size on FAT32 is 4GiB - 1, which 416 * will occupy a cluster chain of exactly 4GiB in 417 * size. On 32-bit platforms, since size_t is 32-bit, 418 * it would wrap back to 0. 419 */ 420 physicalSize = (u_int64_t)chainsize * boot->ClusterSize; 421 } 422 if (physicalSize < dir->size) { 423 pwarn("size of %s is %u, should at most be %ju\n", 424 fullpath(dir), dir->size, (uintmax_t)physicalSize); 425 if (ask(1, "Truncate")) { 426 dir->size = physicalSize; 427 p[28] = (u_char)physicalSize; 428 p[29] = (u_char)(physicalSize >> 8); 429 p[30] = (u_char)(physicalSize >> 16); 430 p[31] = (u_char)(physicalSize >> 24); 431 return FSDIRMOD; 432 } else 433 return FSERROR; 434 } else if (physicalSize - dir->size >= boot->ClusterSize) { 435 pwarn("%s has too many clusters allocated\n", 436 fullpath(dir)); 437 if (ask(1, "Drop superfluous clusters")) { 438 cl_t cl; 439 u_int32_t sz, len; 440 441 for (cl = dir->head, len = sz = 0; 442 (sz += boot->ClusterSize) < dir->size; len++) 443 cl = fat_get_cl_next(fat, cl); 444 clearchain(fat, fat_get_cl_next(fat, cl)); 445 ret = fat_set_cl_next(fat, cl, CLUST_EOF); 446 return (FSFATMOD | ret); 447 } else 448 return FSERROR; 449 } 450 return FSOK; 451} 452 453static const u_char dot_name[11] = ". "; 454static const u_char dotdot_name[11] = ".. "; 455 456/* 457 * Basic sanity check if the subdirectory have good '.' and '..' entries, 458 * and they are directory entries. Further sanity checks are performed 459 * when we traverse into it. 460 */ 461static int 462check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir) 463{ 464 u_char *buf, *cp; 465 off_t off; 466 cl_t cl; 467 int retval = FSOK; 468 int fd; 469 struct bootblock *boot; 470 471 boot = fat_get_boot(fat); 472 fd = fat_get_fd(fat); 473 474 cl = dir->head; 475 if (dir->parent && !fat_is_valid_cl(fat, cl)) { 476 return FSERROR; 477 } 478 479 if (!(boot->flags & FAT32) && !dir->parent) { 480 off = boot->bpbResSectors + boot->bpbFATs * 481 boot->FATsecs; 482 } else { 483 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; 484 } 485 486 /* 487 * We only need to check the first two entries of the directory, 488 * which is found in the first sector of the directory entry, 489 * so read in only the first sector. 490 */ 491 buf = malloc(boot->bpbBytesPerSec); 492 if (buf == NULL) { 493 perr("No space for directory buffer (%u)", 494 boot->bpbBytesPerSec); 495 return FSFATAL; 496 } 497 498 off *= boot->bpbBytesPerSec; 499 if (lseek(fd, off, SEEK_SET) != off || 500 read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) { 501 perr("Unable to read directory"); 502 free(buf); 503 return FSFATAL; 504 } 505 506 /* 507 * Both `.' and `..' must be present and be the first two entries 508 * and be ATTR_DIRECTORY of a valid subdirectory. 509 */ 510 cp = buf; 511 if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 || 512 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) { 513 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name); 514 retval |= FSERROR; 515 } 516 cp += 32; 517 if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 || 518 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) { 519 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name); 520 retval |= FSERROR; 521 } 522 523 free(buf); 524 return retval; 525} 526 527/* 528 * Read a directory and 529 * - resolve long name records 530 * - enter file and directory records into the parent's list 531 * - push directories onto the todo-stack 532 */ 533static int 534readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir) 535{ 536 struct bootblock *boot; 537 struct dosDirEntry dirent, *d; 538 u_char *p, *vallfn, *invlfn, *empty; 539 off_t off; 540 int fd, i, j, k, iosize, entries; 541 bool is_legacyroot; 542 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; 543 char *t; 544 u_int lidx = 0; 545 int shortSum; 546 int mod = FSOK; 547 size_t dirclusters; 548#define THISMOD 0x8000 /* Only used within this routine */ 549 550 boot = fat_get_boot(fat); 551 fd = fat_get_fd(fat); 552 553 cl = dir->head; 554 if (dir->parent && (!fat_is_valid_cl(fat, cl))) { 555 /* 556 * Already handled somewhere else. 557 */ 558 return FSOK; 559 } 560 shortSum = -1; 561 vallfn = invlfn = empty = NULL; 562 563 /* 564 * If we are checking the legacy root (for FAT12/FAT16), 565 * we will operate on the whole directory; otherwise, we 566 * will operate on one cluster at a time, and also take 567 * this opportunity to examine the chain. 568 * 569 * Derive how many entries we are going to encounter from 570 * the I/O size. 571 */ 572 is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32)); 573 if (is_legacyroot) { 574 iosize = boot->bpbRootDirEnts * 32; 575 entries = boot->bpbRootDirEnts; 576 } else { 577 iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec; 578 entries = iosize / 32; 579 mod |= checkchain(fat, dir->head, &dirclusters); 580 } 581 582 do { 583 if (is_legacyroot) { 584 /* 585 * Special case for FAT12/FAT16 root -- read 586 * in the whole root directory. 587 */ 588 off = boot->bpbResSectors + boot->bpbFATs * 589 boot->FATsecs; 590 } else { 591 /* 592 * Otherwise, read in a cluster of the 593 * directory. 594 */ 595 off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; 596 } 597 598 off *= boot->bpbBytesPerSec; 599 if (lseek(fd, off, SEEK_SET) != off || 600 read(fd, buffer, iosize) != iosize) { 601 perr("Unable to read directory"); 602 return FSFATAL; 603 } 604 605 for (p = buffer, i = 0; i < entries; i++, p += 32) { 606 if (dir->fsckflags & DIREMPWARN) { 607 *p = SLOT_EMPTY; 608 continue; 609 } 610 611 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { 612 if (*p == SLOT_EMPTY) { 613 dir->fsckflags |= DIREMPTY; 614 empty = p; 615 empcl = cl; 616 } 617 continue; 618 } 619 620 if (dir->fsckflags & DIREMPTY) { 621 if (!(dir->fsckflags & DIREMPWARN)) { 622 pwarn("%s has entries after end of directory\n", 623 fullpath(dir)); 624 if (ask(1, "Extend")) { 625 u_char *q; 626 627 dir->fsckflags &= ~DIREMPTY; 628 if (delete(fat, 629 empcl, empty - buffer, 630 cl, p - buffer, 1) == FSFATAL) 631 return FSFATAL; 632 q = ((empcl == cl) ? empty : buffer); 633 assert(q != NULL); 634 for (; q < p; q += 32) 635 *q = SLOT_DELETED; 636 mod |= THISMOD|FSDIRMOD; 637 } else if (ask(0, "Truncate")) 638 dir->fsckflags |= DIREMPWARN; 639 } 640 if (dir->fsckflags & DIREMPWARN) { 641 *p = SLOT_DELETED; 642 mod |= THISMOD|FSDIRMOD; 643 continue; 644 } else if (dir->fsckflags & DIREMPTY) 645 mod |= FSERROR; 646 empty = NULL; 647 } 648 649 if (p[11] == ATTR_WIN95) { 650 if (*p & LRFIRST) { 651 if (shortSum != -1) { 652 if (!invlfn) { 653 invlfn = vallfn; 654 invcl = valcl; 655 } 656 } 657 memset(longName, 0, sizeof longName); 658 shortSum = p[13]; 659 vallfn = p; 660 valcl = cl; 661 } else if (shortSum != p[13] 662 || lidx != (*p & LRNOMASK)) { 663 if (!invlfn) { 664 invlfn = vallfn; 665 invcl = valcl; 666 } 667 if (!invlfn) { 668 invlfn = p; 669 invcl = cl; 670 } 671 vallfn = NULL; 672 } 673 lidx = *p & LRNOMASK; 674 if (lidx == 0) { 675 pwarn("invalid long name\n"); 676 if (!invlfn) { 677 invlfn = vallfn; 678 invcl = valcl; 679 } 680 vallfn = NULL; 681 continue; 682 } 683 t = longName + --lidx * 13; 684 for (k = 1; k < 11 && t < longName + 685 sizeof(longName); k += 2) { 686 if (!p[k] && !p[k + 1]) 687 break; 688 *t++ = p[k]; 689 /* 690 * Warn about those unusable chars in msdosfs here? XXX 691 */ 692 if (p[k + 1]) 693 t[-1] = '?'; 694 } 695 if (k >= 11) 696 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) { 697 if (!p[k] && !p[k + 1]) 698 break; 699 *t++ = p[k]; 700 if (p[k + 1]) 701 t[-1] = '?'; 702 } 703 if (k >= 26) 704 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) { 705 if (!p[k] && !p[k + 1]) 706 break; 707 *t++ = p[k]; 708 if (p[k + 1]) 709 t[-1] = '?'; 710 } 711 if (t >= longName + sizeof(longName)) { 712 pwarn("long filename too long\n"); 713 if (!invlfn) { 714 invlfn = vallfn; 715 invcl = valcl; 716 } 717 vallfn = NULL; 718 } 719 if (p[26] | (p[27] << 8)) { 720 pwarn("long filename record cluster start != 0\n"); 721 if (!invlfn) { 722 invlfn = vallfn; 723 invcl = cl; 724 } 725 vallfn = NULL; 726 } 727 continue; /* long records don't carry further 728 * information */ 729 } 730 731 /* 732 * This is a standard msdosfs directory entry. 733 */ 734 memset(&dirent, 0, sizeof dirent); 735 736 /* 737 * it's a short name record, but we need to know 738 * more, so get the flags first. 739 */ 740 dirent.flags = p[11]; 741 742 /* 743 * Translate from 850 to ISO here XXX 744 */ 745 for (j = 0; j < 8; j++) 746 dirent.name[j] = p[j]; 747 dirent.name[8] = '\0'; 748 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--) 749 dirent.name[k] = '\0'; 750 if (k < 0 || dirent.name[k] != '\0') 751 k++; 752 if (dirent.name[0] == SLOT_E5) 753 dirent.name[0] = 0xe5; 754 755 if (dirent.flags & ATTR_VOLUME) { 756 if (vallfn || invlfn) { 757 mod |= removede(fat, 758 invlfn ? invlfn : vallfn, p, 759 invlfn ? invcl : valcl, -1, 0, 760 fullpath(dir), 2); 761 vallfn = NULL; 762 invlfn = NULL; 763 } 764 continue; 765 } 766 767 if (p[8] != ' ') 768 dirent.name[k++] = '.'; 769 for (j = 0; j < 3; j++) 770 dirent.name[k++] = p[j+8]; 771 dirent.name[k] = '\0'; 772 for (k--; k >= 0 && dirent.name[k] == ' '; k--) 773 dirent.name[k] = '\0'; 774 775 if (vallfn && shortSum != calcShortSum(p)) { 776 if (!invlfn) { 777 invlfn = vallfn; 778 invcl = valcl; 779 } 780 vallfn = NULL; 781 } 782 dirent.head = p[26] | (p[27] << 8); 783 if (boot->ClustMask == CLUST32_MASK) 784 dirent.head |= (p[20] << 16) | (p[21] << 24); 785 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24); 786 if (vallfn) { 787 strlcpy(dirent.lname, longName, 788 sizeof(dirent.lname)); 789 longName[0] = '\0'; 790 shortSum = -1; 791 } 792 793 dirent.parent = dir; 794 dirent.next = dir->child; 795 796 if (invlfn) { 797 mod |= k = removede(fat, 798 invlfn, vallfn ? vallfn : p, 799 invcl, vallfn ? valcl : cl, cl, 800 fullpath(&dirent), 0); 801 if (mod & FSFATAL) 802 return FSFATAL; 803 if (vallfn 804 ? (valcl == cl && vallfn != buffer) 805 : p != buffer) 806 if (k & FSDIRMOD) 807 mod |= THISMOD; 808 } 809 810 vallfn = NULL; /* not used any longer */ 811 invlfn = NULL; 812 813 /* 814 * Check if the directory entry is sane. 815 * 816 * '.' and '..' are skipped, their sanity is 817 * checked somewhere else. 818 * 819 * For everything else, check if we have a new, 820 * valid cluster chain (beginning of a file or 821 * directory that was never previously claimed 822 * by another file) when it's a non-empty file 823 * or a directory. The sanity of the cluster 824 * chain is checked at a later time when we 825 * traverse into the directory, or examine the 826 * file's directory entry. 827 * 828 * The only possible fix is to delete the entry 829 * if it's a directory; for file, we have to 830 * truncate the size to 0. 831 */ 832 if (!(dirent.flags & ATTR_DIRECTORY) || 833 (strcmp(dirent.name, ".") != 0 && 834 strcmp(dirent.name, "..") != 0)) { 835 if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) && 836 ((!fat_is_valid_cl(fat, dirent.head) || 837 !fat_is_cl_head(fat, dirent.head)))) { 838 if (!fat_is_valid_cl(fat, dirent.head)) { 839 pwarn("%s starts with cluster out of range(%u)\n", 840 fullpath(&dirent), 841 dirent.head); 842 } else { 843 pwarn("%s doesn't start a new cluster chain\n", 844 fullpath(&dirent)); 845 } 846 847 if (dirent.flags & ATTR_DIRECTORY) { 848 if (ask(0, "Remove")) { 849 *p = SLOT_DELETED; 850 mod |= THISMOD|FSDIRMOD; 851 } else 852 mod |= FSERROR; 853 continue; 854 } else { 855 if (ask(1, "Truncate")) { 856 p[28] = p[29] = p[30] = p[31] = 0; 857 p[26] = p[27] = 0; 858 if (boot->ClustMask == CLUST32_MASK) 859 p[20] = p[21] = 0; 860 dirent.size = 0; 861 dirent.head = 0; 862 mod |= THISMOD|FSDIRMOD; 863 } else 864 mod |= FSERROR; 865 } 866 } 867 } 868 if (dirent.flags & ATTR_DIRECTORY) { 869 /* 870 * gather more info for directories 871 */ 872 struct dirTodoNode *n; 873 874 if (dirent.size) { 875 pwarn("Directory %s has size != 0\n", 876 fullpath(&dirent)); 877 if (ask(1, "Correct")) { 878 p[28] = p[29] = p[30] = p[31] = 0; 879 dirent.size = 0; 880 mod |= THISMOD|FSDIRMOD; 881 } else 882 mod |= FSERROR; 883 } 884 /* 885 * handle `.' and `..' specially 886 */ 887 if (strcmp(dirent.name, ".") == 0) { 888 if (dirent.head != dir->head) { 889 pwarn("`.' entry in %s has incorrect start cluster\n", 890 fullpath(dir)); 891 if (ask(1, "Correct")) { 892 dirent.head = dir->head; 893 p[26] = (u_char)dirent.head; 894 p[27] = (u_char)(dirent.head >> 8); 895 if (boot->ClustMask == CLUST32_MASK) { 896 p[20] = (u_char)(dirent.head >> 16); 897 p[21] = (u_char)(dirent.head >> 24); 898 } 899 mod |= THISMOD|FSDIRMOD; 900 } else 901 mod |= FSERROR; 902 } 903 continue; 904 } else if (strcmp(dirent.name, "..") == 0) { 905 if (dir->parent) { /* XXX */ 906 if (!dir->parent->parent) { 907 if (dirent.head) { 908 pwarn("`..' entry in %s has non-zero start cluster\n", 909 fullpath(dir)); 910 if (ask(1, "Correct")) { 911 dirent.head = 0; 912 p[26] = p[27] = 0; 913 if (boot->ClustMask == CLUST32_MASK) 914 p[20] = p[21] = 0; 915 mod |= THISMOD|FSDIRMOD; 916 } else 917 mod |= FSERROR; 918 } 919 } else if (dirent.head != dir->parent->head) { 920 pwarn("`..' entry in %s has incorrect start cluster\n", 921 fullpath(dir)); 922 if (ask(1, "Correct")) { 923 dirent.head = dir->parent->head; 924 p[26] = (u_char)dirent.head; 925 p[27] = (u_char)(dirent.head >> 8); 926 if (boot->ClustMask == CLUST32_MASK) { 927 p[20] = (u_char)(dirent.head >> 16); 928 p[21] = (u_char)(dirent.head >> 24); 929 } 930 mod |= THISMOD|FSDIRMOD; 931 } else 932 mod |= FSERROR; 933 } 934 } 935 continue; 936 } else { 937 /* 938 * Only one directory entry can point 939 * to dir->head, it's '.'. 940 */ 941 if (dirent.head == dir->head) { 942 pwarn("%s entry in %s has incorrect start cluster\n", 943 dirent.name, fullpath(dir)); 944 if (ask(1, "Remove")) { 945 *p = SLOT_DELETED; 946 mod |= THISMOD|FSDIRMOD; 947 } else 948 mod |= FSERROR; 949 continue; 950 } else if ((check_subdirectory(fat, 951 &dirent) & FSERROR) == FSERROR) { 952 /* 953 * A subdirectory should have 954 * a dot (.) entry and a dot-dot 955 * (..) entry of ATTR_DIRECTORY, 956 * we will inspect further when 957 * traversing into it. 958 */ 959 if (ask(1, "Remove")) { 960 *p = SLOT_DELETED; 961 mod |= THISMOD|FSDIRMOD; 962 } else 963 mod |= FSERROR; 964 continue; 965 } 966 } 967 968 /* create directory tree node */ 969 if (!(d = newDosDirEntry())) { 970 perr("No space for directory"); 971 return FSFATAL; 972 } 973 memcpy(d, &dirent, sizeof(struct dosDirEntry)); 974 /* link it into the tree */ 975 dir->child = d; 976 977 /* Enter this directory into the todo list */ 978 if (!(n = newDirTodo())) { 979 perr("No space for todo list"); 980 return FSFATAL; 981 } 982 n->next = pendingDirectories; 983 n->dir = d; 984 pendingDirectories = n; 985 } else { 986 mod |= k = checksize(fat, p, &dirent); 987 if (k & FSDIRMOD) 988 mod |= THISMOD; 989 } 990 boot->NumFiles++; 991 } 992 993 if (is_legacyroot) { 994 /* 995 * Don't bother to write back right now because 996 * we may continue to make modification to the 997 * non-FAT32 root directory below. 998 */ 999 break; 1000 } else if (mod & THISMOD) { 1001 if (lseek(fd, off, SEEK_SET) != off 1002 || write(fd, buffer, iosize) != iosize) { 1003 perr("Unable to write directory"); 1004 return FSFATAL; 1005 } 1006 mod &= ~THISMOD; 1007 } 1008 } while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl)))); 1009 if (invlfn || vallfn) 1010 mod |= removede(fat, 1011 invlfn ? invlfn : vallfn, p, 1012 invlfn ? invcl : valcl, -1, 0, 1013 fullpath(dir), 1); 1014 1015 /* 1016 * The root directory of non-FAT32 filesystems is in a special 1017 * area and may have been modified above removede() without 1018 * being written out. 1019 */ 1020 if ((mod & FSDIRMOD) && is_legacyroot) { 1021 if (lseek(fd, off, SEEK_SET) != off 1022 || write(fd, buffer, iosize) != iosize) { 1023 perr("Unable to write directory"); 1024 return FSFATAL; 1025 } 1026 mod &= ~THISMOD; 1027 } 1028 return mod & ~THISMOD; 1029} 1030 1031int 1032handleDirTree(struct fat_descriptor *fat) 1033{ 1034 int mod; 1035 1036 mod = readDosDirSection(fat, rootDir); 1037 if (mod & FSFATAL) 1038 return FSFATAL; 1039 1040 /* 1041 * process the directory todo list 1042 */ 1043 while (pendingDirectories) { 1044 struct dosDirEntry *dir = pendingDirectories->dir; 1045 struct dirTodoNode *n = pendingDirectories->next; 1046 1047 /* 1048 * remove TODO entry now, the list might change during 1049 * directory reads 1050 */ 1051 freeDirTodo(pendingDirectories); 1052 pendingDirectories = n; 1053 1054 /* 1055 * handle subdirectory 1056 */ 1057 mod |= readDosDirSection(fat, dir); 1058 if (mod & FSFATAL) 1059 return FSFATAL; 1060 } 1061 1062 return mod; 1063} 1064 1065/* 1066 * Try to reconnect a FAT chain into dir 1067 */ 1068static u_char *lfbuf; 1069static cl_t lfcl; 1070static off_t lfoff; 1071 1072int 1073reconnect(struct fat_descriptor *fat, cl_t head, size_t length) 1074{ 1075 struct bootblock *boot = fat_get_boot(fat); 1076 struct dosDirEntry d; 1077 int len, dosfs; 1078 u_char *p; 1079 1080 dosfs = fat_get_fd(fat); 1081 1082 if (!ask(1, "Reconnect")) 1083 return FSERROR; 1084 1085 if (!lostDir) { 1086 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) { 1087 if (!strcmp(lostDir->name, LOSTDIR)) 1088 break; 1089 } 1090 if (!lostDir) { /* Create LOSTDIR? XXX */ 1091 pwarn("No %s directory\n", LOSTDIR); 1092 return FSERROR; 1093 } 1094 } 1095 if (!lfbuf) { 1096 lfbuf = malloc(boot->ClusterSize); 1097 if (!lfbuf) { 1098 perr("No space for buffer"); 1099 return FSFATAL; 1100 } 1101 p = NULL; 1102 } else 1103 p = lfbuf; 1104 while (1) { 1105 if (p) 1106 for (; p < lfbuf + boot->ClusterSize; p += 32) 1107 if (*p == SLOT_EMPTY 1108 || *p == SLOT_DELETED) 1109 break; 1110 if (p && p < lfbuf + boot->ClusterSize) 1111 break; 1112 lfcl = p ? fat_get_cl_next(fat, lfcl) : lostDir->head; 1113 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) { 1114 /* Extend LOSTDIR? XXX */ 1115 pwarn("No space in %s\n", LOSTDIR); 1116 lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0; 1117 return FSERROR; 1118 } 1119 lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize 1120 + boot->FirstCluster * boot->bpbBytesPerSec; 1121 1122 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 1123 || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 1124 perr("could not read LOST.DIR"); 1125 return FSFATAL; 1126 } 1127 p = lfbuf; 1128 } 1129 1130 boot->NumFiles++; 1131 /* Ensure uniqueness of entry here! XXX */ 1132 memset(&d, 0, sizeof d); 1133 /* worst case -1 = 4294967295, 10 digits */ 1134 len = snprintf(d.name, sizeof(d.name), "%u", head); 1135 d.flags = 0; 1136 d.head = head; 1137 d.size = length * boot->ClusterSize; 1138 1139 memcpy(p, d.name, len); 1140 memset(p + len, ' ', 11 - len); 1141 memset(p + 11, 0, 32 - 11); 1142 p[26] = (u_char)d.head; 1143 p[27] = (u_char)(d.head >> 8); 1144 if (boot->ClustMask == CLUST32_MASK) { 1145 p[20] = (u_char)(d.head >> 16); 1146 p[21] = (u_char)(d.head >> 24); 1147 } 1148 p[28] = (u_char)d.size; 1149 p[29] = (u_char)(d.size >> 8); 1150 p[30] = (u_char)(d.size >> 16); 1151 p[31] = (u_char)(d.size >> 24); 1152 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 1153 || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 1154 perr("could not write LOST.DIR"); 1155 return FSFATAL; 1156 } 1157 return FSDIRMOD; 1158} 1159 1160void 1161finishlf(void) 1162{ 1163 if (lfbuf) 1164 free(lfbuf); 1165 lfbuf = NULL; 1166} 1167