/*
  efibootmgr.c - Manipulates EFI variables as exported in /proc/efi/vars

  Copyright (C) 2001-2004 Dell, Inc. <Matt_Domsch@dell.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


  This must tie the EFI_DEVICE_PATH to /boot/efi/elilo.efi
  The  EFI_DEVICE_PATH will look something like:
    ACPI device path, length 12 bytes
    Hardware Device Path, PCI, length 6 bytes
    Messaging Device Path, SCSI, length 8 bytes, or ATAPI, length ??
    Media Device Path, Hard Drive, partition XX, length 30 bytes
    Media Device Path, File Path, length ??
    End of Hardware Device Path, length 4
    Arguments passed to elilo, as UCS-2 characters, length ??

*/

#define _GNU_SOURCE

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <getopt.h>
#include "list.h"
#include "efi.h"
#include "efichar.h"
#include "unparse_path.h"
#include "disk.h"
#include "efibootmgr.h"


#ifndef EFIBOOTMGR_VERSION
#define EFIBOOTMGR_VERSION "unknown (fix Makefile!)"
#endif


typedef struct _var_entry {
	struct dirent *name;
	uint16_t       num;
	efi_variable_t var_data;
	list_t         list;
} var_entry_t;


/* global variables */
static	LIST_HEAD(boot_entry_list);
static	LIST_HEAD(blk_list);
efibootmgr_opt_t opts;

static inline void
var_num_from_name(const char *pattern, char *name, uint16_t *num)
{
	sscanf(name, pattern, num);
}

static void
fill_bootvar_name(char *dest, size_t len, const char *name)
{
	efi_guid_t guid = EFI_GLOBAL_VARIABLE;
	char text_uuid[40];
	efi_guid_unparse(&guid, text_uuid);
	snprintf(dest, len, "%s-%s", name, text_uuid);
}

static void
fill_var(efi_variable_t *var, const char *name)
{
	efi_guid_t guid = EFI_GLOBAL_VARIABLE;

	efichar_from_char(var->VariableName, name, 1024);
	memcpy(&var->VendorGuid, &guid, sizeof(guid));
	var->Attributes = EFI_VARIABLE_NON_VOLATILE
		| EFI_VARIABLE_BOOTSERVICE_ACCESS
		| EFI_VARIABLE_RUNTIME_ACCESS;
}

static void
free_vars(list_t *head)
{
	list_t *pos, *n;
	var_entry_t *boot;

	list_for_each_safe(pos, n, head) {
		boot = list_entry(pos, var_entry_t, list);
		list_del(&(boot->list));
		free(boot);
	}
}

static void
read_vars(struct dirent **namelist,
	  int num_boot_names,
	  list_t *head)
{
	efi_status_t status;
	var_entry_t *entry;
	int i;

	if (!namelist) return;

	for (i=0; i < num_boot_names; i++)
	{
		if (namelist[i]) {
			entry = malloc(sizeof(var_entry_t));
			if (!entry) return;
			memset(entry, 0, sizeof(var_entry_t));

			status = read_variable(namelist[i]->d_name,
					       &entry->var_data);
			if (status != EFI_SUCCESS) break;
			entry->name = namelist[i];
			list_add_tail(&entry->list, head);
		}
	}
	return;
}





static void
free_dirents(struct dirent **ptr, int num_dirents)
{
	int i;
	if (!ptr) return;
	for (i=0; i < num_dirents; i++) {
		if (ptr[i]) {
			free(ptr[i]);
			ptr[i] = NULL;
		}
	}
	free(ptr);
}



static int
compare(const void *a, const void *b)
{
	int rc = -1;
	uint32_t n1, n2;
	memcpy(&n1, a, sizeof(n1));
	memcpy(&n2, b, sizeof(n2));
	if (n1 < n2) rc = -1;
	if (n1 == n2) rc = 0;
	if (n2 > n2) rc = 1;
	return rc;
}


