/* silo - bootblock installation program
   
   Copyright (C) 1996 Maurizio Plaza
                 1996,1998 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.  */

#define VERSION "0.8.2"
#define IMGVERSION "0.82"
#define DFL_CONFIG "/etc/silo.conf"
#define DFL_BACKUP "/boot/old.b"
#define DFL_BACKUP2 "/boot/old_part1.b"
#define DFL_PRIMARY "/boot/first.b"
#define DFL_PRIMARY_U "/boot/ultra.b"
#define DFL_PRIMARY_CD "/boot/cd.b"
#define DFL_SECONDARY "/boot/second.b"

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <features.h>
#ifdef __GLIBC__
#  include <sys/stat.h>
#  define _LINUX_STAT_H
#  define _LINUX_VFS_H
#endif
#ifdef __linux__
#  include <linux/fs.h>
#  include <linux/ext2_fs.h>
#  ifdef __GLIBC__
#    define _LINUX_TIME_H
#  endif
#  include <linux/ufs_fs.h>
#  include <linux/iso_fs.h>
#  include <scsi/scsi.h>
#  include <endian.h>
#  include <sys/vfs.h>
#  ifdef __GLIBC__
#    include <sys/sysmacros.h>
#    define mmajor(x) major(x)
#    define mminor(x) minor(x)
#    define mmakedev(x,y) makedev(x,y)
#  else
#    define mmajor(x) (int)((x) >> 8)
#    define mminor(x) (int)((x) & 0xff)
#    define mmakedev(major, minor) (((major) << 8) | (minor))
#  endif
#elif defined (__solaris__)
#  include <sys/types.h>
#  include <sys/stat.h>
#  include <non-linux/ext2_fs.h>
#  include <non-linux/ufs_fs.h>
#  include <non-linux/iso_fs.h>
#  include <ext2fs/ext2fs.h>
#  include "ufs.h"
#  include <limits.h>
#  include <sys/byteorder.h>
#  ifdef _BIG_ENDIAN
#    define BYTE_ORDER 4321
#  elif defined (_LITTLE_ENDIAN)
#    define BYTE_ORDER 1234
#  else
#    error "Unknown byteorder"
#  endif
static int ufs_blocks (char *, ino_t);
#else
#  error "Unknown system"
#endif
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <dirent.h>
#include <ctype.h>
#include "cdrom.h"
#include "floppy.h"
#ifndef __intel__
#include "prom.h"
#endif
#include "../first/first.h"
#include "../first/ultra.h"
#include "../first/cd.h"
#define DIGIT_OFFSET (DIGIT_OFFSET_TMP + 0x223)
#define LETTER_OFFSET (LETTER_OFFSET_TMP + 0x223)
#define ULTRA_LETTER_OFFSET (ULTRA_LETTER_OFFSET_TMP + 0x223)
#define CD_DIGIT_OFFSET (CD_DIGIT_OFFSET_TMP + 0x223)
#define CD_LETTER_OFFSET (CD_LETTER_OFFSET_TMP + 0x223)

#ifdef UFS_MINFREE
#define ufs_superblock ufs_super_block
#endif

struct sun_disklabel {
    unsigned char info[128];	/* Informative text string */
    unsigned char spare[292];	/* Boot information etc. */
    unsigned short rspeed;	/* Disk rotational speed */
    unsigned short pcylcount;	/* Physical cylinder count */
    unsigned short sparecyl;	/* extra sects per cylinder */
    unsigned char spare2[4];	/* More magic... */
    unsigned short ilfact;	/* Interleave factor */
    unsigned short ncyl;	/* Data cylinder count */
    unsigned short nacyl;	/* Alt. cylinder count */
    unsigned short ntrks;	/* Tracks per cylinder */
    unsigned short nsect;	/* Sectors per track */
    unsigned char spare3[4];	/* Even more magic... */
    struct sun_partition {
	unsigned long start_cylinder;
	unsigned long num_sectors;
    } partitions[8];
    unsigned short magic;	/* Magic number */
    unsigned short csum;	/* Label xor'd checksum */
};

#if BYTE_ORDER == 4321
__u32 swab32 (__u32 value)
{
    return ((value >> 24) | ((value >> 8) & 0xff00) |
	    ((value << 8) & 0xff0000) | (value << 24));
}

__u16 swab16 (__u16 value)
{
    return (value >> 8) | (value << 8);
}
#define bswab32(x) (x)
#define bswab16(x) (x)
#else
#define swab32(x) (x)
#define swab16(x) (x)
__u32 bswab32 (__u32 value)
{
    return ((value >> 24) | ((value >> 8) & 0xff00) |
	    ((value << 8) & 0xff0000) | (value << 24));
}

__u16 bswab16 (__u16 value)
{
    return (value >> 8) | (value << 8);
}
#endif

int ultra = 0;
unsigned char nsect;
unsigned short bs;
unsigned long doff;
int sda1_offset;
int zero_based_part;
char *cdrom_image = 0;
int floppy_image = 0;
char *cdrom_list = "";
int cd_size = 0;
__u32 blocks[512];
__u32 cd_files[31 * 2];
char *first, *second, *old;	/* File names */
int promver = -1;
int scsi = 0;
int ide = 0;
int oldroot = -1;
enum fs_type { unknownfs, ext2fs, ufsfs, iso9660fs, romfs } fstype = unknownfs;

void fatal (char *fmt,...)
{
    va_list ap;
    va_start (ap, fmt);
    fprintf (stderr, "Fatal error: ");
    vfprintf (stderr, fmt, ap);
    putc ('\n', stderr);
    va_end (ap);
    exit (1);
}

