/* File related stuff Copyright (C) 1996 Maurizio Plaza 1996 Jakub Jelinek 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include "silo.h" typedef int FILE; #include #include #define ec ,},{ static struct { int errnum; char *desc; } ext2errors[] = { { 0, "OK" #include } }; #include "ufs.h" #include static errcode_t linux_open (const char *name, int flags, io_channel * channel); static errcode_t linux_close (io_channel channel); static errcode_t linux_set_blksize (io_channel channel, int blksize); static errcode_t linux_read_blk (io_channel channel, unsigned long block, int count, void *data); static errcode_t linux_write_blk (io_channel channel, unsigned long block, int count, const void *data); static errcode_t linux_flush (io_channel channel); static struct struct_io_manager struct_linux_manager = { EXT2_ET_MAGIC_IO_MANAGER, "linux I/O Manager", linux_open, linux_close, linux_set_blksize, linux_read_blk, linux_write_blk, linux_flush }; ext2_filsys fs = 0; static ino_t root, cwd; static io_manager linux_io_manager = &struct_linux_manager; static do_gunzip = 0; static unsigned int *gzipped_blocks; static unsigned int *cur_gzipped_block; static unsigned int bs; /* Block Size */ static unsigned long int doff; /* Block where partition starts */ static unsigned char *filebuffer; static unsigned char *filelimit; static int first_block; static int block_no; static int block_cnt; static int last_blockcnt; static char *gunzip_buffer; static char *gunzip_inp; static char *gunzip_endbuf; static enum { ext2, ufs } type; extern int solaris; static int read_sun_partition (int partno) { int rc; sun_partition sdl; unsigned short csum, *ush; rc = read ((char *) &sdl, 512, 0); if (rc != 512) { fatal ("Cannot read partition"); return 0; } if (sdl.magic != SUN_LABEL_MAGIC) fatal ("Wrong disklabel magic"); for (csum = 0, ush = ((unsigned short *) ((&sdl) + 1)) - 1; ush >= (unsigned short *) &sdl;) csum ^= *ush--; if (csum) printf ("\nWarning: Your disklabel has wrong checksum. Use fdisk to correct it."); doff = (sdl.ntrks * sdl.nsect * sdl.partitions[partno - 1].start_cylinder) << 9; return 1; } int sprintf (char *buf, char *fmt,...) { strcpy (buf, fmt); } void com_err (const char *a, long i, const char *fmt,...) { printf ((char *) fmt); } static errcode_t linux_open (const char *name, int flags, io_channel * channel) { int partno; io_channel io; if (!name) return EXT2_ET_BAD_DEVICE_NAME; io = (io_channel) malloc (sizeof (struct struct_io_channel)); if (!io) return EXT2_ET_BAD_DEVICE_NAME; memset (io, 0, sizeof (struct struct_io_channel)); io->magic = EXT2_ET_MAGIC_IO_CHANNEL; io->manager = linux_io_manager; io->name = (char *) malloc (strlen (name) + 1); strcpy (io->name, name); io->block_size = bs; io->read_error = 0; io->write_error = 0; doff = 0; if (strncmp (name, "/dev/fd0")) { partno = *(name + strlen (name) - 1) - '0'; if (partno && !read_sun_partition (partno)) return EXT2_ET_BAD_DEVICE_NAME; } *channel = io; return 0; } static errcode_t linux_close (io_channel channel) { } static errcode_t linux_set_blksize (io_channel channel, int blksize) { channel->block_size = bs = blksize; } static errcode_t linux_read_blk (io_channel channel, unsigned long block, int count, void *data) { int size; size = (count < 0) ? -count : count * bs; if (read (data, size, block * bs + doff) != size) { printf ("\nRead error on block %d\n", block); return EXT2_ET_SHORT_READ; } return 0; } static errcode_t linux_write_blk (io_channel channel, unsigned long block, int count, const void *data) { } static errcode_t linux_flush (io_channel channel) { } static void ext2fs_error (int errcode) { int i; for (i = 0; i < sizeof (ext2errors) / sizeof (ext2errors[0]); i++) if (ext2errors [i].errnum == errcode) { printf ("%s", ext2errors [i].desc); return; } printf ("Unknown ext2 error"); } static int open_ext2 (char *device) { int retval; retval = ext2fs_open (device, EXT2_FLAG_RW, 0, 0, linux_io_manager, &fs); if (retval) { printf ("\n"); ext2fs_error (retval); printf ("\n"); return 0; } root = EXT2_ROOT_INO; return 1; } static int open_ufs (char *device) { if (ufs_open (device, linux_io_manager, &fs)) return 0; root = UFS_ROOTINO; return 1; } static void rotate (int freq) { static int i = 0; static char rot[] = "\\|/-"; if (!(i % freq)) printf ("%c\b", rot[(i / freq) % 5]); i++; } static int dump_block (ext2_filsys fs, blk_t * blocknr, int blockcnt, void *private) { size_t nbytes; if (blockcnt < 0) return 0; if (!first_block) rotate (5); if (!first_block && do_gunzip) { if (blockcnt != last_blockcnt + 1) { int i; for (i = blockcnt - last_blockcnt - 1; i > 0; i--) *cur_gzipped_block++ = 0xffffffff; } *cur_gzipped_block++ = *blocknr; last_blockcnt = blockcnt; return 0; } if (*blocknr || block_no) { if (first_block || !*blocknr || blockcnt != last_blockcnt + 1 || (block_no && *blocknr != block_no + block_cnt)) { if (first_block) { block_no = *blocknr; block_cnt = 1; if (blockcnt) { fatal ("File cannot have a hole at beginning"); return BLOCK_ABORT; } last_blockcnt = -1; } if (filebuffer + (block_cnt + ((*blocknr) ? (blockcnt - last_blockcnt - 1) : 0)) * bs > filelimit) { fatal ("Image to large to fit in destination"); return BLOCK_ABORT; } if (io_channel_read_blk (fs->io, block_no, block_cnt, filebuffer)) return BLOCK_ABORT; if (first_block) { first_block = 0; last_blockcnt = 0; if (*filebuffer == 037 && (filebuffer[1] == 0213 || filebuffer[1] == 0236)) { /* gzip magic */ gunzip_buffer = malloc (16 * bs); memcpy (gunzip_buffer, filebuffer, bs); gzipped_blocks = (unsigned int *) malloc ((0x280000 / 512) * sizeof (int)); cur_gzipped_block = gzipped_blocks; *cur_gzipped_block++ = *blocknr; printf ("Uncompressing image...\n"); return 0; } do_gunzip = 0; filebuffer += bs; block_no = 0; return 0; } filebuffer += block_cnt * bs; if (*blocknr && blockcnt != last_blockcnt + 1) { memset (filebuffer, 0, (blockcnt - last_blockcnt - 1) * bs); filebuffer += (blockcnt - last_blockcnt - 1) * bs; } block_no = 0; } if (*blocknr) { if (!block_no) { block_no = *blocknr; block_cnt = 1; } else block_cnt++; last_blockcnt = blockcnt; } } return 0; } extern jmp_buf gunzip_env; static unsigned char get_gzip_input (void) { if (gunzip_inp < gunzip_endbuf) return *gunzip_inp++; { int count = 1; unsigned int first = *cur_gzipped_block++; if (!first) { printf ("\nDecompression error: run out of compressed data\n"); longjmp (gunzip_env, 1); } if (first == 0xffffffff) { /* Hole */ while (*cur_gzipped_block == 0xffffffff && count < 16) { count++; cur_gzipped_block++; } rotate (1); memset (gunzip_buffer, 0, count * bs); } else { while (*cur_gzipped_block == first + count && count < 16) { count++; cur_gzipped_block++; } rotate (1); if (io_channel_read_blk (fs->io, first, count, gunzip_buffer)) { printf ("\nRead error\n"); longjmp (gunzip_env, 1); } } gunzip_endbuf = gunzip_buffer + count * bs; gunzip_inp = gunzip_buffer; } return *gunzip_inp++; } static void unget_gzip_input (void) { if (gunzip_inp > gunzip_buffer) gunzip_inp--; else { printf ("\nInternal error\n"); longjmp (gunzip_env, 1); } } static gunzipped_len = 0; static int dump_finish (void) { if (block_no) { blk_t tmp = 0; if (dump_block (fs, &tmp, 0, 0)) return 0; } if (do_gunzip) { *cur_gzipped_block++ = 0; cur_gzipped_block = gzipped_blocks + 1; gunzip_endbuf = gunzip_buffer + bs; gunzip_inp = gunzip_buffer; if ((gunzipped_len = decompress (filebuffer, filelimit, get_gzip_input, unget_gzip_input)) < 0) { free (gzipped_blocks); free (gunzip_buffer); return 0; } } return 1; } static int dump_ext2 (ino_t inode, char *filename) { errcode_t retval; retval = ext2fs_block_iterate (fs, inode, 0, 0, dump_block, 0); if (retval) { printf ("\n"); ext2fs_error (retval); printf ("\n"); return 0; } return dump_finish (); } static int dump_ufs (ino_t inode, char *filename) { if (ufs_block_iterate (fs, inode, dump_block, 0)) { printf ("Error while loading of %s", filename); return 0; } return dump_finish (); } int dump_device_range (char *filename, char *bogusdev, int *len, void (*lenfunc)(int, char **, char **)) { /* Range of blocks on physical block device */ int start, end = -1; int retval; char *p; bs = 512; p = strchr (filename, '-'); filename++; if (p && *filename >= '0' && *filename <= '9') { *p = 0; start = atoi (filename); filename = p + 1; p = strchr (filename, ']'); if (p && *filename >= '0' && *filename <= '9' && !p[1]) { *p = 0; end = atoi (filename); } } if (end == -1) { if (romvec->pv_romvers < 2) printf ("\nRanges of physical blocks are specified as {device_name}{partno}[xx-yy]" "\nwhere {} means optional part and xx is the starting block" "\n(chunk of 512 bytes) and yy end (not inclusive, i.e. yy won't be read)\n"); else printf ("\nRanges of physical blocks are specified as {prom_path;}{partno}[xx-yy]" "\nwhere {} means optional part, partno defaults to 0 (i.e. whole disk)" "\nand xx is the starting block (chunk of 512 bytes) and yy end (not" "\ninclusive, i.e. yy won't be read)\n"); return 0; } if (lenfunc) (*lenfunc)((end - start) << 9, (char **)&filebuffer, (char **)&filelimit); fs = (ext2_filsys) malloc (sizeof (struct struct_ext2_filsys)); if (fs) { if (!linux_open (bogusdev, 0, &fs->io)) { blk_t tmp; first_block = do_gunzip; block_no = 0; for (tmp = start; tmp < end; tmp++) { if (dump_block (fs, &tmp, tmp - start, 0)) break; } if (tmp == end && dump_finish ()) { if (len) { if (do_gunzip) *len = gunzipped_len; else *len = (end - start) << 9; } return 1; } } } printf ("\nInternal error while loading physical blocks from device\n"); return 0; } int dump_cdrom (char *filename, char *bogusdev, int *len, void (*lenfunc)(int, char **, char **)) { /* Blocks on cdrom device */ int start, end = -1; int retval; int length; extern char silo_conf[256]; bs = 1024; bogusdev[8] = '0'; retval = atoi (filename + 3); start = *(unsigned int *)(silo_conf + 8 * retval + 8); length = *(unsigned int *)(silo_conf + 8 * retval + 12); end = start + ((length + 1023) >> 10); if (lenfunc) (*lenfunc)(length, (char **)&filebuffer, (char **)&filelimit); fs = (ext2_filsys) malloc (sizeof (struct struct_ext2_filsys)); if (fs) { if (!linux_open (bogusdev, 0, &fs->io)) { blk_t tmp; first_block = do_gunzip; block_no = 0; for (tmp = start; tmp < end; tmp++) { if (dump_block (fs, &tmp, tmp - start, 0)) break; } if (tmp == end && dump_finish ()) { if (len) { if (do_gunzip) *len = gunzipped_len; else *len = length; } return 1; } } } printf ("\nInternal error while loading physical blocks from device\n"); return 0; } int get_len (ino_t inode) { if (do_gunzip) return gunzipped_len; if (type == ext2) { struct ext2_inode ei; int retval; if ((retval = ext2fs_read_inode (fs, inode, &ei))) { printf ("\n"); ext2fs_error (retval); printf ("\n"); return 0; } return ei.i_size; } else if (type == ufs) { struct ufs_inode ui; if (ufs_read_inode (fs, inode, &ui)) return 0; /* Hope nobody is so stupid to load 4GB+ kernel into core :)))) */ return ui.ui_size.val[1]; } return 0; } int load_file (char *device, int partno, char *filename, char *buffer, char *limit, int *len, int dogunzip, void (*lenfunc)(int, char **, char **)) { ino_t inode; int retval; int size = -1; char bogusdev[] = "/dev/sdaX"; char *bogdev; if (!device) device = bootdevice; #if 0 printf ("Loading %s from %d\n", filename, partno); #endif bogdev = bogusdev; if (romvec->pv_romvers < 2) { if (device[0] == 'f' && device[1] == 'd') { device = "fd()"; bogdev = "/dev/fd0"; } else bogusdev[8] = partno + '0'; } else bogusdev[8] = partno + '0'; if (setdisk (device) < 0) return 0; do_gunzip = dogunzip; filebuffer = buffer; filelimit = limit; if (!strncmp (filename, "!cd", 3)) { solaris = 0; return dump_cdrom (filename, bogdev, len, lenfunc); } if (*filename == '[') { solaris = 0; return dump_device_range (filename, bogdev, len, lenfunc); } if (!open_ext2 (bogdev)) { if (!open_ufs (bogdev)) { fatal ("Unable to open filesystem"); return 0; } else type = ufs; } else { type = ext2; solaris = 0; } if (type == ext2) { if (retval = ext2fs_namei_follow (fs, root, root, filename, &inode)) { printf ("\nCannot find %s (", filename); ext2fs_error (retval); printf (")\n"); ext2fs_close (fs); return 0; } } else if (type == ufs) { cwd = root; if (solaris) { if (!ufs_namei (fs, root, root, "/platform", &cwd)) { if (!ufs_namei (fs, root, cwd, get_syspackage(), &inode)) cwd = inode; else { char *p = "sun4c"; switch (get_architecture(romvec)) { case sun4: p = "sun4"; break; case sun4c: p = "sun4c"; break; case sun4m: p = "sun4m"; break; case sun4d: p = "sun4d"; break; case sun4e: p = "sun4e"; break; case sun4u: p = "sun4u"; break; } if (!ufs_namei (fs, root, cwd, p, &inode)) cwd = inode; } } if (cwd != root && *filename == '/') filename++; } if (ufs_namei (fs, root, cwd, filename, &inode)) { printf ("\nCannot find %s.", filename); ufs_close (fs); return 0; } if (solaris) { ino_t sinode; if (ufs_namei (fs, root, cwd, "ufsboot", &sinode)) { printf ("\nCannot find Solaris kernel bootloader `ufsboot'. Will try to load it,\n" "but it may fail\n"); solaris = 0; } else inode = sinode; } } if (lenfunc) { do_gunzip = 0; (*lenfunc)(size = get_len (inode), (char **)&filebuffer, (char **)&filelimit); do_gunzip = dogunzip; } first_block = do_gunzip; block_no = 0; retval = 0; if (inode) switch (type) { case ext2: retval = dump_ext2 (inode, filename); break; case ufs: retval = dump_ufs (inode, filename); break; } if (retval && len) *len = (size != -1) ? size : get_len (inode); switch (type) { case ext2: ext2fs_close (fs); break; case ufs: ufs_close (fs); break; } return retval; }