/*
 * Copyright (c)2004 The DragonFly Project.  All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 *   Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 *   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.
 * 
 *   Neither the name of the DragonFly Project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``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 THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS 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. 
 */

/*
 * fn_disk.c
 * Disk functions for installer.
 * $Id: fn_disk.c,v 1.2 2004/07/10 06:01:04 cpressey Exp $
 */

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

#include <dfui/dfui.h>
#include <dfui/fspred.h>
#include <dfui/mem.h>

#include "functions.h"
#include "diskutil.h"
#include "uiutil.h"
#include "commands.h"
#include "pathnames.h"
#include "cmds.h"

/*** EXTERNS ***/

extern struct dfui_connection *c;
extern struct system_storage *ss;
extern char dfly_root[];

/*** DISK-RELATED FUNCTIONS ***/

/*
 * Ask the user which physical disk they want.
 */
struct disk *
fn_select_disk(struct system_storage *ss, char *message, char *cancel)
{
	struct dfui_form *f;
	struct dfui_response *r;
	struct disk *d;

	f = dfui_form_create(
		"select_disk",
		"Select Disk",
		message,
		"",

		"h",	"role", 	"menu",
		"h",	"special",	"nympha_select_disk",

		NULL
	);

	for (d = ss->disk_head; d != NULL; d = d->next) {
		dfui_form_action_add(f, dfui_id_new(d->device),
		    dfui_info_new(d->desc, "", ""));
	}

	dfui_form_action_add(f, dfui_id_new("cancel"),
	    dfui_info_new(cancel, "", ""));

	if (!dfui_be_present(c, f, &r)) abort_backend();

	if (dfui_id_is(dfui_response_get_action_id(r), "cancel")) {
		d = NULL;
	} else {
		d = disk_find(ss, dfui_response_get_action_id(r));
		if (d == NULL) {
			inform(	"Internal error - response from frontend "
				"should be a valid device name.");
		}
	}

	dfui_form_free(f);
	dfui_response_free(r);

	return(d);
}

/*
 * Ask the user which slice on a given disk they want.
 */
struct slice *
fn_select_slice(struct disk *d, char *message, char *cancel)
{
	struct dfui_form *f;
	struct dfui_response *r;
	struct slice *s;
	char string[16];

	f = dfui_form_create(
		"select_slice",
		"Select Primary Partition",
		message,
		"",

		"h",	"role", "menu",
		"h",	"special", "nympha_select_slice",

		NULL
	);

	for (s = d->slice_head; s != NULL; s = s->next) {
		snprintf(string, 16, "%d", s->number);
		dfui_form_action_add(f, dfui_id_new(string),
		    dfui_info_new(s->desc, "", ""));
	}

	dfui_form_action_add(f, dfui_id_new("cancel"),
	    dfui_info_new(cancel, "", ""));

	if (!dfui_be_present(c, f, &r)) abort_backend();

	if (dfui_id_is(dfui_response_get_action_id(r), "cancel")) {
		s = NULL;
	} else {
		s = slice_find(d, atoi(dfui_response_get_action_id(r)));
		if (s == NULL) {
			inform(	"Internal error - response from frontend "
				"should be a valid slice number.");
		}
	}

	dfui_form_free(f);
	dfui_response_free(r);

	return(s);
}

/*
 * If ss->selected_disk == NULL, user will be asked for which disk.
 * Returns 1 if disk was formatted, 0 if it wasn't.
 * If it was, ss->selected_disk and ss->selected_slice are set to it.
 */
