david/ipxe
Archived
1
0
This repository has been archived on 2020-12-06. You can view files and clone it, but cannot push or open issues or pull requests.
ipxe/src/interface/efi/efi_file.c

594 lines
15 KiB
C
Raw Normal View History

/*
* Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
FILE_LICENCE ( GPL2_OR_LATER );
/**
* @file
*
* EFI file protocols
*
*/
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <ipxe/image.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/Protocol/SimpleFileSystem.h>
#include <ipxe/efi/Protocol/BlockIo.h>
#include <ipxe/efi/Guid/FileInfo.h>
#include <ipxe/efi/Guid/FileSystemInfo.h>
#include <ipxe/efi/efi_strings.h>
#include <ipxe/efi/efi_file.h>
/** EFI simple file system protocol GUID */
static EFI_GUID efi_simple_file_system_protocol_guid
= EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
/** EFI file information GUID */
static EFI_GUID efi_file_info_id = EFI_FILE_INFO_ID;
/** EFI file system information GUID */
static EFI_GUID efi_file_system_info_id = EFI_FILE_SYSTEM_INFO_ID;
/** EFI block I/O protocol GUID */
static EFI_GUID efi_block_io_protocol_guid
= EFI_BLOCK_IO_PROTOCOL_GUID;
/** EFI media ID */
#define EFI_MEDIA_ID_MAGIC 0x69505845
/** An image exposed as an EFI file */
struct efi_file {
/** EFI file protocol */
EFI_FILE_PROTOCOL file;
/** Image */
struct image *image;
/** Current file position */
size_t pos;
};
static struct efi_file efi_file_root;
/**
* Get EFI file name (for debugging)
*
* @v file EFI file
* @ret name Name
*/
static const char * efi_file_name ( struct efi_file *file ) {
return ( file->image ? file->image->name : "<root>" );
}
/**
* Open file
*
* @v this EFI file
* @ret new New EFI file
* @v wname Filename
* @v mode File mode
* @v attributes File attributes (for newly-created files)
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new,
CHAR16 *wname, UINT64 mode __unused,
UINT64 attributes __unused ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
char name[ wcslen ( wname ) + 1 /* NUL */ ];
struct efi_file *new_file;
struct image *image;
/* Initial '\' indicates opening from the root directory */
while ( *wname == L'\\' ) {
file = &efi_file_root;
wname++;
}
/* Allow root directory itself to be opened */
if ( ( wname[0] == L'\0' ) || ( wname[0] == L'.' ) ) {
*new = &efi_file_root.file;
return 0;
}
/* Fail unless opening from the root */
if ( file->image ) {
DBGC ( file, "EFIFILE %s is not a directory\n",
efi_file_name ( file ) );
return EFI_NOT_FOUND;
}
/* Identify image */
snprintf ( name, sizeof ( name ), "%ls", wname );
image = find_image ( name );
if ( ! image ) {
DBGC ( file, "EFIFILE \"%s\" does not exist\n", name );
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 );
return EFI_WRITE_PROTECTED;
}
/* Allocate and initialise file */
new_file = zalloc ( sizeof ( *new_file ) );
memcpy ( &new_file->file, &efi_file_root.file,
sizeof ( new_file->file ) );
new_file->image = image_get ( image );
*new = &new_file->file;
DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_file ) );
return 0;
}
/**
* Close file
*
* @v this EFI file
* @ret efirc EFI status code
*/
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 );
return 0;
}
/**
* Close and delete file
*
* @v this EFI file
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI efi_file_delete ( EFI_FILE_PROTOCOL *this ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
DBGC ( file, "EFIFILE %s cannot be deleted\n", efi_file_name ( file ) );
/* Close file */
efi_file_close ( this );
/* Warn of failure to delete */
return EFI_WARN_DELETE_FAILURE;
}
/**
* Return variable-length data structure
*
* @v base Base data structure (starting with UINT64)
* @v base_len Length of base data structure
* @v name Name to append to base data structure
* @v len Length of data buffer
* @v data Data buffer
* @ret efirc EFI status code
*/
static EFI_STATUS efi_file_varlen ( UINT64 *base, size_t base_len,
const char *name, UINTN *len, VOID *data ) {
size_t name_len;
/* Calculate structure length */
name_len = strlen ( name );
*base = ( base_len + ( name_len + 1 /* NUL */ ) * sizeof ( wchar_t ) );
if ( *len < *base ) {
*len = *base;
return EFI_BUFFER_TOO_SMALL;
}
/* Copy data to buffer */
*len = *base;
memcpy ( data, base, base_len );
efi_snprintf ( ( data + base_len ), ( name_len + 1 /* NUL */ ),
"%s", name );
return 0;
}
/**
* Return file information structure
*
* @v image Image, or NULL for the root directory
* @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,
VOID *data ) {
EFI_FILE_INFO info;
const char *name;
/* 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 );
}
/**
* Read directory entry
*
* @v file EFI file
* @v len Length to read
* @v data Data buffer
* @ret efirc EFI status code
*/
static EFI_STATUS efi_file_read_dir ( struct efi_file *file, UINTN *len,
VOID *data ) {
EFI_STATUS efirc;
struct image *image;
unsigned int index;
/* Construct directory entry at current position */
index = file->pos;
for_each_image ( image ) {
if ( index-- == 0 ) {
efirc = efi_file_info ( image, len, data );
if ( efirc == 0 )
file->pos++;
return efirc;
}
}
/* No more entries */
*len = 0;
return 0;
}
/**
* Read from file
*
* @v this EFI file
* @v len Length to read
* @v data Data buffer
* @ret efirc EFI status code
*/
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;
/* If this is the root directory, then construct a directory entry */
if ( ! file->image )
return efi_file_read_dir ( file, len, data );
/* 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;
return 0;
}
/**
* Write to file
*
* @v this EFI file
* @v len Length to write
* @v data Data buffer
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI efi_file_write ( EFI_FILE_PROTOCOL *this,
UINTN *len, VOID *data __unused ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
DBGC ( file, "EFIFILE %s cannot write [%#08zx, %#08zx)\n",
efi_file_name ( file ), file->pos,
( ( size_t ) ( file->pos + *len ) ) );
return EFI_WRITE_PROTECTED;
}
/**
* Set file position
*
* @v this EFI file
* @v position New file position
* @ret efirc EFI status code
*/
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 );
/* 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;
}
/* Check for the magic end-of-file value */
if ( position == 0xffffffffffffffffULL )
position = file->image->len;
/* Fail if we attempt to seek past the end of the file (since
* we do not support writes).
*/
if ( position > file->image->len ) {
DBGC ( file, "EFIFILE %s cannot seek to %#08llx of %#08zx\n",
efi_file_name ( file ), position, file->image->len );
return EFI_UNSUPPORTED;
}
/* Set position */
file->pos = position;
DBGC ( file, "EFIFILE %s position set to %#08zx\n",
efi_file_name ( file ), file->pos );
return 0;
}
/**
* Get file position
*
* @v this EFI file
* @ret position New file position
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI efi_file_get_position ( EFI_FILE_PROTOCOL *this,
UINT64 *position ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
*position = file->pos;
return 0;
}
/**
* Get file information
*
* @v this EFI file
* @v type Type of information
* @v len Buffer size
* @v data Buffer
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI efi_file_get_info ( EFI_FILE_PROTOCOL *this,
EFI_GUID *type,
UINTN *len, VOID *data ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
EFI_FILE_SYSTEM_INFO fsinfo;
struct image *image;
/* Determine information to return */
if ( memcmp ( type, &efi_file_info_id, sizeof ( *type ) ) == 0 ) {
/* Get file information */
DBGC ( file, "EFIFILE %s get file information\n",
efi_file_name ( file ) );
return efi_file_info ( file->image, len, data );
} else if ( memcmp ( type, &efi_file_system_info_id,
sizeof ( *type ) ) == 0 ) {
/* Get file system information */
DBGC ( file, "EFIFILE %s get file system information\n",
efi_file_name ( file ) );
memset ( &fsinfo, 0, sizeof ( fsinfo ) );
fsinfo.ReadOnly = 1;
for_each_image ( image )
fsinfo.VolumeSize += image->len;
return efi_file_varlen ( &fsinfo.Size,
SIZE_OF_EFI_FILE_SYSTEM_INFO, "iPXE",
len, data );
} else {
DBGC ( file, "EFIFILE %s cannot get information of type %s\n",
efi_file_name ( file ), efi_guid_ntoa ( type ) );
return EFI_UNSUPPORTED;
}
}
/**
* Set file information
*
* @v this EFI file
* @v type Type of information
* @v len Buffer size
* @v data Buffer
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
efi_file_set_info ( EFI_FILE_PROTOCOL *this, EFI_GUID *type,
UINTN len __unused, VOID *data __unused ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
DBGC ( file, "EFIFILE %s cannot set information of type %s\n",
efi_file_name ( file ), efi_guid_ntoa ( type ) );
return EFI_WRITE_PROTECTED;
}
/**
* Flush file modified data
*
* @v this EFI file
* @v type Type of information
* @v len Buffer size
* @v data Buffer
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) {
struct efi_file *file = container_of ( this, struct efi_file, file );
DBGC ( file, "EFIFILE %s flushed\n", efi_file_name ( file ) );
return 0;
}
/** Root directory */
static struct efi_file efi_file_root = {
.file = {
.Revision = EFI_FILE_PROTOCOL_REVISION,
.Open = efi_file_open,
.Close = efi_file_close,
.Delete = efi_file_delete,
.Read = efi_file_read,
.Write = efi_file_write,
.GetPosition = efi_file_get_position,
.SetPosition = efi_file_set_position,
.GetInfo = efi_file_get_info,
.SetInfo = efi_file_set_info,
.Flush = efi_file_flush,
},
.image = NULL,
};
/**
* Open root directory
*
* @v filesystem EFI simple file system
* @ret file EFI file handle
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
efi_file_open_volume ( EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *filesystem __unused,
EFI_FILE_PROTOCOL **file ) {
*file = &efi_file_root.file;
return 0;
}
/** EFI simple file system protocol */
static EFI_SIMPLE_FILE_SYSTEM_PROTOCOL efi_simple_file_system_protocol = {
.Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION,
.OpenVolume = efi_file_open_volume,
};
/** Dummy block I/O reset */
static EFI_STATUS EFIAPI
efi_block_io_reset ( EFI_BLOCK_IO_PROTOCOL *this __unused,
BOOLEAN extended __unused ) {
return 0;
}
/** Dummy block I/O read */
static EFI_STATUS EFIAPI
efi_block_io_read_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused,
UINT32 MediaId __unused, EFI_LBA lba __unused,
UINTN len __unused, VOID *data __unused ) {
return EFI_DEVICE_ERROR;
}
/** Dummy block I/O write */
static EFI_STATUS EFIAPI
efi_block_io_write_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused,
UINT32 MediaId __unused, EFI_LBA lba __unused,
UINTN len __unused, VOID *data __unused ) {
return EFI_DEVICE_ERROR;
}
/** Dummy block I/O flush */
static EFI_STATUS EFIAPI
efi_block_io_flush_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused ) {
return 0;
}
/** Dummy block I/O media */
static EFI_BLOCK_IO_MEDIA efi_block_io_media = {
.MediaId = EFI_MEDIA_ID_MAGIC,
.MediaPresent = 1,
.ReadOnly = 1,
.BlockSize = 1,
};
/** Dummy EFI block I/O protocol */
static EFI_BLOCK_IO_PROTOCOL efi_block_io_protocol = {
.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION,
.Media = &efi_block_io_media,
.Reset = efi_block_io_reset,
.ReadBlocks = efi_block_io_read_blocks,
.WriteBlocks = efi_block_io_write_blocks,
.FlushBlocks = efi_block_io_flush_blocks,
};
/**
* Install EFI simple file system protocol
*
* @v handle EFI handle
* @ret rc Return status code
*/
int efi_file_install ( EFI_HANDLE *handle ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
EFI_STATUS efirc;
/* Install the simple file system protocol and the block I/O
* protocol. We don't have a block device, but large parts of
* the EDK2 codebase make the assumption that file systems are
* normally attached to block devices, and so we create a
* dummy block device on the same handle just to keep things
* looking normal.
*/
if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
handle,
&efi_block_io_protocol_guid,
&efi_block_io_protocol,
&efi_simple_file_system_protocol_guid,
&efi_simple_file_system_protocol, NULL ) ) != 0 ) {
DBGC ( handle, "Could not install simple file system protocol: "
"%s\n", efi_strerror ( efirc ) );
return EFIRC_TO_RC ( efirc );
}
return 0;
}
/**
* Uninstall EFI simple file system protocol
*
* @v handle EFI handle
*/
void efi_file_uninstall ( EFI_HANDLE handle ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
/* We must install the file system protocol first, since
* otherwise the EDK2 code will attempt to helpfully uninstall
* it when the block I/O protocol is uninstalled, leading to a
* system lock-up.
*/
bs->UninstallMultipleProtocolInterfaces (
handle,
&efi_simple_file_system_protocol_guid,
&efi_simple_file_system_protocol,
&efi_block_io_protocol_guid,
&efi_block_io_protocol, NULL );
}