/** @file | |
EFI_FILE_PROTOCOL.Open() member function for the Virtio Filesystem driver. | |
Copyright (C) 2020, Red Hat, Inc. | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Library/BaseLib.h> // AsciiStrCmp() | |
#include <Library/MemoryAllocationLib.h> // AllocatePool() | |
#include "VirtioFsDxe.h" | |
/** | |
Open the root directory, possibly for writing. | |
@param[in,out] VirtioFs The Virtio Filesystem device whose root directory | |
should be opened. | |
@param[out] NewHandle The new EFI_FILE_PROTOCOL instance through which | |
the root directory can be accessed. | |
@param[in] OpenForWriting TRUE if the root directory should be opened for | |
read-write access. FALSE if the root directory | |
should be opened for read-only access. Opening the | |
root directory for read-write access is useful for | |
calling EFI_FILE_PROTOCOL.Flush() or | |
EFI_FILE_PROTOCOL.SetInfo() later, for syncing or | |
touching the root directory, respectively. | |
@retval EFI_SUCCESS The root directory has been opened successfully. | |
@retval EFI_ACCESS_DENIED OpenForWriting is TRUE, but the root directory is | |
marked as read-only. | |
@return Error codes propagated from underlying functions. | |
**/ | |
STATIC | |
EFI_STATUS | |
OpenRootDirectory ( | |
IN OUT VIRTIO_FS *VirtioFs, | |
OUT EFI_FILE_PROTOCOL **NewHandle, | |
IN BOOLEAN OpenForWriting | |
) | |
{ | |
EFI_STATUS Status; | |
VIRTIO_FS_FILE *NewVirtioFsFile; | |
// | |
// VirtioFsOpenVolume() opens the root directory for read-only access. If the | |
// current request is to open the root directory for read-write access, so | |
// that EFI_FILE_PROTOCOL.Flush() or EFI_FILE_PROTOCOL.SetInfo()+timestamps | |
// can be used on the root directory later, then we have to check for write | |
// permission first. | |
// | |
if (OpenForWriting) { | |
VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr; | |
EFI_FILE_INFO FileInfo; | |
Status = VirtioFsFuseGetAttr ( | |
VirtioFs, | |
VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID, | |
&FuseAttr | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((FileInfo.Attribute & EFI_FILE_READ_ONLY) != 0) { | |
return EFI_ACCESS_DENIED; | |
} | |
} | |
Status = VirtioFsOpenVolume (&VirtioFs->SimpleFs, NewHandle); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
NewVirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (*NewHandle); | |
NewVirtioFsFile->IsOpenForWriting = OpenForWriting; | |
return EFI_SUCCESS; | |
} | |
/** | |
Open an existent regular file or non-root directory. | |
@param[in,out] VirtioFs The Virtio Filesystem device on which the | |
regular file or directory should be opened. | |
@param[in] DirNodeId The inode number of the immediate parent | |
directory of the regular file or directory to | |
open. | |
@param[in] Name The single-component filename of the regular | |
file or directory to open, under the immediate | |
parent directory identified by DirNodeId. | |
@param[in] OpenForWriting TRUE if the regular file or directory should be | |
opened for read-write access. FALSE if the | |
regular file or directory should be opened for | |
read-only access. Opening a directory for | |
read-write access is useful for deleting, | |
renaming, syncing or touching the directory | |
later. | |
@param[out] NodeId The inode number of the regular file or | |
directory, returned by the Virtio Filesystem | |
device. | |
@param[out] FuseHandle The open handle to the regular file or | |
directory, returned by the Virtio Filesystem | |
device. | |
@param[out] NodeIsDirectory Set to TRUE on output if Name was found to refer | |
to a directory. Set to FALSE if Name was found | |
to refer to a regular file. | |
@retval EFI_SUCCESS The regular file or directory has been looked up | |
and opened successfully. | |
@retval EFI_ACCESS_DENIED OpenForWriting is TRUE, but the regular file or | |
directory is marked read-only. | |
@retval EFI_NOT_FOUND A directory entry called Name was not found in the | |
directory identified by DirNodeId. (EFI_NOT_FOUND | |
is not returned for any other condition.) | |
@return Errors propagated from underlying functions. If | |
the error code to propagate were EFI_NOT_FOUND, it | |
is remapped to EFI_DEVICE_ERROR. | |
**/ | |
STATIC | |
EFI_STATUS | |
OpenExistentFileOrDirectory ( | |
IN OUT VIRTIO_FS *VirtioFs, | |
IN UINT64 DirNodeId, | |
IN CHAR8 *Name, | |
IN BOOLEAN OpenForWriting, | |
OUT UINT64 *NodeId, | |
OUT UINT64 *FuseHandle, | |
OUT BOOLEAN *NodeIsDirectory | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 ResolvedNodeId; | |
VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr; | |
EFI_FILE_INFO FileInfo; | |
BOOLEAN IsDirectory; | |
UINT64 NewFuseHandle; | |
Status = VirtioFsFuseLookup ( | |
VirtioFs, | |
DirNodeId, | |
Name, | |
&ResolvedNodeId, | |
&FuseAttr | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo); | |
if (EFI_ERROR (Status)) { | |
goto ForgetResolvedNodeId; | |
} | |
if (OpenForWriting && ((FileInfo.Attribute & EFI_FILE_READ_ONLY) != 0)) { | |
Status = EFI_ACCESS_DENIED; | |
goto ForgetResolvedNodeId; | |
} | |
IsDirectory = (BOOLEAN)((FileInfo.Attribute & EFI_FILE_DIRECTORY) != 0); | |
if (IsDirectory) { | |
// | |
// If OpenForWriting is TRUE here, that's not passed to | |
// VirtioFsFuseOpenDir(); it does not affect the FUSE_OPENDIR request we | |
// send. OpenForWriting=TRUE will only permit attempts to delete, rename, | |
// flush (sync), and touch the directory. | |
// | |
Status = VirtioFsFuseOpenDir (VirtioFs, ResolvedNodeId, &NewFuseHandle); | |
} else { | |
Status = VirtioFsFuseOpen ( | |
VirtioFs, | |
ResolvedNodeId, | |
OpenForWriting, | |
&NewFuseHandle | |
); | |
} | |
if (EFI_ERROR (Status)) { | |
goto ForgetResolvedNodeId; | |
} | |
*NodeId = ResolvedNodeId; | |
*FuseHandle = NewFuseHandle; | |
*NodeIsDirectory = IsDirectory; | |
return EFI_SUCCESS; | |
ForgetResolvedNodeId: | |
VirtioFsFuseForget (VirtioFs, ResolvedNodeId); | |
return (Status == EFI_NOT_FOUND) ? EFI_DEVICE_ERROR : Status; | |
} | |
/** | |
Create a directory. | |
@param[in,out] VirtioFs The Virtio Filesystem device on which the directory | |
should be created. | |
@param[in] DirNodeId The inode number of the immediate parent directory | |
of the directory to create. | |
@param[in] Name The single-component filename of the directory to | |
create, under the immediate parent directory | |
identified by DirNodeId. | |
@param[out] NodeId The inode number of the directory created, returned | |
by the Virtio Filesystem device. | |
@param[out] FuseHandle The open handle to the directory created, returned | |
by the Virtio Filesystem device. | |
@retval EFI_SUCCESS The directory has been created successfully. | |
@return Errors propagated from underlying functions. | |
**/ | |
STATIC | |
EFI_STATUS | |
CreateDirectory ( | |
IN OUT VIRTIO_FS *VirtioFs, | |
IN UINT64 DirNodeId, | |
IN CHAR8 *Name, | |
OUT UINT64 *NodeId, | |
OUT UINT64 *FuseHandle | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 NewChildDirNodeId; | |
UINT64 NewFuseHandle; | |
Status = VirtioFsFuseMkDir (VirtioFs, DirNodeId, Name, &NewChildDirNodeId); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = VirtioFsFuseOpenDir (VirtioFs, NewChildDirNodeId, &NewFuseHandle); | |
if (EFI_ERROR (Status)) { | |
goto RemoveNewChildDir; | |
} | |
*NodeId = NewChildDirNodeId; | |
*FuseHandle = NewFuseHandle; | |
return EFI_SUCCESS; | |
RemoveNewChildDir: | |
VirtioFsFuseRemoveFileOrDir (VirtioFs, DirNodeId, Name, TRUE /* IsDir */); | |
VirtioFsFuseForget (VirtioFs, NewChildDirNodeId); | |
return Status; | |
} | |
/** | |
Create a regular file. | |
@param[in,out] VirtioFs The Virtio Filesystem device on which the regular | |
file should be created. | |
@param[in] DirNodeId The inode number of the immediate parent directory | |
of the regular file to create. | |
@param[in] Name The single-component filename of the regular file to | |
create, under the immediate parent directory | |
identified by DirNodeId. | |
@param[out] NodeId The inode number of the regular file created, | |
returned by the Virtio Filesystem device. | |
@param[out] FuseHandle The open handle to the regular file created, | |
returned by the Virtio Filesystem device. | |
@retval EFI_SUCCESS The regular file has been created successfully. | |
@return Errors propagated from underlying functions. | |
**/ | |
STATIC | |
EFI_STATUS | |
CreateRegularFile ( | |
IN OUT VIRTIO_FS *VirtioFs, | |
IN UINT64 DirNodeId, | |
IN CHAR8 *Name, | |
OUT UINT64 *NodeId, | |
OUT UINT64 *FuseHandle | |
) | |
{ | |
return VirtioFsFuseOpenOrCreate ( | |
VirtioFs, | |
DirNodeId, | |
Name, | |
NodeId, | |
FuseHandle | |
); | |
} | |
EFI_STATUS | |
EFIAPI | |
VirtioFsSimpleFileOpen ( | |
IN EFI_FILE_PROTOCOL *This, | |
OUT EFI_FILE_PROTOCOL **NewHandle, | |
IN CHAR16 *FileName, | |
IN UINT64 OpenMode, | |
IN UINT64 Attributes | |
) | |
{ | |
VIRTIO_FS_FILE *VirtioFsFile; | |
VIRTIO_FS *VirtioFs; | |
BOOLEAN OpenForWriting; | |
BOOLEAN PermitCreation; | |
BOOLEAN CreateDirectoryIfCreating; | |
VIRTIO_FS_FILE *NewVirtioFsFile; | |
EFI_STATUS Status; | |
CHAR8 *NewCanonicalPath; | |
BOOLEAN RootEscape; | |
UINT64 DirNodeId; | |
CHAR8 *LastComponent; | |
UINT64 NewNodeId; | |
UINT64 NewFuseHandle; | |
BOOLEAN NewNodeIsDirectory; | |
VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This); | |
VirtioFs = VirtioFsFile->OwnerFs; | |
// | |
// Validate OpenMode. | |
// | |
switch (OpenMode) { | |
case EFI_FILE_MODE_READ: | |
OpenForWriting = FALSE; | |
PermitCreation = FALSE; | |
break; | |
case EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE: | |
OpenForWriting = TRUE; | |
PermitCreation = FALSE; | |
break; | |
case EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE: | |
OpenForWriting = TRUE; | |
PermitCreation = TRUE; | |
break; | |
default: | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Set CreateDirectoryIfCreating to suppress incorrect compiler/analyzer | |
// warnings. | |
// | |
CreateDirectoryIfCreating = FALSE; | |
// | |
// Validate the Attributes requested for the case when the file ends up being | |
// created, provided creation is permitted. | |
// | |
if (PermitCreation) { | |
if ((Attributes & ~EFI_FILE_VALID_ATTR) != 0) { | |
// | |
// Unknown attribute requested. | |
// | |
return EFI_INVALID_PARAMETER; | |
} | |
ASSERT (OpenForWriting); | |
if ((Attributes & EFI_FILE_READ_ONLY) != 0) { | |
DEBUG (( | |
DEBUG_ERROR, | |
("%a: Label=\"%s\" CanonicalPathname=\"%a\" FileName=\"%s\" " | |
"OpenMode=0x%Lx Attributes=0x%Lx: nonsensical request to possibly " | |
"create a file marked read-only, for read-write access\n"), | |
__func__, | |
VirtioFs->Label, | |
VirtioFsFile->CanonicalPathname, | |
FileName, | |
OpenMode, | |
Attributes | |
)); | |
return EFI_INVALID_PARAMETER; | |
} | |
CreateDirectoryIfCreating = (BOOLEAN)((Attributes & | |
EFI_FILE_DIRECTORY) != 0); | |
} | |
// | |
// Referring to a file relative to a regular file makes no sense (or at least | |
// it cannot be implemented consistently with how a file is referred to | |
// relative to a directory). See USWG Mantis ticket #2367. | |
// | |
if (!VirtioFsFile->IsDirectory) { | |
BOOLEAN BugCompat; | |
// | |
// Tolerate this bug in the caller if FileName is absolute. If FileName is | |
// absolute, then VirtioFsAppendPath() below will disregard | |
// VirtioFsFile->CanonicalPathname. | |
// | |
BugCompat = (FileName[0] == L'\\'); | |
DEBUG (( | |
BugCompat ? DEBUG_WARN : DEBUG_ERROR, | |
("%a: Label=\"%s\" CanonicalPathname=\"%a\" FileName=\"%s\": " | |
"nonsensical request to open a file or directory relative to a regular " | |
"file\n"), | |
__func__, | |
VirtioFs->Label, | |
VirtioFsFile->CanonicalPathname, | |
FileName | |
)); | |
if (!BugCompat) { | |
return EFI_INVALID_PARAMETER; | |
} | |
} | |
// | |
// Allocate the new VIRTIO_FS_FILE object. | |
// | |
NewVirtioFsFile = AllocatePool (sizeof *NewVirtioFsFile); | |
if (NewVirtioFsFile == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// | |
// Create the canonical pathname at which the desired file is expected to | |
// exist. | |
// | |
Status = VirtioFsAppendPath ( | |
VirtioFsFile->CanonicalPathname, | |
FileName, | |
&NewCanonicalPath, | |
&RootEscape | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeNewVirtioFsFile; | |
} | |
if (RootEscape) { | |
Status = EFI_ACCESS_DENIED; | |
goto FreeNewCanonicalPath; | |
} | |
// | |
// If the desired file is the root directory, just open the volume one more | |
// time, without looking up anything. | |
// | |
if (AsciiStrCmp (NewCanonicalPath, "/") == 0) { | |
FreePool (NewCanonicalPath); | |
FreePool (NewVirtioFsFile); | |
return OpenRootDirectory (VirtioFs, NewHandle, OpenForWriting); | |
} | |
// | |
// Split the new canonical pathname into most specific parent directory | |
// (given by DirNodeId) and last pathname component (i.e., immediate child | |
// within that parent directory). | |
// | |
Status = VirtioFsLookupMostSpecificParentDir ( | |
VirtioFs, | |
NewCanonicalPath, | |
&DirNodeId, | |
&LastComponent | |
); | |
if (EFI_ERROR (Status)) { | |
goto FreeNewCanonicalPath; | |
} | |
// | |
// Set NewNodeIsDirectory to suppress incorrect compiler/analyzer warnings. | |
// | |
NewNodeIsDirectory = FALSE; | |
// | |
// Try to open LastComponent directly under DirNodeId, as an existent regular | |
// file or directory. | |
// | |
Status = OpenExistentFileOrDirectory ( | |
VirtioFs, | |
DirNodeId, | |
LastComponent, | |
OpenForWriting, | |
&NewNodeId, | |
&NewFuseHandle, | |
&NewNodeIsDirectory | |
); | |
// | |
// If LastComponent could not be found under DirNodeId, but the request | |
// allows us to create a new entry, attempt creating the requested regular | |
// file or directory. | |
// | |
if ((Status == EFI_NOT_FOUND) && PermitCreation) { | |
ASSERT (OpenForWriting); | |
if (CreateDirectoryIfCreating) { | |
Status = CreateDirectory ( | |
VirtioFs, | |
DirNodeId, | |
LastComponent, | |
&NewNodeId, | |
&NewFuseHandle | |
); | |
} else { | |
Status = CreateRegularFile ( | |
VirtioFs, | |
DirNodeId, | |
LastComponent, | |
&NewNodeId, | |
&NewFuseHandle | |
); | |
} | |
NewNodeIsDirectory = CreateDirectoryIfCreating; | |
} | |
// | |
// Regardless of the branch taken, we're done with DirNodeId. | |
// | |
if (DirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) { | |
VirtioFsFuseForget (VirtioFs, DirNodeId); | |
} | |
if (EFI_ERROR (Status)) { | |
goto FreeNewCanonicalPath; | |
} | |
// | |
// Populate the new VIRTIO_FS_FILE object. | |
// | |
NewVirtioFsFile->Signature = VIRTIO_FS_FILE_SIG; | |
NewVirtioFsFile->SimpleFile.Revision = EFI_FILE_PROTOCOL_REVISION; | |
NewVirtioFsFile->SimpleFile.Open = VirtioFsSimpleFileOpen; | |
NewVirtioFsFile->SimpleFile.Close = VirtioFsSimpleFileClose; | |
NewVirtioFsFile->SimpleFile.Delete = VirtioFsSimpleFileDelete; | |
NewVirtioFsFile->SimpleFile.Read = VirtioFsSimpleFileRead; | |
NewVirtioFsFile->SimpleFile.Write = VirtioFsSimpleFileWrite; | |
NewVirtioFsFile->SimpleFile.GetPosition = VirtioFsSimpleFileGetPosition; | |
NewVirtioFsFile->SimpleFile.SetPosition = VirtioFsSimpleFileSetPosition; | |
NewVirtioFsFile->SimpleFile.GetInfo = VirtioFsSimpleFileGetInfo; | |
NewVirtioFsFile->SimpleFile.SetInfo = VirtioFsSimpleFileSetInfo; | |
NewVirtioFsFile->SimpleFile.Flush = VirtioFsSimpleFileFlush; | |
NewVirtioFsFile->IsDirectory = NewNodeIsDirectory; | |
NewVirtioFsFile->IsOpenForWriting = OpenForWriting; | |
NewVirtioFsFile->OwnerFs = VirtioFs; | |
NewVirtioFsFile->CanonicalPathname = NewCanonicalPath; | |
NewVirtioFsFile->FilePosition = 0; | |
NewVirtioFsFile->NodeId = NewNodeId; | |
NewVirtioFsFile->FuseHandle = NewFuseHandle; | |
NewVirtioFsFile->FileInfoArray = NULL; | |
NewVirtioFsFile->SingleFileInfoSize = 0; | |
NewVirtioFsFile->NumFileInfo = 0; | |
NewVirtioFsFile->NextFileInfo = 0; | |
// | |
// One more file is now open for the filesystem. | |
// | |
InsertTailList (&VirtioFs->OpenFiles, &NewVirtioFsFile->OpenFilesEntry); | |
*NewHandle = &NewVirtioFsFile->SimpleFile; | |
return EFI_SUCCESS; | |
FreeNewCanonicalPath: | |
FreePool (NewCanonicalPath); | |
FreeNewVirtioFsFile: | |
FreePool (NewVirtioFsFile); | |
return Status; | |
} |