/** @file | |
Initialization and helper routines for the Virtio Filesystem device. | |
Copyright (C) 2020, Red Hat, Inc. | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Library/BaseLib.h> // StrLen() | |
#include <Library/BaseMemoryLib.h> // CopyMem() | |
#include <Library/MemoryAllocationLib.h> // AllocatePool() | |
#include <Library/TimeBaseLib.h> // EpochToEfiTime() | |
#include <Library/VirtioLib.h> // Virtio10WriteFeatures() | |
#include "VirtioFsDxe.h" | |
/** | |
Read the Virtio Filesystem device configuration structure in full. | |
@param[in] Virtio The Virtio protocol underlying the VIRTIO_FS object. | |
@param[out] Config The fully populated VIRTIO_FS_CONFIG structure. | |
@retval EFI_SUCCESS Config has been filled in. | |
@return Error codes propagated from Virtio->ReadDevice(). The | |
contents of Config are indeterminate. | |
**/ | |
STATIC | |
EFI_STATUS | |
VirtioFsReadConfig ( | |
IN VIRTIO_DEVICE_PROTOCOL *Virtio, | |
OUT VIRTIO_FS_CONFIG *Config | |
) | |
{ | |
UINTN Idx; | |
EFI_STATUS Status; | |
for (Idx = 0; Idx < VIRTIO_FS_TAG_BYTES; Idx++) { | |
Status = Virtio->ReadDevice ( | |
Virtio, // This | |
OFFSET_OF (VIRTIO_FS_CONFIG, Tag[Idx]), // FieldOffset | |
sizeof Config->Tag[Idx], // FieldSize | |
sizeof Config->Tag[Idx], // BufferSize | |
&Config->Tag[Idx] // Buffer | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
Status = Virtio->ReadDevice ( | |
Virtio, // This | |
OFFSET_OF (VIRTIO_FS_CONFIG, NumReqQueues), // FieldOffset | |
sizeof Config->NumReqQueues, // FieldSize | |
sizeof Config->NumReqQueues, // BufferSize | |
&Config->NumReqQueues // Buffer | |
); | |
return Status; | |
} | |
/** | |
Configure the Virtio Filesystem device underlying VirtioFs. | |
@param[in,out] VirtioFs The VIRTIO_FS object for which Virtio communication | |
should be set up. On input, the caller is | |
responsible for VirtioFs->Virtio having been | |
initialized. On output, synchronous Virtio | |
Filesystem commands (primitives) may be submitted to | |
the device. | |
@retval EFI_SUCCESS Virtio machinery has been set up. | |
@retval EFI_UNSUPPORTED The host-side configuration of the Virtio Filesystem | |
is not supported by this driver. | |
@return Error codes from underlying functions. | |
**/ | |
EFI_STATUS | |
VirtioFsInit ( | |
IN OUT VIRTIO_FS *VirtioFs | |
) | |
{ | |
UINT8 NextDevStat; | |
EFI_STATUS Status; | |
UINT64 Features; | |
VIRTIO_FS_CONFIG Config; | |
UINTN Idx; | |
UINT64 RingBaseShift; | |
// | |
// Execute virtio-v1.1-cs01-87fa6b5d8155, 3.1.1 Driver Requirements: Device | |
// Initialization. | |
// | |
// 1. Reset the device. | |
// | |
NextDevStat = 0; | |
Status = VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// 2. Set the ACKNOWLEDGE status bit [...] | |
// | |
NextDevStat |= VSTAT_ACK; | |
Status = VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// 3. Set the DRIVER status bit [...] | |
// | |
NextDevStat |= VSTAT_DRIVER; | |
Status = VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// 4. Read device feature bits... | |
// | |
Status = VirtioFs->Virtio->GetDeviceFeatures (VirtioFs->Virtio, &Features); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
if ((Features & VIRTIO_F_VERSION_1) == 0) { | |
Status = EFI_UNSUPPORTED; | |
goto Failed; | |
} | |
// | |
// No device-specific feature bits have been defined in file "virtio-fs.tex" | |
// of the virtio spec at <https://github.com/oasis-tcs/virtio-spec.git>, as | |
// of commit 87fa6b5d8155. | |
// | |
Features &= VIRTIO_F_VERSION_1 | VIRTIO_F_IOMMU_PLATFORM; | |
// | |
// ... and write the subset of feature bits understood by the [...] driver to | |
// the device. [...] | |
// 5. Set the FEATURES_OK status bit. | |
// 6. Re-read device status to ensure the FEATURES_OK bit is still set [...] | |
// | |
Status = Virtio10WriteFeatures (VirtioFs->Virtio, Features, &NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// 7. Perform device-specific setup, including discovery of virtqueues for | |
// the device, [...] reading [...] the device's virtio configuration space | |
// | |
Status = VirtioFsReadConfig (VirtioFs->Virtio, &Config); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
// | |
// 7.a. Convert the filesystem label from UTF-8 to UCS-2. Only labels with | |
// printable ASCII code points (U+0020 through U+007E) are supported. | |
// NUL-terminate at either the terminator we find, or right after the | |
// original label. | |
// | |
for (Idx = 0; Idx < VIRTIO_FS_TAG_BYTES && Config.Tag[Idx] != '\0'; Idx++) { | |
if ((Config.Tag[Idx] < 0x20) || (Config.Tag[Idx] > 0x7E)) { | |
Status = EFI_UNSUPPORTED; | |
goto Failed; | |
} | |
VirtioFs->Label[Idx] = Config.Tag[Idx]; | |
} | |
VirtioFs->Label[Idx] = L'\0'; | |
// | |
// 7.b. We need one queue for sending normal priority requests. | |
// | |
if (Config.NumReqQueues < 1) { | |
Status = EFI_UNSUPPORTED; | |
goto Failed; | |
} | |
// | |
// 7.c. Fetch and remember the number of descriptors we can place on the | |
// queue at once. We'll need two descriptors per request, as a minimum -- | |
// request header, response header. | |
// | |
Status = VirtioFs->Virtio->SetQueueSel ( | |
VirtioFs->Virtio, | |
VIRTIO_FS_REQUEST_QUEUE | |
); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
Status = VirtioFs->Virtio->GetQueueNumMax ( | |
VirtioFs->Virtio, | |
&VirtioFs->QueueSize | |
); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
if (VirtioFs->QueueSize < 2) { | |
Status = EFI_UNSUPPORTED; | |
goto Failed; | |
} | |
// | |
// 7.d. [...] population of virtqueues [...] | |
// | |
Status = VirtioRingInit ( | |
VirtioFs->Virtio, | |
VirtioFs->QueueSize, | |
&VirtioFs->Ring | |
); | |
if (EFI_ERROR (Status)) { | |
goto Failed; | |
} | |
Status = VirtioRingMap ( | |
VirtioFs->Virtio, | |
&VirtioFs->Ring, | |
&RingBaseShift, | |
&VirtioFs->RingMap | |
); | |
if (EFI_ERROR (Status)) { | |
goto ReleaseQueue; | |
} | |
Status = VirtioFs->Virtio->SetQueueAddress ( | |
VirtioFs->Virtio, | |
&VirtioFs->Ring, | |
RingBaseShift | |
); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
// | |
// 8. Set the DRIVER_OK status bit. | |
// | |
NextDevStat |= VSTAT_DRIVER_OK; | |
Status = VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, NextDevStat); | |
if (EFI_ERROR (Status)) { | |
goto UnmapQueue; | |
} | |
return EFI_SUCCESS; | |
UnmapQueue: | |
VirtioFs->Virtio->UnmapSharedBuffer (VirtioFs->Virtio, VirtioFs->RingMap); | |
ReleaseQueue: | |
VirtioRingUninit (VirtioFs->Virtio, &VirtioFs->Ring); | |
Failed: | |
// | |
// If any of these steps go irrecoverably wrong, the driver SHOULD set the | |
// FAILED status bit to indicate that it has given up on the device (it can | |
// reset the device later to restart if desired). [...] | |
// | |
// Virtio access failure here should not mask the original error. | |
// | |
NextDevStat |= VSTAT_FAILED; | |
VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, NextDevStat); | |
return Status; | |
} | |
/** | |
De-configure the Virtio Filesystem device underlying VirtioFs. | |
@param[in] VirtioFs The VIRTIO_FS object for which Virtio communication | |
should be torn down. On input, the caller is responsible | |
for having called VirtioFsInit(). On output, Virtio | |
Filesystem commands (primitives) must no longer be | |
submitted to the device. | |
**/ | |
VOID | |
VirtioFsUninit ( | |
IN OUT VIRTIO_FS *VirtioFs | |
) | |
{ | |
// | |
// Resetting the Virtio device makes it release its resources and forget its | |
// configuration. | |
// | |
VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, 0); | |
VirtioFs->Virtio->UnmapSharedBuffer (VirtioFs->Virtio, VirtioFs->RingMap); | |
VirtioRingUninit (VirtioFs->Virtio, &VirtioFs->Ring); | |
} | |
/** | |
ExitBootServices event notification function for a Virtio Filesystem object. | |
This function resets the VIRTIO_FS.Virtio device, causing it to release all | |
references to guest-side resources. The function may only be called after | |
VirtioFsInit() returns successfully and before VirtioFsUninit() is called. | |
@param[in] ExitBootEvent The VIRTIO_FS.ExitBoot event that has been | |
signaled. | |
@param[in] VirtioFsAsVoid Pointer to the VIRTIO_FS object, passed in as | |
(VOID*). | |
**/ | |
VOID | |
EFIAPI | |
VirtioFsExitBoot ( | |
IN EFI_EVENT ExitBootEvent, | |
IN VOID *VirtioFsAsVoid | |
) | |
{ | |
VIRTIO_FS *VirtioFs; | |
VirtioFs = VirtioFsAsVoid; | |
DEBUG (( | |
DEBUG_VERBOSE, | |
"%a: VirtioFs=0x%p Label=\"%s\"\n", | |
__func__, | |
VirtioFsAsVoid, | |
VirtioFs->Label | |
)); | |
VirtioFs->Virtio->SetDeviceStatus (VirtioFs->Virtio, 0); | |
} | |
/** | |
Validate two VIRTIO_FS_SCATTER_GATHER_LIST objects -- list of request | |
buffers, list of response buffers -- together. | |
On input, the caller is required to populate the following fields: | |
- VIRTIO_FS_IO_VECTOR.Buffer, | |
- VIRTIO_FS_IO_VECTOR.Size, | |
- VIRTIO_FS_SCATTER_GATHER_LIST.IoVec, | |
- VIRTIO_FS_SCATTER_GATHER_LIST.NumVec. | |
On output (on successful return), the following fields will be | |
zero-initialized: | |
- VIRTIO_FS_IO_VECTOR.Mapped, | |
- VIRTIO_FS_IO_VECTOR.MappedAddress, | |
- VIRTIO_FS_IO_VECTOR.Mapping, | |
- VIRTIO_FS_IO_VECTOR.Transferred. | |
On output (on successful return), the following fields will be calculated: | |
- VIRTIO_FS_SCATTER_GATHER_LIST.TotalSize. | |
The function may only be called after VirtioFsInit() returns successfully and | |
before VirtioFsUninit() is called. | |
@param[in] VirtioFs The Virtio Filesystem device that the | |
request-response exchange, expressed via | |
RequestSgList and ResponseSgList, will be | |
submitted to. | |
@param[in,out] RequestSgList The scatter-gather list that describes the | |
request part of the exchange -- the buffers | |
that should be sent to the Virtio Filesystem | |
device in the virtio transfer. | |
@param[in,out] ResponseSgList The scatter-gather list that describes the | |
response part of the exchange -- the buffers | |
that the Virtio Filesystem device should | |
populate in the virtio transfer. May be NULL | |
if the exchange with the Virtio Filesystem | |
device consists of a request only, with the | |
response part omitted altogether. | |
@retval EFI_SUCCESS RequestSgList and ResponseSgList have been | |
validated, output fields have been set. | |
@retval EFI_INVALID_PARAMETER RequestSgList is NULL. | |
@retval EFI_INVALID_PARAMETER On input, a | |
VIRTIO_FS_SCATTER_GATHER_LIST.IoVec field is | |
NULL, or a | |
VIRTIO_FS_SCATTER_GATHER_LIST.NumVec field is | |
zero. | |
@retval EFI_INVALID_PARAMETER On input, a VIRTIO_FS_IO_VECTOR.Buffer field | |
is NULL, or a VIRTIO_FS_IO_VECTOR.Size field | |
is zero. | |
@retval EFI_UNSUPPORTED (RequestSgList->NumVec + | |
ResponseSgList->NumVec) exceeds | |
VirtioFs->QueueSize, meaning that the total | |
list of buffers cannot be placed on the virtio | |
queue in a single descriptor chain (with one | |
descriptor per buffer). | |
@retval EFI_UNSUPPORTED One of the | |
VIRTIO_FS_SCATTER_GATHER_LIST.TotalSize fields | |
would exceed MAX_UINT32. | |
**/ | |
EFI_STATUS | |
VirtioFsSgListsValidate ( | |
IN VIRTIO_FS *VirtioFs, | |
IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList, | |
IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL | |
) | |
{ | |
VIRTIO_FS_SCATTER_GATHER_LIST *SgListParam[2]; | |
UINT16 DescriptorsNeeded; | |
UINTN ListId; | |
if (RequestSgList == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
SgListParam[0] = RequestSgList; | |
SgListParam[1] = ResponseSgList; | |
DescriptorsNeeded = 0; | |
for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) { | |
VIRTIO_FS_SCATTER_GATHER_LIST *SgList; | |
UINT32 SgListTotalSize; | |
UINTN IoVecIdx; | |
SgList = SgListParam[ListId]; | |
if (SgList == NULL) { | |
continue; | |
} | |
// | |
// Sanity-check SgList -- it must provide at least one IO Vector. | |
// | |
if ((SgList->IoVec == NULL) || (SgList->NumVec == 0)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Make sure that, for each IO Vector in this SgList, a virtio descriptor | |
// can be added to the virtio queue, after the other descriptors added | |
// previously. | |
// | |
if ((SgList->NumVec > (UINTN)(MAX_UINT16 - DescriptorsNeeded)) || | |
(DescriptorsNeeded + SgList->NumVec > VirtioFs->QueueSize)) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
DescriptorsNeeded += (UINT16)SgList->NumVec; | |
SgListTotalSize = 0; | |
for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) { | |
VIRTIO_FS_IO_VECTOR *IoVec; | |
IoVec = &SgList->IoVec[IoVecIdx]; | |
// | |
// Sanity-check this IoVec -- it must describe a non-empty buffer. | |
// | |
if ((IoVec->Buffer == NULL) || (IoVec->Size == 0)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Make sure the cumulative size of all IO Vectors in this SgList remains | |
// expressible as a UINT32. | |
// | |
if (IoVec->Size > MAX_UINT32 - SgListTotalSize) { | |
return EFI_UNSUPPORTED; | |
} | |
SgListTotalSize += (UINT32)IoVec->Size; | |
// | |
// Initialize those fields in this IO Vector that will be updated in | |
// relation to mapping / transfer. | |
// | |
IoVec->Mapped = FALSE; | |
IoVec->MappedAddress = 0; | |
IoVec->Mapping = NULL; | |
IoVec->Transferred = 0; | |
} | |
// | |
// Store the cumulative size of all IO Vectors that we have calculated in | |
// this SgList. | |
// | |
SgList->TotalSize = SgListTotalSize; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Submit a validated pair of (request buffer list, response buffer list) to the | |
Virtio Filesystem device. | |
On input, the pair of VIRTIO_FS_SCATTER_GATHER_LIST objects must have been | |
validated together, using the VirtioFsSgListsValidate() function. | |
On output (on successful return), the following fields will be re-initialized | |
to zero (after temporarily setting them to different values): | |
- VIRTIO_FS_IO_VECTOR.Mapped, | |
- VIRTIO_FS_IO_VECTOR.MappedAddress, | |
- VIRTIO_FS_IO_VECTOR.Mapping. | |
On output (on successful return), the following fields will be calculated: | |
- VIRTIO_FS_IO_VECTOR.Transferred. | |
The function may only be called after VirtioFsInit() returns successfully and | |
before VirtioFsUninit() is called. | |
@param[in,out] VirtioFs The Virtio Filesystem device that the | |
request-response exchange, expressed via | |
RequestSgList and ResponseSgList, should now | |
be submitted to. | |
@param[in,out] RequestSgList The scatter-gather list that describes the | |
request part of the exchange -- the buffers | |
that should be sent to the Virtio Filesystem | |
device in the virtio transfer. | |
@param[in,out] ResponseSgList The scatter-gather list that describes the | |
response part of the exchange -- the buffers | |
that the Virtio Filesystem device should | |
populate in the virtio transfer. May be NULL | |
if and only if NULL was passed to | |
VirtioFsSgListsValidate() as ResponseSgList. | |
@retval EFI_SUCCESS Transfer complete. The caller should investigate | |
the VIRTIO_FS_IO_VECTOR.Transferred fields in | |
ResponseSgList, to ensure coverage of the relevant | |
response buffers. Subsequently, the caller should | |
investigate the contents of those buffers. | |
@retval EFI_DEVICE_ERROR The Virtio Filesystem device reported populating | |
more response bytes than ResponseSgList->TotalSize. | |
@return Error codes propagated from | |
VirtioMapAllBytesInSharedBuffer(), VirtioFlush(), | |
or VirtioFs->Virtio->UnmapSharedBuffer(). | |
**/ | |
EFI_STATUS | |
VirtioFsSgListsSubmit ( | |
IN OUT VIRTIO_FS *VirtioFs, | |
IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *RequestSgList, | |
IN OUT VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList OPTIONAL | |
) | |
{ | |
VIRTIO_FS_SCATTER_GATHER_LIST *SgListParam[2]; | |
VIRTIO_MAP_OPERATION SgListVirtioMapOp[ARRAY_SIZE (SgListParam)]; | |
UINT16 SgListDescriptorFlag[ARRAY_SIZE (SgListParam)]; | |
UINTN ListId; | |
VIRTIO_FS_SCATTER_GATHER_LIST *SgList; | |
UINTN IoVecIdx; | |
VIRTIO_FS_IO_VECTOR *IoVec; | |
EFI_STATUS Status; | |
DESC_INDICES Indices; | |
UINT32 TotalBytesWrittenByDevice; | |
UINT32 BytesPermittedForWrite; | |
SgListParam[0] = RequestSgList; | |
SgListVirtioMapOp[0] = VirtioOperationBusMasterRead; | |
SgListDescriptorFlag[0] = 0; | |
SgListParam[1] = ResponseSgList; | |
SgListVirtioMapOp[1] = VirtioOperationBusMasterWrite; | |
SgListDescriptorFlag[1] = VRING_DESC_F_WRITE; | |
// | |
// Map all IO Vectors. | |
// | |
for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) { | |
SgList = SgListParam[ListId]; | |
if (SgList == NULL) { | |
continue; | |
} | |
for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) { | |
IoVec = &SgList->IoVec[IoVecIdx]; | |
// | |
// Map this IO Vector. | |
// | |
Status = VirtioMapAllBytesInSharedBuffer ( | |
VirtioFs->Virtio, | |
SgListVirtioMapOp[ListId], | |
IoVec->Buffer, | |
IoVec->Size, | |
&IoVec->MappedAddress, | |
&IoVec->Mapping | |
); | |
if (EFI_ERROR (Status)) { | |
goto Unmap; | |
} | |
IoVec->Mapped = TRUE; | |
} | |
} | |
// | |
// Compose the descriptor chain. | |
// | |
VirtioPrepare (&VirtioFs->Ring, &Indices); | |
for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) { | |
SgList = SgListParam[ListId]; | |
if (SgList == NULL) { | |
continue; | |
} | |
for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) { | |
UINT16 NextFlag; | |
IoVec = &SgList->IoVec[IoVecIdx]; | |
// | |
// Set VRING_DESC_F_NEXT on all except the very last descriptor. | |
// | |
NextFlag = VRING_DESC_F_NEXT; | |
if ((ListId == ARRAY_SIZE (SgListParam) - 1) && | |
(IoVecIdx == SgList->NumVec - 1)) | |
{ | |
NextFlag = 0; | |
} | |
VirtioAppendDesc ( | |
&VirtioFs->Ring, | |
IoVec->MappedAddress, | |
(UINT32)IoVec->Size, | |
SgListDescriptorFlag[ListId] | NextFlag, | |
&Indices | |
); | |
} | |
} | |
// | |
// Submit the descriptor chain. | |
// | |
Status = VirtioFlush ( | |
VirtioFs->Virtio, | |
VIRTIO_FS_REQUEST_QUEUE, | |
&VirtioFs->Ring, | |
&Indices, | |
&TotalBytesWrittenByDevice | |
); | |
if (EFI_ERROR (Status)) { | |
goto Unmap; | |
} | |
// | |
// Sanity-check: the Virtio Filesystem device should not have written more | |
// bytes than what we offered buffers for. | |
// | |
if (ResponseSgList == NULL) { | |
BytesPermittedForWrite = 0; | |
} else { | |
BytesPermittedForWrite = ResponseSgList->TotalSize; | |
} | |
if (TotalBytesWrittenByDevice > BytesPermittedForWrite) { | |
Status = EFI_DEVICE_ERROR; | |
goto Unmap; | |
} | |
// | |
// Update the transfer sizes in the IO Vectors. | |
// | |
for (ListId = 0; ListId < ARRAY_SIZE (SgListParam); ListId++) { | |
SgList = SgListParam[ListId]; | |
if (SgList == NULL) { | |
continue; | |
} | |
for (IoVecIdx = 0; IoVecIdx < SgList->NumVec; IoVecIdx++) { | |
IoVec = &SgList->IoVec[IoVecIdx]; | |
if (SgListVirtioMapOp[ListId] == VirtioOperationBusMasterRead) { | |
// | |
// We report that the Virtio Filesystem device has read all buffers in | |
// the request. | |
// | |
IoVec->Transferred = IoVec->Size; | |
} else { | |
// | |
// Regarding the response, calculate how much of the current IO Vector | |
// has been populated by the Virtio Filesystem device. In | |
// "TotalBytesWrittenByDevice", VirtioFlush() reported the total count | |
// across all device-writeable descriptors, in the order they were | |
// chained on the ring. | |
// | |
IoVec->Transferred = MIN ( | |
(UINTN)TotalBytesWrittenByDevice, | |
IoVec->Size | |
); | |
TotalBytesWrittenByDevice -= (UINT32)IoVec->Transferred; | |
} | |
} | |
} | |
// | |
// By now, "TotalBytesWrittenByDevice" has been exhausted. | |
// | |
ASSERT (TotalBytesWrittenByDevice == 0); | |
// | |
// We've succeeded; fall through. | |
// | |
Unmap: | |
// | |
// Unmap all mapped IO Vectors on both the success and the error paths. The | |
// unmapping occurs in reverse order of mapping, in an attempt to avoid | |
// memory fragmentation. | |
// | |
ListId = ARRAY_SIZE (SgListParam); | |
while (ListId > 0) { | |
--ListId; | |
SgList = SgListParam[ListId]; | |
if (SgList == NULL) { | |
continue; | |
} | |
IoVecIdx = SgList->NumVec; | |
while (IoVecIdx > 0) { | |
EFI_STATUS UnmapStatus; | |
--IoVecIdx; | |
IoVec = &SgList->IoVec[IoVecIdx]; | |
// | |
// Unmap this IO Vector, if it has been mapped. | |
// | |
if (!IoVec->Mapped) { | |
continue; | |
} | |
UnmapStatus = VirtioFs->Virtio->UnmapSharedBuffer ( | |
VirtioFs->Virtio, | |
IoVec->Mapping | |
); | |
// | |
// Re-set the following fields to the values they initially got from | |
// VirtioFsSgListsValidate() -- the above unmapping attempt is considered | |
// final, even if it fails. | |
// | |
IoVec->Mapped = FALSE; | |
IoVec->MappedAddress = 0; | |
IoVec->Mapping = NULL; | |
// | |
// If we are on the success path, but the unmapping failed, we need to | |
// transparently flip to the failure path -- the caller must learn they | |
// should not consult the response buffers. | |
// | |
// The branch below can be taken at most once. | |
// | |
if (!EFI_ERROR (Status) && EFI_ERROR (UnmapStatus)) { | |
Status = UnmapStatus; | |
} | |
} | |
} | |
return Status; | |
} | |
/** | |
Set up the fields of a new VIRTIO_FS_FUSE_REQUEST object. | |
The function may only be called after VirtioFsInit() returns successfully and | |
before VirtioFsUninit() is called. | |
@param[in,out] VirtioFs The Virtio Filesystem device that the request is | |
being prepared for. The "VirtioFs->RequestId" field | |
will be copied into "Request->Unique". On output (on | |
successful return), "VirtioFs->RequestId" will be | |
incremented. | |
@param[out] Request The VIRTIO_FS_FUSE_REQUEST object whose fields are to | |
be set. | |
@param[in] RequestSize The total size of the request, including | |
sizeof(VIRTIO_FS_FUSE_REQUEST). | |
@param[in] Opcode The VIRTIO_FS_FUSE_OPCODE that identifies the command | |
to send. | |
@param[in] NodeId The inode number of the file that the request refers | |
to. When Opcode is VirtioFsFuseOpInit, NodeId is | |
ignored by the Virtio Filesystem device. | |
@retval EFI_INVALID_PARAMETER RequestSize is smaller than | |
sizeof(VIRTIO_FS_FUSE_REQUEST). | |
@retval EFI_OUT_OF_RESOURCES "VirtioFs->RequestId" is MAX_UINT64, and can | |
be incremented no more. | |
@retval EFI_SUCCESS Request has been populated, | |
"VirtioFs->RequestId" has been incremented. | |
**/ | |
EFI_STATUS | |
VirtioFsFuseNewRequest ( | |
IN OUT VIRTIO_FS *VirtioFs, | |
OUT VIRTIO_FS_FUSE_REQUEST *Request, | |
IN UINT32 RequestSize, | |
IN VIRTIO_FS_FUSE_OPCODE Opcode, | |
IN UINT64 NodeId | |
) | |
{ | |
if (RequestSize < sizeof *Request) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (VirtioFs->RequestId == MAX_UINT64) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Request->Len = RequestSize; | |
Request->Opcode = Opcode; | |
Request->Unique = VirtioFs->RequestId++; | |
Request->NodeId = NodeId; | |
Request->Uid = 0; | |
Request->Gid = 0; | |
Request->Pid = 1; | |
Request->Padding = 0; | |
return EFI_SUCCESS; | |
} | |
/** | |
Check the common FUSE response format. | |
The first buffer in the response scatter-gather list is assumed a | |
VIRTIO_FS_FUSE_RESPONSE structure. Subsequent response buffers, if any, up to | |
and excluding the last one, are assumed fixed size. The last response buffer | |
may or may not be fixed size, as specified by the caller. | |
This function may only be called after VirtioFsSgListsSubmit() returns | |
successfully. | |
@param[in] ResponseSgList The scatter-gather list that describes the | |
response part of the exchange -- the buffers that | |
the Virtio Filesystem device filled in during the | |
virtio transfer. | |
@param[in] RequestId The request identifier to which the response is | |
expected to belong. | |
@param[out] TailBufferFill If NULL, then the last buffer in ResponseSgList | |
is considered fixed size. Otherwise, the last | |
buffer is considered variable size, and on | |
successful return, TailBufferFill reports the | |
number of bytes in the last buffer. | |
@retval EFI_INVALID_PARAMETER TailBufferFill is not NULL (i.e., the last | |
buffer is considered variable size), and | |
ResponseSgList->NumVec is 1. | |
@retval EFI_INVALID_PARAMETER The allocated size of the first buffer does | |
not match sizeof(VIRTIO_FS_FUSE_RESPONSE). | |
@retval EFI_PROTOCOL_ERROR The VIRTIO_FS_FUSE_RESPONSE structure in the | |
first buffer has not been fully populated. | |
@retval EFI_PROTOCOL_ERROR "VIRTIO_FS_FUSE_RESPONSE.Len" in the first | |
buffer does not equal the sum of the | |
individual buffer sizes (as populated). | |
@retval EFI_PROTOCOL_ERROR "VIRTIO_FS_FUSE_RESPONSE.Unique" in the first | |
buffer does not equal RequestId. | |
@retval EFI_PROTOCOL_ERROR "VIRTIO_FS_FUSE_RESPONSE.Error" in the first | |
buffer is zero, but a subsequent fixed size | |
buffer has not been fully populated. | |
@retval EFI_DEVICE_ERROR "VIRTIO_FS_FUSE_RESPONSE.Error" in the first | |
buffer is nonzero. The caller may investigate | |
"VIRTIO_FS_FUSE_RESPONSE.Error". Note that the | |
completeness of the subsequent fixed size | |
buffers is not verified in this case. | |
@retval EFI_SUCCESS Verification successful. | |
**/ | |
EFI_STATUS | |
VirtioFsFuseCheckResponse ( | |
IN VIRTIO_FS_SCATTER_GATHER_LIST *ResponseSgList, | |
IN UINT64 RequestId, | |
OUT UINTN *TailBufferFill | |
) | |
{ | |
UINTN NumFixedSizeVec; | |
VIRTIO_FS_FUSE_RESPONSE *CommonResp; | |
UINT32 TotalTransferred; | |
UINTN Idx; | |
// | |
// Ensured by VirtioFsSgListsValidate(). | |
// | |
ASSERT (ResponseSgList->NumVec > 0); | |
if (TailBufferFill == NULL) { | |
// | |
// All buffers are considered fixed size. | |
// | |
NumFixedSizeVec = ResponseSgList->NumVec; | |
} else { | |
// | |
// If the last buffer is variable size, then we need that buffer to be | |
// different from the first buffer, which is considered a | |
// VIRTIO_FS_FUSE_RESPONSE (fixed size) structure. | |
// | |
if (ResponseSgList->NumVec == 1) { | |
return EFI_INVALID_PARAMETER; | |
} | |
NumFixedSizeVec = ResponseSgList->NumVec - 1; | |
} | |
// | |
// The first buffer is supposed to carry a (fully populated) | |
// VIRTIO_FS_FUSE_RESPONSE structure. | |
// | |
if (ResponseSgList->IoVec[0].Size != sizeof *CommonResp) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (ResponseSgList->IoVec[0].Transferred != ResponseSgList->IoVec[0].Size) { | |
return EFI_PROTOCOL_ERROR; | |
} | |
// | |
// FUSE must report the same number of bytes, written by the Virtio | |
// Filesystem device, as the virtio transport does. | |
// | |
CommonResp = ResponseSgList->IoVec[0].Buffer; | |
TotalTransferred = 0; | |
for (Idx = 0; Idx < ResponseSgList->NumVec; Idx++) { | |
// | |
// Integer overflow and truncation are not possible, based on | |
// VirtioFsSgListsValidate() and VirtioFsSgListsSubmit(). | |
// | |
TotalTransferred += (UINT32)ResponseSgList->IoVec[Idx].Transferred; | |
} | |
if (CommonResp->Len != TotalTransferred) { | |
return EFI_PROTOCOL_ERROR; | |
} | |
// | |
// Enforce that FUSE match our request ID in the response. | |
// | |
if (CommonResp->Unique != RequestId) { | |
return EFI_PROTOCOL_ERROR; | |
} | |
// | |
// If there is an explicit error report, skip checking the transfer | |
// counts for the rest of the fixed size buffers. | |
// | |
if (CommonResp->Error != 0) { | |
return EFI_DEVICE_ERROR; | |
} | |
// | |
// There was no error reported, so we require that the Virtio Filesystem | |
// device populate all fixed size buffers. We checked this for the very first | |
// buffer above; let's check the rest (if any). | |
// | |
ASSERT (NumFixedSizeVec >= 1); | |
for (Idx = 1; Idx < NumFixedSizeVec; Idx++) { | |
if (ResponseSgList->IoVec[Idx].Transferred != | |
ResponseSgList->IoVec[Idx].Size) | |
{ | |
return EFI_PROTOCOL_ERROR; | |
} | |
} | |
// | |
// If the last buffer is considered variable size, report its filled size. | |
// | |
if (TailBufferFill != NULL) { | |
*TailBufferFill = ResponseSgList->IoVec[NumFixedSizeVec].Transferred; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
An ad-hoc function for mapping FUSE (well, Linux) "errno" values to | |
EFI_STATUS. | |
@param[in] Errno The "VIRTIO_FS_FUSE_RESPONSE.Error" value, returned by the | |
Virtio Filesystem device. The value is expected to be | |
negative. | |
@return An EFI_STATUS error code that's deemed a passable | |
mapping for the Errno value. | |
@retval EFI_DEVICE_ERROR Fallback EFI_STATUS code for unrecognized Errno | |
values. | |
**/ | |
EFI_STATUS | |
VirtioFsErrnoToEfiStatus ( | |
IN INT32 Errno | |
) | |
{ | |
switch (Errno) { | |
case -1:// EPERM Operation not permitted | |
return EFI_SECURITY_VIOLATION; | |
case -2: // ENOENT No such file or directory | |
case -3: // ESRCH No such process | |
case -6: // ENXIO No such device or address | |
case -10: // ECHILD No child processes | |
case -19: // ENODEV No such device | |
case -49: // EUNATCH Protocol driver not attached | |
case -65: // ENOPKG Package not installed | |
case -79: // ELIBACC Can not access a needed shared library | |
case -126: // ENOKEY Required key not available | |
return EFI_NOT_FOUND; | |
case -4: // EINTR Interrupted system call | |
case -11: // EAGAIN, EWOULDBLOCK Resource temporarily unavailable | |
case -16: // EBUSY Device or resource busy | |
case -26: // ETXTBSY Text file busy | |
case -35: // EDEADLK, EDEADLOCK Resource deadlock avoided | |
case -39: // ENOTEMPTY Directory not empty | |
case -42: // ENOMSG No message of desired type | |
case -61: // ENODATA No data available | |
case -85: // ERESTART Interrupted system call should be restarted | |
return EFI_NOT_READY; | |
case -5: // EIO Input/output error | |
case -45: // EL2NSYNC Level 2 not synchronized | |
case -46: // EL3HLT Level 3 halted | |
case -47: // EL3RST Level 3 reset | |
case -51: // EL2HLT Level 2 halted | |
case -121: // EREMOTEIO Remote I/O error | |
case -133: // EHWPOISON Memory page has hardware error | |
return EFI_DEVICE_ERROR; | |
case -7: // E2BIG Argument list too long | |
case -36: // ENAMETOOLONG File name too long | |
case -90: // EMSGSIZE Message too long | |
return EFI_BAD_BUFFER_SIZE; | |
case -8: // ENOEXEC Exec format error | |
case -15: // ENOTBLK Block device required | |
case -18: // EXDEV Invalid cross-device link | |
case -20: // ENOTDIR Not a directory | |
case -21: // EISDIR Is a directory | |
case -25: // ENOTTY Inappropriate ioctl for device | |
case -27: // EFBIG File too large | |
case -29: // ESPIPE Illegal seek | |
case -38: // ENOSYS Function not implemented | |
case -59: // EBFONT Bad font file format | |
case -60: // ENOSTR Device not a stream | |
case -83: // ELIBEXEC Cannot exec a shared library directly | |
case -88: // ENOTSOCK Socket operation on non-socket | |
case -91: // EPROTOTYPE Protocol wrong type for socket | |
case -92: // ENOPROTOOPT Protocol not available | |
case -93: // EPROTONOSUPPORT Protocol not supported | |
case -94: // ESOCKTNOSUPPORT Socket type not supported | |
case -95: // ENOTSUP, EOPNOTSUPP Operation not supported | |
case -96: // EPFNOSUPPORT Protocol family not supported | |
case -97: // EAFNOSUPPORT Address family not supported by protocol | |
case -99: // EADDRNOTAVAIL Cannot assign requested address | |
case -118: // ENOTNAM Not a XENIX named type file | |
case -120: // EISNAM Is a named type file | |
case -124: // EMEDIUMTYPE Wrong medium type | |
return EFI_UNSUPPORTED; | |
case -9: // EBADF Bad file descriptor | |
case -14: // EFAULT Bad address | |
case -44: // ECHRNG Channel number out of range | |
case -48: // ELNRNG Link number out of range | |
case -53: // EBADR Invalid request descriptor | |
case -56: // EBADRQC Invalid request code | |
case -57: // EBADSLT Invalid slot | |
case -76: // ENOTUNIQ Name not unique on network | |
case -84: // EILSEQ Invalid or incomplete multibyte or wide character | |
return EFI_NO_MAPPING; | |
case -12: // ENOMEM Cannot allocate memory | |
case -23: // ENFILE Too many open files in system | |
case -24: // EMFILE Too many open files | |
case -31: // EMLINK Too many links | |
case -37: // ENOLCK No locks available | |
case -40: // ELOOP Too many levels of symbolic links | |
case -50: // ENOCSI No CSI structure available | |
case -55: // ENOANO No anode | |
case -63: // ENOSR Out of streams resources | |
case -82: // ELIBMAX Attempting to link in too many shared libraries | |
case -87: // EUSERS Too many users | |
case -105: // ENOBUFS No buffer space available | |
case -109: // ETOOMANYREFS Too many references: cannot splice | |
case -119: // ENAVAIL No XENIX semaphores available | |
case -122: // EDQUOT Disk quota exceeded | |
return EFI_OUT_OF_RESOURCES; | |
case -13:// EACCES Permission denied | |
return EFI_ACCESS_DENIED; | |
case -17: // EEXIST File exists | |
case -98: // EADDRINUSE Address already in use | |
case -106: // EISCONN Transport endpoint is already connected | |
case -114: // EALREADY Operation already in progress | |
case -115: // EINPROGRESS Operation now in progress | |
return EFI_ALREADY_STARTED; | |
case -22: // EINVAL Invalid argument | |
case -33: // EDOM Numerical argument out of domain | |
return EFI_INVALID_PARAMETER; | |
case -28: // ENOSPC No space left on device | |
case -54: // EXFULL Exchange full | |
return EFI_VOLUME_FULL; | |
case -30:// EROFS Read-only file system | |
return EFI_WRITE_PROTECTED; | |
case -32: // EPIPE Broken pipe | |
case -43: // EIDRM Identifier removed | |
case -67: // ENOLINK Link has been severed | |
case -68: // EADV Advertise error | |
case -69: // ESRMNT Srmount error | |
case -70: // ECOMM Communication error on send | |
case -73: // EDOTDOT RFS specific error | |
case -78: // EREMCHG Remote address changed | |
case -86: // ESTRPIPE Streams pipe error | |
case -102: // ENETRESET Network dropped connection on reset | |
case -103: // ECONNABORTED Software caused connection abort | |
case -104: // ECONNRESET Connection reset by peer | |
case -116: // ESTALE Stale file handle | |
case -125: // ECANCELED Operation canceled | |
case -128: // EKEYREVOKED Key has been revoked | |
case -129: // EKEYREJECTED Key was rejected by service | |
case -130: // EOWNERDEAD Owner died | |
case -131: // ENOTRECOVERABLE State not recoverable | |
return EFI_ABORTED; | |
case -34: // ERANGE Numerical result out of range | |
case -75: // EOVERFLOW Value too large for defined data type | |
return EFI_BUFFER_TOO_SMALL; | |
case -52: // EBADE Invalid exchange | |
case -108: // ESHUTDOWN Cannot send after transport endpoint shutdown | |
case -111: // ECONNREFUSED Connection refused | |
return EFI_END_OF_FILE; | |
case -62: // ETIME Timer expired | |
case -110: // ETIMEDOUT Connection timed out | |
case -127: // EKEYEXPIRED Key has expired | |
return EFI_TIMEOUT; | |
case -64: // ENONET Machine is not on the network | |
case -66: // EREMOTE Object is remote | |
case -72: // EMULTIHOP Multihop attempted | |
case -100: // ENETDOWN Network is down | |
case -101: // ENETUNREACH Network is unreachable | |
case -112: // EHOSTDOWN Host is down | |
case -113: // EHOSTUNREACH No route to host | |
case -123: // ENOMEDIUM No medium found | |
case -132: // ERFKILL Operation not possible due to RF-kill | |
return EFI_NO_MEDIA; | |
case -71:// EPROTO Protocol error | |
return EFI_PROTOCOL_ERROR; | |
case -74: // EBADMSG Bad message | |
case -77: // EBADFD File descriptor in bad state | |
case -80: // ELIBBAD Accessing a corrupted shared library | |
case -81: // ELIBSCN .lib section in a.out corrupted | |
case -117: // EUCLEAN Structure needs cleaning | |
return EFI_VOLUME_CORRUPTED; | |
case -89: // EDESTADDRREQ Destination address required | |
case -107: // ENOTCONN Transport endpoint is not connected | |
return EFI_NOT_STARTED; | |
default: | |
break; | |
} | |
return EFI_DEVICE_ERROR; | |
} | |
// | |
// Parser states for canonicalizing a POSIX pathname. | |
// | |
typedef enum { | |
ParserInit, // just starting | |
ParserEnd, // finished | |
ParserSlash, // slash(es) seen | |
ParserDot, // one dot seen since last slash | |
ParserDotDot, // two dots seen since last slash | |
ParserNormal, // a different sequence seen | |
} PARSER_STATE; | |
/** | |
Strip the trailing slash from the parser's output buffer, unless the trailing | |
slash stands for the root directory. | |
@param[in] Buffer The parser's output buffer. Only used for | |
sanity-checking. | |
@param[in,out] Position On entry, points at the next character to produce | |
(i.e., right past the end of the output written by | |
the parser thus far). The last character in the | |
parser's output buffer is a slash. On return, the | |
slash is stripped, by decrementing Position by one. | |
If this action would remove the slash character | |
standing for the root directory, then the function | |
has no effect. | |
**/ | |
STATIC | |
VOID | |
ParserStripSlash ( | |
IN CHAR8 *Buffer, | |
IN OUT UINTN *Position | |
) | |
{ | |
ASSERT (*Position >= 1); | |
ASSERT (Buffer[*Position - 1] == '/'); | |
if (*Position == 1) { | |
return; | |
} | |
(*Position)--; | |
} | |
/** | |
Produce one character in the parser's output buffer. | |
@param[out] Buffer The parser's output buffer. On return, Char8 will | |
have been written. | |
@param[in,out] Position On entry, points at the next character to produce | |
(i.e., right past the end of the output written by | |
the parser thus far). On return, Position is | |
incremented by one. | |
@param[in] Size Total allocated size of the parser's output buffer. | |
Used for sanity-checking. | |
@param[in] Char8 The character to place in the output buffer. | |
**/ | |
STATIC | |
VOID | |
ParserCopy ( | |
OUT CHAR8 *Buffer, | |
IN OUT UINTN *Position, | |
IN UINTN Size, | |
IN CHAR8 Char8 | |
) | |
{ | |
ASSERT (*Position < Size); | |
Buffer[(*Position)++] = Char8; | |
} | |
/** | |
Rewind the last single-dot in the parser's output buffer. | |
@param[in] Buffer The parser's output buffer. Only used for | |
sanity-checking. | |
@param[in,out] Position On entry, points at the next character to produce | |
(i.e., right past the end of the output written by | |
the parser thus far); the parser's output buffer | |
ends with the characters ('/', '.'). On return, the | |
dot is rewound by decrementing Position by one; a | |
slash character will reside at the new end of the | |
parser's output buffer. | |
**/ | |
STATIC | |
VOID | |
ParserRewindDot ( | |
IN CHAR8 *Buffer, | |
IN OUT UINTN *Position | |
) | |
{ | |
ASSERT (*Position >= 2); | |
ASSERT (Buffer[*Position - 1] == '.'); | |
ASSERT (Buffer[*Position - 2] == '/'); | |
(*Position)--; | |
} | |
/** | |
Rewind the last dot-dot in the parser's output buffer. | |
@param[in] Buffer The parser's output buffer. Only used for | |
sanity-checking. | |
@param[in,out] Position On entry, points at the next character to produce | |
(i.e., right past the end of the output written by | |
the parser thus far); the parser's output buffer | |
ends with the characters ('/', '.', '.'). On return, | |
the ('.', '.') pair is rewound unconditionally, by | |
decrementing Position by two; a slash character | |
resides at the new end of the parser's output | |
buffer. | |
If this slash character stands for the root | |
directory, then RootEscape is set to TRUE. | |
Otherwise (i.e., if this slash character is not the | |
one standing for the root directory), then the slash | |
character, and the pathname component preceding it, | |
are removed by decrementing Position further. In | |
this case, the slash character preceding the removed | |
pathname component will reside at the new end of the | |
parser's output buffer. | |
@param[out] RootEscape Set to TRUE on output if the dot-dot component tries | |
to escape the root directory, as described above. | |
Otherwise, RootEscape is not modified. | |
**/ | |
STATIC | |
VOID | |
ParserRewindDotDot ( | |
IN CHAR8 *Buffer, | |
IN OUT UINTN *Position, | |
OUT BOOLEAN *RootEscape | |
) | |
{ | |
ASSERT (*Position >= 3); | |
ASSERT (Buffer[*Position - 1] == '.'); | |
ASSERT (Buffer[*Position - 2] == '.'); | |
ASSERT (Buffer[*Position - 3] == '/'); | |
(*Position) -= 2; | |
if (*Position == 1) { | |
// | |
// Root directory slash reached; don't try to climb higher. | |
// | |
*RootEscape = TRUE; | |
return; | |
} | |
// | |
// Skip slash. | |
// | |
(*Position)--; | |
// | |
// Scan until next slash to the left. | |
// | |
do { | |
ASSERT (*Position > 0); | |
(*Position)--; | |
} while (Buffer[*Position] != '/'); | |
(*Position)++; | |
} | |
/** | |
Append the UEFI-style RhsPath16 to the POSIX-style, canonical format | |
LhsPath8. Output the POSIX-style, canonical format result in ResultPath, as a | |
dynamically allocated string. | |
Canonicalization (aka sanitization) establishes the following properties: | |
- ResultPath is absolute (starts with "/"), | |
- dot (.) and dot-dot (..) components are resolved/eliminated in ResultPath, | |
with the usual semantics, | |
- ResultPath uses forward slashes, | |
- sequences of slashes are squashed in ResultPath, | |
- the printable ASCII character set covers ResultPath, | |
- CHAR8 encoding is used in ResultPath, | |
- no trailing slash present in ResultPath except for the standalone root | |
directory, | |
- the length of ResultPath is at most VIRTIO_FS_MAX_PATHNAME_LENGTH. | |
Any dot-dot in RhsPath16 that would remove the root directory is dropped, and | |
reported through RootEscape, without failing the function call. | |
@param[in] LhsPath8 Identifies the base directory. The caller is | |
responsible for ensuring that LhsPath8 conform to | |
the above canonical pathname format on entry. | |
@param[in] RhsPath16 Identifies the desired file with a UEFI-style CHAR16 | |
pathname. If RhsPath16 starts with a backslash, then | |
RhsPath16 is considered absolute, and LhsPath8 is | |
ignored; RhsPath16 is sanitized in isolation, for | |
producing ResultPath8. Otherwise (i.e., if RhsPath16 | |
is relative), RhsPath16 is transliterated to CHAR8, | |
and naively appended to LhsPath8. The resultant | |
fused pathname is then sanitized, to produce | |
ResultPath8. | |
@param[out] ResultPath8 The POSIX-style, canonical format pathname that | |
leads to the file desired by the caller. After use, | |
the caller is responsible for freeing ResultPath8. | |
@param[out] RootEscape Set to TRUE if at least one dot-dot component in | |
RhsPath16 attempted to escape the root directory; | |
set to FALSE otherwise. | |
@retval EFI_SUCCESS ResultPath8 has been produced. RootEscape has | |
been output. | |
@retval EFI_INVALID_PARAMETER RhsPath16 is zero-length. | |
@retval EFI_INVALID_PARAMETER RhsPath16 failed the | |
VIRTIO_FS_MAX_PATHNAME_LENGTH check. | |
@retval EFI_OUT_OF_RESOURCES Memory allocation failed. | |
@retval EFI_OUT_OF_RESOURCES ResultPath8 would have failed the | |
VIRTIO_FS_MAX_PATHNAME_LENGTH check. | |
@retval EFI_UNSUPPORTED RhsPath16 contains a character that either | |
falls outside of the printable ASCII set, or | |
is a forward slash. | |
**/ | |
EFI_STATUS | |
VirtioFsAppendPath ( | |
IN CHAR8 *LhsPath8, | |
IN CHAR16 *RhsPath16, | |
OUT CHAR8 **ResultPath8, | |
OUT BOOLEAN *RootEscape | |
) | |
{ | |
UINTN RhsLen; | |
CHAR8 *RhsPath8; | |
UINTN Idx; | |
EFI_STATUS Status; | |
UINTN SizeToSanitize; | |
CHAR8 *BufferToSanitize; | |
CHAR8 *SanitizedBuffer; | |
PARSER_STATE State; | |
UINTN SanitizedPosition; | |
// | |
// Appending an empty pathname is not allowed. | |
// | |
RhsLen = StrLen (RhsPath16); | |
if (RhsLen == 0) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Enforce length restriction on RhsPath16. | |
// | |
if (RhsLen > VIRTIO_FS_MAX_PATHNAME_LENGTH) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Transliterate RhsPath16 to RhsPath8 by: | |
// - rejecting RhsPath16 if a character outside of printable ASCII is seen, | |
// - rejecting RhsPath16 if a forward slash is seen, | |
// - replacing backslashes with forward slashes, | |
// - casting the characters from CHAR16 to CHAR8. | |
// | |
RhsPath8 = AllocatePool (RhsLen + 1); | |
if (RhsPath8 == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
for (Idx = 0; RhsPath16[Idx] != L'\0'; Idx++) { | |
if ((RhsPath16[Idx] < 0x20) || (RhsPath16[Idx] > 0x7E) || | |
(RhsPath16[Idx] == L'/')) | |
{ | |
Status = EFI_UNSUPPORTED; | |
goto FreeRhsPath8; | |
} | |
RhsPath8[Idx] = (CHAR8)((RhsPath16[Idx] == L'\\') ? L'/' : RhsPath16[Idx]); | |
} | |
RhsPath8[Idx++] = '\0'; | |
// | |
// Now prepare the input for the canonicalization (squashing of sequences of | |
// forward slashes, and eliminating . (dot) and .. (dot-dot) pathname | |
// components). | |
// | |
// The sanitized path can never be longer than the naive concatenation of the | |
// left hand side and right hand side paths, so we'll use the catenated size | |
// for allocating the sanitized output too. | |
// | |
if (RhsPath8[0] == '/') { | |
// | |
// If the right hand side path is absolute, then it is not appended to the | |
// left hand side path -- it *replaces* the left hand side path. | |
// | |
SizeToSanitize = RhsLen + 1; | |
BufferToSanitize = RhsPath8; | |
} else { | |
// | |
// If the right hand side path is relative, then it is appended (naively) | |
// to the left hand side. | |
// | |
UINTN LhsLen; | |
LhsLen = AsciiStrLen (LhsPath8); | |
SizeToSanitize = LhsLen + 1 + RhsLen + 1; | |
BufferToSanitize = AllocatePool (SizeToSanitize); | |
if (BufferToSanitize == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeRhsPath8; | |
} | |
CopyMem (BufferToSanitize, LhsPath8, LhsLen); | |
BufferToSanitize[LhsLen] = '/'; | |
CopyMem (BufferToSanitize + LhsLen + 1, RhsPath8, RhsLen + 1); | |
} | |
// | |
// Allocate the output buffer. | |
// | |
SanitizedBuffer = AllocatePool (SizeToSanitize); | |
if (SanitizedBuffer == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeBufferToSanitize; | |
} | |
// | |
// State machine for parsing the input and producing the canonical output | |
// follows. | |
// | |
*RootEscape = FALSE; | |
Idx = 0; | |
State = ParserInit; | |
SanitizedPosition = 0; | |
do { | |
CHAR8 Chr8; | |
ASSERT (Idx < SizeToSanitize); | |
Chr8 = BufferToSanitize[Idx++]; | |
switch (State) { | |
case ParserInit: // just starting | |
ASSERT (Chr8 == '/'); | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserSlash; | |
break; | |
case ParserSlash: // slash(es) seen | |
switch (Chr8) { | |
case '\0': | |
ParserStripSlash (SanitizedBuffer, &SanitizedPosition); | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserEnd; | |
break; | |
case '/': | |
// | |
// skip & stay in same state | |
// | |
break; | |
case '.': | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserDot; | |
break; | |
default: | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserNormal; | |
break; | |
} | |
break; | |
case ParserDot: // one dot seen since last slash | |
switch (Chr8) { | |
case '\0': | |
ParserRewindDot (SanitizedBuffer, &SanitizedPosition); | |
ParserStripSlash (SanitizedBuffer, &SanitizedPosition); | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserEnd; | |
break; | |
case '/': | |
ParserRewindDot (SanitizedBuffer, &SanitizedPosition); | |
State = ParserSlash; | |
break; | |
case '.': | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserDotDot; | |
break; | |
default: | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserNormal; | |
break; | |
} | |
break; | |
case ParserDotDot: // two dots seen since last slash | |
switch (Chr8) { | |
case '\0': | |
ParserRewindDotDot (SanitizedBuffer, &SanitizedPosition, RootEscape); | |
ParserStripSlash (SanitizedBuffer, &SanitizedPosition); | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserEnd; | |
break; | |
case '/': | |
ParserRewindDotDot (SanitizedBuffer, &SanitizedPosition, RootEscape); | |
State = ParserSlash; | |
break; | |
case '.': | |
// | |
// fall through | |
// | |
default: | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserNormal; | |
break; | |
} | |
break; | |
case ParserNormal: // a different sequence seen | |
switch (Chr8) { | |
case '\0': | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserEnd; | |
break; | |
case '/': | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
State = ParserSlash; | |
break; | |
case '.': | |
// | |
// fall through | |
// | |
default: | |
// | |
// copy and stay in same state | |
// | |
ParserCopy (SanitizedBuffer, &SanitizedPosition, SizeToSanitize, Chr8); | |
break; | |
} | |
break; | |
default: | |
ASSERT (FALSE); | |
break; | |
} | |
} while (State != ParserEnd); | |
// | |
// Ensure length invariant on ResultPath8. | |
// | |
ASSERT (SanitizedPosition >= 2); | |
if (SanitizedPosition - 1 > VIRTIO_FS_MAX_PATHNAME_LENGTH) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeSanitizedBuffer; | |
} | |
*ResultPath8 = SanitizedBuffer; | |
SanitizedBuffer = NULL; | |
Status = EFI_SUCCESS; | |
// | |
// Fall through. | |
// | |
FreeSanitizedBuffer: | |
if (SanitizedBuffer != NULL) { | |
FreePool (SanitizedBuffer); | |
} | |
FreeBufferToSanitize: | |
if (RhsPath8[0] != '/') { | |
FreePool (BufferToSanitize); | |
} | |
FreeRhsPath8: | |
FreePool (RhsPath8); | |
return Status; | |
} | |
/** | |
For a given canonical pathname (as defined at VirtioFsAppendPath()), look up | |
the NodeId of the most specific parent directory, plus output a pointer to | |
the last pathname component (which is therefore a direct child of said parent | |
directory). | |
The function may only be called after VirtioFsFuseInitSession() returns | |
successfully and before VirtioFsUninit() is called. | |
@param[in,out] VirtioFs The Virtio Filesystem device to send FUSE_LOOKUP | |
and FUSE_FORGET requests to. On output, the FUSE | |
request counter "VirtioFs->RequestId" will have | |
been incremented several times. | |
@param[in,out] Path The canonical pathname (as defined in the | |
description of VirtioFsAppendPath()) to split. | |
Path is modified in-place temporarily; however, on | |
return (successful or otherwise), Path reassumes | |
its original contents. | |
@param[out] DirNodeId The NodeId of the most specific parent directory | |
identified by Path. The caller is responsible for | |
sending a FUSE_FORGET request to the Virtio | |
Filesystem device for DirNodeId -- unless | |
DirNodeId equals VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID | |
--, when DirNodeId's use ends. | |
@param[out] LastComponent A pointer into Path, pointing at the start of the | |
last pathname component. | |
@retval EFI_SUCCESS Splitting successful. | |
@retval EFI_INVALID_PARAMETER Path is "/". | |
@retval EFI_ACCESS_DENIED One of the components on Path before the last | |
is not a directory. | |
@return Error codes propagated from | |
VirtioFsFuseLookup() and | |
VirtioFsFuseAttrToEfiFileInfo(). | |
**/ | |
EFI_STATUS | |
VirtioFsLookupMostSpecificParentDir ( | |
IN OUT VIRTIO_FS *VirtioFs, | |
IN OUT CHAR8 *Path, | |
OUT UINT64 *DirNodeId, | |
OUT CHAR8 **LastComponent | |
) | |
{ | |
UINT64 ParentDirNodeId; | |
CHAR8 *Slash; | |
EFI_STATUS Status; | |
UINT64 NextDirNodeId; | |
if (AsciiStrCmp (Path, "/") == 0) { | |
return EFI_INVALID_PARAMETER; | |
} | |
ParentDirNodeId = VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID; | |
Slash = Path; | |
for ( ; ;) { | |
CHAR8 *NextSlash; | |
VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr; | |
EFI_FILE_INFO FileInfo; | |
// | |
// Find the slash (if any) that terminates the next pathname component. | |
// | |
NextSlash = AsciiStrStr (Slash + 1, "/"); | |
if (NextSlash == NULL) { | |
break; | |
} | |
// | |
// Temporarily replace the found slash character with a NUL in-place, for | |
// easy construction of the single-component filename that we need to look | |
// up. | |
// | |
*NextSlash = '\0'; | |
Status = VirtioFsFuseLookup ( | |
VirtioFs, | |
ParentDirNodeId, | |
Slash + 1, | |
&NextDirNodeId, | |
&FuseAttr | |
); | |
*NextSlash = '/'; | |
// | |
// We're done with the directory inode that was the basis for the lookup. | |
// | |
if (ParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) { | |
VirtioFsFuseForget (VirtioFs, ParentDirNodeId); | |
} | |
// | |
// If we couldn't look up the next *non-final* pathname component, bail. | |
// | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Lookup successful; now check if the next (non-final) component is a | |
// directory. If not, bail. | |
// | |
Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo); | |
if (EFI_ERROR (Status)) { | |
goto ForgetNextDirNodeId; | |
} | |
if ((FileInfo.Attribute & EFI_FILE_DIRECTORY) == 0) { | |
Status = EFI_ACCESS_DENIED; | |
goto ForgetNextDirNodeId; | |
} | |
// | |
// Advance. | |
// | |
ParentDirNodeId = NextDirNodeId; | |
Slash = NextSlash; | |
} | |
// | |
// ParentDirNodeId corresponds to the last containing directory. The | |
// remaining single-component filename represents a direct child under that | |
// directory. Said filename starts at (Slash + 1). | |
// | |
*DirNodeId = ParentDirNodeId; | |
*LastComponent = Slash + 1; | |
return EFI_SUCCESS; | |
ForgetNextDirNodeId: | |
VirtioFsFuseForget (VirtioFs, NextDirNodeId); | |
return Status; | |
} | |
/** | |
Format the last component of a canonical pathname into a caller-provided | |
CHAR16 array. | |
@param[in] Path The canonical pathname (as defined in the | |
description of VirtioFsAppendPath()) to format | |
the last component of. | |
@param[out] Basename If BasenameSize is zero on input, Basename may | |
be NULL. Otherwise, Basename is allocated by the | |
caller. On successful return, Basename contains | |
the last component of Path, formatted as a | |
NUL-terminated CHAR16 string. When Path is "/" | |
on input, Basename is L"" on output. | |
@param[in,out] BasenameSize On input, the number of bytes the caller | |
provides in Basename. On output, regardless of | |
return value, the number of bytes required for | |
formatting Basename, including the terminating | |
L'\0'. | |
@retval EFI_SUCCESS Basename has been filled in. | |
@retval EFI_BUFFER_TOO_SMALL BasenameSize was too small on input; Basename | |
has not been modified. | |
**/ | |
EFI_STATUS | |
VirtioFsGetBasename ( | |
IN CHAR8 *Path, | |
OUT CHAR16 *Basename OPTIONAL, | |
IN OUT UINTN *BasenameSize | |
) | |
{ | |
UINTN AllocSize; | |
UINTN LastComponent; | |
UINTN Idx; | |
UINTN PathSize; | |
AllocSize = *BasenameSize; | |
LastComponent = MAX_UINTN; | |
for (Idx = 0; Path[Idx] != '\0'; Idx++) { | |
if (Path[Idx] == '/') { | |
LastComponent = Idx; | |
} | |
} | |
PathSize = Idx + 1; | |
ASSERT (LastComponent < MAX_UINTN); | |
LastComponent++; | |
*BasenameSize = (PathSize - LastComponent) * sizeof Basename[0]; | |
if (*BasenameSize > AllocSize) { | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
for (Idx = LastComponent; Idx < PathSize; Idx++) { | |
Basename[Idx - LastComponent] = Path[Idx]; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Format the destination of a rename/move operation as a dynamically allocated | |
canonical pathname. | |
Any dot-dot in RhsPath16 that would remove the root directory is dropped, and | |
reported through RootEscape, without failing the function call. | |
@param[in] LhsPath8 The source pathname operand of the rename/move | |
operation, expressed as a canonical pathname (as | |
defined in the description of VirtioFsAppendPath()). | |
The root directory "/" cannot be renamed/moved, and | |
will be rejected. | |
@param[in] RhsPath16 The destination pathname operand expressed as a | |
UEFI-style CHAR16 pathname. | |
If RhsPath16 starts with a backslash, then RhsPath16 | |
is considered absolute. Otherwise, RhsPath16 is | |
interpreted relative to the most specific parent | |
directory found in LhsPath8. | |
Independently, if RhsPath16 ends with a backslash | |
(i.e., RhsPath16 is given in the "move into | |
directory" convenience form), then RhsPath16 is | |
interpreted with the basename of LhsPath8 appended. | |
Otherwise, the last pathname component of RhsPath16 | |
is taken as the last pathname component of the | |
rename/move destination. | |
An empty RhsPath16 is rejected. | |
@param[out] ResultPath8 The POSIX-style, canonical format pathname that | |
leads to the renamed/moved file. After use, the | |
caller is responsible for freeing ResultPath8. | |
@param[out] RootEscape Set to TRUE if at least one dot-dot component in | |
RhsPath16 attempted to escape the root directory; | |
set to FALSE otherwise. | |
@retval EFI_SUCCESS ResultPath8 has been produced. RootEscape has | |
been output. | |
@retval EFI_INVALID_PARAMETER LhsPath8 is "/". | |
@retval EFI_INVALID_PARAMETER RhsPath16 is zero-length. | |
@retval EFI_INVALID_PARAMETER RhsPath16 failed the | |
VIRTIO_FS_MAX_PATHNAME_LENGTH check. | |
@retval EFI_OUT_OF_RESOURCES Memory allocation failed. | |
@retval EFI_OUT_OF_RESOURCES ResultPath8 would have failed the | |
VIRTIO_FS_MAX_PATHNAME_LENGTH check. | |
@retval EFI_UNSUPPORTED RhsPath16 contains a character that either | |
falls outside of the printable ASCII set, or | |
is a forward slash. | |
**/ | |
EFI_STATUS | |
VirtioFsComposeRenameDestination ( | |
IN CHAR8 *LhsPath8, | |
IN CHAR16 *RhsPath16, | |
OUT CHAR8 **ResultPath8, | |
OUT BOOLEAN *RootEscape | |
) | |
{ | |
// | |
// Lengths are expressed as numbers of characters (CHAR8 or CHAR16), | |
// excluding terminating NULs. Sizes are expressed as byte counts, including | |
// the bytes taken up by terminating NULs. | |
// | |
UINTN RhsLen; | |
UINTN LhsBasename16Size; | |
EFI_STATUS Status; | |
UINTN LhsBasenameLen; | |
UINTN DestSuffix16Size; | |
CHAR16 *DestSuffix16; | |
CHAR8 *DestPrefix8; | |
// | |
// An empty destination operand for the rename/move operation is not allowed. | |
// | |
RhsLen = StrLen (RhsPath16); | |
if (RhsLen == 0) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Enforce length restriction on RhsPath16. | |
// | |
if (RhsLen > VIRTIO_FS_MAX_PATHNAME_LENGTH) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Determine the length of the basename of LhsPath8. | |
// | |
LhsBasename16Size = 0; | |
Status = VirtioFsGetBasename (LhsPath8, NULL, &LhsBasename16Size); | |
ASSERT (Status == EFI_BUFFER_TOO_SMALL); | |
ASSERT (LhsBasename16Size >= sizeof (CHAR16)); | |
ASSERT (LhsBasename16Size % sizeof (CHAR16) == 0); | |
LhsBasenameLen = LhsBasename16Size / sizeof (CHAR16) - 1; | |
if (LhsBasenameLen == 0) { | |
// | |
// The root directory cannot be renamed/moved. | |
// | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Resolve the "move into directory" convenience form in RhsPath16. | |
// | |
if (RhsPath16[RhsLen - 1] == L'\\') { | |
// | |
// Append the basename of LhsPath8 as a CHAR16 string to RhsPath16. | |
// | |
DestSuffix16Size = RhsLen * sizeof (CHAR16) + LhsBasename16Size; | |
DestSuffix16 = AllocatePool (DestSuffix16Size); | |
if (DestSuffix16 == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (DestSuffix16, RhsPath16, RhsLen * sizeof (CHAR16)); | |
Status = VirtioFsGetBasename ( | |
LhsPath8, | |
DestSuffix16 + RhsLen, | |
&LhsBasename16Size | |
); | |
ASSERT_EFI_ERROR (Status); | |
} else { | |
// | |
// Just create a copy of RhsPath16. | |
// | |
DestSuffix16Size = (RhsLen + 1) * sizeof (CHAR16); | |
DestSuffix16 = AllocateCopyPool (DestSuffix16Size, RhsPath16); | |
if (DestSuffix16 == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
} | |
// | |
// If the destination operand is absolute, it will be interpreted relative to | |
// the root directory. | |
// | |
// Otherwise (i.e., if the destination operand is relative), then create the | |
// canonical pathname that the destination operand is interpreted relatively | |
// to; that is, the canonical pathname of the most specific parent directory | |
// found in LhsPath8. | |
// | |
if (DestSuffix16[0] == L'\\') { | |
DestPrefix8 = AllocateCopyPool (sizeof "/", "/"); | |
if (DestPrefix8 == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeDestSuffix16; | |
} | |
} else { | |
UINTN LhsLen; | |
UINTN DestPrefixLen; | |
// | |
// Strip the basename of LhsPath8. | |
// | |
LhsLen = AsciiStrLen (LhsPath8); | |
ASSERT (LhsBasenameLen < LhsLen); | |
DestPrefixLen = LhsLen - LhsBasenameLen; | |
ASSERT (LhsPath8[DestPrefixLen - 1] == '/'); | |
// | |
// If we're not at the root directory, strip the slash too. | |
// | |
if (DestPrefixLen > 1) { | |
DestPrefixLen--; | |
} | |
DestPrefix8 = AllocatePool (DestPrefixLen + 1); | |
if (DestPrefix8 == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto FreeDestSuffix16; | |
} | |
CopyMem (DestPrefix8, LhsPath8, DestPrefixLen); | |
DestPrefix8[DestPrefixLen] = '\0'; | |
} | |
// | |
// Now combine DestPrefix8 and DestSuffix16 into the final canonical | |
// pathname. | |
// | |
Status = VirtioFsAppendPath ( | |
DestPrefix8, | |
DestSuffix16, | |
ResultPath8, | |
RootEscape | |
); | |
FreePool (DestPrefix8); | |
// | |
// Fall through. | |
// | |
FreeDestSuffix16: | |
FreePool (DestSuffix16); | |
return Status; | |
} | |
/** | |
Convert select fields of a VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE object to | |
corresponding fields in EFI_FILE_INFO. | |
@param[in] FuseAttr The VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE object to | |
convert the relevant fields from. | |
@param[out] FileInfo The EFI_FILE_INFO structure to modify. Importantly, the | |
FileInfo->Size and FileInfo->FileName fields are not | |
overwritten. | |
@retval EFI_SUCCESS Conversion successful. | |
@retval EFI_UNSUPPORTED The allocated size of the file is inexpressible in | |
EFI_FILE_INFO. | |
@retval EFI_UNSUPPORTED One of the file access times is inexpressible in | |
EFI_FILE_INFO. | |
@retval EFI_UNSUPPORTED The file type is inexpressible in EFI_FILE_INFO. | |
@retval EFI_UNSUPPORTED The file is a regular file that has multiple names | |
on the host side (i.e., its hard link count is | |
greater than one). | |
**/ | |
EFI_STATUS | |
VirtioFsFuseAttrToEfiFileInfo ( | |
IN VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE *FuseAttr, | |
OUT EFI_FILE_INFO *FileInfo | |
) | |
{ | |
UINT64 EpochTime[3]; | |
EFI_TIME *ConvertedTime[ARRAY_SIZE (EpochTime)]; | |
UINTN Idx; | |
FileInfo->FileSize = FuseAttr->Size; | |
// | |
// The unit for FuseAttr->Blocks is 512B. | |
// | |
if (FuseAttr->Blocks >= BIT55) { | |
return EFI_UNSUPPORTED; | |
} | |
FileInfo->PhysicalSize = LShiftU64 (FuseAttr->Blocks, 9); | |
// | |
// Convert the timestamps. File creation time is not tracked by the Virtio | |
// Filesystem device, so set FileInfo->CreateTime from FuseAttr->Mtime as | |
// well. | |
// | |
EpochTime[0] = FuseAttr->Mtime; | |
EpochTime[1] = FuseAttr->Atime; | |
EpochTime[2] = FuseAttr->Mtime; | |
ConvertedTime[0] = &FileInfo->CreateTime; | |
ConvertedTime[1] = &FileInfo->LastAccessTime; | |
ConvertedTime[2] = &FileInfo->ModificationTime; | |
for (Idx = 0; Idx < ARRAY_SIZE (EpochTime); Idx++) { | |
// | |
// EpochToEfiTime() takes a UINTN for seconds. | |
// | |
if (EpochTime[Idx] > MAX_UINTN) { | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Set the following fields in the converted time: Year, Month, Day, Hour, | |
// Minute, Second, Nanosecond. | |
// | |
EpochToEfiTime ((UINTN)EpochTime[Idx], ConvertedTime[Idx]); | |
// | |
// The times are all expressed in UTC. Consequently, they are not affected | |
// by daylight saving. | |
// | |
ConvertedTime[Idx]->TimeZone = 0; | |
ConvertedTime[Idx]->Daylight = 0; | |
// | |
// Clear the padding fields. | |
// | |
ConvertedTime[Idx]->Pad1 = 0; | |
ConvertedTime[Idx]->Pad2 = 0; | |
} | |
// | |
// Set the attributes. | |
// | |
switch (FuseAttr->Mode & VIRTIO_FS_FUSE_MODE_TYPE_MASK) { | |
case VIRTIO_FS_FUSE_MODE_TYPE_DIR: | |
FileInfo->Attribute = EFI_FILE_DIRECTORY; | |
break; | |
case VIRTIO_FS_FUSE_MODE_TYPE_REG: | |
FileInfo->Attribute = 0; | |
break; | |
default: | |
// | |
// Other file types are not supported. | |
// | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Report the regular file or directory as read-only if all classes lack | |
// write permission. | |
// | |
if ((FuseAttr->Mode & (VIRTIO_FS_FUSE_MODE_PERM_WUSR | | |
VIRTIO_FS_FUSE_MODE_PERM_WGRP | | |
VIRTIO_FS_FUSE_MODE_PERM_WOTH)) == 0) | |
{ | |
FileInfo->Attribute |= EFI_FILE_READ_ONLY; | |
} | |
// | |
// A hard link count greater than 1 is not supported for regular files. | |
// | |
if (((FileInfo->Attribute & EFI_FILE_DIRECTORY) == 0) && (FuseAttr->Nlink > 1)) { | |
return EFI_UNSUPPORTED; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Convert a VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE filename to an EFI_FILE_INFO | |
filename. | |
@param[in] FuseDirent The VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE object to | |
convert the filename byte array from. The caller is | |
responsible for ensuring that FuseDirent->Namelen | |
describe valid storage. | |
@param[in,out] FileInfo The EFI_FILE_INFO structure to modify. On input, the | |
caller is responsible for setting FileInfo->Size | |
according to the allocated size. On successful | |
return, FileInfo->Size is reduced to reflect the | |
filename converted into FileInfo->FileName. | |
FileInfo->FileName is set from the filename byte | |
array that directly follows the FuseDirent header | |
object. Fields other than FileInfo->Size and | |
FileInfo->FileName are not modified. | |
@retval EFI_SUCCESS Conversion successful. | |
@retval EFI_INVALID_PARAMETER VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE() | |
returns zero for FuseDirent->Namelen. | |
@retval EFI_BUFFER_TOO_SMALL On input, FileInfo->Size does not provide | |
enough room for converting the filename byte | |
array from FuseDirent. | |
@retval EFI_UNSUPPORTED The FuseDirent filename byte array contains a | |
byte that falls outside of the printable ASCII | |
range, or is a forward slash or a backslash. | |
**/ | |
EFI_STATUS | |
VirtioFsFuseDirentPlusToEfiFileInfo ( | |
IN VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *FuseDirent, | |
IN OUT EFI_FILE_INFO *FileInfo | |
) | |
{ | |
UINTN DirentSize; | |
UINTN FileInfoSize; | |
UINT8 *DirentName; | |
UINT32 Idx; | |
DirentSize = VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (FuseDirent->Namelen); | |
if (DirentSize == 0) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// We're now safe from overflow in the calculation below. | |
// | |
FileInfoSize = (OFFSET_OF (EFI_FILE_INFO, FileName) + | |
((UINTN)FuseDirent->Namelen + 1) * sizeof (CHAR16)); | |
if (FileInfoSize > FileInfo->Size) { | |
return EFI_BUFFER_TOO_SMALL; | |
} | |
// | |
// Convert the name. | |
// | |
DirentName = (UINT8 *)(FuseDirent + 1); | |
for (Idx = 0; Idx < FuseDirent->Namelen; Idx++) { | |
UINT8 NameByte; | |
NameByte = DirentName[Idx]; | |
if ((NameByte < 0x20) || (NameByte > 0x7E) || | |
(NameByte == '/') || (NameByte == '\\')) | |
{ | |
return EFI_UNSUPPORTED; | |
} | |
FileInfo->FileName[Idx] = (CHAR16)NameByte; | |
} | |
FileInfo->FileName[Idx++] = L'\0'; | |
// | |
// Set the (possibly reduced) size. | |
// | |
FileInfo->Size = FileInfoSize; | |
return EFI_SUCCESS; | |
} | |
/** | |
Given an EFI_FILE_INFO object received in an EFI_FILE_PROTOCOL.SetInfo() | |
call, determine whether updating the size of the file is necessary, relative | |
to an EFI_FILE_INFO object describing the current state of the file. | |
@param[in] Info The EFI_FILE_INFO describing the current state of the | |
file. The caller is responsible for populating Info on | |
input with VirtioFsFuseAttrToEfiFileInfo(), from the | |
current FUSE attributes of the file. The Info->Size and | |
Info->FileName members are ignored. | |
@param[in] NewInfo The EFI_FILE_INFO object received in the | |
EFI_FILE_PROTOCOL.SetInfo() call. | |
@param[out] Update Set to TRUE on output if the file size needs to be | |
updated. Set to FALSE otherwise. | |
@param[out] Size If Update is set to TRUE, then Size provides the new file | |
size to set. Otherwise, Size is not written to. | |
**/ | |
VOID | |
VirtioFsGetFuseSizeUpdate ( | |
IN EFI_FILE_INFO *Info, | |
IN EFI_FILE_INFO *NewInfo, | |
OUT BOOLEAN *Update, | |
OUT UINT64 *Size | |
) | |
{ | |
BOOLEAN IsDirectory; | |
IsDirectory = (BOOLEAN)((Info->Attribute & EFI_FILE_DIRECTORY) != 0); | |
if (IsDirectory || (Info->FileSize == NewInfo->FileSize)) { | |
*Update = FALSE; | |
return; | |
} | |
*Update = TRUE; | |
*Size = NewInfo->FileSize; | |
} | |
/** | |
Given an EFI_FILE_INFO object received in an EFI_FILE_PROTOCOL.SetInfo() | |
call, determine whether updating the last access time and/or the last | |
modification time of the file is necessary, relative to an EFI_FILE_INFO | |
object describing the current state of the file. | |
@param[in] Info The EFI_FILE_INFO describing the current state of | |
the file. The caller is responsible for populating | |
Info on input with VirtioFsFuseAttrToEfiFileInfo(), | |
from the current FUSE attributes of the file. The | |
Info->Size and Info->FileName members are ignored. | |
@param[in] NewInfo The EFI_FILE_INFO object received in the | |
EFI_FILE_PROTOCOL.SetInfo() call. | |
@param[out] UpdateAtime Set to TRUE on output if the last access time needs | |
to be updated. Set to FALSE otherwise. | |
@param[out] UpdateMtime Set to TRUE on output if the last modification time | |
needs to be updated. Set to FALSE otherwise. | |
@param[out] Atime If UpdateAtime is set to TRUE, then Atime provides | |
the last access timestamp to set (as seconds since | |
the Epoch). Otherwise, Atime is not written to. | |
@param[out] Mtime If UpdateMtime is set to TRUE, then Mtime provides | |
the last modification timestamp to set (as seconds | |
since the Epoch). Otherwise, Mtime is not written | |
to. | |
@retval EFI_SUCCESS Output parameters have been set successfully. | |
@retval EFI_INVALID_PARAMETER At least one of the CreateTime, LastAccessTime | |
and ModificationTime fields in NewInfo | |
represents an actual update relative to the | |
current state of the file (expressed in Info), | |
but does not satisfy the UEFI spec | |
requirements on EFI_TIME. | |
@retval EFI_ACCESS_DENIED NewInfo requests changing both CreateTime and | |
ModificationTime, but to values that differ | |
from each other. The Virtio Filesystem device | |
does not support this. | |
**/ | |
EFI_STATUS | |
VirtioFsGetFuseTimeUpdates ( | |
IN EFI_FILE_INFO *Info, | |
IN EFI_FILE_INFO *NewInfo, | |
OUT BOOLEAN *UpdateAtime, | |
OUT BOOLEAN *UpdateMtime, | |
OUT UINT64 *Atime, | |
OUT UINT64 *Mtime | |
) | |
{ | |
EFI_TIME *Time[3]; | |
EFI_TIME *NewTime[ARRAY_SIZE (Time)]; | |
UINTN Idx; | |
STATIC CONST EFI_TIME ZeroTime = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; | |
BOOLEAN Change[ARRAY_SIZE (Time)]; | |
UINT64 Seconds[ARRAY_SIZE (Time)]; | |
Time[0] = &Info->CreateTime; | |
Time[1] = &Info->LastAccessTime; | |
Time[2] = &Info->ModificationTime; | |
NewTime[0] = &NewInfo->CreateTime; | |
NewTime[1] = &NewInfo->LastAccessTime; | |
NewTime[2] = &NewInfo->ModificationTime; | |
// | |
// Determine which timestamps differ from the current state. (A zero time | |
// means "don't update", per UEFI spec.) For each timestamp that's being | |
// changed, calculate the seconds since the Epoch. | |
// | |
for (Idx = 0; Idx < ARRAY_SIZE (Time); Idx++) { | |
if ((CompareMem (NewTime[Idx], &ZeroTime, sizeof (EFI_TIME)) == 0) || | |
(CompareMem (NewTime[Idx], Time[Idx], sizeof (EFI_TIME)) == 0)) | |
{ | |
Change[Idx] = FALSE; | |
} else { | |
if (!IsTimeValid (NewTime[Idx])) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Change[Idx] = TRUE; | |
Seconds[Idx] = EfiTimeToEpoch (NewTime[Idx]); | |
} | |
} | |
// | |
// If a change is requested for exactly one of CreateTime and | |
// ModificationTime, we'll change the last modification time. If changes are | |
// requested for both, and to the same timestamp, we'll similarly update the | |
// last modification time. If changes are requested for both, but to | |
// different timestamps, we reject the request. | |
// | |
if (Change[0] && Change[2] && (Seconds[0] != Seconds[2])) { | |
return EFI_ACCESS_DENIED; | |
} | |
*UpdateAtime = FALSE; | |
*UpdateMtime = FALSE; | |
if (Change[0]) { | |
*UpdateMtime = TRUE; | |
*Mtime = Seconds[0]; | |
} | |
if (Change[1]) { | |
*UpdateAtime = TRUE; | |
*Atime = Seconds[1]; | |
} | |
if (Change[2]) { | |
*UpdateMtime = TRUE; | |
*Mtime = Seconds[2]; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Given an EFI_FILE_INFO object received in an EFI_FILE_PROTOCOL.SetInfo() | |
call, determine whether updating the file mode bits of the file is necessary, | |
relative to an EFI_FILE_INFO object describing the current state of the file. | |
@param[in] Info The EFI_FILE_INFO describing the current state of the | |
file. The caller is responsible for populating Info on | |
input with VirtioFsFuseAttrToEfiFileInfo(), from the | |
current FUSE attributes of the file. The Info->Size and | |
Info->FileName members are ignored. | |
@param[in] NewInfo The EFI_FILE_INFO object received in the | |
EFI_FILE_PROTOCOL.SetInfo() call. | |
@param[out] Update Set to TRUE on output if the file mode bits need to be | |
updated. Set to FALSE otherwise. | |
@param[out] Mode If Update is set to TRUE, then Mode provides the file | |
mode bits to set. Otherwise, Mode is not written to. | |
@retval EFI_SUCCESS Output parameters have been set successfully. | |
@retval EFI_ACCESS_DENIED NewInfo requests toggling an unknown bit in the | |
Attribute bitmask. | |
@retval EFI_ACCESS_DENIED NewInfo requests toggling EFI_FILE_DIRECTORY in | |
the Attribute bitmask. | |
**/ | |
EFI_STATUS | |
VirtioFsGetFuseModeUpdate ( | |
IN EFI_FILE_INFO *Info, | |
IN EFI_FILE_INFO *NewInfo, | |
OUT BOOLEAN *Update, | |
OUT UINT32 *Mode | |
) | |
{ | |
UINT64 Toggle; | |
BOOLEAN IsDirectory; | |
BOOLEAN IsWriteable; | |
BOOLEAN WillBeWriteable; | |
Toggle = Info->Attribute ^ NewInfo->Attribute; | |
if ((Toggle & ~EFI_FILE_VALID_ATTR) != 0) { | |
// | |
// Unknown attribute requested. | |
// | |
return EFI_ACCESS_DENIED; | |
} | |
if ((Toggle & EFI_FILE_DIRECTORY) != 0) { | |
// | |
// EFI_FILE_DIRECTORY cannot be toggled. | |
// | |
return EFI_ACCESS_DENIED; | |
} | |
IsDirectory = (BOOLEAN)((Info->Attribute & EFI_FILE_DIRECTORY) != 0); | |
IsWriteable = (BOOLEAN)((Info->Attribute & EFI_FILE_READ_ONLY) == 0); | |
WillBeWriteable = (BOOLEAN)((NewInfo->Attribute & EFI_FILE_READ_ONLY) == 0); | |
if (IsWriteable == WillBeWriteable) { | |
*Update = FALSE; | |
return EFI_SUCCESS; | |
} | |
if (IsDirectory) { | |
if (WillBeWriteable) { | |
*Mode = (VIRTIO_FS_FUSE_MODE_PERM_RWXU | | |
VIRTIO_FS_FUSE_MODE_PERM_RWXG | | |
VIRTIO_FS_FUSE_MODE_PERM_RWXO); | |
} else { | |
*Mode = (VIRTIO_FS_FUSE_MODE_PERM_RUSR | | |
VIRTIO_FS_FUSE_MODE_PERM_XUSR | | |
VIRTIO_FS_FUSE_MODE_PERM_RGRP | | |
VIRTIO_FS_FUSE_MODE_PERM_XGRP | | |
VIRTIO_FS_FUSE_MODE_PERM_ROTH | | |
VIRTIO_FS_FUSE_MODE_PERM_XOTH); | |
} | |
} else { | |
if (WillBeWriteable) { | |
*Mode = (VIRTIO_FS_FUSE_MODE_PERM_RUSR | | |
VIRTIO_FS_FUSE_MODE_PERM_WUSR | | |
VIRTIO_FS_FUSE_MODE_PERM_RGRP | | |
VIRTIO_FS_FUSE_MODE_PERM_WGRP | | |
VIRTIO_FS_FUSE_MODE_PERM_ROTH | | |
VIRTIO_FS_FUSE_MODE_PERM_WOTH); | |
} else { | |
*Mode = (VIRTIO_FS_FUSE_MODE_PERM_RUSR | | |
VIRTIO_FS_FUSE_MODE_PERM_RGRP | | |
VIRTIO_FS_FUSE_MODE_PERM_ROTH); | |
} | |
} | |
*Update = TRUE; | |
return EFI_SUCCESS; | |
} |