/*
 * lphdisk - Hibernation Parititon Prep Utility for laptops running Phoenix
 *           NoteBIOS.
 *
 *     Copyright 2000-2001 Patrick D. Ashmore <pda@procyon.com> and
 *                         Alex Stewart <alex@foogod.com>
 *     This software is released under the Artistic License
 *
 *     lphdisk 0.9.1
 */

/* A couple of notes:
    - Partition numbers are always passed and used base-one (i.e. the first 
      partition is partition 1, not 0).  This ensures consistency both 
      throughout the program and with convention elsewhere, reducing the risk 
      of something using a different partition than it thought it was.  This 
      does mean, however, that if you ever see something like "pi[partnum]", 
      it is probably _wrong_ (it probably should be "pi[partnum-1]").  
      Be careful!
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <getopt.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/io.h>
#include "lrmi.h"
#include "vbe.h"

/* General Program Defines: */

#define SECTOR_SIZE    512     /* Bytes per sector */
#define PP_BLOCK_SIZE  2       /* Sectors per "block" in /proc/partitions */
#define MBR_SIZE       512     /* Boot record is 512-bytes long */
#define TABLE_START    0x1BE   /* Start of partition table in MBR */
#define TABLE_MAGIC    0x1FE   /* Start of "magic number" in MBR */
#define TABLE_ENTRY    16      /* Length of a single partition table entry */
#define PART_TYPE      4       /* Position of partition type byte */
#define PART_START     8       /* Position of start sector (32-bit value) */
#define PART_SIZE      12      /* Position of partition size (32-bit value) */
#define MAGIC_ID       0xAA55  /* "magic number" (little-endian reversed) */
#define HIBERNATION_ID 0xA0    /* NoteBIOS Hibernation partition ID */
#define EXTENDED_ID    0x05    /* ID for an "extended partition" area */
#define PROBE_PADDING  2048    /* Add 2MB "padding" to probed requirements */

/* Error Exit Codes: */

#define ERR_USAGE      1       /* User requested usage message */
#define ERR_BADARGS    2       /* User entered bad command arguments */
#define ERR_OPEN       3       /* Open of device failed */
#define ERR_STAT       4       /* Statting open device failed */
#define ERR_BADFILE    5       /* Bad file type */
#define ERR_READ       6       /* Read error */
#define ERR_TABLE      7       /* Error reading/checking partition table */
#define ERR_FINDPART   8       /* Problem determining which partition to use */
#define ERR_WRITE      9       /* Problem formatting hibernate partition */
#define ERR_CANTPROBE  10      /* Asked to probe only, but can't obtain info */

/* Structs and Type Declarations: */

typedef struct {
  int     type;                /* Partition type ID */
  size_t  start;               /* Start sector of partition */
  size_t  size;                /* Length of partition in sectors */
} partinfo;

/* Global Variables: */

const char *version_string =
  "lphdisk 0.9.1, by Patrick D. Ashmore and Alex Stewart";

const char *pp_filename      = "/proc/partitions";
const char *mtrr_filename    = "/proc/mtrr";
const char *meminfo_filename = "/proc/meminfo";

int force_flag = 0;            /* "force" is off by default */
int quiet_flag = 0;            /* Be noisy by default */
int debug_flag = 0;            /* Debugging messages off by default */
int write_flag = 1;            /* Do actually write things, though */
int probeonly_flag = 0;        /* Continue after probing by default */

char phasers[] = "stun";       /* We come in peace! (Shoot to kill!) */

/* The header, with sector and checksum values set to 0, looks like this:    */
/*                      __ __                            __ __ __ __         */
/*   54 69 6D 4F  00 00 00 00  00 00 00 00  02 00 00 00  00 00 00 00         */
/*                    Checksum                         Size in Sectors       */

const unsigned char header_base[] = {	/* first 16 bytes of an empty header */
  0x54, 0x69, 0x6D, 0x4F,   0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00,   0x02, 0x00, 0x00, 0x00,
};

