//+++2002-08-17
//    Copyright (C) 2001,2002  Mike Rieker, Beverly, MA USA
//
//    This program is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; version 2 of the License.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//---2002-08-17

/************************************************************************/
/*									*/
/*  Modify disk's partition table					*/
/*									*/
/************************************************************************/

//#include <stdlib.h>
//#include <string.h>

#include "ozone.h"
#include "oz_crtl_malloc.h"
#include "oz_io_disk.h"
#include "oz_knl_devio.h"
#include "oz_knl_status.h"
#include "oz_sys_condhand.h"
#include "oz_sys_handle.h"
#include "oz_sys_handle_getinfo.h"
#include "oz_sys_io.h"
#include "oz_sys_io_fs.h"
#include "oz_sys_io_fs_printf.h"
#include "oz_util_start.h"

#define BOOTBLOCK 0
#define BLOCKSIZE 512
#define MAGIC 0xAA55

#include "ozone_part_486.h"

static char *pn = "partition";

static int getpartidx (char **cp_r);
static char *getpname (char **cp_r);
static int getptype (char **cp_r);
static OZ_Dbn getblockcount (char **cp_r, OZ_Dbn disksize);
static OZ_Dbn getblockstart (char **cp_r, OZ_Dbn disksize, OZ_Dbn count);
static int eolcheck (char *cp);
static char *skipspaces (char *cp);
static int matchkeyword (const char *cp, const char *kw, int min);

uLong oz_util_main (int argc, char *argv[])