int check_fs (int fd)
{
    struct ufs_superblock ufs;
    struct ext2_super_block sb;	/* Super Block Info */
    struct iso_primary_descriptor isd;
    struct romfs_super_block {
	__u32 word0;
	__u32 word1;
	__u32 size;
	__u32 checksum;
    } rsd;

    if (lseek (fd, 1024, 0) != 1024 || read (fd, &sb, sizeof (sb)) != sizeof (sb))
	fatal ("Cannot read Super Block!");
    if (swab16 (sb.s_magic) == EXT2_SUPER_MAGIC) {
        if (fstype == unknownfs) fstype = ext2fs;
        return 1024 << swab32 (sb.s_log_block_size);
    }
    if (lseek (fd, 0, 0) != 0 || read (fd, &rsd, sizeof(rsd)) != sizeof (rsd))
    	return -1;
    if (!strncmp ((char *)&rsd, "-rom1fs-", 8)) {
        if (fstype == unknownfs) fstype = romfs;
    	return 512;
    }
    if (lseek (fd, 8192, 0) != 8192 || read (fd, &ufs, sizeof (ufs)) != sizeof (ufs))
        return -1;
    if (bswab32 (ufs.fs_magic) == UFS_MAGIC) {
        if (fstype == unknownfs) fstype = ufsfs;
        return ufs.fs_fsize;
    }
    if (lseek (fd, 32768, 0) != 32768 || read (fd, &isd, sizeof(isd)) != sizeof(isd))
        return -1;
    if (!strncmp ((char *)&isd, "\001CD001\001", 8)) {
        cd_size = swab32 (*(unsigned int *)(isd.volume_space_size)) * 2048;
        if (fstype == unknownfs) fstype = iso9660fs;
        return 1024;
    }
    return -1;
}

void read_sb (char *device, char *bootdev, char *device2)
{
    int fd, partno;
    char buff[512];
    struct sun_disklabel *sdl;
    struct ufs_superblock ufs;
    struct ext2_super_block sb;	/* Super Block Info */
    int offset, i, off = 0;

    sda1_offset = -1;
    zero_based_part = 0;
    if (oldroot != -1) {
    	fchdir(oldroot);
    	off = 1;
    }
    if ((fd = open (device + off, O_RDONLY)) == -1)
	fatal ("Cannot open superblock on %s", device);
    bs = check_fs (fd);
    if (bs == (unsigned short)-1)
	fatal ("File systems other than ext2, ufs and iso9660 not yet supported", device);
    close (fd);
    nsect = bs / 512;
    doff = 0;
    if (!scsi && !ide) return;
#ifdef __linux__
    partno = (int) (*(device + strlen (device) - 1) - '0') - 1;
#elif defined(__solaris__)
    partno = (int) (*(device + strlen (device) - 1) - '0');
#endif
    sdl = (struct sun_disklabel *) &buff;
    if ((fd = open (bootdev + off, O_RDONLY)) == -1)
	fatal ("Error opening %s", bootdev);
    if (read (fd, buff, sizeof (buff)) != sizeof (buff))
        fatal ("Error reading %s's label", bootdev);
    if (!sdl->partitions[0].start_cylinder)
        zero_based_part = 0;
    else if (!sdl->partitions[2].start_cylinder)
        zero_based_part = 2;
    else {
        for (i = 1; i < 8; i++)
            if (!sdl->partitions[i].start_cylinder) {
                zero_based_part = i;
                break;
            }
        if (i == 8)
            fatal ("In order to install SILO you must start at least one partition on cylinder\n"
                   "0. Consider setting sda3 as WHOLE_DISK (starting at 0 and ending at disk end\n"
                   "and sda1 starting on cylinder 0 (both will make you life easier)\n");
    }
    doff = bswab16(sdl->ntrks) * bswab16(sdl->nsect) * bswab32(sdl->partitions[partno].start_cylinder);
    offset = bswab16(sdl->ntrks) * bswab16(sdl->nsect) * bswab32(sdl->partitions[0].start_cylinder);
    close (fd);
    if (offset) {
        if ((fd = open (device2 + off, O_RDONLY)) == -1)
	    fatal ("Cannot open superblock on %s", device2);
        if (check_fs (fd) != -1)
            sda1_offset = offset;
        close (fd);
    }
    if (oldroot != -1)
    	chdir("/");
}

#define SECOND_B_TEMPL "%s___@#$%d___"

void gpb_cleanup(char *filename, int n)
{
    char buffer[1024];
    int i;
    
    for (i = 1; i <= n; i++) {
	sprintf (buffer, SECOND_B_TEMPL, filename, i);
	unlink (buffer);
    }
}

int get_partition_blocks (char *device, char *filename)
{
    int fd, fd2;
    int block, i, j, k;
    struct stat st;
    int movecount = 0;
    char buffer[1024];
    char *name = filename;
    char *buf = NULL;
    int size;
    
again:
    if ((fd = open (name, O_RDONLY)) == -1) {
    	gpb_cleanup(filename, movecount);
	fatal ("Cannot find %s", name);
    }
    if (fstat (fd, &st) < 0) {
    	gpb_cleanup(filename, movecount);
        fatal ("Couldn't stat %s", name);
    }
#ifdef __linux__
    if (!movecount)
    	size = st.st_size;
    if (fstype == romfs) {
    	char *p = strrchr (name, '/');
    	long start;
    	if (p) p++; else p = name;
    	start = (long)st.st_ino + 16 + ((strlen(p) + 16) & ~15);
    	if (start & 511)
    		fatal ("File on romfs not aligned on 512B boundary. Use genromfs -a -r to generate the image");
    	start = doff + start / 512;
    	for (j = 0; j * 512 < size; j++)
    		blocks[j] = start + j;
    	blocks[j] = 0;
    	close(fd);
    	return 0;
    }
    for (i = 0, j = 0;; i++) {
	block = i;
	if ((j << 9) >= size || ioctl (fd, FIBMAP, &block) < 0)
	    break;
	if (!block) {
	    if ((j << 9) < size)
	        fatal ("Filesystem holes are not yet supported for second stage loader. Mail jj@sunsite.mff.cuni.cz");
	    else
	        break;
	}
	if (!ultra && block * nsect + doff >= 0x200000) {
	    if (movecount < 5) {
	    	if (!movecount) {
	    		buf = malloc (size);
	    		if (!buf)
	    			fatal ("Not enough memory");
	    		if (read (fd, buf, size) != size)
	    			fatal ("Cannot read from %s", filename);
	    	}
	    	close (fd);
	    	movecount++;
	    	sprintf (buffer, SECOND_B_TEMPL, filename, movecount);
	    	name = buffer;
	    	fd = creat (name, 0644);
	    	if (!fd || write (fd, buf, size) != size) {
    			gpb_cleanup(filename, movecount);
	    		fatal ("Your %s is located above the magic 1GB boundary from the start of the disk.\n"
	    	   		"Please move it down, so that SILO first stage loader can load it.", filename);
	    	}
	    	close (fd);
	    	goto  again;
	    }
    	    gpb_cleanup(filename, movecount);
	    fatal ("Your %s is located above the magic 1GB boundary from the start of the disk.\n"
	    	   "Please move it down, so that SILO first stage loader can load it.", filename);
	}
	for (k = 0; k < nsect; k++)
	    blocks[j++] = block * nsect + doff + k;
    }
    blocks[j] = 0;
    if (buf) free (buf);
    if (movecount) {
    	if (rename (name, filename) < 0) {
    	    gpb_cleanup(filename, movecount - 1);
    	    fatal ("Cannot rename a suitably located copy (below 1GB from start of disk) of %s to it's old position.\n"
    	    	   "Please check %s\n", filename, filename);
    	}
    	gpb_cleanup(filename, movecount - 1);
    }
#elif defined(__solaris__)
    ufs_blocks (device, st.st_ino);
#endif
    close (fd);
    return 0;
}

