1/* 2 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23/* 24 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank 25 * Copyright (c) 1995 Martin Husemann 26 * Some structure declaration borrowed from Paul Popelka 27 * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference. 28 * 29 * Redistribution and use in source and binary forms, with or without 30 * modification, are permitted provided that the following conditions 31 * are met: 32 * 1. Redistributions of source code must retain the above copyright 33 * notice, this list of conditions and the following disclaimer. 34 * 2. Redistributions in binary form must reproduce the above copyright 35 * notice, this list of conditions and the following disclaimer in the 36 * documentation and/or other materials provided with the distribution. 37 * 3. All advertising materials mentioning features or use of this software 38 * must display the following acknowledgement: 39 * This product includes software developed by Martin Husemann 40 * and Wolfgang Solfrank. 41 * 4. Neither the name of the University nor the names of its contributors 42 * may be used to endorse or promote products derived from this software 43 * without specific prior written permission. 44 * 45 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 46 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 47 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 48 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, 49 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 50 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 51 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 52 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 53 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 54 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 55 */ 56 57 58#include <sys/cdefs.h> 59 60#include <stdint.h> 61#include <stdio.h> 62#include <stdlib.h> 63#include <string.h> 64#include <ctype.h> 65#include <stdio.h> 66#include <unistd.h> 67#include <time.h> 68 69#include <sys/param.h> 70#include <sys/errno.h> 71 72#include "ext.h" 73#include "fsutil.h" 74 75#define SLOT_EMPTY 0x00 /* slot has never been used */ 76#define SLOT_E5 0x05 /* the real value is 0xe5 */ 77#define SLOT_DELETED 0xe5 /* file in this slot deleted */ 78 79#define ATTR_NORMAL 0x00 /* normal file */ 80#define ATTR_READONLY 0x01 /* file is readonly */ 81#define ATTR_HIDDEN 0x02 /* file is hidden */ 82#define ATTR_SYSTEM 0x04 /* file is a system file */ 83#define ATTR_VOLUME 0x08 /* entry is a volume label */ 84#define ATTR_DIRECTORY 0x10 /* entry is a directory name */ 85#define ATTR_ARCHIVE 0x20 /* file is new or modified */ 86 87#define ATTR_WIN95 0x0f /* long name record */ 88 89/* 90 * This is the format of the contents of the deTime field in the direntry 91 * structure. 92 * We don't use bitfields because we don't know how compilers for 93 * arbitrary machines will lay them out. 94 */ 95#define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */ 96#define DT_2SECONDS_SHIFT 0 97#define DT_MINUTES_MASK 0x7E0 /* minutes */ 98#define DT_MINUTES_SHIFT 5 99#define DT_HOURS_MASK 0xF800 /* hours */ 100#define DT_HOURS_SHIFT 11 101 102/* 103 * This is the format of the contents of the deDate field in the direntry 104 * structure. 105 */ 106#define DD_DAY_MASK 0x1F /* day of month */ 107#define DD_DAY_SHIFT 0 108#define DD_MONTH_MASK 0x1E0 /* month */ 109#define DD_MONTH_SHIFT 5 110#define DD_YEAR_MASK 0xFE00 /* year - 1980 */ 111#define DD_YEAR_SHIFT 9 112 113 114/* dir.c */ 115static struct dosDirEntry *newDosDirEntry __P((void)); 116static void freeDosDirEntry __P((struct dosDirEntry *)); 117static struct dirTodoNode *newDirTodo __P((void)); 118static void freeDirTodo __P((struct dirTodoNode *)); 119static char *fullpath __P((struct dosDirEntry *)); 120static u_char calcShortSum __P((u_char *)); 121static int delete(int fd, struct bootblock *boot, cl_t startcl, size_t startoff, cl_t endcl, size_t endoff, int notlast); 122static int msdosfs_removede __P((int, struct bootblock *, u_char *, 123 u_char *, cl_t, cl_t, cl_t, char *, int)); 124static int checksize __P((struct bootblock *, u_char *, struct dosDirEntry *)); 125static int readDosDirSection __P((int, struct bootblock *, struct dosDirEntry *)); 126 127/* 128 * Manage free dosDirEntry structures. 129 */ 130static struct dosDirEntry *freede; 131 132static struct dosDirEntry * 133newDosDirEntry() 134{ 135 struct dosDirEntry *de; 136 137 if (!(de = freede)) { 138 if (!(de = (struct dosDirEntry *)malloc(sizeof *de))) 139 return 0; 140 } else 141 freede = de->next; 142 return de; 143} 144 145static void 146freeDosDirEntry(de) 147 struct dosDirEntry *de; 148{ 149 de->next = freede; 150 freede = de; 151} 152 153/* 154 * The same for dirTodoNode structures. 155 */ 156static struct dirTodoNode *freedt; 157 158static struct dirTodoNode * 159newDirTodo() 160{ 161 struct dirTodoNode *dt; 162 163 if (!(dt = freedt)) { 164 if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt))) 165 return 0; 166 } else 167 freedt = dt->next; 168 return dt; 169} 170 171static void 172freeDirTodo(dt) 173 struct dirTodoNode *dt; 174{ 175 dt->next = freedt; 176 freedt = dt; 177} 178 179/* 180 * The stack of unread directories 181 */ 182struct dirTodoNode *pendingDirectories = NULL; 183 184/* 185 * Return the full pathname for a directory entry. 186 */ 187static char * 188fullpath(dir) 189 struct dosDirEntry *dir; 190{ 191 static char namebuf[MAXPATHLEN + 1]; 192 char *cp, *np; 193 size_t nl; 194 195 /* 196 * The loop below returns the empty string for the root directory. 197 * So special case it to return "/" instead. 198 */ 199 if (dir == rootDir) 200 { 201 namebuf[0] = '/'; 202 namebuf[1] = '\0'; 203 return namebuf; 204 } 205 206 cp = namebuf + sizeof namebuf - 1; 207 *cp = '\0'; 208 do { 209 np = dir->lname[0] ? dir->lname : dir->name; 210 nl = strlen(np); 211 if ((cp -= nl) <= namebuf + 1) 212 break; 213 memcpy(cp, np, nl); 214 *--cp = '/'; 215 } while ((dir = dir->parent) != NULL); 216 if (dir) 217 *--cp = '?'; 218 else 219 cp++; 220 221 return cp; 222} 223 224/* 225 * Calculate a checksum over an 8.3 alias name 226 */ 227static u_char 228calcShortSum(p) 229 u_char *p; 230{ 231 u_char sum = 0; 232 int i; 233 234 for (i = 0; i < 11; i++) { 235 sum = (sum << 7)|(sum >> 1); /* rotate right */ 236 sum += p[i]; 237 } 238 239 return sum; 240} 241 242 243/* 244 * markDosDirChain 245 * 246 * Follow the cluster chain pointed to by @dir. Mark all of the clusters in 247 * use in our bitmap. If we encounter a cluster that is already marked in 248 * use, out of range, reserved, or EOF, then set @dir->end to that cluster 249 * number. Also sets @dir->physicalSize to the size, in bytes, of valid 250 * clusters in the chain. 251 * 252 * Assumes that the caller has already verified that the starting cluster 253 * is valid, not CLUST_FREE, and not yet marked used. 254 */ 255static int 256markDosDirChain(struct bootblock *boot, struct dosDirEntry *dir) 257{ 258 int err = FSOK; 259 cl_t cluster, prev, value; 260 cl_t count; 261 262 cluster = dir->head; 263 prev = 0; 264 count = 0; 265 while (cluster >= CLUST_FIRST && cluster < boot->NumClusters && !isUsed(cluster)) 266 { 267 /* 268 * Clusters that are marked "reserved" or "bad" cannot be part of the 269 * file. We must truncate the file at the previous cluster, which 270 * is why we break from the loop early. 271 * 272 * Clusters marked "free", or which point to invalid cluster numbers 273 * can be allocated to the file my setting them to CLUST_EOF. We 274 * catch these cases on the next iteration of the loop so that the 275 * current cluster will remain part of the file (i.e. it becomes 276 * "previous" as we iterate once more). 277 */ 278 value = fat_get(cluster); 279 if (value == CLUST_RSRVD || value == CLUST_BAD) 280 { 281 cluster = value; 282 break; 283 } 284 markUsed(cluster); 285 ++count; 286 prev = cluster; 287 cluster = fat_get(cluster); 288 } 289 290 /* 291 * We hit the end of the cluster chain. If it wasn't due to EOF, then see 292 * if we can fix the problem. 293 */ 294 if (cluster < CLUST_EOFS) 295 { 296 if (cluster == CLUST_FREE || cluster >= CLUST_RSRVD) 297 pwarn("%s: Cluster chain starting at %u ends with cluster marked %s\n", 298 fullpath(dir), dir->head, rsrvdcltype(cluster)); 299 else if (cluster < CLUST_FIRST || cluster >= boot->NumClusters) 300 pwarn("%s: Cluster chain starting at %u continues with cluster out of range (%u)\n", 301 fullpath(dir), dir->head, cluster); 302 else 303 pwarn("%s: Cluster chain starting at %u is cross-linked at cluster %u\n", 304 fullpath(dir), dir->head, cluster); 305 if (ask(1, "Truncate")) 306 { 307 err = fat_set(prev, CLUST_EOF); 308 if (err) 309 cluster = CLUST_ERROR; 310 else 311 cluster = CLUST_EOF; 312 } 313 } 314 315 dir->end = cluster; 316 dir->physicalSize = (u_int64_t)count * boot->ClusterSize; 317 if (cluster == CLUST_ERROR) 318 return FSFATAL; 319 320 return err; 321} 322 323 324/* 325 * Global variables temporarily used during a directory scan 326 */ 327static char longName[DOSLONGNAMELEN] = ""; 328static u_char *buffer = NULL; 329static u_char *delbuf = NULL; 330 331struct dosDirEntry *rootDir; 332static struct dosDirEntry *lostDir; 333 334/* 335 * Init internal state for a new directory scan. 336 */ 337int 338resetDosDirSection(struct bootblock *boot) 339{ 340 int b1, b2; 341 cl_t cl; 342 int ret = FSOK; 343 344 b1 = boot->RootDirEnts * 32; 345 b2 = boot->SecPerClust * boot->BytesPerSec; 346 347 if (!(buffer = malloc(b1 > b2 ? b1 : b2)) 348 || !(delbuf = malloc(b2)) 349 || !(rootDir = newDosDirEntry())) { 350 perr("No space for directory"); 351 return FSFATAL; 352 } 353 memset(rootDir, 0, sizeof *rootDir); 354 if (boot->flags & FAT32) { 355 if (boot->RootCl < CLUST_FIRST || boot->RootCl >= boot->NumClusters) { 356 pfatal("Root directory starts with cluster out of range(%u)\n", 357 boot->RootCl); 358 return FSFATAL; 359 } 360 361 cl = fat_get(boot->RootCl); 362 if (cl == CLUST_ERROR) 363 return FSFATAL; 364 365 if (cl < CLUST_FIRST 366 || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)) { 367 if (cl == CLUST_FREE) 368 pwarn("Root directory starts with free cluster\n"); 369 else if (cl >= CLUST_RSRVD) 370 pwarn("Root directory starts with cluster marked %s\n", 371 rsrvdcltype(cl)); 372 if (ask(1, "Fix")) { 373 /* 374 * This used to assign CLUST_FREE. How was that a good idea??? 375 */ 376 ret = fat_set(boot->RootCl, CLUST_EOF); 377 if (!ret) 378 ret = FSFATMOD; 379 380 /* 381 * We have to mark the root cluster as free so that 382 * markDosDirChain below won't think the first root 383 * cluster is cross-linked to itself. 384 */ 385 markFree(boot->RootCl); 386 } else 387 return FSFATAL; 388 } 389 390 rootDir->head = boot->RootCl; 391 ret |= markDosDirChain(boot, rootDir); 392 } 393 394 return ret; 395} 396 397/* 398 * Cleanup after a directory scan 399 */ 400void 401finishDosDirSection() 402{ 403 struct dirTodoNode *p, *np; 404 struct dosDirEntry *d, *nd; 405 406 for (p = pendingDirectories; p; p = np) { 407 np = p->next; 408 freeDirTodo(p); 409 } 410 pendingDirectories = 0; 411 for (d = rootDir; d; d = nd) { 412 if ((nd = d->child) != NULL) { 413 d->child = 0; 414 continue; 415 } 416 if (!(nd = d->next)) 417 nd = d->parent; 418 freeDosDirEntry(d); 419 } 420 rootDir = lostDir = NULL; 421 free(buffer); 422 free(delbuf); 423 buffer = NULL; 424 delbuf = NULL; 425} 426 427/* 428 * Delete a range of directory entries. 429 * 430 * Inputs: 431 * fd File descriptor. 432 * startcl Cluster number containing first directory entry. 433 * startoff Offset within cluster of first directory entry. 434 * endcl Cluster number containing last directory entry. 435 * endoff Offset within cluster beyond last byte of last directory entry. 436 * notlast If true, don't delete the directory entries in the last cluster 437 * (endcl); the caller already has that cluster in memory and will 438 * update those entries itself. 439 */ 440static int 441delete(int fd, struct bootblock *boot, cl_t startcl, size_t startoff, cl_t endcl, size_t endoff, int notlast) 442{ 443 u_char *s, *e; 444 off_t off; 445 int clsz = boot->SecPerClust * boot->BytesPerSec; 446 447 s = delbuf + startoff; 448 e = delbuf + clsz; 449 while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) { 450 if (startcl == endcl) { 451 if (notlast) 452 break; 453 e = delbuf + endoff; 454 } 455 off = startcl * boot->SecPerClust + boot->ClusterOffset; 456 off *= boot->BytesPerSec; 457 if (lseek(fd, off, SEEK_SET) != off 458 || read(fd, delbuf, clsz) != clsz) { 459 perr("Unable to read directory"); 460 return FSFATAL; 461 } 462 while (s < e) { 463 *s = SLOT_DELETED; 464 s += 32; 465 } 466 if (lseek(fd, off, SEEK_SET) != off 467 || write(fd, delbuf, clsz) != clsz) { 468 perr("Unable to write directory"); 469 return FSFATAL; 470 } 471 if (startcl == endcl) 472 break; 473 startcl = fat_get(startcl); 474 if (startcl == CLUST_ERROR) 475 return FSFATAL; 476 s = delbuf; 477 } 478 return FSOK; 479} 480 481static int 482msdosfs_removede(f, boot, start, end, startcl, endcl, curcl, path, type) 483 int f; 484 struct bootblock *boot; 485 u_char *start; 486 u_char *end; 487 cl_t startcl; 488 cl_t endcl; 489 cl_t curcl; 490 char *path; 491 int type; 492{ 493 switch (type) { 494 case 0: 495 pwarn("Invalid long filename entry for %s\n", path); 496 break; 497 case 1: 498 pwarn("Invalid long filename entry at end of directory %s\n", path); 499 break; 500 case 2: 501 pwarn("Invalid long filename entry for volume label\n"); 502 break; 503 } 504 if (ask(0, "Remove")) { 505 if (startcl != curcl) { 506 if (delete(f, boot, 507 startcl, start - buffer, 508 endcl, end - buffer, 509 endcl == curcl) == FSFATAL) 510 return FSFATAL; 511 start = buffer; 512 } 513 if (endcl == curcl) 514 for (; start < end; start += 32) 515 *start = SLOT_DELETED; 516 return FSDIRMOD; 517 } 518 return FSERROR; 519} 520 521/* 522 * Check the size of a file represented by an in-memory file entry 523 * 524 * Assumes our caller has checked that dir->head is a valid cluster number. 525 * Assumes that markDosDirChain has been called, and that dir->physicalSize 526 * has been set up. 527 */ 528static int 529checksize(boot, p, dir) 530 struct bootblock *boot; 531 u_char *p; 532 struct dosDirEntry *dir; 533{ 534 /* 535 * Check size on ordinary files 536 */ 537 if (dir->physicalSize < dir->size) { 538 pwarn("size of %s is %u, should at most be %llu\n", 539 fullpath(dir), dir->size, dir->physicalSize); 540 if (ask(1, "Truncate")) { 541 dir->size = (uint32_t)dir->physicalSize; 542 p[28] = (u_char)dir->physicalSize; 543 p[29] = (u_char)(dir->physicalSize >> 8); 544 p[30] = (u_char)(dir->physicalSize >> 16); 545 p[31] = (u_char)(dir->physicalSize >> 24); 546 return FSDIRMOD; 547 } else 548 return FSERROR; 549 } else if (dir->physicalSize - dir->size >= boot->ClusterSize) { 550 pwarn("%s has too many clusters allocated (logical=%u, physical=%llu)\n", 551 fullpath(dir), dir->size, dir->physicalSize); 552 if (ask(1, "Drop superfluous clusters")) { 553 int mod = 0; 554 cl_t cl, next; 555 u_int64_t sz = 0; 556 557 if (dir->size == 0) 558 { 559 /* Set "first cluster" in directory to 0 */ 560 p[20]=p[21]=p[26]=p[27] = 0; 561 mod = FSDIRMOD; 562 563 /* Begin deallocating with the previous first cluster */ 564 cl = dir->head; 565 } 566 else 567 { 568 /* 569 * Skip over the clusters containing the first dir->size bytes 570 */ 571 for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;) 572 { 573 cl = fat_get(cl); 574 if (cl == CLUST_ERROR) 575 return FSFATAL; 576 } 577 578 /* When we get here, "cl" is the new last cluster of the file */ 579 580 /* 581 * Remember the first cluster to be dropped. 582 * Mark the new last cluster as CLUST_EOF. 583 */ 584 next = fat_get(cl); 585 if (next == CLUST_ERROR) 586 return FSFATAL; 587 if (fat_set(cl, CLUST_EOF)) 588 return FSFATAL; 589 cl = next; 590 } 591 592 /* 593 * Free the clusters up to physicalSize 594 * 595 * NOTE: We can't just follow the chain to CLUST_EOF because it 596 * might be cross-linked with some other chain. Assumes 597 * dir->physicalSize is set to the size of the chain before any 598 * error or cross-link. 599 */ 600 while (sz < dir->physicalSize) 601 { 602 next = fat_get(cl); 603 if (next == CLUST_ERROR) 604 return FSFATAL; 605 if (fat_set(cl, CLUST_FREE)) 606 return FSFATAL; 607 cl = next; 608 sz += boot->ClusterSize; 609 } 610 611 return mod | FSFATMOD; 612 } else 613 return FSERROR; 614 } 615 return FSOK; 616} 617 618 619/* 620 * Is the given directory entry really a subdirectory? 621 * 622 * Read the first cluster of the given subdirectory and check whether the 623 * first two short names are "." and "..". 624 * 625 * Return values: 626 * 0 Is a valid subdirectory 627 * ENOTDIR Is not a valid subdirectory 628 * ENOMEM Unable to allocate memory for an I/O buffer 629 * EIO Unable to read from the subdirectory 630 */ 631static errno_t isSubdirectory(int fd, struct bootblock *boot, struct dosDirEntry *dir) 632{ 633 off_t offset; 634 ssize_t amount; 635 char *buf; 636 errno_t err = 0; 637 638 buf = malloc(boot->BytesPerSec); 639 if (buf == NULL) { 640 perr("No memory for subdirectory buffer"); 641 return ENOMEM; 642 } 643 644 offset = ((off_t)dir->head * boot->SecPerClust + boot->ClusterOffset) * boot->BytesPerSec; 645 amount = pread(fd, buf, boot->BytesPerSec, offset); 646 if (amount != boot->BytesPerSec) { 647 pfatal("Unable to read cluster %u", dir->head); 648 err = EIO; 649 goto fail; 650 } 651 652 /* Make sure the first two children are named "." and ".." */ 653 if (memcmp(buf, ". ", 11) || memcmp(buf+32, ".. ", 11)) { 654 err = ENOTDIR; 655 goto fail; 656 } 657 658 /* Make sure "." and ".." are marked as directories */ 659 if ((buf[11] & ATTR_DIRECTORY) == 0 || (buf[32+11] & ATTR_DIRECTORY) == 0) { 660 err = ENOTDIR; 661 } 662 663fail: 664 free(buf); 665 return err; 666} 667 668/* 669 * Read a directory and 670 * - resolve long name records 671 * - enter file and directory records into the parent's list 672 * - push directories onto the todo-stack 673 */ 674static int 675readDosDirSection(f, boot, dir) 676 int f; 677 struct bootblock *boot; 678 struct dosDirEntry *dir; 679{ 680 struct dosDirEntry dirent, *d; 681 u_char *p, *vallfn, *invlfn, *empty; 682 off_t off; 683 int i, j, k, last; 684 cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; 685 cl_t last_cl; 686 char *t; 687 u_int lidx = 0; 688 int shortSum; 689 int mod = FSOK; 690#define THISMOD 0x8000 /* Only used within this routine */ 691 692 cl = dir->head; 693 if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { 694 /* 695 * Already handled somewhere else. 696 */ 697 fprintf(stderr, "readDosDirSection: Start cluster (%u) out of range; ignoring\n", cl); 698 return FSOK; 699 } 700 701 shortSum = -1; 702 vallfn = invlfn = empty = NULL; 703 do { 704 last_cl = cl; /* Remember last cluster accessed before exiting loop */ 705 706 /* 707 * Get the starting offset and length of the current chunk of the 708 * directory. 709 */ 710 if (!(boot->flags & FAT32) && !dir->parent) { 711 last = boot->RootDirEnts * 32; 712 off = boot->ResSectors + boot->FATs * boot->FATsecs; 713 } else { 714 last = boot->SecPerClust * boot->BytesPerSec; 715 off = cl * boot->SecPerClust + boot->ClusterOffset; 716 } 717 718 off *= boot->BytesPerSec; 719 if (lseek(f, off, SEEK_SET) != off 720 || read(f, buffer, last) != last) { 721 perr("Unable to read directory"); 722 return FSFATAL; 723 } 724 last /= 32; 725 726 /* 727 * For each "slot" in the directory... 728 */ 729 for (p = buffer, i = 0; i < last; i++, p += 32) { 730 if (dir->fsckflags & DIREMPWARN) { 731 *p = SLOT_EMPTY; 732 mod |= THISMOD|FSDIRMOD; 733 continue; 734 } 735 736 if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { 737 if (*p == SLOT_EMPTY) { 738 dir->fsckflags |= DIREMPTY; 739 empty = p; 740 empcl = cl; 741 } 742 continue; 743 } 744 745 if (dir->fsckflags & DIREMPTY) { 746 if (!(dir->fsckflags & DIREMPWARN)) { 747 pwarn("%s has entries after end of directory\n", 748 fullpath(dir)); 749 if (ask(1, "Truncate")) 750 dir->fsckflags |= DIREMPWARN; 751 else if (ask(0, "Extend")) { 752 u_char *q; 753 754 dir->fsckflags &= ~DIREMPTY; 755 if (delete(f, boot, 756 empcl, empty - buffer, 757 cl, p - buffer, 1) == FSFATAL) 758 return FSFATAL; 759 q = empcl == cl ? empty : buffer; 760 for (; q < p; q += 32) 761 *q = SLOT_DELETED; 762 mod |= THISMOD|FSDIRMOD; 763 } 764 } 765 if (dir->fsckflags & DIREMPWARN) { 766 *p = SLOT_EMPTY; 767 mod |= THISMOD|FSDIRMOD; 768 continue; 769 } else if (dir->fsckflags & DIREMPTY) 770 mod |= FSERROR; 771 empty = NULL; 772 } 773 774 /* 775 * Check long name entries 776 */ 777 if (p[11] == ATTR_WIN95) { 778 /* Remember or validate the long name checksum */ 779 if (*p & LRFIRST) { 780 if (shortSum != -1) { 781 if (!invlfn) { 782 invlfn = vallfn; 783 invcl = valcl; 784 } 785 } 786 memset(longName, 0, sizeof longName); 787 shortSum = p[13]; 788 vallfn = p; 789 valcl = cl; 790 } else if (shortSum != p[13] 791 || lidx != (*p & LRNOMASK)) { 792 if (!invlfn) { 793 invlfn = vallfn; 794 invcl = valcl; 795 } 796 if (!invlfn) { 797 invlfn = p; 798 invcl = cl; 799 } 800 vallfn = NULL; 801 } 802 lidx = *p & LRNOMASK; 803 804 /* 805 * Gather the characters from this long name entry 806 */ 807 t = longName + --lidx * 13; 808 for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) { 809 if (!p[k] && !p[k + 1]) 810 break; 811 *t++ = p[k]; 812 /* 813 * Warn about those unusable chars in msdosfs here? XXX 814 */ 815 if (p[k + 1]) 816 t[-1] = '?'; 817 } 818 if (k >= 11) 819 for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) { 820 if (!p[k] && !p[k + 1]) 821 break; 822 *t++ = p[k]; 823 if (p[k + 1]) 824 t[-1] = '?'; 825 } 826 if (k >= 26) 827 for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) { 828 if (!p[k] && !p[k + 1]) 829 break; 830 *t++ = p[k]; 831 if (p[k + 1]) 832 t[-1] = '?'; 833 } 834 835 if (t >= longName + sizeof(longName)) { 836 pwarn("long filename too long\n"); 837 if (!invlfn) { 838 invlfn = vallfn; 839 invcl = valcl; 840 } 841 vallfn = NULL; 842 } 843 if (p[26] | (p[27] << 8)) { 844 pwarn("long filename record cluster start != 0\n"); 845 if (!invlfn) { 846 invlfn = vallfn; 847 invcl = cl; 848 } 849 vallfn = NULL; 850 } 851 continue; /* long records don't carry further 852 * information */ 853 } 854 855 /* 856 * This is a standard msdosfs directory entry. 857 */ 858 memset(&dirent, 0, sizeof dirent); 859 860 /* 861 * it's a short name record, but we need to know 862 * more, so get the flags first. 863 */ 864 dirent.flags = p[11]; 865 866 /* 867 * Gather the base name of the short name (the "8" in "8.3"). 868 * Remove any trailing space padding. 869 */ 870 for (j = 0; j < 8; j++) 871 dirent.name[j] = p[j]; 872 dirent.name[8] = '\0'; 873 for (k = 7; k >= 0 && dirent.name[k] == ' '; k--) 874 dirent.name[k] = '\0'; 875 if (dirent.name[k] != '\0') 876 k++; 877 if (dirent.name[0] == SLOT_E5) 878 dirent.name[0] = 0xe5; 879 880 if (dirent.flags & ATTR_VOLUME) { 881 if (vallfn || invlfn) { 882 mod |= msdosfs_removede(f, boot, 883 invlfn ? invlfn : vallfn, p, 884 invlfn ? invcl : valcl, cl, cl, 885 fullpath(dir), 2); 886 vallfn = NULL; 887 invlfn = NULL; 888 } 889 continue; 890 } 891 892 /* 893 * Gather the extension of the short name (if any). 894 */ 895 if (p[8] != ' ') 896 dirent.name[k++] = '.'; 897 for (j = 0; j < 3; j++) 898 dirent.name[k++] = p[j+8]; 899 dirent.name[k] = '\0'; 900 for (k--; k >= 0 && dirent.name[k] == ' '; k--) 901 dirent.name[k] = '\0'; 902 903 /* If there was a long name, make sure its checksum matches. */ 904 if (vallfn && shortSum != calcShortSum(p)) { 905 if (!invlfn) { 906 invlfn = vallfn; 907 invcl = valcl; 908 } 909 vallfn = NULL; 910 } 911 912 /* Get the starting cluster number field(s) */ 913 dirent.head = p[26] | (p[27] << 8); 914 if (boot->ClustMask == CLUST32_MASK) 915 dirent.head |= (p[20] << 16) | (p[21] << 24); 916 /* Get the file size */ 917 dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24); 918 /* Copy the long name, if there is one */ 919 if (vallfn) { 920 strlcpy(dirent.lname, longName, sizeof(dirent.lname)); 921 longName[0] = '\0'; 922 shortSum = -1; 923 } 924 925 dirent.parent = dir; 926 dirent.next = dir->child; 927 928 if (invlfn) { 929 mod |= k = msdosfs_removede(f, boot, 930 invlfn, vallfn ? vallfn : p, 931 invcl, vallfn ? valcl : cl, cl, 932 fullpath(&dirent), 0); 933 if (mod & FSFATAL) 934 return FSFATAL; 935 if (vallfn 936 ? (valcl == cl && vallfn != buffer) 937 : p != buffer) 938 if (k & FSDIRMOD) 939 mod |= THISMOD; 940 } 941 942 vallfn = NULL; /* not used any longer */ 943 invlfn = NULL; 944 945 if (!strcmp(dirent.name, ".") || !strcmp(dirent.name,"..")) 946 { 947 /* 948 * Don't do size or in-use checks for "." or ".." 949 * They'll be checked more below. 950 */ 951 goto MarkedChain; 952 } 953 else 954 { 955 if (dirent.head == CLUST_FREE) 956 { 957 if (dirent.flags & ATTR_DIRECTORY || dirent.size != 0) 958 { 959 pwarn("%s has no clusters\n", fullpath(&dirent)); 960 goto remove_or_truncate; 961 } 962 } 963 else 964 { 965 cl_t next; 966 967 if (dirent.head < CLUST_FIRST || dirent.head >= boot->NumClusters) 968 { 969 pwarn("%s starts with cluster out of range (%u)\n", 970 fullpath(&dirent), dirent.head); 971 goto remove_or_truncate; 972 } 973 974 if (isUsed(dirent.head)) 975 { 976 pwarn("%s starts with cross-linked cluster (%u)\n", 977 fullpath(&dirent), dirent.head); 978 goto remove_or_truncate; 979 } 980 981 next = fat_get(dirent.head); 982 if (next == CLUST_ERROR) 983 return FSFATAL; 984 985 if (next == CLUST_FREE) 986 { 987 pwarn("%s starts with free cluster\n", fullpath(&dirent)); 988 goto remove_or_truncate; 989 } 990 991 if (next >= CLUST_RSRVD && next < CLUST_EOFS) 992 { 993 pwarn("%s starts with cluster marked %s\n", 994 fullpath(&dirent), 995 rsrvdcltype(next)); 996 remove_or_truncate: 997 if (dirent.flags & ATTR_DIRECTORY) { 998 if (ask(0, "Remove")) { 999 *p = SLOT_DELETED; 1000 mod |= THISMOD|FSDIRMOD; 1001 } else 1002 mod |= FSERROR; 1003 continue; 1004 } else { 1005 if (ask(1, "Truncate")) { 1006 p[28] = p[29] = p[30] = p[31] = 0; 1007 p[26] = p[27] = 0; 1008 if (boot->ClustMask == CLUST32_MASK) 1009 p[20] = p[21] = 0; 1010 dirent.head = 0; 1011 dirent.size = 0; 1012 mod |= THISMOD|FSDIRMOD; 1013 } else 1014 mod |= FSERROR; 1015 } 1016 } 1017 } 1018 } 1019 1020 if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters) 1021 { 1022 mod |= markDosDirChain(boot, &dirent); 1023 if (mod & FSFATAL) 1024 return FSFATAL; 1025 } 1026 1027MarkedChain: 1028 if (dirent.flags & ATTR_DIRECTORY) { 1029 /* 1030 * gather more info for directories 1031 */ 1032 if (dirent.size) { 1033 pwarn("Directory %s has size != 0\n", 1034 fullpath(&dirent)); 1035 if (ask(1, "Correct")) { 1036 p[28] = p[29] = p[30] = p[31] = 0; 1037 dirent.size = 0; 1038 mod |= THISMOD|FSDIRMOD; 1039 } else 1040 mod |= FSERROR; 1041 } 1042 /* 1043 * handle `.' and `..' specially 1044 */ 1045 if (strcmp(dirent.name, ".") == 0) { 1046 if (dirent.head != dir->head) { 1047 pwarn("`.' entry in %s has incorrect start cluster\n", 1048 fullpath(dir)); 1049 if (ask(1, "Correct")) { 1050 dirent.head = dir->head; 1051 p[26] = (u_char)dirent.head; 1052 p[27] = (u_char)(dirent.head >> 8); 1053 if (boot->ClustMask == CLUST32_MASK) { 1054 p[20] = (u_char)(dirent.head >> 16); 1055 p[21] = (u_char)(dirent.head >> 24); 1056 } 1057 mod |= THISMOD|FSDIRMOD; 1058 } else 1059 mod |= FSERROR; 1060 } 1061 continue; 1062 } 1063 if (strcmp(dirent.name, "..") == 0) { 1064 if (dir->parent) { /* XXX */ 1065 if (!dir->parent->parent) { 1066 if (dirent.head) { 1067 pwarn("`..' entry in %s has non-zero start cluster\n", 1068 fullpath(dir)); 1069 if (ask(1, "Correct")) { 1070 dirent.head = 0; 1071 p[26] = p[27] = 0; 1072 if (boot->ClustMask == CLUST32_MASK) 1073 p[20] = p[21] = 0; 1074 mod |= THISMOD|FSDIRMOD; 1075 } else 1076 mod |= FSERROR; 1077 } 1078 } else if (dirent.head != dir->parent->head) { 1079 pwarn("`..' entry in %s has incorrect start cluster\n", 1080 fullpath(dir)); 1081 if (ask(1, "Correct")) { 1082 dirent.head = dir->parent->head; 1083 p[26] = (u_char)dirent.head; 1084 p[27] = (u_char)(dirent.head >> 8); 1085 if (boot->ClustMask == CLUST32_MASK) { 1086 p[20] = (u_char)(dirent.head >> 16); 1087 p[21] = (u_char)(dirent.head >> 24); 1088 } 1089 mod |= THISMOD|FSDIRMOD; 1090 } else 1091 mod |= FSERROR; 1092 } 1093 } 1094 continue; 1095 } 1096 1097 /* 1098 * We've found something that claims to be a subdirectory. 1099 * Make sure the contents of the first cluster contain "." 1100 * and ".." entries; if not, assume this is actually a file. 1101 */ 1102 errno_t err = isSubdirectory(f, boot, &dirent); 1103 if (err) { 1104 if (err == ENOTDIR) { 1105 pwarn("Item %s does not appear to be a subdirectory\n", fullpath(&dirent)); 1106 if (ask(0, "Correct")) { 1107 p[11] &= ~ATTR_DIRECTORY; 1108 dirent.flags &= ~ATTR_DIRECTORY; 1109 mod |= THISMOD|FSDIRMOD; 1110 goto check_file; 1111 } else { 1112 mod |= FSERROR; 1113 } 1114 } else { 1115 return FSFATAL; 1116 } 1117 } 1118 1119 /* create directory tree node */ 1120 if (!(d = newDosDirEntry())) { 1121 perr("No space for directory"); 1122 return FSFATAL; 1123 } 1124 memcpy(d, &dirent, sizeof(struct dosDirEntry)); 1125 /* link it into the tree */ 1126 dir->child = d; 1127 1128 /* Enter this directory into the todo list */ 1129 struct dirTodoNode *n; 1130 if (!(n = newDirTodo())) { 1131 perr("No space for todo list"); 1132 return FSFATAL; 1133 } 1134 n->next = pendingDirectories; 1135 n->dir = d; 1136 pendingDirectories = n; 1137 } else { 1138 check_file: 1139 mod |= k = checksize(boot, p, &dirent); 1140 if (k & FSDIRMOD) 1141 mod |= THISMOD; 1142 } 1143 boot->NumFiles++; 1144 } 1145 if (mod & THISMOD) { 1146 if (lseek(f, off, SEEK_SET) != off 1147 || write(f, buffer, last*32) != last*32) { 1148 perr("Unable to write directory"); 1149 return FSFATAL; 1150 } 1151 mod &= ~THISMOD; 1152 } 1153 1154 /* 1155 * If this is a FAT12 or FAT16 root directory, there is no cluster chain 1156 * to follow. In this case, we'll exit the loop with cl==0. 1157 */ 1158 if (!(boot->flags & FAT32) && !dir->parent) 1159 break; 1160 1161 /* What about errors below? */ 1162 } while ((cl = fat_get(cl)) >= CLUST_FIRST && cl < boot->NumClusters && cl != dir->end); 1163 if (cl == CLUST_ERROR) 1164 mod |= FSFATAL; 1165 if (invlfn || vallfn) 1166 { 1167 mod |= msdosfs_removede(f, boot, 1168 invlfn ? invlfn : vallfn, p, 1169 invlfn ? invcl : valcl, last_cl, last_cl, 1170 fullpath(dir), 1); 1171 if (lseek(f, off, SEEK_SET) != off 1172 || write(f, buffer, last*32) != last*32) { 1173 perr("Unable to write directory"); 1174 return FSFATAL; 1175 } 1176 } 1177 return mod & ~THISMOD; 1178} 1179 1180int 1181handleDirTree(int dosfs, struct bootblock *boot) 1182{ 1183 int mod; 1184 1185 mod = readDosDirSection(dosfs, boot, rootDir); 1186 if (mod & FSFATAL) 1187 return FSFATAL; 1188 1189 /* 1190 * process the directory todo list 1191 */ 1192 while (pendingDirectories) { 1193 struct dosDirEntry *dir = pendingDirectories->dir; 1194 struct dirTodoNode *n = pendingDirectories->next; 1195 1196 /* 1197 * remove TODO entry now, the list might change during 1198 * directory reads 1199 */ 1200 freeDirTodo(pendingDirectories); 1201 pendingDirectories = n; 1202 1203 /* 1204 * handle subdirectory 1205 */ 1206 mod |= readDosDirSection(dosfs, boot, dir); 1207 if (mod & FSFATAL) 1208 return FSFATAL; 1209 } 1210 1211 return mod; 1212} 1213