/*
  Return an available boot variable number,
  or -1 on failure.
*/
static int
find_free_boot_var(list_t *boot_list)
{
	int num_vars=0, i=0, found;
	uint16_t *vars, free_number;
	list_t *pos;
	var_entry_t *boot;
	list_for_each(pos, boot_list) {
		num_vars++;
	}
	vars = malloc(sizeof(uint16_t) * num_vars);
	if (!vars) return -1;
	memset(vars, 0, sizeof(uint16_t) * num_vars);

	list_for_each(pos, boot_list) {
		boot = list_entry(pos, var_entry_t, list);
		vars[i] = boot->num;
			i++;
	}
	qsort(vars, i, sizeof(uint16_t), compare);
	found = 1;

	num_vars = i;
	for (free_number = 0; free_number < num_vars && found; free_number++) {
		found = 0;
		list_for_each(pos, boot_list) {
			boot = list_entry(pos, var_entry_t, list);
			if (boot->num == free_number) {
				found = 1;
				break;
			}
		}
		if (!found) break;
	}
	if (found && num_vars) free_number = vars[num_vars-1] + 1;
	free(vars);
	return free_number;
}


static void
warn_duplicate_name(list_t *boot_list)
{
	list_t *pos;
	var_entry_t *boot;
	EFI_LOAD_OPTION *load_option;

	list_for_each(pos, boot_list) {
		boot = list_entry(pos, var_entry_t, list);
		load_option = (EFI_LOAD_OPTION *)
			boot->var_data.Data;
		if (!efichar_char_strcmp(opts.label,
					 load_option->description)) {
			fprintf(stderr, "** Warning ** : %.8s has same label %s\n",
			       boot->name->d_name,
			       opts.label);
		}
	}
}


static var_entry_t *
make_boot_var(list_t *boot_list)
{
	var_entry_t *boot;
	int free_number;
	list_t *pos;

	if (opts.bootnum == -1)
		free_number = find_free_boot_var(boot_list);
	else {
		list_for_each(pos, boot_list) {
			boot = list_entry(pos, var_entry_t, list);
			if (boot->num == opts.bootnum) {
				fprintf(stderr, "** Warning ** : bootnum %04X "
				        "already exists\n", opts.bootnum);
				return NULL;
			}
		}
		free_number = opts.bootnum;
	}

	if (free_number == -1) return NULL;

	/* Create a new var_entry_t object
	   and populate it.
	*/

	boot = malloc(sizeof(*boot));
	if (!boot) return NULL;
	memset(boot, 0, sizeof(*boot));
	boot->num = free_number;
	if (!make_linux_efi_variable(&boot->var_data, free_number)) {
		free(boot);
		return NULL;
	}
	create_variable(&boot->var_data);
	list_add_tail(&boot->list, boot_list);
	return boot;
}



static efi_status_t
read_boot(efi_variable_t *var, const char *name)
{
	char name_guid[PATH_MAX];

	memset(var, 0, sizeof(*var));
	fill_bootvar_name(name_guid, sizeof(name_guid), name);
	return read_variable(name_guid, var);
}

static efi_status_t
read_boot_order(efi_variable_t *boot_order)
{
	efi_status_t status;

	status = read_boot(boot_order, "BootOrder");
	if (status != EFI_SUCCESS && status != EFI_NOT_FOUND)
		return status;

	if (status == EFI_NOT_FOUND) {
		fill_var(boot_order, "BootOrder");
	}
	return EFI_SUCCESS;
}


static efi_status_t
add_to_boot_order(uint16_t num)
{
	efi_status_t status;
	efi_variable_t boot_order;
	uint64_t new_data_size;
	uint16_t *new_data, *old_data;

	status = read_boot_order(&boot_order);
	if (status != EFI_SUCCESS) return status;

	/* We've now got an array (in boot_order.Data) of the
	   boot order.  First add our entry, then copy the old array.
	*/
	old_data = (uint16_t *)&(boot_order.Data);
	new_data_size = boot_order.DataSize + sizeof(uint16_t);
	new_data = malloc(new_data_size);

	new_data[0] = num;
	memcpy(new_data+1, old_data, boot_order.DataSize);

	/* Now new_data has what we need */
	memcpy(&(boot_order.Data), new_data, new_data_size);
	boot_order.DataSize = new_data_size;
	return create_or_edit_variable(&boot_order);
}