/*****************************************************************************/
/*                      General Purpose Utility Routines                     */
/*****************************************************************************/

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* The following is a hack to take advantage of the ext2 "_llseek" system    */
/* call to do seeks to "long long" offsets under linux (this is needed to    */
/* seek to sectors beyond 4194303 (2GB)).  This isn't directly supported by  */
/* glibc, so we need to make our own interface function for it.  We should   */
/* be able to get the _NR__llseek define from linux/unistd.h.  From this we  */
/* can construct a wrapper to perform the right system call.                 */

#include <linux/unistd.h>       /* for __NR__llseek */

typedef long long lloff_t;

#ifdef __NR__llseek

static _syscall5(int,_llseek, unsigned int,fd, unsigned long,offset_high,
                 unsigned long,offset_low, lloff_t *,result,
                 unsigned int,origin)

lloff_t llseek (unsigned int fd, lloff_t offset, unsigned int origin) {
  lloff_t result;
  int retval;

  retval = _llseek (fd, ((unsigned long long) offset) >> 32,
                   ((unsigned long long) offset) & 0xffffffff,
                   &result, origin);
  return (retval == -1 ? (lloff_t) retval : result);
}

#else /* __NR__llseek */

/* Somehow, __NR__llseek wasn't in linux/unistd.h.  This shouldn't ever      */
/* happen, but better safe than sorry.. The best we can do is emulate it     */
/* with lseek, and hope we don't get an offset that's too large (throw an    */
/* error if we do)                                                           */

lloff_t llseek (unsigned int fd, lloff_t offset, unsigned int origin) {
  off_t offt_offset = (off_t) offset;

  if ((lloff_t)offt_offset != offset) {
    /* converting to off_t and back yields different result, indicating an */
    /* overflow.. */
    errno = EINVAL;
    return -1;
  } else {
    return lseek(fd, offt_offset, origin);
  }
}

#endif /* __NR__llseek */

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#define get16(p) get_int_le(p,2)
#define get32(p) get_int_le(p,4)

/* get_int_le(): Pull an integer in little-endian format from 'buf'.        */
/* 'numbytes' should be 2 or 4, for an 16-bit or 32-bit value.  Returns the */
/* value read.  Generally called using the get16 and get32 macros above.    */

int get_int_le (const unsigned char *buf, int numbytes) {
  int value = 0;
  int i;

  for (i=numbytes-1; i>=0; i--) {
    value = (value << 8) + buf[i];
  }
  return value;
}

#define put16(p,v) put_int_le(p,2,v)
#define put32(p,v) put_int_le(p,4,v)

/* put_int_le(): Insert 'value' into 'buf' in little-endian format. */
/* 'numbytes' should be 2 or 4, for an 16-bit or 32-bit value.      */
/* Generally called using the put16 and put32 macros above.         */

void put_int_le (unsigned char *buf, int numbytes, int value) {
  int i;

  for (i=numbytes-1; i>=0; i--) {
    buf[i] = value & 0xFF;
    value = value >> 8;
  }
}

/* seek_sector(): Seek to a particular sector on the disk.  Returns zero on */
/* success, nonzero on error. */

int seek_sector (int fd, size_t secno) {
  lloff_t offset = (lloff_t) secno * SECTOR_SIZE;

  if (llseek(fd, offset, SEEK_SET) == (lloff_t) -1)
    return -1;

  return 0;
}

/*****************************************************************************/
/*                         lphdisk-specific Functions                        */
/*****************************************************************************/

/* debug(): Write a debugging message (if debug_flag is set).  Arguments are */
/* the same as for printf.                                                   */

void debug (char *fmt, ...) {
  va_list ap;

  va_start(ap, fmt);
  if (debug_flag) vprintf(fmt, ap);
  va_end(ap);
}

/* do_write(): Write 'count' bytes from 'buf' to file 'fd'. Returns number */
/* of bytes written. (this is a wrapper for the write() system call which  */
/* simply checks write_flag first, to support the -n command line option)  */

ssize_t do_write (int fd, const void *buf, size_t count) {
//static FILE *f = 0;

  if (write_flag) {
//if (f == 0) f = fdopen(fd, "a");
//return fwrite(buf, 1, count, f);
    return write(fd, buf, count);
  } else {
    return count;
  }
}

