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 * boot->bpbSecPerClust + boot->ClusterOffset; 317 off *= boot->bpbBytesPerSec; 318 if (lseek(f, off, SEEK_SET) != off) { 319 perr("Unable to lseek to %" PRId64, off); 320 return FSFATAL; 321 } 322 if (read(f, delbuf, clsz) != clsz) { 323 perr("Unable to read directory"); 324 return FSFATAL; 325 } 326 while (s < e) { 327 *s = SLOT_DELETED; 328 s += 32; 329 } 330 if (lseek(f, off, SEEK_SET) != off) { 331 perr("Unable to lseek to %" PRId64, off); 332 return FSFATAL; 333 } 334 if (write(f, delbuf, clsz) != clsz) { 335 perr("Unable to write directory"); 336 return FSFATAL; 337 } 338 if (startcl == endcl) 339 break; 340 startcl = fat[startcl].next; 341 s = delbuf; 342 } 343 return FSOK; 344 } 345 346 static int 347 removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start, 348 u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type) 349 { 350 switch (type) { 351 case 0: 352 pwarn("Invalid long filename entry for %s\n", path); 353 break; 354 case 1: 355 pwarn("Invalid long filename entry at end of directory %s\n", 356 path); 357 break; 358 case 2: 359 pwarn("Invalid long filename entry for volume label\n"); 360 break; 361 } 362 if (ask(0, "Remove")) { 363 if (startcl != curcl) { 364 if (delete(f, boot, fat, 365 startcl, start - buffer, 366 endcl, end - buffer, 367 endcl == curcl) == FSFATAL) 368 return FSFATAL; 369 start = buffer; 370 } 371 /* startcl is < CLUST_FIRST for !fat32 root */ 372 if ((endcl == curcl) || (startcl < CLUST_FIRST)) 373 for (; start < end; start += 32) 374 *start = SLOT_DELETED; 375 return FSDIRMOD; 376 } 377 return FSERROR; 378 } 379 380 /* 381 * Check an in-memory file entry 382 */ 383 static int 384 checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p, 385 struct dosDirEntry *dir) 386 { 387 /* 388 * Check size on ordinary files 389 */ 390 uint32_t physicalSize; 391 392 if (dir->head == CLUST_FREE) 393 physicalSize = 0; 394 else { 395 if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters) 396 return FSERROR; 397 physicalSize = fat[dir->head].length * boot->ClusterSize; 398 } 399 if (physicalSize < dir->size) { 400 pwarn("size of %s is %u, should at most be %u\n", 401 fullpath(dir), dir->size, physicalSize); 402 if (ask(1, "Truncate")) { 403 dir->size = physicalSize; 404 p[28] = (u_char)physicalSize; 405 p[29] = (u_char)(physicalSize >> 8); 406 p[30] = (u_char)(physicalSize >> 16); 407 p[31] = (u_char)(physicalSize >> 24); 408 return FSDIRMOD; 409 } else 410 return FSERROR; 411 } else if (physicalSize - dir->size >= boot->ClusterSize) { 412 pwarn("%s has too many clusters allocated\n", 413 fullpath(dir)); 414 if (ask(1, "Drop superfluous clusters")) { 415 cl_t cl; 416 uint32_t sz, len; 417 418 for (cl = dir->head, len = sz = 0; 419 (sz += boot->ClusterSize) < dir->size; len++) 420 cl = fat[cl].next; 421 clearchain(boot, fat, fat[cl].next); 422 fat[cl].next = CLUST_EOF; 423 fat[dir->head].length = len; 424 return FSFATMOD; 425 } else 426 return FSERROR; 427 } 428 return FSOK; 429 } 430 431 static const u_char dot_name[11] = ". "; 432 static const u_char dotdot_name[11] = ".. "; 433 434 /* 435 * Basic sanity check if the subdirectory have good '.' and '..' entries, 436 * and they are directory entries. Further sanity checks are performed 437 * when we traverse into it. 438 */ 439 static int 440 check_subdirectory(int f, struct bootblock *boot, struct dosDirEntry *dir) 441 { 442 u_char *buf, *cp; 443 off_t off; 444 cl_t cl; 445 int retval = FSOK; 446 447 cl = dir->head; 448 if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { 449 return FSERROR; 450 } 451 452 if (!(boot->flags & FAT32) && !dir->parent) { 453 off = boot->bpbResSectors + boot->bpbFATs * 454 boot->FATsecs; 455 } else { 456 off = cl * boot->bpbSecPerClust + boot->ClusterOffset; 457 } 458 459 /* 460 * We only need to check the first two entries of the directory, 461 * which is found in the first sector of the directory entry, 462 * so read in only the first sector. 463 */ 464 buf = malloc(boot->bpbBytesPerSec); 465 if (buf == NULL) { 466 perr("No space for directory buffer (%u)", 467 boot->bpbBytesPerSec); 468 return FSFATAL; 469 } 470 471 off *= boot->bpbBytesPerSec; 472 if (lseek(f, off, SEEK_SET) != off || 473 read(f, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) { 474 perr("Unable to read directory"); 475 free(buf); 476 return FSFATAL; 477 } 478 479 /* 480 * Both `.' and `..' must be present and be the first two entries 481 * and be ATTR_DIRECTORY of a valid subdirectory. 482 */ 483 cp = buf; 484 if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 || 485 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) { 486 pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name); 487 retval |= FSERROR; 488 } 489 cp += 32; 490 if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 || 491 (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) { 492 pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name); 493 retval |= FSERROR; 494 } 495 496 free(buf); 497 return retval; 498 } 499 500 /* 501 * Read a directory and 502 * - resolve long name records 503 * - enter file and directory records into the parent's list 504 * - push directories onto the todo-stack 505 */ 506 static int 507 readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat, 508 struct dosDirEntry *dir) 509 { 510 struct dosDirEntry dirent, *d; 511 u_char *p, *vallfn, *invlfn, *empty; 512 off_t off; 513 int i, j, k, last; 514 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; 515 char *t; 516 u_int lidx = 0; 517 int shortSum; 518 int mod = FSOK; 519 #define THISMOD 0x8000 /* Only used within this routine */ 520 521 cl = dir->head; 522 if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { 523 /* 524 * Already handled somewhere else. 525 */ 526 return FSOK; 527 } 528 shortSum = -1; 529 vallfn = invlfn = empty = NULL; 530 do { 531 if (!(boot->flags & FAT32) && !dir->parent) { 532 last = boot->bpbRootDirEnts * 32; 533 off = boot->bpbResSectors + boot->bpbFATs * 534 boot->FATsecs; 535 } else { 536 last = boot->bpbSecPerClust * boot->bpbBytesPerSec; 537 off = cl * boot->bpbSecPerClust + boot->ClusterOffset; 538 } 539 540 off *= boot->bpbBytesPerSec; 541 if (lseek(f, off, SEEK_SET) != off 542 || read(f, buffer, last) != last) { 543 perr("Unable to read directory"); 544 return FSFATAL; 545 } 546 last /= 32; 547 for (p = buffer, i = 0; i < last; i++, p += 32) { 548 if (dir->fsckflags & DIREMPWARN) { 549 *p = SLOT_EMPTY; 550 continue; 551 } 552 553 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { 554 if (*p == SLOT_EMPTY) { 555 dir->fsckflags |= DIREMPTY; 556 empty = p; 557 empcl = cl; 558 } 559 continue; 560 } 561 562 if (dir->fsckflags & DIREMPTY) { 563 if (!(dir->fsckflags & DIREMPWARN)) { 564 pwarn("%s has entries after end of directory\n", 565 fullpath(dir)); 566 if (ask(1, "Extend")) { 567 u_char *q; 568 569 dir->fsckflags &= ~DIREMPTY; 570 if (delete(f, boot, fat, 571 empcl, empty - buffer, 572 cl, p - buffer, 1) == FSFATAL) 573 return FSFATAL; 574 q = ((empcl == cl) ? empty : buffer); 575 assert(q != NULL); 576 for (; q < p; q += 32) 577 *q = SLOT_DELETED; 578 mod |= THISMOD|FSDIRMOD; 579 } else if (ask(0, "Truncate")) 580 dir->fsckflags |= DIREMPWARN; 581 } 582 if (dir->fsckflags & DIREMPWARN) { 583 *p = SLOT_DELETED; 584 mod |= THISMOD|FSDIRMOD; 585 continue; 586 } else if (dir->fsckflags & DIREMPTY) 587 mod |= FSERROR; 588 empty = NULL; 589 } 590 591 if (p[11] == ATTR_WIN95) { 592 if (*p & LRFIRST) { 593 if (shortSum != -1) { 594 if (!invlfn) { 595 invlfn = vallfn; 596 invcl = valcl; 597 } 598 } 599 memset(longName, 0, sizeof longName); 600 shortSum = p[13]; 601 vallfn = p; 602 valcl = cl; 603 } else if (shortSum != p[13] 604 || lidx != (*p & LRNOMASK)) { 605 if (!invlfn) { 606 invlfn = vallfn; 607 invcl = valcl; 608 } 609 if (!invlfn) { 610 invlfn = p; 611 invcl = cl; 612 } 613 vallfn = NULL; 614 } 615 lidx = *p & LRNOMASK; 616 if (lidx == 0) { 617 pwarn("invalid long name\n"); 618 if (!invlfn) { 619 invlfn = vallfn; 620 invcl = valcl; 621 } 622 vallfn = NULL; 623 continue; 624 } 625 t = longName + --lidx * 13; 626 for (k = 1; k < 11 && t < longName + 627 sizeof(longName); k += 2) { 628 if (!p[k] && !p[k + 1]) 629 break; 630 *t++ = p[k]; 631 /* 632 * Warn about those unusable chars in msdosfs here? XXX 633 */ 634 if (p[k + 1]) 635 t[-1] = '?'; 636 } 637 if (k >= 11) 638 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) { 639 if (!p[k] && !p[k + 1]) 640 break; 641 *t++ = p[k]; 642 if (p[k + 1]) 643 t[-1] = '?'; 644 } 645 if (k >= 26) 646 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) { 647 if (!p[k] && !p[k + 1]) 648 break; 649 *t++ = p[k]; 650 if (p[k + 1]) 651 t[-1] = '?'; 652 } 653 if (t >= longName + sizeof(longName)) { 654 pwarn("long filename too long\n"); 655 if (!invlfn) { 656 invlfn = vallfn; 657 invcl = valcl; 658 } 659 vallfn = NULL; 660 } 661 if (p[26] | (p[27] << 8)) { 662 pwarn("long filename record cluster start != 0\n"); 663 if (!invlfn) { 664 invlfn = vallfn; 665 invcl = cl; 666 } 667 vallfn = NULL; 668 } 669 continue; /* long records don't carry further 670 * information */ 671 } 672 673 /* 674 * This is a standard msdosfs directory entry. 675 */ 676 memset(&dirent, 0, sizeof dirent); 677 678 /* 679 * it's a short name record, but we need to know 680 * more, so get the flags first. 681 */ 682 dirent.flags = p[11]; 683 684 /* 685 * Translate from 850 to ISO here XXX 686 */ 687 for (j = 0; j < 8; j++) 688 dirent.name[j] = p[j]; 689 dirent.name[8] = '\0'; 690 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--) 691 dirent.name[k] = '\0'; 692 if (k < 0 || dirent.name[k] != '\0') 693 k++; 694 if (dirent.name[0] == SLOT_E5) 695 dirent.name[0] = 0xe5; 696 697 if (dirent.flags & ATTR_VOLUME) { 698 if (vallfn || invlfn) { 699 mod |= removede(f, boot, fat, 700 invlfn ? invlfn : vallfn, p, 701 invlfn ? invcl : valcl, -1, 0, 702 fullpath(dir), 2); 703 vallfn = NULL; 704 invlfn = NULL; 705 } 706 continue; 707 } 708 709 if (p[8] != ' ') 710 dirent.name[k++] = '.'; 711 for (j = 0; j < 3; j++) 712 dirent.name[k++] = p[j+8]; 713 dirent.name[k] = '\0'; 714 for (k--; k >= 0 && dirent.name[k] == ' '; k--) 715 dirent.name[k] = '\0'; 716 717 if (vallfn && shortSum != calcShortSum(p)) { 718 if (!invlfn) { 719 invlfn = vallfn; 720 invcl = valcl; 721 } 722 vallfn = NULL; 723 } 724 dirent.head = p[26] | (p[27] << 8); 725 if (boot->ClustMask == CLUST32_MASK) 726 dirent.head |= (p[20] << 16) | (p[21] << 24); 727 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24); 728 if (vallfn) { 729 strlcpy(dirent.lname, longName, 730 sizeof(dirent.lname)); 731 longName[0] = '\0'; 732 shortSum = -1; 733 } 734 735 dirent.parent = dir; 736 dirent.next = dir->child; 737 738 if (invlfn) { 739 mod |= k = removede(f, boot, fat, 740 invlfn, vallfn ? vallfn : p, 741 invcl, vallfn ? valcl : cl, cl, 742 fullpath(&dirent), 0); 743 if (mod & FSFATAL) 744 return FSFATAL; 745 if (vallfn 746 ? (valcl == cl && vallfn != buffer) 747 : p != buffer) 748 if (k & FSDIRMOD) 749 mod |= THISMOD; 750 } 751 752 vallfn = NULL; /* not used any longer */ 753 invlfn = NULL; 754 755 if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) { 756 if (dirent.head != 0) { 757 pwarn("%s has clusters, but size 0\n", 758 fullpath(&dirent)); 759 if (ask(1, "Drop allocated clusters")) { 760 p[26] = p[27] = 0; 761 if (boot->ClustMask == CLUST32_MASK) 762 p[20] = p[21] = 0; 763 clearchain(boot, fat, dirent.head); 764 dirent.head = 0; 765 mod |= THISMOD|FSDIRMOD|FSFATMOD; 766 } else 767 mod |= FSERROR; 768 } 769 } else if (dirent.head == 0 770 && !strcmp(dirent.name, "..") 771 && dir->parent /* XXX */ 772 && !dir->parent->parent) { 773 /* 774 * Do nothing, the parent is the root 775 */ 776 } else if (dirent.head < CLUST_FIRST 777 || dirent.head >= boot->NumClusters 778 || fat[dirent.head].next == CLUST_FREE 779 || (fat[dirent.head].next >= CLUST_RSRVD 780 && fat[dirent.head].next < CLUST_EOFS) 781 || fat[dirent.head].head != dirent.head) { 782 if (dirent.head == 0) 783 pwarn("%s has no clusters\n", 784 fullpath(&dirent)); 785 else if (dirent.head < CLUST_FIRST 786 || dirent.head >= boot->NumClusters) 787 pwarn("%s starts with cluster out of range(%u)\n", 788 fullpath(&dirent), 789 dirent.head); 790 else if (fat[dirent.head].next == CLUST_FREE) 791 pwarn("%s starts with free cluster\n", 792 fullpath(&dirent)); 793 else if (fat[dirent.head].next >= CLUST_RSRVD) 794 pwarn("%s starts with cluster marked %s\n", 795 fullpath(&dirent), 796 rsrvdcltype(fat[dirent.head].next)); 797 else 798 pwarn("%s doesn't start a cluster chain\n", 799 fullpath(&dirent)); 800 if (dirent.flags & ATTR_DIRECTORY) { 801 if (ask(0, "Remove")) { 802 *p = SLOT_DELETED; 803 mod |= THISMOD|FSDIRMOD; 804 } else 805 mod |= FSERROR; 806 continue; 807 } else { 808 if (ask(1, "Truncate")) { 809 p[28] = p[29] = p[30] = p[31] = 0; 810 p[26] = p[27] = 0; 811 if (boot->ClustMask == CLUST32_MASK) 812 p[20] = p[21] = 0; 813 dirent.size = 0; 814 mod |= THISMOD|FSDIRMOD; 815 } else 816 mod |= FSERROR; 817 } 818 } 819 820 if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters) 821 fat[dirent.head].flags |= FAT_USED; 822 823 if (dirent.flags & ATTR_DIRECTORY) { 824 /* 825 * gather more info for directories 826 */ 827 struct dirTodoNode *n; 828 829 if (dirent.size) { 830 pwarn("Directory %s has size != 0\n", 831 fullpath(&dirent)); 832 if (ask(1, "Correct")) { 833 p[28] = p[29] = p[30] = p[31] = 0; 834 dirent.size = 0; 835 mod |= THISMOD|FSDIRMOD; 836 } else 837 mod |= FSERROR; 838 } 839 /* 840 * handle `.' and `..' specially 841 */ 842 if (strcmp(dirent.name, ".") == 0) { 843 if (dirent.head != dir->head) { 844 pwarn("`.' entry in %s has incorrect start cluster\n", 845 fullpath(dir)); 846 if (ask(1, "Correct")) { 847 dirent.head = dir->head; 848 p[26] = (u_char)dirent.head; 849 p[27] = (u_char)(dirent.head >> 8); 850 if (boot->ClustMask == CLUST32_MASK) { 851 p[20] = (u_char)(dirent.head >> 16); 852 p[21] = (u_char)(dirent.head >> 24); 853 } 854 mod |= THISMOD|FSDIRMOD; 855 } else 856 mod |= FSERROR; 857 } 858 continue; 859 } 860 if (strcmp(dirent.name, "..") == 0) { 861 if (dir->parent) { /* XXX */ 862 if (!dir->parent->parent) { 863 if (dirent.head) { 864 pwarn("`..' entry in %s has non-zero start cluster\n", 865 fullpath(dir)); 866 if (ask(1, "Correct")) { 867 dirent.head = 0; 868 p[26] = p[27] = 0; 869 if (boot->ClustMask == CLUST32_MASK) 870 p[20] = p[21] = 0; 871 mod |= THISMOD|FSDIRMOD; 872 } else 873 mod |= FSERROR; 874 } 875 } else if (dirent.head != dir->parent->head) { 876 pwarn("`..' entry in %s has incorrect start cluster\n", 877 fullpath(dir)); 878 if (ask(1, "Correct")) { 879 dirent.head = dir->parent->head; 880 p[26] = (u_char)dirent.head; 881 p[27] = (u_char)(dirent.head >> 8); 882 if (boot->ClustMask == CLUST32_MASK) { 883 p[20] = (u_char)(dirent.head >> 16); 884 p[21] = (u_char)(dirent.head >> 24); 885 } 886 mod |= THISMOD|FSDIRMOD; 887 } else 888 mod |= FSERROR; 889 } 890 } 891 continue; 892 } else { 893 /* 894 * Only one directory entry can point 895 * to dir->head, it's '.'. 896 */ 897 if (dirent.head == dir->head) { 898 pwarn("%s entry in %s has incorrect start cluster\n", 899 dirent.name, fullpath(dir)); 900 if (ask(1, "Remove")) { 901 *p = SLOT_DELETED; 902 mod |= THISMOD|FSDIRMOD; 903 } else 904 mod |= FSERROR; 905 continue; 906 } else if ((check_subdirectory(f, boot, 907 &dirent) & FSERROR) == FSERROR) { 908 /* 909 * A subdirectory should have 910 * a dot (.) entry and a dot-dot 911 * (..) entry of ATTR_DIRECTORY, 912 * we will inspect further when 913 * traversing into it. 914 */ 915 if (ask(1, "Remove")) { 916 *p = SLOT_DELETED; 917 mod |= THISMOD|FSDIRMOD; 918 } else 919 mod |= FSERROR; 920 continue; 921 } 922 } 923 924 /* create directory tree node */ 925 if (!(d = newDosDirEntry())) { 926 perr("No space for directory"); 927 return FSFATAL; 928 } 929 memcpy(d, &dirent, sizeof(struct dosDirEntry)); 930 /* link it into the tree */ 931 dir->child = d; 932 933 /* Enter this directory into the todo list */ 934 if (!(n = newDirTodo())) { 935 perr("No space for todo list"); 936 return FSFATAL; 937 } 938 n->next = pendingDirectories; 939 n->dir = d; 940 pendingDirectories = n; 941 } else { 942 mod |= k = checksize(boot, fat, p, &dirent); 943 if (k & FSDIRMOD) 944 mod |= THISMOD; 945 } 946 boot->NumFiles++; 947 } 948 949 if (!(boot->flags & FAT32) && !dir->parent) 950 break; 951 952 if (mod & THISMOD) { 953 last *= 32; 954 if (lseek(f, off, SEEK_SET) != off 955 || write(f, buffer, last) != last) { 956 perr("Unable to write directory"); 957 return FSFATAL; 958 } 959 mod &= ~THISMOD; 960 } 961 } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters); 962 if (invlfn || vallfn) 963 mod |= removede(f, boot, fat, 964 invlfn ? invlfn : vallfn, p, 965 invlfn ? invcl : valcl, -1, 0, 966 fullpath(dir), 1); 967 968 /* The root directory of non fat32 filesystems is in a special 969 * area and may have been modified above without being written out. 970 */ 971 if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) { 972 last *= 32; 973 if (lseek(f, off, SEEK_SET) != off 974 || write(f, buffer, last) != last) { 975 perr("Unable to write directory"); 976 return FSFATAL; 977 } 978 mod &= ~THISMOD; 979 } 980 return mod & ~THISMOD; 981 } 982 983 int 984 handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat) 985 { 986 int mod; 987 988 mod = readDosDirSection(dosfs, boot, fat, rootDir); 989 if (mod & FSFATAL) 990 return FSFATAL; 991 992 /* 993 * process the directory todo list 994 */ 995 while (pendingDirectories) { 996 struct dosDirEntry *dir = pendingDirectories->dir; 997 struct dirTodoNode *n = pendingDirectories->next; 998 999 /* 1000 * remove TODO entry now, the list might change during 1001 * directory reads 1002 */ 1003 freeDirTodo(pendingDirectories); 1004 pendingDirectories = n; 1005 1006 /* 1007 * handle subdirectory 1008 */ 1009 mod |= readDosDirSection(dosfs, boot, fat, dir); 1010 if (mod & FSFATAL) 1011 return FSFATAL; 1012 } 1013 1014 return mod; 1015 } 1016 1017 /* 1018 * Try to reconnect a FAT chain into dir 1019 */ 1020 static u_char *lfbuf; 1021 static cl_t lfcl; 1022 static off_t lfoff; 1023 1024 int 1025 reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head) 1026 { 1027 struct dosDirEntry d; 1028 int len; 1029 u_char *p; 1030 1031 if (!ask(1, "Reconnect")) 1032 return FSERROR; 1033 1034 if (!lostDir) { 1035 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) { 1036 if (!strcmp(lostDir->name, LOSTDIR)) 1037 break; 1038 } 1039 if (!lostDir) { /* Create LOSTDIR? XXX */ 1040 pwarn("No %s directory\n", LOSTDIR); 1041 return FSERROR; 1042 } 1043 } 1044 if (!lfbuf) { 1045 lfbuf = malloc(boot->ClusterSize); 1046 if (!lfbuf) { 1047 perr("No space for buffer"); 1048 return FSFATAL; 1049 } 1050 p = NULL; 1051 } else 1052 p = lfbuf; 1053 while (1) { 1054 if (p) 1055 for (; p < lfbuf + boot->ClusterSize; p += 32) 1056 if (*p == SLOT_EMPTY 1057 || *p == SLOT_DELETED) 1058 break; 1059 if (p && p < lfbuf + boot->ClusterSize) 1060 break; 1061 lfcl = p ? fat[lfcl].next : lostDir->head; 1062 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) { 1063 /* Extend LOSTDIR? XXX */ 1064 pwarn("No space in %s\n", LOSTDIR); 1065 lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0; 1066 return FSERROR; 1067 } 1068 lfoff = lfcl * boot->ClusterSize 1069 + boot->ClusterOffset * boot->bpbBytesPerSec; 1070 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 1071 || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 1072 perr("could not read LOST.DIR"); 1073 return FSFATAL; 1074 } 1075 p = lfbuf; 1076 } 1077 1078 boot->NumFiles++; 1079 /* Ensure uniqueness of entry here! XXX */ 1080 memset(&d, 0, sizeof d); 1081 /* worst case -1 = 4294967295, 10 digits */ 1082 len = snprintf(d.name, sizeof(d.name), "%u", head); 1083 d.flags = 0; 1084 d.head = head; 1085 d.size = fat[head].length * boot->ClusterSize; 1086 1087 memcpy(p, d.name, len); 1088 memset(p + len, ' ', 11 - len); 1089 memset(p + 11, 0, 32 - 11); 1090 p[26] = (u_char)d.head; 1091 p[27] = (u_char)(d.head >> 8); 1092 if (boot->ClustMask == CLUST32_MASK) { 1093 p[20] = (u_char)(d.head >> 16); 1094 p[21] = (u_char)(d.head >> 24); 1095 } 1096 p[28] = (u_char)d.size; 1097 p[29] = (u_char)(d.size >> 8); 1098 p[30] = (u_char)(d.size >> 16); 1099 p[31] = (u_char)(d.size >> 24); 1100 fat[head].flags |= FAT_USED; 1101 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 1102 || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 1103 perr("could not write LOST.DIR"); 1104 return FSFATAL; 1105 } 1106 return FSDIRMOD; 1107 } 1108 1109 void 1110 finishlf(void) 1111 { 1112 if (lfbuf) 1113 free(lfbuf); 1114 lfbuf = NULL; 1115 } 1116