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

/*
 * cmds.c
 * Native C functions used in commands_execute(), and ``generator''
 * functions which create command chains for later execution.
 * $Id: cmds.c,v 1.3 2004/07/11 17:14:44 adonijah Exp $
 */

#include <sys/stat.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>

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

#include "dfui/fspred.h"
#include "dfui/mem.h"
#include "commands.h"
#include "diskutil.h"
#include "functions.h"
#include "pathnames.h"

extern char dfly_root[];

void
generate_disklabel_line(FILE *log, FILE *out, struct subpartition *sp)
{
	if (sp->is_swap) {
		fprintf(out,
		    "  %c:     %s        *      swap\n",
		    sp->letter,
		    capacity_to_string(sp->capacity)
		);
	} else {
		fprintf(out,
		    "  %c:     %s       %10s    4.2BSD     %10ld  %10ld      99\n",
		    sp->letter,
		    capacity_to_string(sp->capacity),
		    sp->letter == 'a' ? "0" : "*",
		    sp->fsize,
		    sp->bsize
		);
	}
	fprintfo(log, "wrote %c:%s\n", sp->letter,
	    capacity_to_string(sp->capacity));
}

/*
 * Create a new disklabel file based on the existing disklabel
 * and user's subpartition selections.
 */
int
cmd_weave_disklabel(FILE *log, void *userdata)
{
	FILE *out, *in;
	struct subpartition *sp;
	struct system_storage *ss = userdata;
	char line[256];
	char *filename;
	int wrote_c = 0;

	fprintfo(log, "Weaving together disklabels...\n");

	asprintf(&filename, INSTALLER_TEMP "install.disklabel.%ss%d",
	    ss->selected_disk->device, ss->selected_slice->number);
	in = fopen(filename, "r");
	free(filename);
	if (in == NULL) {
		fprintfo(log, "Could not open %s for reading\n", filename);
		return(1);
	}

	if ((out = fopen(INSTALLER_TEMP "install.disklabel", "w")) == NULL) {
		fprintfo(log, "Could not open disklabel for writing\n");
		fclose(in);
		return(1);
	}

	while(fgets(line, 255, in) != NULL) {
		if (!strncmp(line, "  c:", 4)) {
			for (sp = ss->selected_slice->subpartition_head;
			     sp != NULL; sp = sp->next) {
				if (sp->letter > 'c' && !wrote_c) {
					/* Retain original c: line. */
					fprintf(out, line);
					wrote_c = 1;
				}
				generate_disklabel_line(log, out, sp);
			}
			if (!wrote_c) {
				fprintf(out, line);
			}
		} else {
			fprintf(out, line);
		}
	}
	
	fclose(in);
	fclose(out);

	fprintfo(log, "Disklabels successfully woven together.\n");

	return(0);	/* indicates success, like a command-line utility */
}

/*
 * Create a new /etc/fstab file that reflects the subpartitions
 * on the newly installed disk.
 */
int
cmd_write_fstab(FILE *log, void *userdata)
{
	FILE *out;
	struct subpartition *sp;
	struct system_storage *ss = userdata;
	char *filename;

	fprintfo(log, "Writing new fstab...\n");

	asprintf(&filename, "%smnt/etc/fstab", dfly_root);
	out = fopen(filename, "w");
	free(filename);
	if (out == NULL) {
		fprintfo(log, "Could not open fstab for writing\n");
		return(1);
	}

	fprintf(out,
		"# Device		Mountpoint	FStype	Options		Dump	Pass#\n");

	for (sp = ss->selected_slice->subpartition_head; sp != NULL; sp = sp->next) {
		if (!strcmp(sp->mountpoint, "/")) {
			fprintf(out,
			"/dev/%ss%d%c		%s		ufs	rw		1	1\n",
			ss->selected_disk->device, ss->selected_slice->number, sp->letter, sp->mountpoint);
		} else if (sp->is_swap) {
			fprintf(out,
			"/dev/%ss%d%c		none		swap	sw		0	0\n",
			ss->selected_disk->device, ss->selected_slice->number, sp->letter);
		} else {
			fprintf(out,
			"/dev/%ss%d%c		%s		ufs	rw		2	2\n",
			ss->selected_disk->device, ss->selected_slice->number, sp->letter, sp->mountpoint);
		}
	}

	fprintf(out,
		"proc			/proc		procfs	rw		0	0\n");
	
	fclose(out);

	fprintfo(log, "New fstab successfully written.\n");

	return(0);
}

