/*
 * 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. 
 */

/*
 * diskutil.c
 * Disk utility functions for installer.
 * Hacks for now, until libdiskng is ready.
 * $Id: diskutil.c,v 1.4 2004/07/11 02:33:32 cpressey Exp $
 */

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

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

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

extern struct dfui_connection *c;

/** STORAGE DESCRIPTORS **/

struct system_storage *
system_storage_new(void)
{
	struct system_storage *s;

	DFUI_MALLOC(s, system_storage);

	s->disk_head = NULL;
	s->disk_tail = NULL;
	s->selected_disk = NULL;
	s->selected_slice = NULL;
	s->ram = -1;

	return(s);
}

void
system_storage_free(struct system_storage *s)
{
	disks_free(s);
	DFUI_FREE(s, system_storage);
}

/*
 * Create a new disk description structure.
 * desc is a string obtained from dmesg, and should be in the form:
 * "ad0: 9773MB <FUJITSU MPE3102AT> [19857/16/63] at ata0-master UDMA33"
 */
struct disk *
disk_new(struct system_storage *s, char *desc)
{
	struct disk *d;
	char *c;

	DFUI_MALLOC(d, disk);

	d->number = desc[2] - '0';	/* XXX */
	d->device = NULL;

	switch (desc[0]) {
	case 'a':
		if (desc[1] == 'd') {
			/* standard ide drive */
			asprintf(&d->device, "ad%c", desc[2]);
		} else if (desc[1] == 'm') {
			/* LSILogic MegaRAID */
			asprintf(&d->device, "amrd%c", desc[4]);
		}
		break;
	case 'd':
		/* scsi device */
		asprintf(&d->device, "da%c", desc[2]);
		break;
	case 't':
		/* 3ware device */
		asprintf(&d->device, "twed%c", desc[4]);
		break;
	}

	if (d->device == NULL) {
		/* Invalid disk type */
		DFUI_FREE(d, disk);
		return(NULL);
	}
		
	if (disk_find(s, d->device) != NULL) {
		/* Already discovered */
		free(d->device);
		DFUI_FREE(d, disk);
		return(NULL);
	}

	d->desc = dfui_strdup(desc);

	/*
	 * Get the disk's total capacity.
	 */
	c = d->desc;
	while (*c != ':' && *c != '\0')
		c++;
	if (*c == '\0') {
		d->capacity = 0;
	} else {
		d->capacity = atoi(c + 1);
	}

	d->we_formatted = 0;

	d->slice_head = NULL;
	d->slice_tail = NULL;

	d->next = NULL;
	if (s->disk_head == NULL) {
		s->disk_head = d;
	} else {
		s->disk_tail->next = d;
	}
	d->prev = s->disk_tail;
	s->disk_tail = d;

	return(d);
}

/*
 * Find a disk description structure from the device name.
 */
struct disk *
disk_find(struct system_storage *s, char *device)
{
	struct disk *d = s->disk_head;

	while (d != NULL) {
		if (!strcmp(device, d->device))
			return(d);
		d = d->next;
	}
	
	return(NULL);
}

/*
 * Free the memory allocated to hold the set of disk descriptions.
 */
void
disks_free(struct system_storage *s)
{
	struct disk *d = s->disk_head, *next;

	while (d != NULL) {
		next = d->next;
		slices_free(d->slice_head);
		free(d->desc);
		free(d->device);
		DFUI_FREE(d, disk);
		d = next;
	}

	s->disk_head = NULL;
	s->disk_tail = NULL;
}

/*
 * Look through the dmesg log created at boot-time to find out
 * what IDE and SCSI disks are attached to this computer.
 */