char *new_root = NULL;

int get_cdrom_file (int i, char *filename)
{
#ifdef __linux__
    int fd;
    int block;
    struct stat st;
    struct statfs sf;
    char *p = strdup (filename);

    if ((fd = open (p, O_RDONLY)) == -1) {
	fatal ("Cannot find %s", filename);
    }
    free (p);
    block = 0;
    if (ioctl (fd, FIBMAP, &block) < 0 || !block)
        fatal ("Couldn't get block # of %s", filename);
    if (fstatfs (fd, &sf))
        fatal ("Couldn't statfs %s", filename);
    if (fstat (fd, &st) < 0)
        fatal ("Couldn't stat %s", filename);
    if (sf.f_bsize < 1024 || sf.f_bsize % 1024)
        fatal ("CDs have to use at least 1K blocksize");
    cd_files [2 * i] = (block * (sf.f_bsize / 1024));
    cd_files [2 * i + 1] = st.st_size;
    close (fd);
#endif
    return 0;
}

int get_cdrom_files (char *config_file)
{
    int i;
    char *p = cdrom_list, *q;
    
    get_cdrom_file (0, config_file);
    for (i = 1; i < 31; i++) {
        q = strchr (p, ',');
        if (!q) break;
        *q = 0;
        get_cdrom_file (i, p);
        p = q + 1;
    }
    if (i < 31) get_cdrom_file (i, p);
}

void write_block_device (char *device, int cdimg)
{
    int fd, rc;
    int offset;
    __u32 tmp;
    unsigned char part;
    int off = 0;
    
    if (oldroot != -1) {
    	fchdir(oldroot);
    	off = 1;
    }
    if ((fd = open (device + off, O_RDWR)) == -1)
	fatal ("Cannot open %s", device);
    if (cdimg) off = 1020 + 512;
    else off = 1020;
    if (lseek (fd, off, SEEK_SET) != off)
	fatal ("Seek error on %s", device);
    tmp = bswab32 (blocks[0]);
    rc = write (fd, &tmp, 4);
    if (rc != 4)
    	fatal ("Couldn't write to %s", device);
    if (!ultra || cdimg) {
    	    if (cdimg)
    	        offset = CD_DIGIT_OFFSET;
    	    else
    	        offset = DIGIT_OFFSET;
	    if (lseek (fd, offset, SEEK_SET) != offset)
		fatal ("Seek error on %s", device);
	    part = zero_based_part + '0';
	    rc = write (fd, &part, 1);
	    if (rc != 1)
	    	fatal ("Couldn't write to %s", device);
    }
    if (cdimg)
        offset = CD_LETTER_OFFSET;
    else if (ultra)
        offset = ULTRA_LETTER_OFFSET;
    else
        offset = LETTER_OFFSET;
    if (lseek (fd, offset, SEEK_SET) != offset)
	fatal ("Seek error on %s", device);
    part = zero_based_part + 'a';
    rc = write (fd, &part, 1);
    if (rc != 1)
    	fatal ("Couldn't write to %s", device);
    close (fd);
    if (oldroot != -1)
    	chdir("/");
}

void write_block_table (char *device, char *device2, char *filename, char *config_file, int partno, int cdimg)
{
    int fd, rc, i;
    char buffer[260];
    __u32 blks [sizeof (blocks) / 4];
    __u32 *Blocks = blocks;
    __u32 cdrom_files [sizeof (cd_files) / 4];
    int offset = 0;
    int tordonly = 0;
    int off = 0;

    if (cdrom_image) { 
    	filename = device;
    	if (oldroot != -1) {
    	    fchdir(oldroot);
    	    off = 1;
    	}
    }
    if ((fd = open (filename + off, O_RDWR)) == -1) {
        if (!cdrom_image && (fd = open (filename, O_RDONLY)) >= 0) {
            off = 0;
            close (fd); /* Maybe it is a not-yet-rw-ufs?? or romfs */
            if (oldroot != -1) {
		fchdir(oldroot);
		off = 1;
	    }
            if ((fd = open (device + off, O_RDWR)) == -1)
	    	fatal ("Cannot open %s", device);
	    if (oldroot != -1)
	    	chdir("/");
	    tordonly = 1;
        } else
	    fatal ("Cannot open %s", filename);
    }
    if (cdrom_image) {
        if (oldroot != -1)
            chdir("/");
        offset = 512 * blocks [0];
    }
    if (!tordonly && lseek (fd, offset, SEEK_SET) != offset)
        fatal ("Cannot seek in %s", filename);
#if BYTE_ORDER == 1234
    for (i = 0; i < sizeof (blocks) / 4; i++)
        blks [i] = bswab32 (blocks[i]);
    if (cdrom_image) {
        for (i = 0; i < sizeof (cd_files) / 4; i++)
            cdrom_files [i] = bswab32 (cd_files[i]);
        memcpy (buffer + 12, cdrom_files, 248);
    }
    Blocks = blks;
#else
    if (cdrom_image)
        memcpy (buffer + 12, cd_files, 248);
#endif
    if (tordonly) {
        char *p = (char *)Blocks, *pend = p + sizeof (blocks);
        
        for (i = 0, rc = 0; p < pend; i++, p+=512) {
            if (lseek (fd, blocks [i] * 512, SEEK_SET) != blocks [i] * 512)
        	fatal ("Cannot seek in %s", filename);
            rc += write (fd, p, 512);
        }
    } else
        rc = write (fd, Blocks, sizeof (blocks));
    if (rc != sizeof (blocks)) fatal ("Couldn't write to %s", filename);
    if (tordonly)
    	i = blocks [0x808/512] * 512 + 0x808 % 512;
    else
    	i = 0x808 + offset;
    if (lseek (fd, i, SEEK_SET) != i)
        fatal ("Cannot seek in %s", filename);
    buffer[0] = 'L';
    buffer[1] = partno;
    buffer[2] = zero_based_part;
    if (cdrom_image)
        strcpy (buffer + 4, "!cd0");
    else {
        strncpy (buffer + 4, config_file, 255);
        buffer[259] = 0;
    }
    if (write (fd, buffer, 260) != 260) fatal ("Couldn't write to %s", filename);
    close (fd);
    write_block_device (device, cdimg);
    if (!cdimg && sda1_offset != -1)
        write_block_device (device2, 0);
}

