aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2021-05-18 14:03:15 +0100
committerMichael Brown <mcb30@ipxe.org>2021-05-21 16:32:36 +0100
commitef9953b7122937dc12762e9f5ce797ba6859a24e (patch)
treeb8f8a837694c97bec80cd89beb5dcde0fc21bce3
parentbfca3db41e9af78e56e7a9b7f30121df83b6af0a (diff)
downloadipxe-ef9953b7122937dc12762e9f5ce797ba6859a24e.zip
ipxe-ef9953b7122937dc12762e9f5ce797ba6859a24e.tar.gz
ipxe-ef9953b7122937dc12762e9f5ce797ba6859a24e.tar.bz2
[efi] Allow for non-image-backed virtual files
Restructure the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL implementation to allow for the existence of virtual files that are not simply backed by a single underlying image. Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r--src/interface/efi/efi_file.c290
1 files changed, 221 insertions, 69 deletions
diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c
index 52de098..c67fdea 100644
--- a/src/interface/efi/efi_file.c
+++ b/src/interface/efi/efi_file.c
@@ -50,19 +50,55 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** EFI media ID */
#define EFI_MEDIA_ID_MAGIC 0x69505845
-/** An image exposed as an EFI file */
+/** An EFI virtual file reader */
+struct efi_file_reader {
+ /** EFI file */
+ struct efi_file *file;
+ /** Position within virtual file */
+ size_t pos;
+ /** Output data buffer */
+ void *data;
+ /** Length of output data buffer */
+ size_t len;
+};
+
+/** An EFI file */
struct efi_file {
+ /** Reference count */
+ struct refcnt refcnt;
/** EFI file protocol */
EFI_FILE_PROTOCOL file;
- /** Image */
+ /** Image (if any) */
struct image *image;
+ /** Filename */
+ const char *name;
/** Current file position */
size_t pos;
+ /**
+ * Read from file
+ *
+ * @v reader File reader
+ * @ret len Length read
+ */
+ size_t ( * read ) ( struct efi_file_reader *reader );
};
static struct efi_file efi_file_root;
/**
+ * Free EFI file
+ *
+ * @v refcnt Reference count
+ */
+static void efi_file_free ( struct refcnt *refcnt ) {
+ struct efi_file *file =
+ container_of ( refcnt, struct efi_file, refcnt );
+
+ image_put ( file->image );
+ free ( file );
+}
+
+/**
* Get EFI file name (for debugging)
*
* @v file EFI file
@@ -70,28 +106,140 @@ static struct efi_file efi_file_root;
*/
static const char * efi_file_name ( struct efi_file *file ) {
- return ( file->image ? file->image->name : "<root>" );
+ return ( file == &efi_file_root ? "<root>" : file->name );
}
/**
* Find EFI file image
*
- * @v wname Filename
+ * @v name Filename
* @ret image Image, or NULL
*/
-static struct image * efi_file_find ( const CHAR16 *wname ) {
- char name[ wcslen ( wname ) + 1 /* NUL */ ];
+static struct image * efi_file_find ( const char *name ) {
struct image *image;
/* Find image */
- snprintf ( name, sizeof ( name ), "%ls", wname );
list_for_each_entry ( image, &images, list ) {
if ( strcasecmp ( image->name, name ) == 0 )
return image;
}
return NULL;
+}
+
+/**
+ * Get length of EFI file
+ *
+ * @v file EFI file
+ * @ret len Length of file
+ */
+static size_t efi_file_len ( struct efi_file *file ) {
+ struct efi_file_reader reader;
+
+ /* If this is the root directory, then treat as length zero */
+ if ( ! file->read )
+ return 0;
+
+ /* Initialise reader */
+ reader.file = file;
+ reader.pos = 0;
+ reader.data = NULL;
+ reader.len = 0;
+
+ /* Perform dummy read to determine file length */
+ file->read ( &reader );
+
+ return reader.pos;
+}
+
+/**
+ * Read chunk of EFI file
+ *
+ * @v reader EFI file reader
+ * @v data Input data, or UNULL to zero-fill
+ * @v len Length of input data
+ * @ret len Length of output data
+ */
+static size_t efi_file_read_chunk ( struct efi_file_reader *reader,
+ userptr_t data, size_t len ) {
+ struct efi_file *file = reader->file;
+ size_t offset;
+
+ /* Calculate offset into input data */
+ offset = ( file->pos - reader->pos );
+
+ /* Consume input data range */
+ reader->pos += len;
+
+ /* Calculate output length */
+ if ( offset < len ) {
+ len -= offset;
+ } else {
+ len = 0;
+ }
+ if ( len > reader->len )
+ len = reader->len;
+
+ /* Copy or zero output data */
+ if ( data ) {
+ copy_from_user ( reader->data, data, offset, len );
+ } else {
+ memset ( reader->data, 0, len );
+ }
+
+ /* Consume output buffer */
+ file->pos += len;
+ reader->data += len;
+ reader->len -= len;
+
+ return len;
+}
+
+/**
+ * Read from image-backed file
+ *
+ * @v reader EFI file reader
+ * @ret len Length read
+ */
+static size_t efi_file_read_image ( struct efi_file_reader *reader ) {
+ struct efi_file *file = reader->file;
+ struct image *image = file->image;
+
+ /* Read from file */
+ return efi_file_read_chunk ( reader, image->data, image->len );
+}
+
+/**
+ * Open fixed file
+ *
+ * @v file EFI file
+ * @v new New EFI file
+ * @ret efirc EFI status code
+ */
+static EFI_STATUS efi_file_open_fixed ( struct efi_file *file,
+ EFI_FILE_PROTOCOL **new ) {
+
+ /* Increment reference count */
+ ref_get ( &file->refcnt );
+
+ /* Return opened file */
+ *new = &file->file;
+
+ DBGC ( file, "EFIFILE %s opened\n", efi_file_name ( file ) );
+ return 0;
+}
+/**
+ * Associate file with image
+ *
+ * @v file EFI file
+ * @v image Image
+ */
+static void efi_file_image ( struct efi_file *file, struct image *image ) {
+
+ file->image = image;
+ file->name = image->name;
+ file->read = efi_file_read_image;
}
/**
@@ -106,50 +254,56 @@ static struct image * efi_file_find ( const CHAR16 *wname ) {
*/
static EFI_STATUS EFIAPI
efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
- CHAR16 *wname, UINT64 mode __unused,
- UINT64 attributes __unused ) {
+ CHAR16 *wname, UINT64 mode, UINT64 attributes __unused ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
+ char buf[ wcslen ( wname ) + 1 /* NUL */ ];
struct efi_file *new_file;
struct image *image;
+ char *name;
+
+ /* Convert name to ASCII */
+ snprintf ( buf, sizeof ( buf ), "%ls", wname );
+ name = buf;
/* Initial '\' indicates opening from the root directory */
- while ( *wname == L'\\' ) {
+ while ( *name == '\\' ) {
file = &efi_file_root;
- wname++;
+ name++;
}
/* Allow root directory itself to be opened */
- if ( ( wname[0] == L'\0' ) || ( wname[0] == L'.' ) ) {
- *new = &efi_file_root.file;
- return 0;
- }
+ if ( ( name[0] == '\0' ) || ( name[0] == '.' ) )
+ return efi_file_open_fixed ( &efi_file_root, new );
/* Fail unless opening from the root */
- if ( file->image ) {
+ if ( file != &efi_file_root ) {
DBGC ( file, "EFIFILE %s is not a directory\n",
efi_file_name ( file ) );
return EFI_NOT_FOUND;
}
- /* Identify image */
- image = efi_file_find ( wname );
- if ( ! image ) {
- DBGC ( file, "EFIFILE \"%ls\" does not exist\n", wname );
- return EFI_NOT_FOUND;
- }
-
/* Fail unless opening read-only */
if ( mode != EFI_FILE_MODE_READ ) {
DBGC ( file, "EFIFILE %s cannot be opened in mode %#08llx\n",
- image->name, mode );
+ name, mode );
return EFI_WRITE_PROTECTED;
}
+ /* Identify image */
+ image = efi_file_find ( name );
+ if ( ! image ) {
+ DBGC ( file, "EFIFILE %s does not exist\n", name );
+ return EFI_NOT_FOUND;
+ }
+
/* Allocate and initialise file */
new_file = zalloc ( sizeof ( *new_file ) );
+ if ( ! new_file )
+ return EFI_OUT_OF_RESOURCES;
+ ref_init ( &file->refcnt, efi_file_free );
memcpy ( &new_file->file, &efi_file_root.file,
sizeof ( new_file->file ) );
- new_file->image = image_get ( image );
+ efi_file_image ( new_file, image_get ( image ) );
*new = &new_file->file;
DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_file ) );
@@ -165,14 +319,9 @@ efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
static EFI_STATUS EFIAPI efi_file_close ( EFI_FILE_PROTOCOL *this ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
- /* Do nothing if this is the root */
- if ( ! file->image )
- return 0;
-
/* Close file */
DBGC ( file, "EFIFILE %s closed\n", efi_file_name ( file ) );
- image_put ( file->image );
- free ( file );
+ ref_put ( &file->refcnt );
return 0;
}
@@ -229,30 +378,29 @@ static EFI_STATUS efi_file_varlen ( UINT64 *base, size_t base_len,
/**
* Return file information structure
*
- * @v image Image, or NULL for the root directory
+ * @v file EFI file
* @v len Length of data buffer
* @v data Data buffer
* @ret efirc EFI status code
*/
-static EFI_STATUS efi_file_info ( struct image *image, UINTN *len,
+static EFI_STATUS efi_file_info ( struct efi_file *file, UINTN *len,
VOID *data ) {
EFI_FILE_INFO info;
- const char *name;
+ size_t file_len;
+
+ /* Get file length */
+ file_len = efi_file_len ( file );
/* Populate file information */
memset ( &info, 0, sizeof ( info ) );
- if ( image ) {
- info.FileSize = image->len;
- info.PhysicalSize = image->len;
- info.Attribute = EFI_FILE_READ_ONLY;
- name = image->name;
- } else {
- info.Attribute = ( EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY );
- name = "";
- }
-
- return efi_file_varlen ( &info.Size, SIZE_OF_EFI_FILE_INFO, name,
- len, data );
+ info.FileSize = file_len;
+ info.PhysicalSize = file_len;
+ info.Attribute = EFI_FILE_READ_ONLY;
+ if ( file == &efi_file_root )
+ info.Attribute |= EFI_FILE_DIRECTORY;
+
+ return efi_file_varlen ( &info.Size, SIZE_OF_EFI_FILE_INFO,
+ file->name, len, data );
}
/**
@@ -266,14 +414,16 @@ static EFI_STATUS efi_file_info ( struct image *image, UINTN *len,
static EFI_STATUS efi_file_read_dir ( struct efi_file *file, UINTN *len,
VOID *data ) {
EFI_STATUS efirc;
+ struct efi_file entry;
struct image *image;
unsigned int index;
- /* Construct directory entry at current position */
+ /* Construct directory entries for image-backed files */
index = file->pos;
for_each_image ( image ) {
if ( index-- == 0 ) {
- efirc = efi_file_info ( image, len, data );
+ efi_file_image ( &entry, image );
+ efirc = efi_file_info ( &entry, len, data );
if ( efirc == 0 )
file->pos++;
return efirc;
@@ -296,21 +446,25 @@ static EFI_STATUS efi_file_read_dir ( struct efi_file *file, UINTN *len,
static EFI_STATUS EFIAPI efi_file_read ( EFI_FILE_PROTOCOL *this,
UINTN *len, VOID *data ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
- size_t remaining;
+ struct efi_file_reader reader;
+ size_t pos = file->pos;
/* If this is the root directory, then construct a directory entry */
- if ( ! file->image )
+ if ( ! file->read )
return efi_file_read_dir ( file, len, data );
+ /* Initialise reader */
+ reader.file = file;
+ reader.pos = 0;
+ reader.data = data;
+ reader.len = *len;
+
/* Read from the file */
- remaining = ( file->image->len - file->pos );
- if ( *len > remaining )
- *len = remaining;
DBGC ( file, "EFIFILE %s read [%#08zx,%#08zx)\n",
- efi_file_name ( file ), file->pos,
- ( ( size_t ) ( file->pos + *len ) ) );
- copy_from_user ( data, file->image->data, file->pos, *len );
- file->pos += *len;
+ efi_file_name ( file ), pos, file->pos );
+ *len = file->read ( &reader );
+ assert ( ( pos + *len ) == file->pos );
+
return 0;
}
@@ -342,24 +496,21 @@ static EFI_STATUS EFIAPI efi_file_write ( EFI_FILE_PROTOCOL *this,
static EFI_STATUS EFIAPI efi_file_set_position ( EFI_FILE_PROTOCOL *this,
UINT64 position ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
+ size_t len;
- /* If this is the root directory, reset to the start */
- if ( ! file->image ) {
- DBGC ( file, "EFIFILE root directory rewound\n" );
- file->pos = 0;
- return 0;
- }
+ /* Get file length */
+ len = efi_file_len ( file );
/* Check for the magic end-of-file value */
if ( position == 0xffffffffffffffffULL )
- position = file->image->len;
+ position = len;
/* Fail if we attempt to seek past the end of the file (since
* we do not support writes).
*/
- if ( position > file->image->len ) {
+ if ( position > len ) {
DBGC ( file, "EFIFILE %s cannot seek to %#08llx of %#08zx\n",
- efi_file_name ( file ), position, file->image->len );
+ efi_file_name ( file ), position, len );
return EFI_UNSUPPORTED;
}
@@ -408,7 +559,7 @@ static EFI_STATUS EFIAPI efi_file_get_info ( EFI_FILE_PROTOCOL *this,
/* Get file information */
DBGC ( file, "EFIFILE %s get file information\n",
efi_file_name ( file ) );
- return efi_file_info ( file->image, len, data );
+ return efi_file_info ( file, len, data );
} else if ( memcmp ( type, &efi_file_system_info_id,
sizeof ( *type ) ) == 0 ) {
@@ -468,6 +619,7 @@ static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) {
/** Root directory */
static struct efi_file efi_file_root = {
+ .refcnt = REF_INIT ( ref_no_free ),
.file = {
.Revision = EFI_FILE_PROTOCOL_REVISION,
.Open = efi_file_open,
@@ -482,6 +634,7 @@ static struct efi_file efi_file_root = {
.Flush = efi_file_flush,
},
.image = NULL,
+ .name = "",
};
/**
@@ -496,8 +649,7 @@ efi_file_open_volume ( EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *filesystem __unused,
EFI_FILE_PROTOCOL **file ) {
DBGC ( &efi_file_root, "EFIFILE open volume\n" );
- *file = &efi_file_root.file;
- return 0;
+ return efi_file_open_fixed ( &efi_file_root, file );
}
/** EFI simple file system protocol */