dosfs.c revision 40005
1/* 2 * Copyright (c) 1996, 1998 Robert Nordier 3 * All rights reserved. 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 12 * the documentation and/or other materials provided with the 13 * distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS 16 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY 19 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 21 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 23 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 25 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28/* 29 * Readonly filesystem for Microsoft FAT12/FAT16/FAT32 filesystems, 30 * also supports VFAT. 31 */ 32 33#include <sys/types.h> 34#include <string.h> 35#include <stddef.h> 36 37#include "stand.h" 38 39#include "dosfs.h" 40 41 42static int dos_open(const char *path, struct open_file *fd); 43static int dos_close(struct open_file *fd); 44static int dos_read(struct open_file *fd, void *buf, size_t size, size_t *resid); 45static off_t dos_seek(struct open_file *fd, off_t offset, int whence); 46static int dos_stat(struct open_file *fd, struct stat *sb); 47 48struct fs_ops dosfs_fsops = { 49 "dosfs", dos_open, dos_close, dos_read, null_write, dos_seek, dos_stat 50}; 51 52#define SECSIZ 512 /* sector size */ 53#define SSHIFT 9 /* SECSIZ shift */ 54#define DEPSEC 16 /* directory entries per sector */ 55#define DSHIFT 4 /* DEPSEC shift */ 56#define LOCLUS 2 /* lowest cluster number */ 57 58/* DOS "BIOS Parameter Block" */ 59typedef struct { 60 u_char secsiz[2]; /* sector size */ 61 u_char spc; /* sectors per cluster */ 62 u_char ressec[2]; /* reserved sectors */ 63 u_char fats; /* FATs */ 64 u_char dirents[2]; /* root directory entries */ 65 u_char secs[2]; /* total sectors */ 66 u_char media; /* media descriptor */ 67 u_char spf[2]; /* sectors per FAT */ 68 u_char spt[2]; /* sectors per track */ 69 u_char heads[2]; /* drive heads */ 70 u_char hidsec[4]; /* hidden sectors */ 71 u_char lsecs[4]; /* huge sectors */ 72 u_char lspf[4]; /* huge sectors per FAT */ 73 u_char xflg[2]; /* flags */ 74 u_char vers[2]; /* filesystem version */ 75 u_char rdcl[4]; /* root directory start cluster */ 76 u_char infs[2]; /* filesystem info sector */ 77 u_char bkbs[2]; /* backup boot sector */ 78} DOS_BPB; 79 80/* Initial portion of DOS boot sector */ 81typedef struct { 82 u_char jmp[3]; /* usually 80x86 'jmp' opcode */ 83 u_char oem[8]; /* OEM name and version */ 84 DOS_BPB bpb; /* BPB */ 85} DOS_BS; 86 87/* Supply missing "." and ".." root directory entries */ 88static const char *const dotstr[2] = {".", ".."}; 89static DOS_DE dot[2] = { 90 {". ", " ", FA_DIR, {0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, 91 {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}}, 92 {".. ", " ", FA_DIR, {0, 0, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, 93 {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0}} 94}; 95 96/* The usual conversion macros to avoid multiplication and division */ 97#define bytsec(n) ((n) >> SSHIFT) 98#define secbyt(s) ((s) << SSHIFT) 99#define entsec(e) ((e) >> DSHIFT) 100#define bytblk(fs, n) ((n) >> (fs)->bshift) 101#define blkbyt(fs, b) ((b) << (fs)->bshift) 102#define secblk(fs, s) ((s) >> ((fs)->bshift - SSHIFT)) 103#define blksec(fs, b) ((b) << ((fs)->bshift - SSHIFT)) 104 105/* Convert cluster number to offset within filesystem */ 106#define blkoff(fs, b) (secbyt((fs)->lsndta) + blkbyt(fs, (b) - LOCLUS)) 107 108/* Convert cluster number to logical sector number */ 109#define blklsn(fs, b) ((fs)->lsndta + blksec(fs, (b) - LOCLUS)) 110 111/* Convert cluster number to offset within FAT */ 112#define fatoff(sz, c) ((sz) == 12 ? (c) + ((c) >> 1) : \ 113 (sz) == 16 ? (c) << 1 : \ 114 (c) << 2) 115 116/* Does cluster number reference a valid data cluster? */ 117#define okclus(fs, c) ((c) >= LOCLUS && (c) <= (fs)->xclus) 118 119/* Get start cluster from directory entry */ 120#define stclus(sz, de) ((sz) != 32 ? cv2((de)->clus) : \ 121 ((u_int)cv2((de)->dex.h_clus) << 16) | \ 122 cv2((de)->clus)) 123 124static int dosunmount(DOS_FS *); 125static int parsebs(DOS_FS *, DOS_BS *); 126static int namede(DOS_FS *, const char *, DOS_DE **); 127static int lookup(DOS_FS *, u_int, const char *, DOS_DE **); 128static void cp_xdnm(u_char *, DOS_XDE *); 129static void cp_sfn(u_char *, DOS_DE *); 130static off_t fsize(DOS_FS *, DOS_DE *); 131static int fatcnt(DOS_FS *, u_int); 132static int fatget(DOS_FS *, u_int *); 133static int fatend(u_int, u_int); 134static int ioread(DOS_FS *, u_int, void *, u_int); 135static int iobuf(DOS_FS *, u_int); 136static int ioget(struct open_file *, u_int, void *, u_int); 137 138/* 139 * Mount DOS filesystem 140 */ 141static int 142dos_mount(DOS_FS *fs, struct open_file *fd) 143{ 144 int err; 145 146 bzero(fs, sizeof(DOS_FS)); 147 fs->fd = fd; 148 if ((err = !(fs->buf = malloc(SECSIZ)) ? errno : 0) || 149 (err = ioget(fs->fd, 0, fs->buf, 1)) || 150 (err = parsebs(fs, (DOS_BS *)fs->buf))) { 151 (void)dosunmount(fs); 152 return(err); 153 } 154 return 0; 155} 156 157/* 158 * Unmount mounted filesystem 159 */ 160static int 161dos_unmount(DOS_FS *fs) 162{ 163 int err; 164 165 if (fs->links) 166 return(EBUSY); 167 if ((err = dosunmount(fs))) 168 return(err); 169 return 0; 170} 171 172/* 173 * Common code shared by dos_mount() and dos_unmount() 174 */ 175static int 176dosunmount(DOS_FS *fs) 177{ 178 if (fs->buf) 179 free(fs->buf); 180 free(fs); 181 return(0); 182} 183 184/* 185 * Open DOS file 186 */ 187static int 188dos_open(const char *path, struct open_file *fd) 189{ 190 DOS_DE *de; 191 DOS_FILE *f; 192 DOS_FS *fs; 193 u_int size, clus; 194 int err = 0; 195 196 /* Allocate mount structure, associate with open */ 197 fs = malloc(sizeof(DOS_FS)); 198 199 if ((err = dos_mount(fs, fd))) 200 goto out; 201 202 if ((err = namede(fs, path, &de))) 203 goto out; 204 205 clus = stclus(fs->fatsz, de); 206 size = cv4(de->size); 207 208 if ((!(de->attr & FA_DIR) && (!clus != !size)) || 209 ((de->attr & FA_DIR) && size) || 210 (clus && !okclus(fs, clus))) { 211 err = EINVAL; 212 goto out; 213 } 214 f = malloc(sizeof(DOS_FILE)); 215 bzero(f, sizeof(DOS_FILE)); 216 f->fs = fs; 217 fs->links++; 218 f->de = *de; 219 fd->f_fsdata = (void *)f; 220 221 out: 222 return(err); 223} 224 225/* 226 * Read from file 227 */ 228static int 229dos_read(struct open_file *fd, void *buf, size_t nbyte, size_t *resid) 230{ 231 off_t size; 232 u_int nb, off, clus, c, cnt, n; 233 DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; 234 int err = 0; 235 236 nb = (u_int)nbyte; 237 if ((size = fsize(f->fs, &f->de)) == -1) 238 return EINVAL; 239 if (nb > (n = size - f->offset)) 240 nb = n; 241 off = f->offset; 242 if ((clus = stclus(f->fs->fatsz, &f->de))) 243 off &= f->fs->bsize - 1; 244 c = f->c; 245 cnt = nb; 246 while (cnt) { 247 n = 0; 248 if (!c) { 249 if ((c = clus)) 250 n = bytblk(f->fs, f->offset); 251 } else if (!off) 252 n++; 253 while (n--) { 254 if ((err = fatget(f->fs, &c))) 255 goto out; 256 if (!okclus(f->fs, c)) { 257 err = EINVAL; 258 goto out; 259 } 260 } 261 if (!clus || (n = f->fs->bsize - off) > cnt) 262 n = cnt; 263 if ((err = ioread(f->fs, (c ? blkoff(f->fs, c) : 264 secbyt(f->fs->lsndir)) + off, 265 buf, n))) 266 goto out; 267 f->offset += n; 268 f->c = c; 269 off = 0; 270 buf += n; 271 cnt -= n; 272 } 273 out: 274 if (resid) 275 *resid = nbyte - nb + cnt; 276 return(err); 277} 278 279/* 280 * Reposition within file 281 */ 282static off_t 283dos_seek(struct open_file *fd, off_t offset, int whence) 284{ 285 off_t off; 286 u_int size; 287 DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; 288 289 size = cv4(f->de.size); 290 switch (whence) { 291 case SEEK_SET: 292 off = 0; 293 break; 294 case SEEK_CUR: 295 off = f->offset; 296 break; 297 case SEEK_END: 298 off = size; 299 break; 300 default: 301 return(-1); 302 } 303 off += offset; 304 if (off < 0 || off > size) 305 return(-1); 306 f->offset = (u_int)off; 307 f->c = 0; 308 return(off); 309} 310 311/* 312 * Close open file 313 */ 314static int 315dos_close(struct open_file *fd) 316{ 317 DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; 318 DOS_FS *fs = f->fs; 319 320 f->fs->links--; 321 free(f); 322 dos_unmount(fs); 323 return 0; 324} 325 326/* 327 * Return some stat information on a file. 328 */ 329static int 330dos_stat(struct open_file *fd, struct stat *sb) 331{ 332 DOS_FILE *f = (DOS_FILE *)fd->f_fsdata; 333 334 /* only important stuff */ 335 sb->st_mode = f->de.attr & FA_DIR ? S_IFDIR | 0555 : S_IFREG | 0444; 336 sb->st_nlink = 1; 337 sb->st_uid = 0; 338 sb->st_gid = 0; 339 if ((sb->st_size = fsize(f->fs, &f->de)) == -1) 340 return EINVAL; 341 return (0); 342} 343 344/* 345 * Parse DOS boot sector 346 */ 347static int 348parsebs(DOS_FS *fs, DOS_BS *bs) 349{ 350 u_int sc; 351 352 if ((bs->jmp[0] != 0x69 && 353 bs->jmp[0] != 0xe9 && 354 (bs->jmp[0] != 0xeb || bs->jmp[2] != 0x90)) || 355 bs->bpb.media < 0xf0) 356 return EINVAL; 357 if (cv2(bs->bpb.secsiz) != SECSIZ) 358 return EINVAL; 359 if (!(fs->spc = bs->bpb.spc) || fs->spc & (fs->spc - 1)) 360 return EINVAL; 361 fs->bsize = secbyt(fs->spc); 362 fs->bshift = ffs(fs->bsize) - 1; 363 if ((fs->spf = cv2(bs->bpb.spf))) { 364 if (bs->bpb.fats != 2) 365 return EINVAL; 366 if (!(fs->dirents = cv2(bs->bpb.dirents))) 367 return EINVAL; 368 } else { 369 if (!(fs->spf = cv4(bs->bpb.lspf))) 370 return EINVAL; 371 if (!bs->bpb.fats || bs->bpb.fats > 16) 372 return EINVAL; 373 if ((fs->rdcl = cv4(bs->bpb.rdcl)) < LOCLUS) 374 return EINVAL; 375 } 376 if (!(fs->lsnfat = cv2(bs->bpb.ressec))) 377 return EINVAL; 378 fs->lsndir = fs->lsnfat + fs->spf * bs->bpb.fats; 379 fs->lsndta = fs->lsndir + entsec(fs->dirents); 380 if (!(sc = cv2(bs->bpb.secs)) && !(sc = cv4(bs->bpb.lsecs))) 381 return EINVAL; 382 if (fs->lsndta > sc) 383 return EINVAL; 384 if ((fs->xclus = secblk(fs, sc - fs->lsndta) + 1) < LOCLUS) 385 return EINVAL; 386 fs->fatsz = fs->dirents ? fs->xclus < 0xff6 ? 12 : 16 : 32; 387 sc = (secbyt(fs->spf) << 1) / (fs->fatsz >> 2) - 1; 388 if (fs->xclus > sc) 389 fs->xclus = sc; 390 return 0; 391} 392 393/* 394 * Return directory entry from path 395 */ 396static int 397namede(DOS_FS *fs, const char *path, DOS_DE **dep) 398{ 399 char name[256]; 400 DOS_DE *de; 401 char *s; 402 size_t n; 403 int err; 404 405 err = 0; 406 de = dot; 407 if (*path == '/') 408 path++; 409 while (*path) { 410 if (!(s = strchr(path, '/'))) 411 s = strchr(path, 0); 412 if ((n = s - path) > 255) 413 return ENAMETOOLONG; 414 memcpy(name, path, n); 415 name[n] = 0; 416 path = s; 417 if (!(de->attr & FA_DIR)) 418 return ENOTDIR; 419 if ((err = lookup(fs, stclus(fs->fatsz, de), name, &de))) 420 return err; 421 if (*path == '/') 422 path++; 423 } 424 *dep = de; 425 return 0; 426} 427 428/* 429 * Lookup path segment 430 */ 431static int 432lookup(DOS_FS *fs, u_int clus, const char *name, DOS_DE **dep) 433{ 434 static DOS_DIR dir[DEPSEC]; 435 u_char lfn[261]; 436 u_char sfn[13]; 437 u_int nsec, lsec, xdn, chk, sec, ent, x; 438 int err, ok, i; 439 440 if (!clus) 441 for (ent = 0; ent < 2; ent++) 442 if (!strcasecmp(name, dotstr[ent])) { 443 *dep = dot + ent; 444 return 0; 445 } 446 if (!clus && fs->fatsz == 32) 447 clus = fs->rdcl; 448 nsec = !clus ? entsec(fs->dirents) : fs->spc; 449 lsec = 0; 450 xdn = chk = 0; 451 for (;;) { 452 if (!clus && !lsec) 453 lsec = fs->lsndir; 454 else if (okclus(fs, clus)) 455 lsec = blklsn(fs, clus); 456 else 457 return EINVAL; 458 for (sec = 0; sec < nsec; sec++) { 459 if ((err = ioget(fs->fd, lsec + sec, dir, 1))) 460 return err; 461 for (ent = 0; ent < DEPSEC; ent++) { 462 if (!*dir[ent].de.name) 463 return ENOENT; 464 if (*dir[ent].de.name != 0xe5) 465 if ((dir[ent].de.attr & FA_MASK) == FA_XDE) { 466 x = dir[ent].xde.seq; 467 if (x & 0x40 || (x + 1 == xdn && 468 dir[ent].xde.chk == chk)) { 469 if (x & 0x40) { 470 chk = dir[ent].xde.chk; 471 x &= ~0x40; 472 } 473 if (x >= 1 && x <= 20) { 474 cp_xdnm(lfn, &dir[ent].xde); 475 xdn = x; 476 continue; 477 } 478 } 479 } else if (!(dir[ent].de.attr & FA_LABEL)) { 480 if ((ok = xdn == 1)) { 481 for (x = 0, i = 0; i < 11; i++) 482 x = ((((x & 1) << 7) | (x >> 1)) + 483 dir[ent].de.name[i]) & 0xff; 484 ok = chk == x && 485 !strcasecmp(name, (const char *)lfn); 486 } 487 if (!ok) { 488 cp_sfn(sfn, &dir[ent].de); 489 ok = !strcasecmp(name, (const char *)sfn); 490 } 491 if (ok) { 492 *dep = &dir[ent].de; 493 return 0; 494 } 495 } 496 xdn = 0; 497 } 498 } 499 if (!clus) 500 break; 501 if ((err = fatget(fs, &clus))) 502 return err; 503 if (fatend(fs->fatsz, clus)) 504 break; 505 } 506 return ENOENT; 507} 508 509/* 510 * Copy name from extended directory entry 511 */ 512static void 513cp_xdnm(u_char *lfn, DOS_XDE *xde) 514{ 515 static struct { 516 u_int off; 517 u_int dim; 518 } ix[3] = { 519 {offsetof(DOS_XDE, name1), sizeof(xde->name1) / 2}, 520 {offsetof(DOS_XDE, name2), sizeof(xde->name2) / 2}, 521 {offsetof(DOS_XDE, name3), sizeof(xde->name3) / 2} 522 }; 523 u_char *p; 524 u_int n, x, c; 525 526 lfn += 13 * ((xde->seq & ~0x40) - 1); 527 for (n = 0; n < 3; n++) 528 for (p = (u_char *)xde + ix[n].off, x = ix[n].dim; x; 529 p += 2, x--) { 530 if ((c = cv2(p)) && (c < 32 || c > 127)) 531 c = '?'; 532 if (!(*lfn++ = c)) 533 return; 534 } 535 if (xde->seq & 0x40) 536 *lfn = 0; 537} 538 539/* 540 * Copy short filename 541 */ 542static void 543cp_sfn(u_char *sfn, DOS_DE *de) 544{ 545 u_char *p; 546 int j, i; 547 548 p = sfn; 549 if (*de->name != ' ') { 550 for (j = 7; de->name[j] == ' '; j--); 551 for (i = 0; i <= j; i++) 552 *p++ = de->name[i]; 553 if (*de->ext != ' ') { 554 *p++ = '.'; 555 for (j = 2; de->ext[j] == ' '; j--); 556 for (i = 0; i <= j; i++) 557 *p++ = de->ext[i]; 558 } 559 } 560 *p = 0; 561 if (*sfn == 5) 562 *sfn = 0xe5; 563} 564 565/* 566 * Return size of file in bytes 567 */ 568static off_t 569fsize(DOS_FS *fs, DOS_DE *de) 570{ 571 u_long size; 572 u_int c; 573 int n; 574 575 if (!(size = cv4(de->size)) && de->attr & FA_DIR) 576 if (!(c = cv2(de->clus))) 577 size = fs->dirents * sizeof(DOS_DE); 578 else { 579 if ((n = fatcnt(fs, c)) == -1) 580 return n; 581 size = blkbyt(fs, n); 582 } 583 return size; 584} 585 586/* 587 * Count number of clusters in chain 588 */ 589static int 590fatcnt(DOS_FS *fs, u_int c) 591{ 592 int n; 593 594 for (n = 0; okclus(fs, c); n++) 595 if (fatget(fs, &c)) 596 return -1; 597 return fatend(fs->fatsz, c) ? n : -1; 598} 599 600/* 601 * Get next cluster in cluster chain 602 */ 603static int 604fatget(DOS_FS *fs, u_int *c) 605{ 606 u_char buf[4]; 607 u_int x; 608 int err; 609 610 err = ioread(fs, secbyt(fs->lsnfat) + fatoff(fs->fatsz, *c), buf, 611 fs->fatsz != 32 ? 2 : 4); 612 if (err) 613 return err; 614 x = fs->fatsz != 32 ? cv2(buf) : cv4(buf); 615 *c = fs->fatsz == 12 ? *c & 1 ? x >> 4 : x & 0xfff : x; 616 return 0; 617} 618 619/* 620 * Is cluster an end-of-chain marker? 621 */ 622static int 623fatend(u_int sz, u_int c) 624{ 625 return c > (sz == 12 ? 0xff7U : sz == 16 ? 0xfff7U : 0xffffff7); 626} 627 628/* 629 * Offset-based I/O primitive 630 */ 631static int 632ioread(DOS_FS *fs, u_int offset, void *buf, u_int nbyte) 633{ 634 char *s; 635 u_int off, n; 636 int err; 637 638 s = buf; 639 if ((off = offset & (SECSIZ - 1))) { 640 offset -= off; 641 if ((err = iobuf(fs, bytsec(offset)))) 642 return err; 643 offset += SECSIZ; 644 if ((n = SECSIZ - off) > nbyte) 645 n = nbyte; 646 memcpy(s, fs->buf + off, n); 647 s += n; 648 nbyte -= n; 649 } 650 n = nbyte & (SECSIZ - 1); 651 if (nbyte -= n) { 652 if ((err = ioget(fs->fd, bytsec(offset), s, bytsec(nbyte)))) 653 return err; 654 offset += nbyte; 655 s += nbyte; 656 } 657 if (n) { 658 if ((err = iobuf(fs, bytsec(offset)))) 659 return err; 660 memcpy(s, fs->buf, n); 661 } 662 return 0; 663} 664 665/* 666 * Buffered sector-based I/O primitive 667 */ 668static int 669iobuf(DOS_FS *fs, u_int lsec) 670{ 671 int err; 672 673 if (fs->bufsec != lsec) { 674 if ((err = ioget(fs->fd, lsec, fs->buf, 1))) 675 return err; 676 fs->bufsec = lsec; 677 } 678 return 0; 679} 680 681/* 682 * Sector-based I/O primitive 683 */ 684static int 685ioget(struct open_file *fd, u_int lsec, void *buf, u_int nsec) 686{ 687 int err; 688 689 if ((err = (fd->f_dev->dv_strategy)(fd->f_devdata, F_READ, lsec, 690 secbyt(nsec), buf, NULL))) 691 return(err); 692 return(0); 693} 694