/*	$NetBSD: bim.c,v 1.6 1997/10/01 02:25:55 enami Exp $	*/

/*
 * Copyright (c) 1994 Philip A. Nelson.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Philip A. Nelson.
 * 4. The name of Philip A. Nelson may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY PHILIP NELSON ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL PHILIP NELSON BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: bim.c,v 1.6 1997/10/01 02:25:55 enami Exp $");
#endif /* not lint */

/*
 *  Boot Image Manager
 *
 *  (First copy called "hdsetup" and was written under Minix.)
 *
 *   Phil Nelson
 *   Sept 30, 1990
 */

#include <sys/types.h>
#include <sys/param.h>
#include <unistd.h>
#include <a.out.h>
#include <err.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>

#define DKTYPENAMES
#include <sys/disklabel.h>
#include "images.h"

#include "extern.h"

int	copy_bytes __P((int, int, int, int, int));
unsigned short dkcksum __P((struct disklabel *));
void	getlf __P((char));
void	getstr __P((char *, int));
void	init_images __P((char));
int	main __P((int, char *[]));
void	save_images __P((void));
void	usage __P((void));

#define TRUE 1
#define FALSE 0
#define MAXARGCMDS 20

#define BLOCK_SIZE 1024

#define DEFAULT_DEVICE "/dev/sd0c"

/*  Global data....  */
char    disk_info[BLOCK_SIZE];
int     disk_fd;		/* The file descriptor for the disk. */

struct disklabel *dk_label = (struct disklabel *) & disk_info[LABELOFFSET];
struct imageinfo *im_table =
(struct imageinfo *) (&disk_info[LABELOFFSET] + sizeof(struct disklabel));

int     label_changed = FALSE;
int     images_changed = FALSE;
int     secsize;


/* Utility routines... */
/***********************/

void 
usage()
{
	printf("usage: %s [-c command [-c command ...]] [device]\n",
	    __progname);
	printf("  Maximum of %d commands\n", MAXARGCMDS);
	exit(1);
}

void 
getlf(inchar)
	char	inchar;
{
	while (inchar != '\n')
		inchar = getchar();
}

void 
getstr(str, size)
	char	*str;
	int	size;
{
	char	inchar;
	int	count;

	count = 0;
	inchar = getchar();
	while (count < size - 1 && inchar != '\n') {
		*str++ = inchar;
		count++;
		inchar = getchar();
	}
	*str++ = 0;
	getlf(inchar);
}

/* Checksum a disk label */
unsigned short
dkcksum(lp)
	struct disklabel *lp;
{
	unsigned short *start, *end, sum = 0;

	start = (unsigned short *) lp;
	end = (unsigned short *) &lp->d_partitions[lp->d_npartitions];
	while (start < end)
		sum ^= *start++;
	return sum;
}

void 
save_images()
{
	int     count;

	count = (int) lseek(disk_fd, (off_t) 0, SEEK_SET);
	if (count != 0)
		err(1, "lseek error in saving image info");
	count = write(disk_fd, disk_info, BLOCK_SIZE);
	if (count != BLOCK_SIZE)
		err(1, "write error in saving image info");
	sync();
}

/* This function will initialize the image information . */
void 
init_images(badmagic)
	char    badmagic;
{
	char    answer[80];
	int     index;

	if (badmagic) {
		printf("Image information has improper magic number.\n");
		while (TRUE) {
			prompt(answer, 3,
			    "Do you want the images initialized? (y or n) ");
			if (answer[0] == 'y')
				break;
			if (answer[0] == 'n')
				errx(1, "images not initialized.");
		}
	}
	/* Initialize the image table. */
	im_table->ii_magic = IMAGE_MAGIC;
	im_table->ii_boot_partition = -1;
	for (index = 0; index < dk_label->d_npartitions; index++)
		if (dk_label->d_partitions[index].p_fstype == FS_BOOT) {
			im_table->ii_boot_partition = index;
			break;
		}
	im_table->ii_boot_count = MAXIMAGES;
	im_table->ii_boot_used = 0;
	im_table->ii_boot_default = -1;
	images_changed = TRUE;
}

