[efi] Allow autoexec script to be located alongside iPXE binary

Try loading the autoexec.ipxe script first from the directory
containing the iPXE binary (based on the relative file path provided
to us via EFI_LOADED_IMAGE_PROTOCOL), then fall back to trying the
root directory.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/include/ipxe/efi/efi_autoexec.h b/src/include/ipxe/efi/efi_autoexec.h
index 1f93b41..08ddf58 100644
--- a/src/include/ipxe/efi/efi_autoexec.h
+++ b/src/include/ipxe/efi/efi_autoexec.h
@@ -11,6 +11,7 @@
 
 #include <ipxe/efi/efi.h>
 
-extern int efi_autoexec_load ( EFI_HANDLE device );
+extern int efi_autoexec_load ( EFI_HANDLE device,
+			       EFI_DEVICE_PATH_PROTOCOL *path );
 
 #endif /* _IPXE_EFI_AUTOEXEC_H */
diff --git a/src/interface/efi/efi_autoexec.c b/src/interface/efi/efi_autoexec.c
index 79d4a4c..fb12cef 100644
--- a/src/interface/efi/efi_autoexec.c
+++ b/src/interface/efi/efi_autoexec.c
@@ -54,12 +54,14 @@
 static size_t efi_autoexec_len;
 
 /**
- * Load autoexec script from filesystem
+ * Load autoexec script from path within filesystem
  *
  * @v device		Device handle
+ * @v path		Relative path to image, or NULL to load from root
  * @ret rc		Return status code
  */
-static int efi_autoexec_filesystem ( EFI_HANDLE device ) {
+static int efi_autoexec_filesystem ( EFI_HANDLE device,
+				     EFI_DEVICE_PATH_PROTOCOL *path ) {
 	EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
 	union {
 		void *interface;
@@ -70,13 +72,58 @@
 		CHAR16 name[ sizeof ( efi_autoexec_wname ) /
 			     sizeof ( efi_autoexec_wname[0] ) ];
 	} info;
+	FILEPATH_DEVICE_PATH *filepath;
 	EFI_FILE_PROTOCOL *root;
 	EFI_FILE_PROTOCOL *file;
 	UINTN size;
 	VOID *data;
+	unsigned int dirlen;
+	size_t len;
+	CHAR16 *wname;
 	EFI_STATUS efirc;
 	int rc;
 
+	/* Identify directory */
+	if ( path ) {
+
+		/* Check relative device path is a file path */
+		if ( ! ( ( path->Type == MEDIA_DEVICE_PATH ) &&
+			 ( path->SubType == MEDIA_FILEPATH_DP ) ) ) {
+			DBGC ( device, "EFI %s image path ",
+			       efi_handle_name ( device ) );
+			DBGC ( device, " \"%s\" is not a file path\n",
+			       efi_devpath_text ( path ) );
+			rc = -ENOTTY;
+			goto err_not_filepath;
+		}
+		filepath = container_of ( path, FILEPATH_DEVICE_PATH, Header );
+
+		/* Find length of containing directory */
+		dirlen = ( ( ( ( path->Length[1] << 8 ) | path->Length[0] )
+			     - offsetof ( typeof ( *filepath ), PathName ) )
+			   / sizeof ( filepath->PathName[0] ) );
+		for ( ; dirlen ; dirlen-- ) {
+			if ( filepath->PathName[ dirlen - 1 ] == L'\\' )
+				break;
+		}
+
+	} else {
+
+		/* Use root directory */
+		filepath = NULL;
+		dirlen = 0;
+	}
+
+	/* Allocate filename */
+	len = ( ( dirlen * sizeof ( wname[0] ) ) + sizeof ( efi_autoexec_wname ) );
+	wname = malloc ( len );
+	if ( ! wname ) {
+		rc = -ENOMEM;
+		goto err_wname;
+	}
+	memcpy ( wname, filepath->PathName, ( dirlen * sizeof ( wname[0] ) ) );
+	memcpy ( &wname[dirlen], efi_autoexec_wname, sizeof ( efi_autoexec_wname ) );
+
 	/* Open simple file system protocol */
 	if ( ( efirc = bs->OpenProtocol ( device,
 					  &efi_simple_file_system_protocol_guid,
@@ -98,12 +145,11 @@
 	}
 
 	/* Open autoexec script */
-	if ( ( efirc = root->Open ( root, &file, efi_autoexec_wname,
+	if ( ( efirc = root->Open ( root, &file, wname,
 				    EFI_FILE_MODE_READ, 0 ) ) != 0 ) {
 		rc = -EEFI ( efirc );
 		DBGC ( device, "EFI %s has no %ls: %s\n",
-		       efi_handle_name ( device ), efi_autoexec_wname,
-		       strerror ( rc ) );
+		       efi_handle_name ( device ), wname, strerror ( rc ) );
 		goto err_open;
 	}
 
@@ -113,8 +159,7 @@
 				       &info ) ) != 0 ) {
 		rc = -EEFI ( efirc );
 		DBGC ( device, "EFI %s could not get %ls info: %s\n",
-		       efi_handle_name ( device ), efi_autoexec_wname,
-		       strerror ( rc ) );
+		       efi_handle_name ( device ), wname, strerror ( rc ) );
 		goto err_getinfo;
 	}
 	size = info.info.FileSize;
@@ -123,7 +168,7 @@
 	if ( ! size ) {
 		rc = -EINVAL;
 		DBGC ( device, "EFI %s has zero-length %ls\n",
-		       efi_handle_name ( device ), efi_autoexec_wname );
+		       efi_handle_name ( device ), wname );
 		goto err_empty;
 	}
 
@@ -132,8 +177,7 @@
 					  &data ) ) != 0 ) {
 		rc = -EEFI ( efirc );
 		DBGC ( device, "EFI %s could not allocate %ls: %s\n",
-		       efi_handle_name ( device ), efi_autoexec_wname,
-		       strerror ( rc ) );
+		       efi_handle_name ( device ), wname, strerror ( rc ) );
 		goto err_alloc;
 	}
 
