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