[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>
pull/373/head
Michael Brown 2021-05-18 14:03:15 +01:00
parent bfca3db41e
commit ef9953b712
1 changed files with 220 additions and 68 deletions

View File

@ -50,18 +50,54 @@ 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)
*
@ -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 = "";
}
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, name,
len, data );
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 */