/* read_mbr(): Read the Master Boot Record from sector 0 of 'fd' and store */
/* it in 'buf'.  'buf' must point to a buffer at least MBR_SIZE.  Returns  */
/* nonzero on error.                                                       */

int read_mbr (int fd, unsigned char *buf) {
  if (seek_sector(fd, 0) ||
     (read(fd, buf, MBR_SIZE) != MBR_SIZE)) {
    if (errno) {
      perror("read_mbr");
    } else {
      fprintf(stderr, "read_mbr: short read\n");
    }
    return 1;
  }

  return 0;
}

/* parse_table(): Take an MBR in 'buf', parse the important bits of the      */
/* primary partition table, and store them in 'pi' (an array of 4 partinfo   */
/* structs, one for each primary partition).  Performs basic sanity checking */
/* on what it finds.  Returns nonzero on error.                              */

int parse_table (const unsigned char *buf, partinfo *pi) {
  int i, i_start, i_end;
  int j, j_start, j_end;
  const unsigned char *pos;

  /* Do some minimal sanity checking by verifying that the sector we've */
  /* read has the right (two-byte) magic number at the end of it for a  */
  /* partition table */

  if (get16(buf + TABLE_MAGIC) != MAGIC_ID) {
    debug("magic number=%04X\n", get16(buf + TABLE_MAGIC));
    fprintf(stderr, "parse_table: Invalid partition table magic number!\n");
    return 1;
  }

  /* Parse the important bits of the 4 primary partitions in the MBR */

  debug("Parsing Partition table:\n");
  for (i=1; i<5; i++) {
    pos = buf + TABLE_START + ((i-1) * TABLE_ENTRY);
    pi[i-1].type  = *(pos + PART_TYPE);
    pi[i-1].start = get32(pos + PART_START);
    pi[i-1].size  = get32(pos + PART_SIZE);
    debug("  %d: type=%02x, start=%u, size=%u\n", i, pi[i-1].type,
          pi[i-1].start, pi[i-1].size);

    /* Also sanity-check the partition's allocation on the disk.. */

    if (pi[i-1].type) {
      i_start = pi[i-1].start;
      i_end = i_start + pi[i-1].size;
      for (j=1; j<i; j++) {
        j_start = pi[j-1].start;
        j_end = j_start + pi[j-1].size;
        if (pi[j-1].type &&
            (((i_start >= j_start) && (i_start < j_end)) ||
             ((j_start >= i_start) && (j_start < i_end)))) {
          fprintf(stderr, "parse_table: Partition %d overlaps with"
                  " partition %d!\n", i, j);
          return 1;
        }
      }
    }
  }

  return 0;
}

/* check_proc_partitions(): Take parsed partition information for a device  */
/* (in 'pi' array) and attempt to correlate it with what the kernel reports */
/* in /proc/partitions for that device.  'dev' indicates the major/minor    */
/* numbers for the device that 'pi' contains info for.  Returns nonzero on  */
/* error. */

