2 EFI_FILE_PROTOCOL.SetInfo() member function for the Virtio Filesystem driver.
4 Copyright (C) 2020, Red Hat, Inc.
6 SPDX-License-Identifier: BSD-2-Clause-Patent
9 #include <Guid/FileSystemInfo.h> // gEfiFileSystemInfoGuid
10 #include <Guid/FileSystemVolumeLabelInfo.h> // gEfiFileSystemVolumeLabelInfo...
11 #include <Library/BaseLib.h> // StrCmp()
12 #include <Library/BaseMemoryLib.h> // CompareGuid()
13 #include <Library/MemoryAllocationLib.h> // FreePool()
15 #include "VirtioFsDxe.h"
18 Validate a buffer that the EFI_FILE_PROTOCOL.SetInfo() caller passes in for a
19 particular InformationType GUID.
21 The structure to be validated is supposed to end with a variable-length,
22 NUL-terminated CHAR16 Name string.
24 @param[in] SizeByProtocolCaller The BufferSize parameter as provided by the
25 EFI_FILE_PROTOCOL.SetInfo() caller.
27 @param[in] MinimumStructSize The minimum structure size that is required
28 for the given InformationType GUID,
29 including a single CHAR16 element from the
32 @param[in] IsSizeByInfoPresent TRUE if and only if the expected structure
33 starts with a UINT64 Size field that reports
34 the actual structure size.
36 @param[in] Buffer The Buffer parameter as provided by the
37 EFI_FILE_PROTOCOL.SetInfo() caller.
39 @retval EFI_SUCCESS Validation successful, Buffer is well-formed.
41 @retval EFI_BAD_BUFFER_SIZE The EFI_FILE_PROTOCOL.SetInfo()
42 caller provided a BufferSize that is smaller
43 than the minimum structure size required for
44 the given InformationType GUID.
46 @retval EFI_INVALID_PARAMETER IsSizeByInfoPresent is TRUE, and the leading
47 UINT64 Size field does not match the
48 EFI_FILE_PROTOCOL.SetInfo() caller-provided
51 @retval EFI_INVALID_PARAMETER The trailing Name field does not consist of a
52 whole multiple of CHAR16 elements.
54 @retval EFI_INVALID_PARAMETER The trailing Name field is not NUL-terminated.
58 ValidateInfoStructure (
59 IN UINTN SizeByProtocolCaller
,
60 IN UINTN MinimumStructSize
,
61 IN BOOLEAN IsSizeByInfoPresent
,
65 UINTN NameFieldByteOffset
;
67 UINTN NameFieldChar16s
;
71 // Make sure the internal function asking for validation passes in sane
74 ASSERT (MinimumStructSize
>= sizeof (CHAR16
));
75 NameFieldByteOffset
= MinimumStructSize
- sizeof (CHAR16
);
77 if (IsSizeByInfoPresent
) {
78 ASSERT (MinimumStructSize
>= sizeof (UINT64
) + sizeof (CHAR16
));
79 ASSERT (NameFieldByteOffset
>= sizeof (UINT64
));
83 // Check whether the protocol caller provided enough bytes for the minimum
84 // size of this info structure.
86 if (SizeByProtocolCaller
< MinimumStructSize
) {
87 return EFI_BAD_BUFFER_SIZE
;
91 // If the info structure starts with a UINT64 Size field, check if that
92 // agrees with the protocol caller-provided size.
94 if (IsSizeByInfoPresent
) {
98 if (*SizeByInfo
!= SizeByProtocolCaller
) {
99 return EFI_INVALID_PARAMETER
;
104 // The CHAR16 Name field at the end of the structure must have an even number
107 // The subtraction below cannot underflow, and yields at least
110 ASSERT (SizeByProtocolCaller
>= NameFieldByteOffset
);
111 NameFieldBytes
= SizeByProtocolCaller
- NameFieldByteOffset
;
112 ASSERT (NameFieldBytes
>= sizeof (CHAR16
));
113 if (NameFieldBytes
% sizeof (CHAR16
) != 0) {
114 return EFI_INVALID_PARAMETER
;
118 // The CHAR16 Name field at the end of the structure must be NUL-terminated.
120 NameFieldChar16s
= NameFieldBytes
/ sizeof (CHAR16
);
121 ASSERT (NameFieldChar16s
>= 1);
123 NameField
= (CHAR16
*)((UINT8
*)Buffer
+ NameFieldByteOffset
);
124 if (NameField
[NameFieldChar16s
- 1] != L
'\0') {
125 return EFI_INVALID_PARAMETER
;
132 Rename a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.FileName.
134 @param[in,out] VirtioFsFile The VIRTIO_FS_FILE to rename.
136 @param[in] NewFileName The new file name requested by
137 EFI_FILE_PROTOCOL.SetInfo().
139 @retval EFI_SUCCESS The canonical format destination path that is
140 determined from the input value of
141 VirtioFsFile->CanonicalPathname and from
142 NewFileName is identical to the input value of
143 VirtioFsFile->CanonicalPathname. This means that
144 EFI_FILE_INFO does not constitute a rename
145 request. VirtioFsFile has not been changed.
147 @retval EFI_SUCCESS VirtioFsFile has been renamed.
148 VirtioFsFile->CanonicalPathname has assumed the
149 destination pathname in canonical format.
151 @retval EFI_ACCESS_DENIED VirtioFsFile refers to the root directory, and
152 NewFileName expresses an actual rename/move
155 @retval EFI_ACCESS_DENIED VirtioFsFile is the (possibly indirect) parent
156 directory of at least one other VIRTIO_FS_FILE
157 that is open for the same Virtio Filesystem
158 (identified by VirtioFsFile->OwnerFs). Renaming
159 VirtioFsFile would invalidate the canonical
160 pathnames of those VIRTIO_FS_FILE instances;
161 therefore the request has been rejected.
163 @retval EFI_ACCESS_DENIED VirtioFsFile is not open for writing, but
164 NewFileName expresses an actual rename/move
167 @retval EFI_NOT_FOUND At least one dot-dot component in NewFileName
168 attempted to escape the root directory.
170 @return Error codes propagated from underlying functions.
175 IN OUT VIRTIO_FS_FILE
*VirtioFsFile
,
176 IN CHAR16
*NewFileName
183 UINT64 OldParentDirNodeId
;
184 CHAR8
*OldLastComponent
;
185 UINT64 NewParentDirNodeId
;
186 CHAR8
*NewLastComponent
;
188 VirtioFs
= VirtioFsFile
->OwnerFs
;
191 // The root directory cannot be renamed.
193 if (AsciiStrCmp (VirtioFsFile
->CanonicalPathname
, "/") == 0) {
194 if (StrCmp (NewFileName
, L
"") == 0) {
196 // Not a rename request anyway.
201 return EFI_ACCESS_DENIED
;
205 // Compose the canonical pathname for the destination.
207 Status
= VirtioFsComposeRenameDestination (
208 VirtioFsFile
->CanonicalPathname
,
213 if (EFI_ERROR (Status
)) {
218 Status
= EFI_NOT_FOUND
;
219 goto FreeDestination
;
223 // If the rename would leave VirtioFsFile->CanonicalPathname unchanged, then
224 // EFI_FILE_PROTOCOL.SetInfo() isn't asking for a rename actually.
226 if (AsciiStrCmp (VirtioFsFile
->CanonicalPathname
, Destination
) == 0) {
227 Status
= EFI_SUCCESS
;
228 goto FreeDestination
;
232 // Check if the rename would break the canonical pathnames of other
233 // VIRTIO_FS_FILE instances of the same VIRTIO_FS.
235 if (VirtioFsFile
->IsDirectory
) {
237 LIST_ENTRY
*OpenFilesEntry
;
239 PathLen
= AsciiStrLen (VirtioFsFile
->CanonicalPathname
);
240 BASE_LIST_FOR_EACH (OpenFilesEntry
, &VirtioFs
->OpenFiles
) {
241 VIRTIO_FS_FILE
*OtherFile
;
243 OtherFile
= VIRTIO_FS_FILE_FROM_OPEN_FILES_ENTRY (OpenFilesEntry
);
244 if ((OtherFile
!= VirtioFsFile
) &&
246 VirtioFsFile
->CanonicalPathname
,
247 OtherFile
->CanonicalPathname
,
250 ((OtherFile
->CanonicalPathname
[PathLen
] == '\0') ||
251 (OtherFile
->CanonicalPathname
[PathLen
] == '/')))
254 // OtherFile refers to the same directory as VirtioFsFile, or is a
255 // (possibly indirect) child of the directory referred to by
258 Status
= EFI_ACCESS_DENIED
;
259 goto FreeDestination
;
265 // From this point on, the file needs to be open for writing.
267 if (!VirtioFsFile
->IsOpenForWriting
) {
268 Status
= EFI_ACCESS_DENIED
;
269 goto FreeDestination
;
273 // Split both source and destination canonical pathnames into (most specific
274 // parent directory, last component) pairs.
276 Status
= VirtioFsLookupMostSpecificParentDir (
278 VirtioFsFile
->CanonicalPathname
,
282 if (EFI_ERROR (Status
)) {
283 goto FreeDestination
;
286 Status
= VirtioFsLookupMostSpecificParentDir (
292 if (EFI_ERROR (Status
)) {
293 goto ForgetOldParentDirNodeId
;
297 // Perform the rename. If the destination path exists, the rename will fail.
299 Status
= VirtioFsFuseRename (
306 if (EFI_ERROR (Status
)) {
307 goto ForgetNewParentDirNodeId
;
311 // Swap in the new canonical pathname.
313 FreePool (VirtioFsFile
->CanonicalPathname
);
314 VirtioFsFile
->CanonicalPathname
= Destination
;
316 Status
= EFI_SUCCESS
;
321 ForgetNewParentDirNodeId
:
322 if (NewParentDirNodeId
!= VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID
) {
323 VirtioFsFuseForget (VirtioFs
, NewParentDirNodeId
);
326 ForgetOldParentDirNodeId
:
327 if (OldParentDirNodeId
!= VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID
) {
328 VirtioFsFuseForget (VirtioFs
, OldParentDirNodeId
);
332 if (Destination
!= NULL
) {
333 FreePool (Destination
);
340 Update the attributes of a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.
342 @param[in,out] VirtioFsFile The VIRTIO_FS_FILE to update the attributes of.
344 @param[in] NewFileInfo The new attributes requested by
345 EFI_FILE_PROTOCOL.SetInfo(). NewFileInfo->Size
346 and NewFileInfo->FileName are ignored.
348 @retval EFI_SUCCESS No attributes had to be updated.
350 @retval EFI_SUCCESS The required set of attribute updates has been
351 determined and performed successfully.
353 @retval EFI_ACCESS_DENIED NewFileInfo requests an update to a property
354 different from the EFI_FILE_READ_ONLY bit in the
355 Attribute field, but VirtioFsFile is not open for
358 @return Error codes propagated from underlying functions.
363 IN OUT VIRTIO_FS_FILE
*VirtioFsFile
,
364 IN EFI_FILE_INFO
*NewFileInfo
369 VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr
;
370 EFI_FILE_INFO FileInfo
;
371 BOOLEAN UpdateFileSize
;
380 VirtioFs
= VirtioFsFile
->OwnerFs
;
383 // Fetch the current attributes first, so we can build the difference between
384 // them and NewFileInfo.
386 Status
= VirtioFsFuseGetAttr (VirtioFs
, VirtioFsFile
->NodeId
, &FuseAttr
);
387 if (EFI_ERROR (Status
)) {
391 Status
= VirtioFsFuseAttrToEfiFileInfo (&FuseAttr
, &FileInfo
);
392 if (EFI_ERROR (Status
)) {
397 // Collect the updates.
399 if (VirtioFsFile
->IsDirectory
) {
400 UpdateFileSize
= FALSE
;
402 VirtioFsGetFuseSizeUpdate (
410 Status
= VirtioFsGetFuseTimeUpdates (
418 if (EFI_ERROR (Status
)) {
422 Status
= VirtioFsGetFuseModeUpdate (
428 if (EFI_ERROR (Status
)) {
433 // If no attribute updates are necessary, we're done.
435 if (!UpdateFileSize
&& !UpdateAtime
&& !UpdateMtime
&& !UpdateMode
) {
440 // If the file is not open for writing, then only Mode may be updated (for
441 // toggling EFI_FILE_READ_ONLY).
443 if (!VirtioFsFile
->IsOpenForWriting
&&
444 (UpdateFileSize
|| UpdateAtime
|| UpdateMtime
))
446 return EFI_ACCESS_DENIED
;
450 // Send the FUSE_SETATTR request now.
452 Status
= VirtioFsFuseSetAttr (
454 VirtioFsFile
->NodeId
,
455 UpdateFileSize
? &FileSize
: NULL
,
456 UpdateAtime
? &Atime
: NULL
,
457 UpdateMtime
? &Mtime
: NULL
,
458 UpdateMode
? &Mode
: NULL
464 Process an EFI_FILE_INFO setting request.
469 IN EFI_FILE_PROTOCOL
*This
,
474 VIRTIO_FS_FILE
*VirtioFsFile
;
476 EFI_FILE_INFO
*FileInfo
;
478 VirtioFsFile
= VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This
);
481 // Validate if Buffer passes as EFI_FILE_INFO.
483 Status
= ValidateInfoStructure (
484 BufferSize
, // SizeByProtocolCaller
488 ) + sizeof (CHAR16
), // MinimumStructSize
489 TRUE
, // IsSizeByInfoPresent
492 if (EFI_ERROR (Status
)) {
499 // Perform the rename/move request, if any.
501 Status
= Rename (VirtioFsFile
, FileInfo
->FileName
);
502 if (EFI_ERROR (Status
)) {
507 // Update any attributes requested.
509 Status
= UpdateAttributes (VirtioFsFile
, FileInfo
);
511 // The UEFI spec does not speak about partial failure in
512 // EFI_FILE_PROTOCOL.SetInfo(); we won't try to roll back the rename (if
513 // there was one) in case the attribute updates fail.
519 Process an EFI_FILE_SYSTEM_INFO setting request.
524 IN EFI_FILE_PROTOCOL
*This
,
529 VIRTIO_FS_FILE
*VirtioFsFile
;
532 EFI_FILE_SYSTEM_INFO
*FileSystemInfo
;
534 VirtioFsFile
= VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This
);
535 VirtioFs
= VirtioFsFile
->OwnerFs
;
538 // Validate if Buffer passes as EFI_FILE_SYSTEM_INFO.
540 Status
= ValidateInfoStructure (
541 BufferSize
, // SizeByProtocolCaller
543 EFI_FILE_SYSTEM_INFO
,
545 ) + sizeof (CHAR16
), // MinimumStructSize
546 TRUE
, // IsSizeByInfoPresent
549 if (EFI_ERROR (Status
)) {
553 FileSystemInfo
= Buffer
;
556 // EFI_FILE_SYSTEM_INFO fields other than VolumeLabel cannot be changed, per
559 // If the label is being changed to its current value, report success;
560 // otherwise, reject the request, as the Virtio Filesystem device does not
561 // support changing the label.
563 if (StrCmp (FileSystemInfo
->VolumeLabel
, VirtioFs
->Label
) == 0) {
567 return EFI_WRITE_PROTECTED
;
571 Process an EFI_FILE_SYSTEM_VOLUME_LABEL setting request.
575 SetFileSystemVolumeLabelInfo (
576 IN EFI_FILE_PROTOCOL
*This
,
581 VIRTIO_FS_FILE
*VirtioFsFile
;
584 EFI_FILE_SYSTEM_VOLUME_LABEL
*FileSystemVolumeLabel
;
586 VirtioFsFile
= VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This
);
587 VirtioFs
= VirtioFsFile
->OwnerFs
;
590 // Validate if Buffer passes as EFI_FILE_SYSTEM_VOLUME_LABEL.
592 Status
= ValidateInfoStructure (
593 BufferSize
, // SizeByProtocolCaller
595 EFI_FILE_SYSTEM_VOLUME_LABEL
,
597 ) + sizeof (CHAR16
), // MinimumStructSize
598 FALSE
, // IsSizeByInfoPresent
601 if (EFI_ERROR (Status
)) {
605 FileSystemVolumeLabel
= Buffer
;
608 // If the label is being changed to its current value, report success;
609 // otherwise, reject the request, as the Virtio Filesystem device does not
610 // support changing the label.
612 if (StrCmp (FileSystemVolumeLabel
->VolumeLabel
, VirtioFs
->Label
) == 0) {
616 return EFI_WRITE_PROTECTED
;
621 VirtioFsSimpleFileSetInfo (
622 IN EFI_FILE_PROTOCOL
*This
,
623 IN EFI_GUID
*InformationType
,
628 if (CompareGuid (InformationType
, &gEfiFileInfoGuid
)) {
629 return SetFileInfo (This
, BufferSize
, Buffer
);
632 if (CompareGuid (InformationType
, &gEfiFileSystemInfoGuid
)) {
633 return SetFileSystemInfo (This
, BufferSize
, Buffer
);
636 if (CompareGuid (InformationType
, &gEfiFileSystemVolumeLabelInfoIdGuid
)) {
637 return SetFileSystemVolumeLabelInfo (This
, BufferSize
, Buffer
);
640 return EFI_UNSUPPORTED
;