void usage (char *s)
{
    fprintf (stderr,
            "SILO " VERSION " Sparc Improved boot LOader\n"
            "Usage: %s [options]\n"
            "Options:\n"
            " -r root_path   chroots into root_path (all paths relative to this)\n"
            " -b secondary   use secondary as second stage loader instead of /boot/second.b\n"
            " -i primary     install primary as first stage loader (instead of\n"
            "                /boot/first.b). If -i is specified, your boot block will be\n"
            "                always overwritten (by default only if it is not SILO or has\n"
            "                wrong version)\n"
            " -C config      specify alternate config file instead of /etc/silo.conf\n"
            "                (your config file has to reside on the same physical disk as\n"
            "                secondary - perhaps on different partitions - on that disk's\n"
            "                bootblock will be primary installed)\n"
            " -S backup      force saving your old bootblock into backup\n"
            " -s backup      save your old bootblock only if backup doesn't exist yet\n"
            " -o backup      save your old bootblock from 1st partition if sdX1 doesn't\n"
            "                start on cylinder 0 and backup doesn't exist yet\n"
            " -O backup      as -o, but force saving of 1st partition bootblock\n"
            " -p 0|2         force prom version to be 0 or 2 (default is auto-detection)\n"
            " -f             force overwriting of bootblock\n"
            " -d             print PROM device name of the bootblock on stdout\n"
            " -V             show version\n"
            " -v             print your PROM major version (0 or 2) and exit\n"
            " -c             device/file image for silo-bootable cdrom preparation\n"
            " -l list        list is comma separated list of files (relative to -r)\n"
            "                which will be available through !cd1 - !cd30 from cd\n"
            "                this switch makes sense with -c and -r only\n"
            " -u             force v9 (ultrasparc) target, even on v8\n"
            " -U             force v8 target, even on v9\n"
            " -F             make a romfs bootable floppy\n"
            ,s);
    exit (1);
}

int examine_bootblock (char *device, char *filename, int do_backup, int promver)
{
    char buffer[512 * 15];
    struct Block_Table *sbt;
    int fd, rc;
    FILE *fp;
    int ret = 0;
    int off = 0;

    if (oldroot != -1) {
    	fchdir(oldroot);
    	off = 1;
    }
    if ((fd = open (device + off, O_RDONLY)) == -1)
	fatal ("Cannot open %s", device);
    if (lseek (fd, 512, 0) != 512)
        fatal ("Couldn't seek on %s", device);
    if (read (fd, buffer, sizeof (buffer)) != sizeof(buffer))
        fatal ("Couldn't read your old bootblock");
    close (fd);
    if (oldroot != -1)
    	chdir("/");
    if (memcmp (buffer + 24, "SILO" IMGVERSION, 8))
        ret = 1;

    if (do_backup) {
    	if ((fp = fopen (filename, "w")) == NULL) {
    	    if (do_backup >= 2)
    	    	return ret;
	    fatal ("Cannot open file for saving backup of your bootblock");
	}
        if (rc = fwrite (buffer, 1, sizeof (buffer), fp) != sizeof (buffer))
            fatal ("Couldn't write to %s backup of your bootblock", filename);
        fclose (fp);
    }
    return ret;
}

