/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:dosboot.c 12.0$ */
/* $ACIS:dosboot.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/standca/RCS/dosboot.c,v $ */

#ifndef lint
static char *rcsid = "$Header:dosboot.c 12.0$";
#endif

#ifndef SAUTIL
#include "param.h"
#include "inode.h"
#include "fs.h"
#include "vm.h"
#include <a.out.h>
#include "saio.h"
#include "reboot.h"
#include <ctype.h>
#include "err.h"


/*
 * Boot program... arguments passed in r10 and r11 determine
 * whether boot stops to ask for system name and which device
 * boot comes from.
 */

#include "../machine/rdb.h"		/* get POST address */
#define POST_END	(POST_START+POST_SIZE)       /* next location after the POST */

#undef main

char line[100];

#endif SAUTIL

int sector;
int length;
int dos_offset;

#define BSS_SLOP 8 * 512		/* extra bss to clear */
#define LSEEK(fd, pos, how) lseek(fd, dos_offset + (pos), how)

main(argc,argv)
char **argv;
{
	register howto, devtype;       /* howto=r11, devtype=r10 */
	int io;
	int retry = 0;

#ifdef lint
	howto = 0;
	devtype = 0;
#endif
	printf("\n4.3 BSD UNIX (DOS Floppy) Standalone Boot Program\n\n");
	putchar(07);		       /* ring my chimes */
	howto = RB_ASKNAME | RB_SINGLE;
	/*
	 printf("Enter DOS device [fd(0,0)]: ");
	 gets(line);
	 if (line[0] == 0)
		strcpy(line, "fd(0,0)");
	 */
	if (argc > 1)
		strcpy(line, argv[1]);
	else	
		strcpy(line, "fd(0,0)");
	if ((io = open(line, 0)) < 0) {
		printf("can't open %s", line);
		exit(1);
		}
	printf("Enter DOS filename.ext to boot\n");
	for (;;) {
		printf(": ");
		gets(line);
		if (dosopen(io, line, &sector, &length)) {
/*			printf("start reading at sector %d\n",sector); /* */
			doscopyunix(howto, io, sector * 512);
			printf("boot failed\n");
		} else if (line[0])
			printf("could not find %s\n", line);
		if (++retry > 2)
			howto = RB_SINGLE | RB_ASKNAME;
	}
}

#define INSIDE(addr,low,high) ((int)(low) <= (int)(addr) && (int)(addr) < (int)(high))

#define BASEMASK 0x0ff80000	       /* mask for calculating base address */

