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