int
fn_format_disk(void)
{
	struct commands *cmds;
	char *selected_disk_string;

	if (ss->selected_disk == NULL) {
		 ss->selected_disk = fn_select_disk(ss,
		    "Select a disk to format.",
		    "Return to Utilities Menu");
	}
	if (ss->selected_disk == NULL) {
		return(0);
	}

	if (confirm_dangerous_action(
	    "WARNING!  ALL data in ALL partitions on the disk\n\n"
	    "%s\n\nwill be IRREVOCABLY ERASED!\n\nAre you ABSOLUTELY "
	    "SURE you wish to take this action?  This is your "
	    "LAST CHANCE to cancel!", ss->selected_disk->desc)) {
		cmds = commands_new();
			
		/*
		 * Create the device node if it does not exist.
		 */
		command_add_function(cmds, cmd_maybe_makedev,
		    (void *)dfui_strdup(ss->selected_disk->device),
		    "maybe_makedev()");

		command_add(cmds, "fdisk -I %s", ss->selected_disk->device);
		command_add(cmds, "yes | fdisk -B %s", ss->selected_disk->device);

		if (!commands_execute(c, cmds)) {
			inform(	"The disk\n\n%s\n\nwas "
				"not correctly formatted, and may "
				"now be in an inconsistent state. "
				"We recommend re-formatting it "
				"before attempting to install "
				"DragonFly BSD on it.",
				ss->selected_disk->desc);
			commands_free(cmds);
			return(0);
		}
		commands_free(cmds);

		/*
		 * Since one of the disks has now changed, we must
		 * refresh our view of them and re-select the disk
		 * since the selected_disk pointer will be invalidated.
		 */
		selected_disk_string = dfui_strdup(ss->selected_disk->device);
		discover_storage(ss);
		ss->selected_disk = disk_find(ss, selected_disk_string);
		free(selected_disk_string);

		/*
		 * Note that we formatted this disk and that we want
		 * to use the first (and only) slice of it.
		 */
		ss->selected_disk->we_formatted = 1;
		ss->selected_slice = ss->selected_disk->slice_head;

		if (!format_slice(ss->selected_disk->device,
		    ss->selected_slice->number)) {
			inform(	"The sole primary partition of "
				"the disk\n\n%s\n\nwas "
				"not correctly formatted, and may "
				"now be in an inconsistent state. "
				"We recommend re-formatting the "
				"disk before attempting to install "
				"DragonFly BSD on it.",
				ss->selected_disk->desc);
			return(0);
		}

		inform("The disk\n\n%s\n\nwas formatted.", ss->selected_disk->desc);
		return(1);
	} else {
		inform("Action cancelled - no disks were formatted.");
		return(0);
	}
}

void
fn_wipe_start_of_disk(void)
{
	struct commands *cmds;
	struct disk *d;

	d = fn_select_disk(ss,
	    "If you are having problems formatting a disk, "
	    "it may be because of junk that has accumulated "
	    "in the boot block and the partition table. "
	    "A cure for this is to wipe out everything on "
	    "the first few sectors of the disk.  However, this "
	    "is a rather drastic action to take, so it is not "
	    "recommended unless you are otherwise "
	    "encountering problems.", "Return to Utilities Menu");

	/* XXX check to make sure no slices on this disk are mounted first? */
	if (d != NULL && confirm_dangerous_action(
	    "WARNING!  ALL data in ALL partitions on the disk\n\n"
	    "%s\n\nwill be IRREVOCABLY ERASED!\n\nAre you ABSOLUTELY "
	    "SURE you wish to take this action?  This is your "
	    "LAST CHANCE to cancel!", d->desc)) {
		cmds = commands_new();
		command_add(cmds,
		    "dd if=%sdev/zero of=%sdev/%s bs=32k count=16",
		    dfly_root, dfly_root,
		    d->device);
		if (commands_execute(c, cmds)) {
			inform( "Start of disk was successfully wiped.");
		} else {
			inform( "Some errors occurred. "
				"Start of disk was not successfully wiped.");
		}
		commands_free(cmds);
	}
}

void
fn_wipe_start_of_slice(void)
{
	struct commands *cmds;
	struct disk *d;
	struct slice *s;

	d = fn_select_disk(ss,
	    "If you are having problems formatting a primary partition, "
	    "it may be because of junk that has accumulated in the "
	    "partition's `disklabel'. A cure for this is to wipe out "
	    "everything on the first few sectors of the primary partition. "
	    "However, this is a rather drastic action to take, so it is not "
	    "recommended unless you are otherwise encountering problems.",
	    "Return to Utilities Menu");
	if (d == NULL) {
		return;
	}
	s = fn_select_slice(d,
	  "Select a primary partition to wipe.",
	  "Return to Utilities Menu");
	if (s == NULL) {
		return;
	}
	if (confirm_dangerous_action(
	    "WARNING!  ALL data in primary partition #%d,\n\n%s\n\non the "
	    "disk\n\n%s\n\n will be IRREVOCABLY ERASED!\n\nAre you "
	    "ABSOLUTELY SURE you wish to take this action?  This is "
	    "your LAST CHANCE to cancel!",
	    s->number, s->desc, d->desc)) {
		/* XXX check to make sure this slice is not mounted first */
		cmds = commands_new();
		command_add(cmds, "dd if=%sdev/zero of=%sdev/%ss%d bs=32k count=16",
		    dfly_root, dfly_root, d->device, s->number);
		if (commands_execute(c, cmds)) {
			inform( "Start of primary partition was successfully wiped.");
		} else {
			inform( "Some errors occurred. "
				"Start of primary partition was not successfully wiped.");
		}
		commands_free(cmds);
	}
}

