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