static efi_status_t
remove_from_boot_order(uint16_t num)
{
	efi_status_t status;
	efi_variable_t boot_order;
	uint64_t new_data_size;
	uint16_t *new_data, *old_data;
	int old_i,new_i;
	char boot_order_name[PATH_MAX];

	status = read_boot_order(&boot_order);
	if (status != EFI_SUCCESS) return status;
	/* If it's empty, yea! */
	if (!boot_order.DataSize) return EFI_SUCCESS;

	fill_bootvar_name(boot_order_name, sizeof(boot_order_name),
			  "BootOrder");

	/* We've now got an array (in boot_order.Data) of the
	   boot order.  Simply copy the array, skipping the
	   entry we're deleting.
	*/
	old_data = (uint16_t *)&(boot_order.Data);
	/* Start with the same size */
	new_data_size = boot_order.DataSize;
	new_data = malloc(new_data_size);
	for (old_i=0,new_i=0;
	     old_i < boot_order.DataSize / sizeof(uint16_t);
	     old_i++) {
		if (old_data[old_i] != num) {
				/* Copy this value */
			new_data[new_i] = old_data[old_i];
			new_i++;
		}
	}

	/* Now new_data has what we need */
	new_data_size = new_i * sizeof(uint16_t);
	memset(&(boot_order.Data), 0, boot_order.DataSize);
	memcpy(&(boot_order.Data), new_data, new_data_size);
	boot_order.DataSize = new_data_size;

	return edit_variable(&boot_order);
}

static efi_status_t
delete_var(const char *name)
{
	efi_variable_t var;

	memset(&var, 0, sizeof(var));
	fill_var(&var, name);
	return delete_variable(&var);
}

static int
read_boot_u16(const char *name)
{
	efi_status_t status;
	efi_variable_t var;
	uint16_t *n = (uint16_t *)(var.Data);

	memset(&var, 0, sizeof(var));
	status = read_boot(&var, name);
	if (status) return -1;
	return *n;
}

static efi_status_t
set_boot_u16(const char *name, uint16_t num)
{
	efi_variable_t var;
	uint16_t *n = (uint16_t *)var.Data;

	memset(&var, 0, sizeof(var));

	fill_var(&var, name);
	*n = num;
	var.DataSize = sizeof(uint16_t);
	return create_or_edit_variable(&var);
}

static efi_status_t
delete_boot_var(uint16_t num)
{
	efi_status_t status;
	efi_variable_t var;
	char name[16];
	list_t *pos, *n;
	var_entry_t *boot;

	snprintf(name, sizeof(name), "Boot%04X", num);
	memset(&var, 0, sizeof(var));
	fill_var(&var, name);
	status = delete_variable(&var);

	/* For backwards compatibility, try to delete abcdef entries as well */
	if (status) {
		snprintf(name, sizeof(name), "Boot%04x", num);
		memset(&var, 0, sizeof(var));
		fill_var(&var, name);
		status = delete_variable(&var);
	}

	if (status) {
		fprintf (stderr,"\nboot entry: %X not found\n\n",num);
		return status;
	}
	list_for_each_safe(pos, n, &boot_entry_list) {
		boot = list_entry(pos, var_entry_t, list);
		if (boot->num == num) {
			status = remove_from_boot_order(num);
			if (status) return status;
			list_del(&(boot->list));
			break; /* short-circuit since it was found */
		}
	}
	return EFI_SUCCESS;
}