int
fn_install_bootblocks(char *desc, char *cancel)
{
	struct dfui_form *f;
	struct dfui_response *r;
	struct dfui_dataset *ds;
	struct dfui_celldata *cd;
	struct disk *d;
	struct commands *cmds;
	char disk[64], boot0cfg[32], packet[32];

	f = dfui_form_create(
		"install_bootstrap",
		"Install Bootblock(s)",
		desc,

		"'Packet Mode' refers to using newer BIOS calls to boot "
		"from a partition of the disk.  It is generally not "
		"required unless:\n\n"
		"- your BIOS does not support legacy mode; or\n"
		"- your DragonFly primary partition resides on a "
		  "cylinder of the disk beyond cylinder 1024; or\n"
		"- you just can't get it to boot without it.",

		"h",	"special", "nympha_install_bootstrap",

		"f",	"disk",		"Disk",
			"The disk on which you wish to install a bootblock", "",
			"h",	"editable", "false",
		"f",	"boot0cfg",	"Install Bootblock?",
			"Install a bootblock on this disk", "",
			"h",	"control", "checkbox",
		"f",	"packet",	"Packet Mode?",
			"Select this to use 'packet mode' to boot the disk", "",
			"h",	"control", "checkbox",
	
		"a",	"ok", 		"Accept and Install Bootblocks", "", "",
		"a",	"cancel", 	cancel, "", "", 

		NULL
	);

	dfui_form_set_multiple(f, 1);

	for (d = ss->disk_head; d != NULL; d = d->next) {
		ds = dfui_dataset_new();
		dfui_dataset_celldata_add(ds, dfui_id_new("disk"), d->device);
		dfui_dataset_celldata_add(ds, dfui_id_new("boot0cfg"), "Y");
		dfui_dataset_celldata_add(ds, dfui_id_new("packet"), "N");
		dfui_form_dataset_add(f, ds);
	}

	if (!dfui_be_present(c, f, &r)) abort_backend();

	if (dfui_id_is(dfui_response_get_action_id(r), "ok")) {
		cmds = commands_new();

		for (ds = dfui_response_dataset_get_first(r); ds != NULL;
		     ds = dfui_dataset_get_next(ds)) {
			strcpy(disk, "");
			strcpy(boot0cfg, "N");
			strcpy(packet, "N");
			if ((cd = dfui_dataset_celldata_find(ds, dfui_id_new("disk"))) != NULL)
				strlcpy(disk, dfui_celldata_get_value(cd), 64);
			if ((cd = dfui_dataset_celldata_find(ds, dfui_id_new("boot0cfg"))) != NULL)
				strlcpy(boot0cfg, dfui_celldata_get_value(cd), 32);
			if ((cd = dfui_dataset_celldata_find(ds, dfui_id_new("packet"))) != NULL)
				strlcpy(packet, dfui_celldata_get_value(cd), 32);

			if (strcasecmp(boot0cfg, "Y") == 0) {
				command_add(cmds, "boot0cfg -B %s %s",
				    strcasecmp(packet, "Y") == 0 ? "-o packet" : "",
				    disk);
				command_add(cmds, "boot0cfg -v %s", disk);
			}
		}

		if (!commands_execute(c, cmds)) {
			inform( "Bootblocks were not successfully installed.");
		} else {
			inform( "Bootblocks were successfully installed!");

			commands_free(cmds);
			dfui_form_free(f);
			dfui_response_free(r);
			return(1);
		}
		commands_free(cmds);
	}

	dfui_form_free(f);
	dfui_response_free(r);
	return(0);
}

void
fn_format_msdos_floppy(void)
{
	struct commands *cmds;

	switch (dfui_be_present_dialog(c, "Format MSDOS Floppy",
		"Format Floppy|Return to Utilities Menu",
		"Please insert the floppy to be formatted "
		"in unit 0 (``drive A:'').")) {
	case 1:
		cmds = commands_new();
		command_add(cmds, "fdformat -y -f 1440 /dev/fd0");
		command_add(cmds, "newfs_msdos -f 1440 fd0");
		if (commands_execute(c, cmds)) {
			inform("Floppy successfully formatted!");
		} else {
			inform("Floppy was not successfully formatted.");
		}
		break;
	case 2:
		return;
	default:
		abort_backend();
	}
}

