blob: d73d23fe866568f59b7c8347dca3229c13709d15 [file] [log] [blame]
/** @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;
}
//
// 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"),
__FUNCTION__,
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).
//
if (!VirtioFsFile->IsDirectory) {
DEBUG ((
DEBUG_ERROR,
("%a: Label=\"%s\" CanonicalPathname=\"%a\" FileName=\"%s\": "
"nonsensical request to open a file or directory relative to a regular "
"file\n"),
__FUNCTION__,
VirtioFs->Label,
VirtioFsFile->CanonicalPathname,
FileName
));
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;
}
//
// 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;
}