int check_proc_partitions (dev_t dev, const partinfo *pi) {
  FILE *f;
  int devmajor = major(dev);
  int devminor = minor(dev);
  int checked[4] = {0,0,0,0};
  int result, major, minor, size, partition;
  int i;

  if (!(f = fopen(pp_filename, "r"))) {
    fprintf(stderr, "Unable to open %s: %s\n", pp_filename, strerror(errno));
    return 1;
  }
  
  result = fscanf(f, "%*[^\n]\n");    /* Read the header line and discard it */

  while (result != EOF) {
    result = fscanf(f, "%d %d %d %*[^\n]\n", &major, &minor, &size);
    if (result == 3 && major == devmajor) {
      if (minor > devminor && minor < devminor+5) {

        /* Found one within the first four partitions of our disk.. */

        partition = minor - devminor;

        if (!(pi[partition-1].type)) {
          debug("Warning: Partition %d is listed in %s, but is not present in "
                "partition table!\n", partition, pp_filename);
          return 1;
        }

        if (size != (pi[partition-1].size / PP_BLOCK_SIZE)) {

          /* Sizes don't match! */

          if (pi[partition-1].type == EXTENDED_ID) {

            /* It's ok, the extended partition doesn't have an accurate size */
            /* listed in /proc/partitions, so we can't expect it to match.   */

          } else {
            debug("Warning: Size of partition %d in partition table (%u) does"
                  " not match size listed in %s (%u)!\n", partition,
                  pi[partition-1].size, pp_filename, size * PP_BLOCK_SIZE);
            return 1;
          }
        }

        /* Everything checks out for this partition.. */

        checked[partition-1] = 1;
      }
    }
  }

  for (i=1; i<5; i++) {
    if (pi[i-1].type && !checked[i-1]) {

      /* Found a partition in the table that isn't in /proc/partitions! */

      if (pi[i-1].type == HIBERNATION_ID) {

        /* We'll make an exception if the (apparently newly created)         */
        /* partition is the hibernation partition and there's nothing else   */
        /* odd about the disk (if the user just created it, and we're not in */
        /* danger of clobbering some other partition, it'll save on reboots  */
        /* if they can run this immediately afterward, and it should be      */
        /* safe). */

        debug("Hibernation partition %d is not listed in %s.\n", i,
              pp_filename);
      } else {
        debug("Partition %d is listed in partition table, but is not"
              " present in %s!\n", i, pp_filename);
        return 1;
      }
    }
  }

  /* All systems are go! */
  return 0;
}

/* seek_a0(): Given a partinfo array ('pi'), find the partition with the  */
/* right ID for a hibernation partition.  Returns the partition number if */
/* found, 0 if not found, or -1 if more than one partition matches.       */

int seek_a0 (const partinfo *pi) { /* FUNCTION - find a0 partition */
  int i;
  int partition = 0;

  for (i=1; i<5; i++) {
    if (pi[i-1].type && (pi[i-1].type == HIBERNATION_ID)) {
      debug("Hibernation partition found on partition %d\n", i);
      if (partition == 0) {
        partition = i;
      } else {
        partition = -1;
      }
    }
  }

  return partition;  
}

/* create_header(): Given a buffer of size SECTOR_SIZE in 'buf', create a */
/* NoteBIOS hibernation header sector appropriate for a hibernation       */
/* partition of size 'partsize'.                                          */

void create_header (unsigned char *buf, size_t partsize) {
  unsigned int word;			/* byte pair for checksum addition */
  unsigned int checksum;		/* running checksum total */
  int i;  				/* general counter variable */

  memcpy(buf, header_base, 16);         /* Start off with the base header */

  buf[16] = partsize & 0xFF;         /* first byte in sector size */
  buf[17] = (partsize >> 8) & 0xFF;  /* second byte in sector size */
  buf[18] = (partsize >> 16) & 0xFF; /* third byte in sector size */
  buf[19] = (partsize >> 24) & 0xFF; /* fourth byte in sector size */

  debug("create_header: read_sz_b0: %02X\n", buf[16]);
  debug("create_header: read_sz_b1: %02X\n", buf[17]);
  debug("create_header: read_sz_b2: %02X\n", buf[18]);
  debug("create_header: read_sz_b3: %02X\n", buf[19]);
  debug("create_header: Bytes for total sectors: %08X\n", partsize);

  for (i = 20; i < SECTOR_SIZE; i++) buf[i] = 0xFF; /* header filler with FFs */

  checksum = 0;
  for (i = SECTOR_SIZE-2; i > 6; i -= 2) {	/* compute the checksum */
    
    word = get16(buf+i); 
    checksum = checksum + word;			/* add word to checksum */

    debug("create_header: i=%03d W=%04X CS=%08X %08X %08X\n",
      i, word, checksum, -checksum, ~checksum);

  }

  checksum = ~checksum;			/* invert checksum */
  buf[6] = checksum & 0xFF;             /* significant high byte of checksum */
  buf[7] = (checksum >> 8) & 0xFF;      /* significant low byte of checksum */

  debug("create_header: Bytes (in order) to insert in checksum spot:");
  debug(" %02X %02X\n", buf[6], buf[7]);
}