/* Print out the header and other information about the disk. */
int 
display_part(num, args, syntax)
	int     num;
	char  **args;
	char   *syntax;
{
	int     count;

	printf("\nDisk: %s   Type: %s\n", dk_label->d_packname,
	    dk_label->d_typename);
	printf("Physical Sector Size = %d\n", dk_label->d_secsize);
	printf("Disk Size = %ld\n", (long)dk_label->d_secperunit);

	/* Disk Partitions. */
	printf(" partition         type  sector start  length in sectors\n");
	for (count = 0; count < dk_label->d_npartitions; count++) {
		if (dk_label->d_partitions[count].p_fstype != FS_UNUSED) {
			printf(" %5c  ", 'a' + count);
			printf("%14s",
			    fstypenames[dk_label->d_partitions[count].p_fstype]);
			printf("%14ld  %17ld\n",
			    (long)dk_label->d_partitions[count].p_offset,
			    (long)dk_label->d_partitions[count].p_size);
		}
	}
	printf("\n");

	return FALSE;
}

int 
display_image(num, args, syntax)
	int     num;
	char  **args;
	char   *syntax;
{
	int     count;

	/* Boot Images. */
	if (im_table->ii_boot_partition != -1)
		printf("Boot partition = %c\n",
		    'a' + im_table->ii_boot_partition);
	if (im_table->ii_boot_default != -1)
		printf("Default boot image  = %d\n", im_table->ii_boot_default);
	printf("Boot Images: total of %d\n", im_table->ii_boot_count);
	printf("  (image address and size in sectors.)\n");
	printf("Image  address   size  load addr  run addr   name\n");
	for (count = 0; count < im_table->ii_boot_used; count++) {
		printf("%5d %8lx %6lx  %9lx %9lx   %s\n", count,
		    im_table->ii_images[count].boot_address / secsize,
		    im_table->ii_images[count].boot_size / secsize,
		    im_table->ii_images[count].boot_load_adr,
		    im_table->ii_images[count].boot_run_adr,
		    im_table->ii_images[count].boot_name);
	}
	printf("\n");
	return FALSE;
}

int 
display_head(num, args, syntax)
	int     num;
	char  **args;
	char   *syntax;
{
	printf("\nDisk: %s   Type: %s\n", dk_label->d_packname,
	    dk_label->d_typename);
	printf("Physical Sector Size = %d\n", dk_label->d_secsize);
	printf("Disk Size = %ld\n", (long)dk_label->d_secperunit);

	return FALSE;
}


/*
 * Utility routine for moving boot images.  These are byte addresses
 *  relative to the start of the files.
 */
int 
copy_bytes(from_fd, from_adr, to_fd, to_adr, number)
	int     from_fd, from_adr, to_fd, to_adr, number;
{
	int     count;
	int     index;
	char    buffer[BLOCK_SIZE];

	/* Check the parameters. */
	if (to_adr > from_adr && from_fd == to_fd) {
		printf("There is a system error. (copy_bytes)\n");
		return 0;
	}
	/* Do the copy. */
	for (index = 0; index < number; index += BLOCK_SIZE) {
		count = lseek(from_fd, (off_t) (from_adr + index), SEEK_SET);
		if (count != from_adr + index) {
			printf("Error in copying (seek from)\n");
			return 0;
		}
		count = read(from_fd, buffer, BLOCK_SIZE);
		if (count != BLOCK_SIZE) {
			if (index != number - 1 || count < 0) {
				printf("Error in copying (read from)\n");
				return 0;
			} else {
				while (count < BLOCK_SIZE)
					buffer[count++] = 0;
			}
		}
		count = lseek(to_fd, (off_t) (to_adr + index), SEEK_SET);
		if (count != to_adr + index) {
			printf("Error in copying (seek to)\n");
			return 0;
		}
		count = write(to_fd, buffer, BLOCK_SIZE);
		if (count != BLOCK_SIZE) {
			printf("Error in copying (write to)\n");
			return 0;
		}
	}

	/* Success. */
	return 1;
}

