| /** @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; | |
| } |