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
184 UINT64 OldParentDirNodeId
;
185 CHAR8
*OldLastComponent
;
186 UINT64 NewParentDirNodeId
;
187 CHAR8
*NewLastComponent
;
189 VirtioFs
= VirtioFsFile
->OwnerFs
;
192 // The root directory cannot be renamed.
194 if (AsciiStrCmp (VirtioFsFile
->CanonicalPathname
, "/") == 0) {
195 if (StrCmp (NewFileName
, L
"") == 0) {
197 // Not a rename request anyway.
201 return EFI_ACCESS_DENIED
;
205 // Compose the canonical pathname for the destination.
207 Status
= VirtioFsComposeRenameDestination (VirtioFsFile
->CanonicalPathname
,
208 NewFileName
, &Destination
, &RootEscape
);
209 if (EFI_ERROR (Status
)) {
213 Status
= EFI_NOT_FOUND
;
214 goto FreeDestination
;
217 // If the rename would leave VirtioFsFile->CanonicalPathname unchanged, then
218 // EFI_FILE_PROTOCOL.SetInfo() isn't asking for a rename actually.
220 if (AsciiStrCmp (VirtioFsFile
->CanonicalPathname
, Destination
) == 0) {
221 Status
= EFI_SUCCESS
;
222 goto FreeDestination
;
225 // Check if the rename would break the canonical pathnames of other
226 // VIRTIO_FS_FILE instances of the same VIRTIO_FS.
228 if (VirtioFsFile
->IsDirectory
) {
230 LIST_ENTRY
*OpenFilesEntry
;
232 PathLen
= AsciiStrLen (VirtioFsFile
->CanonicalPathname
);
233 BASE_LIST_FOR_EACH (OpenFilesEntry
, &VirtioFs
->OpenFiles
) {
234 VIRTIO_FS_FILE
*OtherFile
;
236 OtherFile
= VIRTIO_FS_FILE_FROM_OPEN_FILES_ENTRY (OpenFilesEntry
);
237 if (OtherFile
!= VirtioFsFile
&&
238 AsciiStrnCmp (VirtioFsFile
->CanonicalPathname
,
239 OtherFile
->CanonicalPathname
, PathLen
) == 0 &&
240 (OtherFile
->CanonicalPathname
[PathLen
] == '\0' ||
241 OtherFile
->CanonicalPathname
[PathLen
] == '/')) {
243 // OtherFile refers to the same directory as VirtioFsFile, or is a
244 // (possibly indirect) child of the directory referred to by
247 Status
= EFI_ACCESS_DENIED
;
248 goto FreeDestination
;
253 // From this point on, the file needs to be open for writing.
255 if (!VirtioFsFile
->IsOpenForWriting
) {
256 Status
= EFI_ACCESS_DENIED
;
257 goto FreeDestination
;
260 // Split both source and destination canonical pathnames into (most specific
261 // parent directory, last component) pairs.
263 Status
= VirtioFsLookupMostSpecificParentDir (VirtioFs
,
264 VirtioFsFile
->CanonicalPathname
, &OldParentDirNodeId
,
266 if (EFI_ERROR (Status
)) {
267 goto FreeDestination
;
269 Status
= VirtioFsLookupMostSpecificParentDir (VirtioFs
, Destination
,
270 &NewParentDirNodeId
, &NewLastComponent
);
271 if (EFI_ERROR (Status
)) {
272 goto ForgetOldParentDirNodeId
;
275 // Perform the rename. If the destination path exists, the rename will fail.
277 Status
= VirtioFsFuseRename (VirtioFs
, OldParentDirNodeId
, OldLastComponent
,
278 NewParentDirNodeId
, NewLastComponent
);
279 if (EFI_ERROR (Status
)) {
280 goto ForgetNewParentDirNodeId
;
284 // Swap in the new canonical pathname.
286 FreePool (VirtioFsFile
->CanonicalPathname
);
287 VirtioFsFile
->CanonicalPathname
= Destination
;
289 Status
= EFI_SUCCESS
;
294 ForgetNewParentDirNodeId
:
295 if (NewParentDirNodeId
!= VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID
) {
296 VirtioFsFuseForget (VirtioFs
, NewParentDirNodeId
);
299 ForgetOldParentDirNodeId
:
300 if (OldParentDirNodeId
!= VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID
) {
301 VirtioFsFuseForget (VirtioFs
, OldParentDirNodeId
);
305 if (Destination
!= NULL
) {
306 FreePool (Destination
);
312 Update the attributes of a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.
314 @param[in,out] VirtioFsFile The VIRTIO_FS_FILE to update the attributes of.
316 @param[in] NewFileInfo The new attributes requested by
317 EFI_FILE_PROTOCOL.SetInfo(). NewFileInfo->Size
318 and NewFileInfo->FileName are ignored.
320 @retval EFI_SUCCESS No attributes had to be updated.
322 @retval EFI_SUCCESS The required set of attribute updates has been
323 determined and performed successfully.
325 @retval EFI_ACCESS_DENIED NewFileInfo requests an update to a property
326 different from the EFI_FILE_READ_ONLY bit in the
327 Attribute field, but VirtioFsFile is not open for
330 @return Error codes propagated from underlying functions.
335 IN OUT VIRTIO_FS_FILE
*VirtioFsFile
,
336 IN EFI_FILE_INFO
*NewFileInfo
341 VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr
;
342 EFI_FILE_INFO FileInfo
;
343 BOOLEAN UpdateFileSize
;
352 VirtioFs
= VirtioFsFile
->OwnerFs
;
355 // Fetch the current attributes first, so we can build the difference between
356 // them and NewFileInfo.
358 Status
= VirtioFsFuseGetAttr (VirtioFs
, VirtioFsFile
->NodeId
, &FuseAttr
);
359 if (EFI_ERROR (Status
)) {
362 Status
= VirtioFsFuseAttrToEfiFileInfo (&FuseAttr
, &FileInfo
);
363 if (EFI_ERROR (Status
)) {
367 // Collect the updates.
369 if (VirtioFsFile
->IsDirectory
) {
370 UpdateFileSize
= FALSE
;
372 VirtioFsGetFuseSizeUpdate (&FileInfo
, NewFileInfo
, &UpdateFileSize
,
376 Status
= VirtioFsGetFuseTimeUpdates (&FileInfo
, NewFileInfo
, &UpdateAtime
,
377 &UpdateMtime
, &Atime
, &Mtime
);
378 if (EFI_ERROR (Status
)) {
382 Status
= VirtioFsGetFuseModeUpdate (&FileInfo
, NewFileInfo
, &UpdateMode
,
384 if (EFI_ERROR (Status
)) {
389 // If no attribute updates are necessary, we're done.
391 if (!UpdateFileSize
&& !UpdateAtime
&& !UpdateMtime
&& !UpdateMode
) {
395 // If the file is not open for writing, then only Mode may be updated (for
396 // toggling EFI_FILE_READ_ONLY).
398 if (!VirtioFsFile
->IsOpenForWriting
&&
399 (UpdateFileSize
|| UpdateAtime
|| UpdateMtime
)) {
400 return EFI_ACCESS_DENIED
;
403 // Send the FUSE_SETATTR request now.
405 Status
= VirtioFsFuseSetAttr (
407 VirtioFsFile
->NodeId
,
408 UpdateFileSize
? &FileSize
: NULL
,
409 UpdateAtime
? &Atime
: NULL
,
410 UpdateMtime
? &Mtime
: NULL
,
411 UpdateMode
? &Mode
: NULL
417 Process an EFI_FILE_INFO setting request.
422 IN EFI_FILE_PROTOCOL
*This
,
427 VIRTIO_FS_FILE
*VirtioFsFile
;
429 EFI_FILE_INFO
*FileInfo
;
431 VirtioFsFile
= VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This
);
434 // Validate if Buffer passes as EFI_FILE_INFO.
436 Status
= ValidateInfoStructure (
437 BufferSize
, // SizeByProtocolCaller
438 OFFSET_OF (EFI_FILE_INFO
,
439 FileName
) + sizeof (CHAR16
), // MinimumStructSize
440 TRUE
, // IsSizeByInfoPresent
443 if (EFI_ERROR (Status
)) {
449 // Perform the rename/move request, if any.
451 Status
= Rename (VirtioFsFile
, FileInfo
->FileName
);
452 if (EFI_ERROR (Status
)) {
456 // Update any attributes requested.
458 Status
= UpdateAttributes (VirtioFsFile
, FileInfo
);
460 // The UEFI spec does not speak about partial failure in
461 // EFI_FILE_PROTOCOL.SetInfo(); we won't try to roll back the rename (if
462 // there was one) in case the attribute updates fail.
468 Process an EFI_FILE_SYSTEM_INFO setting request.
473 IN EFI_FILE_PROTOCOL
*This
,
478 VIRTIO_FS_FILE
*VirtioFsFile
;
481 EFI_FILE_SYSTEM_INFO
*FileSystemInfo
;
483 VirtioFsFile
= VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This
);
484 VirtioFs
= VirtioFsFile
->OwnerFs
;
487 // Validate if Buffer passes as EFI_FILE_SYSTEM_INFO.
489 Status
= ValidateInfoStructure (
490 BufferSize
, // SizeByProtocolCaller
491 OFFSET_OF (EFI_FILE_SYSTEM_INFO
,
492 VolumeLabel
) + sizeof (CHAR16
), // MinimumStructSize
493 TRUE
, // IsSizeByInfoPresent
496 if (EFI_ERROR (Status
)) {
499 FileSystemInfo
= Buffer
;
502 // EFI_FILE_SYSTEM_INFO fields other than VolumeLabel cannot be changed, per
505 // If the label is being changed to its current value, report success;
506 // otherwise, reject the request, as the Virtio Filesystem device does not
507 // support changing the label.
509 if (StrCmp (FileSystemInfo
->VolumeLabel
, VirtioFs
->Label
) == 0) {
512 return EFI_WRITE_PROTECTED
;
516 Process an EFI_FILE_SYSTEM_VOLUME_LABEL setting request.
520 SetFileSystemVolumeLabelInfo (
521 IN EFI_FILE_PROTOCOL
*This
,
526 VIRTIO_FS_FILE
*VirtioFsFile
;
529 EFI_FILE_SYSTEM_VOLUME_LABEL
*FileSystemVolumeLabel
;
531 VirtioFsFile
= VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This
);
532 VirtioFs
= VirtioFsFile
->OwnerFs
;
535 // Validate if Buffer passes as EFI_FILE_SYSTEM_VOLUME_LABEL.
537 Status
= ValidateInfoStructure (
538 BufferSize
, // SizeByProtocolCaller
539 OFFSET_OF (EFI_FILE_SYSTEM_VOLUME_LABEL
,
540 VolumeLabel
) + sizeof (CHAR16
), // MinimumStructSize
541 FALSE
, // IsSizeByInfoPresent
544 if (EFI_ERROR (Status
)) {
547 FileSystemVolumeLabel
= Buffer
;
550 // If the label is being changed to its current value, report success;
551 // otherwise, reject the request, as the Virtio Filesystem device does not
552 // support changing the label.
554 if (StrCmp (FileSystemVolumeLabel
->VolumeLabel
, VirtioFs
->Label
) == 0) {
557 return EFI_WRITE_PROTECTED
;
562 VirtioFsSimpleFileSetInfo (
563 IN EFI_FILE_PROTOCOL
*This
,
564 IN EFI_GUID
*InformationType
,
569 if (CompareGuid (InformationType
, &gEfiFileInfoGuid
)) {
570 return SetFileInfo (This
, BufferSize
, Buffer
);
573 if (CompareGuid (InformationType
, &gEfiFileSystemInfoGuid
)) {
574 return SetFileSystemInfo (This
, BufferSize
, Buffer
);
577 if (CompareGuid (InformationType
, &gEfiFileSystemVolumeLabelInfoIdGuid
)) {
578 return SetFileSystemVolumeLabelInfo (This
, BufferSize
, Buffer
);
581 return EFI_UNSUPPORTED
;