/*ARGSUSED*/
doscopyunix(howto, io, offset)
	register howto, io;
{
	struct exec x;
	register int i;
	char *addr;
	register int baseaddr;	       /* segment base address */
	register int bootstart = BASEMASK & fnaddr(main);
	register int size;
	extern char end;

	dosseek(io, offset, 0);	       /* seek to start of file */
	i = dosread(io, (char *) & x, sizeof x);
	if (i != sizeof x ||
	    (x.a_magic != 0407 && x.a_magic != 0413 && x.a_magic != 0410))
		err("bad magic number - not a.out format");
	size = x.a_text+x.a_data;
	baseaddr = ((int)x.a_entry) & BASEMASK;	/* same base as entry */
/*
 * following allows boot to boot itself without overwritting itself (which
 * tends not to work very well).
 */
	if (INSIDE(baseaddr,bootstart,&end) ||
			INSIDE(bootstart, baseaddr, baseaddr+size)) {
		register int newbase = baseaddr ^ 0x80000;
		printf("would overlay boot - loading at %x rather than %x\n", newbase, baseaddr);
		baseaddr = newbase;    /* hope it's relocatable! */
		x.a_entry = (x.a_entry & ~BASEMASK) + baseaddr;
	}

/*	printf("baseaddr=%x end=%x bootstart=%x bootend=%x\n",baseaddr,baseaddr+x.a_text+x.a_data+x.a_bss,bootstart,&end); /* DEBUGGING */
	/*
	 * test if program will overlap boot 
	 * first test if program start address is inside boot program
	 * then test if boot start address is inside program.
	 */
	if (INSIDE(baseaddr,bootstart,&end) ||
			INSIDE(bootstart, baseaddr, baseaddr+size))
		_stop("help: program overlaps boot!");
	/*
	 * now see if entire program (including bss) overlaps boot)
	 */
	if (INSIDE(bootstart, baseaddr, baseaddr+size+x.a_bss+BSS_SLOP)) {
		printf("bss would clobber boot; bss not cleared\n");
		x.a_bss = 0;
	}
	printf("%d", x.a_text);
	if (x.a_magic == 0413 && dosseek(io, offset + NBPG, 0) == -1)
		goto shdosread;
	/*
	 * protect the POST by splitting up any dosread the crosses the POST
	 * into three dosreads (refusing to dosread data into the POST.
	 */
	if (baseaddr <= POST_START && x.a_text > POST_END) {
		/* text will span the POST */
		register int len = POST_START - baseaddr;
		register char *p;
		char buff[POST_END - POST_START]; /* data for POST */

		if (dosread(io, (char *)baseaddr, len) != len)
			goto shdosread;	/* dosread up to POST */
		if (dosread(io, buff, POST_END - POST_START) != POST_END - POST_START)
			goto shdosread;	/* dosread POST */
		for (len = 0, p = buff; p < buff + POST_END - POST_START; ++p)
			if (*p)
				++len;
		if (len)
			printf("(%d non-zero POST)", len);
		else
			printf("*");   /* POST ok marker */
		len = x.a_text - (POST_END - baseaddr);
		if (dosread(io, (char *)POST_END, len) != len)
			goto shdosread;
	} else if (baseaddr + x.a_text < POST_END)
		printf("text/data inside POST\n");
	else			       /* fall thru to the normal case */

	if (dosread(io, (char *)baseaddr, x.a_text) != x.a_text)
		goto shdosread;
	addr = (char *)x.a_text + baseaddr;
	if (x.a_magic == 0413 || x.a_magic == 0410)
		while ((int)addr & CLOFSET)
			*addr++ = 0;
	printf("+%d", x.a_data);
	if (dosread(io, addr, x.a_data) != x.a_data)
		goto shdosread;
	addr += x.a_data;
	printf("+%d", x.a_bss);
/*	printf("clearing %x...",addr);	/* */
	x.a_bss += 128 * 512;	       /* slop */
	for (i = 0; i < x.a_bss; i++)
		*addr++ = 0;
/*	printf("%x\n",addr);	/* */
	close(io);
	x.a_entry &= 0x0fffffff;       /* remove segment number */
	printf(" start 0x%x\n", x.a_entry);
	delay(100);
	if (*(short *)x.a_entry)
		callabs(x.a_entry,0,0);
	exit(0);
shdosread:
	exit(2);
}


#define MAX_FILE_LENGTH	8
#define MAX_FILE_EXT	3

#if defined(vax) || defined(ibmpc)
#define swaph(n) n
#define swapw(n) n
#define WORD(x) (((x[0]) << 8) + (x[1]))
#else
#define swaph(n) ((((n)>>8)&0xff) | (((n)&0xff) << 8))
#define swapw(n) (swaph((n)>>16) | (swaph((n)) << 16))
#define WORD(x) (((x[1]) << 8) + (x[0]))
#endif

struct dir {
	u_char file_name[MAX_FILE_LENGTH];
#define FILE_NOT_USED	0	       /* never been used */
#define FILE_ERASED	0xe5
#define FILE_DIR	0x2e	       /* file is a directory */
	u_char file_ext[MAX_FILE_EXT]; /* extention */
	u_char file_attr;	       /* file attributes */
#define ATTR_READ_ONLY	0x01	       /* file is read only */
#define ATTR_HIDDEN	0x02	       /* file is hidden */
#define ATTR_SYSTEM	0x04	       /* file is a system file */
#define ATTR_VOL_LABEL	0x08	       /* volume label */
#define ATTR_SUBDIR	0x10	       /* sub-directory */
#define ATTR_ARCHIVE	0x20	       /* file changed since archive */
	u_char file_fill[10];	       /* reserved */
	unsigned short file_time;      /* time last changed */
	unsigned short file_date;      /* date last changed */
	unsigned short file_start;     /* starting clustor */
	long file_size;		       /* file size in bytes */
};

