[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>
This commit is contained in:
Michael Brown
2021-05-18 14:03:15 +01:00
parent bfca3db41e
commit ef9953b712

View File

@@ -50,18 +50,54 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/** EFI media ID */ /** EFI media ID */
#define EFI_MEDIA_ID_MAGIC 0x69505845 #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 { struct efi_file {
/** Reference count */
struct refcnt refcnt;
/** EFI file protocol */ /** EFI file protocol */
EFI_FILE_PROTOCOL file; EFI_FILE_PROTOCOL file;
/** Image */ /** Image (if any) */
struct image *image; struct image *image;
/** Filename */
const char *name;
/** Current file position */ /** Current file position */
size_t pos; 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; 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) * Get EFI file name (for debugging)
* *
@@ -70,28 +106,140 @@ static struct efi_file efi_file_root;
*/ */
static const char * efi_file_name ( struct efi_file *file ) { 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 * Find EFI file image
* *
* @v wname Filename * @v name Filename
* @ret image Image, or NULL * @ret image Image, or NULL
*/ */
static struct image * efi_file_find ( const CHAR16 *wname ) { static struct image * efi_file_find ( const char *name ) {
char name[ wcslen ( wname ) + 1 /* NUL */ ];
struct image *image; struct image *image;
/* Find image */ /* Find image */
snprintf ( name, sizeof ( name ), "%ls", wname );
list_for_each_entry ( image, &images, list ) { list_for_each_entry ( image, &images, list ) {
if ( strcasecmp ( image->name, name ) == 0 ) if ( strcasecmp ( image->name, name ) == 0 )
return image; return image;
} }
return NULL; 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 static EFI_STATUS EFIAPI
efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new, efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
CHAR16 *wname, UINT64 mode __unused, CHAR16 *wname, UINT64 mode, UINT64 attributes __unused ) {
UINT64 attributes __unused ) {
struct efi_file *file = container_of ( this, struct efi_file, file ); struct efi_file *file = container_of ( this, struct efi_file, file );
char buf[ wcslen ( wname ) + 1 /* NUL */ ];
struct efi_file *new_file; struct efi_file *new_file;
struct image *image; struct image *image;
char *name;
/* Convert name to ASCII */
snprintf ( buf, sizeof ( buf ), "%ls", wname );
name = buf;
/* Initial '\' indicates opening from the root directory */ /* Initial '\' indicates opening from the root directory */
while ( *wname == L'\\' ) { while ( *name == '\\' ) {
file = &efi_file_root; file = &efi_file_root;
wname++; name++;
} }
/* Allow root directory itself to be opened */ /* Allow root directory itself to be opened */
if ( ( wname[0] == L'\0' ) || ( wname[0] == L'.' ) ) { if ( ( name[0] == '\0' ) || ( name[0] == '.' ) )
*new = &efi_file_root.file; return efi_file_open_fixed ( &efi_file_root, new );
return 0;
}
/* Fail unless opening from the root */ /* Fail unless opening from the root */
if ( file->image ) { if ( file != &efi_file_root ) {
DBGC ( file, "EFIFILE %s is not a directory\n", DBGC ( file, "EFIFILE %s is not a directory\n",
efi_file_name ( file ) ); efi_file_name ( file ) );
return EFI_NOT_FOUND; 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 */ /* Fail unless opening read-only */
if ( mode != EFI_FILE_MODE_READ ) { if ( mode != EFI_FILE_MODE_READ ) {
DBGC ( file, "EFIFILE %s cannot be opened in mode %#08llx\n", DBGC ( file, "EFIFILE %s cannot be opened in mode %#08llx\n",
image->name, mode ); name, mode );
return EFI_WRITE_PROTECTED; 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 */ /* Allocate and initialise file */
new_file = zalloc ( sizeof ( *new_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, memcpy ( &new_file->file, &efi_file_root.file,
sizeof ( new_file->file ) ); sizeof ( new_file->file ) );
new_file->image = image_get ( image ); efi_file_image ( new_file, image_get ( image ) );
*new = &new_file->file; *new = &new_file->file;
DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_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 ) { static EFI_STATUS EFIAPI efi_file_close ( EFI_FILE_PROTOCOL *this ) {
struct efi_file *file = container_of ( this, struct efi_file, file ); 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 */ /* Close file */
DBGC ( file, "EFIFILE %s closed\n", efi_file_name ( file ) ); DBGC ( file, "EFIFILE %s closed\n", efi_file_name ( file ) );
image_put ( file->image ); ref_put ( &file->refcnt );
free ( file );
return 0; return 0;
} }
@@ -229,30 +378,29 @@ static EFI_STATUS efi_file_varlen ( UINT64 *base, size_t base_len,
/** /**
* Return file information structure * Return file information structure
* *
* @v image Image, or NULL for the root directory * @v file EFI file
* @v len Length of data buffer * @v len Length of data buffer
* @v data Data buffer * @v data Data buffer
* @ret efirc EFI status code * @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 ) { VOID *data ) {
EFI_FILE_INFO info; EFI_FILE_INFO info;
const char *name; size_t file_len;
/* Get file length */
file_len = efi_file_len ( file );
/* Populate file information */ /* Populate file information */
memset ( &info, 0, sizeof ( info ) ); memset ( &info, 0, sizeof ( info ) );
if ( image ) { info.FileSize = file_len;
info.FileSize = image->len; info.PhysicalSize = file_len;
info.PhysicalSize = image->len; info.Attribute = EFI_FILE_READ_ONLY;
info.Attribute = EFI_FILE_READ_ONLY; if ( file == &efi_file_root )
name = image->name; info.Attribute |= EFI_FILE_DIRECTORY;
} else {
info.Attribute = ( EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY );
name = "";
}
return efi_file_varlen ( &info.Size, SIZE_OF_EFI_FILE_INFO, name, return efi_file_varlen ( &info.Size, SIZE_OF_EFI_FILE_INFO,
len, data ); 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, static EFI_STATUS efi_file_read_dir ( struct efi_file *file, UINTN *len,
VOID *data ) { VOID *data ) {
EFI_STATUS efirc; EFI_STATUS efirc;
struct efi_file entry;
struct image *image; struct image *image;
unsigned int index; unsigned int index;
/* Construct directory entry at current position */ /* Construct directory entries for image-backed files */
index = file->pos; index = file->pos;
for_each_image ( image ) { for_each_image ( image ) {
if ( index-- == 0 ) { 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 ) if ( efirc == 0 )
file->pos++; file->pos++;
return efirc; 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, static EFI_STATUS EFIAPI efi_file_read ( EFI_FILE_PROTOCOL *this,
UINTN *len, VOID *data ) { UINTN *len, VOID *data ) {
struct efi_file *file = container_of ( this, struct efi_file, file ); 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 this is the root directory, then construct a directory entry */
if ( ! file->image ) if ( ! file->read )
return efi_file_read_dir ( file, len, data ); 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 */ /* Read from the file */
remaining = ( file->image->len - file->pos );
if ( *len > remaining )
*len = remaining;
DBGC ( file, "EFIFILE %s read [%#08zx,%#08zx)\n", DBGC ( file, "EFIFILE %s read [%#08zx,%#08zx)\n",
efi_file_name ( file ), file->pos, efi_file_name ( file ), pos, file->pos );
( ( size_t ) ( file->pos + *len ) ) ); *len = file->read ( &reader );
copy_from_user ( data, file->image->data, file->pos, *len ); assert ( ( pos + *len ) == file->pos );
file->pos += *len;
return 0; 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, static EFI_STATUS EFIAPI efi_file_set_position ( EFI_FILE_PROTOCOL *this,
UINT64 position ) { UINT64 position ) {
struct efi_file *file = container_of ( this, struct efi_file, file ); struct efi_file *file = container_of ( this, struct efi_file, file );
size_t len;
/* If this is the root directory, reset to the start */ /* Get file length */
if ( ! file->image ) { len = efi_file_len ( file );
DBGC ( file, "EFIFILE root directory rewound\n" );
file->pos = 0;
return 0;
}
/* Check for the magic end-of-file value */ /* Check for the magic end-of-file value */
if ( position == 0xffffffffffffffffULL ) if ( position == 0xffffffffffffffffULL )
position = file->image->len; position = len;
/* Fail if we attempt to seek past the end of the file (since /* Fail if we attempt to seek past the end of the file (since
* we do not support writes). * we do not support writes).
*/ */
if ( position > file->image->len ) { if ( position > len ) {
DBGC ( file, "EFIFILE %s cannot seek to %#08llx of %#08zx\n", 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; return EFI_UNSUPPORTED;
} }
@@ -408,7 +559,7 @@ static EFI_STATUS EFIAPI efi_file_get_info ( EFI_FILE_PROTOCOL *this,
/* Get file information */ /* Get file information */
DBGC ( file, "EFIFILE %s get file information\n", DBGC ( file, "EFIFILE %s get file information\n",
efi_file_name ( file ) ); 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, } else if ( memcmp ( type, &efi_file_system_info_id,
sizeof ( *type ) ) == 0 ) { sizeof ( *type ) ) == 0 ) {
@@ -468,6 +619,7 @@ static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) {
/** Root directory */ /** Root directory */
static struct efi_file efi_file_root = { static struct efi_file efi_file_root = {
.refcnt = REF_INIT ( ref_no_free ),
.file = { .file = {
.Revision = EFI_FILE_PROTOCOL_REVISION, .Revision = EFI_FILE_PROTOCOL_REVISION,
.Open = efi_file_open, .Open = efi_file_open,
@@ -482,6 +634,7 @@ static struct efi_file efi_file_root = {
.Flush = efi_file_flush, .Flush = efi_file_flush,
}, },
.image = NULL, .image = NULL,
.name = "",
}; };
/** /**
@@ -496,8 +649,7 @@ efi_file_open_volume ( EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *filesystem __unused,
EFI_FILE_PROTOCOL **file ) { EFI_FILE_PROTOCOL **file ) {
DBGC ( &efi_file_root, "EFIFILE open volume\n" ); DBGC ( &efi_file_root, "EFIFILE open volume\n" );
*file = &efi_file_root.file; return efi_file_open_fixed ( &efi_file_root, file );
return 0;
} }
/** EFI simple file system protocol */ /** EFI simple file system protocol */