{
  char cmdbuff[64], *cp, diskname[OZ_DEVUNIT_NAMESIZE], *diskparent, partdiskname[OZ_DEVUNIT_NAMESIZE];
  int i, itisours, j, nomodsatall;
  OZ_Dbn diskoffset, disksize;
  OZ_Handle h_disk, h_partchn, h_partdev, h_prev;
  OZ_Handle_item hitems[2];
  OZ_IO_disk_getinfo1 disk_getinfo1, part_getinfo1;
  OZ_IO_disk_readblocks disk_readblocks;
  OZ_IO_disk_setvolvalid disk_setvolvalid;
  OZ_IO_disk_writeblocks disk_writeblocks;
  OZ_IO_fs_readrec fs_readrec;
  struct { char string[OZ_DEVUNIT_NAMESIZE]; } partdisknames[4];
#pragma pack (1)
  struct { uByte code[BLOCKSIZE-2-64-32];
           struct { char string[8]; } partnames[4];
           struct { uByte flag;
                    uByte fill1[3];
                    uByte ptype;
                    uByte fill2[3];
                    uLong start;
                    uLong count;
                  } partitions[4];
           uWord magic;
         } original, working;
#pragma nopack
  uLong cmdrlen, sts;

  if (argc > 0) pn = argv[0];

  if (argc != 2) {
    oz_sys_io_fs_printf (oz_util_h_error, "%s: usage: %s <disk>\n", pn, pn);
    return (OZ_MISSINGPARAM);
  }

  memset (partdisknames, 0, sizeof partdisknames);

  memset (&fs_readrec, 0, sizeof fs_readrec);
  fs_readrec.size    = sizeof cmdbuff - 1;
  fs_readrec.buff    = cmdbuff;
  fs_readrec.trmsize = 1;
  fs_readrec.trmbuff = "\n";
  fs_readrec.rlen    = &cmdrlen;
  cp = malloc (strlen (pn) + 4);
  strcpy (cp, pn);
  strcat (cp, "> ");
  fs_readrec.pmtsize = strlen (cp);
  fs_readrec.pmtbuff = cp;

  /* Assign channel to disk and get its offical unit name */

  sts = oz_sys_io_assign (OZ_PROCMODE_KNL, &h_disk, argv[1], OZ_LOCKMODE_CW);
  if (sts != OZ_SUCCESS) {
    oz_sys_io_fs_printf (oz_util_h_error, "%s: error %u assigning channel to disk %s\n", pn, sts, argv[1]);
    return (sts);
  }
  sts = oz_sys_iochan_getunitname (h_disk, sizeof diskname, diskname);
  if (sts != OZ_SUCCESS) oz_sys_condhand_signal (2, sts, 0);

  /* Make sure disk is spun up */

  memset (&disk_setvolvalid, 0, sizeof disk_setvolvalid);
  disk_setvolvalid.valid = 1;
  sts = oz_sys_io (OZ_PROCMODE_KNL, h_disk, 0, OZ_IO_DISK_SETVOLVALID, sizeof disk_setvolvalid, &disk_setvolvalid);
  if (sts != OZ_SUCCESS) {
    oz_sys_io_fs_printf (oz_util_h_error, "%s: error %u turning disk %u online\n", pn, sts, diskname);
    return (sts);
  }

  /* Read about the disk */

  memset (&disk_getinfo1, 0, sizeof disk_getinfo1);
  sts = oz_sys_io (OZ_PROCMODE_KNL, h_disk, 0, OZ_IO_DISK_GETINFO1, sizeof disk_getinfo1, &disk_getinfo1);
  if (sts != OZ_SUCCESS) {
    oz_sys_io_fs_printf (oz_util_h_error, "%s: error %u getting info about disk %u\n", pn, sts, diskname);
    return (sts);
  }

  if (disk_getinfo1.blocksize != sizeof original) {
    oz_sys_io_fs_printf (oz_util_h_error, "%s: disk has blocksize %u, we only deal wif %u\n", pn, disk_getinfo1.blocksize, sizeof original);
    return (OZ_BADBLOCKSIZE);
  }
  disksize   = disk_getinfo1.totalblocks;
  diskoffset = disk_getinfo1.parthoststartblock;
  diskparent = disk_getinfo1.parthostdevname;

  /* Read existing partition block */

  memset (&disk_readblocks, 0, sizeof disk_readblocks);
  disk_readblocks.size = sizeof original;
  disk_readblocks.buff = &original;
  disk_readblocks.slbn = BOOTBLOCK;

  sts = oz_sys_io (OZ_PROCMODE_KNL, h_disk, 0, OZ_IO_DISK_READBLOCKS, sizeof disk_readblocks, &disk_readblocks);
  if (sts != OZ_SUCCESS) {
    oz_sys_io_fs_printf (oz_util_h_error, "%s: error %u reading %u byte block %u from %s\n", pn, sts, sizeof original, BOOTBLOCK, diskname);
    return (sts);
  }
  working  = original;
  itisours = 0;

  /* Make sure it has the Magic Word */

  if (original.magic != MAGIC) {
    oz_sys_io_fs_printf (oz_util_h_output, "existing block does not have magic word (%4.4x instead of %4.4x)\n", original.magic, MAGIC);
  }

  /* Now see if it is even a partition table (and not a boot block) */

  else for (i = 0; i < 4; i ++) {
    if (original.partitions[i].flag & 0x7F != 0) {
      oz_sys_io_fs_printf (oz_util_h_output, "disk does not contain a partition table (flag[%d] is %2.2x)\n", i, original.partitions[i].flag);
      working.magic = 0;
    }
  }

  /* If there are any active disks on the partitions, mark the partitions so we can't move them */

  nomodsatall = 0;
  hitems[0].code = OZ_HANDLE_CODE_DEVICE_FIRST;
  hitems[0].size = sizeof h_partdev;
  hitems[0].buff = &h_partdev;
  hitems[0].rlen = NULL;
  hitems[1].code = OZ_HANDLE_CODE_DEVICE_UNITNAME;
  hitems[1].size = sizeof partdiskname;
  hitems[1].buff = partdiskname;
  hitems[1].rlen = NULL;
  for (h_prev = 0;; h_prev = h_partdev) {

    /* Get handle to first/next device in system */

    sts = oz_sys_handle_getinfo (h_prev, 1, hitems + 0, NULL);		/* get handle to first/next device */
    if (sts != OZ_SUCCESS) oz_sys_condhand_signal (2, sts, 0);		/* should always be successful */
    oz_sys_handle_release (OZ_PROCMODE_KNL, h_prev);			/* release handle to previous device */
    if (h_partdev == 0) break;						/* stop if reached end of the list */
    hitems[0].code = OZ_HANDLE_CODE_DEVICE_NEXT;			/* from now on get next one (not first) */
    sts = oz_sys_handle_getinfo (h_partdev, 1, hitems + 1, NULL);	/* get new device's name */
    if (sts != OZ_SUCCESS) oz_sys_condhand_signal (2, sts, 0);
    if (strcmp (partdiskname, diskname) == 0) continue;

    /* See if it is a disk that is one of our partitions, skip over it if it isn't */

    sts = oz_sys_io_assign (OZ_PROCMODE_KNL, &h_partchn, partdiskname, OZ_LOCKMODE_NL);
    if (sts != OZ_SUCCESS) {
      if (sts != OZ_KERNELONLY) oz_sys_io_fs_printf (oz_util_h_error, "%s: error %u assigning channel to device %s\n", pn, sts, partdiskname);
      continue;
    }
    memset (&part_getinfo1, 0, sizeof part_getinfo1);
    sts = oz_sys_io (OZ_PROCMODE_KNL, h_partchn, 0, OZ_IO_DISK_GETINFO1, sizeof part_getinfo1, &part_getinfo1);
    oz_sys_handle_release (OZ_PROCMODE_KNL, h_partchn);
    if (sts != OZ_SUCCESS) {
      if ((sts != OZ_BADIOFUNC) && (sts != OZ_DEVOFFLINE)) oz_sys_io_fs_printf (oz_util_h_error, "%s: error %u getting %s info\n", pn, sts, partdiskname);
      continue;
    }
    if (strcmp (part_getinfo1.parthostdevname, diskname) != 0) continue;

    /* Try to find it in the partition table */

    for (i = 0; i < 4; i ++) {
      if ((working.magic == MAGIC) 
       && (working.partitions[i].count == part_getinfo1.totalblocks) 
       && (working.partitions[i].start == part_getinfo1.parthoststartblock)) break;
    }

    /* If not found, who knows why, so don't allow any modifications */

    if (i == 4) {
      oz_sys_io_fs_printf (oz_util_h_output, "disk %s references %s for %u blocks at %u but can't be found in original table\n", 
                           partdiskname, diskname, part_getinfo1.totalblocks, part_getinfo1.parthoststartblock);
      nomodsatall = 1;
    }

    /* Found, make sure they can't change its definition */

    else strcpy (partdisknames[i].string, partdiskname);
  }

  /* See if it is our partition code or someone elses.  If it is ours, we can print out names, else we cant. */

  if (working.magic == MAGIC) {
    if (memcmp (working.code, ourcode, sizeof ourcode) == 0) itisours = 1;
    goto printout;
  }

  /* Re-initialize partition table -                                     */
  /* We always overlay the code section                                  */
  /* Reset the partition names only if it wasn't in our format           */
  /* Reset the partition definitions only if it wasn't a partition block */

reinit:
  oz_sys_io_fs_printf (oz_util_h_output, "re-initializing partition block\n");
  movc4 (sizeof ourcode, ourcode, sizeof working.code, working.code);		/* always plunk our code in there */
  if (!itisours) {								/* see if it wasn't ours */
    memset (working.partnames, 0, sizeof working.partnames);			/* it wasn't, re-init the names */
    itisours = 1;								/* (it is now our format) */
  }
  if (working.magic != MAGIC) {							/* see if table was valid */
    memset (working.partitions, 0, sizeof working.partitions);			/* if not, clear it out */
    working.magic = MAGIC;							/* table is now valid */
  } else {
    for (i = 0; i < 4; i ++) working.partitions[i].flag = 0;
  }

  /* Print out partition table */

printout:
  oz_sys_io_fs_printf (oz_util_h_output, "\nDisk %s contains %u blocks", diskname, disksize);
  if (diskoffset != 0) oz_sys_io_fs_printf (oz_util_h_output, " starting at offset %u of %s\n", diskoffset, diskparent);
  oz_sys_io_fs_printf (oz_util_h_output, "\n\n");

  for (i = 0; i < 4; i ++) {
    oz_sys_io_fs_printf (oz_util_h_output, "  %d%c", i + 1, working.partitions[i].flag ? '*' : ' ');
    if (itisours) oz_sys_io_fs_printf (oz_util_h_output, " %8s", working.partnames[i].string);
    oz_sys_io_fs_printf (oz_util_h_output, ": type %2.2x has %10u blocks at %10u", 
                         working.partitions[i].ptype, working.partitions[i].count, working.partitions[i].start);
    if (partdisknames[i].string[0] != 0) oz_sys_io_fs_printf (oz_util_h_output, "  %s", partdisknames[i].string);
    oz_sys_io_fs_printf (oz_util_h_output, "\n");
  }

  /* Read command and process it */

  do {
    sts = oz_sys_io (OZ_PROCMODE_KNL, oz_util_h_input, 0, OZ_IO_FS_READREC, sizeof fs_readrec, &fs_readrec);
    if (sts != OZ_SUCCESS) {
      oz_sys_io_fs_printf (oz_util_h_error, "%s: error %u reading command input\n", pn, sts);
      return (sts);
    }
    cmdbuff[cmdrlen] = 0;
    cp = skipspaces (cmdbuff);
  } while (*cp == 0);

  /* Activate partition */

  if (i = matchkeyword (cp, "activate", 1)) {
    cp = skipspaces (cp + i);
    i  = getpartidx (&cp);
    if (i < 0) goto printout;
    if (!eolcheck (cp)) goto printout;
    if (nomodsatall) {
      oz_sys_io_fs_printf (oz_util_h_output, "no modifications allowed\n");
      goto printout;
    }
    for (j = 0; j < 4; j ++) {
      working.partitions[j].flag = 0;
      if (j == i) working.partitions[j].flag = 0x80;
    }
    goto printout;
  }

  /* Change partition parameters */

  if (i = matchkeyword (cp, "change", 1)) {
    char *pname;
    int ptype;
    OZ_Dbn count, start;

    cp = skipspaces (cp + i);
    i  = getpartidx (&cp);
    if (i < 0) goto printout;
    if (nomodsatall) {
      oz_sys_io_fs_printf (oz_util_h_output, "no modifications allowed\n");
      goto printout;
    }
    if (itisours) {
      pname = getpname (&cp);
      if (pname == NULL) goto printout;
    }
    ptype = working.partitions[i].ptype;
    count = working.partitions[i].count;
    start = working.partitions[i].start;
    if (*cp != 0) {
      ptype = getptype (&cp);
      if (ptype < 0) goto printout;
      if (*cp != 0) {
        count = getblockcount (&cp, disksize);
        if (count == 0) goto printout;
        if (*cp != 0) {
          start = getblockstart (&cp, disksize, count);
          if (start == 0) goto printout;
          if (!eolcheck (cp)) goto printout;
        }
      }
    }
    if ((partdisknames[i].string[0] != 0) && ((working.partitions[i].ptype != ptype) || (working.partitions[i].count != count) || (working.partitions[i].start != start))) {
      oz_sys_io_fs_printf (oz_util_h_output, "no modifications allowed - referenced by disk %s\n", partdisknames[i].string);
    } else {
      if (itisours) strncpyz (working.partnames[i].string, pname, sizeof working.partnames[i].string);
      working.partitions[i].ptype = ptype;
      working.partitions[i].count = count;
      working.partitions[i].start = start;
    }
    goto printout;
  }

  /* Delete partition */

  if (i = matchkeyword (cp, "delete", 3)) {
    cp = skipspaces (cp + i);
    i  = getpartidx (&cp);
    if (i < 0) goto printout;
    if (!eolcheck (cp)) goto printout;
    if (nomodsatall || (partdisknames[i].string[0] != 0)) {
      oz_sys_io_fs_printf (oz_util_h_output, "no modifications allowed\n");
      goto printout;
    }
    if (itisours) memset (working.partnames[i].string, 0, sizeof working.partnames[i].string);
    working.partitions[i].ptype = 0;
    working.partitions[i].count = 0;
    working.partitions[i].start = 0;
    goto printout;
  }

  /* Re-initialize */

  if (i = matchkeyword (cp, "initialize", 4)) {
    cp = skipspaces (cp + i);
    if (!eolcheck (cp)) goto printout;
    if (nomodsatall) {
      oz_sys_io_fs_printf (oz_util_h_output, "no modifications allowed\n");
      goto printout;
    }
    goto reinit;
  }

  /* Quit (abandon changes) */

  if (i = matchkeyword (cp, "quit", 4)) {
    cp = skipspaces (cp + i);
    if (!eolcheck (cp)) goto printout;
    return (OZ_SUCCESS);
  }

  /* Write partition table out to disk */

  if (i = matchkeyword (cp, "write", 5)) {
    cp = skipspaces (cp + i);
    if (!eolcheck (cp)) goto printout;

    if (nomodsatall) {
      oz_sys_io_fs_printf (oz_util_h_output, "no modifications allowed\n");
      goto printout;
    }

    /* Check for overlapping partitions */

    for (i = 0; i < 4; i ++) {
      for (j = 0; j < 4; j ++) {
        if (i == j) continue;
        if ((working.partitions[j].start >= working.partitions[i].start) 
         && (working.partitions[j].start < working.partitions[i].count + working.partitions[i].start)) {
          oz_sys_io_fs_printf (oz_util_h_output, "partitions %d and %d overlap\n", i + 1, j + 1);
          goto printout;
        }
      }
    }

    /* It is ok to write the partition table */

    memset (&disk_writeblocks, 0, sizeof disk_writeblocks);
    disk_writeblocks.size = sizeof working;
    disk_writeblocks.buff = &working;
    disk_writeblocks.slbn = BOOTBLOCK;

    sts = oz_sys_io (OZ_PROCMODE_KNL, h_disk, 0, OZ_IO_DISK_WRITEBLOCKS, sizeof disk_writeblocks, &disk_writeblocks);
    if (sts == OZ_SUCCESS) oz_sys_io_fs_printf (oz_util_h_output, "new partition table written to disk %s\n", diskname);
    else oz_sys_io_fs_printf (oz_util_h_error, "%s: error %u writing %u byte block %u to %s\n", pn, sts, sizeof working, BOOTBLOCK, diskname);
    return (sts);
  }

  /* Unknown, print help text */

  oz_sys_io_fs_printf (oz_util_h_output, "\nCommands are:\n");
  oz_sys_io_fs_printf (oz_util_h_output, "  activate <number>\n");
  if (!itisours) oz_sys_io_fs_printf (oz_util_h_output, "  change <number> [<type> [<blockcount> [<start>]]]\n");
  else oz_sys_io_fs_printf (oz_util_h_output, "  change <number> <name> [<type> [<blockcount> [<start>]]]\n");
  oz_sys_io_fs_printf (oz_util_h_output, "  delete <number>\n");
  oz_sys_io_fs_printf (oz_util_h_output, "  initialize\n");
  oz_sys_io_fs_printf (oz_util_h_output, "  quit\n");
  oz_sys_io_fs_printf (oz_util_h_output, "  write\n");
  goto printout;
}