/* do_format(): Actually do the formatting of a hibernate partition.  'fd' */
/* is a descriptor for the "master" disk device (_not_ the partition       */
/* device), and 'pinfo' contains the partinfo struct for the appropriate   */
/* partition to use.  Returns nonzero on failure.                          */

int do_format (int fd, partinfo pinfo) { 
  int i;
  unsigned char *buf;
  unsigned int start_sector = pinfo.start;
  unsigned int partsize = pinfo.size;

  buf = malloc(SECTOR_SIZE);
  if (!buf) {
    fprintf(stderr, "do_format: memory allocation failed!\n");
    return 1;
  }

  create_header(buf, partsize);

/* debug section */
  if (debug_flag) {
    char *spacer;			/* debug output beautifier :) */
    debug("do_format: Partition header:\ndo_format: ");
    for (i=0; i<512; i++) {				

      if ((i+1) % 16 == 0) {
         spacer = "\ndo_format: ";
      } else {
        if ((i+1) % 8 == 0) {
          spacer = "   ";
        } else if ((i+1) % 4 == 0) {
          spacer = "  ";
        } else {
          spacer = " ";
        }
      }

      debug("%02X%s", buf[i], spacer);
    }
    debug("End of header...\n");
  }
/* end debug section */

  debug("seeking to sector %u...\n", start_sector);
  seek_sector(fd, start_sector);       /* position to beginning of partition */

  /* Write two copies of the header sector */
  if ((do_write(fd, buf, SECTOR_SIZE) != SECTOR_SIZE) ||
      (do_write(fd, buf, SECTOR_SIZE) != SECTOR_SIZE)) {
    perror("do_format(header)");
    return 1;
  }

  for (i = 0; i < SECTOR_SIZE; i++) buf[i] = 0x50;

  /* Write partsize-2 "blank" sectors */
  for (i = 3; i <= partsize; i++) {
    if (do_write(fd, buf, SECTOR_SIZE) != SECTOR_SIZE) {
      perror("do_format");
      return 1;
    }
				/* only update output every 50 sectors */
    if (((i % 50) == 0) || (i == partsize)) {
      if (quiet_flag == 0) {
        printf("Formatting sector %d of %u.", i, partsize);
        printf(" (sectors of %d bytes)\r", SECTOR_SIZE);
      }
    }
  }

  return 0;
}

/* mtrr_physmem(): Use /proc/mtrr to attempt to determine the amount of   */
/* physical RAM in the system.  Returns the size of RAM (in KB) indicated */
/* by /proc/mtrr, or zero if it could not determine an appropriate value. */

int mtrr_physmem(void) {
  FILE *f;
  int base, size;

  if (!(f = fopen(mtrr_filename, "r"))) {
    debug("Unable to open %s: %s\n", mtrr_filename, strerror(errno));
    return 0;
  }
  if ((fscanf(f, "reg%*d: base=0x%*x (%dMB), size=%dMB", &base, &size) != 2) ||
      (base != 0)) {
    debug("Parse of %s failed.\n", mtrr_filename);
    return 0;
  }
  fclose(f);

  size *= 1024;
  debug("%s reports main RAM as %d KB\n", mtrr_filename, size);

  return size;
}

/* meminfo_physmem(): Use /proc/meminfo to attempt to determine the amount   */
/* of physical RAM in the system.  Returns the size of RAM (in KB) indicated */
/* by /proc/meminfo, or zero if it could not determine an appropriate value. */

int meminfo_physmem(void) {
  FILE *f;
  unsigned int size;
  int ramsize;

  if (!(f = fopen(meminfo_filename, "r"))) {
    debug("Unable to open %s: %s\n", meminfo_filename, strerror(errno));
    return 0;
  }
  fscanf(f, "%*[^\n]\n");             /* Read the header line and discard it */
 
  if (fscanf(f, "Mem: %u", &size) != 1) {
    debug("Parse of %s failed.\n", meminfo_filename);
    return 0;
  }
  fclose(f);

  /* convert to KB and then round up to the next power of 2 (since RAM */
  /* sizes don't come in anything else, so this should correct for the */
  /* kernel size, etc)                                                 */
  size >>= 10;
  debug("%s reports memory size of %d KB", meminfo_filename, size);
  for (ramsize = 1; size; size >>= 1) ramsize <<= 1;

  debug(" (adjusted=%d)\n", ramsize);

  return ramsize;
}