static void
set_var_nums(const char *pattern, list_t *list)
{
	list_t *pos;
	var_entry_t *var;
	int num=0, rc;
	char *name;
	int warn=0;

	list_for_each(pos, list) {
		var = list_entry(pos, var_entry_t, list);
		rc = sscanf(var->name->d_name, pattern, &num);
		if (rc == 1) {
			var->num = num;
			name = var->name->d_name; /* shorter name */
			if ((isalpha(name[4]) && islower(name[4])) ||
			    (isalpha(name[5]) && islower(name[5])) ||
			    (isalpha(name[6]) && islower(name[6])) ||
			    (isalpha(name[7]) && islower(name[7]))) {
				fprintf(stderr, "** Warning ** : %.8s is not "
				        "EFI 1.10 compliant (lowercase hex in name)\n", name);
				warn++;
			}
		}
	}
	if (warn) {
		fprintf(stderr, "** Warning ** : please recreate these using efibootmgr to remove this warning.\n");
	}
}

#if 0
static efi_variable_t *
find_pci_scsi_disk_blk(int fd, int bus, int device, int func,
		       list_t *blk_list)
{
	list_t *pos;
	int rc;
	Scsi_Idlun idlun;
	unsigned char host, channel, id, lun;
	var_entry_t *blk;
	efi_variable_t *blk_var;
	long size = 0;

	memset(&idlun, 0, sizeof(idlun));
	rc = get_scsi_idlun(fd, &idlun);
	if (rc) return NULL;

	rc = disk_get_size(fd, &size);

	idlun_to_components(&idlun, &host, &channel, &id, &lun);

	list_for_each(pos, blk_list) {
		blk = list_entry(pos, var_entry_t, list);
		blk_var = blk->var_data;

		if (!compare_pci_scsi_disk_blk(blk_var,
					       bus, device, func,
					       host, channel, id, lun,
					       0, size)) {
			return blk_var;
		}
	}
	return NULL;
}




/* The right blkX variable contains:
   1) the PCI and SCSI information for the disk passed in disk_name
   2) Does not contain a partition field 
*/


static efi_variable_t *
find_disk_blk(char *disk_name, list_t *blk_list)
{
	efi_variable_t *disk_blk = NULL;
	int fd, rc;
	unsigned char bus=0,device=0,func=0;
	int interface_type=interface_type_unknown;
	unsigned int controllernum=0, disknum=0;
	unsigned char part=0;

	fd = open(disk_name, O_RDONLY|O_DIRECT);
	rc = disk_get_pci(fd, &bus, &device, &func);
	if (rc) {
		fprintf(stderr, "disk_get_pci() failed.\n");
		return NULL;
	}
	rc = disk_info_from_fd(fd,
			       &interface_type,
			       &controllernum,
			       &disknum,
			       &part);
	if (rc) {
		fprintf(stderr, "disk_info_from_fd() failed.\n");
		return NULL;
	}
	switch (interface_type)
	{
	case scsi:
		return find_pci_scsi_disk_blk(fd,bus,device,func,blk_list);
		break;
	case ata:
		return find_pci_ata_disk_blk(fd,bus,device,func,blk_list);
		break;
	case i2o:
		return find_pci_i2o_disk_blk(fd,bus,device,func,blk_list);
		break;
	case md:
		return find_pci_md_disk_blk(fd,bus,device,func,blk_list);
		break;
	default:
		break;
	}
	return NULL;
}
#endif

static void
unparse_boot_order(uint16_t *order, int length)
{
	int i;
	printf("BootOrder: ");
	for (i=0; i<length; i++) {
		printf("%04X", order[i]);
		if (i < (length-1))
			printf(",");
	}
	printf("\n");
}

static int
is_current_boot_entry(int b)
{
	list_t *pos;
	var_entry_t *boot;

	list_for_each(pos, &boot_entry_list) {
		boot = list_entry(pos, var_entry_t, list);
		if (boot->num == b)
			return 1;
	}
	return 0;
}


static int
parse_boot_order(char *buffer, uint16_t *order, int length)
{
	int i;
	int num, rc;

	for (i=0; i<length && *buffer; i++) {
		rc = sscanf(buffer, "%x", &num);
		if (rc == 1) order[i] = num & 0xFFFF;
		else {
			fprintf(stderr,"\nInvalid hex characters in boot order: %s\n\n",buffer);
			return -1;
		}
		/* make sure this is an existing boot entry */
		if (!is_current_boot_entry(order[i])) {
			fprintf (stderr,"\nboot entry %X does not exist\n\n",order[i]);
			return -1;
		}

		/* Advance to the comma */ 
		while (*buffer && *buffer != ',') buffer++;
		/* Advance through the comma(s) */
		while (*buffer && *buffer == ',') buffer++;
	}
	return i;
}

