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