/* get_physmem(): Use all available methods to get a best guess of the     */
/* amount of physical RAM in the system.  Returns the size of RAM (in KB), */
/* or zero if it could not determine an appropriate value.                 */

int get_physmem(void) {
  int mtrr_size, meminfo_size;

  /* First try /proc/mtrr, as this gives us actual physical memory on most */
  /* systems.  This info won't exist on earlier kernels or if the kernel   */
  /* wasn't compiled with it enabled, though. (this can also give rather   */
  /* wonky info on non-IA32 systems, or possibly really odd IA32 ones, but */
  /* it's very unlikely we'll find a laptop with one of these              */
  /* configurations, especially one with a NoteBIOS in it)                 */

  mtrr_size = mtrr_physmem();

  /* Now try /proc/meminfo.  This is the total usable memory reported by the */
  /* kernel, and should always be available, but this can be reduced by the  */
  /* kernel's size and internal allocations, as well as being overridable by */
  /* boot parameters, etc.  The meminfo_physmem() routine attempts to        */
  /* compensate for the space used by the kernel, but can't do anything      */
  /* about boot parameters or some other things that can change this value.  */

  meminfo_size = meminfo_physmem();

  if (mtrr_size >= meminfo_size) {
    debug("get_physmem: RAM size is %d KB\n", mtrr_size);
    return mtrr_size;
  } else {
    debug("get_physmem: RAM size is %d KB\n", meminfo_size);
    return meminfo_size;
  }
}

/* vesa_videomem(): Use VESA BIOS Extension calls to attempt to query the  */
/* amount of RAM on the video card (this should work on almost all modern  */
/* models).  This routine uses a copy of Josh Vanderhoof's LRMI library    */
/* taken from svgalib to perform the appropriate BIOS interrupts.  Returns */
/* the amount of video RAM detected (in KB) or zero if it was unable to    */
/* obtain a number.                                                        */

int vesa_videomem(void) {
  struct LRMI_regs r;
  struct vbe_info_block *info;
  int video_mem;

  /* (we do this first so we can catch the case of the user not being root  */
  /* (and quietly handle it with a debug message) before LRMI_init fails to */
  /* open /dev/mem and prints an error message (since the user not being    */
  /* root is not technically an error, it just reduces some of the          */
  /* functionality we can provide)                                          */
  /* Allow read/write to all IO ports: */

  if (iopl(3) == -1) {
    debug("Can't set privilege level for VESA probe: %s\n", strerror(errno));
    return 0;
  }

  if (!LRMI_init()) {
    debug("LRMI initialization failed.\n");
    iopl(0);
    return 0;
  }

  info = LRMI_alloc_real(sizeof(struct vbe_info_block));

  if (info == NULL) {
    debug("vesa_videomem: can't alloc real mode memory.\n");
    iopl(0);
    return 0;
  }

  memcpy(info->vbe_signature, "VBE2", 4);

  memset(&r, 0, sizeof(r));
  r.eax = 0x4f00;
  r.es = (unsigned int)info >> 4;
  r.edi = 0;

  if (!LRMI_int(0x10, &r)) {
    debug("Can't get VESA info (vm86 failure).\n");
    iopl(0);
    return 0;
  }

  /* Set privilege level back to normal now that we're done with it */
  iopl(0);
  
  if ((r.eax & 0xffff) != 0x4f ||
      strncmp(info->vbe_signature, "VESA", 4) != 0) {
    debug("No VESA bios.\n");
    return 0;
  }

  debug("VBE Version %x.%x\n", (int)(info->vbe_version >> 8) & 0xff,
        (int)info->vbe_version & 0xff);

  debug("Video card identified as: %s\n",
        (char *)(info->oem_string_seg * 16 + info->oem_string_off));

  /* The VESA BIOS call returns memory in 64KB units.  Multiply to get KB.. */

  video_mem = (int)(info->total_memory) * 64;

  debug("Total video memory: %u KB\n", video_mem);

  LRMI_free_real(info);

  return video_mem;
}

