/* hdinfo_sco.c
 * adadpted from dosemu's hdinfo
 * (available from http://www.suse.com/~dosemu and elsewhere)
 * to interpret SCO's divvy tables
 * swine@iinet.net.au 970323
 * 
 * I should have put sco_fs mods in fdisk, but hdinfo.c was the first source
 * to come to hand when trying to decrease reliance on patching genhd.c.
 * It fulfils a similar need for loopback mounting of filesystems
 * with a calculated byte offset.
 * I suppose both hdimage & sco_fs support should go into fdisk one day
 */
#define CONFIG_SCO_DISKLABEL


/* header from hdinfo.c ....
 *
 * 6/2/93, gt8134b@prism.gatech.edu, Robert Sanders
 *
 * print the partition information for a given disk file (device or image).
 * Print the byte offset from the beginning of the file to the
 * start of the partition, which is useful for mounting the loop
 * device (i.e. mounting a file), which is why I wrote this.
 *
 * It's made slightly more complicated by the header prepended to
 * dosemu hdimage files.  The length of the header is kept in byte
 * 19 of the header as an unsigned long.  The header is guaranteed
 * to always be at least 128 bytes long, but never more than 512.
 *
 * BUGS: on my disk, the second logical partition of the extended partition
 *   is said to start 1 sector earlier than it actually does (and therefore
 *   512 bytes earlier).
 *
 * $Date: 1995/02/25 22:38:25 $
 * $Source: /home/src/dosemu0.60/periph/RCS/hdinfo.c,v $
 * $Revision: 2.3 $
 * $State: Exp $
 *
 */

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <stdio.h>
#ifdef __linux__
#include <linux/genhd.h>
#include <linux/hdreg.h>
#endif
#ifdef __NetBSD__
#include <machine/disklabel.h>
#define partition dos_partition
#define boot_ind dp_flag
#define head dp_shd
#define sector dp_ssect
#define cyl dp_scyl
#define sys_ind dp_typ
#define end_head dp_ehd
#define end_sector dp_esect
#define end_cyl dp_ecyl
#define start_sect dp_start
#define nr_sects dp_size
#endif

#define SECTOR_SIZE	512
#define EXT_MAGIC	5	/* sys_ind for an extended partition */

int fd = -1;
char *decfmt = "%sSector=%-6d   Offset=%-10d   Bytes=%-10d   Type=0x%02x%s\n";
char *hexfmt = "%sSector=0x%-6x   Offset=0x%-8x   Bytes=0x%-8x   Type=0x%02x%s\n";
char *fmtstring;
char *filename;

void
usage(void)
{
  fprintf(stderr, "usage: hdinfo [-h] <image file or disk device>\n");
}

#ifdef CONFIG_SCO_DISKLABEL

#ifndef SCO_PARTITION
/*
 * SCO disklabel support by Peter Swain <swine@iinet.net.au>
 * may also be in linux/genhd.h
 */

#define GNU_HURD_PARTITION 	0x63 	/* not used (yet), but same as ... */
#define SCO_PARTITION 		0x63 	/* SCO's 'divvy' extended partitions */
#endif /* !SCO_PARTITION */

enum {
	/* enable/disable chatter
	*/
	SCO_DEBUG = 0,

	/* Almost certainly wrong (from my test disk) --
	** fall back to this compiled-in default
	** if we cannot obtain geometry from device!
	** SCO aligns filesystems on cylinder boundaries, 
	** yet leaves no obvious hint in either partition tables or 
	** divvy tables as to what actual block numbers they're are aligned to!
	*/
	SCO_DEF_HEADS = 16,
	SCO_DEF_SECTORS = 62,

	/* signatures */
	DIVVY_MAGIC 	= 0x1234,
	BADTRK_MAGIC 	= 0x4321,

	/* address of magic structs (blocks into partiton)
	*/
	DIVVY_ADDR_b	= 42,
	BADTRK_ADDR_b	= 44,
};

/* patchable in a module,
** but we need a best guess in here for rootfs:
** cyl == size-of-cylinder-in-512-byte-blocks
** offset == block-offset-of-filesystems (always 0?)
*/
int sco_divvy_cyl = 0;
int sco_divvy_cyls = 1;
int sco_ignore_bad_bad = 1;	/* ignore bad-block-map error (0 or 1) */


