/*
 *   Copyright (c) 1996-1999 David Parsons. 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 David Parsons
 *   (orc@pell.chi.il.us)
 *
 *  4. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``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 DAVID
 *  PARSONS 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.
 */

static const char rcsid[] = "$Id: s.checkdisk.c 1.12 02/06/12 00:44:58-07:00 root@egypt.pell.portland.or.us $";

/*
 * checkdisk: check and partition disks
 *
 *	checkdisk0()	-- checks the first disk on the system for size,
 *			   dos partition.
 *	dopartition()	-- automatically partition the first disk.
 *
 *	hasdosC		-- does the first disk have a dos partition?
 *	hasroomforLinux	-- does the first disk have room to fit
 *			   a full Mastodon installation on it?
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/param.h>
#include <string.h>
#include <ctype.h>

#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>

#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/fs.h>
#include <sys/ioctl.h>

#include <dialog.h>

#include "partition.h"
#include "util.h"


int hasdosC = 0;
int hasroomforLinux = 0;

static int heads, sectors, cylinders;	/* disk geometry */
static char block0[BYTES_PER_SECTOR];

#define BIOS_MAX_CYL	1023	/* a PC bios can only address this many
				 * cylinders on a disk */

static struct partition *partitions[4] = {  (void*)partition_offset(block0, 0),
					    (void*)partition_offset(block0, 1),
					    (void*)partition_offset(block0, 2),
					    (void*)partition_offset(block0, 3)};

#define Sector(s)       ((s) & 0x3f)
#define Cylinder(s, c)  ((c) | (((s) & 0xc0) << 2))
#define PARSIZE(x)	((x->end_sector - x->start_sect)/2)

#define set_chs(c,h,s,sector) { \
		    int SECTOR = (sector);		\
		    s = SECTOR % sectors + 1;		\
		    SECTOR /= sectors;			\
		    h = SECTOR % heads;			\
		    SECTOR /= heads;			\
		    c = SECTOR & 0xff;			\
		    s |= (SECTOR >> 2) & 0xc0;		\
				}

static int showchs(char *title);


/*
 * readmbr() -- reads the boot sector off the given disk
 */
static int
readmbr(char *device)
{
    int disk = open(device, O_RDWR);
    struct hd_geometry chs;

    if (disk < 0)
	return EOF;
    if (read(disk, block0, BYTES_PER_SECTOR) != BYTES_PER_SECTOR) {
	close(disk);
	return EOF;
    }
    if (ioctl(disk, HDIO_GETGEO, &chs) == 0) {
	heads     = chs.heads;
	sectors   = chs.sectors;
	cylinders = chs.cylinders;
    }
    else {
	close(disk);
	return EOF;
    }
    return disk;
} /* readmbr */


/*
 * default_filesystem() builds a default root filesystem and swap
 * partition on the given disk. (128mb is swap, remainder is / [and
 * /boot, if the disk has more than 1024 cylinders.])
 *
 * Before building the partition table, check to see that this disk has
 * at least 500mb available (128 for swap, 372 for /); if not, return EOF.
 */
