| /** @file | |
| EFI_FILE_PROTOCOL.SetInfo() member function for the Virtio Filesystem driver. | |
| Copyright (C) 2020, Red Hat, Inc. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Guid/FileSystemInfo.h> // gEfiFileSystemInfoGuid | |
| #include <Guid/FileSystemVolumeLabelInfo.h> // gEfiFileSystemVolumeLabelInfo... | |
| #include <Library/BaseLib.h> // StrCmp() | |
| #include <Library/BaseMemoryLib.h> // CompareGuid() | |
| #include <Library/MemoryAllocationLib.h> // FreePool() | |
| #include "VirtioFsDxe.h" | |
| /** | |
| Validate a buffer that the EFI_FILE_PROTOCOL.SetInfo() caller passes in for a | |
| particular InformationType GUID. | |
| The structure to be validated is supposed to end with a variable-length, | |
| NUL-terminated CHAR16 Name string. | |
| @param[in] SizeByProtocolCaller The BufferSize parameter as provided by the | |
| EFI_FILE_PROTOCOL.SetInfo() caller. | |
| @param[in] MinimumStructSize The minimum structure size that is required | |
| for the given InformationType GUID, | |
| including a single CHAR16 element from the | |
| trailing Name field. | |
| @param[in] IsSizeByInfoPresent TRUE if and only if the expected structure | |
| starts with a UINT64 Size field that reports | |
| the actual structure size. | |
| @param[in] Buffer The Buffer parameter as provided by the | |
| EFI_FILE_PROTOCOL.SetInfo() caller. | |
| @retval EFI_SUCCESS Validation successful, Buffer is well-formed. | |
| @retval EFI_BAD_BUFFER_SIZE The EFI_FILE_PROTOCOL.SetInfo() | |
| caller provided a BufferSize that is smaller | |
| than the minimum structure size required for | |
| the given InformationType GUID. | |
| @retval EFI_INVALID_PARAMETER IsSizeByInfoPresent is TRUE, and the leading | |
| UINT64 Size field does not match the | |
| EFI_FILE_PROTOCOL.SetInfo() caller-provided | |
| BufferSize. | |
| @retval EFI_INVALID_PARAMETER The trailing Name field does not consist of a | |
| whole multiple of CHAR16 elements. | |
| @retval EFI_INVALID_PARAMETER The trailing Name field is not NUL-terminated. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| ValidateInfoStructure ( | |
| IN UINTN SizeByProtocolCaller, | |
| IN UINTN MinimumStructSize, | |
| IN BOOLEAN IsSizeByInfoPresent, | |
| IN VOID *Buffer | |
| ) | |
| { | |
| UINTN NameFieldByteOffset; | |
| UINTN NameFieldBytes; | |
| UINTN NameFieldChar16s; | |
| CHAR16 *NameField; | |
| // | |
| // Make sure the internal function asking for validation passes in sane | |
| // values. | |
| // | |
| ASSERT (MinimumStructSize >= sizeof (CHAR16)); | |
| NameFieldByteOffset = MinimumStructSize - sizeof (CHAR16); | |
| if (IsSizeByInfoPresent) { | |
| ASSERT (MinimumStructSize >= sizeof (UINT64) + sizeof (CHAR16)); | |
| ASSERT (NameFieldByteOffset >= sizeof (UINT64)); | |
| } | |
| // | |
| // Check whether the protocol caller provided enough bytes for the minimum | |
| // size of this info structure. | |
| // | |
| if (SizeByProtocolCaller < MinimumStructSize) { | |
| return EFI_BAD_BUFFER_SIZE; | |
| } | |
| // | |
| // If the info structure starts with a UINT64 Size field, check if that | |
| // agrees with the protocol caller-provided size. | |
| // | |
| if (IsSizeByInfoPresent) { | |
| UINT64 *SizeByInfo; | |
| SizeByInfo = Buffer; | |
| if (*SizeByInfo != SizeByProtocolCaller) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| } | |
| // | |
| // The CHAR16 Name field at the end of the structure must have an even number | |
| // of bytes. | |
| // | |
| // The subtraction below cannot underflow, and yields at least | |
| // sizeof(CHAR16). | |
| // | |
| ASSERT (SizeByProtocolCaller >= NameFieldByteOffset); | |
| NameFieldBytes = SizeByProtocolCaller - NameFieldByteOffset; | |
| ASSERT (NameFieldBytes >= sizeof (CHAR16)); | |
| if (NameFieldBytes % sizeof (CHAR16) != 0) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // The CHAR16 Name field at the end of the structure must be NUL-terminated. | |
| // | |
| NameFieldChar16s = NameFieldBytes / sizeof (CHAR16); | |
| ASSERT (NameFieldChar16s >= 1); | |
| NameField = (CHAR16 *)((UINT8 *)Buffer + NameFieldByteOffset); | |
| if (NameField[NameFieldChar16s - 1] != L'\0') { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Rename a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.FileName. | |
| @param[in,out] VirtioFsFile The VIRTIO_FS_FILE to rename. | |
| @param[in] NewFileName The new file name requested by | |
| EFI_FILE_PROTOCOL.SetInfo(). | |
| @retval EFI_SUCCESS The canonical format destination path that is | |
| determined from the input value of | |
| VirtioFsFile->CanonicalPathname and from | |
| NewFileName is identical to the input value of | |
| VirtioFsFile->CanonicalPathname. This means that | |
| EFI_FILE_INFO does not constitute a rename | |
| request. VirtioFsFile has not been changed. | |
| @retval EFI_SUCCESS VirtioFsFile has been renamed. | |
| VirtioFsFile->CanonicalPathname has assumed the | |
| destination pathname in canonical format. | |
| @retval EFI_ACCESS_DENIED VirtioFsFile refers to the root directory, and | |
| NewFileName expresses an actual rename/move | |
| request. | |
| @retval EFI_ACCESS_DENIED VirtioFsFile is the (possibly indirect) parent | |
| directory of at least one other VIRTIO_FS_FILE | |
| that is open for the same Virtio Filesystem | |
| (identified by VirtioFsFile->OwnerFs). Renaming | |
| VirtioFsFile would invalidate the canonical | |
| pathnames of those VIRTIO_FS_FILE instances; | |
| therefore the request has been rejected. | |
| @retval EFI_ACCESS_DENIED VirtioFsFile is not open for writing, but | |
| NewFileName expresses an actual rename/move | |
| request. | |
| @retval EFI_NOT_FOUND At least one dot-dot component in NewFileName | |
| attempted to escape the root directory. | |
| @return Error codes propagated from underlying functions. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| Rename ( | |
| IN OUT VIRTIO_FS_FILE *VirtioFsFile, | |
| IN CHAR16 *NewFileName | |
| ) | |
| { | |
| VIRTIO_FS *VirtioFs; | |
| EFI_STATUS Status; | |
| CHAR8 *Destination; | |
| BOOLEAN RootEscape; | |
| UINT64 OldParentDirNodeId; | |
| CHAR8 *OldLastComponent; | |
| UINT64 NewParentDirNodeId; | |
| CHAR8 *NewLastComponent; | |
| VirtioFs = VirtioFsFile->OwnerFs; | |
| // | |
| // The root directory cannot be renamed. | |
| // | |
| if (AsciiStrCmp (VirtioFsFile->CanonicalPathname, "/") == 0) { | |
| if (StrCmp (NewFileName, L"") == 0) { | |
| // | |
| // Not a rename request anyway. | |
| // | |
| return EFI_SUCCESS; | |
| } | |
| return EFI_ACCESS_DENIED; | |
| } | |
| // | |
| // Compose the canonical pathname for the destination. | |
| // | |
| Status = VirtioFsComposeRenameDestination ( | |
| VirtioFsFile->CanonicalPathname, | |
| NewFileName, | |
| &Destination, | |
| &RootEscape | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| if (RootEscape) { | |
| Status = EFI_NOT_FOUND; | |
| goto FreeDestination; | |
| } | |
| // | |
| // If the rename would leave VirtioFsFile->CanonicalPathname unchanged, then | |
| // EFI_FILE_PROTOCOL.SetInfo() isn't asking for a rename actually. | |
| // | |
| if (AsciiStrCmp (VirtioFsFile->CanonicalPathname, Destination) == 0) { | |
| Status = EFI_SUCCESS; | |
| goto FreeDestination; | |
| } | |
| // | |
| // Check if the rename would break the canonical pathnames of other | |
| // VIRTIO_FS_FILE instances of the same VIRTIO_FS. | |
| // | |
| if (VirtioFsFile->IsDirectory) { | |
| UINTN PathLen; | |
| LIST_ENTRY *OpenFilesEntry; | |
| PathLen = AsciiStrLen (VirtioFsFile->CanonicalPathname); | |
| BASE_LIST_FOR_EACH (OpenFilesEntry, &VirtioFs->OpenFiles) { | |
| VIRTIO_FS_FILE *OtherFile; | |
| OtherFile = VIRTIO_FS_FILE_FROM_OPEN_FILES_ENTRY (OpenFilesEntry); | |
| if ((OtherFile != VirtioFsFile) && | |
| (AsciiStrnCmp ( | |
| VirtioFsFile->CanonicalPathname, | |
| OtherFile->CanonicalPathname, | |
| PathLen | |
| ) == 0) && | |
| ((OtherFile->CanonicalPathname[PathLen] == '\0') || | |
| (OtherFile->CanonicalPathname[PathLen] == '/'))) | |
| { | |
| // | |
| // OtherFile refers to the same directory as VirtioFsFile, or is a | |
| // (possibly indirect) child of the directory referred to by | |
| // VirtioFsFile. | |
| // | |
| Status = EFI_ACCESS_DENIED; | |
| goto FreeDestination; | |
| } | |
| } | |
| } | |
| // | |
| // From this point on, the file needs to be open for writing. | |
| // | |
| if (!VirtioFsFile->IsOpenForWriting) { | |
| Status = EFI_ACCESS_DENIED; | |
| goto FreeDestination; | |
| } | |
| // | |
| // Split both source and destination canonical pathnames into (most specific | |
| // parent directory, last component) pairs. | |
| // | |
| Status = VirtioFsLookupMostSpecificParentDir ( | |
| VirtioFs, | |
| VirtioFsFile->CanonicalPathname, | |
| &OldParentDirNodeId, | |
| &OldLastComponent | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto FreeDestination; | |
| } | |
| Status = VirtioFsLookupMostSpecificParentDir ( | |
| VirtioFs, | |
| Destination, | |
| &NewParentDirNodeId, | |
| &NewLastComponent | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto ForgetOldParentDirNodeId; | |
| } | |
| // | |
| // Perform the rename. If the destination path exists, the rename will fail. | |
| // | |
| Status = VirtioFsFuseRename ( | |
| VirtioFs, | |
| OldParentDirNodeId, | |
| OldLastComponent, | |
| NewParentDirNodeId, | |
| NewLastComponent | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto ForgetNewParentDirNodeId; | |
| } | |
| // | |
| // Swap in the new canonical pathname. | |
| // | |
| FreePool (VirtioFsFile->CanonicalPathname); | |
| VirtioFsFile->CanonicalPathname = Destination; | |
| Destination = NULL; | |
| Status = EFI_SUCCESS; | |
| // | |
| // Fall through. | |
| // | |
| ForgetNewParentDirNodeId: | |
| if (NewParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) { | |
| VirtioFsFuseForget (VirtioFs, NewParentDirNodeId); | |
| } | |
| ForgetOldParentDirNodeId: | |
| if (OldParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) { | |
| VirtioFsFuseForget (VirtioFs, OldParentDirNodeId); | |
| } | |
| FreeDestination: | |
| if (Destination != NULL) { | |
| FreePool (Destination); | |
| } | |
| return Status; | |
| } | |
| /** | |
| Update the attributes of a VIRTIO_FS_FILE as requested in EFI_FILE_INFO. | |
| @param[in,out] VirtioFsFile The VIRTIO_FS_FILE to update the attributes of. | |
| @param[in] NewFileInfo The new attributes requested by | |
| EFI_FILE_PROTOCOL.SetInfo(). NewFileInfo->Size | |
| and NewFileInfo->FileName are ignored. | |
| @retval EFI_SUCCESS No attributes had to be updated. | |
| @retval EFI_SUCCESS The required set of attribute updates has been | |
| determined and performed successfully. | |
| @retval EFI_ACCESS_DENIED NewFileInfo requests an update to a property | |
| different from the EFI_FILE_READ_ONLY bit in the | |
| Attribute field, but VirtioFsFile is not open for | |
| writing. | |
| @return Error codes propagated from underlying functions. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| UpdateAttributes ( | |
| IN OUT VIRTIO_FS_FILE *VirtioFsFile, | |
| IN EFI_FILE_INFO *NewFileInfo | |
| ) | |
| { | |
| VIRTIO_FS *VirtioFs; | |
| EFI_STATUS Status; | |
| VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr; | |
| EFI_FILE_INFO FileInfo; | |
| BOOLEAN UpdateFileSize; | |
| UINT64 FileSize; | |
| BOOLEAN UpdateAtime; | |
| BOOLEAN UpdateMtime; | |
| UINT64 Atime; | |
| UINT64 Mtime; | |
| BOOLEAN UpdateMode; | |
| UINT32 Mode; | |
| VirtioFs = VirtioFsFile->OwnerFs; | |
| // | |
| // Fetch the current attributes first, so we can build the difference between | |
| // them and NewFileInfo. | |
| // | |
| Status = VirtioFsFuseGetAttr (VirtioFs, VirtioFsFile->NodeId, &FuseAttr); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| // | |
| // Collect the updates. | |
| // | |
| if (VirtioFsFile->IsDirectory) { | |
| UpdateFileSize = FALSE; | |
| } else { | |
| VirtioFsGetFuseSizeUpdate ( | |
| &FileInfo, | |
| NewFileInfo, | |
| &UpdateFileSize, | |
| &FileSize | |
| ); | |
| } | |
| Status = VirtioFsGetFuseTimeUpdates ( | |
| &FileInfo, | |
| NewFileInfo, | |
| &UpdateAtime, | |
| &UpdateMtime, | |
| &Atime, | |
| &Mtime | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| Status = VirtioFsGetFuseModeUpdate ( | |
| &FileInfo, | |
| NewFileInfo, | |
| &UpdateMode, | |
| &Mode | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| // | |
| // If no attribute updates are necessary, we're done. | |
| // | |
| if (!UpdateFileSize && !UpdateAtime && !UpdateMtime && !UpdateMode) { | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // If the file is not open for writing, then only Mode may be updated (for | |
| // toggling EFI_FILE_READ_ONLY). | |
| // | |
| if (!VirtioFsFile->IsOpenForWriting && | |
| (UpdateFileSize || UpdateAtime || UpdateMtime)) | |
| { | |
| return EFI_ACCESS_DENIED; | |
| } | |
| // | |
| // Send the FUSE_SETATTR request now. | |
| // | |
| Status = VirtioFsFuseSetAttr ( | |
| VirtioFs, | |
| VirtioFsFile->NodeId, | |
| UpdateFileSize ? &FileSize : NULL, | |
| UpdateAtime ? &Atime : NULL, | |
| UpdateMtime ? &Mtime : NULL, | |
| UpdateMode ? &Mode : NULL | |
| ); | |
| return Status; | |
| } | |
| /** | |
| Process an EFI_FILE_INFO setting request. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| SetFileInfo ( | |
| IN EFI_FILE_PROTOCOL *This, | |
| IN UINTN BufferSize, | |
| IN VOID *Buffer | |
| ) | |
| { | |
| VIRTIO_FS_FILE *VirtioFsFile; | |
| EFI_STATUS Status; | |
| EFI_FILE_INFO *FileInfo; | |
| VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This); | |
| // | |
| // Validate if Buffer passes as EFI_FILE_INFO. | |
| // | |
| Status = ValidateInfoStructure ( | |
| BufferSize, // SizeByProtocolCaller | |
| OFFSET_OF ( | |
| EFI_FILE_INFO, | |
| FileName | |
| ) + sizeof (CHAR16), // MinimumStructSize | |
| TRUE, // IsSizeByInfoPresent | |
| Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| FileInfo = Buffer; | |
| // | |
| // Perform the rename/move request, if any. | |
| // | |
| Status = Rename (VirtioFsFile, FileInfo->FileName); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| // | |
| // Update any attributes requested. | |
| // | |
| Status = UpdateAttributes (VirtioFsFile, FileInfo); | |
| // | |
| // The UEFI spec does not speak about partial failure in | |
| // EFI_FILE_PROTOCOL.SetInfo(); we won't try to roll back the rename (if | |
| // there was one) in case the attribute updates fail. | |
| // | |
| return Status; | |
| } | |
| /** | |
| Process an EFI_FILE_SYSTEM_INFO setting request. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| SetFileSystemInfo ( | |
| IN EFI_FILE_PROTOCOL *This, | |
| IN UINTN BufferSize, | |
| IN VOID *Buffer | |
| ) | |
| { | |
| VIRTIO_FS_FILE *VirtioFsFile; | |
| VIRTIO_FS *VirtioFs; | |
| EFI_STATUS Status; | |
| EFI_FILE_SYSTEM_INFO *FileSystemInfo; | |
| VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This); | |
| VirtioFs = VirtioFsFile->OwnerFs; | |
| // | |
| // Validate if Buffer passes as EFI_FILE_SYSTEM_INFO. | |
| // | |
| Status = ValidateInfoStructure ( | |
| BufferSize, // SizeByProtocolCaller | |
| OFFSET_OF ( | |
| EFI_FILE_SYSTEM_INFO, | |
| VolumeLabel | |
| ) + sizeof (CHAR16), // MinimumStructSize | |
| TRUE, // IsSizeByInfoPresent | |
| Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| FileSystemInfo = Buffer; | |
| // | |
| // EFI_FILE_SYSTEM_INFO fields other than VolumeLabel cannot be changed, per | |
| // spec. | |
| // | |
| // If the label is being changed to its current value, report success; | |
| // otherwise, reject the request, as the Virtio Filesystem device does not | |
| // support changing the label. | |
| // | |
| if (StrCmp (FileSystemInfo->VolumeLabel, VirtioFs->Label) == 0) { | |
| return EFI_SUCCESS; | |
| } | |
| return EFI_WRITE_PROTECTED; | |
| } | |
| /** | |
| Process an EFI_FILE_SYSTEM_VOLUME_LABEL setting request. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| SetFileSystemVolumeLabelInfo ( | |
| IN EFI_FILE_PROTOCOL *This, | |
| IN UINTN BufferSize, | |
| IN VOID *Buffer | |
| ) | |
| { | |
| VIRTIO_FS_FILE *VirtioFsFile; | |
| VIRTIO_FS *VirtioFs; | |
| EFI_STATUS Status; | |
| EFI_FILE_SYSTEM_VOLUME_LABEL *FileSystemVolumeLabel; | |
| VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This); | |
| VirtioFs = VirtioFsFile->OwnerFs; | |
| // | |
| // Validate if Buffer passes as EFI_FILE_SYSTEM_VOLUME_LABEL. | |
| // | |
| Status = ValidateInfoStructure ( | |
| BufferSize, // SizeByProtocolCaller | |
| OFFSET_OF ( | |
| EFI_FILE_SYSTEM_VOLUME_LABEL, | |
| VolumeLabel | |
| ) + sizeof (CHAR16), // MinimumStructSize | |
| FALSE, // IsSizeByInfoPresent | |
| Buffer | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| FileSystemVolumeLabel = Buffer; | |
| // | |
| // If the label is being changed to its current value, report success; | |
| // otherwise, reject the request, as the Virtio Filesystem device does not | |
| // support changing the label. | |
| // | |
| if (StrCmp (FileSystemVolumeLabel->VolumeLabel, VirtioFs->Label) == 0) { | |
| return EFI_SUCCESS; | |
| } | |
| return EFI_WRITE_PROTECTED; | |
| } | |
| EFI_STATUS | |
| EFIAPI | |
| VirtioFsSimpleFileSetInfo ( | |
| IN EFI_FILE_PROTOCOL *This, | |
| IN EFI_GUID *InformationType, | |
| IN UINTN BufferSize, | |
| IN VOID *Buffer | |
| ) | |
| { | |
| if (CompareGuid (InformationType, &gEfiFileInfoGuid)) { | |
| return SetFileInfo (This, BufferSize, Buffer); | |
| } | |
| if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) { | |
| return SetFileSystemInfo (This, BufferSize, Buffer); | |
| } | |
| if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) { | |
| return SetFileSystemVolumeLabelInfo (This, BufferSize, Buffer); | |
| } | |
| return EFI_UNSUPPORTED; | |
| } |