/* get_videomem(): Use all available methods to get a best guess of the     */
/* amount of RAM in the video card.  Returns the size of RAM (in KB),       */
/* or zero if it could not determine an appropriate value.                  */
/* (currently vesa_videomem() is the only method, so this is just a wrapper */
/* define)                                                                  */

#define get_videomem() vesa_videomem()

/*****************************************************************************/
/*                                Main Program                               */
/*****************************************************************************/

char *argv0;

const char short_opts[] = "hpqdnf";
const struct option long_opts[] = {
  {"help",      0, 0, 'h'},
  {"probeonly", 0, 0, 'p'},
  {"quiet",     0, 0, 'q'},
  {"debug",     0, 0, 'd'},
  {"nowrite",   0, 0, 'n'},
  {"force",     0, 0, 'f'},
{0,0,0,0}};

const char usage_string[] = "\
Usage: %1$s [options] [device]
Prepare a hibernation partition for APM suspend-to-disk.

options:
  -h, --help       Display brief usage and option information (this screen)
  -p, --probeonly  Only calculate and display required size, do not format
  -q, --quiet      Turn off informational messages, useful for scripts
  -d, --debug      Turn on (verbose) debugging messages
  -n, --nowrite    Do not actually write to the disk
  -f, --force      **DANGEROUS**  Format without regard to potential problems

'device' should be a raw disk device (not a partition).  The default device
is /dev/hda.

(%2$s)\n\n";

void print_usage (void) {
  char *progname = rindex(argv0, '/');
  progname = progname ? progname+1 : argv0;
  printf(usage_string, progname, version_string);
}

