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