#undef BSIZE
#define BSIZE	512		       /* DOS block size */
#define FAT_SECTOR	1	       /* location of FAT */
#define FAT_POS	(FAT_SECTOR * BSIZE)   /* offset to the DOS FAT */
#define MAX_FAT_SECTORS 2
u_char *dbuff;
u_char *fat;
int fd;
int sectors;			       /* number of sectors */
int sides;			       /* number of sides */
int cluster_size;		       /* number of sectors/cluster */
int dir_sectors;		       /* number of directory sectors */
int dir_sector;			       /* directory offset */
int fat_size;			       /* number of sectors for fat */
int data_sector;		       /* start of data area (sector) */
int argstart;
int vflg;
char *calloc();
int copies = 2;
int partition;

char *file = "/dev/fd0";
char *basename();

/*
 * open a dos file.
 * return the sector it starts at and its length
 */
dosopen(fd, name, psector, plength)
	register int fd;
	char *name;
	int *psector, *plength;
{
	register struct dir *dirp;
	register int i;
	char filename[8 + 1 + 3 + 1];  /* filename.ext */

	if (dbuff == 0 && (dbuff = (u_char *) calloc(BSIZE,1)) == 0)
		err("can't allocate buffer");
	if (fat == 0 && (fat = (u_char *) calloc(MAX_FAT_SECTORS * BSIZE,1)) == 0)
		err("can't allocate fat (%d bytes)",MAX_FAT_SECTORS * BSIZE);
	LSEEK(fd, FAT_POS, 0);	       /* point to FAT on disk */
	if (read(fd, fat, MAX_FAT_SECTORS * BSIZE) != MAX_FAT_SECTORS * BSIZE)
		err("could not read FAT");
	switch (fat[0]) {
	case 0xfe:		       /* single sided, 8 sector-per-track diskette */
		fat_size = 1;
		sectors = 8;
		sides = 1;
		dir_sectors = 4;
		cluster_size = 1;
		break;
	case 0xff:		       /* dual sided, 8 sector-per-track diskette */
		fat_size = 1;
		sectors = 8;
		sides = 2;
		dir_sectors = 7;
		cluster_size = 2;
		break;
	case 0xfd:		       /* dual sided, 9 sector-per-track diskette */
		fat_size = 2;
		sectors = 9;
		sides = 2;
		dir_sectors = 7;
		cluster_size = 2;
		break;
	case 0xfc:		       /* single sided, 9 sector-per-track diskette */
		fat_size = 2;
		sectors = 9;
		sides = 1;
		dir_sectors = 4;
		cluster_size = 1;
		break;
#ifndef ATR
	case 0xf9:		       /* double sided, 15 sector-per-track diskette */
		fat_size = 7;
		sectors = 15;
		sides = 2;
		dir_sectors = 14;
		cluster_size = 1;
/*	tracks = 80;		/* */
		break;
#else
	case 0xf0:		       /* 3.5" 80 track, 18 sector 2 heads */
		fat_size = 9;
		sectors = 18;
		sides = 2;
		dir_sectors = 14;
		cluster_size = 1;
		break;
	case 0xf9:		       /* 3.5" 80 track, 9 sector 2 heads */
		fat_size = 3;
		sectors = 9;
		sides = 2;
		dir_sectors = 7;
		cluster_size = 2;
/*	tracks = 80;		/* */
		break;
#endif ATR
	default:
		if (!get_partition(fd))
			err("illegal diskette type %x\n", fat[0]);
		LSEEK(fd, FAT_POS, 0);	       /* point to FAT on disk */
		if (read(fd, fat, MAX_FAT_SECTORS * BSIZE) != MAX_FAT_SECTORS * BSIZE)
			err("could not read FAT");
		if (fat[0] != 0xf8)
			err("illegal diskette type %x\n", fat[0]);
		read_fixed_info(fd);
		break;
	}
	dir_sector = FAT_SECTOR + (fat_size * copies);
	data_sector = dir_sector + dir_sectors;

	if (vflg > 1) {
		printf("%d sectors, %d sides, %d directory sectors, %d sectors/cluster\n",
		    sectors, sides, dir_sectors, cluster_size);
		printf("directory at block %d, data at block %d\n",
		    dir_sector, data_sector);
	}
	LSEEK(fd, dir_sector * BSIZE, 0); /* seek to directory */
	for (i = 0; i < dir_sectors; ++i) {
		if (read(fd, dbuff, BSIZE) != BSIZE)
			err("read error");
		for (dirp = (struct dir *)dbuff; dirp < (struct dir *)(dbuff + BSIZE); ++dirp) {
			switch (dirp->file_name[0]) {
			case FILE_NOT_USED:
				return (0); /* end if dir scan */
			case FILE_ERASED:
			case FILE_DIR:
				continue;
			default:
				sprintf(filename, "%.8s.%.3s", dirp->file_name, dirp->file_ext);
				fixname(filename);
				if (name[0] == 0)
					printf("%s\n", filename); /* print names */
				if (strcmp(filename, name) != 0)
					continue; /* not one we wanted */
				dirp->file_start = swaph(dirp->file_start);
				dirp->file_time = swaph(dirp->file_time);
				dirp->file_date = swaph(dirp->file_date);
				dirp->file_size = swapw(dirp->file_size);
				*psector = cvt_cluster(dirp->file_start);
				*plength = dirp->file_size;
				return (1);
			}
		}
	}
	return (0);		       /* not found */
}