int main (int argc, char **argv) {      /* MAIN FUNCTION - the beast */
  char drive[FILENAME_MAX] = "/dev/hda";/* Default to /dev/hda */
  int fd;
  struct stat st;
  unsigned char mbr_buf[MBR_SIZE];
  partinfo pi[4];
  dev_t dev;
  int partition;
  int ramsize, vramsize, required_size;
  size_t required_sectors;

  argv0 = argv[0];

  while (1) {                           /* loop to parse command line args */
    int c;                              /* current getopt option */

    c = getopt_long (argc, argv, short_opts, long_opts, 0);
    if (c == -1) break;                 /* break if error */

    switch (c) {                        /* case switch for options */
      case 'h':                         /* case h - help */
        print_usage();			/* print help information */
        return ERR_USAGE;

      case 'p':                         /* case p - probeonly */
        probeonly_flag = 1;
        break;

      case 'q':				/* case q - be quiet! */
        quiet_flag = 1;			/* set quiet flag */
        break;

      case 'd':				/* case d - debug on */
        debug_flag = 1;
        break;

      case 'n':				/* case n - no write */
        write_flag = 0;
        break;

      case 'f':                         /* case f - force */
        force_flag = 1;                 /* use the force */
        break;

      case '?':
      case ':':
        print_usage();
        return ERR_BADARGS;

    }
  }

  if (optind < argc) strcpy(drive, argv[optind++]);

  if (optind < argc) {                /* extra argument routine */
    printf("Unexpected arguments: "); /* print error */
    while (optind < argc) {
      printf ("%s ", argv[optind++]); /* print bad args */
    }
    printf ("\n");
    print_usage();
    return ERR_BADARGS;
  }

  /* That takes care of argument parsing, now on with the actual work... */

  if (!(ramsize = get_physmem())) {
    if (!quiet_flag) printf("Warning: Cannot determine physical RAM.\n");
    required_size = 0;
  } else if (!(vramsize = get_videomem())) {
    if (!quiet_flag) printf("Warning: Unable to determine the amount of video"
                            " RAM.\n");
    required_size = 0;
    required_sectors = 0;
  } else {
    required_size = ramsize + vramsize + PROBE_PADDING;
    required_sectors = (((size_t)required_size * 1024) / SECTOR_SIZE) + 2;
  }

  if (!required_size) {
    if (!quiet_flag) printf("Reccomended partition size is unknown.\n");
  } else {
    if (!quiet_flag) printf("Reccomended partition size is %d MB"
                            " (%d sectors)\n", ((required_size+1023) >> 10),
                            required_sectors);
  }

  if (probeonly_flag) {
    return (required_size ? 0 : ERR_CANTPROBE);
  }

  /* Open the file. */

  if ((fd = open(drive, O_RDWR)) == -1) {
    fprintf(stderr, "Error: cannot open %s: %s\n", drive, strerror(errno));

    /* This error pops up a lot if someone runs lphdisk with no arguments    */
    /* and isn't root, which can happen if they've just                      */
    /* installed/encountered it and don't know what it does or how it works. */
    /* Give 'em a hint if this is the case.                                  */

    if (argc == 1) printf("(Try '%s --help' for help)\n", argv0);

    return ERR_OPEN;
  }

  /* Are we looking at a block device? */

  if (fstat(fd, &st)) {
    perror("fstat");
    return ERR_STAT;
  }
  if (S_ISBLK(st.st_mode)) {
    dev = st.st_rdev;
    debug("%s is a block device (major=%d, minor=%d)\n", drive, major(dev),
          minor(dev));
  } else {
    if (force_flag) {
      if (!quiet_flag) fprintf(stderr, "Warning: %s is not a block device.\n",
                               drive);
      dev = 0;
    } else {
      fprintf(stderr, "Error: %s is not a block device (override with -f)\n",
              drive);
      return ERR_BADFILE;
    }
  }

  /* Read the MBR and parse the partition table. */

  if (read_mbr(fd, mbr_buf)) {
    fprintf(stderr, "Unable to read master boot record.\n");
    return ERR_READ;
  }

  if (parse_table(mbr_buf, pi)) {
    fprintf(stderr, "Unable to parse partition table.\n");
    return ERR_TABLE;
  }

  /* If we're using a block device, then verify it against /proc/partitions */

  if (dev && check_proc_partitions(dev, pi)) {
    if (force_flag) {
      if (!quiet_flag) fprintf(stderr, "Warning: /proc/partitions does not"
                                       " match partition table.\n");
    } else {
      fprintf(stderr, "Error: /proc/partitions does not match partition table"
                      " (override with -f).\n");
      return ERR_TABLE;
    }
  }

  /* Find the right partition. */

  partition = seek_a0(pi);
  if (!partition) {
    fprintf(stderr, "Error: Unable to find partition of type %02X.\n",
            HIBERNATION_ID);
    return ERR_FINDPART;
  } else if (partition < 0) {
    fprintf(stderr, "Error: More than one partition is of type %02X.  Unable"
                    " to determine which to use.\n", HIBERNATION_ID);
    return ERR_FINDPART;
  }


  /* So far so good.. now it's time to actually _do_ it. */

  if (!quiet_flag) printf("Creating hibernate area on %s, partition %d...\n",
                          drive, partition);

  /* Check for a couple of things to warn people about.. */

  if ((dev != makedev(3, 0)) && !quiet_flag) {
    fprintf(stderr, "Warning: The BIOS will probably be unable to use this"
            " hibernate partition\n         because it is not on the first IDE"
            " drive.\n");
  }

  if ((pi[partition-1].size < required_sectors) && !quiet_flag) {
    fprintf(stderr, "Warning: hibernate partition size (%d) is smaller than"
                    " reccomended size (%d).\n", pi[partition-1].size,
            required_sectors);
  }

  /* And off we go! */

  if (do_format(fd, pi[partition-1])) {
    if (!quiet_flag) printf("\n");
    fprintf(stderr, "Format failed.\n");
    return ERR_WRITE;
  } else {
    if (!quiet_flag) printf("\nFormat complete.\n");
  }

  /* All done..  Clean up and exit. */

  close(fd);
  return 0;
}