/*
 * Create a device node if it does not already exist.
 * NOTE WELL that this function, when executed, FREES the data at userdata.
 * So, do not command_add_function this with constant userdata - dfui_strdup
 * it instead.  And, if using asprintf, do not subsequently free() the
 * string yourself, let cmd_maybe_makedev() do it.
 */
int
cmd_maybe_makedev(FILE *log, void *userdata)
{
	char *command;
	char *dev_name;
	char *dname;
	int result;

	/*
	 * Strip leading /dev/ from string, if present.
	 */
	dname = dev_name = (char *)userdata;
	while (dname[0] == '/')
		dname++;

	if (dname[0] == 'd' && dname[1] == 'e' && dname[2] == 'v')
		dname += 3;

	while (dname[0] == '/')
		dname++;

	fprintfo(log, "| Seeing if %s needs to be created...\n", dname);

	if (is_device("/dev/%s", dname)) {
		fprintfo(log, "| No, %s already exists.\n", dname);
		result = 0;
	} else {
		fprintfo(log, "| Yes, creating %s...\n", dname);
		asprintf(&command, "cd %sdev && ./MAKEDEV %s",
		    dfly_root, dname);
		/* XXX pipe the output through us */
		result = system(command) / 256;
		free(command);
	}

	free(dev_name);
	return(result);
}

int
parse_geometry_info(char *line, int *cyl, int *head, int *sec)
{
	char *word;

	/*
	 * /dev/ad3: 2112 cyl 16 hd 63 sec
	 */
	if ((word = strtok(line, " \t")) == NULL)	/* /dev/ad3: */
		return(0);
	if ((word = strtok(NULL, " \t")) == NULL)	/* 2112 */
		return(0);
	*cyl = atoi(word);
	if ((word = strtok(NULL, " \t")) == NULL)	/* cyl */
		return(0);
	if ((word = strtok(NULL, " \t")) == NULL)	/* 16 */
		return(0);
	*head = atoi(word);
	if ((word = strtok(NULL, " \t")) == NULL)	/* hd */
		return(0);
	if ((word = strtok(NULL, " \t")) == NULL)	/* 63 */
		return(0);
	*sec = atoi(word);

	return(1);
}

int
parse_slice_info(char *line, int *slice, long *start, long *size, int *type, int *flags)
{
	char *word;

	/*
	 * Part        Start        Size Type Flags
	 *    1:          63     2128833 0xa5 0x80
	 */
	if ((word = strtok(line, " \t")) == NULL)	/* 1: */
		return(0);
	*slice = atoi(word);
	if ((word = strtok(NULL, " \t")) == NULL)	/* 63 */
		return(0);
	*start = atol(word);
	if ((word = strtok(NULL, " \t")) == NULL)	/* 2128833 */
		return(0);
	*size = atol(word);
	if ((word = strtok(NULL, " \t")) == NULL)	/* 0xa5 */
		return(0);
	if (!hex_to_int(word, type))
		return(0);
	if ((word = strtok(NULL, " \t")) == NULL)	/* 0x80 */
		return(0);
	if (!hex_to_int(word, flags))
		return(0);

	return(1);
}

/*
 * Set the sysid of a slice without changing anything else about it.
 */
int
cmd_set_slice_sysid_dfly(FILE *log, void *userdata)
{
	FILE *f;
	struct system_storage *ss = userdata;
	char line[256];
	long size, start;
	int cyl, flags, head, sec, temp, type;
	int got_geometry = 0, got_slice_info = 0;

	fprintfo(log, "| Creating fdisk script to set sysid of partition %d to 165...\n",
	    ss->selected_slice->number);

	f = fopen(INSTALLER_TEMP "/slice.fdisk", "r");
	if (f == NULL) {
		fprintfo(log, "| Error: couldn't open slice.fdisk\n");
		return(1);
	}

	/*
	 * /dev/ad3: 2112 cyl 16 hd 63 sec
	 * Part        Start        Size Type Flags
	 *    1:          63     2128833 0xa5 0x80
	 */
	while (fgets(line, 255, f) != NULL) {
		fprintfo(log, "|--%s", line);
		if (strncmp(line, "/dev/", 5) == 0) {
			got_geometry = parse_geometry_info(line, &cyl, &head, &sec);
		} else if (strncmp(line, "Part", 4) == 0) {
			/* ignore it */
		} else {
			got_slice_info =
			    parse_slice_info(line, &temp, &start, &size, &type, &flags);
		}
	}
	fclose(f);

	/*
	 * Make sure we gathered values OK.
	 */
	if (!got_geometry || !got_slice_info) {
		fprintfo(log, "| Error: couldn't read geometry or slice info\n");
		return(1);
	}

	fprintfo(log,
	    "| Cylinders: %d, Heads: %d, Sectors/Track: %d\n"
	    "| Start: %ld, Size: %ld, Type: %d, Flags: %d\n",
		cyl, head, sec, start, size, type, flags);

	/*
	 * Construct a fdisk script using the gathered values.
	 */
	if ((f = fopen(INSTALLER_TEMP "/new.fdisk", "w")) == NULL) {
		fprintfo(log, "| Error: couldn't create new.fdisk\n");
		return(1);
	}
	fprintf(f, "g c%d h%d s%d\n", cyl, head, sec);
	fprintf(f, "p %d %d %ld %ld\n", ss->selected_slice->number, 165, start,
	    size);

	if (flags & 0x80)
		fprintf(f, "a %d\n", ss->selected_slice->number);

	fclose(f);

	return(0);
}

