/* * Copyright (c) 1996 Robert Nordier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "dosio.h" #define SECSIZ 512 /* sector size */ #define SSHIFT 9 /* SECSIZ shift */ #define DEPSEC 16 /* directory entries per sector */ #define DSHIFT 4 /* DEPSEC shift */ #define NFATS 2 /* number of FATs */ #define DENMSZ 8 /* DE name size */ #define DEXTSZ 3 /* DE extension size */ #define DENXSZ 11 /* DE name + extension size */ #define LOCLUS 2 /* lowest cluster number */ /* DOS "BIOS Parameter Block" */ typedef struct { u_char secsiz[2]; /* sector size */ u_char spc; /* sectors per cluster */ u_char ressec[2]; /* reserved sectors */ u_char fats; /* FATs */ u_char dirents[2]; /* root directory entries */ u_char secs[2]; /* total sectors */ u_char media; /* media descriptor */ u_char spf[2]; /* sectors per FAT */ u_char spt[2]; /* sectors per track */ u_char heads[2]; /* drive heads */ u_char hidsec[4]; /* hidden sectors */ u_char lsecs[4]; /* huge sectors */ } DOS_BPB; /* Fixed portion of DOS boot sector */ typedef struct { u_char jmp[3]; /* usually 80x86 'jmp' opcode */ u_char oem[8]; /* OEM name and version */ DOS_BPB bpb; /* BPB */ u_char drive; /* drive number */ u_char reserved; /* reserved */ u_char extsig; /* extended boot signature */ u_char volid[4]; /* volume ID */ u_char label[11]; /* volume label */ u_char fstype[8]; /* file system type */ } DOS_BS; /* Supply missing "." and ".." root directory entries */ static DOS_DE dot[2] = { {". ", " ", FA_DIR, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0} }, {".. ", " ", FA_DIR, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0}, {0x21, 0}, {0, 0}, {0, 0, 0, 0} } }; /* I/O error handler address */ int (*dos_ioerr)(int op) = NULL; /* The usual conversion macros to avoid multiplication and division */ #define bytsec(n) ((n) >> (SSHIFT)) #define secbyt(s) ((u_long)(s) << (SSHIFT)) #define entsec(e) ((e) >> (DSHIFT)) #define bytblk(fs, n) ((n) >> (fs)->bshift) #define blkbyt(fs, b) ((u_long)(b) << (fs)->bshift) #define blksec(fs, b) ((u_long)(b) << ((fs)->bshift - (SSHIFT))) /* Convert cluster number to offset within filesystem */ #define blkoff(fs, b) secbyt((fs)->lsndta) + blkbyt(fs, (b) - (LOCLUS)) /* Convert cluster number to logical sector number */ #define blklsn(fs, b) ((fs)->lsndta + blksec(fs, (b) - (LOCLUS))) /* Convert cluster number to offset within FAT */ #define fatoff(fat12, c) ((u_long)(c) + ((fat12) ? (c) >> 1 : (c))) /* Does cluster number reference a valid data cluster? */ #define okclus(fs, c) ((c) >= (LOCLUS) && (c) <= (fs)->xclus) /* Return on error */ #define RETERR(err) { \ errno = err; \ return -1; \ } static int dosunmount(DOS_FS *fs); static int dosstat(DOS_FS *fs, DOS_DE *de, struct stat *sb); static int parsebs(DOS_FS *fs, DOS_BS *bs); static int namede(DOS_FS *fs, const char *path, DOS_DE **dep); static DOS_DE *lookup(DOS_FS *fs, unsigned c, const u_char *nx, int *err); static off_t fsize(DOS_FS *fs, DOS_DE *de); static u_short fatget(DOS_FS *fs, u_short c); static int fatend(int fat12, u_short c); static int fatcnt(DOS_FS *fs, u_short c); static int ioread(int fd, off_t offset, void *buf, size_t nbytes); static int ioget(int fd, u_long lsec, void *buf, u_int nsec); static u_char *nxname(const char *name, char **endptr); static int sepchar(int c); static int wildchar(int c); static int doschar(int c); /* * Mount DOS filesystem */ int dos_mount(DOS_FS *fs, const char *devname) { char buf[SECSIZ]; int err; memset(fs, 0, sizeof(DOS_FS)); if ((fs->fd = open(devname, O_RDONLY)) == -1) RETERR(errno); if (!(err = ioget(fs->fd, 0, buf, 1)) && !(err = parsebs(fs, (DOS_BS *)buf))) if (!(fs->fat = malloc(secbyt(fs->spf)))) err = errno; else err = ioget(fs->fd, fs->lsnfat, fs->fat, fs->spf); if (err) { dosunmount(fs); RETERR(err); } fs->bsize = secbyt(fs->spc); fs->bshift = ffs(fs->bsize) - 1; return 0; } /* * Unmount mounted filesystem */ int dos_unmount(DOS_FS *fs) { int err; if (fs->links) RETERR(EBUSY); if ((err = dosunmount(fs))) RETERR(err); return 0; } /* * Common code shared by dos_mount() and dos_unmount() */ static int dosunmount(DOS_FS *fs) { if (fs->fat) free(fs->fat); return close(fs->fd) ? errno : 0; } /* * Determine free data space in filesystem (in bytes) */ u_long dos_free(DOS_FS *fs) { unsigned n, c; n = 0; for (c = LOCLUS; c <= fs->xclus; c++) if (!fatget(fs, c)) n++; return blkbyt(fs, n); } /* * Close open file */ int dos_close(void *v) { DOS_FILE *f = v; f->fs->links--; free(f); return 0; } /* * Reposition with file */ fpos_t dos_seek(void *v, fpos_t offset, int whence) { off_t off; u_long size; DOS_FILE *f = v; size = cv4(f->de.size); switch (whence) { case SEEK_SET: off = 0; break; case SEEK_CUR: off = f->offset; break; case SEEK_END: off = size; break; default: RETERR(EINVAL); } off += offset; if (off < 0 || off > size) RETERR(EINVAL); f->offset = off; f->c = 0; return 0; } /* * Read from file */ int dos_read(void *v, char *buf, int nbytes) { off_t size; u_long off, cnt, n; unsigned clus, c; int err; DOS_FILE *f = v; if ((size = fsize(f->fs, &f->de)) == -1) RETERR(EBADFS); if (nbytes > (n = size - f->offset)) nbytes = n; off = f->offset; if ((clus = cv2(f->de.clus))) off &= f->fs->bsize - 1; c = f->c; cnt = nbytes; while (cnt) { n = 0; if (!c) { if ((c = clus)) n = bytblk(f->fs, f->offset); } else if (!off) n++; while (n--) { c = fatget(f->fs, c); if (!okclus(f->fs, c)) RETERR(EBADFS); } if (!clus || (n = f->fs->bsize - off) > cnt) n = cnt; if ((err = ioread(f->fs->fd, (c ? blkoff(f->fs, c) : secbyt(f->fs->lsndir)) + off, buf, n))) RETERR(err); f->offset += n; f->c = c; off = 0; buf += n; cnt -= n; } return nbytes; } /* * Get file status */ int dos_stat(DOS_FS *fs, const char *path, struct stat *sb) { DOS_DE *de; int err; if ((err = namede(fs, path, &de)) || (err = dosstat(fs, de, sb))) RETERR(err); return 0; } /* * Get file status of open file */ int dos_fstat(DOS_FILE *f, struct stat *sb) { int err; if ((err = dosstat(f->fs, &f->de, sb))) RETERR(err); return 0; } /* * File status primitive */ static int dosstat(DOS_FS *fs, DOS_DE *de, struct stat *sb) { memset(sb, 0, sizeof(struct stat)); sb->st_mode = (de->attr & FA_DIR) ? S_IFDIR | 0777 : S_IFREG | 0666; if (de->attr & FA_RDONLY) sb->st_mode &= ~0222; if (de->attr & FA_HIDDEN) sb->st_mode &= ~0007; if (de->attr & FA_SYSTEM) sb->st_mode &= ~0077; sb->st_nlink = 1; dos_cvtime(&sb->st_atime, cv2(de->date), cv2(de->time)); sb->st_mtime = sb->st_atime; sb->st_ctime = sb->st_atime; if ((sb->st_size = fsize(fs, de)) == -1) return EBADFS; if (!(de->attr & FA_DIR) || cv2(de->clus)) sb->st_blocks = bytblk(fs, sb->st_size + fs->bsize - 1); sb->st_blksize = fs->bsize; return 0; } /* * Convert from DOS date and time */ void dos_cvtime(time_t *timer, u_short ddate, u_short dtime) { struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_sec = (dtime & 0x1f) << 1; tm.tm_min = dtime >> 5 & 0x3f; tm.tm_hour = dtime >> 11; tm.tm_mday = ddate & 0x1f; tm.tm_mon = (ddate >> 5 & 0xf) - 1; tm.tm_year = 80 + (ddate >> 9); *timer = mktime(&tm); } /* * Open DOS file */ FILE * dos_open(DOS_FS *fs, const char *path) { DOS_DE *de; DOS_FILE *f; u_long size; u_int clus; int err; FILE *fp; if ((err = namede(fs, path, &de))) return NULL; clus = cv2(de->clus); size = cv4(de->size); if ((clus && (!okclus(fs, clus) || (!(de->attr & FA_DIR) && !size))) || (!clus && !(de->attr & FA_DIR) && size)) return NULL; f = (DOS_FILE *)malloc(sizeof(DOS_FILE)); memset(f, 0, sizeof(DOS_FILE)); f->fs = fs; fs->links++; f->de = *de; fp = funopen(f, dos_read, NULL, dos_seek, dos_close); return fp; } /* * Parse DOS boot sector */ static int parsebs(DOS_FS *fs, DOS_BS *bs) { u_long sc; if ((bs->jmp[0] != 0xe9 && (bs->jmp[0] != 0xeb || bs->jmp[2] != 0x90)) || bs->bpb.media < 0xf0 || cv2(bs->bpb.secsiz) != SECSIZ || !bs->bpb.spc || (bs->bpb.spc ^ (bs->bpb.spc - 1)) < bs->bpb.spc) return EINVAL; fs->spf = cv2(bs->bpb.spf); fs->dirents = cv2(bs->bpb.dirents); fs->spc = bs->bpb.spc; sc = cv2(bs->bpb.secs); if (!sc && bs->extsig == 0x29) sc = cv4(bs->bpb.lsecs); if (!sc || bs->bpb.fats != NFATS || bs->bpb.spc > 64) return EINVAL; if (!fs->dirents || fs->dirents & (DEPSEC - 1)) return EINVAL; fs->lsnfat = cv2(bs->bpb.ressec); fs->lsndir = fs->lsnfat + (u_long)fs->spf * NFATS; fs->lsndta = fs->lsndir + entsec(fs->dirents); if (fs->lsndta > sc || !(sc = (sc - fs->lsndta) / fs->spc) || sc >= 0xfff6) return EINVAL; fs->fat12 = sc < 0xff6; fs->xclus = sc + 1; if (fs->spf < bytsec(fatoff(fs->fat12, fs->xclus) + SECSIZ)) return EINVAL; if (bs->extsig == 0x29) fs->volid = cv4(bs->volid); return 0; } /* * Return directory entry from path */ static int namede(DOS_FS *fs, const char *path, DOS_DE **dep) { DOS_DE *de; u_char *nx; int err; err = 0; de = dot; if (*path == '/') path++; while (*path) { if (!(nx = nxname(path, (char **)&path))) return EINVAL; if (!(de->attr & FA_DIR)) return ENOTDIR; if (!(de = lookup(fs, cv2(de->clus), nx, &err))) return err ? err : ENOENT; if (*path == '/') path++; } *dep = de; return 0; } /* * Lookup path segment */ static DOS_DE * lookup(DOS_FS *fs, unsigned c, const u_char *nx, int *err) { static DOS_DE dir[DEPSEC]; u_long lsec; u_int nsec; int s, e; if (!c) for (e = 0; e < 2; e++) if (!memcmp(dot + e, nx, DENXSZ)) return dot + e; nsec = !c ? entsec(fs->dirents) : fs->spc; lsec = 0; do { if (!c && !lsec) lsec = fs->lsndir; else if okclus(fs, c) lsec = blklsn(fs, c); else { *err = EBADFS; return NULL; } for (s = 0; s < nsec; s++) { if ((e = ioget(fs->fd, lsec + s, dir, 1))) { *err = e; return NULL; } for (e = 0; e < DEPSEC; e++) { if (!*dir[e].name) return NULL; if (*dir[e].name == 0xe5 || dir[e].attr & FA_LABEL) continue; if (!memcmp(dir + e, nx, DENXSZ)) return dir + e; } } } while (c && !fatend(fs->fat12, c = fatget(fs, c))); return NULL; } /* * Return size of file in bytes */ static off_t fsize(DOS_FS *fs, DOS_DE *de) { u_long size; u_int c; int n; if (!(size = cv4(de->size)) && de->attr & FA_DIR) if (!(c = cv2(de->clus))) size = fs->dirents * sizeof(DOS_DE); else { if ((n = fatcnt(fs, c)) == -1) return n; size = blkbyt(fs, n); } return size; } /* * Return next cluster in cluster chain */ static u_short fatget(DOS_FS *fs, u_short c) { u_short x; x = cv2(fs->fat + fatoff(fs->fat12, c)); return fs->fat12 ? c & 1 ? x >> 4 : x & 0xfff : x; } /* * Count number of clusters in chain */ static int fatcnt(DOS_FS *fs, u_short c) { int n; for (n = 0; okclus(fs, c); n++) c = fatget(fs, c); return fatend(fs->fat12, c) ? n : -1; } /* * Is cluster an end-of-chain marker? */ static int fatend(int fat12, u_short c) { return c > (fat12 ? 0xff7 : 0xfff7) || c == 0xfff0; } /* * Offset-based I/O primitive */ static int ioread(int fd, off_t offset, void *buf, size_t nbytes) { char tmp[SECSIZ]; u_int off, n; int err; if ((off = offset & (SECSIZ - 1))) { offset -= off; if ((err = ioget(fd, bytsec(offset), tmp, 1))) return err; offset += SECSIZ; if ((n = SECSIZ - off) > nbytes) n = nbytes; memcpy(buf, tmp + off, n); buf += n; nbytes -= n; } n = nbytes & (SECSIZ - 1); if (nbytes -= n) { if ((err = ioget(fd, bytsec(offset), buf, bytsec(nbytes)))) return err; offset += nbytes; buf += nbytes; } if (n) { if ((err = ioget(fd, bytsec(offset), tmp, 1))) return err; memcpy(buf, tmp, n); } return 0; } /* * Sector-based I/O primitive */ static int ioget(int fd, u_long lsec, void *buf, u_int nsec) { size_t nbytes; ssize_t n; nbytes = secbyt(nsec); do { if (lseek(fd, secbyt(lsec), SEEK_SET) == -1) return errno; n = read(fd, buf, nbytes); } while (n == -1 && errno == EIO && dos_ioerr && dos_ioerr(0)); if (n != nbytes) return n == -1 ? errno : EIO; return 0; } /* * Convert name to DOS directory (name + extension) format */ static u_char * nxname(const char *name, char **endptr) { static u_char nx[DENXSZ]; int i; memset(nx, ' ', sizeof(nx)); for (i = 0; i < DENMSZ && doschar(*name); i++) nx[i] = toupper(*name++); if (i) { if (i == DENMSZ) while (!sepchar(*name)) name++; if (*name == '.') { name++; for (i = 0; i < DEXTSZ && doschar(*name); i++) nx[DENMSZ + i] = toupper(*name++); if (i == DEXTSZ) while(!sepchar(*name)) name++; } } else if (*name == '.') { nx[0] = *name++; if (*name == '.') nx[1] = *name++; } if ((*name && *name != '/') || *nx == ' ') return NULL; if (*nx == 0xe5) *nx = 5; *endptr = (char *)name; return nx; } /* * Is character a path-separator? */ static int sepchar(int c) { return !wildchar(c) && !doschar(c); } /* * Is character a wildcard? */ static int wildchar(int c) { return c == '*' || c == '?'; } /* * Is character valid in a DOS name? */ static int doschar(int c) { return c & 0x80 || (c >= ' ' && !strchr("\"*+,./:;<=>?[\\]|", c)); }