efilinux: Minimal configuration file support

efilinux lacks a way of automatically booting a default
kernel. Instead, the kernel image path and kernel command line
arguments must be passed as arguments to efilinux on every invocation.

This commit allows a simple config file to specify a default kernel
and kernel arguments, which are passed to efilinux instead of
requiring them to be entered at the EFI shell. Now, when efilinux is
started it will first search for a file named 'efilinux.cfg' in the
same directory as the efilinux executable.

The syntax for a configuration file is exactly the same as the syntax
for efilinux's command line arguments. For example, to boot a linux
kernel the contents of 'efilinux.cfg' would be a single line,

	"-f 0:\EFI\BOOT\vmlinux console=ttys0 initrd=0:\EFI\BOOT\initrd"

The contents of the file are passed to efilinux as though they were
typed at the EFI shell prompt. Multiple lines are not supported.

Cc: Darrent Hart <dvhart@linux.intel.com>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
diff --git a/efilinux.h b/efilinux.h
index c571e35..70fed1f 100644
--- a/efilinux.h
+++ b/efilinux.h
@@ -45,6 +45,8 @@
 #define EFILINUX_VERSION_MAJOR 0
 #define EFILINUX_VERSION_MINOR 9
 
+#define EFILINUX_CONFIG	L"efilinux.cfg"
+
 extern EFI_SYSTEM_TABLE *sys_table;
 extern EFI_BOOT_SERVICES *boot;
 extern EFI_RUNTIME_SERVICES *runtime;
diff --git a/entry.c b/entry.c
index 20d263b..a1e7967 100644
--- a/entry.c
+++ b/entry.c
@@ -154,29 +154,30 @@
 	return err;
 }
 
