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