/* check for a SCO-style divvy partition
** a sort of 'extended partition' from the xenix days
** before DOS fdisk offered more than 4 partitions.
** Why they didn't move to dog-compatible extended partitions i don't know...
*/
static int sco_divvy(struct partition *part, size_t offset, int sect_off, int ext)
{
	struct sco_divvytab {
		unsigned short magic;      /* Magic number */
		struct sco_div {
			unsigned long start_sector_k;
			unsigned long num_sectors_k;
		} div[8];
	} __attribute__ ((packed)) * dtab;		

	struct sco_badtab {
		unsigned short magic;      /* Magic number */
		short nbad;      /* how many entries */
		struct {
			unsigned short bad;
			unsigned short replace;
		} bad_pair[512 / (2*sizeof(unsigned short)) - 1];
	} * btab;		

	unsigned char buf[512];

	inline long int ktob(long int kbyte) { return kbyte*2; }

	unsigned long divvy_base_b = part->start_sect + sect_off;
	unsigned long subfs_base_b;
	const char *cyl_how = "precompiled";
	int bad_bad = 0;
	int fatal_bad = 0;
	int i;
	struct hd_geometry geom;


	if (lseek(fd, 512 * (divvy_base_b + BADTRK_ADDR_b), 0) < 0
	 || read(fd, buf, 512) != 512) {
		if (SCO_DEBUG)
			fprintf(stderr, "{bad SCO badtrk read @%ld+%db}\n",
				divvy_base_b, BADTRK_ADDR_b);
		return 0;
	}

	btab = (struct sco_badtab *) buf;
	if (btab->magic != BADTRK_MAGIC) {
		bad_bad++;
	} else {
		int i;
		const int lim =
		    sizeof(btab->bad_pair)/sizeof(btab->bad_pair[i]);

		if (btab->nbad >= lim)
			bad_bad++;

		for (i = 0; i < btab->nbad && i < lim; i++) {
			if (btab->bad_pair[i].bad != 0xffff) {
				/* too hard for now, we don't do bad-track
				** remapping yet. Perhaps it could be
				** accomplished in conjunction with the md
				** driver, expressing the virtual drive as the
				** concatenation of large slices of good tracks,
				** separated by thin slices of remapped tracks?
				*/
				fprintf(stderr, "{SCO badtrk[%d]=%d, too hard!}\n", 
					i, btab->bad_pair[i].bad);
				fatal_bad++;
			}
		}
	}

	if (bad_bad || fatal_bad) {
		fprintf(stderr, "{invalid SCO badtrk table (%x:%d) @%ld+%db}\n",
			btab->magic, btab->nbad,
			divvy_base_b,  BADTRK_ADDR_b);
	}

	if (fatal_bad || (bad_bad && !sco_ignore_bad_bad))
		return 0;

	if (lseek(fd, 512 * (divvy_base_b + DIVVY_ADDR_b), 0) < 0
	 || read(fd, buf, 512) != 512) {
		if (SCO_DEBUG)
			fprintf(stderr, "{bad SCO divvy read @%ld+%db}\n",
				divvy_base_b,  DIVVY_ADDR_b);
		return 0;
	}

	dtab = (struct sco_divvytab *) buf;
	if (dtab->magic != DIVVY_MAGIC) {
		if (SCO_DEBUG)
			fprintf(stderr, "{invalid SCO divvy @%ld+%db (%x)}\n",
				divvy_base_b, DIVVY_ADDR_b, dtab->magic);
		return 0;
	}

	if (SCO_DEBUG)
		fprintf(stderr, "{SCO divvy/badtrk tables @%ld+%db/%ld+%db}\n", 
		    divvy_base_b, DIVVY_ADDR_b,
		    divvy_base_b, BADTRK_ADDR_b);

	if (ioctl(fd, HDIO_GETGEO, &geom) >= 0) {
		sco_divvy_cyl = geom.heads * geom.sectors;
		cyl_how = "HDIO_GETGEO";
	} else {
		sco_divvy_cyl = SCO_DEF_HEADS * SCO_DEF_SECTORS;
		fprintf(stderr, "HDIO_GETGEO failed, assuming heads=%d sectors=%d!\n",
			SCO_DEF_HEADS, SCO_DEF_SECTORS);
		cyl_how = "(precompiled guess)";
	}

	/* i saw this behaviour on a 15 heads, 63 sectors drive
	** don't know whether SCO does this naturally, or it was something funny i did....
	*/
	if (sco_divvy_cyl & 1) {
		fprintf(stderr, "SCO behaviour when heads and sectors are both odd is unknown\n"
			"doubling cylinder offset to realign to 1Kbyte boundary.\n"
			"this *might* work, please mail siwne@iinet.net.au either way...\n");
		sco_divvy_cyls = 2;
	}

	/* The actual partitions begin at a cylinder boundary (usually cyl 1, maybe different?).  */
	subfs_base_b = sco_divvy_cyls * sco_divvy_cyl;

	if (SCO_DEBUG)
		fprintf(stderr, "SCO divvy offset (%s) %d*%d = %ldb\n",
			cyl_how, 
			sco_divvy_cyls, sco_divvy_cyl,
			subfs_base_b);

	for (i=0; i < 8; i++) {
		struct sco_div div = dtab->div[i];
		unsigned long last_sector_k = 
			div.start_sector_k + div.num_sectors_k - 1;
		char buf[8];

		if (div.num_sectors_k < 2)
			continue;

		/* a SCO-ish divvy name */
		printf("SCO %s/%c ", filename, "12345adc"[i]);

		/* note that we ignore (part->start_sect + sect_off),
		** as i think SCO's divvy tables are absolute offsets,
		** not relative to the enclosing partition!
		*/
		printf(fmtstring, "<",
		       ktob(div.start_sector_k) + subfs_base_b,
		       (ktob(div.start_sector_k) + subfs_base_b) * SECTOR_SIZE + offset,
		       1024 * div.num_sectors_k,
		       part->sys_ind, ">");
	}

	return 1;
}