int cvt_cluster(n)
	register int n;
{
	register int result = (n - 2) * cluster_size + data_sector;
	if (vflg > 1)
		printf("cluster %d ==> sector %d\n", n, result);
	return (result);
}


int next_cluster(n)
	register int n;
{
	register int x = n + n + n;    /* n * 3 */
	register u_char * f = fat + (x >> 1); /* point to proper fat entry */
	register int result;

	if ((x & 1) == 0)
		result = f[0] | (f[1] << 8); /* 8 plus 4 from next */
	else
		result = (f[0] >> 4) | (f[1] << 4); /* 4 plus 8 from next */
	result &= 0xfff;
	if (vflg > 1)
		printf("next(%d)=%d fat+%d=%02x, %02x\n",
		    n, result, x >> 1, f[0], f[1]);
	return (result);
}


/*
 * convert dos name to unix format:
 * 1. lower case letters
 * 2. no blanks
 * 3. remove trailing .
 */
fixname(filename)
	register char *filename;
{
	register char *p = filename;

	for (; *p = *filename++;) {
		if (isupper(*p))
			*p = tolower(*p);
		if (*p != ' ')
			++p;
	}
	if (p[-1] == '.')
		*--p = 0;
}


long dospos;
dosseek(fd, pos, how)
	register int fd;
	long pos;
	int how;
{
	dospos = pos;
	return (LSEEK(fd, pos, how));
}


#define FDSIZE 512
dosread(fd, dbuff, len)
	register int fd;
	register char *dbuff;
	register int len;
{
	register int l = 0;
	register int off;
	char buffer[FDSIZE];

	if (dospos & (FDSIZE - 1)) {
		off = dospos & (FDSIZE - 1);
		LSEEK(fd, dospos & ~(FDSIZE - 1), 0);
		read(fd, buffer, FDSIZE);
		l = FDSIZE - off;
		if (l > len)
			l = len;
		bcopy(buffer + off, dbuff, l);
		len -= l;
		dbuff += l;
		dospos += l;
	}
	if (len > 0) {
		l += read(fd, dbuff, len);
		dospos += len;
	}
	return (l);
}
#define MAX_PART	4	/* maximum number of partitions */
#define BOOT_IND	0x80	/* boot indicator */
struct boot_block
{
#define PREAMBLE_SIZE 0x1be	/* Sigh */
    char            preamble[PREAMBLE_SIZE];	/* who knows what goes here */
    struct partition
    {
	char            boot_ind, boot_head, boot_sector, boot_cyl;
	char            sys_ind, sys_head, sys_sector, sys_cyl;
	unsigned short  rel_low, rel_high;
	unsigned short  size_low, size_high;
    }               part[MAX_PART];
    char            signature[2];
#define SIGNATURE_0 0x55
#define SIGNATURE_1 0xaa

};

