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