static efi_status_t
set_boot_order()
{
	efi_variable_t boot_order;
	uint16_t *n = (uint16_t *)boot_order.Data;

	if (!opts.bootorder) return EFI_SUCCESS;

	memset(&boot_order, 0, sizeof(boot_order));
	fill_var(&boot_order, "BootOrder");

	boot_order.DataSize = parse_boot_order(opts.bootorder, n, 1024/sizeof(uint16_t)) * sizeof(uint16_t);
	if (boot_order.DataSize < 0)
		return 1;
	else
		return create_or_edit_variable(&boot_order);
}

static void
show_boot_vars()
{
	list_t *pos;
	var_entry_t *boot;
	char description[80];
	EFI_LOAD_OPTION *load_option;
	EFI_DEVICE_PATH *path;
	char text_path[1024], *p;
	unsigned long optional_data_len=0;

	list_for_each(pos, &boot_entry_list) {
		boot = list_entry(pos, var_entry_t, list);
		load_option = (EFI_LOAD_OPTION *)
			boot->var_data.Data;
		efichar_to_char(description,
				load_option->description, sizeof(description));
		memset(text_path, 0, sizeof(text_path));
		path = load_option_path(load_option);
		if (boot->name)
			printf("%.8s", boot->name->d_name);
		else
			printf("Boot%04X", boot->num);

		if (load_option->attributes & LOAD_OPTION_ACTIVE)
			printf("* ");
		else    printf("  ");
		printf("%s", description);

		if (opts.verbose) {
			unparse_path(text_path, path,
				     load_option->file_path_list_length);
			/* Print optional data */
			optional_data_len =
				boot->var_data.DataSize -
				load_option->file_path_list_length -
				((char *)path - (char *)load_option);
			if (optional_data_len) {
				p = text_path;
				p += strlen(text_path);
				unparse_raw_text(p, ((uint8_t *)path) +
						 load_option->file_path_list_length,
						 optional_data_len);
			}

			printf("\t%s", text_path);
		}
		printf("\n");
	}
}



static void
show_boot_order()
{
	efi_status_t status;
	efi_variable_t boot_order;
	uint16_t *data;

	status = read_boot_order(&boot_order);

	if (status != EFI_SUCCESS) {
		perror("show_boot_order()");
		return;
	}

	/* We've now got an array (in boot_order.Data) of the
	   boot order.  First add our entry, then copy the old array.
	*/
	data = (uint16_t *)&(boot_order.Data);
	if (boot_order.DataSize)
		unparse_boot_order(data, boot_order.DataSize / sizeof(uint16_t));

}

static efi_status_t
set_active_state()
{
	list_t *pos;
	var_entry_t *boot;
	EFI_LOAD_OPTION *load_option;

	list_for_each(pos, &boot_entry_list) {
		boot = list_entry(pos, var_entry_t, list);
		load_option = (EFI_LOAD_OPTION *)
			boot->var_data.Data;
		if (boot->num == opts.bootnum) {
			if (opts.active == 1) {
				if (load_option->attributes
				    & LOAD_OPTION_ACTIVE) return EFI_SUCCESS;
				else {
					load_option->attributes
						|= LOAD_OPTION_ACTIVE;
					return edit_variable(&boot->var_data);
				}
			}
			else if (opts.active == 0) {
				if (!(load_option->attributes
				      & LOAD_OPTION_ACTIVE))
					return EFI_SUCCESS;
				else {
					load_option->attributes
						&= ~LOAD_OPTION_ACTIVE;
					return edit_variable(&boot->var_data);
				}
			}
		}
	}
	/* if we reach here then the bootnumber supplied was not found */
	fprintf(stderr,"\nboot entry %x not found\n\n",opts.bootnum);
	return EFI_NOT_FOUND;
}




