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