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