void
discover_disks(struct system_storage *s)
{
	FILE *p;
	char *command;
	char line[256];
	struct disk *d;
	struct commands *cmds;

	disks_free(s);

	command = "egrep '^(ad|da|twed|amrd).+: \\<.*\\>' " DMESG_BOOT;
	if ((p = popen(command, "r")) == NULL) {
		fprintf(stderr, "+ Could not execute ``%s''\n", command);
		return;
	}

	while (fgets(line, 255, p) != NULL) {
		line[strlen(line) - 1] = '\0';
		if ((d = disk_new(s, line)) != NULL) {
			cmds = commands_new();

			/*
			 * Create device entry for the slice
			 * before trying to use fdisk on it.
			 */
			command_add_function(cmds, cmd_maybe_makedev,
			    (void *)dfui_strdup(d->device),
			    "maybe_makedev()");

			/*
			 * Get a summary of the slices on this disk.
			 */
			command_add(cmds, "fdisk -s %s >%stemp.fdisk || "
			    "echo '' >%stemp.fdisk",
			    d->device, INSTALLER_TEMP, INSTALLER_TEMP);

			command_add_function(cmds, cmd_discover_slices,
			    (void *)d, "discover_slices()");

			command_add(cmds, "rm -f %stemp.fdisk",
			    INSTALLER_TEMP);

			if (!commands_execute(c, cmds)) {
				inform(	"Errors occurred while probing disks "
					"for partition information.");
			}

			commands_free(cmds);
		}
	}
	pclose(p);
}

void
discover_storage(struct system_storage *ss)
{
	measure_physical_memory(ss);
	discover_disks(ss);
}

/*
 * Create a new slice description and add it to a disk description.
 */
struct slice *
slice_new(struct disk *d, int number, int type, long start, long size)
{
	struct slice *s;
	int i;
	char *sysid_desc = NULL;
	char unknown[256];

	dfui_debug("** adding slice %d (start %ld, size %ld, sysid %d)"
	    "to disk %s\n", number, start, size, type, d->device);

	DFUI_MALLOC(s, slice);
	
	s->subpartition_head = NULL;
	s->subpartition_tail = NULL;
	s->number = number;

	for (i = 0; ; i++) {
		if (part_types[i].type == type) {
			sysid_desc = part_types[i].name;
			break;
		}
		if (part_types[i].type == 255)
			break;
	}
	if (sysid_desc == NULL) {
		snprintf(unknown, 256, "??? Unknown, sysid = %d", type);
		sysid_desc = unknown;
	}

	asprintf(&s->desc, "%ldM - %ldM: %s",
	    start / 2048, (start + size) / 2048, sysid_desc);
	s->capacity = size / 2048;

	s->next = NULL;
	if (d->slice_head == NULL) {
		d->slice_head = s;
	} else {
		d->slice_tail->next = s;
	}
	s->prev = d->slice_tail;
	d->slice_tail = s;

	return(s);
}

/*
 * Find a slice description on a given disk description given the
 * slice number.
 */
struct slice *
slice_find(struct disk *d, int number)
{
	struct slice *s = d->slice_head;

	while (s != NULL) {
		if (s->number == number)
			return(s);
		s = s->next;
	}
	
	return(NULL);
}

/*
 * Free all memory for a list of slice descriptions.
 */
void
slices_free(struct slice *head)
{
	struct slice *next;

	while (head != NULL) {
		next = head->next;
		subpartitions_free(head);
		free(head->desc);
		DFUI_FREE(head, slice);
		head = next;
	}
}

/*
 * NOTE: arguments to this function are not checked for sanity.
 *
 * fsize and/or bsize may both be -1, indicating
 * "choose a reasonable default."
 */
struct subpartition *
subpartition_new(struct slice *s, char *mountpoint, long capacity,
		 int softupdates, long fsize, long bsize)
{
	struct subpartition *sp;

	DFUI_MALLOC(sp, subpartition);

	if (s->subpartition_tail == NULL)
		sp->letter = 'a';
	else if (s->subpartition_tail->letter == 'b')
		sp->letter = 'd';
	else
		sp->letter = (char)(s->subpartition_tail->letter + 1);

	sp->mountpoint = dfui_strdup(mountpoint);
	sp->capacity = capacity;

	if (fsize == -1) {
		if (sp->capacity < 1024) {
			sp->fsize = 1024;
		} else {
			sp->fsize = 4096;
		}
	} else {
		sp->fsize = fsize;
	}

	if (bsize == -1) {
		if (sp->capacity < 1024) {
			sp->bsize = 8192;
		} else {
			sp->bsize = 16384;
		}
	} else {
		sp->bsize = bsize;
	}

	if (softupdates == -1) {
		if (strcmp(mountpoint, "/") == 0) {
			sp->softupdates = 0;
		} else {
			sp->softupdates = 1;
		}
	} else {
		sp->softupdates = softupdates;
	}

	sp->is_swap = 0;
	if (strcasecmp(mountpoint, "swap") == 0)
		sp->is_swap = 1;

	sp->next = NULL;
	if (s->subpartition_head == NULL) {
		s->subpartition_head = sp;
	} else {
		s->subpartition_tail->next = sp;
	}
	sp->prev = s->subpartition_tail;
	s->subpartition_tail = sp;

	return(sp);
}