static int
default_filesystem(char *device, int disk, int bootdisk, int withdosC)
{
#define ROOTDISKSIZE	(500*ONE_MB/BYTES_PER_SECTOR)
#define SWAPPARTSIZE	(SWAPMEGS*ONE_MB/BYTES_PER_SECTOR)
#define BOOTPARTSIZE	(40*ONE_MB/BYTES_PER_SECTOR)

    unsigned int totalsize = sectors * heads * cylinders;
    unsigned int SectorsPerCyl =  heads * sectors;
    unsigned int swapcylinders;
    unsigned int bootcylinders;

    int start;				/* starting block of partition */
    int part;				/* partition index */
    int sects;

    char par_dev[80];
    char *mountpt;
    enum partype partmap[3];
    int nrpart = 0;
    int rootpt;
    int hasbootpt = 0;

    if (bootdisk && (totalsize < ROOTDISKSIZE))
	return EP_SMALL;

    if (bootdisk && (cylinders > BIOS_MAX_CYL)) {
	/* we might have to do some ornate gymnastics to fit ourself
	 * onto this disk; the bios can only address 1023 cylinders,
	 * so we need to make sure that /boot lives between 0 and 1023.
	 */
	partmap[withdosC+0] = I_BOOT;
	partmap[withdosC+1] = I_SWAP;
	partmap[withdosC+2] = I_ROOT;
	nrpart = withdosC+3;
	rootpt = withdosC+2;
	hasbootpt = 1;
    }
    else {
	partmap[withdosC+0] = I_SWAP;
	partmap[withdosC+1] = I_ROOT;
	nrpart = withdosC+2;
	rootpt = withdosC+1;
    }


    if (withdosC) {
	snprintf(par_dev, sizeof par_dev, "%s1", device);
	addDevToList(par_dev, "/C", I_DOS, 0, 0);
	start = partitions[0]->start_sect + partitions[0]->nr_sects;
    }
    else {
	/* Zero the partition table and validate it */
	memset(block0+PARTITION_OFFSET, 0, BYTES_PER_SECTOR-PARTITION_OFFSET);
	make_partition_ok(block0);

	start = sectors;
    }

    swapcylinders = (SWAPPARTSIZE+SectorsPerCyl-1)/SectorsPerCyl;
    bootcylinders = (BOOTPARTSIZE+SectorsPerCyl-1)/SectorsPerCyl;

    for (part=withdosC+0; part<nrpart; part++) {
	partitions[part]->boot_ind = 0;
	partitions[part]->sys_ind = (partmap[part] == I_SWAP)
					? PARTYPE_SWAP
					: PARTYPE_LINUX;
	partitions[part]->start_sect = start;

	switch (partmap[part]) {
	case I_SWAP:
	    sects = (sectors*heads*swapcylinders)-(start % (sectors*heads));
	    mountpt = "swap";
	    break;
	case I_BOOT:
	    sects = (sectors*heads*bootcylinders)-(start % (sectors*heads));
	    mountpt = "/boot";
	    if (!withdosC)
		partitions[part]->boot_ind = 0x80;
	    break;
	default:
	    sects = (sectors*heads*cylinders)-start;
	    mountpt = "/";
	    break;
	}
	if (device) {
	    snprintf(par_dev, sizeof par_dev, "%s%d", device, part+1);
	    addDevToList(par_dev, mountpt, partmap[part], 1, 0);
	}

	partitions[part]->nr_sects = sects;

	set_chs(partitions[part]->cyl,
		partitions[part]->head,
		partitions[part]->sector,
		start);

	set_chs(partitions[part]->end_cyl,
		partitions[part]->end_head,
		partitions[part]->end_sector,
		partitions[part]->start_sect+partitions[part]->nr_sects-1);

	start += sects;
    }
    if ( !(hasbootpt || withdosC) )
	partitions[rootpt]->boot_ind = 0x80;
    return EP_OK;
} /* default_filesystem */


/*
 * write_partition_table() writes the partition table to the disk
 */
static int
write_partition_table(int disk)
{
    showchs("Partition table");
    if (getpid() == 1) {
	dialog_msgbox(INSTALLER, "writing partition table", -1, 50, 0);
	lseek(disk, 0L, 0);
	if (write(disk, block0, BYTES_PER_SECTOR) != BYTES_PER_SECTOR)
	    return EP_WRITE;
	sync();
	close(dup(disk));
	fsync(disk);
	sleep(2);
	ioctl(disk, BLKRRPART);
    }
    else {
	dialog_msgbox(INSTALLER, "not about to write the partition table", -1, 50, 0);
	sleep(2);
    }
    close(disk);
    return EP_OK;
} /* write_partition_table */


/*
 * showchs() DEBUG shows the partition table
 */
