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