freebsd-src/release/sysinstall/dosio.c
Jordan K. Hubbard 0fd6d431cf Do a few things I've been threatening to do for a long time:
1. Don't use the MSDOSFS code for accessing FreeBSD distribution data.
   Use Robert Nordier's stand-alone DOS I/O library for the purpose.
   It this works as well as Robert says it does, it should drastically reduce
   (or even eliminate) our "I can't install from my DOS partition!" calls.

2. As a result of the above, go to stdio file descriptors for all
   media types.

3. Taking advantage of #2, start using libftpio for FTP transfers instead
   of maintaining our own parallel version of the FTP transfer code.
   Yay!  I ripped something out for a change!

#1 Submitted-By: Robert Nordier <rnordier@iafrica.com>
1996-12-11 09:35:06 +00:00

675 lines
16 KiB
C

/*
* 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 <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#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));
}