static int getpartidx (char **cp_r)

{
  char *cp;
  int i;

  cp = *cp_r;
  i  = *(cp ++) - '1';
  if ((i < 0) || (i > 3) || (*cp > ' ')) {
    oz_sys_io_fs_printf (oz_util_h_output, "partition number must be digit 1..4\n");
    return (-1);
  }
  *cp_r = skipspaces (cp);
  return (i);
}

static char *getpname (char **cp_r)

{
  char *cp;
  int i;

  cp = *cp_r;
  for (i = 0; i < 8; i ++) if (cp[i] <= ' ') break;
  if (i == 8) {
    oz_sys_io_fs_printf (oz_util_h_output, "partition name must be at most 7 chars\n");
    return (NULL);
  }
  *cp_r = cp + i;
  if (cp[i] != 0) {
    *cp_r = skipspaces (cp + i);
    cp[i] = 0;
  }
  return (cp);
}

static int getptype (char **cp_r)

{
  char *cp;
  int ptype;

  ptype = strtoul (*cp_r, &cp, 16);
  if ((*cp > ' ') || (ptype < 1) || (ptype > 255)) {
    oz_sys_io_fs_printf (oz_util_h_output, "ptype must be hex integer 01..FF\n");
    ptype = -1;
  }
  *cp_r = skipspaces (cp);
  return (ptype);
}

