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