/* Add a boot image. */
int
add_image(num, args, syntax)
	int     num;
	char  **args;
	char   *syntax;
{
	struct exec im_exec;	/* Information about a new image. */
	int     im_file;
	int     im_size;	/* Size of text and data in bytes. Rounded up
				 * to a full block in both text and data. */
	int     which_image;	/* Which image is to be operated upon. */
	int     count;		/* read/write counts. */
	int     index;		/* temporary variable for loops. */

	int     part_size;	/* The total size of the boot partition (in
				 * bytes). */
	int     total_size;	/* The total size of all images (in bytes). */
	int     boot_start;	/* Byte address of start of boot partition. */
	int     new_size;	/* The new total size of all images. */
	int     im_addr;	/* Byte address of the new boot image. */
	unsigned int im_load_adr;	/* The memory load address. */
	unsigned int im_run_adr;/* The memory run address. */
	char   *nptr;		/* Pointer for makeing name lower case. */

	/* Check argument numbers. */
	if (num != 2 && num != 3) {
		printf("Syntax: %s\n", syntax);
		return FALSE;
	}
	/* Check for a boot partition. */
	if (im_table->ii_boot_partition == -1) {
		printf("There is no boot partition.\n");
		return FALSE;
	}
	/* Any free images? */
	which_image = im_table->ii_boot_used;
	if (which_image == im_table->ii_boot_count) {
		printf("No more boot image slots available.\n");
		return FALSE;
	}
	/* Open the file. */
	im_file = open(args[1], O_RDONLY);
	if (im_file < 0) {
		printf("Could not open %s\n", args[1]);
		return FALSE;
	}
	/* check the exec header. */
	count = read(im_file, (char *) &im_exec, sizeof(struct exec));
	if (count != sizeof(struct exec)) {
		printf("Read problems for file %s\n", args[1]);
		close(im_file);
		return FALSE;
	}
	if (N_GETMAGIC(im_exec) != ZMAGIC || N_GETMID(im_exec) != MID_MACHINE) {
		printf("%s is not a a pc532 executable file.\n", args[1]);
		close(im_file);
		return FALSE;
	}
	if (im_exec.a_entry < 0x2000) {
		printf("%s has a load address less than 0x2000.\n", args[1]);
		close(im_file);
		return FALSE;
	}
	im_load_adr = im_exec.a_entry - sizeof(im_exec); /* & ~(__LDPGSZ-1); */
	im_run_adr = im_exec.a_entry;

	if (im_load_adr > 0xFFFFFF) {
		im_load_adr = im_load_adr & 0xFFFFFF;
		im_run_adr = im_run_adr & 0xFFFFFF;
		printf("%s has a load address greater than 0xFFFFFF.\n",
		    args[1]);
		printf(
		    "using the address:\n\tload address = 0x%x\n\trun address = 0x%x\n",
		    im_load_adr, im_run_adr);
	}
	/* Check the sizes.  */
	boot_start = 
	    dk_label->d_partitions[im_table->ii_boot_partition].p_offset
	    * secsize;
	part_size = dk_label->d_partitions[im_table->ii_boot_partition].p_size
	    * secsize;
	total_size = 0;
	for (index = 0; index < im_table->ii_boot_used; index++)
		total_size = total_size + im_table->ii_images[index].boot_size;

	/* Calculate other things. */
	im_size = im_exec.a_text + im_exec.a_data;

	/* Final check. */
	new_size = total_size + im_size;
	if (new_size > part_size) {
		printf("Image too big to fit in boot partition.\n");
		close(im_file);
	}
	/* Add the image. */
	im_addr = (total_size + secsize - 1) / secsize * secsize;
	im_table->ii_images[which_image].boot_address = im_addr;
	im_table->ii_images[which_image].boot_size = im_size;
	im_table->ii_images[which_image].boot_load_adr = im_load_adr;
	im_table->ii_images[which_image].boot_run_adr = im_run_adr;
	if (num == 3)
		strncpy(im_table->ii_images[which_image].boot_name, args[2],
		    15);
	else
		strncpy(im_table->ii_images[which_image].boot_name, args[1],
		    15);
	if (copy_bytes(im_file, 0, disk_fd, boot_start + im_addr, im_size)) {
		im_table->ii_boot_used++;
		/* Make name lowercase and report on image. */
		for (nptr = im_table->ii_images[which_image].boot_name;
		    *nptr != 0;
		    nptr++)
			if (isupper(*nptr))
				*nptr = tolower(*nptr);
		printf("added image %d (%s).\n", which_image,
		    im_table->ii_images[which_image].boot_name);
		close(im_file);
	} else {
		printf("Problems in installing image.\n");
		close(im_file);
		return FALSE;
	}

	/* Save the changes. */
	save_images();
	return FALSE;
}