void install_first_stage (char *device, char *filename, int cdimg)
{
    char buff[1024];
    int rc;
    int fd;
    FILE *fp;
    struct sun_disklabel sdl;
    int off = 0;

    if (oldroot != -1) {
    	fchdir(oldroot);
    	off = 1;
    }
    if ((fd = open (device + off, floppy_image ? O_RDWR : O_WRONLY)) == -1)
	fatal ("Couldn't open device %s for writing", device);
    if (oldroot != -1)
    	chdir("/");
    if ((fp = fopen (filename, "r")) == NULL)
	fatal ("Couldn't open file %s", filename);
    rc = fread (buff, 1, cdimg ? 1024 : 512, fp);
    if ((!cdimg && rc != 512) || rc <= 0)
	fatal ("Couldn't read new silo bootblock from %s", filename);
    if (cdrom_image) {
        int i = ((cd_size + 511) >> 9), csum;
        unsigned short *ush;
        
        memcpy (&sdl, cdrom_label, 512);
        sdl.pcylcount = bswab16 ((i + 639) / 640);
        sdl.ncyl = bswab16 ((i + 639) / 640);
        sdl.partitions[0].num_sectors = i;
        for (csum = 0, ush = (unsigned short *)&sdl; ush < (unsigned short *)&(sdl.csum); csum ^= *ush++);
        sdl.csum = csum;
        if (write (fd, &sdl, 512) != 512)
            fatal ("Couldn't write to %s your new partition table", device);
    } else if (floppy_image) {
        unsigned short *ush;
        unsigned short x;
        unsigned int s, d;
        int i;

	if (lseek (fd, 0, 0))
		fatal ("Couldn't seek on %s", device);
	if (read(fd, (char *)&sdl, 512) != 512)
		fatal ("Couldn't read on %s", device);
	if (lseek (fd, 0, 0))
		fatal ("Couldn't seek on %s", device);
	if (strncmp ((char *)&sdl, "-rom1fs-", 8))
		fatal ("Couldn't find romfs image on %s", device);
        memcpy (((char *)&sdl) + 128, floppy_label + 128, 512 - 128);
        ush = (unsigned short *)&sdl;
        
        /* This is a bit difficult, since we have to make two checksums right:
           partition table expects xor of all u16 in the first 512 bytes to be 0,
           romfs expects sum of all u32 in the first 512 bytes to be 0.
           Algorithm written by Gert-jan Los (los@lsdb.bwl.uni-mannheim.de). */
        sdl.csum = 0xdead; /* Could be zero... */
        ush[6] = 0; ush[7] = 0;
        memset (ush + 60, 0, 8);
	for (i = 0, d = 0; i < 256; i+=2)
        	d += *(unsigned int *)(ush + i);
	for (i = 0, x = 0; i < 256; i++)
        	x ^= ush[i];
        if (d & 1)
        	s = (x ^ 1) << 16 ^ 1;
        else
        	s = x << 16;
        *(unsigned int *)(ush + 6) = s;
        d += s;
        *(unsigned int *)(ush + 60) = (-d/2);
        *(unsigned int *)(ush + 62) = (-d/2);

        if (write (fd, &sdl, 512) != 512)
            fatal ("Couldn't write to %s your new partition table", device);
    } else if (lseek (fd, 512, 0) != 512)
        fatal ("Couldn't seek on %s", device);
    if (write (fd, buff, rc) != rc)
        fatal ("Couldn't write to %s your new silo bootblock", device);
    close (fd);
    fclose (fp);
}

int get_prom_ver(int use_prom)
{
#ifdef __intel__
    return 0;
#elif defined(__linux__)
    FILE *f = fopen("/proc/cpuinfo","r");
    int ver = -1;
    char buffer[1024];
    char *p;
    
    if (f) {
    	while (fgets(buffer, 1024, f)) {
    	    if (!strncmp (buffer, "promlib", 7)) {
    	        p = strstr (buffer, "Version ");
    	        if (p) {
    	            p += 8;
    	            if (*p == '0' || (*p >= '2' && *p <= '3')) {
    	                ver = *p - '0';
    	            }
    	        }
    	        break;
    	    }
    	}
    }
    if (ver == -1)
        fatal ("Couldn't determine your prom version number. Please use options\n"
               "-p0 (for version 0) and -p2 (for versions 2 and 3)");
    return ver;
#elif defined(__solaris__)
    int ver = -1;
    
    if (use_prom)
        ver = prom_getversion ();
    if (ver == -1)
        fatal ("Couldn't determine your prom version number. Please use options\n"
               "-p 0 (for version 0) and -p 2 (for versions 2 and 3)\n"
               "Your prom is probably version 0, if you use device names of type sd(0,3,0)\n"
               "and is probaly 2 if you use device names of type /iommu/sbus/espdma/esp/sd@3,0\n");
    return ver;
#endif
}

/* Canonicalize path, and return a new path. Do everything in situ.
   The new path differs from path in:
	Multiple `/'s are collapsed to a single `/'.
	Leading `./'s and trailing `/.'s are removed.
	Trailing `/'s are removed.
	Non-leading `../'s and trailing `..'s are handled by removing
	portions of the path. */
char *canonicalize_pathname (char *path)
{
    int i, start;
    char stub_char;

    stub_char = (*path == '/') ? '/' : '.';

    /* Walk along path looking for things to compact. */
    i = 0;
    for (;;) {
        if (!path[i])
	    break;

      	while (path[i] && path[i] != '/')
	    i++;

      	start = i++;

      	/* If we didn't find any slashes, then there is nothing left to do. */
      	if (!path[start])
	    break;

        /* Handle multiple `/'s in a row. */
        while (path[i] == '/')
	    i++;

        if ((start + 1) != i) {
	    strcpy (path + start + 1, path + i);
	    i = start + 1;
	}

        /* Handle backquoted `/'. */
        if (start > 0 && path[start - 1] == '\\')
	    continue;

        /* Check for trailing `/'. */
        if (start && !path[i]) {
	zero_last:
	    path[--i] = '\0';
	    break;
	}

        /* Check for `../', `./' or trailing `.' by itself. */
        if (path[i] == '.') {
	    /* Handle trailing `.' by itself. */
	    if (!path[i + 1])
	        goto zero_last;

	    /* Handle `./'. */
	    if (path[i + 1] == '/') {
	        strcpy (path + i, path + i + 1);
	        i = start;
	        continue;
	    }

	    /* Handle `../' or trailing `..' by itself. 
	       Remove the previous ?/ part with the exception of
	       ../, which we should leave intact. */
	    if (path[i + 1] == '.' && (path[i + 2] == '/' || !path[i + 2])) {
	        while (--start > -1 && path[start] != '/');
	        if (!strncmp (path + start + 1, "../", 3))
	            continue;
	        strcpy (path + start + 1, path + i + 2);
	        i = start;
	        continue;
	    }
	}
    }

    if (!*path) {
        *path = stub_char;
        path[1] = '\0';
    }
    return path;
}

char *find_dev(dev_t number)
{
#ifdef __linux__
#  define DEVNAME "/dev"
#else
#  define DEVNAME "/dev/dsk"
#endif
    DIR *dp;
    char *p;
    struct dirent *dir;
    static char name[PATH_MAX+2];
    struct stat s;
    char *ret = NULL;

    p = DEVNAME;
    if (oldroot != -1) {
    	fchdir(oldroot);
    	p++;
    }
    if (!number) goto out;
    if ((dp = opendir(p)) == NULL) goto out;
    strcpy(name + 1,p);
    p = strchr (name + 1, 0);
    *p++ = '/';
    while (dir = readdir(dp)) {
        strcpy(p,dir->d_name);
        if (stat(name + 1,&s) < 0) return NULL;
        if (S_ISBLK(s.st_mode) && s.st_rdev == number) {
        	ret = name + 1;
        	if (oldroot != -1) {
        		name[0] = '/';
        		ret--;
        	}
        	break;
        }
    }
out:
    if (oldroot != -1)
    	chdir("/");
    return ret;
}

