blob: d7a603bbea0eed60b156bc40e71237a9b5c7ad53 [file] [log] [blame]
/* CD-SILO : SILO CDROM (ISO-9660) boot block
Copyright (C) 1996 Jakub Jelinek
1998 Jan Vondrak
2003 Ben Collins
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA. */
#include <sys/types.h>
#include <linux/iso_fs.h>
#include <silo.h>
#include <rock.h>
#include <stringops.h>
#define SECOND_BLK "/boot/second.b"
#define SILO_CONF "/boot/silo.conf"
#define LOAD_ADDR 0x10000
struct isofs_inode {
unsigned int extent;
unsigned int size;
};
struct silo_info {
char id;
char conf_part;
char part;
char pad;
char conf_file[256];
};
static struct isofs_inode root_ino;
static int link_count;
static char silo_conf[] = SILO_CONF;
static int fd;
static int open_namei(const char *pathname, struct isofs_inode *res_inode,
struct isofs_inode *base);
static int cd_init (void)
{
char iso_bootdevice[1024];
char *s = iso_bootdevice;
if (prom_vers == PROM_V0) {
struct linux_arguments_v0 *ap = *romvec->pv_v0bootargs;
*s++ = ap->boot_dev[0];
*s++ = ap->boot_dev[1];
*s++ = '(';
*s++ = (ap->boot_dev_ctrl & 07) + '0';
*s++ = ',';
// Hopefully it's never > 10
*s++ = (ap->boot_dev_unit & 07) + '0';
*s++ = ',';
*s++ = '0';
*s++ = ')';
*s = 0;
fd = (*romvec->pv_v0devops.v0_devopen) (iso_bootdevice);
} else {
if (prom_vers == PROM_P1275)
prom_getproperty (prom_chosen, "bootpath", iso_bootdevice, sizeof(iso_bootdevice));
else
strcpy(iso_bootdevice, *romvec->pv_v2bootargs.bootpath);
for (; *s && *s != ':'; s++)
/* Do nothing */;
if (!*s) {
*s++ = ':'; *s++ = 'a'; *s = 0;
} else if (s[1] >= 'a' && s[1] <= 'z' && !s[2])
s[1] = 'a';
if (prom_vers == PROM_P1275)
fd = p1275_cmd ("open", 1, iso_bootdevice);
else
fd = (*romvec->pv_v2devops.v2_dev_open) (iso_bootdevice);
}
if (fd == 0 || fd == -1)
return 1;
return 0;
}
static void cd_fini(void)
{
switch (prom_vers) {
case PROM_V0:
romvec->pv_v0devops.v0_devclose(fd);
break;
case PROM_V2:
case PROM_V3:
romvec->pv_v2devops.v2_dev_close(fd);
break;
case PROM_P1275:
p1275_cmd("close", 1, fd);
break;
};
}
static int cd_read_block(unsigned long long offset, int size, void *data)
{
int ret;
if (!size)
return 0;
if (prom_vers == PROM_V0) {
/* ISOFS_BLOCK_SIZE / 512 == 4 */
size <<= 2;
offset <<= 2;
ret = (*romvec->pv_v0devops.v0_rdblkdev)
(fd, size, (unsigned)offset, data);
} else {
static unsigned long long seekp = 0xffffffffffffffffULL;
size <<= ISOFS_BLOCK_BITS;
offset <<= ISOFS_BLOCK_BITS;
if (seekp != offset) {
if (prom_vers == PROM_P1275) {
if (p1275_cmd("seek", P1275_ARG_64B(2) | 3, fd, 0, offset) == -1)
return -1;
} else {
if ((*romvec->pv_v2devops.v2_dev_seek)
(fd, (unsigned)(offset >> 32), (unsigned)offset) == -1)
return -1;
}
seekp = offset;
}
if (prom_vers == PROM_P1275)
ret = p1275_cmd ("read", 3, fd, data, size);
else
ret = (*romvec->pv_v2devops.v2_dev_read) (fd, data, size);
seekp += ret;
}
if (ret != size)
ret = -1;
return ret;
}
static int isonum_733 (char * p)
{
return ((p[0] & 0xff) | ((p[1] & 0xff) << 8)
| ((p[2] & 0xff) << 16) | ((p[3] & 0xff) << 24));
}
static int isofs_read_super(void)
{
struct iso_primary_descriptor iso;
if (cd_read_block(16, 1, &iso) < 0)
return -1;
if (memcmp(iso.id, ISO_STANDARD_ID, sizeof (iso.id)))
return -1;
root_ino.extent = isonum_733 (((struct iso_directory_record *)
(iso.root_directory_record))->extent);
root_ino.size = isonum_733 (((struct iso_directory_record *)
(iso.root_directory_record))->size);
return 0;
}
static void parse_rr (unsigned char *chr, unsigned char *end,
char *name, char *symlink)
{
int cont_extent = 0, cont_offset = 0, cont_size = 0;
struct rock_ridge *rr;
int sig;
int truncate = 0;
int rootflag;
*name = 0;
while (chr < end) {
rr = (struct rock_ridge *)chr;
if (rr->len == 0)
goto out;
sig = (chr[0] << 8) + chr[1];
chr += rr->len;
switch (sig) {
case SIG('R','R'):
if ((rr->u.RR.flags[0] &
(RR_PX | RR_TF | RR_SL | RR_CL | RR_NM | RR_PX | RR_TF)) == 0)
goto out;
break;
case SIG('N','M'):
if (truncate || rr->u.NM.flags & ~1)
break;
if ((strlen(name) + rr->len - 5) >= 254) {
truncate = 1;
break;
}
strncat(name, rr->u.NM.name, rr->len - 5);
break;
case SIG('S','L'):
{
int slen;
struct SL_component * slp;
struct SL_component * oldslp;
slen = rr->len - 5;
slp = &rr->u.SL.link;
while (slen > 1) {
rootflag = 0;
switch (slp->flags & ~1) {
case 0:
strncat(symlink, slp->text, slp->len);
break;
case 2:
strcat(symlink, ".");
break;
case 4:
strcat(symlink, "..");
break;
case 8:
rootflag = 1;
strcat(symlink, "/");
break;
default:
break;
}
slen -= slp->len + 2;
oldslp = slp;
slp = (struct SL_component *) (((char *) slp) +
slp->len + 2);
if (slen < 2)
break;
if (!rootflag && (oldslp->flags & 1) == 0)
strcat (symlink, "/");
}
}
break;
case SIG('C','E'):
CHECK_CE;
break;
case SIG('P','X'):
case SIG('T','F'):
break;
}
if (chr >= end && cont_extent) {
char sect_buf[ISOFS_BLOCK_SIZE];
if (cd_read_block(cont_extent, 1, sect_buf) < 0)
return;
parse_rr((unsigned char *)(&sect_buf[cont_offset]),
(unsigned char *)(&sect_buf[cont_offset +
cont_size - 3]), name, symlink);
}
}
out:
return;
}
static int isofs_lookup (struct isofs_inode *dir, const char *name,
int len, struct isofs_inode *result)
{
char buffer [ISOFS_BLOCK_SIZE];
char symlink [512];
char namebuf [512];
int block, size;
struct iso_directory_record *idr;
unsigned char *rr;
size = dir->size;
block = dir->extent;
while (size > 0) {
int i;
if (cd_read_block(block, 1, buffer) < 0)
return -1;
size -= ISOFS_BLOCK_SIZE;
block++;
for (i = 0 ;; ) {
idr = (struct iso_directory_record *) (buffer + i);
if (!idr->length[0])
break;
i += (unsigned char)idr->length[0];
memcpy(namebuf, idr->name, (unsigned char)idr->name_len[0]);
namebuf[(unsigned char)idr->name_len[0]] = 0;
rr = (unsigned char *)(idr + 1);
rr += ((unsigned char)idr->name_len[0]) - sizeof(idr->name);
if (!(idr->name_len[0] & 1))
rr++;
*symlink = 0;
parse_rr(rr, (unsigned char *)(&buffer[i-3]), namebuf, symlink);
if (idr->name_len[0] == 1 && !idr->name[0]) {
namebuf[0] = '.';
namebuf[1] = '\0';
} else if (idr->name_len[0] == 1 && idr->name[0] == 1) {
namebuf[0] = namebuf[1] = '.';
namebuf[2] = '\0';
}
if (strlen(namebuf) == len && !memcmp(name, namebuf, len)) {
if (*symlink) {
int error;
if (link_count > 5)
return -1; /* Looping */
link_count++;
error = open_namei(symlink, result, dir);
link_count--;
return error;
}
result->extent = isonum_733 (idr->extent);
result->size = isonum_733 (idr->size);
return 0;
}
if (i >= ISOFS_BLOCK_SIZE - sizeof(struct iso_directory_record) +
sizeof(idr->name))
break;
}
}
return -1;
}
static int dir_namei(const char *pathname, int *namelen, const char **name,
struct isofs_inode *base, struct isofs_inode *res_inode)
{
char c;
const char *thisname;
int len;
struct isofs_inode this_inode;
if ((c = *pathname) == '/') {
base = &root_ino;
pathname++;
}
while (1) {
thisname = pathname;
for (len = 0; (c = *(pathname++)) && (c != '/'); len++)
/* Do nothing */;
if (!c)
break;
if (isofs_lookup (base, thisname, len, &this_inode))
return -1;
base = &this_inode;
}
*name = thisname;
*namelen = len;
*res_inode = *base;
return 0;
}
static int open_namei(const char *pathname,
struct isofs_inode *res_inode,
struct isofs_inode *base)
{
struct isofs_inode dir;
const char *basename;
int namelen;
if (dir_namei(pathname, &namelen, &basename, base, &dir))
return -1;
if (isofs_lookup(&dir, basename, namelen, res_inode))
return -1;
return 0;
}
char *cd_main (struct linux_romvec *promvec, void *cifh, void *cifs)
{
struct isofs_inode inode;
unsigned char *dest = (unsigned char *)LOAD_ADDR;
struct silo_info *sinfo;
prom_init(promvec, cifh, cifs);
prom_putchar('S');
if (cd_init())
prom_halt();
if (isofs_read_super())
prom_halt();
link_count = 0;
if (open_namei(SECOND_BLK, &inode, &root_ino))
prom_halt();
prom_putchar('I');
if (cd_read_block(inode.extent, (inode.size + (ISOFS_BLOCK_SIZE - 1)) /
ISOFS_BLOCK_SIZE, dest) < 0)
prom_halt();
dest += 0x800;
sinfo = (struct silo_info *)&dest[0x08];
if (sinfo->id != 'L')
prom_halt();
memset(sinfo, 0, sizeof(*sinfo));
sinfo->id = 'L';
sinfo->conf_part = 1;
strcpy(sinfo->conf_file, silo_conf);
cd_fini();
prom_putchar(sinfo->id);
return (char *)dest;
}
/* Utility functions */
int memcmp(const void *cs, const void *ct, size_t count)
{
const unsigned char *su1, *su2;
signed char res = 0;
for (su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--)
if ((res = *su1 - *su2) != 0)
break;
return res;
}
void *memcpy(void *dest, const void *src, size_t count)
{
char *tmp = (char *) dest, *s = (char *) src;
while (count--)
*tmp++ = *s++;
return dest;
}
int strlen(const char *s)
{
const char *sc;
for (sc = s; *sc != '\0'; ++sc)
/* Do nothing */;
return sc - s;
}
char *strcat(char *dest, const char *src)
{
char *tmp = dest;
while (*dest) dest++;
while ((*dest++ = *src++) != '\0');
return tmp;
}
char *strncat(char *dest, const char *src, size_t n)
{
char *tmp = dest;
while (*dest) dest++;
while (n && (*dest++ = *src++) != '\0') n--;
if (!n) *dest = 0;
return tmp;
}
int strcmp(const char *cs, const char *ct)
{
register signed char __res;
while (1)
if ((__res = *cs - *ct++) != 0 || !*cs++)
break;
return __res;
}
void *memset(void *s,int c,size_t count)
{
char *xs = (char *) s;
while (count--)
*xs++ = c;
return s;
}
char *strcpy(char *dest, const char *src)
{
char *tmp = dest;
while ((*dest++ = *src++) != '\0');
return tmp;
}