]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/VirtioFsDxe/SimpleFsSetInfo.c
OvmfPkg/VirtioFsDxe: handle attribute updates in EFI_FILE_PROTOCOL.SetInfo
[mirror_edk2.git] / OvmfPkg / VirtioFsDxe / SimpleFsSetInfo.c
1 /** @file
2 EFI_FILE_PROTOCOL.SetInfo() member function for the Virtio Filesystem driver.
3
4 Copyright (C) 2020, Red Hat, Inc.
5
6 SPDX-License-Identifier: BSD-2-Clause-Patent
7 **/
8
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()
14
15 #include "VirtioFsDxe.h"
16
17 /**
18 Validate a buffer that the EFI_FILE_PROTOCOL.SetInfo() caller passes in for a
19 particular InformationType GUID.
20
21 The structure to be validated is supposed to end with a variable-length,
22 NUL-terminated CHAR16 Name string.
23
24 @param[in] SizeByProtocolCaller The BufferSize parameter as provided by the
25 EFI_FILE_PROTOCOL.SetInfo() caller.
26
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
30 trailing Name field.
31
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.
35
36 @param[in] Buffer The Buffer parameter as provided by the
37 EFI_FILE_PROTOCOL.SetInfo() caller.
38
39 @retval EFI_SUCCESS Validation successful, Buffer is well-formed.
40
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.
45
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
49 BufferSize.
50
51 @retval EFI_INVALID_PARAMETER The trailing Name field does not consist of a
52 whole multiple of CHAR16 elements.
53
54 @retval EFI_INVALID_PARAMETER The trailing Name field is not NUL-terminated.
55 **/
56 STATIC
57 EFI_STATUS
58 ValidateInfoStructure (
59 IN UINTN SizeByProtocolCaller,
60 IN UINTN MinimumStructSize,
61 IN BOOLEAN IsSizeByInfoPresent,
62 IN VOID *Buffer
63 )
64 {
65 UINTN NameFieldByteOffset;
66 UINTN NameFieldBytes;
67 UINTN NameFieldChar16s;
68 CHAR16 *NameField;
69
70 //
71 // Make sure the internal function asking for validation passes in sane
72 // values.
73 //
74 ASSERT (MinimumStructSize >= sizeof (CHAR16));
75 NameFieldByteOffset = MinimumStructSize - sizeof (CHAR16);
76
77 if (IsSizeByInfoPresent) {
78 ASSERT (MinimumStructSize >= sizeof (UINT64) + sizeof (CHAR16));
79 ASSERT (NameFieldByteOffset >= sizeof (UINT64));
80 }
81
82 //
83 // Check whether the protocol caller provided enough bytes for the minimum
84 // size of this info structure.
85 //
86 if (SizeByProtocolCaller < MinimumStructSize) {
87 return EFI_BAD_BUFFER_SIZE;
88 }
89
90 //
91 // If the info structure starts with a UINT64 Size field, check if that
92 // agrees with the protocol caller-provided size.
93 //
94 if (IsSizeByInfoPresent) {
95 UINT64 *SizeByInfo;
96
97 SizeByInfo = Buffer;
98 if (*SizeByInfo != SizeByProtocolCaller) {
99 return EFI_INVALID_PARAMETER;
100 }
101 }
102
103 //
104 // The CHAR16 Name field at the end of the structure must have an even number
105 // of bytes.
106 //
107 // The subtraction below cannot underflow, and yields at least
108 // sizeof(CHAR16).
109 //
110 ASSERT (SizeByProtocolCaller >= NameFieldByteOffset);
111 NameFieldBytes = SizeByProtocolCaller - NameFieldByteOffset;
112 ASSERT (NameFieldBytes >= sizeof (CHAR16));
113 if (NameFieldBytes % sizeof (CHAR16) != 0) {
114 return EFI_INVALID_PARAMETER;
115 }
116
117 //
118 // The CHAR16 Name field at the end of the structure must be NUL-terminated.
119 //
120 NameFieldChar16s = NameFieldBytes / sizeof (CHAR16);
121 ASSERT (NameFieldChar16s >= 1);
122
123 NameField = (CHAR16 *)((UINT8 *)Buffer + NameFieldByteOffset);
124 if (NameField[NameFieldChar16s - 1] != L'\0') {
125 return EFI_INVALID_PARAMETER;
126 }
127
128 return EFI_SUCCESS;
129 }
130
131 /**
132 Rename a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.FileName.
133
134 @param[in,out] VirtioFsFile The VIRTIO_FS_FILE to rename.
135
136 @param[in] NewFileName The new file name requested by
137 EFI_FILE_PROTOCOL.SetInfo().
138
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.
146
147 @retval EFI_SUCCESS VirtioFsFile has been renamed.
148 VirtioFsFile->CanonicalPathname has assumed the
149 destination pathname in canonical format.
150
151 @retval EFI_ACCESS_DENIED VirtioFsFile refers to the root directory, and
152 NewFileName expresses an actual rename/move
153 request.
154
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.
162
163 @retval EFI_ACCESS_DENIED VirtioFsFile is not open for writing, but
164 NewFileName expresses an actual rename/move
165 request.
166
167 @retval EFI_NOT_FOUND At least one dot-dot component in NewFileName
168 attempted to escape the root directory.
169
170 @return Error codes propagated from underlying functions.
171 **/
172 STATIC
173 EFI_STATUS
174 Rename (
175 IN OUT VIRTIO_FS_FILE *VirtioFsFile,
176 IN CHAR16 *NewFileName
177 )
178 {
179
180 VIRTIO_FS *VirtioFs;
181 EFI_STATUS Status;
182 CHAR8 *Destination;
183 BOOLEAN RootEscape;
184 UINT64 OldParentDirNodeId;
185 CHAR8 *OldLastComponent;
186 UINT64 NewParentDirNodeId;
187 CHAR8 *NewLastComponent;
188
189 VirtioFs = VirtioFsFile->OwnerFs;
190
191 //
192 // The root directory cannot be renamed.
193 //
194 if (AsciiStrCmp (VirtioFsFile->CanonicalPathname, "/") == 0) {
195 if (StrCmp (NewFileName, L"") == 0) {
196 //
197 // Not a rename request anyway.
198 //
199 return EFI_SUCCESS;
200 }
201 return EFI_ACCESS_DENIED;
202 }
203
204 //
205 // Compose the canonical pathname for the destination.
206 //
207 Status = VirtioFsComposeRenameDestination (VirtioFsFile->CanonicalPathname,
208 NewFileName, &Destination, &RootEscape);
209 if (EFI_ERROR (Status)) {
210 return Status;
211 }
212 if (RootEscape) {
213 Status = EFI_NOT_FOUND;
214 goto FreeDestination;
215 }
216 //
217 // If the rename would leave VirtioFsFile->CanonicalPathname unchanged, then
218 // EFI_FILE_PROTOCOL.SetInfo() isn't asking for a rename actually.
219 //
220 if (AsciiStrCmp (VirtioFsFile->CanonicalPathname, Destination) == 0) {
221 Status = EFI_SUCCESS;
222 goto FreeDestination;
223 }
224 //
225 // Check if the rename would break the canonical pathnames of other
226 // VIRTIO_FS_FILE instances of the same VIRTIO_FS.
227 //
228 if (VirtioFsFile->IsDirectory) {
229 UINTN PathLen;
230 LIST_ENTRY *OpenFilesEntry;
231
232 PathLen = AsciiStrLen (VirtioFsFile->CanonicalPathname);
233 BASE_LIST_FOR_EACH (OpenFilesEntry, &VirtioFs->OpenFiles) {
234 VIRTIO_FS_FILE *OtherFile;
235
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] == '/')) {
242 //
243 // OtherFile refers to the same directory as VirtioFsFile, or is a
244 // (possibly indirect) child of the directory referred to by
245 // VirtioFsFile.
246 //
247 Status = EFI_ACCESS_DENIED;
248 goto FreeDestination;
249 }
250 }
251 }
252 //
253 // From this point on, the file needs to be open for writing.
254 //
255 if (!VirtioFsFile->IsOpenForWriting) {
256 Status = EFI_ACCESS_DENIED;
257 goto FreeDestination;
258 }
259 //
260 // Split both source and destination canonical pathnames into (most specific
261 // parent directory, last component) pairs.
262 //
263 Status = VirtioFsLookupMostSpecificParentDir (VirtioFs,
264 VirtioFsFile->CanonicalPathname, &OldParentDirNodeId,
265 &OldLastComponent);
266 if (EFI_ERROR (Status)) {
267 goto FreeDestination;
268 }
269 Status = VirtioFsLookupMostSpecificParentDir (VirtioFs, Destination,
270 &NewParentDirNodeId, &NewLastComponent);
271 if (EFI_ERROR (Status)) {
272 goto ForgetOldParentDirNodeId;
273 }
274 //
275 // Perform the rename. If the destination path exists, the rename will fail.
276 //
277 Status = VirtioFsFuseRename (VirtioFs, OldParentDirNodeId, OldLastComponent,
278 NewParentDirNodeId, NewLastComponent);
279 if (EFI_ERROR (Status)) {
280 goto ForgetNewParentDirNodeId;
281 }
282
283 //
284 // Swap in the new canonical pathname.
285 //
286 FreePool (VirtioFsFile->CanonicalPathname);
287 VirtioFsFile->CanonicalPathname = Destination;
288 Destination = NULL;
289 Status = EFI_SUCCESS;
290
291 //
292 // Fall through.
293 //
294 ForgetNewParentDirNodeId:
295 if (NewParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {
296 VirtioFsFuseForget (VirtioFs, NewParentDirNodeId);
297 }
298
299 ForgetOldParentDirNodeId:
300 if (OldParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {
301 VirtioFsFuseForget (VirtioFs, OldParentDirNodeId);
302 }
303
304 FreeDestination:
305 if (Destination != NULL) {
306 FreePool (Destination);
307 }
308 return Status;
309 }
310
311 /**
312 Update the attributes of a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.
313
314 @param[in,out] VirtioFsFile The VIRTIO_FS_FILE to update the attributes of.
315
316 @param[in] NewFileInfo The new attributes requested by
317 EFI_FILE_PROTOCOL.SetInfo(). NewFileInfo->Size
318 and NewFileInfo->FileName are ignored.
319
320 @retval EFI_SUCCESS No attributes had to be updated.
321
322 @retval EFI_SUCCESS The required set of attribute updates has been
323 determined and performed successfully.
324
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
328 writing.
329
330 @return Error codes propagated from underlying functions.
331 **/
332 STATIC
333 EFI_STATUS
334 UpdateAttributes (
335 IN OUT VIRTIO_FS_FILE *VirtioFsFile,
336 IN EFI_FILE_INFO *NewFileInfo
337 )
338 {
339 VIRTIO_FS *VirtioFs;
340 EFI_STATUS Status;
341 VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr;
342 EFI_FILE_INFO FileInfo;
343 BOOLEAN UpdateFileSize;
344 UINT64 FileSize;
345 BOOLEAN UpdateAtime;
346 BOOLEAN UpdateMtime;
347 UINT64 Atime;
348 UINT64 Mtime;
349 BOOLEAN UpdateMode;
350 UINT32 Mode;
351
352 VirtioFs = VirtioFsFile->OwnerFs;
353
354 //
355 // Fetch the current attributes first, so we can build the difference between
356 // them and NewFileInfo.
357 //
358 Status = VirtioFsFuseGetAttr (VirtioFs, VirtioFsFile->NodeId, &FuseAttr);
359 if (EFI_ERROR (Status)) {
360 return Status;
361 }
362 Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo);
363 if (EFI_ERROR (Status)) {
364 return Status;
365 }
366 //
367 // Collect the updates.
368 //
369 if (VirtioFsFile->IsDirectory) {
370 UpdateFileSize = FALSE;
371 } else {
372 VirtioFsGetFuseSizeUpdate (&FileInfo, NewFileInfo, &UpdateFileSize,
373 &FileSize);
374 }
375
376 Status = VirtioFsGetFuseTimeUpdates (&FileInfo, NewFileInfo, &UpdateAtime,
377 &UpdateMtime, &Atime, &Mtime);
378 if (EFI_ERROR (Status)) {
379 return Status;
380 }
381
382 Status = VirtioFsGetFuseModeUpdate (&FileInfo, NewFileInfo, &UpdateMode,
383 &Mode);
384 if (EFI_ERROR (Status)) {
385 return Status;
386 }
387
388 //
389 // If no attribute updates are necessary, we're done.
390 //
391 if (!UpdateFileSize && !UpdateAtime && !UpdateMtime && !UpdateMode) {
392 return EFI_SUCCESS;
393 }
394 //
395 // If the file is not open for writing, then only Mode may be updated (for
396 // toggling EFI_FILE_READ_ONLY).
397 //
398 if (!VirtioFsFile->IsOpenForWriting &&
399 (UpdateFileSize || UpdateAtime || UpdateMtime)) {
400 return EFI_ACCESS_DENIED;
401 }
402 //
403 // Send the FUSE_SETATTR request now.
404 //
405 Status = VirtioFsFuseSetAttr (
406 VirtioFs,
407 VirtioFsFile->NodeId,
408 UpdateFileSize ? &FileSize : NULL,
409 UpdateAtime ? &Atime : NULL,
410 UpdateMtime ? &Mtime : NULL,
411 UpdateMode ? &Mode : NULL
412 );
413 return Status;
414 }
415
416 /**
417 Process an EFI_FILE_INFO setting request.
418 **/
419 STATIC
420 EFI_STATUS
421 SetFileInfo (
422 IN EFI_FILE_PROTOCOL *This,
423 IN UINTN BufferSize,
424 IN VOID *Buffer
425 )
426 {
427 VIRTIO_FS_FILE *VirtioFsFile;
428 EFI_STATUS Status;
429 EFI_FILE_INFO *FileInfo;
430
431 VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
432
433 //
434 // Validate if Buffer passes as EFI_FILE_INFO.
435 //
436 Status = ValidateInfoStructure (
437 BufferSize, // SizeByProtocolCaller
438 OFFSET_OF (EFI_FILE_INFO,
439 FileName) + sizeof (CHAR16), // MinimumStructSize
440 TRUE, // IsSizeByInfoPresent
441 Buffer
442 );
443 if (EFI_ERROR (Status)) {
444 return Status;
445 }
446 FileInfo = Buffer;
447
448 //
449 // Perform the rename/move request, if any.
450 //
451 Status = Rename (VirtioFsFile, FileInfo->FileName);
452 if (EFI_ERROR (Status)) {
453 return Status;
454 }
455 //
456 // Update any attributes requested.
457 //
458 Status = UpdateAttributes (VirtioFsFile, FileInfo);
459 //
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.
463 //
464 return Status;
465 }
466
467 /**
468 Process an EFI_FILE_SYSTEM_INFO setting request.
469 **/
470 STATIC
471 EFI_STATUS
472 SetFileSystemInfo (
473 IN EFI_FILE_PROTOCOL *This,
474 IN UINTN BufferSize,
475 IN VOID *Buffer
476 )
477 {
478 VIRTIO_FS_FILE *VirtioFsFile;
479 VIRTIO_FS *VirtioFs;
480 EFI_STATUS Status;
481 EFI_FILE_SYSTEM_INFO *FileSystemInfo;
482
483 VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
484 VirtioFs = VirtioFsFile->OwnerFs;
485
486 //
487 // Validate if Buffer passes as EFI_FILE_SYSTEM_INFO.
488 //
489 Status = ValidateInfoStructure (
490 BufferSize, // SizeByProtocolCaller
491 OFFSET_OF (EFI_FILE_SYSTEM_INFO,
492 VolumeLabel) + sizeof (CHAR16), // MinimumStructSize
493 TRUE, // IsSizeByInfoPresent
494 Buffer
495 );
496 if (EFI_ERROR (Status)) {
497 return Status;
498 }
499 FileSystemInfo = Buffer;
500
501 //
502 // EFI_FILE_SYSTEM_INFO fields other than VolumeLabel cannot be changed, per
503 // spec.
504 //
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.
508 //
509 if (StrCmp (FileSystemInfo->VolumeLabel, VirtioFs->Label) == 0) {
510 return EFI_SUCCESS;
511 }
512 return EFI_WRITE_PROTECTED;
513 }
514
515 /**
516 Process an EFI_FILE_SYSTEM_VOLUME_LABEL setting request.
517 **/
518 STATIC
519 EFI_STATUS
520 SetFileSystemVolumeLabelInfo (
521 IN EFI_FILE_PROTOCOL *This,
522 IN UINTN BufferSize,
523 IN VOID *Buffer
524 )
525 {
526 VIRTIO_FS_FILE *VirtioFsFile;
527 VIRTIO_FS *VirtioFs;
528 EFI_STATUS Status;
529 EFI_FILE_SYSTEM_VOLUME_LABEL *FileSystemVolumeLabel;
530
531 VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
532 VirtioFs = VirtioFsFile->OwnerFs;
533
534 //
535 // Validate if Buffer passes as EFI_FILE_SYSTEM_VOLUME_LABEL.
536 //
537 Status = ValidateInfoStructure (
538 BufferSize, // SizeByProtocolCaller
539 OFFSET_OF (EFI_FILE_SYSTEM_VOLUME_LABEL,
540 VolumeLabel) + sizeof (CHAR16), // MinimumStructSize
541 FALSE, // IsSizeByInfoPresent
542 Buffer
543 );
544 if (EFI_ERROR (Status)) {
545 return Status;
546 }
547 FileSystemVolumeLabel = Buffer;
548
549 //
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.
553 //
554 if (StrCmp (FileSystemVolumeLabel->VolumeLabel, VirtioFs->Label) == 0) {
555 return EFI_SUCCESS;
556 }
557 return EFI_WRITE_PROTECTED;
558 }
559
560 EFI_STATUS
561 EFIAPI
562 VirtioFsSimpleFileSetInfo (
563 IN EFI_FILE_PROTOCOL *This,
564 IN EFI_GUID *InformationType,
565 IN UINTN BufferSize,
566 IN VOID *Buffer
567 )
568 {
569 if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
570 return SetFileInfo (This, BufferSize, Buffer);
571 }
572
573 if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
574 return SetFileSystemInfo (This, BufferSize, Buffer);
575 }
576
577 if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
578 return SetFileSystemVolumeLabelInfo (This, BufferSize, Buffer);
579 }
580
581 return EFI_UNSUPPORTED;
582 }