+static inline BOOLEAN isspace(CHAR16 ch)
+{
+	return ((unsigned char)ch <= ' ');
+}
+
 static EFI_STATUS
 parse_args(CHAR16 *options, UINT32 size, CHAR16 **name, char **cmdline)
 {
 	CHAR16 *n, *o, *filename = NULL;
 	EFI_STATUS err;
-	int i;
+	int i = 0;
 
 	*cmdline = NULL;
 	*name = NULL;
 
-	if (!options || size == 0)
-		goto fail;
-
-	/* Skip the first word, that's our name. */
-	for (i = 0; i < size && options[i] != ' '; i++)
-		;
+	/* Skip whitespace */
+	for (i = 0; i < size && isspace(options[i]); i++)
+		     ;
 
 	/* No arguments */
 	if (i == size)
 		goto fail;
 
-	n = &options[++i];
-
+	n = &options[i];
 	while (n <= &options[size]) {
 		if (*n == '-') {
 			switch (*++n) {
@@ -186,12 +187,12 @@
 				n++;	/* Skip 'f' */
 
 				/* Skip whitespace */
-				while (*n == ' ')
+				while (isspace(*n))
 					n++;
 
 				filename = n;
 				i = 0;	
-				while (*n && *n != ' ' && *n != '\n') {
+				while (*n && !isspace(*n)) {
 					i++;
 					n++;
 				}
@@ -267,6 +268,132 @@
 	return err;
 }
 
+static inline BOOLEAN
+get_path(EFI_LOADED_IMAGE *image, CHAR16 *path, UINTN len)
+{
+	CHAR16 *buf, *p, *q;
+	int i, dev;
+
+	dev = handle_to_dev(image->DeviceHandle);
+	if (dev == -1) {
+		Print(L"Couldn't find boot device handle\n");
+		return FALSE;
+	}
+
+	/* Find the path of the efilinux executable*/
+	p = DevicePathToStr(image->FilePath);
+	q = p + StrLen(p);
+
+	i = StrLen(p);
+	while (*q != '\\' && *q != '/') {
+		q--;
+		i--;
+	}
+
+	buf = malloc(i * sizeof(CHAR16));
+	if (!buf) {
+		Print(L"Failed to allocate buf\n");
+		FreePool(p);
+		return FALSE;
+	}
+
+	memcpy((char *)buf, (char *)p, i * sizeof(CHAR16));
+	FreePool(p);
+
+	buf[i] = '\0';
+	SPrint(path, len, L"%d:%s\\%s", dev, buf, EFILINUX_CONFIG);
+
+	return TRUE;
+}
+
+static BOOLEAN
+read_config_file(EFI_LOADED_IMAGE *image, CHAR16 **options,
+		 UINT32 *options_size)
+{
+	struct file *file;
+	EFI_STATUS err;
+	CHAR16 path[4096];
+	CHAR16 *u_buf, *q;
+	char *a_buf, *p;
+	UINT64 size;
+	int i;
+
+	err = get_path(image, path, sizeof(path));
+	if (err != TRUE)
+		return FALSE;
+
+	err = file_open(path, &file);
+	if (err != EFI_SUCCESS)
+		return FALSE;
+
+	err = file_size(file, &size);
+	if (err != EFI_SUCCESS)
+		goto fail;
+
+	/*
+	 * The config file contains ASCII characters, but the command
+	 * line parser expects arguments to be UTF-16. Convert them
+	 * once we've read them into 'a_buf'.
+	 */
+
+	/* Make sure we don't overflow the UINT32 */
+	if (size > 0xffffffff || (size * 2) > 0xffffffff ) {
+		Print(L"Config file size too large. Ignoring.\n");
+		goto fail;
+	}
+
+	a_buf = malloc((UINTN)size);
+	if (!a_buf) {
+		Print(L"Failed to alloc buffer %d bytes\n", size);
+		goto fail;
+	}
+
+	u_buf = malloc((UINTN)size * 2);
+	if (!u_buf) {
+		Print(L"Failed to alloc buffer %d bytes\n", size);
+		free(a_buf);
+		goto fail;
+	}
+
+	err = file_read(file, (UINTN *)&size, a_buf);
+	if (err != EFI_SUCCESS)
+		goto fail;
+
+	Print(L"Using efilinux config file\n");
+
+	/*
+	 * Read one line. Stamp a NUL-byte into the buffer once we've
+	 * read the end of the first line.
+	 */
+	for (p = a_buf, i = 0; *p && *p != '\n' && i < size; p++, i++)
+		;
+	if (*p == '\n')
+		*p++ = '\0';
+
+	if (i == size && *p) {
+		Print(L"Error: missing newline at end of config file?\n");
+		goto fail;
+	}
+
+	if ((p - a_buf) < size)
+		Print(L"Warning: config file contains multiple lines?\n");
+
+	p = a_buf;
+	q = u_buf;
+	for (i = 0; i < size; i++)
+		*q++ = *p++;
+	free(a_buf);
+
+	*options = u_buf;
+	*options_size = (UINT32)size * 2;
+
+	file_close(file);
+	return TRUE;
+fail:
+	file_close(file);
+	return FALSE;
+}
+
 /**
  * efi_main - The entry point for the OS loader image.
  * @image: firmware-allocated handle that identifies the image
@@ -278,7 +405,8 @@
 	WCHAR *error_buf;
 	EFI_STATUS err;
 	EFI_LOADED_IMAGE *info;
-	CHAR16 *name;
+	CHAR16 *name, *options;
+	UINT32 options_size;
 	char *cmdline;
 
 	InitializeLib(image, _table);
@@ -299,10 +427,24 @@
 	if (err != EFI_SUCCESS)
 		goto fs_deinit;
 
-	err = parse_args(info->LoadOptions, info->LoadOptionsSize,
-			 &name, &cmdline);
-	if (err != EFI_SUCCESS)
-		goto fs_deinit;
+	if (!read_config_file(info, &options, &options_size)) {
+		int i;
+
+		options = info->LoadOptions;
+		options_size = info->LoadOptionsSize;
+
+		/* Skip the first word, that's our name. */
+		for (i = 0; i < options_size && options[i] != ' '; i++)
+			;
+		options = &options[i];
+		options_size -= i;
+	}
+
+	if (options && options_size != 0) {
+		err = parse_args(options, options_size, &name, &cmdline);
+		if (err != EFI_SUCCESS)
+			goto fs_deinit;
+	}
 
 	err = load_image(image, name, cmdline);
 	if (err != EFI_SUCCESS)
diff --git a/fs/fs.c b/fs/fs.c
index dccf418..1cbed4e 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -48,6 +48,26 @@
 static UINTN nr_fs_devices;
 
 /**
+ * handle_to_dev - Return the device number for a handle
+ * @handle: the device handle to search for
+ */
+int
+handle_to_dev(EFI_HANDLE *handle)
+{
+	int i;
+
+	for (i = 0; i < nr_fs_devices; i++) {
+		if (fs_devices[i].handle == handle)
+			break;
+	}
+
+	if (i == nr_fs_devices)
+		return -1;
+
+	return i;
+}
+
+/**
  * file_open - Open a file on a volume
  * @name: pathname of the file to open
  * @file: used to return a pointer to the allocated file on success
diff --git a/fs/fs.h b/fs/fs.h
index 6ffbb84..0f76d0c 100644
--- a/fs/fs.h
+++ b/fs/fs.h
@@ -102,6 +102,7 @@
 extern EFI_STATUS file_close(struct file *f);
 
 extern void list_boot_devices(void);
+extern int handle_to_dev(EFI_HANDLE *handle);
 
 extern void fs_close(void);