static void
usage()
{
	printf("efibootmgr version %s\n", EFIBOOTMGR_VERSION);
	printf("usage: efibootmgr [options]\n");
	printf("\t-a | --active         sets bootnum active\n");
	printf("\t-A | --inactive       sets bootnum inactive\n");
	printf("\t-b | --bootnum XXXX   modify BootXXXX (hex)\n");
	printf("\t-B | --delete-bootnum delete bootnum (hex)\n");
	printf("\t-c | --create         create new variable bootnum and add to bootorder\n");
	printf("\t-d | --disk disk       (defaults to /dev/sda) containing loader\n");
	printf("\t-e | --edd [1|3|-1]   force EDD 1.0 or 3.0 creation variables, or guess\n");
	printf("\t-E | --device num      EDD 1.0 device number (defaults to 0x80)\n");
	printf("\t-g | --gpt            force disk with invalid PMBR to be treated as GPT\n");
	printf("\t-H | --acpi_hid XXXX  set the ACPI HID (used with -i)\n");
	printf("\t-i | --iface name     create a netboot entry for the named interface\n");
	printf("\t-l | --loader name     (defaults to \\elilo.efi)\n");
	printf("\t-L | --label label     Boot manager display label (defaults to \"Linux\")\n");
	printf("\t-n | --bootnext XXXX   set BootNext to XXXX (hex)\n");
	printf("\t-N | --delete-bootnext delete BootNext\n");
	printf("\t-o | --bootorder XXXX,YYYY,ZZZZ,...     explicitly set BootOrder (hex)\n");
	printf("\t-O | --delete-bootorder delete BootOrder\n");
	printf("\t-p | --part part        (defaults to 1) containing loader\n");
	printf("\t-q | --quiet            be quiet\n");
	printf("\t   | --test filename    don't write to NVRAM, write to filename.\n");
	printf("\t-t | --timeout seconds  set boot manager timeout waiting for user input.\n");
	printf("\t-T | --delete-timeout   delete Timeout.\n");
	printf("\t-u | --unicode | --UCS-2  pass extra args as UCS-2 (default is ASCII)\n");
	printf("\t-U | --acpi_uid XXXX    set the ACPI UID (used with -i)\n");
	printf("\t-v | --verbose          print additional information\n");
	printf("\t-V | --version          return version and exit\n");
	printf("\t-w | --write-signature  write unique sig to MBR if needed\n");
	printf("\t-@ | --append-binary-args file  append extra args from file (use \"-\" for stdin)\n");
}

static void
set_default_opts()
{
	memset(&opts, 0, sizeof(opts));
	opts.bootnum         = -1;   /* auto-detect */
	opts.bootnext        = -1;   /* Don't set it */
	opts.active          = -1;   /* Don't set it */
	opts.timeout         = -1;   /* Don't set it */
	opts.edd10_devicenum = 0x80;
	opts.loader          = "\\elilo.efi";
	opts.label           = "Linux";
	opts.disk            = "/dev/sda";
	opts.iface           = NULL;
	opts.part            = 1;
	opts.acpi_hid        = -1;
	opts.acpi_uid        = -1;
}