int
cmd_discover_slices(FILE *log, void *userdata)
{
	FILE *f;
	struct disk *d = userdata;
	char line[256];
	long size, start;
	int flags, number, type;

	f = fopen(INSTALLER_TEMP "/temp.fdisk", "r");
	if (f == NULL) {
		fprintfo(log, "| Error: couldn't open temp.fdisk\n");
		return(1);
	}

	/*
	 * /dev/ad3: 2112 cyl 16 hd 63 sec
	 * Part        Start        Size Type Flags
	 *    1:          63     2128833 0xa5 0x80
	 */
	while (fgets(line, 255, f) != NULL) {
		if (strncmp(line, "/dev/", 5) == 0) {
			/* ignore it */
		} else if (strncmp(line, "Part", 4) == 0) {
			/* ignore it */
		} else {
			if (parse_slice_info(line, &number, &start, &size,
			    &type, &flags)) {
				fprintfo(log, "| Found slice #%d, sysid %d, "
				    "start %ld, size %ld\n",
				    number, type, start, size);
				slice_new(d, number, type, start, size);
			}
		}
	}
	fclose(f);

	return(0);
}

/*** COMMAND GENERATORS ***/

/*
 * Recursive portion of unmount_all_under().
 */
void
unmount_all_under_r(struct commands *cmds, char *mtpt,
    struct statfs *mt_array, int count)
{
	struct statfs *mt_ptr;
	char *command;
	int unmount_me = 0;
	int k = count;

	for (mt_ptr = mt_array; k > 0; mt_ptr++, k--) {
		if (strcmp(mt_ptr->f_mntonname, mtpt) == 0)
			unmount_me = 1;

		if (strncmp(mt_ptr->f_mntonname, mtpt, strlen(mtpt)) == 0 &&
		    strlen(mtpt) < strlen(mt_ptr->f_mntonname)) {
			unmount_all_under_r(cmds, mt_ptr->f_mntonname, mt_array, count);
		}
	}

	if (unmount_me) {
		asprintf(&command, "umount %s", mtpt);
		system(command);
		free(command);
	}
}

/*
 * Unmount all mountpoints under a given mountpoint.  Recursively unmounts
 * dependent mountpoints, so that unmount_all'ing /mnt will first unmount
 * /mnt/usr/local, then /mnt/usr, then /mnt itself.
 */
void
unmount_all_under(struct commands *cmds, char *fmt, ...)
{
	struct statfs *mt_array;
	va_list args;
	char *mtpt;
	int count;

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

	count = getmntinfo(&mt_array, MNT_WAIT);
	unmount_all_under_r(cmds, mtpt, mt_array, count);
	free(mtpt);
}

/*
 * cpdup a bunch of subdirectories.
 * Don't cpdup them if they already appear in sp.
 * dir should end with a trailing slash.
 */
void
cpdup_dir(struct commands *cmds, struct slice *s, char *dir, ...)
{
	va_list args;
	char *subdir;

	va_start(args, dir);
	while ((subdir = va_arg(args, char *)) != NULL) {
		if (s == NULL || subpartition_find(s, "%s%s", dir, subdir) == NULL) {
			command_add_logless(cmds, "%s %s%s%s %smnt%s%s",
			    CPDUP_CMD, dfly_root, &dir[1], subdir, dfly_root, dir, subdir);
		}
	}
	va_end(args);
}
