freebsd-nq/sys/i386/boot/cdboot/cdrom.c
1999-08-28 01:08:13 +00:00

403 lines
9.2 KiB
C

/*
* Copyright © 1997 Pluto Technologies International, Inc. Boulder CO
* Copyright © 1997 interface business GmbH, Dresden.
* All rights reserved.
*
* This code was written by Jörg Wunsch, Dresden.
* Direct comments to <joerg_wunsch@interface-business.de>.
*
* 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.
*
* $FreeBSD$
*/
#include "boot.h"
#include <isofs/cd9660/iso.h>
#define BLKSIZE 2048 /* CD-ROM data block size */
#define BIOSSEC 512 /* BIOS sector size */
#define CD2LBA(rba) ((rba) << 2) /* CD-ROM relative block to BIOS LBA */
u_int32_t sessionstart;
static struct iso_primary_descriptor pdesc;
static char *rootdirbuf;
static size_t rootdirsize;
static char xbuf[BLKSIZE];
static u_int32_t curblk, startblk, filesize, offset;
static int bread(u_int32_t rba, size_t nblks, void *buf);
static void badread(const char *msg, u_int32_t blkno);
static struct iso_directory_record *find(const char *path, int list_only);
static char *get_rr_name(struct iso_directory_record *dirp, size_t *len_ret);
static int iread(u_char *buf, size_t len,
void (*copyfun)(const void *src, void *dst, size_t size));
static struct daddrpacket dpkt = { 0x10 };
int
devopen(u_int32_t session)
{
int rv;
u_int32_t rootdirblk;
struct iso_directory_record *rootdirp;
if ((rv = bread(session + 16, 1, &pdesc)) != 0) {
printf("Error reading primary ISO descriptor: %d\n", rv);
return -1;
}
rootdirp = (struct iso_directory_record *)pdesc.root_directory_record;
rootdirblk = isonum_733(rootdirp->extent);
rootdirsize = isonum_733(rootdirp->size);
/* just in case, round up */
rootdirsize = (rootdirsize + BLKSIZE - 1) & ~(BLKSIZE - 1);
if (rootdirbuf != NULL)
free(rootdirbuf);
if ((rootdirbuf = malloc(rootdirsize)) == 0) {
printf("Cannot allocate memory for the root "
"directory buffer.\n");
return -1;
}
if ((rv = bread(rootdirblk, rootdirsize / BLKSIZE, rootdirbuf))
!= 0) {
printf("Error reading root directory: %d\n", rv);
return -1;
}
DPRINTF(("Root directory is 0x%x bytes @ %d\n",
rootdirsize, rootdirblk));
return 0;
}
static int
bread(u_int32_t rba, size_t nblks, void *buf)
{
int i, rv;
for (i = 0, rv = -1; rv != 0 && i < 3; i++) {
dpkt.nblocks = nblks * (BLKSIZE / BIOSSEC);
dpkt.boffs = (u_int16_t)((int)buf & 0xffff);
dpkt.bseg = BOOTSEG;
dpkt.lba = CD2LBA(rba);
#ifdef DEBUG_VERBOSE
DPRINTF(("Calling biosreadlba(%d blocks, lba %d) = ",
dpkt.nblocks, dpkt.lba));
#endif
rv = biosreadlba(&dpkt);
#ifdef DEBUG_VERBOSE
DPRINTF(("%d\n", rv));
#endif
}
return rv;
}
void
seek(u_int32_t offs)
{
offset = offs;
}
static void
badread(const char *msg, u_int32_t blkno)
{
printf("Error reading block %d from CD-ROM: %s\n",
blkno, msg);
}
static __inline size_t
minlen(size_t a, size_t b)
{
return a < b? a: b;
}
/*
* Internal form of read()/xread().
*/
static int
iread(u_char *buf, size_t len,
void (*copyfun)(const void *src, void *dst, size_t size))
{
u_int32_t newblk, ptr;
size_t bsize;
newblk = offset / BLKSIZE + startblk;
if (newblk != curblk) {
if (offset + len >= filesize) {
badread("access beyond file limit", newblk);
return -1;
}
if (bread(newblk, 1, xbuf)) {
badread("BIOS read error", newblk);
return -1;
}
curblk = newblk;
}
ptr = offset & (BLKSIZE - 1);
if (ptr > 0) {
/* initial short transfer */
bsize = minlen(BLKSIZE - ptr, len);
copyfun(xbuf + ptr, buf, bsize);
buf += bsize;
len -= bsize;
offset += bsize;
}
for (; len > 0; len -= bsize) {
bsize = minlen(len, BLKSIZE);
newblk = offset / BLKSIZE + startblk;
if (newblk != curblk) {
if (offset + bsize > filesize) {
badread("access beyond file limit", newblk);
return -1;
}
if (bread(newblk, 1, xbuf)) {
badread("BIOS read error", newblk);
return -1;
}
curblk = newblk;
}
copyfun(xbuf, buf, bsize);
buf += bsize;
offset += bsize;
}
return 0;
}
int
read(u_char *buf, size_t len)
{
DPRINTF(("read(0x%x, %d)\n", (int)buf, len));
return iread(buf, len, bcopy);
}
int
xread(u_char *buf, size_t len)
{
DPRINTF(("xread(0x%x, %d)\n", (int)buf, len));
return iread(buf, len, pcpy);
}
static char *
get_rr_name(struct iso_directory_record *dirp, size_t *len_ret)
{
struct rr_header {
char type[2];
u_char len;
u_char version;
} *rrp;
struct rr_nm_header {
struct rr_header rrh;
u_char flags;
char name[0]; /* XXX -- using gcc extension */
} *rrnmp;
char *cp;
cp = dirp->name + (u_char)dirp->name_len[0];
/* round up to 16-bit boundary; ugly */
cp = (char *)(((int)cp + 1) & ~1);
rrp = (struct rr_header *)cp;
if (rrp->type[0] != 'R' || rrp->type[1] != 'R') {
DPRINTF(("no RR, "));
return 0;
}
DPRINTF(("RR attribs: "));
cp += rrp->len;
while (cp - (char *)dirp <= (u_char)dirp->length[0]) {
rrp = (struct rr_header *)cp;
DPRINTF(("%c%c ", rrp->type[0], rrp->type[1]));
if (rrp->type[0] == 'N' && rrp->type[1] == 'M') {
rrnmp = (struct rr_nm_header *)rrp;
*len_ret = rrp->len - sizeof(struct rr_nm_header);
return rrnmp->name;
}
cp += rrp->len;
}
return 0;
}
static struct iso_directory_record *
find(const char *path, int list_only)
{
struct iso_directory_record *dirp;
char *ptr, *rrname;
size_t len, entrylen;
char namebuf[256];
int i;
int (*comp)(const char *, const char *);
while (*path && *path == '/')
path++;
for (ptr = rootdirbuf, i = 1;
ptr < rootdirbuf + rootdirsize;
ptr += entrylen, i++) {
dirp = (struct iso_directory_record *)ptr;
entrylen = (u_char)dirp->length[0];
len = (u_char)dirp->name_len[0];
DPRINTF(("# %d: offset 0x%x, length 0x%x = %d, ",
i, (int)(ptr - rootdirbuf), entrylen, entrylen));
if (entrylen == 0) {
/*
* Dir entry of length 0. That's the last
* entry in this block, advance to the next
* block (if any). In case we get beyond the
* end of the directory, we'll fall off the
* loop due to the rootdirsize condition in
* the `for' statement.
*/
DPRINTF(("entrylen 0\n"));
entrylen = (~((ptr - rootdirbuf) + BLKSIZE - 1))
& (BLKSIZE - 1);
continue;
}
if (len == 0) {
DPRINTF(("name_len 0\n"));
continue;
}
if (len == 1 &&
(dirp->name[0] == '\0' || dirp->name[1] == '\1')) {
DPRINTF(("dot/dot-dot entry\n"));
continue;
}
/* don't consider directories */
if (dirp->flags[0] & 2) {
DPRINTF(("directory\n"));
continue;
}
rrname = get_rr_name(dirp, &len);
comp = rrname? strcmp: strcasecmp;
bcopy(rrname? rrname: dirp->name, namebuf, len);
namebuf[len] = 0;
DPRINTF(("name `%s'\n", namebuf));
if (list_only) {
#ifndef DEBUG
printf("%s ", namebuf);
#endif
} else if (comp(path, namebuf) == 0)
return dirp;
}
#ifndef DEBUG
if (list_only)
printf("\n");
#endif
return 0;
}
int
openrd(char *name)
{
char *cp;
const char *fname;
u_int32_t oldsession;
int session, list_only;
struct iso_directory_record *dirp;
session = 0;
fname = name;
/*
* We accept the following boot string:
*
* [@sessionstart] name
*/
for (cp = name; *cp; cp++)
switch (*cp) {
/* we don't support filenames with spaces */
case ' ': case '\t':
break;
case '@':
if (session) {
printf("Syntax error\n");
return -1;
}
session++;
oldsession = sessionstart;
sessionstart = 0;
break;
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7': case '8':
case '9':
if (session == 1) {
sessionstart *= 10;
sessionstart += *cp - '0';
}
break;
default:
if (session == 1) {
session++;
fname = cp;
}
}
if (session && devopen(sessionstart) == -1) {
(void)devopen(oldsession);
sessionstart = oldsession;
}
if (session == 1)
/* XXX no filename, only session arg */
return -1;
list_only = fname[0] == '?' && fname[1] == 0;
DPRINTF(("Calling find(%s, %d):\n", fname, list_only));
dirp = find(fname, list_only);
DPRINTF(("find() returned 0x%x\n", (int)dirp));
if (list_only)
return -1;
if (dirp == 0)
return 1;
startblk = isonum_733(dirp->extent);
filesize = isonum_733(dirp->size);
DPRINTF(("startblk = %d, filesize = %d\n", startblk, filesize));
curblk = 0; /* force a re-read, 0 is impossible file start */
seek(0);
return 0;
}