1 /* 2 * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank 3 * Copyright (c) 1995 Martin Husemann 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by Martin Husemann 16 * and Wolfgang Solfrank. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 34 #include <sys/cdefs.h> 35 #ifndef lint 36 __RCSID("$NetBSD: fat.c,v 1.12 2000/10/10 20:24:52 is Exp $"); 37 static const char rcsid[] = 38 "$FreeBSD$"; 39 #endif /* not lint */ 40 41 #include <stdlib.h> 42 #include <string.h> 43 #include <ctype.h> 44 #include <stdio.h> 45 #include <unistd.h> 46 47 #include "ext.h" 48 #include "fsutil.h" 49 50 static int checkclnum(struct bootblock *, int, cl_t, cl_t *); 51 static int clustdiffer(cl_t, cl_t *, cl_t *, int); 52 static int tryclear(struct bootblock *, struct fatEntry *, cl_t, cl_t *); 53 static int _readfat(int, struct bootblock *, int, u_char **); 54 55 /*- 56 * The first 2 FAT entries contain pseudo-cluster numbers with the following 57 * layout: 58 * 59 * 31...... ........ ........ .......0 60 * rrrr1111 11111111 11111111 mmmmmmmm FAT32 entry 0 61 * rrrrsh11 11111111 11111111 11111xxx FAT32 entry 1 62 * 63 * 11111111 mmmmmmmm FAT16 entry 0 64 * sh111111 11111xxx FAT16 entry 1 65 * 66 * r = reserved 67 * m = BPB media ID byte 68 * s = clean flag (1 = dismounted; 0 = still mounted) 69 * h = hard error flag (1 = ok; 0 = I/O error) 70 * x = any value ok 71 */ 72 73 int 74 checkdirty(int fs, struct bootblock *boot) 75 { 76 off_t off; 77 u_char *buffer; 78 int ret = 0; 79 80 if (boot->ClustMask != CLUST16_MASK && boot->ClustMask != CLUST32_MASK) 81 return 0; 82 83 off = boot->ResSectors; 84 off *= boot->BytesPerSec; 85 86 buffer = malloc(boot->BytesPerSec); 87 if (buffer == NULL) { 88 perror("No space for FAT"); 89 return 1; 90 } 91 92 if (lseek(fs, off, SEEK_SET) != off) { 93 perror("Unable to read FAT"); 94 goto err; 95 } 96 97 if (read(fs, buffer, boot->BytesPerSec) != boot->BytesPerSec) { 98 perror("Unable to read FAT"); 99 goto err; 100 } 101 102 /* 103 * If we don't understand the FAT, then the file system must be 104 * assumed to be unclean. 105 */ 106 if (buffer[0] != boot->Media || buffer[1] != 0xff) 107 goto err; 108 if (boot->ClustMask == CLUST16_MASK) { 109 if ((buffer[2] & 0xf8) != 0xf8 || (buffer[3] & 0x3f) != 0x3f) 110 goto err; 111 } else { 112 if (buffer[2] != 0xff || (buffer[3] & 0x0f) != 0x0f 113 || (buffer[4] & 0xf8) != 0xf8 || buffer[5] != 0xff 114 || buffer[6] != 0xff || (buffer[7] & 0x03) != 0x03) 115 goto err; 116 } 117 118 /* 119 * Now check the actual clean flag (and the no-error flag). 120 */ 121 if (boot->ClustMask == CLUST16_MASK) { 122 if ((buffer[3] & 0xc0) == 0xc0) 123 ret = 1; 124 } else { 125 if ((buffer[7] & 0x0c) == 0x0c) 126 ret = 1; 127 } 128 129 err: 130 free(buffer); 131 return ret; 132 } 133 134 /* 135 * Check a cluster number for valid value 136 */ 137 static int 138 checkclnum(struct bootblock *boot, int fat, cl_t cl, cl_t *next) 139 { 140 if (*next >= (CLUST_RSRVD&boot->ClustMask)) 141 *next |= ~boot->ClustMask; 142 if (*next == CLUST_FREE) { 143 boot->NumFree++; 144 return FSOK; 145 } 146 if (*next == CLUST_BAD) { 147 boot->NumBad++; 148 return FSOK; 149 } 150 if (*next < CLUST_FIRST 151 || (*next >= boot->NumClusters && *next < CLUST_EOFS)) { 152 pwarn("Cluster %u in FAT %d continues with %s cluster number %u\n", 153 cl, fat, 154 *next < CLUST_RSRVD ? "out of range" : "reserved", 155 *next&boot->ClustMask); 156 if (ask(0, "Truncate")) { 157 *next = CLUST_EOF; 158 return FSFATMOD; 159 } 160 return FSERROR; 161 } 162 return FSOK; 163 } 164 165 /* 166 * Read a FAT from disk. Returns 1 if successful, 0 otherwise. 167 */ 168 static int 169 _readfat(int fs, struct bootblock *boot, int no, u_char **buffer) 170 { 171 off_t off; 172 173 *buffer = malloc(boot->FATsecs * boot->BytesPerSec); 174 if (*buffer == NULL) { 175 perror("No space for FAT"); 176 return 0; 177 } 178 179 off = boot->ResSectors + no * boot->FATsecs; 180 off *= boot->BytesPerSec; 181 182 if (lseek(fs, off, SEEK_SET) != off) { 183 perror("Unable to read FAT"); 184 goto err; 185 } 186 187 if (read(fs, *buffer, boot->FATsecs * boot->BytesPerSec) 188 != boot->FATsecs * boot->BytesPerSec) { 189 perror("Unable to read FAT"); 190 goto err; 191 } 192 193 return 1; 194 195 err: 196 free(*buffer); 197 return 0; 198 } 199 200 /* 201 * Read a FAT and decode it into internal format 202 */ 203 int 204 readfat(int fs, struct bootblock *boot, int no, struct fatEntry **fp) 205 { 206 struct fatEntry *fat; 207 u_char *buffer, *p; 208 cl_t cl; 209 int ret = FSOK; 210 211 boot->NumFree = boot->NumBad = 0; 212 213 if (!_readfat(fs, boot, no, &buffer)) 214 return FSFATAL; 215 216 fat = calloc(boot->NumClusters, sizeof(struct fatEntry)); 217 if (fat == NULL) { 218 perror("No space for FAT"); 219 free(buffer); 220 return FSFATAL; 221 } 222 223 if (buffer[0] != boot->Media 224 || buffer[1] != 0xff || buffer[2] != 0xff 225 || (boot->ClustMask == CLUST16_MASK && buffer[3] != 0xff) 226 || (boot->ClustMask == CLUST32_MASK 227 && ((buffer[3]&0x0f) != 0x0f 228 || buffer[4] != 0xff || buffer[5] != 0xff 229 || buffer[6] != 0xff || (buffer[7]&0x0f) != 0x0f))) { 230 231 /* Windows 95 OSR2 (and possibly any later) changes 232 * the FAT signature to 0xXXffff7f for FAT16 and to 233 * 0xXXffff0fffffff07 for FAT32 upon boot, to know that the 234 * file system is dirty if it doesn't reboot cleanly. 235 * Check this special condition before errorring out. 236 */ 237 if (buffer[0] == boot->Media && buffer[1] == 0xff 238 && buffer[2] == 0xff 239 && ((boot->ClustMask == CLUST16_MASK && buffer[3] == 0x7f) 240 || (boot->ClustMask == CLUST32_MASK 241 && buffer[3] == 0x0f && buffer[4] == 0xff 242 && buffer[5] == 0xff && buffer[6] == 0xff 243 && buffer[7] == 0x07))) 244 ret |= FSDIRTY; 245 else { 246 /* just some odd byte sequence in FAT */ 247 248 switch (boot->ClustMask) { 249 case CLUST32_MASK: 250 pwarn("%s (%02x%02x%02x%02x%02x%02x%02x%02x)\n", 251 "FAT starts with odd byte sequence", 252 buffer[0], buffer[1], buffer[2], buffer[3], 253 buffer[4], buffer[5], buffer[6], buffer[7]); 254 break; 255 case CLUST16_MASK: 256 pwarn("%s (%02x%02x%02x%02x)\n", 257 "FAT starts with odd byte sequence", 258 buffer[0], buffer[1], buffer[2], buffer[3]); 259 break; 260 default: 261 pwarn("%s (%02x%02x%02x)\n", 262 "FAT starts with odd byte sequence", 263 buffer[0], buffer[1], buffer[2]); 264 break; 265 } 266 267 268 if (ask(1, "Correct")) 269 ret |= FSFIXFAT; 270 } 271 } 272 switch (boot->ClustMask) { 273 case CLUST32_MASK: 274 p = buffer + 8; 275 break; 276 case CLUST16_MASK: 277 p = buffer + 4; 278 break; 279 default: 280 p = buffer + 3; 281 break; 282 } 283 for (cl = CLUST_FIRST; cl < boot->NumClusters;) { 284 switch (boot->ClustMask) { 285 case CLUST32_MASK: 286 fat[cl].next = p[0] + (p[1] << 8) 287 + (p[2] << 16) + (p[3] << 24); 288 fat[cl].next &= boot->ClustMask; 289 ret |= checkclnum(boot, no, cl, &fat[cl].next); 290 cl++; 291 p += 4; 292 break; 293 case CLUST16_MASK: 294 fat[cl].next = p[0] + (p[1] << 8); 295 ret |= checkclnum(boot, no, cl, &fat[cl].next); 296 cl++; 297 p += 2; 298 break; 299 default: 300 fat[cl].next = (p[0] + (p[1] << 8)) & 0x0fff; 301 ret |= checkclnum(boot, no, cl, &fat[cl].next); 302 cl++; 303 if (cl >= boot->NumClusters) 304 break; 305 fat[cl].next = ((p[1] >> 4) + (p[2] << 4)) & 0x0fff; 306 ret |= checkclnum(boot, no, cl, &fat[cl].next); 307 cl++; 308 p += 3; 309 break; 310 } 311 } 312 313 free(buffer); 314 *fp = fat; 315 return ret; 316 } 317 318 /* 319 * Get type of reserved cluster 320 */ 321 char * 322 rsrvdcltype(cl_t cl) 323 { 324 if (cl == CLUST_FREE) 325 return "free"; 326 if (cl < CLUST_BAD) 327 return "reserved"; 328 if (cl > CLUST_BAD) 329 return "as EOF"; 330 return "bad"; 331 } 332 333 static int 334 clustdiffer(cl_t cl, cl_t *cp1, cl_t *cp2, int fatnum) 335 { 336 if (*cp1 == CLUST_FREE || *cp1 >= CLUST_RSRVD) { 337 if (*cp2 == CLUST_FREE || *cp2 >= CLUST_RSRVD) { 338 if ((*cp1 != CLUST_FREE && *cp1 < CLUST_BAD 339 && *cp2 != CLUST_FREE && *cp2 < CLUST_BAD) 340 || (*cp1 > CLUST_BAD && *cp2 > CLUST_BAD)) { 341 pwarn("Cluster %u is marked %s with different indicators\n", 342 cl, rsrvdcltype(*cp1)); 343 if (ask(1, "Fix")) { 344 *cp2 = *cp1; 345 return FSFATMOD; 346 } 347 return FSFATAL; 348 } 349 pwarn("Cluster %u is marked %s in FAT 0, %s in FAT %d\n", 350 cl, rsrvdcltype(*cp1), rsrvdcltype(*cp2), fatnum); 351 if (ask(0, "Use FAT 0's entry")) { 352 *cp2 = *cp1; 353 return FSFATMOD; 354 } 355 if (ask(0, "Use FAT %d's entry", fatnum)) { 356 *cp1 = *cp2; 357 return FSFATMOD; 358 } 359 return FSFATAL; 360 } 361 pwarn("Cluster %u is marked %s in FAT 0, but continues with cluster %u in FAT %d\n", 362 cl, rsrvdcltype(*cp1), *cp2, fatnum); 363 if (ask(0, "Use continuation from FAT %d", fatnum)) { 364 *cp1 = *cp2; 365 return FSFATMOD; 366 } 367 if (ask(0, "Use mark from FAT 0")) { 368 *cp2 = *cp1; 369 return FSFATMOD; 370 } 371 return FSFATAL; 372 } 373 if (*cp2 == CLUST_FREE || *cp2 >= CLUST_RSRVD) { 374 pwarn("Cluster %u continues with cluster %u in FAT 0, but is marked %s in FAT %d\n", 375 cl, *cp1, rsrvdcltype(*cp2), fatnum); 376 if (ask(0, "Use continuation from FAT 0")) { 377 *cp2 = *cp1; 378 return FSFATMOD; 379 } 380 if (ask(0, "Use mark from FAT %d", fatnum)) { 381 *cp1 = *cp2; 382 return FSFATMOD; 383 } 384 return FSERROR; 385 } 386 pwarn("Cluster %u continues with cluster %u in FAT 0, but with cluster %u in FAT %d\n", 387 cl, *cp1, *cp2, fatnum); 388 if (ask(0, "Use continuation from FAT 0")) { 389 *cp2 = *cp1; 390 return FSFATMOD; 391 } 392 if (ask(0, "Use continuation from FAT %d", fatnum)) { 393 *cp1 = *cp2; 394 return FSFATMOD; 395 } 396 return FSERROR; 397 } 398 399 /* 400 * Compare two FAT copies in memory. Resolve any conflicts and merge them 401 * into the first one. 402 */ 403 int 404 comparefat(struct bootblock *boot, struct fatEntry *first, 405 struct fatEntry *second, int fatnum) 406 { 407 cl_t cl; 408 int ret = FSOK; 409 410 for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++) 411 if (first[cl].next != second[cl].next) 412 ret |= clustdiffer(cl, &first[cl].next, &second[cl].next, fatnum); 413 return ret; 414 } 415 416 void 417 clearchain(struct bootblock *boot, struct fatEntry *fat, cl_t head) 418 { 419 cl_t p, q; 420 421 for (p = head; p >= CLUST_FIRST && p < boot->NumClusters; p = q) { 422 if (fat[p].head != head) 423 break; 424 q = fat[p].next; 425 fat[p].next = fat[p].head = CLUST_FREE; 426 fat[p].length = 0; 427 } 428 } 429 430 int 431 tryclear(struct bootblock *boot, struct fatEntry *fat, cl_t head, cl_t *trunc) 432 { 433 if (ask(0, "Clear chain starting at %u", head)) { 434 clearchain(boot, fat, head); 435 return FSFATMOD; 436 } else if (ask(0, "Truncate")) { 437 *trunc = CLUST_EOF; 438 return FSFATMOD; 439 } else 440 return FSERROR; 441 } 442 443 /* 444 * Check a complete FAT in-memory for crosslinks 445 */ 446 int 447 checkfat(struct bootblock *boot, struct fatEntry *fat) 448 { 449 cl_t head, p, h, n; 450 u_int len; 451 int ret = 0; 452 int conf; 453 454 /* 455 * pass 1: figure out the cluster chains. 456 */ 457 for (head = CLUST_FIRST; head < boot->NumClusters; head++) { 458 /* find next untravelled chain */ 459 if (fat[head].head != 0 /* cluster already belongs to some chain */ 460 || fat[head].next == CLUST_FREE 461 || fat[head].next == CLUST_BAD) 462 continue; /* skip it. */ 463 464 /* follow the chain and mark all clusters on the way */ 465 for (len = 0, p = head; 466 p >= CLUST_FIRST && p < boot->NumClusters; 467 p = fat[p].next) { 468 fat[p].head = head; 469 len++; 470 } 471 472 /* the head record gets the length */ 473 fat[head].length = fat[head].next == CLUST_FREE ? 0 : len; 474 } 475 476 /* 477 * pass 2: check for crosslinked chains (we couldn't do this in pass 1 because 478 * we didn't know the real start of the chain then - would have treated partial 479 * chains as interlinked with their main chain) 480 */ 481 for (head = CLUST_FIRST; head < boot->NumClusters; head++) { 482 /* find next untravelled chain */ 483 if (fat[head].head != head) 484 continue; 485 486 /* follow the chain to its end (hopefully) */ 487 for (p = head; 488 (n = fat[p].next) >= CLUST_FIRST && n < boot->NumClusters; 489 p = n) 490 if (fat[n].head != head) 491 break; 492 if (n >= CLUST_EOFS) 493 continue; 494 495 if (n == CLUST_FREE || n >= CLUST_RSRVD) { 496 pwarn("Cluster chain starting at %u ends with cluster marked %s\n", 497 head, rsrvdcltype(n)); 498 ret |= tryclear(boot, fat, head, &fat[p].next); 499 continue; 500 } 501 if (n < CLUST_FIRST || n >= boot->NumClusters) { 502 pwarn("Cluster chain starting at %u ends with cluster out of range (%u)\n", 503 head, n); 504 ret |= tryclear(boot, fat, head, &fat[p].next); 505 continue; 506 } 507 pwarn("Cluster chains starting at %u and %u are linked at cluster %u\n", 508 head, fat[n].head, n); 509 conf = tryclear(boot, fat, head, &fat[p].next); 510 if (ask(0, "Clear chain starting at %u", h = fat[n].head)) { 511 if (conf == FSERROR) { 512 /* 513 * Transfer the common chain to the one not cleared above. 514 */ 515 for (p = n; 516 p >= CLUST_FIRST && p < boot->NumClusters; 517 p = fat[p].next) { 518 if (h != fat[p].head) { 519 /* 520 * Have to reexamine this chain. 521 */ 522 head--; 523 break; 524 } 525 fat[p].head = head; 526 } 527 } 528 clearchain(boot, fat, h); 529 conf |= FSFATMOD; 530 } 531 ret |= conf; 532 } 533 534 return ret; 535 } 536 537 /* 538 * Write out FATs encoding them from the internal format 539 */ 540 int 541 writefat(int fs, struct bootblock *boot, struct fatEntry *fat, int correct_fat) 542 { 543 u_char *buffer, *p; 544 cl_t cl; 545 int i; 546 u_int32_t fatsz; 547 off_t off; 548 int ret = FSOK; 549 550 buffer = malloc(fatsz = boot->FATsecs * boot->BytesPerSec); 551 if (buffer == NULL) { 552 perror("No space for FAT"); 553 return FSFATAL; 554 } 555 memset(buffer, 0, fatsz); 556 boot->NumFree = 0; 557 p = buffer; 558 if (correct_fat) { 559 *p++ = (u_char)boot->Media; 560 *p++ = 0xff; 561 *p++ = 0xff; 562 switch (boot->ClustMask) { 563 case CLUST16_MASK: 564 *p++ = 0xff; 565 break; 566 case CLUST32_MASK: 567 *p++ = 0x0f; 568 *p++ = 0xff; 569 *p++ = 0xff; 570 *p++ = 0xff; 571 *p++ = 0x0f; 572 break; 573 } 574 } else { 575 /* use same FAT signature as the old FAT has */ 576 int count; 577 u_char *old_fat; 578 579 switch (boot->ClustMask) { 580 case CLUST32_MASK: 581 count = 8; 582 break; 583 case CLUST16_MASK: 584 count = 4; 585 break; 586 default: 587 count = 3; 588 break; 589 } 590 591 if (!_readfat(fs, boot, boot->ValidFat >= 0 ? boot->ValidFat :0, 592 &old_fat)) { 593 free(buffer); 594 return FSFATAL; 595 } 596 597 memcpy(p, old_fat, count); 598 free(old_fat); 599 p += count; 600 } 601 602 for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++) { 603 switch (boot->ClustMask) { 604 case CLUST32_MASK: 605 if (fat[cl].next == CLUST_FREE) 606 boot->NumFree++; 607 *p++ = (u_char)fat[cl].next; 608 *p++ = (u_char)(fat[cl].next >> 8); 609 *p++ = (u_char)(fat[cl].next >> 16); 610 *p &= 0xf0; 611 *p++ |= (fat[cl].next >> 24)&0x0f; 612 break; 613 case CLUST16_MASK: 614 if (fat[cl].next == CLUST_FREE) 615 boot->NumFree++; 616 *p++ = (u_char)fat[cl].next; 617 *p++ = (u_char)(fat[cl].next >> 8); 618 break; 619 default: 620 if (fat[cl].next == CLUST_FREE) 621 boot->NumFree++; 622 if (cl + 1 < boot->NumClusters 623 && fat[cl + 1].next == CLUST_FREE) 624 boot->NumFree++; 625 *p++ = (u_char)fat[cl].next; 626 *p++ = (u_char)((fat[cl].next >> 8) & 0xf) 627 |(u_char)(fat[cl+1].next << 4); 628 *p++ = (u_char)(fat[++cl].next >> 4); 629 break; 630 } 631 } 632 for (i = 0; i < boot->FATs; i++) { 633 off = boot->ResSectors + i * boot->FATsecs; 634 off *= boot->BytesPerSec; 635 if (lseek(fs, off, SEEK_SET) != off 636 || write(fs, buffer, fatsz) != fatsz) { 637 perror("Unable to write FAT"); 638 ret = FSFATAL; /* Return immediately? XXX */ 639 } 640 } 641 free(buffer); 642 return ret; 643 } 644 645 /* 646 * Check a complete in-memory FAT for lost cluster chains 647 */ 648 int 649 checklost(int dosfs, struct bootblock *boot, struct fatEntry *fat) 650 { 651 cl_t head; 652 int mod = FSOK; 653 int ret; 654 655 for (head = CLUST_FIRST; head < boot->NumClusters; head++) { 656 /* find next untravelled chain */ 657 if (fat[head].head != head 658 || fat[head].next == CLUST_FREE 659 || (fat[head].next >= CLUST_RSRVD 660 && fat[head].next < CLUST_EOFS) 661 || (fat[head].flags & FAT_USED)) 662 continue; 663 664 pwarn("Lost cluster chain at cluster %u\n%d Cluster(s) lost\n", 665 head, fat[head].length); 666 mod |= ret = reconnect(dosfs, boot, fat, head); 667 if (mod & FSFATAL) 668 break; 669 if (ret == FSERROR && ask(0, "Clear")) { 670 clearchain(boot, fat, head); 671 mod |= FSFATMOD; 672 } 673 } 674 finishlf(); 675 676 if (boot->FSInfo) { 677 ret = 0; 678 if (boot->FSFree != boot->NumFree) { 679 pwarn("Free space in FSInfo block (%d) not correct (%d)\n", 680 boot->FSFree, boot->NumFree); 681 if (ask(1, "Fix")) { 682 boot->FSFree = boot->NumFree; 683 ret = 1; 684 } 685 } 686 if (boot->NumFree && fat[boot->FSNext].next != CLUST_FREE) { 687 pwarn("Next free cluster in FSInfo block (%u) not free\n", 688 boot->FSNext); 689 if (ask(1, "Fix")) 690 for (head = CLUST_FIRST; head < boot->NumClusters; head++) 691 if (fat[head].next == CLUST_FREE) { 692 boot->FSNext = head; 693 ret = 1; 694 break; 695 } 696 } 697 if (ret) 698 mod |= writefsinfo(dosfs, boot); 699 } 700 701 return mod; 702 } 703