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