char *find_bootpath (char *device)
{
    static char buffer[2048];
    int i, part;
#ifdef __linux__
#ifdef __intel__
    return 0;
#else
    int fd;
    int id;
    /* For now assume there is only one SCSI HA in the machine */
    if (!scsi) return 0;
    if (strncmp (device, "/dev/sd", 7) || device[7] < 'a' || device[7] > 'z' ||
        (device[8] && !isdigit(device[8]))) return 0;
    fd = open (device, O_RDONLY);
    if (fd < 0) return 0;
    if (ioctl (fd, SCSI_IOCTL_GET_IDLUN, &i) < 0) return 0;
    id = i & 0xff;
    part = device[8] ? (device[8] - '1') : 0;
    close (fd);
    if (!promver) {
        sprintf(buffer, "sd(0,%d,%d)", id, part);
        return buffer;
    } else {
        char *p;
        
        sprintf(buffer, "/esp/sd@%d,0:%c", id, part + 'a');
        p = prom_canon (buffer);
        if (!p) return 0;
        strcpy (buffer, p);
        return buffer;
    }
#endif    
#elif defined (__solaris__)
    if (!promver) {
        if (strncmp (device, "/dev/dsk/c", 10) || strlen (device) != 17 ||
            !isdigit(device[10]) || !isdigit(device[12]) || !isdigit(device[16]))
            return 0;
        sprintf(buffer, "sd(%d,%d,%d)", device[10] - '0', device[12] - '0', device[14] - '0');
        return buffer;
    } else {
        i = readlink (device, buffer, 512);
        if (i < 0) return 0;
        buffer [i] = 0;
        if (strncmp(buffer, "../../devices/", 14)) return 0;
        return buffer + 13;
    }
#endif
}