#endif /* CONFIG_SCO_DISKLABEL */

void
print_part(struct partition *part, size_t offset, int sect_off, int ext)
{
  char *indent = ext ? "       [" : "";
  char *exdent = ext ? "]" : "";
  int i;

  if (part->sys_ind)
    printf(fmtstring, indent,
	   part->start_sect + sect_off,
	   (part->start_sect + sect_off) * SECTOR_SIZE + offset,
	   part->nr_sects * SECTOR_SIZE,
	   part->sys_ind, exdent);

  if (part->sys_ind == EXT_MAGIC && !ext) {
    char extblock[512];

    lseek(fd, part->start_sect * SECTOR_SIZE + offset, SEEK_SET);
    if (read(fd, extblock, 512) < 512) {
      perror("hdinfo: Could not read sector");
      exit(1);
    }

    for (i = 0; i < 4; i++) {
      print_part((struct partition *) (extblock + 0x1be + i * 16), offset,
		 part->start_sect, 1);
    }
  }
#ifdef CONFIG_SCO_DISKLABEL
  if (part->sys_ind == SCO_PARTITION)
    sco_divvy(part, offset, sect_off, ext);
#endif
}

void
main(int argc, char **argv)
{
  int i, hdimage_off = 0;
  char mbr[1024];

  fmtstring = decfmt;

  if (argc == 3) {
    if (!strcmp(argv[1], "-h"))
      fmtstring = hexfmt;
    else {
      usage();
      exit(1);
    }
    filename = argv[2];
  }
  else if (argc == 2)
    filename = argv[1];
  else {
    usage();
    exit(1);
  }

  if ((fd = open(filename, O_RDONLY)) < 0) {
    perror("hdinfo: Cannot open file");
    exit(1);
  }

  if (read(fd, mbr, 1024) < 1024) {
    perror("hdinfo: Could not read MBR\n");
    exit(1);
  }

  if (!strncmp(mbr, "DOSEMU", 6))
    hdimage_off = *(u_long *) (mbr + 19);
  else
    hdimage_off = 0;

  printf("Partition info for %s %s\n=================================================\n",
	 filename, hdimage_off ? "(a DOSEMU 0.49 hdimage file)" : "");

  for (i = 0; i < 4; i++)
    print_part((struct partition *) (mbr + 0x1be + hdimage_off + i * 16),
	       hdimage_off, 0, 0);
  exit(0);
}
