mirror of https://github.com/ipxe/ipxe.git
[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
parent
bfca3db41e
commit
ef9953b712
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue