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