#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#include <sys/stat.h>
#include <sys/types.h>

#include "system.h"
#include <disklabel.h>
#include <config.h>
#include "library.h"

#define SECT_SIZE 512
#define BOOT_SECTOR 2

int read_configured_partition(int disk_fd, char* buf)
{
  u_int64_t bootsize, bootsect, bootpart = 0;
  long *p = (long *) buf;

  if(lseek(disk_fd,60*8,SEEK_SET)<0) {
    perror("lseek on disk");
    exit(1);
  }
  /* Find old configuration */
  read(disk_fd, &bootsize, sizeof(bootsize));
  read(disk_fd, &bootsect, sizeof(bootsect));
  if(lseek(disk_fd,SECT_SIZE*bootsect,SEEK_SET)<0)
    return 0; /* probably random garbage in the boot block - not
		 a fatal error */
  read(disk_fd, buf, SECT_SIZE);
  while ((char *)p < buf + SECT_SIZE)
    if (*p++ == ABOOT_MAGIC)
      bootpart = *p;
  return bootpart;
}

int main(int argc, char **argv)
{
   u_int64_t bootsize,kernelsize=0;
   u_int64_t bootsect=BOOT_SECTOR;
   u_int64_t magicnum=0;
   int disk_fd,file_fd,kernel_fd=0;
   struct disklabel dlabel;
   struct stat s;
   int x;
   char buf[2048];
   int c;
   int err=0, part, bootpart=0;
   unsigned force_overlap=0;
   int verbose=0;
   extern int optind;
   extern char *optarg;
   char *bootfile=0, *device=0, *kernel=0;

   while ((c=getopt(argc,argv,"f:c:v?"))!=EOF)
     switch(c)
     {
       case '?':
         err=1;
         break;
       case 'f':
	 part = atoi(optarg);
	 if (part < 1 || part > 8) {
	     fprintf(stderr, "%s: partition number must be in range 1-8\n",
		     argv[0]);
	     exit(1);
	 }
         force_overlap |= 1U << (part - 1);
         break;
       case 'c':
	 bootpart = atoi(optarg);
	 if (bootpart < 1 || bootpart > 8) {
	     fprintf(stderr, "%s: partition number must be in range 1-8\n",
		     argv[0]);
	     exit(1);
	 }
	 break;
       case 'v':
         verbose=1;
         break;
       default:
         err=1;
         break;
     }

  if(optind<argc)
    device=argv[optind++];
  if(optind<argc)
    bootfile=argv[optind++];
  if(optind<argc)
    kernel=argv[optind++];

  if(!bootfile || !device || err)
  {
      fprintf(stderr, "Usage: %s [-f[1-8]] [-c[1-8]] [-v] disk bootfile [kernel]\n",
	      argv[0]);
      exit(1);
   }

   disk_fd=open(device,O_RDWR);
   file_fd=open(bootfile,O_RDONLY);
   if(disk_fd<0) {
      perror("open disk device");
      exit(1);
   }
   if(file_fd<0) {
      perror("open bootfile");
      exit(1);
   }

   if(kernel)
   {
     kernel_fd=open(kernel,O_RDONLY);
     if (kernel_fd<0)
     {
       perror("open kernel");
       exit(1);
     }
     else
     {
       if(fstat(kernel_fd,&s)) {
         perror("fstat kernel");
         exit(1);
       }
       kernelsize=(s.st_size+SECT_SIZE-1)/SECT_SIZE;
     }
   }
   if(read_disklabel(disk_fd,&dlabel)) {
      fprintf(stderr,"Couldn't get a valid disk label, exiting\n");
      exit(1);
   }
   if(fstat(file_fd,&s)) {
      perror("fstat bootfile");
      exit(1);
   }
   bootsize=(s.st_size+SECT_SIZE-1)/SECT_SIZE;

   if(-1 !=(x=overlaplabel(&dlabel,bootsect,bootsize+bootsect+kernelsize,force_overlap)))
   {
      fprintf(stderr,
	      "error: bootcode overlaps with partition #%d. "
	      "If you really want this, use -f%d\n",
	      x + 1, x + 1);
      exit(1);
   }

   if(!bootpart) {
      bootpart = read_configured_partition(disk_fd, buf);
      if (verbose) {
	 if (bootpart) {
	    printf("preserving boot partition %d\n", bootpart);
	 } else {
	    printf("could not find existing aboot, configuring for second partition\n");
	 }
      }
   } else {
      if (verbose) {
	 printf("setting boot partition to %d\n", bootpart);
      }
   }
   if(lseek(disk_fd,60*8,SEEK_SET)<0) {
      perror("lseek on disk");
      exit(1);
   }
   write(disk_fd,&bootsize,sizeof(bootsize));
   write(disk_fd,&bootsect,sizeof(bootsect));
   write(disk_fd,&magicnum,sizeof(magicnum));
   if (verbose)
   {
     fprintf(stderr,"bootsize:%lu sectors\n",bootsize);
     fprintf(stderr,"bootsect:%lu\n",bootsect);
   }
   if(lseek(disk_fd,SECT_SIZE*bootsect,SEEK_SET)<0) {
      perror("lseek #3 on disk\n");
      exit(1);
   }
   while((x=read(file_fd,buf,2048))>0) {
      write(disk_fd,buf,x);
   }
   close(file_fd);
   if (kernel_fd > 0 && kernelsize>0)
   {
     unsigned long len = 0;

     if (verbose)
       fprintf(stderr,"kernel:%lu sectors\n",kernelsize);
#if 0
     if(lseek(disk,BOOT_SIZE+BOOT_SECTOR*SECT_SIZE,SEEK_SET)<0) {
       perror("lseek #4 on disk\n");
       exit(1);
     }
#endif
     while((x=read(kernel_fd,buf,2048))>0)
     {
       write(disk_fd,buf,x);
       len += x;
     }
     close(kernel_fd);
     if ((len+SECT_SIZE-1)/SECT_SIZE != kernelsize)
       fprintf(stderr,"warning: kernel read %lu, should be %lu\n",(len+SECT_SIZE-1)/SECT_SIZE,kernelsize);
   }
   /* Now write the aboot partition config if we had one */
   if (bootpart) {
     long *p = (long *) buf;

     if(lseek(disk_fd,SECT_SIZE*bootsect,SEEK_SET)<0) {
       perror("lseek #5 on disk\n");
       exit(1);
     }
     read(disk_fd, buf, SECT_SIZE);
     while ((char *)p < buf + SECT_SIZE) {
       if (*p++ == ABOOT_MAGIC) {
	 *p = bootpart;
       }
     }
     lseek(disk_fd,SECT_SIZE*bootsect,SEEK_SET);
     write(disk_fd, buf, SECT_SIZE);
   }
   dosumlabel(disk_fd,&dlabel);
   close(disk_fd);
   if(verbose)
     fprintf(stderr,"done!\n");
   return 0;
}
