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