From 9913a405ea151706770559d42e758bc2c1da8525 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 14 Mar 2016 15:23:42 +0000 Subject: [PATCH] [efi] Provide access to files stored on EFI filesystems Provide access to local files via the "file://" URI scheme. There are three syntaxes: - An opaque URI with a relative path (e.g. "file:script.ipxe"). This will be interpreted as a path relative to the iPXE binary. - A hierarchical URI with a non-network absolute path (e.g. "file:/boot/script.ipxe"). This will be interpreted as a path relative to the root of the filesystem from which the iPXE binary was loaded. - A hierarchical URI with a network path in which the authority is a volume label (e.g. "file://bootdisk/script.ipxe"). This will be interpreted as a path relative to the root of the filesystem with the specified volume label. Note that the potentially desirable shell mappings (e.g. "fs0:" and "blk0:") are concepts internal to the UEFI shell binary, and do not seem to be exposed in any way to external executables. The old EFI_SHELL_PROTOCOL (which did provide access to these mappings) is no longer installed by current versions of the UEFI shell. Signed-off-by: Michael Brown --- src/config/config_efi.c | 4 + src/config/defaults/efi.h | 2 + src/config/general.h | 1 + src/include/ipxe/errfile.h | 1 + src/interface/efi/efi_local.c | 573 ++++++++++++++++++++++++++++++++++ 5 files changed, 581 insertions(+) create mode 100644 src/interface/efi/efi_local.c diff --git a/src/config/config_efi.c b/src/config/config_efi.c index 1f73dad4..92678d12 100644 --- a/src/config/config_efi.c +++ b/src/config/config_efi.c @@ -21,6 +21,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); +#include #include /** @file @@ -45,3 +46,6 @@ REQUIRE_OBJECT ( efi_fbcon ); #ifdef CONSOLE_FRAMEBUFFER REQUIRE_OBJECT ( efi_fbcon ); #endif +#ifdef DOWNLOAD_PROTO_FILE +REQUIRE_OBJECT ( efi_local ); +#endif diff --git a/src/config/defaults/efi.h b/src/config/defaults/efi.h index 24b93a02..ea9c31e2 100644 --- a/src/config/defaults/efi.h +++ b/src/config/defaults/efi.h @@ -24,6 +24,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define TIME_EFI #define REBOOT_EFI +#define DOWNLOAD_PROTO_FILE /* Local filesystem access */ + #define IMAGE_EFI /* EFI image support */ #define IMAGE_SCRIPT /* iPXE script image support */ diff --git a/src/config/general.h b/src/config/general.h index e9b781fb..675c11e0 100644 --- a/src/config/general.h +++ b/src/config/general.h @@ -57,6 +57,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #undef DOWNLOAD_PROTO_FTP /* File Transfer Protocol */ #undef DOWNLOAD_PROTO_SLAM /* Scalable Local Area Multicast */ #undef DOWNLOAD_PROTO_NFS /* Network File System Protocol */ +//#undef DOWNLOAD_PROTO_FILE /* Local filesystem access */ /* * SAN boot protocols diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 4129861a..63893157 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -347,6 +347,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_efi_pxe ( ERRFILE_OTHER | 0x004a0000 ) #define ERRFILE_efi_usb ( ERRFILE_OTHER | 0x004b0000 ) #define ERRFILE_efi_fbcon ( ERRFILE_OTHER | 0x004c0000 ) +#define ERRFILE_efi_local ( ERRFILE_OTHER | 0x004d0000 ) /** @} */ diff --git a/src/interface/efi/efi_local.c b/src/interface/efi/efi_local.c new file mode 100644 index 00000000..bd010ad2 --- /dev/null +++ b/src/interface/efi/efi_local.c @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2016 Michael Brown . + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * EFI local file access + * + */ + +/** Download blocksize */ +#define EFI_LOCAL_BLKSIZE 4096 + +/** An EFI local file */ +struct efi_local { + /** Reference count */ + struct refcnt refcnt; + /** Data transfer interface */ + struct interface xfer; + /** Download process */ + struct process process; + + /** EFI root directory */ + EFI_FILE_PROTOCOL *root; + /** EFI file */ + EFI_FILE_PROTOCOL *file; + /** Length of file */ + size_t len; +}; + +/** + * Close local file + * + * @v local Local file + * @v rc Reason for close + */ +static void efi_local_close ( struct efi_local *local, int rc ) { + + /* Stop process */ + process_del ( &local->process ); + + /* Shut down data transfer interface */ + intf_shutdown ( &local->xfer, rc ); + + /* Close EFI file */ + if ( local->file ) { + local->file->Close ( local->file ); + local->file = NULL; + } + + /* Close EFI root directory */ + if ( local->root ) { + local->root->Close ( local->root ); + local->root = NULL; + } +} + +/** + * Local file process + * + * @v local Local file + */ +static void efi_local_step ( struct efi_local *local ) { + EFI_FILE_PROTOCOL *file = local->file; + struct io_buffer *iobuf = NULL; + size_t remaining; + size_t frag_len; + UINTN size; + EFI_STATUS efirc; + int rc; + + /* Wait until data transfer interface is ready */ + if ( ! xfer_window ( &local->xfer ) ) + return; + + /* Presize receive buffer */ + remaining = local->len; + xfer_seek ( &local->xfer, remaining ); + xfer_seek ( &local->xfer, 0 ); + + /* Get file contents */ + while ( remaining ) { + + /* Calculate length for this fragment */ + frag_len = remaining; + if ( frag_len > EFI_LOCAL_BLKSIZE ) + frag_len = EFI_LOCAL_BLKSIZE; + + /* Allocate I/O buffer */ + iobuf = xfer_alloc_iob ( &local->xfer, frag_len ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err; + } + + /* Read block */ + size = frag_len; + if ( ( efirc = file->Read ( file, &size, iobuf->data ) ) != 0 ){ + rc = -EEFI ( efirc ); + DBGC ( local, "LOCAL %p could not read from file: %s\n", + local, strerror ( rc ) ); + goto err; + } + assert ( size <= frag_len ); + iob_put ( iobuf, size ); + + /* Deliver data */ + if ( ( rc = xfer_deliver_iob ( &local->xfer, + iob_disown ( iobuf ) ) ) != 0 ) { + DBGC ( local, "LOCAL %p could not deliver data: %s\n", + local, strerror ( rc ) ); + goto err; + } + + /* Move to next block */ + remaining -= frag_len; + } + + /* Close download */ + efi_local_close ( local, 0 ); + + return; + + err: + free_iob ( iobuf ); + efi_local_close ( local, rc ); +} + +/** Data transfer interface operations */ +static struct interface_operation efi_local_operations[] = { + INTF_OP ( xfer_window_changed, struct efi_local *, efi_local_step ), + INTF_OP ( intf_close, struct efi_local *, efi_local_close ), +}; + +/** Data transfer interface descriptor */ +static struct interface_descriptor efi_local_xfer_desc = + INTF_DESC ( struct efi_local, xfer, efi_local_operations ); + +/** Process descriptor */ +static struct process_descriptor efi_local_process_desc = + PROC_DESC_ONCE ( struct efi_local, process, efi_local_step ); + +/** + * Check for matching volume name + * + * @v local Local file + * @v device Device handle + * @v root Root filesystem handle + * @v volume Volume name + * @ret rc Return status code + */ +static int efi_local_check_volume_name ( struct efi_local *local, + EFI_HANDLE device, + EFI_FILE_PROTOCOL *root, + const char *volume ) { + EFI_FILE_SYSTEM_INFO *info; + UINTN size; + char *label; + EFI_STATUS efirc; + int rc; + + /* Get length of file system information */ + size = 0; + root->GetInfo ( root, &efi_file_system_info_id, &size, NULL ); + + /* Allocate file system information */ + info = malloc ( size ); + if ( ! info ) { + rc = -ENOMEM; + goto err_alloc_info; + } + + /* Get file system information */ + if ( ( efirc = root->GetInfo ( root, &efi_file_system_info_id, &size, + info ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( local, "LOCAL %p could not get file system info on %s: " + "%s\n", local, efi_handle_name ( device ), + strerror ( rc ) ); + goto err_get_info; + } + DBGC2 ( local, "LOCAL %p found %s with label \"%ls\"\n", + local, efi_handle_name ( device ), info->VolumeLabel ); + + /* Construct volume label for comparison */ + if ( asprintf ( &label, "%ls", info->VolumeLabel ) < 0 ) { + rc = -ENOMEM; + goto err_alloc_label; + } + + /* Compare volume label */ + if ( strcasecmp ( volume, label ) != 0 ) { + rc = -ENOENT; + goto err_compare; + } + + /* Success */ + rc = 0; + + err_compare: + free ( label ); + err_alloc_label: + err_get_info: + free ( info ); + err_alloc_info: + return rc; +} + +/** + * Open root filesystem + * + * @v local Local file + * @v device Device handle + * @v root Root filesystem handle to fill in + * @ret rc Return status code + */ +static int efi_local_open_root ( struct efi_local *local, EFI_HANDLE device, + EFI_FILE_PROTOCOL **root ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + void *interface; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs; + } u; + EFI_STATUS efirc; + int rc; + + /* Open file system protocol */ + if ( ( efirc = bs->OpenProtocol ( device, + &efi_simple_file_system_protocol_guid, + &u.interface, efi_image_handle, + device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( local, "LOCAL %p could not open filesystem on %s: %s\n", + local, efi_handle_name ( device ), strerror ( rc ) ); + goto err_filesystem; + } + + /* Open root directory */ + if ( ( efirc = u.fs->OpenVolume ( u.fs, root ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( local, "LOCAL %p could not open volume on %s: %s\n", + local, efi_handle_name ( device ), strerror ( rc ) ); + goto err_volume; + } + + /* Success */ + rc = 0; + + err_volume: + bs->CloseProtocol ( device, &efi_simple_file_system_protocol_guid, + efi_image_handle, device ); + err_filesystem: + return rc; +} + +/** + * Open root filesystem of specified volume + * + * @v local Local file + * @v volume Volume name, or NULL to use loaded image's device + * @ret rc Return status code + */ +static int efi_local_open_volume ( struct efi_local *local, + const char *volume ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_GUID *protocol = &efi_simple_file_system_protocol_guid; + int ( * check ) ( struct efi_local *local, EFI_HANDLE device, + EFI_FILE_PROTOCOL *root, const char *volume ); + EFI_FILE_PROTOCOL *root; + EFI_HANDLE *handles; + EFI_HANDLE device; + UINTN num_handles; + UINTN i; + EFI_STATUS efirc; + int rc; + + /* Identify candidate handles */ + if ( volume ) { + /* Locate all filesystem handles */ + if ( ( efirc = bs->LocateHandleBuffer ( ByProtocol, protocol, + NULL, &num_handles, + &handles ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( local, "LOCAL %p could not enumerate handles: " + "%s\n", local, strerror ( rc ) ); + return rc; + } + check = efi_local_check_volume_name; + } else { + /* Use our loaded image's device handle */ + handles = &efi_loaded_image->DeviceHandle; + num_handles = 1; + check = NULL; + } + + /* Find matching handle */ + for ( i = 0 ; i < num_handles ; i++ ) { + + /* Get this device handle */ + device = handles[i]; + + /* Open root directory */ + if ( ( rc = efi_local_open_root ( local, device, &root ) ) != 0) + continue; + + /* Check volume name, if applicable */ + if ( ( check == NULL ) || + ( ( rc = check ( local, device, root, volume ) ) == 0 ) ) { + DBGC ( local, "LOCAL %p using %s", + local, efi_handle_name ( device ) ); + if ( volume ) + DBGC ( local, " with label \"%s\"", volume ); + DBGC ( local, "\n" ); + local->root = root; + break; + } + + /* Close root directory */ + root->Close ( root ); + } + + /* Free handles, if applicable */ + if ( volume ) + bs->FreePool ( handles ); + + /* Fail if we found no matching handle */ + if ( ! local->root ) { + DBGC ( local, "LOCAL %p found no matching handle\n", local ); + return -ENOENT; + } + + return 0; +} + +/** + * Open fully-resolved path + * + * @v local Local file + * @v resolved Resolved path + * @ret rc Return status code + */ +static int efi_local_open_resolved ( struct efi_local *local, + const char *resolved ) { + size_t name_len = strlen ( resolved ); + CHAR16 name[ name_len + 1 /* wNUL */ ]; + EFI_FILE_PROTOCOL *file; + EFI_STATUS efirc; + int rc; + + /* Construct filename */ + efi_snprintf ( name, ( name_len + 1 /* wNUL */ ), "%s", resolved ); + + /* Open file */ + if ( ( efirc = local->root->Open ( local->root, &file, name, + EFI_FILE_MODE_READ, 0 ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( local, "LOCAL %p could not open \"%s\": %s\n", + local, resolved, strerror ( rc ) ); + return rc; + } + local->file = file; + + return 0; +} + +/** + * Open specified path + * + * @v local Local file + * @v path Path to file + * @ret rc Return status code + */ +static int efi_local_open_path ( struct efi_local *local, const char *path ) { + FILEPATH_DEVICE_PATH *fp = container_of ( efi_loaded_image->FilePath, + FILEPATH_DEVICE_PATH, Header); + size_t fp_len = ( fp ? efi_devpath_len ( &fp->Header ) : 0 ); + char base[ fp_len / 2 /* Cannot exceed this length */ ]; + size_t remaining = sizeof ( base ); + size_t len; + char *resolved; + char *tmp; + int rc; + + /* Construct base path to our own image, if possible */ + memset ( base, 0, sizeof ( base ) ); + tmp = base; + while ( fp && ( fp->Header.Type != END_DEVICE_PATH_TYPE ) ) { + len = snprintf ( tmp, remaining, "%ls", fp->PathName ); + assert ( len < remaining ); + tmp += len; + remaining -= len; + fp = ( ( ( void * ) fp ) + ( ( fp->Header.Length[1] << 8 ) | + fp->Header.Length[0] ) ); + } + DBGC2 ( local, "LOCAL %p base path \"%s\"\n", + local, base ); + + /* Convert to sane path separators */ + for ( tmp = base ; *tmp ; tmp++ ) { + if ( *tmp == '\\' ) + *tmp = '/'; + } + + /* Resolve path */ + resolved = resolve_path ( base, path ); + if ( ! resolved ) { + rc = -ENOMEM; + goto err_resolve; + } + + /* Convert to insane path separators */ + for ( tmp = resolved ; *tmp ; tmp++ ) { + if ( *tmp == '/' ) + *tmp = '\\'; + } + DBGC ( local, "LOCAL %p using \"%s\"\n", + local, resolved ); + + /* Open resolved path */ + if ( ( rc = efi_local_open_resolved ( local, resolved ) ) != 0 ) + goto err_open; + + err_open: + free ( resolved ); + err_resolve: + return rc; +} + +/** + * Get file length + * + * @v local Local file + * @ret rc Return status code + */ +static int efi_local_len ( struct efi_local *local ) { + EFI_FILE_PROTOCOL *file = local->file; + EFI_FILE_INFO *info; + EFI_STATUS efirc; + UINTN size; + int rc; + + /* Get size of file information */ + size = 0; + file->GetInfo ( file, &efi_file_info_id, &size, NULL ); + + /* Allocate file information */ + info = malloc ( size ); + if ( ! info ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Get file information */ + if ( ( efirc = file->GetInfo ( file, &efi_file_info_id, &size, + info ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( local, "LOCAL %p could not get file info: %s\n", + local, strerror ( rc ) ); + goto err_info; + } + + /* Record file length */ + local->len = info->FileSize; + + /* Success */ + rc = 0; + + err_info: + free ( info ); + err_alloc: + return rc; +} + +/** + * Open local file + * + * @v xfer Data transfer interface + * @v uri Request URI + * @ret rc Return status code + */ +static int efi_local_open ( struct interface *xfer, struct uri *uri ) { + struct efi_local *local; + const char *volume; + const char *path; + int rc; + + /* Parse URI */ + volume = ( ( uri->host && uri->host[0] ) ? uri->host : NULL ); + path = ( uri->opaque ? uri->opaque : uri->path ); + + /* Allocate and initialise structure */ + local = zalloc ( sizeof ( *local ) ); + if ( ! local ) { + rc = -ENOMEM; + goto err_alloc; + } + ref_init ( &local->refcnt, NULL ); + intf_init ( &local->xfer, &efi_local_xfer_desc, &local->refcnt ); + process_init ( &local->process, &efi_local_process_desc, + &local->refcnt ); + + /* Open specified volume */ + if ( ( rc = efi_local_open_volume ( local, volume ) ) != 0 ) + goto err_open_root; + + /* Open specified path */ + if ( ( rc = efi_local_open_path ( local, path ) ) != 0 ) + goto err_open_file; + + /* Get length of file */ + if ( ( rc = efi_local_len ( local ) ) != 0 ) + goto err_len; + + /* Attach to parent interface, mortalise self, and return */ + intf_plug_plug ( &local->xfer, xfer ); + ref_put ( &local->refcnt ); + return 0; + + err_len: + err_open_file: + err_open_root: + efi_local_close ( local, 0 ); + ref_put ( &local->refcnt ); + err_alloc: + return rc; +} + +/** EFI local file URI opener */ +struct uri_opener efi_local_uri_opener __uri_opener = { + .scheme = "file", + .open = efi_local_open, +};