| /* 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 *)(§_buf[cont_offset]), |
| (unsigned char *)(§_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; |
| } |