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