int main(int argc,char **argv)
{
    char *name = NULL, *config_file, *install = NULL, *secondary, *backup, *backup2;
    struct utsname uts;
    int version = 0;
    int cdimg = 0;
    struct stat st1, st2, st3;
    int fd;
    int force_backup = 0, force_backup2 = 0;
    int config_file_partno = 1;
    char bootdev[1024], spart[1024], bootdev2[1024];
    int force = 0;
    int f, unit;
    int print_device = 0;
    int use_prom = 0;
    int print_prom_version = 0;

#ifndef __intel__
    if (prom_init () >= 0)
        use_prom = 1;
#endif
    if (uname (&uts) >= 0 && !strcmp (uts.machine, "sparc64"))
    	ultra = 1;
    config_file = DFL_CONFIG;
    backup = DFL_BACKUP;
    backup2 = DFL_BACKUP2;
    secondary = DFL_SECONDARY;
    new_root = NULL;
    name = *argv++;
    argc--;
    while (argc && **argv == '-') {
	argc--;
	if (argv[0][2] && argv[0][1] != 'p') usage(name);
	switch ((*argv++)[1]) {
	    case 'b':
	        if (!argc) usage(name);
	        secondary = *argv++;
	        argc--;
	        break;
	    case 'i':
		if (!argc) usage(name);
		install = *argv++;
		argc--;
		break;
	    case 'c':
	        if (!argc) usage(name);
#ifndef __linux__
		fatal("Please use Linux to build SILO bootable CDROMs");
#endif	        
	        cdrom_image = *argv++;
	        argc--;
	        break;
	    case 'F':
	        floppy_image = 1;
	        break;
	    case 'p':
	        if ((argv[-1])[2] == '0')
	            promver = 0;
	        else if ((argv[-1])[2] == '2')
	            promver = 2;
	        else if (argv[-1][2])
	            usage(name);
	        else if (argc && **argv == '0') {
	            promver = 0;
	            argv++;
	            argc--;
	        } else if (argc && **argv == '2') {
	            promver = 2;
	            argv++;
	            argc--;
	        } else usage(name);
	        break;
	    case 'd':
	        print_device = 1;
	        break;
	    case 'f':
	    	force = 1;
	    	break;
	    case 'C':
		if (!argc) usage(name);
		config_file = *argv++;
		argc--;
		break;
	    case 'S':
		if (!argc) usage(name);
		backup = *argv++;
		force_backup = 1;
		argc--;
		break;
	    case 's':
		if (!argc) usage(name);
		backup = *argv++;
		argc--;
		break;
	    case 'o':
	        if (!argc) usage(name);
	        backup2 = *argv++;
	        argc--;
	        break;
	    case 'O':
	        if (!argc) usage(name);
	        backup2 = *argv++;
	        force_backup2 = 1;
	        argc--;
	        break;
	    case 'r':
		if (!argc) usage(name);
		new_root = *argv++;
		argc--;
		break;
	    case 'l':
	        if (!argc) usage(name);
	        cdrom_list = *argv++;
	        argc--;
	        break;
	    case 'V':
		version = 1;
		break;
	    case 'v':
	    	print_prom_version = 1;
	    	break;
	    case 'u':
	        ultra = 1;
	        break;
	    case 'U':
	        ultra = 0;
	        break;
	    default:
		usage(name);
	}
    }
    if (!cdrom_image && !floppy_image && promver == -1) promver = get_prom_ver(use_prom);
    if (print_prom_version) {
        printf ("%d\n", promver);
        return 0;
    }
    if (!new_root) new_root = getenv("ROOT");
    if ((!new_root && cdrom_image) || (!cdrom_image && *cdrom_list) || (cdrom_image && !*cdrom_list))
        fatal ("When preparing a cdrom image, you have to specify -r with the path\n"
               "the cdrom image is mounted to, -c with the path of device/file which\n"
               "holds the image and -l with a comma separated list of files accessible\n"
               "via !cd1 - !cd30. Example:\n"
               "silo -r /mnt -c /dev/sda2 -l /vmlinux,/etc/welcome.msg,/initrd.image\n");
    if (cdrom_image && (promver != -1 || force || strcmp (backup, DFL_BACKUP) || strcmp (backup2, DFL_BACKUP2)))
        fatal ("Options -p, -f, -s, -S, -o, -O are not suitable for making cdroms");
    if (version) {
	fprintf(stderr, "SILO version " VERSION "\n");
	return 0;
    }
    if (argc) usage(name);
#ifdef __intel__
    if (!cdrom_image) fatal ("intelsilo is suitable for making Sparc bootable CD-ROMS only.\n"
                             "Use -r, -c and -l options for that or run intelsilo -? for help");
#endif    

    if (new_root && strcmp("/", new_root)) {
       oldroot = open("/", O_RDONLY);
       chroot(new_root);
       chdir("/");
    }

    secondary = strdup (secondary);
    if (stat (secondary, &st1) < 0)
        fatal ("Cannot open second stage loader %s", secondary);
    if (cdrom_image) {
        strcpy (spart, cdrom_image);
        strcpy (bootdev, cdrom_image);
    } else switch (mmajor(st1.st_dev)) {
#ifdef __linux__
	case 8:
	case 65:
	case 66:
	case 67:
	case 68:
	case 69:
	case 70:
	case 71:
		unit = (mmajor(st1.st_dev) & 7) * 16 + ((mminor(st1.st_dev) & 0xf0) >> 4);
		if (unit < 26) {
			sprintf (spart, "/dev/sd%c%c", unit + 'a', (mminor(st1.st_dev) & 0xf) + '0');
			sprintf (bootdev2, "/dev/sd%c1", unit + 'a');
			strcpy (bootdev, spart);
			bootdev [8] = 0;
		} else {
			sprintf (spart, "/dev/sd%c%c%c", (unit / 26) + 'a' - 1, (unit % 26) + 'a', (mminor(st1.st_dev) & 0xf) + '0');
			sprintf (bootdev2, "/dev/sd%c%c1", (unit / 26) + 'a' - 1, (unit % 26) + 'a');
			strcpy (bootdev, spart);
			bootdev [9] = 0;
		}
		scsi = 1;
		break;
	case 3: ide = 1; unit = 0; goto do_ide;
	case 22:ide = 1; unit = 2; goto do_ide;
	case 33:
	case 34:ide = 1; unit = 2*(mmajor(st1.st_dev) - 33) + 4;
	do_ide:	
		unit += ((mminor(st1.st_dev) & 64) >> 6);
		sprintf (spart, "/dev/hd%c%d", unit + 'a', (mminor(st1.st_dev) & 0x3f));
		sprintf (bootdev2, "/dev/hd%c1", unit + 'a');
		strcpy (bootdev, spart);
		bootdev [8] = 0;
		break;
#endif	
	default: {
		char *p = find_dev (st1.st_dev);

		if (!p) fatal ("Couldn't find out what device is second stage on");
		strcpy (spart, p);
		strcpy (bootdev, p);
		strcpy (bootdev2, p);
#ifdef __solaris__
		if (strlen (p) == strlen ("/dev/dsk/c0t0d0s0")) {
		    p = strchr (p, 0) - 2;
		    if (*p == 's' && p[1] >= '0' && p[1] <= '7') {
	        	p = strchr (bootdev, 0) - 1;
		        *p = '2';
		        p = strchr (bootdev2, 0) - 1;
	        	*p = '0';
		        scsi = 1;
		    }
		}
#endif
		}
		break;
    }
    backup = strdup (backup);
    backup2 = strdup (backup2);
    if (!cdrom_image)
        config_file = strdup (config_file);
    if (!cdrom_image && stat (config_file, &st2) >= 0) {
#ifdef __linux__    
        if ((scsi && (mmajor(st2.st_dev) != mmajor(st1.st_dev) || (mminor(st2.st_dev) & (~0xf)) != (mminor(st1.st_dev) & (~0xf)))) ||
            (ide && (mmajor(st2.st_dev) != mmajor(st1.st_dev) || (mminor(st2.st_dev) & (~0x3f)) != (mminor(st1.st_dev) & (~0x3f)))) ||
#elif defined(__solaris__)
	if ((scsi && (st2.st_dev & (~0x7)) != (st1.st_dev & (~0x7))) ||
#else
#  error "Unknown system"
#endif        
	    (!scsi && st2.st_dev != st1.st_dev))
            fatal ("Config file %s has to be on the %s device (on any partition there)", config_file, bootdev);
        else {
            char *p, *q, *r, c;
     	    char readlinkbuf[2048];
     	    int len;
     	    char buffer[2048], buffer2[2048];

     	    strcpy (buffer, config_file);
	    for (p = buffer;;) {
	        q = strchr (p, '/');
	        if (q) {
	            c = *q;
	            *q = 0;
	        } else c = 0;
	        if (lstat (*buffer ? buffer : "/", &st3) < 0)
	            fatal ("Couldn't stat %s\n", config_file);
	        if (st3.st_dev == st2.st_dev) {
	            *q = c;
	            config_file = q;
	            break;
	        }
	        if (S_ISLNK(st3.st_mode)) {
	            len = readlink (buffer, readlinkbuf, 2048);
	            if (len < 0)
	                fatal ("Couldn't readlink %s\n", config_file);
	            readlinkbuf[len] = 0;
	            if (*readlinkbuf == '/') {
	                if (c) {
	                    r = readlinkbuf+len;
	                    if (readlinkbuf[len - 1] != '/')
	                        *r++ = '/';
	                    strcpy (r, q + 1);
	                }
	                strcpy (buffer, readlinkbuf);
	        	p = buffer + 1;
	        	continue;
	            } else {
	            	strcpy (buffer2, buffer);
	            	strcat (buffer2, "/");
	            	strcat (buffer2, readlinkbuf);
	            	if (c) {
	            	    strcat (buffer2, "/");
	            	    strcat (buffer2, q + 1);
	            	}
	            	strcpy (buffer, buffer2);
	            	p = buffer + 1;
	            	continue;
	            }
	        } else {
	            *q = c;
	            p = q + 1;
	            if (!c)
	            	fatal ("Internal error\n");
	        }
	    }     	               
        }
	if (scsi)
#ifdef __linux__	
            config_file_partno = (mminor(st2.st_dev) & 0x0f);
#elif defined(__solaris__)
	    config_file_partno = (st2.st_dev & 7) + 1;
#else
#  error "Unknown system"
#endif
#ifdef __linux__	
	else if (ide)            
            config_file_partno = (mminor(st2.st_dev) & 0x3f);
#endif
	else
	    config_file_partno = 1;
    }
    if (backup && !force_backup) {
        if (stat (backup, &st2) < 0)
            force_backup = 2;
    }    
    read_sb (spart, bootdev, bootdev2);
    if (sda1_offset == -1)
        force_backup2 = 0;
    else if (backup2 && !force_backup2) {
        if (stat (backup2, &st2) < 0)
            force_backup2 = 2;
    }
    if (!cdrom_image)
        f = examine_bootblock (bootdev, backup, force_backup, promver);
    if (cdrom_image || 
        (sda1_offset != -1 && examine_bootblock (bootdev2, backup2, force_backup2, promver)) || 
        f || install || force) {
        if (!install) {
            if (cdrom_image)
            	install = strdup (DFL_PRIMARY_CD);
            else
            	install = strdup (ultra ? DFL_PRIMARY_U : DFL_PRIMARY);
        }
        else if (*install == '/')
            install = strdup (install);
        if (stat (install, &st2) >= 0 && st2.st_size == 1024)
            cdimg = 1;
	install_first_stage (bootdev, install, cdimg);
	if (!cdrom_image && !cdimg && sda1_offset != -1)
	    install_first_stage (bootdev2, install, 0);
    }
    if (cdrom_image) {
        int i, j, k;
    	get_cdrom_file (0, secondary);
    	j = cd_files[0] * 2;
    	k = (cd_files[1] + 511) / 512;
    	for (i = 0; i < k; i++)
    		blocks[i] = j + i;
        get_cdrom_files (config_file);
    } else
    	get_partition_blocks (spart, secondary);
    write_block_table (bootdev, bootdev2, secondary, config_file, config_file_partno, cdimg);
    sync();
#ifndef __intel__
    if (scsi && use_prom) {
        char *p = find_bootpath (bootdev2);
        
        if (p) {
            if (print_device)
                printf ("%s\n", p);
            else {
                char *q, buffer[2048], *r;
                int differ = 0, i, j;
                
                if (!promver) {
                    q = prom_getopt ("boot-from");
                    if (q) {
                    	strcpy (buffer, q);
                    	q = strchr (buffer, ')');
                    	if (q) {
                    	    q[1] = 0;
                    	    q = buffer;
                            if (strcmp (p, q))
                                differ = 1;
                        }
                    }
                } else {
                    q = prom_getopt ("boot-device");
                    if (q) {
                        strcpy (buffer, q);
                        q = prom_canon (buffer);
                        if (q) {
                            if (strcmp (q, p)) {
                                i = strlen (p);
                                j = strlen (q);
                                if (i != j + 2 || p[j] != ':' || p[j+1] != 'a')
                                    differ = 1;
                            }
                        }
                    }
                }
                if (differ) {
                    fprintf (stderr, "Warning: If you want to boot SILO, please check if you're booting"
                                     " from %s\n", p);
                }
            }
        }
    }
#endif
    exit(0);
}

#ifdef __solaris__

static errcode_t std_open (const char *name, int flags, io_channel * channel);
static errcode_t std_close (io_channel channel);
static errcode_t std_set_blksize (io_channel channel, int blksize);
static errcode_t std_read_blk (io_channel channel, unsigned long block, int count, void *data);
static errcode_t std_write_blk (io_channel channel, unsigned long block, int count, const void *data);
static errcode_t std_flush (io_channel channel);

static struct struct_io_manager struct_std_manager =
{
    EXT2_ET_MAGIC_IO_MANAGER,
    "linux I/O Manager",
    std_open,
    std_close,
    std_set_blksize,
    std_read_blk,
    std_write_blk,
    std_flush
};

static ufs_filsys fs = NULL;
static io_manager std_io_manager = &struct_std_manager;
static int std_fd = 0;

static unsigned int cbs = 1024;		/* Block Size */

static errcode_t std_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;
    std_fd = open (name, O_RDONLY);
    if (std_fd < 0)
    	fatal ("Cannot open %s", name);
    memset (io, 0, sizeof (struct struct_io_channel));
    io->magic = EXT2_ET_MAGIC_IO_CHANNEL;
    io->manager = std_io_manager;
    io->name = (char *) malloc (strlen (name) + 1);
    strcpy (io->name, name);
    io->block_size = cbs;
    io->read_error = 0;
    io->write_error = 0;

    *channel = io;
    return 0;
}