static int
showchs(char *title)
{
#if DEBUG
    int x;
    char msg[800];

    snprintf(msg, sizeof msg,
	   "  Heads:%d Sectors:%d, Cylinders:%d\n"
	   "  sys     boot    start   nr      C     H   S\n",
	   heads, sectors, cylinders);
    for (x=0; x<4; x++) {
	if (partitions[x]->nr_sects == 0)
	    continue;
	snprintf(msg+strlen(msg), sizeof msg - strlen(msg),
		"%d:%02x      %02x      %-8d%-8d%-6d%-4d%-4d\n"
		"%d:%-32s%-6d%-4d%-4d\n",
	     x,
	     partitions[x]->sys_ind,
	     partitions[x]->boot_ind,
	     partitions[x]->start_sect,
	     partitions[x]->nr_sects,
	     Cylinder(partitions[x]->sector, partitions[x]->cyl),
	     partitions[x]->head,
	     Sector(partitions[x]->sector),
	     x,
	     "",
	     Cylinder(partitions[x]->end_sector, partitions[x]->end_cyl),
	     partitions[x]->end_head,
	     Sector(partitions[x]->end_sector));
    }
    dialog_msgbox("DEBUG", msg, -1, -1, 1);
#endif
    return 0;
} /* showchs */


/*
 * dopartition() partitions a disk with the root or user default filesystems.
 *
 * parameters:
 *	device		-- the disk we're partitioning.
 *	isroot		-- partition this as a root partition.
 *	withdosC	-- partition around an existing DOS partition.
 *
 * returns:
 *	1		-- everything worked and we can continue.
 *	0		-- oops, but it's a nonfatal oops. (If it's
 *			   a fatal oops, the function returns by shutting
 *			   off the machine.)
 */
int
dopartition(char *device, int isroot, int withdosC)
{
    int disk;
    int rc;

    if ((disk = readmbr(device)) < 0)
	rc = EP_READ;
    else if ((rc = default_filesystem(device, disk, isroot, withdosC)) == EP_OK)
	    rc = write_partition_table(disk);

    switch (rc) {
    case EP_READ:
	    die("When reading partition table: %s", strerror(errno));
	    break;
    case EP_WRITE:
	    die("While writing partition table: %s", strerror(errno));
	    break;
    case EP_SMALL:
	    Error("root disk is too small to fit the system onto!\n"
		  "I require at least %s on the root disk",
		  Size(ROOTDISKSIZE / ONE_K));
	    break;
    default:
	    return 1;
    }
    return 0;
} /* dopartition */


/*
 * checkdisk0() checks out the first disk to see if it's already got
 * a dos partition as partition 1, and, if it does, if there is also
 * enough room to install Linux on the remaining disk.
 *
 * parameters:
 *	diskC -- the Disk[] index for the first disk
 *
 * returns:
 *	0	-- no dos partition.
 *	1	-- dos partition, and enough room to fit Mastodon.
 *	2	-- dos partiton, but not enough room to fit Mastodon.
 */
int
checkdisk0(int diskC)
{
    unsigned long sectors_left;
    int disk;
    int typ;
    int rc = 0;
    int hasdosC = 0;

    if ((disk = readmbr(Disk[diskC].device)) < 0)
	return 0;

    /* Only check partitions if the magic is ok
     * and we can find some linux partitions that are > 0 Mb
     */
    if (partition_is_ok(block0)) {
	/* MS-DOS partitions can, apparently, have the high bit set without
	 * any rhyme or reason.  So we only care about the low 7 bits of
	 * the partition type.
	 */
	typ = 0x7f & partitions[0]->sys_ind;
	if (typ == PARTYPE_FAT12 || typ == PARTYPE_FAT16 || typ == PARTYPE_BIGFAT) {
	    /* we have a dos partition. */
	    hasdosC = 1;

	    /* now check to see if we have enough space for Linux */
	    sectors_left = (sectors*heads*cylinders)
		     - (partitions[0]->start_sect + partitions[0]->nr_sects);
	    if (sectors_left > ROOTDISKSIZE)
		rc = 1;
	}
    }
    close(disk);
    return hasdosC + rc;
} /* checkdisk0 */