static void
parse_opts(int argc, char **argv)
{
	int c, num, rc;
	int option_index = 0;

	while (1)
	{
		static struct option long_options[] =
			/* name, has_arg, flag, val */
		{
			{"active",                 no_argument, 0, 'a'},
			{"inactive",               no_argument, 0, 'A'},
			{"bootnum",          required_argument, 0, 'b'},
			{"delete-bootnum",         no_argument, 0, 'B'},
			{"create",                 no_argument, 0, 'c'},
			{"disk",             required_argument, 0, 'd'},
			{"iface",            required_argument, 0, 'i'},
			{"acpi_hid",         required_argument, 0, 'H'},
			{"edd-device",       required_argument, 0, 'E'},
			{"edd30",            required_argument, 0, 'e'},
			{"gpt",                    no_argument, 0, 'g'},
			{"loader",           required_argument, 0, 'l'},
			{"label",            required_argument, 0, 'L'},
			{"bootnext",         required_argument, 0, 'n'},
			{"delete-bootnext",        no_argument, 0, 'N'},
			{"bootorder",        required_argument, 0, 'o'},
			{"delete-bootorder",       no_argument, 0, 'O'},
			{"part",             required_argument, 0, 'p'},
			{"quiet",                  no_argument, 0, 'q'},
			{"test",             required_argument, 0,   1},
			{"timeout",          required_argument, 0, 't'},
			{"delete-timeout",         no_argument, 0, 'T'},
			{"unicode",                no_argument, 0, 'u'},
			{"UCS-2",                  no_argument, 0, 'u'},
			{"acpi_uid",         required_argument, 0, 'U'},
			{"verbose",          optional_argument, 0, 'v'},
			{"version",                no_argument, 0, 'V'},
			{"write-signature",        no_argument, 0, 'w'},
			{"append-binary-args", required_argument, 0, '@'},
			{0, 0, 0, 0}
		};

		c = getopt_long (argc, argv,
				 "AaBb:cd:e:E:gH:i:l:L:n:No:Op:qt:TuU:v::Vw@:",
				 long_options, &option_index);
		if (c == -1)
			break;

		switch (c)
		{
		case '@':
			opts.extra_opts_file = optarg;
			break;
		case 'a':
			opts.active = 1;
			break;
		case 'A':
			opts.active = 0;
			break;
		case 'B':
			opts.delete_boot = 1;
			break;
		case 'b':
			rc = sscanf(optarg, "%X", &num);
			if (rc == 1) opts.bootnum = num;
			else {
				fprintf (stderr,"invalid hex value %s\n",optarg);
				exit(1);
			}
			break;
		case 'c':
			opts.create = 1;
			break;
		case 'd':
			opts.disk = optarg;
			break;
		case 'e':
			rc = sscanf(optarg, "%d", &num);
			if (rc == 1) opts.edd_version = num;
			else {
				fprintf (stderr,"invalid numeric value %s\n",optarg);
				exit(1);
			}
			break;
		case 'E':
			rc = sscanf(optarg, "%x", &num);
			if (rc == 1) opts.edd10_devicenum = num;
			else {
				fprintf (stderr,"invalid hex value %s\n",optarg);
				exit(1);
			}
			break;
		case 'g':
			opts.forcegpt = 1;
			break;
		case 'H':
			rc = sscanf(optarg, "%x", &num);
			if (rc == 1) opts.acpi_hid = num;
			else {
				fprintf (stderr,"invalid hex value %s\n",optarg);
				exit(1);
			}
			break;
		case 'i':
			opts.iface = optarg;
			break;
		case 'l':
			opts.loader = optarg;
			break;
		case 'L':
			opts.label = optarg;
			break;
		case 'N':
			opts.delete_bootnext = 1;
			break;
		case 'n':
			rc = sscanf(optarg, "%x", &num);
			if (rc == 1) opts.bootnext = num;
			else {
				fprintf (stderr,"invalid hex value %s\n",optarg);
				exit(1);
			}
			break;
		case 'o':
			opts.bootorder = optarg;
			break;
		case 'O':
			opts.delete_bootorder = 1;
			break;
		case 'p':
			rc = sscanf(optarg, "%u", &num);
			if (rc == 1) opts.part = num;
			else {
				fprintf (stderr,"invalid numeric value %s\n",optarg);
				exit(1);
			}
			break;
		case 'q':
			opts.quiet = 1;
			break;
		case 1:
			opts.testfile = optarg;
			break;
		case 't':
			rc = sscanf(optarg, "%u", &num);
			if (rc == 1) {
				opts.timeout = num;
				opts.set_timeout = 1;
			}
			else {
				fprintf (stderr,"invalid numeric value %s\n",optarg);
				exit(1);
			}
			break;
		case 'T':
			opts.delete_timeout = 1;
			break;
		case 'u':
			opts.unicode = 1;
			break;

		case 'U':
			rc = sscanf(optarg, "%x", &num);
			if (rc == 1) opts.acpi_uid = num;
			else {
				fprintf (stderr,"invalid hex value %s\n",optarg);
				exit(1);
			}
			break;
		case 'v':
			opts.verbose = 1;
			if (optarg) {
				if (!strcmp(optarg, "v"))  opts.verbose = 2;
				if (!strcmp(optarg, "vv")) opts.verbose = 3;
				rc = sscanf(optarg, "%d", &num);
				if (rc == 1)  opts.verbose = num;
				else {
					fprintf (stderr,"invalid numeric value %s\n",optarg);
					exit(1);
				}
			}
			break;
		case 'V':
			opts.showversion = 1;
			break;

		case 'w':
			opts.write_signature = 1;
			break;

		default:
			usage();
			exit(1);
		}
	}

	if (optind < argc) {
		opts.argc = argc;
		opts.argv = argv;
		opts.optind = optind;
	}
}


