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