/*
 * look for a hard disk partition table 
 * and set the offset variable appropriately
 * if the user specified -p without a partition number
 * then we will look for the active partition; otherwise
 * we will just use the one that was specified.
 */
get_partition(fd)
{
    struct boot_block block;
    register struct partition *part = 0;
    register long   rel, size;
    register int    i;

#ifdef DEBUG
    if (sizeof block != BSIZE)
	err("boot block size incorrect (%d)", sizeof block);
#endif DEBUG
    lseek(fd, (long) 0, 0);
    if (read(fd, (char *) &block, sizeof block) != sizeof block)
	err("reading partition table (at block 0)");
    if (block.signature[0] != SIGNATURE_0 ||
	block.signature[1] != SIGNATURE_1)
	return(0);
    if (partition)
    {
	--partition;		/* make it zero based */
	if ((unsigned) partition >= MAX_PART)
	    err("invalid partition number (%d)", partition + 1);
	part = &block.part[partition];
    } else
    {
	for (i = 0; i < MAX_PART; ++i)
	{
	    if (block.part[i].boot_ind == BOOT_IND)
	    {
		if (part)
		    err("more than 1 active partition");
		partition = i;
		part = &block.part[partition];
	    }
	}
	if (part == 0)
	    err("no active partition found");
    }
    part->rel_high = swaph(part->rel_high);
    part->rel_low = swaph(part->rel_low);
    rel = (part->rel_high << 16) + part->rel_low;
    size = (part->size_high << 16) + part->size_low;
#ifdef DEBUG
    if (vflg > 1)
	printf("partition %d: start=%ld size=%ld boot=%s fat=%s\n",
	       partition + 1, rel, size,
	       part->boot_ind == BOOT_IND ? "yes" : "no",
	       part->sys_ind == 0x01 ? "12bit" : part->sys_ind == 0x04 ? "16bit" : "unknown");
#endif
    if (size <= 0)
	err("partition %d not allocated\n", partition);
    dos_offset = rel * BSIZE;
    if (part->sys_ind == 0x4)
	err("16 bit FAT's not supported");
    return(1);
}

/*
 * structure of a DOS boot record as per DOS Technical Reference 3.00
 * page 3-22.
 */
struct dos_boot_info
{
    u_char          jump[3];	/* 0-2: near or far jump */
    u_char          oem[8];	/* 3-10: OEM name */
    u_char          block_size[2];	/* 11-12: bytes/sector */
    u_char          cluster_size;	/* 13: number of sectors/cluster */
    u_char          reserved[2];/* 14-15: reserved sectors */
    u_char          fat_copies;	/* 16: number of fat's */
    u_char          dir_entries[2];	/* 17-18: number of root directory
					 * entires */
    u_char          device_size[2];	/* 19-20: number of sectors total */
    u_char          media_descriptor;	/* 21: who knows */
    u_char          fat_size[2];/* 22-23: number of sectors per fat */
    u_char          sectors[2];	/* 24-25: sector size */
    u_char          tracks[2];	/* 26-27: number of heads */
    u_char          hidden[2];	/* 28-29: number of hidden sectors */
};

/*
 * read the information about a hard disk partition off the disk
 */
read_fixed_info(fd)
{
    struct dos_boot_info info;

    LSEEK(fd, (long) 0, 0);
    if (read(fd, (char *) &info, sizeof info) != sizeof info)
	err("couldn't read fixed disk information");
    if (vflg > 1)
	printf("oem=%.8s\n", info.oem);
    fat_size = WORD(info.fat_size);
    copies = info.fat_copies;
    cluster_size = info.cluster_size;
    dir_sectors = WORD(info.dir_entries) /
	(BSIZE / (sizeof(struct dir)));
    sides = WORD(info.tracks);
    sectors = WORD(info.sectors);
#ifdef notdef
    size = WORD(info.device_size);
    cylsize = (sectors * heads);
    if (cylsize)
	tracks = size / cylsize;
#endif
}
