1 /* $NetBSD: dosfs.c,v 1.23 2020/01/22 06:11:28 martin Exp $ */ 2 3 /* 4 * Copyright (c) 1996, 1998 Robert Nordier 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in 14 * the documentation and/or other materials provided with the 15 * distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS 18 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 23 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 25 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 27 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 /* 31 * Readonly filesystem for Microsoft FAT12/FAT16/FAT32 filesystems, 32 * also supports VFAT. 33 */ 34 35 /* 36 * XXX DOES NOT SUPPORT: 37 * 38 * LIBSA_FS_SINGLECOMPONENT 39 */ 40 41 #include <sys/param.h> 42 43 #include <fs/msdosfs/bpb.h> 44 #include <fs/msdosfs/direntry.h> 45 46 #ifdef _STANDALONE 47 #include <lib/libkern/libkern.h> 48 #else 49 #include <string.h> 50 #include <stddef.h> 51 #endif 52 53 #include "stand.h" 54 #include "dosfs.h" 55 56 #define SECSIZ 512 /* sector size */ 57 #define SSHIFT 9 /* SECSIZ shift */ 58 #define DEPSEC 16 /* directory entries per sector */ 59 #define DSHIFT 4 /* DEPSEC shift */ 60 #define LOCLUS 2 /* lowest cluster number */ 61 62 typedef union { 63 struct direntry de; /* standard directory entry */ 64 struct winentry xde; /* extended directory entry */ 65 } DOS_DIR; 66 67 typedef struct { 68 struct open_file *fd; /* file descriptor */ 69 u_char *buf; /* buffer */ 70 u_int bufsec; /* buffered sector */ 71 u_int links; /* active links to structure */ 72 u_int spc; /* sectors per cluster */ 73 u_int bsize; /* cluster size in bytes */ 74 u_int bshift; /* cluster conversion shift */ 75 u_int dirents; /* root directory entries */ 76 u_int spf; /* sectors per fat */ 77 u_int rdcl; /* root directory start cluster */ 78 u_int lsnfat; /* start of fat */ 79 u_int lsndir; /* start of root dir */ 80 u_int lsndta; /* start of data area */ 81 u_int fatsz; /* FAT entry size */ 82 u_int xclus; /* maximum cluster number */ 83 } DOS_FS; 84 85 typedef struct { 86 DOS_FS *fs; /* associated filesystem */ 87 struct direntry de; /* directory entry */ 88 u_int offset; /* current offset */ 89 u_int c; /* last cluster read */ 90 } DOS_FILE; 91 92 /* Initial portion of DOS boot sector */ 93 typedef struct { 94 u_char jmp[3]; /* usually 80x86 'jmp' opcode */ 95 u_char oem[8]; /* OEM name and version */ 96 struct byte_bpb710 bpb; /* BPB */ 97 } DOS_BS; 98 99 /* Supply missing "." and ".." root directory entries */ 100 static const char *const dotstr[2] = {".", ".."}; 101 static const struct direntry dot[2] = { 102 {". ", " ", ATTR_DIRECTORY, 103 0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}, 104 {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}}, 105 106 {".. ", " ", ATTR_DIRECTORY, 107 0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}, 108 {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}} 109 }; 110 111 #ifdef SA_DOSFS_NO_BIG_PART_SUPPORT 112 #define BYTE_OFF_T u_int 113 #else 114 #define BYTE_OFF_T uint64_t 115 #endif 116 117 /* The usual conversion macros to avoid multiplication and division */ 118 #define bytsec(n) ((n) >> SSHIFT) 119 #define secbyt(s) ((BYTE_OFF_T)(s) << SSHIFT) 120 #define entsec(e) ((e) >> DSHIFT) 121 #define bytblk(fs, n) ((n) >> (fs)->bshift) 122 #define blkbyt(fs, b) ((BYTE_OFF_T)(b) << (fs)->bshift) 123 #define secblk(fs, s) ((s) >> ((fs)->bshift - SSHIFT)) 124 #define blksec(fs, b) ((b) << ((fs)->bshift - SSHIFT)) 125 126 /* Convert cluster number to offset within filesystem */ 127 #define blkoff(fs, b) (secbyt((fs)->lsndta) + blkbyt(fs, (b) - LOCLUS)) 128 129 /* Convert cluster number to logical sector number */ 130 #define blklsn(fs, b) ((fs)->lsndta + blksec(fs, (b) - LOCLUS)) 131 132 /* Convert cluster number to offset within FAT */ 133 #define fatoff(sz, c) ((sz) == 12 ? (c) + ((c) >> 1) : \ 134 (sz) == 16 ? (c) << 1 : \ 135 (c) << 2) 136 137 /* Does cluster number reference a valid data cluster? */ 138 #define okclus(fs, c) ((c) >= LOCLUS && (c) <= (fs)->xclus) 139 140 /* Get start cluster from directory entry */ 141 #define stclus(sz, de) ((sz) != 32 ? (u_int)getushort((de)->deStartCluster) : \ 142 ((u_int)getushort((de)->deHighClust) << 16) | \ 143 (u_int)getushort((de)->deStartCluster)) 144 145 static int dosunmount(DOS_FS *); 146 static int parsebs(DOS_FS *, DOS_BS *); 147 static int namede(DOS_FS *, const char *, const struct direntry **); 148 static int lookup(DOS_FS *, u_int, const char *, const struct direntry **); 149 static void cp_xdnm(u_char *, struct winentry *); 150 static void cp_sfn(u_char *, struct direntry *); 151 static off_t fsize(DOS_FS *, struct direntry *); 152 static int fatcnt(DOS_FS *, u_int); 153 static int fatget(DOS_FS *, u_int *); 154 static int fatend(u_int, u_int); 155 static int ioread(DOS_FS *, BYTE_OFF_T, void *, u_int); 156 static int iobuf(DOS_FS *, u_int); 157 static int ioget(struct open_file *, u_int, void *, u_int); 158 159 #define strcasecmp(s1, s2) dos_strcasecmp(s1, s2) 160 static int 161 strcasecmp(const char *s1, const char *s2) 162 { 163 char c1, c2; 164 #define TO_UPPER(c) ((c) >= 'a' && (c) <= 'z' ? (c) - ('a' - 'A') : (c)) 165 for (;;) { 166 c1 = *s1++; 167 c2 = *s2++; 168 if (TO_UPPER(c1) != TO_UPPER(c2)) 169 return 1; 170 if (c1 == 0) 171 return 0; 172 } 173 #undef TO_UPPER 174 } 175 176 /* 177 * Mount DOS filesystem 178 */ 179 static int 180 dos_mount(DOS_FS *fs, struct open_file *fd) 181 { 182 int err; 183 184 (void)memset(fs, 0, sizeof(DOS_FS)); 185 fs->fd = fd; 186 if ((err = !(fs->buf = alloc(SECSIZ)) ? errno : 0) || 187 (err = ioget(fs->fd, 0, fs->buf, 1)) || 188 (err = parsebs(fs, (DOS_BS *)fs->buf))) { 189 (void) dosunmount(fs); 190 return err; 191 } 192 return 0; 193 } 194 195 #ifndef LIBSA_NO_FS_CLOSE 196 /* 197 * Unmount mounted filesystem 198 */ 199 static int 200 dos_unmount(DOS_FS *fs) 201 { 202 int err; 203 204 if (fs->links) 205 return EBUSY; 206 if ((err = dosunmount(fs))) 207 return err; 208 return 0; 209 } 210 #endif 211 212 /* 213 * Common code shared by dos_mount() and dos_unmount() 214 */ 215 static int 216 dosunmount(DOS_FS *fs) 217 { 218 if (fs->buf) 219 dealloc(fs->buf, SECSIZ); 220 dealloc(fs, sizeof(DOS_FS)); 221 return 0; 222 } 223 224 /* 225 * Open DOS file 226 */ 227 __compactcall int 228 dosfs_open(const char *path, struct open_file *fd) 229 { 230 const struct direntry *de; 231 DOS_FILE *f; 232 DOS_FS *fs; 233 u_int size, clus; 234 int err = 0; 235 236 /* Allocate mount structure, associate with open */ 237 fs = alloc(sizeof(DOS_FS)); 238 239 if ((err = dos_mount(fs, fd))) 240 goto out; 241 242 if ((err = namede(fs, path, &de))) 243 goto out; 244 245 clus = stclus(fs->fatsz, de); 246 size = getulong(de->deFileSize); 247 248 if ((!(de->deAttributes & ATTR_DIRECTORY) && (!clus != !size)) || 249 ((de->deAttributes & ATTR_DIRECTORY) && size) || 250 (clus && !okclus(fs, clus))) { 251 err = EINVAL; 252 goto out; 253 } 254 255 f = alloc(sizeof(DOS_FILE)); 256 #ifdef BOOTXX 257 /* due to __internal_memset_ causing all sorts of register spillage 258 (and being completely unoptimized for zeroing small amounts of 259 memory), if we hand-initialize the remaining members of f to zero, 260 the code size drops 68 bytes. This makes no sense, admittedly. */ 261 f->offset = 0; 262 f->c = 0; 263 #else 264 (void)memset(f, 0, sizeof(DOS_FILE)); 265 #endif 266 f->fs = fs; 267 fs->links++; 268 f->de = *de; 269 fd->f_fsdata = (void *)f; 270 fsmod = "msdos"; 271 272 out: 273 return err; 274 } 275 276 /* 277 * Read from file 278 */ 279 __compactcall int 280 dosfs_read(struct open_file *fd, void *vbuf, size_t nbyte, size_t *resid) 281 { 282 off_t size; 283 u_int8_t *buf = vbuf; 284 u_int nb, off, clus, c, cnt, n; 285 DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; 286 int err = 0; 287 288 nb = (u_int) nbyte; 289 if ((size = fsize(f->fs, &f->de)) == -1) 290 return EINVAL; 291 n = (u_int)(size - f->offset); 292 if (nb > n) 293 nb = n; 294 off = f->offset; 295 if ((clus = stclus(f->fs->fatsz, &f->de))) 296 off &= f->fs->bsize - 1; 297 c = f->c; 298 cnt = nb; 299 while (cnt) { 300 n = 0; 301 if (!c) { 302 if ((c = clus)) 303 n = bytblk(f->fs, f->offset); 304 } else if (!off) { 305 n++; 306 } 307 while (n--) { 308 if ((err = fatget(f->fs, &c))) 309 goto out; 310 if (!okclus(f->fs, c)) { 311 err = EINVAL; 312 goto out; 313 } 314 } 315 if (!clus || (n = f->fs->bsize - off) > cnt) 316 n = cnt; 317 if ((err = ioread(f->fs, (c ? blkoff(f->fs, c) : 318 secbyt(f->fs->lsndir)) + off, 319 buf, n))) 320 goto out; 321 f->offset += n; 322 f->c = c; 323 off = 0; 324 buf += n; 325 cnt -= n; 326 } 327 out: 328 if (resid) 329 *resid = nbyte - nb + cnt; 330 return err; 331 } 332 333 #ifndef LIBSA_NO_FS_WRITE 334 /* 335 * Not implemented. 336 */ 337 __compactcall int 338 dosfs_write(struct open_file *fd, void *start, size_t size, size_t *resid) 339 { 340 341 return EROFS; 342 } 343 #endif /* !LIBSA_NO_FS_WRITE */ 344 345 #ifndef LIBSA_NO_FS_SEEK 346 /* 347 * Reposition within file 348 */ 349 __compactcall off_t 350 dosfs_seek(struct open_file *fd, off_t offset, int whence) 351 { 352 off_t off; 353 u_int size; 354 DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; 355 356 size = getulong(f->de.deFileSize); 357 switch (whence) { 358 case SEEK_SET: 359 off = 0; 360 break; 361 case SEEK_CUR: 362 off = f->offset; 363 break; 364 case SEEK_END: 365 off = size; 366 break; 367 default: 368 return -1; 369 } 370 off += offset; 371 if (off < 0 || off > size) 372 return -1; 373 f->offset = (u_int) off; 374 f->c = 0; 375 return off; 376 } 377 #endif /* !LIBSA_NO_FS_SEEK */ 378 379 #ifndef LIBSA_NO_FS_CLOSE 380 /* 381 * Close open file 382 */ 383 __compactcall int 384 dosfs_close(struct open_file *fd) 385 { 386 DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; 387 DOS_FS *fs = f->fs; 388 389 f->fs->links--; 390 dealloc(f, sizeof(DOS_FILE)); 391 dos_unmount(fs); 392 return 0; 393 } 394 #endif /* !LIBSA_NO_FS_CLOSE */ 395 396 /* 397 * Return some stat information on a file. 398 */ 399 __compactcall int 400 dosfs_stat(struct open_file *fd, struct stat *sb) 401 { 402 DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; 403 404 /* only important stuff */ 405 sb->st_mode = (f->de.deAttributes & ATTR_DIRECTORY) ? 406 (S_IFDIR | 0555) : (S_IFREG | 0444); 407 sb->st_nlink = 1; 408 sb->st_uid = 0; 409 sb->st_gid = 0; 410 if ((sb->st_size = fsize(f->fs, &f->de)) == -1) 411 return EINVAL; 412 return 0; 413 } 414 415 #if defined(LIBSA_ENABLE_LS_OP) 416 #include "ls.h" 417 __compactcall void 418 dosfs_ls(struct open_file *f, const char *pattern) 419 { 420 lsunsup("dosfs"); 421 } 422 #endif 423 424 /* 425 * Parse DOS boot sector 426 */ 427 static int 428 parsebs(DOS_FS *fs, DOS_BS *bs) 429 { 430 u_int sc; 431 432 if ((bs->jmp[0] != 0x69 && 433 bs->jmp[0] != 0xe9 && 434 (bs->jmp[0] != 0xeb || bs->jmp[2] != 0x90)) || 435 bs->bpb.bpbMedia < 0xf0) 436 return EINVAL; 437 if (getushort(bs->bpb.bpbBytesPerSec) != SECSIZ) 438 return EINVAL; 439 if (!(fs->spc = bs->bpb.bpbSecPerClust) || fs->spc & (fs->spc - 1)) 440 return EINVAL; 441 fs->bsize = secbyt(fs->spc); 442 fs->bshift = (u_int)ffs((int)fs->bsize) - 1; 443 if ((fs->spf = getushort(bs->bpb.bpbFATsecs))) { 444 if (bs->bpb.bpbFATs != 2) 445 return EINVAL; 446 if (!(fs->dirents = getushort(bs->bpb.bpbRootDirEnts))) 447 return EINVAL; 448 } else { 449 if (!(fs->spf = getulong(bs->bpb.bpbBigFATsecs))) 450 return EINVAL; 451 if (!bs->bpb.bpbFATs || bs->bpb.bpbFATs > 16) 452 return EINVAL; 453 if ((fs->rdcl = getulong(bs->bpb.bpbRootClust)) < LOCLUS) 454 return EINVAL; 455 } 456 if (!(fs->lsnfat = getushort(bs->bpb.bpbResSectors))) 457 return EINVAL; 458 fs->lsndir = fs->lsnfat + fs->spf * bs->bpb.bpbFATs; 459 fs->lsndta = fs->lsndir + entsec(fs->dirents); 460 if (!(sc = getushort(bs->bpb.bpbSectors)) && 461 !(sc = getulong(bs->bpb.bpbHugeSectors))) 462 return EINVAL; 463 if (fs->lsndta > sc) 464 return EINVAL; 465 if ((fs->xclus = secblk(fs, sc - fs->lsndta) + 1) < LOCLUS) 466 return EINVAL; 467 fs->fatsz = fs->dirents ? fs->xclus < 0xff6 ? 12 : 16 : 32; 468 sc = (secbyt(fs->spf) << 1) / (fs->fatsz >> 2) - 1; 469 if (fs->xclus > sc) 470 fs->xclus = sc; 471 return 0; 472 } 473 474 /* 475 * Return directory entry from path 476 */ 477 static int 478 namede(DOS_FS *fs, const char *path, const struct direntry **dep) 479 { 480 char name[256]; 481 const struct direntry *de; 482 char *s; 483 size_t n; 484 int err; 485 486 err = 0; 487 de = dot; 488 if (*path == '/') 489 path++; 490 while (*path) { 491 if (!(s = strchr(path, '/'))) 492 s = strchr(path, 0); 493 n = (size_t)(s - path); 494 if (n > 255) 495 return ENAMETOOLONG; 496 memcpy(name, path, n); 497 name[n] = 0; 498 path = s; 499 if (!(de->deAttributes & ATTR_DIRECTORY)) 500 return ENOTDIR; 501 if ((err = lookup(fs, stclus(fs->fatsz, de), name, &de))) 502 return err; 503 if (*path == '/') 504 path++; 505 } 506 *dep = de; 507 return 0; 508 } 509 510 /* 511 * Lookup path segment 512 */ 513 static int 514 lookup(DOS_FS *fs, u_int clus, const char *name, const struct direntry **dep) 515 { 516 static DOS_DIR *dir = NULL; 517 u_char lfn[261]; 518 u_char sfn[13]; 519 u_int nsec, lsec, xdn, chk, sec, ent, x; 520 int err = 0, ok, i; 521 522 if (!clus) 523 for (ent = 0; ent < 2; ent++) 524 if (!strcasecmp(name, dotstr[ent])) { 525 *dep = dot + ent; 526 return 0; 527 } 528 529 if (dir == NULL) { 530 dir = alloc(sizeof(DOS_DIR) * DEPSEC); 531 if (dir == NULL) 532 return ENOMEM; 533 } 534 535 if (!clus && fs->fatsz == 32) 536 clus = fs->rdcl; 537 nsec = !clus ? entsec(fs->dirents) : fs->spc; 538 lsec = 0; 539 xdn = chk = 0; 540 for (;;) { 541 if (!clus && !lsec) 542 lsec = fs->lsndir; 543 else if (okclus(fs, clus)) 544 lsec = blklsn(fs, clus); 545 else { 546 err = EINVAL; 547 goto out; 548 } 549 for (sec = 0; sec < nsec; sec++) { 550 if ((err = ioget(fs->fd, lsec + sec, dir, 1))) 551 goto out; 552 for (ent = 0; ent < DEPSEC; ent++) { 553 if (!*dir[ent].de.deName) { 554 err = ENOENT; 555 goto out; 556 } 557 if (*dir[ent].de.deName != 0xe5) { 558 if (dir[ent].de.deAttributes == 559 ATTR_WIN95) { 560 x = dir[ent].xde.weCnt; 561 if (x & WIN_LAST || 562 (x + 1 == xdn && 563 dir[ent].xde.weChksum == 564 chk)) { 565 if (x & WIN_LAST) { 566 chk = dir[ent].xde.weChksum; 567 x &= WIN_CNT; 568 } 569 if (x >= 1 && x <= 20) { 570 cp_xdnm(lfn, &dir[ent].xde); 571 xdn = x; 572 continue; 573 } 574 } 575 } else if (!(dir[ent].de.deAttributes & 576 ATTR_VOLUME)) { 577 ok = xdn == 1; 578 if (ok) { 579 for (x = 0, i = 0; 580 i < 11; i++) 581 x = ((((x & 1) << 7) | (x >> 1)) + 582 (size_t)msdos_dirchar(&dir[ent].de,(size_t)i)) & 0xff; 583 ok = chk == x && 584 !strcasecmp(name, (const char *)lfn); 585 } 586 if (!ok) { 587 cp_sfn(sfn, &dir[ent].de); 588 ok = !strcasecmp(name, (const char *)sfn); 589 } 590 if (ok) { 591 *dep = &dir[ent].de; 592 goto out2; 593 } 594 } 595 } 596 xdn = 0; 597 } 598 } 599 if (!clus) 600 break; 601 if ((err = fatget(fs, &clus))) 602 goto out; 603 if (fatend(fs->fatsz, clus)) 604 break; 605 } 606 err = ENOENT; 607 out: 608 dealloc(dir, sizeof(DOS_DIR) * DEPSEC); 609 dir = NULL; 610 out2: 611 return err; 612 } 613 614 /* 615 * Copy name from extended directory entry 616 */ 617 static void 618 cp_xdnm(u_char *lfn, struct winentry *xde) 619 { 620 static const struct { 621 u_int off; 622 u_int dim; 623 } ix[3] = { 624 { offsetof(struct winentry, wePart1), 625 sizeof(xde->wePart1) / 2 }, 626 { offsetof(struct winentry, wePart2), 627 sizeof(xde->wePart2) / 2 }, 628 { offsetof(struct winentry, wePart3), 629 sizeof(xde->wePart3) / 2 } 630 }; 631 u_char *p; 632 u_int n, x, c; 633 634 lfn += 13 * ((xde->weCnt & WIN_CNT) - 1); 635 for (n = 0; n < 3; n++) 636 for (p = (u_char *)xde + ix[n].off, x = ix[n].dim; x; 637 p += 2, x--) { 638 if ((c = getushort(p)) && (c < 32 || c > 127)) 639 c = '?'; 640 if (!(*lfn++ = (u_char)c)) 641 return; 642 } 643 if (xde->weCnt & WIN_LAST) 644 *lfn = 0; 645 } 646 647 /* 648 * Copy short filename 649 */ 650 static void 651 cp_sfn(u_char *sfn, struct direntry *de) 652 { 653 u_char *p; 654 int j, i; 655 656 p = sfn; 657 if (*de->deName != ' ') { 658 for (j = 7; de->deName[j] == ' '; j--); 659 for (i = 0; i <= j; i++) 660 *p++ = de->deName[i]; 661 if (*de->deExtension != ' ') { 662 *p++ = '.'; 663 for (j = 2; de->deExtension[j] == ' '; j--); 664 for (i = 0; i <= j; i++) 665 *p++ = de->deExtension[i]; 666 } 667 } 668 *p = 0; 669 if (*sfn == 5) 670 *sfn = 0xe5; 671 } 672 673 /* 674 * Return size of file in bytes 675 */ 676 static off_t 677 fsize(DOS_FS *fs, struct direntry *de) 678 { 679 size_t size; 680 u_int c; 681 int n; 682 683 if (!(size = getulong(de->deFileSize)) && 684 de->deAttributes & ATTR_DIRECTORY) { 685 if (!(c = getushort(de->deStartCluster))) { 686 size = fs->dirents * sizeof(struct direntry); 687 } else { 688 if ((n = fatcnt(fs, c)) == -1) 689 return n; 690 size = (size_t)blkbyt(fs, n); 691 } 692 } 693 return (off_t)size; 694 } 695 696 /* 697 * Count number of clusters in chain 698 */ 699 static int 700 fatcnt(DOS_FS *fs, u_int c) 701 { 702 int n; 703 704 for (n = 0; okclus(fs, c); n++) 705 if (fatget(fs, &c)) 706 return -1; 707 return fatend(fs->fatsz, c) ? n : -1; 708 } 709 710 /* 711 * Get next cluster in cluster chain 712 */ 713 static int 714 fatget(DOS_FS *fs, u_int *c) 715 { 716 u_char buf[4]; 717 u_int x; 718 int err; 719 720 err = ioread(fs, secbyt(fs->lsnfat) + fatoff(fs->fatsz, *c), buf, 721 fs->fatsz != 32 ? 2 : 4); 722 if (err) 723 return err; 724 x = fs->fatsz != 32 ? getushort(buf) : getulong(buf); 725 *c = fs->fatsz == 12 ? *c & 1 ? x >> 4 : x & 0xfff : x; 726 return 0; 727 } 728 729 /* 730 * Is cluster an end-of-chain marker? 731 */ 732 static int 733 fatend(u_int sz, u_int c) 734 { 735 return c > (sz == 12 ? 0xff7U : sz == 16 ? 0xfff7U : 0xffffff7); 736 } 737 738 /* 739 * Offset-based I/O primitive 740 */ 741 static int 742 ioread(DOS_FS *fs, BYTE_OFF_T offset, void *buf, u_int nbyte) 743 { 744 char *s; 745 u_int off, n; 746 int err; 747 748 s = buf; 749 if ((off = offset & (SECSIZ - 1))) { 750 offset -= off; 751 if ((err = iobuf(fs, bytsec(offset)))) 752 return err; 753 offset += SECSIZ; 754 if ((n = SECSIZ - off) > nbyte) 755 n = nbyte; 756 memcpy(s, fs->buf + off, n); 757 s += n; 758 nbyte -= n; 759 } 760 n = nbyte & (SECSIZ - 1); 761 if (nbyte -= n) { 762 if ((err = ioget(fs->fd, bytsec(offset), s, bytsec(nbyte)))) 763 return err; 764 offset += nbyte; 765 s += nbyte; 766 } 767 if (n) { 768 if ((err = iobuf(fs, bytsec(offset)))) 769 return err; 770 memcpy(s, fs->buf, n); 771 } 772 return 0; 773 } 774 775 /* 776 * Buffered sector-based I/O primitive 777 */ 778 static int 779 iobuf(DOS_FS *fs, u_int lsec) 780 { 781 int err; 782 783 if (fs->bufsec != lsec) { 784 if ((err = ioget(fs->fd, lsec, fs->buf, 1))) 785 return err; 786 fs->bufsec = lsec; 787 } 788 return 0; 789 } 790 791 /* 792 * Sector-based I/O primitive 793 */ 794 static int 795 ioget(struct open_file *fd, u_int lsec, void *buf, u_int nsec) 796 { 797 size_t rsize; 798 int err; 799 800 #ifndef LIBSA_NO_TWIDDLE 801 twiddle(); 802 #endif 803 err = DEV_STRATEGY(fd->f_dev)(fd->f_devdata, F_READ, lsec, 804 secbyt(nsec), buf, &rsize); 805 return err; 806 } 807