diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c | 1142 |
1 files changed, 1142 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c b/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c new file mode 100644 index 00000000..9a1168c1 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/VBoxPkg/VBoxFsDxe/fsw_efi.c @@ -0,0 +1,1142 @@ +/* $Id: fsw_efi.c $ */ +/** @file + * fsw_efi.c - EFI host environment code. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * 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, in version 3 of the + * License. + * + * 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, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + * --------------------------------------------------------------------------- + * This code is based on: + * + * Copyright (c) 2006 Christoph Pfisterer + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Christoph Pfisterer nor the names of the + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fsw_efi.h" + +#define DEBUG_LEVEL 0 + +#ifndef FSTYPE +#ifdef VBOX +#error FSTYPE must be defined! +#else +#define FSTYPE ext2 +#endif +#endif + +/** Helper macro for stringification. */ +#define FSW_EFI_STRINGIFY(x) L#x +/** Expands to the EFI driver name given the file system type name. */ +#define FSW_EFI_DRIVER_NAME(t) L"Fsw " FSW_EFI_STRINGIFY(t) L" File System Driver" + + +// function prototypes + +EFI_STATUS EFIAPI fsw_efi_DriverBinding_Supported(IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath); +EFI_STATUS EFIAPI fsw_efi_DriverBinding_Start(IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath); +EFI_STATUS EFIAPI fsw_efi_DriverBinding_Stop(IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer); + +EFI_STATUS EFIAPI fsw_efi_ComponentName_GetDriverName(IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName); +EFI_STATUS EFIAPI fsw_efi_ComponentName_GetControllerName(IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_HANDLE ChildHandle OPTIONAL, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName); + +void fsw_efi_change_blocksize(struct fsw_volume *vol, + fsw_u32 old_phys_blocksize, fsw_u32 old_log_blocksize, + fsw_u32 new_phys_blocksize, fsw_u32 new_log_blocksize); +fsw_status_t fsw_efi_read_block(struct fsw_volume *vol, fsw_u32 phys_bno, void *buffer); + +EFI_STATUS fsw_efi_map_status(fsw_status_t fsw_status, FSW_VOLUME_DATA *Volume); + +EFI_STATUS EFIAPI fsw_efi_FileSystem_OpenVolume(IN EFI_FILE_IO_INTERFACE *This, + OUT EFI_FILE **Root); +EFI_STATUS fsw_efi_dnode_to_FileHandle(IN struct fsw_dnode *dno, + OUT EFI_FILE **NewFileHandle); + +EFI_STATUS fsw_efi_file_read(IN FSW_FILE_DATA *File, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer); +EFI_STATUS fsw_efi_file_getpos(IN FSW_FILE_DATA *File, + OUT UINT64 *Position); +EFI_STATUS fsw_efi_file_setpos(IN FSW_FILE_DATA *File, + IN UINT64 Position); + +EFI_STATUS fsw_efi_dir_open(IN FSW_FILE_DATA *File, + OUT EFI_FILE **NewHandle, + IN CHAR16 *FileName, + IN UINT64 OpenMode, + IN UINT64 Attributes); +EFI_STATUS fsw_efi_dir_read(IN FSW_FILE_DATA *File, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer); +EFI_STATUS fsw_efi_dir_setpos(IN FSW_FILE_DATA *File, + IN UINT64 Position); + +EFI_STATUS fsw_efi_dnode_getinfo(IN FSW_FILE_DATA *File, + IN EFI_GUID *InformationType, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer); +EFI_STATUS fsw_efi_dnode_fill_FileInfo(IN FSW_VOLUME_DATA *Volume, + IN struct fsw_dnode *dno, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer); + +#if defined(VBOX) && (FSTYPE == hfs) +extern fsw_status_t fsw_hfs_get_blessed_file(void *vol, struct fsw_string *path); +#endif + +/** + * Interface structure for the EFI Driver Binding protocol. + */ + +EFI_DRIVER_BINDING_PROTOCOL fsw_efi_DriverBinding_table = { + fsw_efi_DriverBinding_Supported, + fsw_efi_DriverBinding_Start, + fsw_efi_DriverBinding_Stop, + 0x10, + NULL, + NULL +}; + +/** + * Interface structure for the EFI Component Name protocol. + */ + +EFI_COMPONENT_NAME_PROTOCOL fsw_efi_ComponentName_table = { + fsw_efi_ComponentName_GetDriverName, + fsw_efi_ComponentName_GetControllerName, + "eng" +}; + +/** + * Dispatch table for our FSW host driver. + */ + +struct fsw_host_table fsw_efi_host_table = { + FSW_STRING_TYPE_UTF16, + + fsw_efi_change_blocksize, + fsw_efi_read_block +}; + +extern struct fsw_fstype_table FSW_FSTYPE_TABLE_NAME(FSTYPE); + + + +/** + * Image entry point. Installs the Driver Binding and Component Name protocols + * on the image's handle. Actually mounting a file system is initiated through + * the Driver Binding protocol at the firmware's request. + */ +EFI_STATUS EFIAPI fsw_efi_main(IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable) +{ + EFI_STATUS Status; + +#ifndef VBOX + InitializeLib(ImageHandle, SystemTable); +#endif + + // complete Driver Binding protocol instance + fsw_efi_DriverBinding_table.ImageHandle = ImageHandle; + fsw_efi_DriverBinding_table.DriverBindingHandle = ImageHandle; + // install Driver Binding protocol + Status = BS->InstallProtocolInterface(&fsw_efi_DriverBinding_table.DriverBindingHandle, + &PROTO_NAME(DriverBindingProtocol), + EFI_NATIVE_INTERFACE, + &fsw_efi_DriverBinding_table); + if (EFI_ERROR (Status)) { + return Status; + } + + // install Component Name protocol + Status = BS->InstallProtocolInterface(&fsw_efi_DriverBinding_table.DriverBindingHandle, + &PROTO_NAME(ComponentNameProtocol), + EFI_NATIVE_INTERFACE, + &fsw_efi_ComponentName_table); + if (EFI_ERROR (Status)) { + return Status; + } + + return EFI_SUCCESS; +} + +/** + * Driver Binding EFI protocol, Supported function. This function is called by EFI + * to test if this driver can handle a certain device. Our implementation only checks + * if the device is a disk (i.e. that it supports the Block I/O and Disk I/O protocols) + * and implicitly checks if the disk is already in use by another driver. + */ + +EFI_STATUS EFIAPI fsw_efi_DriverBinding_Supported(IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath) +{ + EFI_STATUS Status; + EFI_DISK_IO *DiskIo; + + // we check for both DiskIO and BlockIO protocols + + // first, open DiskIO + Status = BS->OpenProtocol(ControllerHandle, + &PROTO_NAME(DiskIoProtocol), + (VOID **) &DiskIo, + This->DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(Status)) + { + return Status; + } + + // we were just checking, close it again + BS->CloseProtocol(ControllerHandle, + &PROTO_NAME(DiskIoProtocol), + This->DriverBindingHandle, + ControllerHandle); + + // next, check BlockIO without actually opening it + Status = BS->OpenProtocol(ControllerHandle, + &PROTO_NAME(BlockIoProtocol), + NULL, + This->DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_TEST_PROTOCOL); + return Status; +} + +static EFI_STATUS fsw_efi_ReMount(IN FSW_VOLUME_DATA *pVolume, + IN EFI_HANDLE ControllerHandle, + EFI_DISK_IO *pDiskIo, + EFI_BLOCK_IO *pBlockIo) +{ + EFI_STATUS Status; + pVolume->Signature = FSW_VOLUME_DATA_SIGNATURE; + pVolume->Handle = ControllerHandle; + pVolume->DiskIo = pDiskIo; + pVolume->MediaId = pBlockIo->Media->MediaId; + pVolume->LastIOStatus = EFI_SUCCESS; + + // mount the filesystem + Status = fsw_efi_map_status(fsw_mount(pVolume, &fsw_efi_host_table, + &FSW_FSTYPE_TABLE_NAME(FSTYPE), &pVolume->vol), + pVolume); + + if (!EFI_ERROR(Status)) { + // register the SimpleFileSystem protocol + pVolume->FileSystem.Revision = EFI_FILE_IO_INTERFACE_REVISION; + pVolume->FileSystem.OpenVolume = fsw_efi_FileSystem_OpenVolume; + Status = BS->InstallMultipleProtocolInterfaces(&ControllerHandle, + &PROTO_NAME(SimpleFileSystemProtocol), &pVolume->FileSystem, + NULL); +#if DEBUG_LEVEL /* This error is always printed and destroys the boot logo. */ + if (EFI_ERROR(Status)) + Print(L"Fsw ERROR: InstallMultipleProtocolInterfaces returned %x\n", Status); +#endif + } + return Status; +} + +/** + * Driver Binding EFI protocol, Start function. This function is called by EFI + * to start driving the given device. It is still possible at this point to + * return EFI_UNSUPPORTED, and in fact we will do so if the file system driver + * cannot find the superblock signature (or equivalent) that it expects. + * + * This function allocates memory for a per-volume structure, opens the + * required protocols (just Disk I/O in our case, Block I/O is only looked + * at to get the MediaId field), and lets the FSW core mount the file system. + * If successful, an EFI Simple File System protocol is exported on the + * device handle. + */ + +EFI_STATUS EFIAPI fsw_efi_DriverBinding_Start(IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath) +{ + EFI_STATUS Status; + EFI_BLOCK_IO *BlockIo; + EFI_DISK_IO *DiskIo; + FSW_VOLUME_DATA *Volume; + + // open consumed protocols + Status = BS->OpenProtocol(ControllerHandle, + &PROTO_NAME(BlockIoProtocol), + (VOID **) &BlockIo, + This->DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); // NOTE: we only want to look at the MediaId + if (EFI_ERROR(Status)) { + return Status; + } + + Status = BS->OpenProtocol(ControllerHandle, + &PROTO_NAME(DiskIoProtocol), + (VOID **) &DiskIo, + This->DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_BY_DRIVER); + if (EFI_ERROR(Status)) { + return Status; + } + + // allocate volume structure + Volume = AllocateZeroPool(sizeof(FSW_VOLUME_DATA)); + Status = fsw_efi_ReMount(Volume, ControllerHandle, DiskIo, BlockIo); + + // on errors, close the opened protocols + if (EFI_ERROR(Status)) { + if (Volume->vol != NULL) + fsw_unmount(Volume->vol); + FreePool(Volume); + +#if 0 + if (Status == EFI_MEDIA_CHANGED) + Status = fsw_efi_ReMount(Volume, ControllerHandle, DiskIo, BlockIo); + else +#endif + BS->CloseProtocol(ControllerHandle, + &PROTO_NAME(DiskIoProtocol), + This->DriverBindingHandle, + ControllerHandle); + } + + return Status; +} + +/** + * Driver Binding EFI protocol, Stop function. This function is called by EFI + * to stop the driver on the given device. This translates to an unmount + * call for the FSW core. + * + * We assume that all file handles on the volume have been closed before + * the driver is stopped. At least with the EFI shell, that is actually the + * case; it closes all file handles between commands. + */ + +EFI_STATUS EFIAPI fsw_efi_DriverBinding_Stop(IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer) +{ + EFI_STATUS Status; + EFI_FILE_IO_INTERFACE *FileSystem; + FSW_VOLUME_DATA *Volume; + +#if DEBUG_LEVEL + Print(L"fsw_efi_DriverBinding_Stop\n"); +#endif + + // get the installed SimpleFileSystem interface + Status = BS->OpenProtocol(ControllerHandle, + &PROTO_NAME(SimpleFileSystemProtocol), + (VOID **) &FileSystem, + This->DriverBindingHandle, + ControllerHandle, + EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR(Status)) + return EFI_UNSUPPORTED; + + // get private data structure + Volume = FSW_VOLUME_FROM_FILE_SYSTEM(FileSystem); + + // uninstall Simple File System protocol + Status = BS->UninstallMultipleProtocolInterfaces(ControllerHandle, + &PROTO_NAME(SimpleFileSystemProtocol), &Volume->FileSystem, + NULL); + if (EFI_ERROR(Status)) { + Print(L"Fsw ERROR: UninstallMultipleProtocolInterfaces returned %x\n", Status); + return Status; + } +#if DEBUG_LEVEL + Print(L"fsw_efi_DriverBinding_Stop: protocol uninstalled successfully\n"); +#endif + + // release private data structure + if (Volume->vol != NULL) + fsw_unmount(Volume->vol); + FreePool(Volume); + + // close the consumed protocols + Status = BS->CloseProtocol(ControllerHandle, + &PROTO_NAME(DiskIoProtocol), + This->DriverBindingHandle, + ControllerHandle); + + return Status; +} + +/** + * Component Name EFI protocol, GetDriverName function. Used by the EFI + * environment to inquire the name of this driver. The name returned is + * based on the file system type actually used in compilation. + */ + +EFI_STATUS EFIAPI fsw_efi_ComponentName_GetDriverName(IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN CHAR8 *Language, + OUT CHAR16 **DriverName) +{ + if (Language == NULL || DriverName == NULL) + return EFI_INVALID_PARAMETER; +#if 0 + + if (Language[0] == 'e' && Language[1] == 'n' && Language[2] == 'g' && Language[3] == 0) { + *DriverName = FSW_EFI_DRIVER_NAME(FSTYPE); + return EFI_SUCCESS; + } +#endif + return EFI_UNSUPPORTED; +} + +/** + * Component Name EFI protocol, GetControllerName function. Not implemented + * because this is not a "bus" driver in the sense of the EFI Driver Model. + */ + +EFI_STATUS EFIAPI fsw_efi_ComponentName_GetControllerName(IN EFI_COMPONENT_NAME_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN EFI_HANDLE ChildHandle OPTIONAL, + IN CHAR8 *Language, + OUT CHAR16 **ControllerName) +{ + return EFI_UNSUPPORTED; +} + +/** + * FSW interface function for block size changes. This function is called by the FSW core + * when the file system driver changes the block sizes for the volume. + */ + +void fsw_efi_change_blocksize(struct fsw_volume *vol, + fsw_u32 old_phys_blocksize, fsw_u32 old_log_blocksize, + fsw_u32 new_phys_blocksize, fsw_u32 new_log_blocksize) +{ + // nothing to do +} + +/** + * FSW interface function to read data blocks. This function is called by the FSW core + * to read a block of data from the device. The buffer is allocated by the core code. + */ + +fsw_status_t fsw_efi_read_block(struct fsw_volume *vol, fsw_u32 phys_bno, void *buffer) +{ + EFI_STATUS Status; + FSW_VOLUME_DATA *Volume = (FSW_VOLUME_DATA *)vol->host_data; + + FSW_MSG_DEBUGV((FSW_MSGSTR("fsw_efi_read_block: %d (%d)\n"), phys_bno, vol->phys_blocksize)); + + // read from disk + Status = Volume->DiskIo->ReadDisk(Volume->DiskIo, Volume->MediaId, + (UINT64)phys_bno * vol->phys_blocksize, + vol->phys_blocksize, + buffer); + Volume->LastIOStatus = Status; + if (EFI_ERROR(Status)) + return FSW_IO_ERROR; + return FSW_SUCCESS; +} + +/** + * Map FSW status codes to EFI status codes. The FSW_IO_ERROR code is only produced + * by fsw_efi_read_block, so we map it back to the EFI status code remembered from + * the last I/O operation. + */ + +EFI_STATUS fsw_efi_map_status(fsw_status_t fsw_status, FSW_VOLUME_DATA *Volume) +{ + switch (fsw_status) { + case FSW_SUCCESS: + return EFI_SUCCESS; + case FSW_OUT_OF_MEMORY: + return EFI_VOLUME_CORRUPTED; + case FSW_IO_ERROR: + return Volume->LastIOStatus; + case FSW_UNSUPPORTED: + return EFI_UNSUPPORTED; + case FSW_NOT_FOUND: + return EFI_NOT_FOUND; + case FSW_VOLUME_CORRUPTED: + return EFI_VOLUME_CORRUPTED; + default: + return EFI_DEVICE_ERROR; + } +} + +/** + * File System EFI protocol, OpenVolume function. Creates a file handle for + * the root directory and returns it. Note that this function may be called + * multiple times and returns a new file handle each time. Each returned + * handle is closed by the client using it. + */ + +EFI_STATUS EFIAPI fsw_efi_FileSystem_OpenVolume(IN EFI_FILE_IO_INTERFACE *This, + OUT EFI_FILE **Root) +{ + EFI_STATUS Status; + FSW_VOLUME_DATA *Volume = FSW_VOLUME_FROM_FILE_SYSTEM(This); + +#if DEBUG_LEVEL + Print(L"fsw_efi_FileSystem_OpenVolume\n"); +#endif + + Status = fsw_efi_dnode_to_FileHandle(Volume->vol->root, Root); + + return Status; +} + +/** + * File Handle EFI protocol, Open function. Dispatches the call + * based on the kind of file handle. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_Open(IN EFI_FILE *This, + OUT EFI_FILE **NewHandle, + IN CHAR16 *FileName, + IN UINT64 OpenMode, + IN UINT64 Attributes) +{ + FSW_FILE_DATA *File = FSW_FILE_FROM_FILE_HANDLE(This); + + if (File->Type == FSW_EFI_FILE_TYPE_DIR) + return fsw_efi_dir_open(File, NewHandle, FileName, OpenMode, Attributes); + // not supported for regular files + return EFI_UNSUPPORTED; +} + +/** + * File Handle EFI protocol, Close function. Closes the FSW shandle + * and frees the memory used for the structure. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_Close(IN EFI_FILE *This) +{ + FSW_FILE_DATA *File = FSW_FILE_FROM_FILE_HANDLE(This); + +#if DEBUG_LEVEL + Print(L"fsw_efi_FileHandle_Close\n"); +#endif + + fsw_shandle_close(&File->shand); + FreePool(File); + + return EFI_SUCCESS; +} + +/** + * File Handle EFI protocol, Delete function. Calls through to Close + * and returns a warning because this driver is read-only. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_Delete(IN EFI_FILE *This) +{ + EFI_STATUS Status; + + Status = This->Close(This); + if (Status == EFI_SUCCESS) { + // this driver is read-only + Status = EFI_WARN_DELETE_FAILURE; + } + + return Status; +} + +/** + * File Handle EFI protocol, Read function. Dispatches the call + * based on the kind of file handle. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_Read(IN EFI_FILE *This, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer) +{ + FSW_FILE_DATA *File = FSW_FILE_FROM_FILE_HANDLE(This); + + if (File->Type == FSW_EFI_FILE_TYPE_FILE) + return fsw_efi_file_read(File, BufferSize, Buffer); + else if (File->Type == FSW_EFI_FILE_TYPE_DIR) + return fsw_efi_dir_read(File, BufferSize, Buffer); + return EFI_UNSUPPORTED; +} + +/** + * File Handle EFI protocol, Write function. Returns unsupported status + * because this driver is read-only. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_Write(IN EFI_FILE *This, + IN OUT UINTN *BufferSize, + IN VOID *Buffer) +{ + // this driver is read-only + return EFI_WRITE_PROTECTED; +} + +/** + * File Handle EFI protocol, GetPosition function. Dispatches the call + * based on the kind of file handle. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_GetPosition(IN EFI_FILE *This, + OUT UINT64 *Position) +{ + FSW_FILE_DATA *File = FSW_FILE_FROM_FILE_HANDLE(This); + + if (File->Type == FSW_EFI_FILE_TYPE_FILE) + return fsw_efi_file_getpos(File, Position); + // not defined for directories + return EFI_UNSUPPORTED; +} + +/** + * File Handle EFI protocol, SetPosition function. Dispatches the call + * based on the kind of file handle. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_SetPosition(IN EFI_FILE *This, + IN UINT64 Position) +{ + FSW_FILE_DATA *File = FSW_FILE_FROM_FILE_HANDLE(This); + + if (File->Type == FSW_EFI_FILE_TYPE_FILE) + return fsw_efi_file_setpos(File, Position); + else if (File->Type == FSW_EFI_FILE_TYPE_DIR) + return fsw_efi_dir_setpos(File, Position); + return EFI_UNSUPPORTED; +} + +/** + * File Handle EFI protocol, GetInfo function. Dispatches to the common + * function implementing this. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_GetInfo(IN EFI_FILE *This, + IN EFI_GUID *InformationType, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer) +{ + FSW_FILE_DATA *File = FSW_FILE_FROM_FILE_HANDLE(This); + + return fsw_efi_dnode_getinfo(File, InformationType, BufferSize, Buffer); +} + +/** + * File Handle EFI protocol, SetInfo function. Returns unsupported status + * because this driver is read-only. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_SetInfo(IN EFI_FILE *This, + IN EFI_GUID *InformationType, + IN UINTN BufferSize, + IN VOID *Buffer) +{ + // this driver is read-only + return EFI_WRITE_PROTECTED; +} + +/** + * File Handle EFI protocol, Flush function. Returns unsupported status + * because this driver is read-only. + */ + +EFI_STATUS EFIAPI fsw_efi_FileHandle_Flush(IN EFI_FILE *This) +{ + // this driver is read-only + return EFI_WRITE_PROTECTED; +} + +/** + * Set up a file handle for a dnode. This function allocates a data structure + * for a file handle, opens a FSW shandle and populates the EFI_FILE structure + * with the interface functions. + */ + +EFI_STATUS fsw_efi_dnode_to_FileHandle(IN struct fsw_dnode *dno, + OUT EFI_FILE **NewFileHandle) +{ + EFI_STATUS Status; + FSW_FILE_DATA *File; + + // make sure the dnode has complete info + Status = fsw_efi_map_status(fsw_dnode_fill(dno), (FSW_VOLUME_DATA *)dno->vol->host_data); + if (EFI_ERROR(Status)) + return Status; + + // check type + if (dno->type != FSW_DNODE_TYPE_FILE && dno->type != FSW_DNODE_TYPE_DIR) + return EFI_UNSUPPORTED; + + // allocate file structure + File = AllocateZeroPool(sizeof(FSW_FILE_DATA)); + File->Signature = FSW_FILE_DATA_SIGNATURE; + if (dno->type == FSW_DNODE_TYPE_FILE) + File->Type = FSW_EFI_FILE_TYPE_FILE; + else if (dno->type == FSW_DNODE_TYPE_DIR) + File->Type = FSW_EFI_FILE_TYPE_DIR; + + // open shandle + Status = fsw_efi_map_status(fsw_shandle_open(dno, &File->shand), + (FSW_VOLUME_DATA *)dno->vol->host_data); + if (EFI_ERROR(Status)) { + FreePool(File); + return Status; + } + + // populate the file handle + File->FileHandle.Revision = EFI_FILE_HANDLE_REVISION; + File->FileHandle.Open = fsw_efi_FileHandle_Open; + File->FileHandle.Close = fsw_efi_FileHandle_Close; + File->FileHandle.Delete = fsw_efi_FileHandle_Delete; + File->FileHandle.Read = fsw_efi_FileHandle_Read; + File->FileHandle.Write = fsw_efi_FileHandle_Write; + File->FileHandle.GetPosition = fsw_efi_FileHandle_GetPosition; + File->FileHandle.SetPosition = fsw_efi_FileHandle_SetPosition; + File->FileHandle.GetInfo = fsw_efi_FileHandle_GetInfo; + File->FileHandle.SetInfo = fsw_efi_FileHandle_SetInfo; + File->FileHandle.Flush = fsw_efi_FileHandle_Flush; + + *NewFileHandle = &File->FileHandle; + return EFI_SUCCESS; +} + +/** + * Data read function for regular files. Calls through to fsw_shandle_read. + */ + +EFI_STATUS fsw_efi_file_read(IN FSW_FILE_DATA *File, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer) +{ + EFI_STATUS Status; + fsw_u32 buffer_size; + +#if DEBUG_LEVEL + Print(L"fsw_efi_file_read %d bytes\n", *BufferSize); +#endif + + buffer_size = (fsw_u32)*BufferSize; + if (buffer_size != *BufferSize) + buffer_size = ~(fsw_u32)0; + Status = fsw_efi_map_status(fsw_shandle_read(&File->shand, &buffer_size, Buffer), + (FSW_VOLUME_DATA *)File->shand.dnode->vol->host_data); + *BufferSize = buffer_size; + + return Status; +} + +/** + * Get file position for regular files. + */ + +EFI_STATUS fsw_efi_file_getpos(IN FSW_FILE_DATA *File, + OUT UINT64 *Position) +{ + *Position = File->shand.pos; + return EFI_SUCCESS; +} + +/** + * Set file position for regular files. EFI specifies the all-ones value + * to be a special value for the end of the file. + */ + +EFI_STATUS fsw_efi_file_setpos(IN FSW_FILE_DATA *File, + IN UINT64 Position) +{ + if (Position == 0xFFFFFFFFFFFFFFFFULL) + File->shand.pos = File->shand.dnode->size; + else + File->shand.pos = Position; + return EFI_SUCCESS; +} + +/** + * Open function used to open new file handles relative to a directory. + * In EFI, the "open file" function is implemented by directory file handles + * and is passed a relative or volume-absolute path to the file or directory + * to open. We use fsw_dnode_lookup_path to find the node plus an additional + * call to fsw_dnode_resolve because EFI has no concept of symbolic links. + */ + +EFI_STATUS fsw_efi_dir_open(IN FSW_FILE_DATA *File, + OUT EFI_FILE **NewHandle, + IN CHAR16 *FileName, + IN UINT64 OpenMode, + IN UINT64 Attributes) +{ + EFI_STATUS Status; + FSW_VOLUME_DATA *Volume = (FSW_VOLUME_DATA *)File->shand.dnode->vol->host_data; + struct fsw_dnode *dno; + struct fsw_dnode *target_dno; + struct fsw_string lookup_path; + +#if DEBUG_LEVEL + Print(L"fsw_efi_dir_open: '%s'\n", FileName); +#endif + + if (OpenMode != EFI_FILE_MODE_READ) + return EFI_WRITE_PROTECTED; + + lookup_path.type = FSW_STRING_TYPE_UTF16; + lookup_path.len = (int)StrLen(FileName); + lookup_path.size = lookup_path.len * sizeof(fsw_u16); + lookup_path.data = FileName; + + // resolve the path (symlinks along the way are automatically resolved) + Status = fsw_efi_map_status(fsw_dnode_lookup_path(File->shand.dnode, &lookup_path, '\\', &dno), + Volume); + if (EFI_ERROR(Status)) + return Status; + + // if the final node is a symlink, also resolve it + Status = fsw_efi_map_status(fsw_dnode_resolve(dno, &target_dno), + Volume); + fsw_dnode_release(dno); + if (EFI_ERROR(Status)) + return Status; + dno = target_dno; + + // make a new EFI handle for the target dnode + Status = fsw_efi_dnode_to_FileHandle(dno, NewHandle); + fsw_dnode_release(dno); + return Status; +} + +/** + * Read function for directories. A file handle read on a directory retrieves + * the next directory entry. + */ + +EFI_STATUS fsw_efi_dir_read(IN FSW_FILE_DATA *File, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer) +{ + EFI_STATUS Status; + FSW_VOLUME_DATA *Volume = (FSW_VOLUME_DATA *)File->shand.dnode->vol->host_data; + struct fsw_dnode *dno; + +#if DEBUG_LEVEL + Print(L"fsw_efi_dir_read...\n"); +#endif + + // read the next entry + Status = fsw_efi_map_status(fsw_dnode_dir_read(&File->shand, &dno), + Volume); + if (Status == EFI_NOT_FOUND) { + // end of directory + *BufferSize = 0; +#if DEBUG_LEVEL + Print(L"...no more entries\n"); +#endif + return EFI_SUCCESS; + } + if (EFI_ERROR(Status)) + return Status; + + // get info into buffer + Status = fsw_efi_dnode_fill_FileInfo(Volume, dno, BufferSize, Buffer); + fsw_dnode_release(dno); + return Status; +} + +/** + * Set file position for directories. The only allowed set position operation + * for directories is to rewind the directory completely by setting the + * position to zero. + */ + +EFI_STATUS fsw_efi_dir_setpos(IN FSW_FILE_DATA *File, + IN UINT64 Position) +{ + if (Position == 0) { + File->shand.pos = 0; + return EFI_SUCCESS; + } else { + // directories can only rewind to the start + return EFI_UNSUPPORTED; + } +} + +/** + * Get file or volume information. This function implements the GetInfo call + * for all file handles. Control is dispatched according to the type of information + * requested by the caller. + */ + +EFI_STATUS fsw_efi_dnode_getinfo(IN FSW_FILE_DATA *File, + IN EFI_GUID *InformationType, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer) +{ + EFI_STATUS Status; + FSW_VOLUME_DATA *Volume = (FSW_VOLUME_DATA *)File->shand.dnode->vol->host_data; + EFI_FILE_SYSTEM_INFO *FSInfo; + UINTN RequiredSize; + struct fsw_volume_stat vsb; + + if (CompareGuid(InformationType, &GUID_NAME(FileInfo))) { +#if DEBUG_LEVEL + Print(L"fsw_efi_dnode_getinfo: FILE_INFO\n"); +#endif + + Status = fsw_efi_dnode_fill_FileInfo(Volume, File->shand.dnode, BufferSize, Buffer); + + } else if (CompareGuid(InformationType, &GUID_NAME(FileSystemInfo))) { +#if DEBUG_LEVEL + Print(L"fsw_efi_dnode_getinfo: FILE_SYSTEM_INFO\n"); +#endif + + // check buffer size + RequiredSize = SIZE_OF_EFI_FILE_SYSTEM_INFO + fsw_efi_strsize(&Volume->vol->label); + if (*BufferSize < RequiredSize) { + *BufferSize = RequiredSize; + return EFI_BUFFER_TOO_SMALL; + } + + // fill structure + FSInfo = (EFI_FILE_SYSTEM_INFO *)Buffer; + FSInfo->Size = RequiredSize; + FSInfo->ReadOnly = TRUE; + FSInfo->BlockSize = Volume->vol->log_blocksize; + fsw_efi_strcpy(FSInfo->VolumeLabel, &Volume->vol->label); + + // get the missing info from the fs driver + ZeroMem(&vsb, sizeof(struct fsw_volume_stat)); + Status = fsw_efi_map_status(fsw_volume_stat(Volume->vol, &vsb), Volume); + if (EFI_ERROR(Status)) + return Status; + FSInfo->VolumeSize = vsb.total_bytes; + FSInfo->FreeSpace = vsb.free_bytes; + + // prepare for return + *BufferSize = RequiredSize; + Status = EFI_SUCCESS; + + } else if (CompareGuid(InformationType, &GUID_NAME(FileSystemVolumeLabelInfoId))) { +#if DEBUG_LEVEL + Print(L"fsw_efi_dnode_getinfo: FILE_SYSTEM_VOLUME_LABEL\n"); +#endif + + // check buffer size + RequiredSize = SIZE_OF_EFI_FILE_SYSTEM_VOLUME_LABEL_INFO + fsw_efi_strsize(&Volume->vol->label); + if (*BufferSize < RequiredSize) { + *BufferSize = RequiredSize; + return EFI_BUFFER_TOO_SMALL; + } + + // copy volume label + fsw_efi_strcpy(((EFI_FILE_SYSTEM_VOLUME_LABEL_INFO *)Buffer)->VolumeLabel, &Volume->vol->label); + + // prepare for return + *BufferSize = RequiredSize; + Status = EFI_SUCCESS; + +#ifdef VBOX + } else if (CompareGuid(InformationType, &gVBoxFsBlessedFileInfoGuid)) { + +# if FSTYPE == hfs + struct fsw_string StrBlessedFile; + + fsw_status_t rc = fsw_hfs_get_blessed_file(Volume->vol, &StrBlessedFile); + if (!rc) + { + // check buffer size + RequiredSize = SIZE_OF_VBOX_FS_BLESSED_FILE + fsw_efi_strsize(&StrBlessedFile); + if (*BufferSize < RequiredSize) { + *BufferSize = RequiredSize; + return EFI_BUFFER_TOO_SMALL; + } + + // copy volume label + fsw_efi_strcpy(((VBOX_FS_BLESSED_FILE *)Buffer)->BlessedFile, &StrBlessedFile); + + // prepare for return + *BufferSize = RequiredSize; + Status = EFI_SUCCESS; + } + else +# endif + Status = EFI_UNSUPPORTED; +#endif + + } else { + Status = EFI_UNSUPPORTED; + } + + return Status; +} + +/** + * Time mapping callback for the fsw_dnode_stat call. This function converts + * a Posix style timestamp into an EFI_TIME structure and writes it to the + * appropriate member of the EFI_FILE_INFO structure that we're filling. + */ + +static void fsw_efi_store_time_posix(struct fsw_dnode_stat *sb, int which, fsw_u32 posix_time) +{ + EFI_FILE_INFO *FileInfo = (EFI_FILE_INFO *)sb->host_data; + + if (which == FSW_DNODE_STAT_CTIME) + fsw_efi_decode_time(&FileInfo->CreateTime, posix_time); + else if (which == FSW_DNODE_STAT_MTIME) + fsw_efi_decode_time(&FileInfo->ModificationTime, posix_time); + else if (which == FSW_DNODE_STAT_ATIME) + fsw_efi_decode_time(&FileInfo->LastAccessTime, posix_time); +} + +/** + * Mode mapping callback for the fsw_dnode_stat call. This function looks at + * the Posix mode passed by the file system driver and makes appropriate + * adjustments to the EFI_FILE_INFO structure that we're filling. + */ + +static void fsw_efi_store_attr_posix(struct fsw_dnode_stat *sb, fsw_u16 posix_mode) +{ + EFI_FILE_INFO *FileInfo = (EFI_FILE_INFO *)sb->host_data; + + if ((posix_mode & S_IWUSR) == 0) + FileInfo->Attribute |= EFI_FILE_READ_ONLY; +} + +/** + * Common function to fill an EFI_FILE_INFO with information about a dnode. + */ + +EFI_STATUS fsw_efi_dnode_fill_FileInfo(IN FSW_VOLUME_DATA *Volume, + IN struct fsw_dnode *dno, + IN OUT UINTN *BufferSize, + OUT VOID *Buffer) +{ + EFI_STATUS Status; + EFI_FILE_INFO *FileInfo; + UINTN RequiredSize; + struct fsw_dnode *target_dno; + struct fsw_dnode_stat sb; + + // make sure the dnode has complete info + Status = fsw_efi_map_status(fsw_dnode_fill(dno), Volume); + if (EFI_ERROR(Status)) + return Status; + + /// @todo check/assert that the dno's name is in UTF16 + + // check buffer size + RequiredSize = SIZE_OF_EFI_FILE_INFO + fsw_efi_strsize(&dno->name); + if (*BufferSize < RequiredSize) { + /// @todo wind back the directory in this case + +#if DEBUG_LEVEL + Print(L"...BUFFER TOO SMALL\n"); +#endif + *BufferSize = RequiredSize; + return EFI_BUFFER_TOO_SMALL; + } + + // fill structure + ZeroMem(Buffer, RequiredSize); + FileInfo = (EFI_FILE_INFO *)Buffer; + + // must preserve the original file name + fsw_efi_strcpy(FileInfo->FileName, &dno->name); + + // if the node is a symlink, also resolve it + Status = fsw_efi_map_status(fsw_dnode_resolve(dno, &target_dno), Volume); + fsw_dnode_release(dno); + if (EFI_ERROR(Status)) + return Status; + dno = target_dno; + // make sure the dnode has complete info again + Status = fsw_efi_map_status(fsw_dnode_fill(dno), Volume); + if (EFI_ERROR(Status)) + return Status; + + FileInfo->Size = RequiredSize; + FileInfo->FileSize = dno->size; + FileInfo->Attribute = 0; + if (dno->type == FSW_DNODE_TYPE_DIR) + FileInfo->Attribute |= EFI_FILE_DIRECTORY; + + // get the missing info from the fs driver + ZeroMem(&sb, sizeof(struct fsw_dnode_stat)); + sb.store_time_posix = fsw_efi_store_time_posix; + sb.store_attr_posix = fsw_efi_store_attr_posix; + sb.host_data = FileInfo; + Status = fsw_efi_map_status(fsw_dnode_stat(dno, &sb), Volume); + if (EFI_ERROR(Status)) + return Status; + FileInfo->PhysicalSize = sb.used_bytes; + + // prepare for return + *BufferSize = RequiredSize; +#if DEBUG_LEVEL + Print(L"...returning '%s'\n", FileInfo->FileName); +#endif + return EFI_SUCCESS; +} + +// EOF |