static OZ_Dbn getblockcount (char **cp_r, OZ_Dbn disksize)

{
  char *cp;
  OZ_Dbn count;

  count = strtoul (*cp_r, &cp, 0);
  if ((*cp > ' ') || (count == 0) || (count >= disksize)) {
    oz_sys_io_fs_printf (oz_util_h_output, "count must be integer less than disk size\n");
    count = 0;
  }
  *cp_r = skipspaces (cp);
  return (count);
}

static OZ_Dbn getblockstart (char **cp_r, OZ_Dbn disksize, OZ_Dbn count)

{
  char *cp;
  OZ_Dbn start;

  start = strtoul (*cp_r, &cp, 0);
  if ((*cp > ' ') || (start == 0) || (start + count > disksize)) {
    oz_sys_io_fs_printf (oz_util_h_output, "start must be an integer that does not run off end of disk\n");
    start = 0;
  }
  *cp_r = skipspaces (cp);
  return (start);
}

static int eolcheck (char *cp)

{
  if (*cp == 0) return (1);
  oz_sys_io_fs_printf (oz_util_h_output, "extra stuff at end of command (%s)\n", cp);
  return (0);
}

static char *skipspaces (char *cp)

{
  while ((*cp != 0) && (*cp <= ' ')) cp ++;
  return (cp);
}

static int matchkeyword (const char *cp, const char *kw, int min)

{
  int i;

  for (i = strlen (kw); i >= min; i --) if (strncasecmp (cp, kw, i) == 0) return (i);
  return (0);
}