/*	$OpenBSD: bim.c,v 1.5 1997/09/04 00:51:52 mickey Exp $	*/
/*	$NetBSD: bim.c,v 1.4 1995/09/28 07:08:49 phil 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.
 */

/*
 *  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 <stdio.h>
#include <fcntl.h>
#include <a.out.h>
#include <string.h>
#include <ctype.h>

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

#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;
extern char * __progname;

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

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

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;
{
        register 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 (3, "lseek in saving image info");
  count = write (disk_fd, disk_info, BLOCK_SIZE);
  if (count != BLOCK_SIZE)
    err ("write in saveing image info");
  sync ();
}

/* Get a number using the prompt routine. */
GetInt (num, prompt_str)
     int *num;
     char *prompt_str;
{
  char answer[80];
  
  prompt (answer, 80, prompt_str);
  while (!Str2Int (answer, num))
    {
      printf ("Bad number.\n");
      prompt (answer, 80, prompt_str);
    }
}


/* 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 (3, "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", 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",
		dk_label->d_partitions[count].p_offset,
		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", 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;
  int index1;
  int left;
  int xfer_size;
  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;
    }
  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! */
/*********************/

main (argc, argv)
int argc;
char *argv[];
{
    int count;		/* Used by reads. */
    char answer, *fname;
    int cmdscnt;	/* Number of argument line commands. */
    char *argcmds[MAXARGCMDS];
    extern int optind, opterr;
    extern char *optarg;
    int 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(3, "%s", fname);

    /* Read the disk information block. */
    count = read (disk_fd, disk_info, BLOCK_SIZE);
    if (count != BLOCK_SIZE) errx(3, "Could not read info block on %s", fname);
    
    /* Check for correct information and set up pointers. */
    if (dk_label->d_magic != DISKMAGIC) 
    	err (3, "Could not find a disk label on %s", fname);
    if (im_table->ii_magic != IMAGE_MAGIC)  init_images (TRUE);
    if (dkcksum (dk_label) != 0) 
       warnx ("Warning: bad checksum in disk label.");

    /* 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 ();
     }
}