void
fn_create_cdboot_floppy(void)
{
	struct commands *cmds;

	switch (dfui_be_present_dialog(c, "Create CDBoot Floppy",
		"Create CDBoot Floppy|Return to Utilities Menu",
		"DragonFly BSD cannot be installed from a floppy; "
		"it must be installed from a booted CD-ROM. "
		"However, many older systems do not support booting "
		"from a CD-ROM. For these systems, a boot disk can be "
		"created. This boot disk contains the Smart Boot "
		"Manager program, which can boot a CD-ROM even "
		"on systems with BIOSes which do not support booting "
		"from the CD-ROM.\n\n"
		"Smart Boot Manager is not a part of DragonFly BSD; "
		"the Smart Boot Manager project can be found here:\n\n"
		"http://btmgr.sourceforge.net/\n\n"
		"To create a CDBoot floppy, insert a blank floppy "
		"in unit 0 (``drive A:'') before proceeding."
		"")) {
	case 1:
		cmds = commands_new();
		command_add(cmds, "bunzip2 -c %sboot/cdboot.flp.bz2 | "
				  "dd of=/dev/fd0 bs=32k", dfly_root);
		if (commands_execute(c, cmds)) {
			inform("CDBoot floppy successfully created!");
		} else {
			inform("CDBoot floppy was not successfully created.");
		}
		break;
	case 2:
		return;
	default:
		abort_backend();
	}
}

void
fn_create_memtest86_floppy(void)
{
	struct commands *cmds;

	switch (dfui_be_present_dialog(c, "Create memtest86 Floppy",
		"Create memtest86 Floppy|Return to Utilities Menu",
		"While this installer allows you to test memory "
		"on-line, the fact that the installer and operating "
		"system are already loaded means that the memory "
		"test has certain limits.  For a more thorough "
		"memory test, you can create a floppy containing "
		"the memtest86 program, which boots up independently "
		"of any operating system, allowing it access to "
		"almost the entire memory of the computer for testing.\n\n"
		"memtest86 is not a part of DragonFly BSD; "
		"the memtest86 project can be found here:\n\n"
		"http://www.memtest86.com/\n\n"
		"To create a memtest86 floppy, insert a blank floppy "
		"in unit 0 (``drive A:'') before proceeding."
		"")) {
	case 1:
		cmds = commands_new();
		command_add(cmds, "bunzip2 -c %sboot/memtest86.flp.bz2 | "
				  "dd of=/dev/fd0 bs=32k", dfly_root);
		if (commands_execute(c, cmds)) {
			inform("memtest86 floppy successfully created!");
		} else {
			inform("memtest86 floppy was not successfully created.");
		}
		break;
	case 2:
		return;
	default:
		abort_backend();
	}
}

/**** NON-fn_ FUNCTIONS ***/

int
format_slice(char *device, int slice)
{
	char *dev_name;
	struct commands *cmds;
	int result;

	cmds = commands_new();

	/*
	 * Create the device node if it does not exist.
	 */
	asprintf(&dev_name, "%ss%d", device, slice);
	command_add_function(cmds, cmd_maybe_makedev,
	    (void *)dev_name, "maybe_makedev()");

	command_add(cmds, "fdisk -s -%d %s >" INSTALLER_TEMP "slice.fdisk",
	    ss->selected_slice->number, ss->selected_disk->device);

	/*
	 * Set the slice's sysid to 165.
	 */
	command_add_function(cmds, cmd_set_slice_sysid_dfly,
	    (void *)ss, "set_slice_sysid_dfly()");

	/*
	 * Execute the fdisk script.
	 */
	command_add(cmds, "fdisk -v -f " INSTALLER_TEMP "new.fdisk %s",
	    ss->selected_disk->device);

	command_add(cmds, "rm -f " INSTALLER_TEMP "slice.fdisk");
	command_add(cmds, "rm -f " INSTALLER_TEMP "new.fdisk");

	/*
	 * Auto-disklabel the slice.
	 * NB: one cannot use "/dev/adXsY" here -
	 * it must be in the form "adXsY".
	 */
	command_add(cmds, "disklabel -B -r -w %ss%d auto",
	    device, slice);

	/*
	 * If there is an old 'virgin' disklabel hanging around
	 * in the temp dir, get rid of it.  This won't happen
	 * from a real CD, but might happen with '-o' installs.
	 */
	command_add(cmds, "rm -f %sinstall.disklabel.%ss%d",
	    INSTALLER_TEMP, device, slice);

	result = commands_execute(c, cmds);

	commands_free(cmds);

	return(result);
}