int
main(int argc, char **argv)
{
	struct dirent  **boot_names = NULL;
	var_entry_t *new_boot = NULL;
	int num, num_boot_names=0;
	efi_status_t ret=0;

	set_default_opts();
	parse_opts(argc, argv);
	if (opts.showversion) {
		printf("version %s\n", EFIBOOTMGR_VERSION);
		return 0;
	}

	if (opts.iface && opts.acpi_hid == -1 && opts.acpi_uid == -1) {
		fprintf(stderr, "\nYou must specify the ACPI HID and UID when using -i.\n\n");
		return 1;
	}

	if (!opts.testfile)
		set_fs_kernel_calls();

	if (!opts.testfile) {
		num_boot_names = read_boot_var_names(&boot_names);
		read_vars(boot_names, num_boot_names, &boot_entry_list);
		set_var_nums("Boot%04X-%*s", &boot_entry_list);

		if (opts.delete_boot) {
			if (opts.bootnum == -1) {
				fprintf(stderr, "\nYou must specify a boot entry to delete (see the -b option).\n\n");
				return 1;
			}
			else
				ret = delete_boot_var(opts.bootnum);
		}

		if (opts.active >= 0) {
			if (opts.bootnum == -1) {
				fprintf(stderr, "\nYou must specify a boot entry to delete (see the -b option).\n\n");
				return 1;
			}
			else
				ret=set_active_state();
		}
	}

	if (opts.create) {
		warn_duplicate_name(&boot_entry_list);
		new_boot = make_boot_var(&boot_entry_list);
		if (!new_boot)
			return 1;

		/* Put this boot var in the right BootOrder */
		if (!opts.testfile && new_boot)
			ret=add_to_boot_order(new_boot->num);
	}

	if (!opts.testfile) {

		if (opts.delete_bootorder) {
			ret=delete_var("BootOrder");
		}

		if (opts.bootorder) {
			ret=set_boot_order();
		}


		if (opts.delete_bootnext) {
			ret=delete_var("BootNext");
		}

		if (opts.delete_timeout) {
			ret=delete_var("Timeout");
		}

		if (opts.bootnext >= 0) {
			if (!is_current_boot_entry(opts.bootnext & 0xFFFF)){
				fprintf (stderr,"\n\nboot entry %X does not exist\n\n",
					opts.bootnext);
				return 1;
			}
			ret=set_boot_u16("BootNext", opts.bootnext & 0xFFFF);
		}

		if (opts.set_timeout) {
			ret=set_boot_u16("Timeout", opts.timeout);
		}

		if (!opts.quiet && ret == 0) {
			num = read_boot_u16("BootNext");
			if (num != -1 ) {
				printf("BootNext: %04X\n", num);
			}
			num = read_boot_u16("BootCurrent");
			if (num != -1) {
				printf("BootCurrent: %04X\n", num);
			}
			num = read_boot_u16("Timeout");
			if (num != -1) {
				printf("Timeout: %u seconds\n", num);
			}
			show_boot_order();
			show_boot_vars();
		}
	}
	free_dirents(boot_names, num_boot_names);
	free_vars(&boot_entry_list);
	if (ret)
		return 1;
	return 0;
}

