]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Allow for non-image-backed virtual files
authorMichael Brown <mcb30@ipxe.org>
Tue, 18 May 2021 13:03:15 +0000 (14:03 +0100)
committerMichael Brown <mcb30@ipxe.org>
Fri, 21 May 2021 15:32:36 +0000 (16:32 +0100)
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>
src/interface/efi/efi_file.c

index 52de0987cea28b9a20d698b1757ccdf99cb5c08c..c67fdea54d659efd86f3bb79eb15f99d9674af64 100644 (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 = "";
-       }
-
-       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 */