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