@@ -141,8 +185,7 @@
 	if ( ( efirc = file->Read ( file, &size, data ) ) != 0 ) {
 		rc = -EEFI ( efirc );
 		DBGC ( device, "EFI %s could not read %ls: %s\n",
-		       efi_handle_name ( device ), efi_autoexec_wname,
-		       strerror ( rc ) );
+		       efi_handle_name ( device ), wname, strerror ( rc ) );
 		goto err_read;
 	}
 
@@ -150,8 +193,7 @@
 	efi_autoexec = data;
 	efi_autoexec_len = size;
 	data = NULL;
-	DBGC ( device, "EFI %s found %ls\n",
-	       efi_handle_name ( device ), efi_autoexec_wname );
+	DBGC ( device, "EFI %s found %ls\n", efi_handle_name ( device ), wname );
 
 	/* Success */
 	rc = 0;
@@ -169,6 +211,9 @@
 	bs->CloseProtocol ( device, &efi_simple_file_system_protocol_guid,
 			    efi_image_handle, device );
  err_filesystem:
+	free ( wname );
+ err_wname:
+ err_not_filepath:
 	return rc;
 }
 
@@ -345,17 +390,23 @@
  * Load autoexec script
  *
  * @v device		Device handle
+ * @v path		Image path within device handle
  * @ret rc		Return status code
  */
-int efi_autoexec_load ( EFI_HANDLE device ) {
+int efi_autoexec_load ( EFI_HANDLE device,
+			EFI_DEVICE_PATH_PROTOCOL *path ) {
 	int rc;
 
 	/* Sanity check */
 	assert ( efi_autoexec == NULL );
 	assert ( efi_autoexec_len == 0 );
 
-	/* Try loading from file system, if supported */
-	if ( ( rc = efi_autoexec_filesystem ( device ) ) == 0 )
+	/* Try loading from file system loaded image directory, if supported */
+	if ( ( rc = efi_autoexec_filesystem ( device, path ) ) == 0 )
+		return 0;
+
+	/* Try loading from file system root directory, if supported */
+	if ( ( rc = efi_autoexec_filesystem ( device, NULL ) ) == 0 )
 		return 0;
 
 	/* Try loading via TFTP, if supported */
diff --git a/src/interface/efi/efiprefix.c b/src/interface/efi/efiprefix.c
index bdc36d9..2611606 100644
--- a/src/interface/efi/efiprefix.c
+++ b/src/interface/efi/efiprefix.c
@@ -78,16 +78,17 @@
  */
 static void efi_init_application ( void ) {
 	EFI_HANDLE device = efi_loaded_image->DeviceHandle;
-	EFI_DEVICE_PATH_PROTOCOL *path = efi_loaded_image_path;
+	EFI_DEVICE_PATH_PROTOCOL *devpath = efi_loaded_image_path;
+	EFI_DEVICE_PATH_PROTOCOL *filepath = efi_loaded_image->FilePath;
 
 	/* Identify autoboot device, if any */
-	efi_set_autoboot_ll_addr ( device, path );
+	efi_set_autoboot_ll_addr ( device, devpath );
 
 	/* Store cached DHCP packet, if any */
-	efi_cachedhcp_record ( device, path );
+	efi_cachedhcp_record ( device, devpath );
 
 	/* Load autoexec script, if any */
-	efi_autoexec_load ( device );
+	efi_autoexec_load ( device, filepath );
 }
 
 /** EFI application initialisation function */