struct subpartition *
subpartition_find(struct slice *s, char *fmt, ...)
{
	struct subpartition *sp = s->subpartition_head;
	char *mountpoint;
	va_list args;

	va_start(args, fmt);
	vasprintf(&mountpoint, fmt, args);
	va_end(args);

	while (sp != NULL) {
		if (!strcmp(mountpoint, sp->mountpoint)) {
			free(mountpoint);
			return(sp);
		}
		sp = sp->next;
	}

	free(mountpoint);
	return(NULL);
}

struct subpartition *
subpartition_find_capacity(struct slice *s, long capacity)
{
	struct subpartition *sp = s->subpartition_head;

	while (sp != NULL) {
		if (sp->capacity == capacity)
			return(sp);
		sp = sp->next;
	}

	return(NULL);
}

int
subpartition_count(struct slice *s)
{
	struct subpartition *sp = s->subpartition_head;
	int count = 0;

	while (sp != NULL) {
		count++;
		sp = sp->next;
	}

	return(count);
}

void
subpartitions_free(struct slice *s)
{
	struct subpartition *sp = s->subpartition_head, *next;

	while (sp != NULL) {
		next = sp->next;
		free(sp->mountpoint);
		DFUI_FREE(sp, subpartition);
		sp = next;
	}

	s->subpartition_head = NULL;
	s->subpartition_tail = NULL;
}

/*
 * Look through the dmesg log created at boot-time to find out
 * how much physical memory this machine has.  Return it in MB.
 */
long
measure_physical_memory(struct system_storage *ss)
{
	FILE *p;
	char line[256];
	char *mems;

	p = popen("egrep '^real memory.*=' " DMESG_BOOT, "r");
	while (fgets(line, 255, p) != NULL) {
		for (mems = line; *mems != '=' && *mems != '\0'; mems++)
			;
		if (*mems == '=') {
			ss->ram = next_power_of_two(atol(++mems)) /
			    (1024 * 1024);
		}
	}
	pclose(p);

	return(ss->ram);
}

long
measure_activated_swap(void)
{
	FILE *p;
	char line[256];
	char *word;
	long swap = 0;

	if ((p = popen("swapinfo -k", "r")) == NULL)
		return(0);
	while (fgets(line, 255, p) != NULL) {
		if ((word = strtok(line, " \t")) == NULL)
			continue;
		if (strcmp(word, "Device") == 0)
			continue;
		if ((word = strtok(NULL, " \t")) == NULL)
			continue;
		swap += atol(word);
	}
	pclose(p);

	return(swap / 1024);
}

long
measure_activated_swap_from_slice(struct disk *d, struct slice *s)
{
	FILE *p;
	char *dev, *word;
	char line[256];
	long swap = 0;

	if ((p = popen("swapinfo -k", "r")) == NULL)
		return(0);

	asprintf(&dev, "/dev/%ss%d", d->device, s->number);

	while (fgets(line, 255, p) != NULL) {
		if ((word = strtok(line, " \t")) == NULL)
			continue;
		if (strcmp(word, "Device") == 0)
			continue;
		if (strstr(word, dev) != word)
			continue;
		if ((word = strtok(NULL, " \t")) == NULL)
			continue;
		swap += atol(word);
	}
	pclose(p);
	free(dev);

	return(swap / 1024);
}

long
measure_activated_swap_from_disk(struct disk *d)
{
	struct slice *s;
	long swap = 0;

	for (s = d->slice_head; s != NULL; s = s->next) {
		swap += measure_activated_swap_from_slice(d, s);
	}

	return(swap);
}
