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 static 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, len; 423 424 for (cl = dir->head, len = sz = 0; 425 (sz += boot->ClusterSize) < dir->size; len++) 426 cl = fat[cl].next; 427 clearchain(boot, fat, fat[cl].next); 428 fat[cl].next = CLUST_EOF; 429 fat[dir->head].length = len; 430 return FSFATMOD; 431 } else 432 return FSERROR; 433 } 434 return FSOK; 435 } 436 437 /* 438 * Read a directory and 439 * - resolve long name records 440 * - enter file and directory records into the parent's list 441 * - push directories onto the todo-stack 442 */ 443 static int 444 readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat, 445 struct dosDirEntry *dir) 446 { 447 struct dosDirEntry dirent, *d; 448 u_char *p, *vallfn, *invlfn, *empty; 449 off_t off; 450 int i, j, k, last; 451 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; 452 char *t; 453 u_int lidx = 0; 454 int shortSum; 455 int mod = FSOK; 456 #define THISMOD 0x8000 /* Only used within this routine */ 457 458 cl = dir->head; 459 if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { 460 /* 461 * Already handled somewhere else. 462 */ 463 return FSOK; 464 } 465 shortSum = -1; 466 vallfn = invlfn = empty = NULL; 467 do { 468 if (!(boot->flags & FAT32) && !dir->parent) { 469 last = boot->bpbRootDirEnts * 32; 470 off = boot->bpbResSectors + boot->bpbFATs * 471 boot->FATsecs; 472 } else { 473 last = boot->bpbSecPerClust * boot->bpbBytesPerSec; 474 off = cl * boot->bpbSecPerClust + boot->ClusterOffset; 475 } 476 477 off *= boot->bpbBytesPerSec; 478 if (lseek(f, off, SEEK_SET) != off 479 || read(f, buffer, last) != last) { 480 perr("Unable to read directory"); 481 return FSFATAL; 482 } 483 last /= 32; 484 /* 485 * Check `.' and `..' entries here? XXX 486 */ 487 for (p = buffer, i = 0; i < last; i++, p += 32) { 488 if (dir->fsckflags & DIREMPWARN) { 489 *p = SLOT_EMPTY; 490 continue; 491 } 492 493 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { 494 if (*p == SLOT_EMPTY) { 495 dir->fsckflags |= DIREMPTY; 496 empty = p; 497 empcl = cl; 498 } 499 continue; 500 } 501 502 if (dir->fsckflags & DIREMPTY) { 503 if (!(dir->fsckflags & DIREMPWARN)) { 504 pwarn("%s has entries after end of directory\n", 505 fullpath(dir)); 506 if (ask(1, "Extend")) { 507 u_char *q; 508 509 dir->fsckflags &= ~DIREMPTY; 510 if (delete(f, boot, fat, 511 empcl, empty - buffer, 512 cl, p - buffer, 1) == FSFATAL) 513 return FSFATAL; 514 q = empcl == cl ? empty : buffer; 515 for (; q < p; q += 32) 516 *q = SLOT_DELETED; 517 mod |= THISMOD|FSDIRMOD; 518 } else if (ask(0, "Truncate")) 519 dir->fsckflags |= DIREMPWARN; 520 } 521 if (dir->fsckflags & DIREMPWARN) { 522 *p = SLOT_DELETED; 523 mod |= THISMOD|FSDIRMOD; 524 continue; 525 } else if (dir->fsckflags & DIREMPTY) 526 mod |= FSERROR; 527 empty = NULL; 528 } 529 530 if (p[11] == ATTR_WIN95) { 531 if (*p & LRFIRST) { 532 if (shortSum != -1) { 533 if (!invlfn) { 534 invlfn = vallfn; 535 invcl = valcl; 536 } 537 } 538 memset(longName, 0, sizeof longName); 539 shortSum = p[13]; 540 vallfn = p; 541 valcl = cl; 542 } else if (shortSum != p[13] 543 || lidx != (*p & LRNOMASK)) { 544 if (!invlfn) { 545 invlfn = vallfn; 546 invcl = valcl; 547 } 548 if (!invlfn) { 549 invlfn = p; 550 invcl = cl; 551 } 552 vallfn = NULL; 553 } 554 lidx = *p & LRNOMASK; 555 t = longName + --lidx * 13; 556 for (k = 1; k < 11 && t < longName + 557 sizeof(longName); k += 2) { 558 if (!p[k] && !p[k + 1]) 559 break; 560 *t++ = p[k]; 561 /* 562 * Warn about those unusable chars in msdosfs here? XXX 563 */ 564 if (p[k + 1]) 565 t[-1] = '?'; 566 } 567 if (k >= 11) 568 for (k = 14; k < 26 && 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 (k >= 26) 576 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) { 577 if (!p[k] && !p[k + 1]) 578 break; 579 *t++ = p[k]; 580 if (p[k + 1]) 581 t[-1] = '?'; 582 } 583 if (t >= longName + sizeof(longName)) { 584 pwarn("long filename too long\n"); 585 if (!invlfn) { 586 invlfn = vallfn; 587 invcl = valcl; 588 } 589 vallfn = NULL; 590 } 591 if (p[26] | (p[27] << 8)) { 592 pwarn("long filename record cluster start != 0\n"); 593 if (!invlfn) { 594 invlfn = vallfn; 595 invcl = cl; 596 } 597 vallfn = NULL; 598 } 599 continue; /* long records don't carry further 600 * information */ 601 } 602 603 /* 604 * This is a standard msdosfs directory entry. 605 */ 606 memset(&dirent, 0, sizeof dirent); 607 608 /* 609 * it's a short name record, but we need to know 610 * more, so get the flags first. 611 */ 612 dirent.flags = p[11]; 613 614 /* 615 * Translate from 850 to ISO here XXX 616 */ 617 for (j = 0; j < 8; j++) 618 dirent.name[j] = p[j]; 619 dirent.name[8] = '\0'; 620 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--) 621 dirent.name[k] = '\0'; 622 if (dirent.name[k] != '\0') 623 k++; 624 if (dirent.name[0] == SLOT_E5) 625 dirent.name[0] = 0xe5; 626 627 if (dirent.flags & ATTR_VOLUME) { 628 if (vallfn || invlfn) { 629 mod |= removede(f, boot, fat, 630 invlfn ? invlfn : vallfn, p, 631 invlfn ? invcl : valcl, -1, 0, 632 fullpath(dir), 2); 633 vallfn = NULL; 634 invlfn = NULL; 635 } 636 continue; 637 } 638 639 if (p[8] != ' ') 640 dirent.name[k++] = '.'; 641 for (j = 0; j < 3; j++) 642 dirent.name[k++] = p[j+8]; 643 dirent.name[k] = '\0'; 644 for (k--; k >= 0 && dirent.name[k] == ' '; k--) 645 dirent.name[k] = '\0'; 646 647 if (vallfn && shortSum != calcShortSum(p)) { 648 if (!invlfn) { 649 invlfn = vallfn; 650 invcl = valcl; 651 } 652 vallfn = NULL; 653 } 654 dirent.head = p[26] | (p[27] << 8); 655 if (boot->ClustMask == CLUST32_MASK) 656 dirent.head |= (p[20] << 16) | (p[21] << 24); 657 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24); 658 if (vallfn) { 659 strlcpy(dirent.lname, longName, 660 sizeof(dirent.lname)); 661 longName[0] = '\0'; 662 shortSum = -1; 663 } 664 665 dirent.parent = dir; 666 dirent.next = dir->child; 667 668 if (invlfn) { 669 mod |= k = removede(f, boot, fat, 670 invlfn, vallfn ? vallfn : p, 671 invcl, vallfn ? valcl : cl, cl, 672 fullpath(&dirent), 0); 673 if (mod & FSFATAL) 674 return FSFATAL; 675 if (vallfn 676 ? (valcl == cl && vallfn != buffer) 677 : p != buffer) 678 if (k & FSDIRMOD) 679 mod |= THISMOD; 680 } 681 682 vallfn = NULL; /* not used any longer */ 683 invlfn = NULL; 684 685 if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) { 686 if (dirent.head != 0) { 687 pwarn("%s has clusters, but size 0\n", 688 fullpath(&dirent)); 689 if (ask(1, "Drop allocated clusters")) { 690 p[26] = p[27] = 0; 691 if (boot->ClustMask == CLUST32_MASK) 692 p[20] = p[21] = 0; 693 clearchain(boot, fat, dirent.head); 694 dirent.head = 0; 695 mod |= THISMOD|FSDIRMOD|FSFATMOD; 696 } else 697 mod |= FSERROR; 698 } 699 } else if (dirent.head == 0 700 && !strcmp(dirent.name, "..") 701 && dir->parent /* XXX */ 702 && !dir->parent->parent) { 703 /* 704 * Do nothing, the parent is the root 705 */ 706 } else if (dirent.head < CLUST_FIRST 707 || dirent.head >= boot->NumClusters 708 || fat[dirent.head].next == CLUST_FREE 709 || (fat[dirent.head].next >= CLUST_RSRVD 710 && fat[dirent.head].next < CLUST_EOFS) 711 || fat[dirent.head].head != dirent.head) { 712 if (dirent.head == 0) 713 pwarn("%s has no clusters\n", 714 fullpath(&dirent)); 715 else if (dirent.head < CLUST_FIRST 716 || dirent.head >= boot->NumClusters) 717 pwarn("%s starts with cluster out of range(%u)\n", 718 fullpath(&dirent), 719 dirent.head); 720 else if (fat[dirent.head].next == CLUST_FREE) 721 pwarn("%s starts with free cluster\n", 722 fullpath(&dirent)); 723 else if (fat[dirent.head].next >= CLUST_RSRVD) 724 pwarn("%s starts with cluster marked %s\n", 725 fullpath(&dirent), 726 rsrvdcltype(fat[dirent.head].next)); 727 else 728 pwarn("%s doesn't start a cluster chain\n", 729 fullpath(&dirent)); 730 if (dirent.flags & ATTR_DIRECTORY) { 731 if (ask(0, "Remove")) { 732 *p = SLOT_DELETED; 733 mod |= THISMOD|FSDIRMOD; 734 } else 735 mod |= FSERROR; 736 continue; 737 } else { 738 if (ask(1, "Truncate")) { 739 p[28] = p[29] = p[30] = p[31] = 0; 740 p[26] = p[27] = 0; 741 if (boot->ClustMask == CLUST32_MASK) 742 p[20] = p[21] = 0; 743 dirent.size = 0; 744 mod |= THISMOD|FSDIRMOD; 745 } else 746 mod |= FSERROR; 747 } 748 } 749 750 if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters) 751 fat[dirent.head].flags |= FAT_USED; 752 753 if (dirent.flags & ATTR_DIRECTORY) { 754 /* 755 * gather more info for directories 756 */ 757 struct dirTodoNode *n; 758 759 if (dirent.size) { 760 pwarn("Directory %s has size != 0\n", 761 fullpath(&dirent)); 762 if (ask(1, "Correct")) { 763 p[28] = p[29] = p[30] = p[31] = 0; 764 dirent.size = 0; 765 mod |= THISMOD|FSDIRMOD; 766 } else 767 mod |= FSERROR; 768 } 769 /* 770 * handle `.' and `..' specially 771 */ 772 if (strcmp(dirent.name, ".") == 0) { 773 if (dirent.head != dir->head) { 774 pwarn("`.' entry in %s has incorrect start cluster\n", 775 fullpath(dir)); 776 if (ask(1, "Correct")) { 777 dirent.head = dir->head; 778 p[26] = (u_char)dirent.head; 779 p[27] = (u_char)(dirent.head >> 8); 780 if (boot->ClustMask == CLUST32_MASK) { 781 p[20] = (u_char)(dirent.head >> 16); 782 p[21] = (u_char)(dirent.head >> 24); 783 } 784 mod |= THISMOD|FSDIRMOD; 785 } else 786 mod |= FSERROR; 787 } 788 continue; 789 } 790 if (strcmp(dirent.name, "..") == 0) { 791 if (dir->parent) { /* XXX */ 792 if (!dir->parent->parent) { 793 if (dirent.head) { 794 pwarn("`..' entry in %s has non-zero start cluster\n", 795 fullpath(dir)); 796 if (ask(1, "Correct")) { 797 dirent.head = 0; 798 p[26] = p[27] = 0; 799 if (boot->ClustMask == CLUST32_MASK) 800 p[20] = p[21] = 0; 801 mod |= THISMOD|FSDIRMOD; 802 } else 803 mod |= FSERROR; 804 } 805 } else if (dirent.head != dir->parent->head) { 806 pwarn("`..' entry in %s has incorrect start cluster\n", 807 fullpath(dir)); 808 if (ask(1, "Correct")) { 809 dirent.head = dir->parent->head; 810 p[26] = (u_char)dirent.head; 811 p[27] = (u_char)(dirent.head >> 8); 812 if (boot->ClustMask == CLUST32_MASK) { 813 p[20] = (u_char)(dirent.head >> 16); 814 p[21] = (u_char)(dirent.head >> 24); 815 } 816 mod |= THISMOD|FSDIRMOD; 817 } else 818 mod |= FSERROR; 819 } 820 } 821 continue; 822 } 823 824 /* create directory tree node */ 825 if (!(d = newDosDirEntry())) { 826 perr("No space for directory"); 827 return FSFATAL; 828 } 829 memcpy(d, &dirent, sizeof(struct dosDirEntry)); 830 /* link it into the tree */ 831 dir->child = d; 832 833 /* Enter this directory into the todo list */ 834 if (!(n = newDirTodo())) { 835 perr("No space for todo list"); 836 return FSFATAL; 837 } 838 n->next = pendingDirectories; 839 n->dir = d; 840 pendingDirectories = n; 841 } else { 842 mod |= k = checksize(boot, fat, p, &dirent); 843 if (k & FSDIRMOD) 844 mod |= THISMOD; 845 } 846 boot->NumFiles++; 847 } 848 849 if (!(boot->flags & FAT32) && !dir->parent) 850 break; 851 852 if (mod & THISMOD) { 853 last *= 32; 854 if (lseek(f, off, SEEK_SET) != off 855 || write(f, buffer, last) != last) { 856 perr("Unable to write directory"); 857 return FSFATAL; 858 } 859 mod &= ~THISMOD; 860 } 861 } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters); 862 if (invlfn || vallfn) 863 mod |= removede(f, boot, fat, 864 invlfn ? invlfn : vallfn, p, 865 invlfn ? invcl : valcl, -1, 0, 866 fullpath(dir), 1); 867 868 /* The root directory of non fat32 filesystems is in a special 869 * area and may have been modified above without being written out. 870 */ 871 if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) { 872 last *= 32; 873 if (lseek(f, off, SEEK_SET) != off 874 || write(f, buffer, last) != last) { 875 perr("Unable to write directory"); 876 return FSFATAL; 877 } 878 mod &= ~THISMOD; 879 } 880 return mod & ~THISMOD; 881 } 882 883 int 884 handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat) 885 { 886 int mod; 887 888 mod = readDosDirSection(dosfs, boot, fat, rootDir); 889 if (mod & FSFATAL) 890 return FSFATAL; 891 892 /* 893 * process the directory todo list 894 */ 895 while (pendingDirectories) { 896 struct dosDirEntry *dir = pendingDirectories->dir; 897 struct dirTodoNode *n = pendingDirectories->next; 898 899 /* 900 * remove TODO entry now, the list might change during 901 * directory reads 902 */ 903 freeDirTodo(pendingDirectories); 904 pendingDirectories = n; 905 906 /* 907 * handle subdirectory 908 */ 909 mod |= readDosDirSection(dosfs, boot, fat, dir); 910 if (mod & FSFATAL) 911 return FSFATAL; 912 } 913 914 return mod; 915 } 916 917 /* 918 * Try to reconnect a FAT chain into dir 919 */ 920 static u_char *lfbuf; 921 static cl_t lfcl; 922 static off_t lfoff; 923 924 int 925 reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head) 926 { 927 struct dosDirEntry d; 928 int len; 929 u_char *p; 930 931 if (!ask(1, "Reconnect")) 932 return FSERROR; 933 934 if (!lostDir) { 935 for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) { 936 if (!strcmp(lostDir->name, LOSTDIR)) 937 break; 938 } 939 if (!lostDir) { /* Create LOSTDIR? XXX */ 940 pwarn("No %s directory\n", LOSTDIR); 941 return FSERROR; 942 } 943 } 944 if (!lfbuf) { 945 lfbuf = malloc(boot->ClusterSize); 946 if (!lfbuf) { 947 perr("No space for buffer"); 948 return FSFATAL; 949 } 950 p = NULL; 951 } else 952 p = lfbuf; 953 while (1) { 954 if (p) 955 for (; p < lfbuf + boot->ClusterSize; p += 32) 956 if (*p == SLOT_EMPTY 957 || *p == SLOT_DELETED) 958 break; 959 if (p && p < lfbuf + boot->ClusterSize) 960 break; 961 lfcl = p ? fat[lfcl].next : lostDir->head; 962 if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) { 963 /* Extend LOSTDIR? XXX */ 964 pwarn("No space in %s\n", LOSTDIR); 965 return FSERROR; 966 } 967 lfoff = lfcl * boot->ClusterSize 968 + boot->ClusterOffset * boot->bpbBytesPerSec; 969 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 970 || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 971 perr("could not read LOST.DIR"); 972 return FSFATAL; 973 } 974 p = lfbuf; 975 } 976 977 boot->NumFiles++; 978 /* Ensure uniqueness of entry here! XXX */ 979 memset(&d, 0, sizeof d); 980 /* worst case -1 = 4294967295, 10 digits */ 981 len = snprintf(d.name, sizeof(d.name), "%u", head); 982 d.flags = 0; 983 d.head = head; 984 d.size = fat[head].length * boot->ClusterSize; 985 986 memcpy(p, d.name, len); 987 memset(p + len, ' ', 11 - len); 988 memset(p + 11, 0, 32 - 11); 989 p[26] = (u_char)d.head; 990 p[27] = (u_char)(d.head >> 8); 991 if (boot->ClustMask == CLUST32_MASK) { 992 p[20] = (u_char)(d.head >> 16); 993 p[21] = (u_char)(d.head >> 24); 994 } 995 p[28] = (u_char)d.size; 996 p[29] = (u_char)(d.size >> 8); 997 p[30] = (u_char)(d.size >> 16); 998 p[31] = (u_char)(d.size >> 24); 999 fat[head].flags |= FAT_USED; 1000 if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 1001 || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 1002 perr("could not write LOST.DIR"); 1003 return FSFATAL; 1004 } 1005 return FSDIRMOD; 1006 } 1007 1008 void 1009 finishlf(void) 1010 { 1011 if (lfbuf) 1012 free(lfbuf); 1013 lfbuf = NULL; 1014 } 1015