/* Delete a boot image. */
int
delete_image(num, args, syntax)
	int     num;
	char  **args;
	char   *syntax;
{
	int     which_image;	/* Which image is to be operated upon. */
	int     index;		/* temporary variable for loops. */
	int     boot_start;	/* Zone number of start of boot partition. */
	int     del_size;	/* Size of the deleted image. */

	/* Check arguments. */
	if (num != 2) {
		printf("Syntax: %s\n", syntax);
		return FALSE;
	}
	/* Find the image. */
	which_image = -1;
	for (index = which_image; index < im_table->ii_boot_used; index++)
		if (strcmp(im_table->ii_images[index].boot_name,
		    args[1]) == 0) {
			which_image = index;
			break;
		}
	if (which_image == -1)
		if (!Str2Int(args[1], &which_image)) {
			printf("Syntax: %s\n", syntax);
			return FALSE;
		}
	if (which_image < 0 || which_image >= im_table->ii_boot_used) {
		printf("Delete: No such image (%s)\n", args[1]);
		return FALSE;
	}
	/* Report on image we are deleteing. */
	printf("deleting image %d (%s).\n", which_image,
	    im_table->ii_images[which_image].boot_name);

	/* Do the delete. */
	boot_start =
	    dk_label->d_partitions[im_table->ii_boot_partition].p_offset
	    * dk_label->d_secsize;
	del_size = im_table->ii_images[which_image].boot_size;
	for (index = which_image; index < im_table->ii_boot_used - 1; index++) {
		copy_bytes(
		    disk_fd,
		    boot_start + im_table->ii_images[index + 1].boot_address,
		    disk_fd,
		    boot_start + im_table->ii_images[index + 1].boot_address
			- del_size,
		    im_table->ii_images[index + 1].boot_size);
		im_table->ii_images[index] = im_table->ii_images[index + 1];
		im_table->ii_images[index].boot_address -= del_size;
	}
	im_table->ii_boot_used--;
	if (which_image == im_table->ii_boot_default)
		im_table->ii_boot_default = -1;
	else
		if (which_image < im_table->ii_boot_default)
			im_table->ii_boot_default--;

	/* Save the changes. */
	save_images();
	return FALSE;
}

/* Set the default boot image. */
int
set_default_image(num, args, syntax)
	int     num;
	char  **args;
	char   *syntax;
{
	int     which_image;

	if (num != 2 || !Str2Int(args[1], &which_image)) {
		printf("Syntax: %s\n", syntax);
		return FALSE;
	}
	if (which_image >= im_table->ii_boot_used) {
		printf("No such image.\n");
		return FALSE;
	}
	im_table->ii_boot_default = which_image;
	images_changed = TRUE;
	return FALSE;
}

/* Initialize the disk or just the image portion. */
int
initialize(num, args, syntax)
	int     num;
	char  **args;
	char   *syntax;
{
	/* Check the args */
	if (num > 1) {
		printf("Syntax: %s\n", syntax);
		return FALSE;
	}
	init_images(FALSE);
	return FALSE;
}

/* Write the disk header and exit. */
int 
write_exit(num, args, syntax)
	int     num;
	char  **args;
	char   *syntax;
{
	if (images_changed)
		save_images();

	return TRUE;
}

/* The main program! */
/*********************/

int
main(argc, argv)
	int     argc;
	char   *argv[];
{
	int     count;		/* Used by reads. */
	char    *fname;
	int     cmdscnt;	/* Number of argument line commands. */
	char   *argcmds[MAXARGCMDS];
	char    optchar;
	int     index;

	/* Check the parameters.  */
	cmdscnt = 0;
	opterr = TRUE;
	fname = DEFAULT_DEVICE;
	while ((optchar = getopt(argc, argv, "c:")) != -1)
		switch (optchar) {
		case 'c':
			if (cmdscnt == MAXARGCMDS)
				usage();
			argcmds[cmdscnt++] = optarg;
			break;
		case '?':
			usage();
		}

	if (argc - optind > 1)
		usage();
	if (optind < argc)
		fname = argv[optind];

	disk_fd = open(fname, O_RDWR);
	if (disk_fd < 0)
		err(1, "Could not open %s", fname);

	/* Read the disk information block. */
	count = read(disk_fd, disk_info, BLOCK_SIZE);
	if (count != BLOCK_SIZE)
		err(1, "Could not read info block on %s", fname);

	/* Check for correct information and set up pointers. */
	if (dk_label->d_magic != DISKMAGIC)
		errx(1, "Could not find a disk label on %s", fname);
	if (im_table->ii_magic != IMAGE_MAGIC)
		init_images(TRUE);
	if (dkcksum(dk_label) != 0)
		printf("Warning: bad checksum in disk label.\n");

	/* initialize secsize. */
	secsize = dk_label->d_secsize;

	/* do the commands.... */
	if (cmdscnt > 0) {
		/* Process the argv commands. */
		for (index = 0; index < cmdscnt; index++)
			one_command(argcmds[index]);
	} else {
		/* Interactive command loop.  */
		display_part(0, NULL, NULL);
		display_image(0, NULL, NULL);
		command_loop();
	}
	exit(0);
}
