From mboxrd@z Thu Jan 1 00:00:00 1970 From: Laszlo Ersek Date: Wed, 16 Dec 2020 22:11:03 +0100 Message-Id: <20201216211125.19496-27-lersek@redhat.com> In-Reply-To: <20201216211125.19496-1-lersek@redhat.com> References: <20201216211125.19496-1-lersek@redhat.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit Subject: [Virtio-fs] [edk2 PATCH 26/48] OvmfPkg/VirtioFsDxe: implement EFI_FILE_PROTOCOL.Open() List-Id: Development discussions about virtio-fs List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: devel@edk2.groups.io, virtio-fs@redhat.com, lersek@redhat.com Cc: Jordan Justen , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Ard Biesheuvel Using the functions introduced previously, we can now implement VirtioFsSimpleFileOpen(). This lets the "MKDIR" command to work in the UEFI shell, for example. Cc: Ard Biesheuvel Cc: Jordan Justen Cc: Philippe Mathieu-Daudé Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=3097 Signed-off-by: Laszlo Ersek --- OvmfPkg/VirtioFsDxe/SimpleFsOpen.c | 480 +++++++++++++++++++- 1 file changed, 479 insertions(+), 1 deletion(-) diff --git a/OvmfPkg/VirtioFsDxe/SimpleFsOpen.c b/OvmfPkg/VirtioFsDxe/SimpleFsOpen.c index f0e249184079..2649c796ac97 100644 --- a/OvmfPkg/VirtioFsDxe/SimpleFsOpen.c +++ b/OvmfPkg/VirtioFsDxe/SimpleFsOpen.c @@ -1,22 +1,500 @@ /** @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 // AsciiStrCmp() +#include // 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 ) { - return EFI_NO_MEDIA; + 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->NodeId = NewNodeId; + NewVirtioFsFile->FuseHandle = NewFuseHandle; + + // + // 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; } -- 2.19.1.3.g30247aa5d201