static errcode_t std_close (io_channel channel)
{
    close (std_fd);
}

static errcode_t std_set_blksize (io_channel channel, int blksize)
{
    channel->block_size = cbs = blksize;
}

static errcode_t std_read_blk (io_channel channel, unsigned long block, int count, void *data)
{
    int size;

    size = (count < 0) ? -count : count * cbs;
    if (lseek (std_fd, block * cbs, SEEK_SET) != block * cbs)
    	fatal ("Cannot seek");
    if (read (std_fd, data, size) != size)
	fatal ("Read error on block %d", block);
    return 0;
}

static errcode_t std_write_blk (io_channel channel, unsigned long block, int count, const void *data)
{
}

static errcode_t std_flush (io_channel channel)
{
}

static int ufs_block_idx = 0;
static int ufs_blocks_dump (ufs_filsys fs, blk_t *block, int i, void *private)
{
    int j;
    for (j = 0; j < nsect; j++)
        blocks [ufs_block_idx++] = *block * nsect + doff + j;
    return 0;
}

static int ufs_blocks (char *device, ino_t inode)
{
    if (ufs_open (device, std_io_manager, &fs))
    	fatal ("Cannot open ufs filesystem containing second stage");
    nsect = cbs / 512;
    if (ufs_block_iterate (fs, inode, ufs_blocks_dump, 0))
        fatal ("Block iterating error on second stage");
    blocks [ufs_block_idx] = 0;
    return 0;
}

#endif
