]> git.proxmox.com Git - mirror_edk2.git/blame - OvmfPkg/VirtioFsDxe/SimpleFsSetInfo.c
OvmfPkg/VirtioFsDxe: handle file rename/move in EFI_FILE_PROTOCOL.SetInfo
[mirror_edk2.git] / OvmfPkg / VirtioFsDxe / SimpleFsSetInfo.c
CommitLineData
334c13e1
LE
1/** @file\r
2 EFI_FILE_PROTOCOL.SetInfo() member function for the Virtio Filesystem driver.\r
3\r
4 Copyright (C) 2020, Red Hat, Inc.\r
5\r
6 SPDX-License-Identifier: BSD-2-Clause-Patent\r
7**/\r
8\r
c9f473df
LE
9#include <Guid/FileSystemInfo.h> // gEfiFileSystemInfoGuid\r
10#include <Guid/FileSystemVolumeLabelInfo.h> // gEfiFileSystemVolumeLabelInfo...\r
11#include <Library/BaseLib.h> // StrCmp()\r
12#include <Library/BaseMemoryLib.h> // CompareGuid()\r
dd091185 13#include <Library/MemoryAllocationLib.h> // FreePool()\r
c9f473df 14\r
334c13e1
LE
15#include "VirtioFsDxe.h"\r
16\r
c9f473df
LE
17/**\r
18 Validate a buffer that the EFI_FILE_PROTOCOL.SetInfo() caller passes in for a\r
19 particular InformationType GUID.\r
20\r
21 The structure to be validated is supposed to end with a variable-length,\r
22 NUL-terminated CHAR16 Name string.\r
23\r
24 @param[in] SizeByProtocolCaller The BufferSize parameter as provided by the\r
25 EFI_FILE_PROTOCOL.SetInfo() caller.\r
26\r
27 @param[in] MinimumStructSize The minimum structure size that is required\r
28 for the given InformationType GUID,\r
29 including a single CHAR16 element from the\r
30 trailing Name field.\r
31\r
32 @param[in] IsSizeByInfoPresent TRUE if and only if the expected structure\r
33 starts with a UINT64 Size field that reports\r
34 the actual structure size.\r
35\r
36 @param[in] Buffer The Buffer parameter as provided by the\r
37 EFI_FILE_PROTOCOL.SetInfo() caller.\r
38\r
39 @retval EFI_SUCCESS Validation successful, Buffer is well-formed.\r
40\r
41 @retval EFI_BAD_BUFFER_SIZE The EFI_FILE_PROTOCOL.SetInfo()\r
42 caller provided a BufferSize that is smaller\r
43 than the minimum structure size required for\r
44 the given InformationType GUID.\r
45\r
46 @retval EFI_INVALID_PARAMETER IsSizeByInfoPresent is TRUE, and the leading\r
47 UINT64 Size field does not match the\r
48 EFI_FILE_PROTOCOL.SetInfo() caller-provided\r
49 BufferSize.\r
50\r
51 @retval EFI_INVALID_PARAMETER The trailing Name field does not consist of a\r
52 whole multiple of CHAR16 elements.\r
53\r
54 @retval EFI_INVALID_PARAMETER The trailing Name field is not NUL-terminated.\r
55**/\r
56STATIC\r
57EFI_STATUS\r
58ValidateInfoStructure (\r
59 IN UINTN SizeByProtocolCaller,\r
60 IN UINTN MinimumStructSize,\r
61 IN BOOLEAN IsSizeByInfoPresent,\r
62 IN VOID *Buffer\r
63 )\r
64{\r
65 UINTN NameFieldByteOffset;\r
66 UINTN NameFieldBytes;\r
67 UINTN NameFieldChar16s;\r
68 CHAR16 *NameField;\r
69\r
70 //\r
71 // Make sure the internal function asking for validation passes in sane\r
72 // values.\r
73 //\r
74 ASSERT (MinimumStructSize >= sizeof (CHAR16));\r
75 NameFieldByteOffset = MinimumStructSize - sizeof (CHAR16);\r
76\r
77 if (IsSizeByInfoPresent) {\r
78 ASSERT (MinimumStructSize >= sizeof (UINT64) + sizeof (CHAR16));\r
79 ASSERT (NameFieldByteOffset >= sizeof (UINT64));\r
80 }\r
81\r
82 //\r
83 // Check whether the protocol caller provided enough bytes for the minimum\r
84 // size of this info structure.\r
85 //\r
86 if (SizeByProtocolCaller < MinimumStructSize) {\r
87 return EFI_BAD_BUFFER_SIZE;\r
88 }\r
89\r
90 //\r
91 // If the info structure starts with a UINT64 Size field, check if that\r
92 // agrees with the protocol caller-provided size.\r
93 //\r
94 if (IsSizeByInfoPresent) {\r
95 UINT64 *SizeByInfo;\r
96\r
97 SizeByInfo = Buffer;\r
98 if (*SizeByInfo != SizeByProtocolCaller) {\r
99 return EFI_INVALID_PARAMETER;\r
100 }\r
101 }\r
102\r
103 //\r
104 // The CHAR16 Name field at the end of the structure must have an even number\r
105 // of bytes.\r
106 //\r
107 // The subtraction below cannot underflow, and yields at least\r
108 // sizeof(CHAR16).\r
109 //\r
110 ASSERT (SizeByProtocolCaller >= NameFieldByteOffset);\r
111 NameFieldBytes = SizeByProtocolCaller - NameFieldByteOffset;\r
112 ASSERT (NameFieldBytes >= sizeof (CHAR16));\r
113 if (NameFieldBytes % sizeof (CHAR16) != 0) {\r
114 return EFI_INVALID_PARAMETER;\r
115 }\r
116\r
117 //\r
118 // The CHAR16 Name field at the end of the structure must be NUL-terminated.\r
119 //\r
120 NameFieldChar16s = NameFieldBytes / sizeof (CHAR16);\r
121 ASSERT (NameFieldChar16s >= 1);\r
122\r
123 NameField = (CHAR16 *)((UINT8 *)Buffer + NameFieldByteOffset);\r
124 if (NameField[NameFieldChar16s - 1] != L'\0') {\r
125 return EFI_INVALID_PARAMETER;\r
126 }\r
127\r
128 return EFI_SUCCESS;\r
129}\r
130\r
dd091185
LE
131/**\r
132 Rename a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.FileName.\r
133\r
134 @param[in,out] VirtioFsFile The VIRTIO_FS_FILE to rename.\r
135\r
136 @param[in] NewFileName The new file name requested by\r
137 EFI_FILE_PROTOCOL.SetInfo().\r
138\r
139 @retval EFI_SUCCESS The canonical format destination path that is\r
140 determined from the input value of\r
141 VirtioFsFile->CanonicalPathname and from\r
142 NewFileName is identical to the input value of\r
143 VirtioFsFile->CanonicalPathname. This means that\r
144 EFI_FILE_INFO does not constitute a rename\r
145 request. VirtioFsFile has not been changed.\r
146\r
147 @retval EFI_SUCCESS VirtioFsFile has been renamed.\r
148 VirtioFsFile->CanonicalPathname has assumed the\r
149 destination pathname in canonical format.\r
150\r
151 @retval EFI_ACCESS_DENIED VirtioFsFile refers to the root directory, and\r
152 NewFileName expresses an actual rename/move\r
153 request.\r
154\r
155 @retval EFI_ACCESS_DENIED VirtioFsFile is the (possibly indirect) parent\r
156 directory of at least one other VIRTIO_FS_FILE\r
157 that is open for the same Virtio Filesystem\r
158 (identified by VirtioFsFile->OwnerFs). Renaming\r
159 VirtioFsFile would invalidate the canonical\r
160 pathnames of those VIRTIO_FS_FILE instances;\r
161 therefore the request has been rejected.\r
162\r
163 @retval EFI_ACCESS_DENIED VirtioFsFile is not open for writing, but\r
164 NewFileName expresses an actual rename/move\r
165 request.\r
166\r
167 @retval EFI_NOT_FOUND At least one dot-dot component in NewFileName\r
168 attempted to escape the root directory.\r
169\r
170 @return Error codes propagated from underlying functions.\r
171**/\r
172STATIC\r
173EFI_STATUS\r
174Rename (\r
175 IN OUT VIRTIO_FS_FILE *VirtioFsFile,\r
176 IN CHAR16 *NewFileName\r
177 )\r
178{\r
179\r
180 VIRTIO_FS *VirtioFs;\r
181 EFI_STATUS Status;\r
182 CHAR8 *Destination;\r
183 BOOLEAN RootEscape;\r
184 UINT64 OldParentDirNodeId;\r
185 CHAR8 *OldLastComponent;\r
186 UINT64 NewParentDirNodeId;\r
187 CHAR8 *NewLastComponent;\r
188\r
189 VirtioFs = VirtioFsFile->OwnerFs;\r
190\r
191 //\r
192 // The root directory cannot be renamed.\r
193 //\r
194 if (AsciiStrCmp (VirtioFsFile->CanonicalPathname, "/") == 0) {\r
195 if (StrCmp (NewFileName, L"") == 0) {\r
196 //\r
197 // Not a rename request anyway.\r
198 //\r
199 return EFI_SUCCESS;\r
200 }\r
201 return EFI_ACCESS_DENIED;\r
202 }\r
203\r
204 //\r
205 // Compose the canonical pathname for the destination.\r
206 //\r
207 Status = VirtioFsComposeRenameDestination (VirtioFsFile->CanonicalPathname,\r
208 NewFileName, &Destination, &RootEscape);\r
209 if (EFI_ERROR (Status)) {\r
210 return Status;\r
211 }\r
212 if (RootEscape) {\r
213 Status = EFI_NOT_FOUND;\r
214 goto FreeDestination;\r
215 }\r
216 //\r
217 // If the rename would leave VirtioFsFile->CanonicalPathname unchanged, then\r
218 // EFI_FILE_PROTOCOL.SetInfo() isn't asking for a rename actually.\r
219 //\r
220 if (AsciiStrCmp (VirtioFsFile->CanonicalPathname, Destination) == 0) {\r
221 Status = EFI_SUCCESS;\r
222 goto FreeDestination;\r
223 }\r
224 //\r
225 // Check if the rename would break the canonical pathnames of other\r
226 // VIRTIO_FS_FILE instances of the same VIRTIO_FS.\r
227 //\r
228 if (VirtioFsFile->IsDirectory) {\r
229 UINTN PathLen;\r
230 LIST_ENTRY *OpenFilesEntry;\r
231\r
232 PathLen = AsciiStrLen (VirtioFsFile->CanonicalPathname);\r
233 BASE_LIST_FOR_EACH (OpenFilesEntry, &VirtioFs->OpenFiles) {\r
234 VIRTIO_FS_FILE *OtherFile;\r
235\r
236 OtherFile = VIRTIO_FS_FILE_FROM_OPEN_FILES_ENTRY (OpenFilesEntry);\r
237 if (OtherFile != VirtioFsFile &&\r
238 AsciiStrnCmp (VirtioFsFile->CanonicalPathname,\r
239 OtherFile->CanonicalPathname, PathLen) == 0 &&\r
240 (OtherFile->CanonicalPathname[PathLen] == '\0' ||\r
241 OtherFile->CanonicalPathname[PathLen] == '/')) {\r
242 //\r
243 // OtherFile refers to the same directory as VirtioFsFile, or is a\r
244 // (possibly indirect) child of the directory referred to by\r
245 // VirtioFsFile.\r
246 //\r
247 Status = EFI_ACCESS_DENIED;\r
248 goto FreeDestination;\r
249 }\r
250 }\r
251 }\r
252 //\r
253 // From this point on, the file needs to be open for writing.\r
254 //\r
255 if (!VirtioFsFile->IsOpenForWriting) {\r
256 Status = EFI_ACCESS_DENIED;\r
257 goto FreeDestination;\r
258 }\r
259 //\r
260 // Split both source and destination canonical pathnames into (most specific\r
261 // parent directory, last component) pairs.\r
262 //\r
263 Status = VirtioFsLookupMostSpecificParentDir (VirtioFs,\r
264 VirtioFsFile->CanonicalPathname, &OldParentDirNodeId,\r
265 &OldLastComponent);\r
266 if (EFI_ERROR (Status)) {\r
267 goto FreeDestination;\r
268 }\r
269 Status = VirtioFsLookupMostSpecificParentDir (VirtioFs, Destination,\r
270 &NewParentDirNodeId, &NewLastComponent);\r
271 if (EFI_ERROR (Status)) {\r
272 goto ForgetOldParentDirNodeId;\r
273 }\r
274 //\r
275 // Perform the rename. If the destination path exists, the rename will fail.\r
276 //\r
277 Status = VirtioFsFuseRename (VirtioFs, OldParentDirNodeId, OldLastComponent,\r
278 NewParentDirNodeId, NewLastComponent);\r
279 if (EFI_ERROR (Status)) {\r
280 goto ForgetNewParentDirNodeId;\r
281 }\r
282\r
283 //\r
284 // Swap in the new canonical pathname.\r
285 //\r
286 FreePool (VirtioFsFile->CanonicalPathname);\r
287 VirtioFsFile->CanonicalPathname = Destination;\r
288 Destination = NULL;\r
289 Status = EFI_SUCCESS;\r
290\r
291 //\r
292 // Fall through.\r
293 //\r
294ForgetNewParentDirNodeId:\r
295 if (NewParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {\r
296 VirtioFsFuseForget (VirtioFs, NewParentDirNodeId);\r
297 }\r
298\r
299ForgetOldParentDirNodeId:\r
300 if (OldParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {\r
301 VirtioFsFuseForget (VirtioFs, OldParentDirNodeId);\r
302 }\r
303\r
304FreeDestination:\r
305 if (Destination != NULL) {\r
306 FreePool (Destination);\r
307 }\r
308 return Status;\r
309}\r
310\r
311/**\r
312 Process an EFI_FILE_INFO setting request.\r
313**/\r
314STATIC\r
315EFI_STATUS\r
316SetFileInfo (\r
317 IN EFI_FILE_PROTOCOL *This,\r
318 IN UINTN BufferSize,\r
319 IN VOID *Buffer\r
320 )\r
321{\r
322 VIRTIO_FS_FILE *VirtioFsFile;\r
323 EFI_STATUS Status;\r
324 EFI_FILE_INFO *FileInfo;\r
325\r
326 VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);\r
327\r
328 //\r
329 // Validate if Buffer passes as EFI_FILE_INFO.\r
330 //\r
331 Status = ValidateInfoStructure (\r
332 BufferSize, // SizeByProtocolCaller\r
333 OFFSET_OF (EFI_FILE_INFO,\r
334 FileName) + sizeof (CHAR16), // MinimumStructSize\r
335 TRUE, // IsSizeByInfoPresent\r
336 Buffer\r
337 );\r
338 if (EFI_ERROR (Status)) {\r
339 return Status;\r
340 }\r
341 FileInfo = Buffer;\r
342\r
343 //\r
344 // Perform the rename/move request, if any.\r
345 //\r
346 Status = Rename (VirtioFsFile, FileInfo->FileName);\r
347 if (EFI_ERROR (Status)) {\r
348 return Status;\r
349 }\r
350 //\r
351 // Update any attributes requested.\r
352 //\r
353 Status = EFI_UNSUPPORTED;\r
354 //\r
355 // The UEFI spec does not speak about partial failure in\r
356 // EFI_FILE_PROTOCOL.SetInfo(); we won't try to roll back the rename (if\r
357 // there was one) in case the attribute updates fail.\r
358 //\r
359 return Status;\r
360}\r
361\r
c9f473df
LE
362/**\r
363 Process an EFI_FILE_SYSTEM_INFO setting request.\r
364**/\r
365STATIC\r
366EFI_STATUS\r
367SetFileSystemInfo (\r
368 IN EFI_FILE_PROTOCOL *This,\r
369 IN UINTN BufferSize,\r
370 IN VOID *Buffer\r
371 )\r
372{\r
373 VIRTIO_FS_FILE *VirtioFsFile;\r
374 VIRTIO_FS *VirtioFs;\r
375 EFI_STATUS Status;\r
376 EFI_FILE_SYSTEM_INFO *FileSystemInfo;\r
377\r
378 VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);\r
379 VirtioFs = VirtioFsFile->OwnerFs;\r
380\r
381 //\r
382 // Validate if Buffer passes as EFI_FILE_SYSTEM_INFO.\r
383 //\r
384 Status = ValidateInfoStructure (\r
385 BufferSize, // SizeByProtocolCaller\r
386 OFFSET_OF (EFI_FILE_SYSTEM_INFO,\r
387 VolumeLabel) + sizeof (CHAR16), // MinimumStructSize\r
388 TRUE, // IsSizeByInfoPresent\r
389 Buffer\r
390 );\r
391 if (EFI_ERROR (Status)) {\r
392 return Status;\r
393 }\r
394 FileSystemInfo = Buffer;\r
395\r
396 //\r
397 // EFI_FILE_SYSTEM_INFO fields other than VolumeLabel cannot be changed, per\r
398 // spec.\r
399 //\r
400 // If the label is being changed to its current value, report success;\r
401 // otherwise, reject the request, as the Virtio Filesystem device does not\r
402 // support changing the label.\r
403 //\r
404 if (StrCmp (FileSystemInfo->VolumeLabel, VirtioFs->Label) == 0) {\r
405 return EFI_SUCCESS;\r
406 }\r
407 return EFI_WRITE_PROTECTED;\r
408}\r
409\r
410/**\r
411 Process an EFI_FILE_SYSTEM_VOLUME_LABEL setting request.\r
412**/\r
413STATIC\r
414EFI_STATUS\r
415SetFileSystemVolumeLabelInfo (\r
416 IN EFI_FILE_PROTOCOL *This,\r
417 IN UINTN BufferSize,\r
418 IN VOID *Buffer\r
419 )\r
420{\r
421 VIRTIO_FS_FILE *VirtioFsFile;\r
422 VIRTIO_FS *VirtioFs;\r
423 EFI_STATUS Status;\r
424 EFI_FILE_SYSTEM_VOLUME_LABEL *FileSystemVolumeLabel;\r
425\r
426 VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);\r
427 VirtioFs = VirtioFsFile->OwnerFs;\r
428\r
429 //\r
430 // Validate if Buffer passes as EFI_FILE_SYSTEM_VOLUME_LABEL.\r
431 //\r
432 Status = ValidateInfoStructure (\r
433 BufferSize, // SizeByProtocolCaller\r
434 OFFSET_OF (EFI_FILE_SYSTEM_VOLUME_LABEL,\r
435 VolumeLabel) + sizeof (CHAR16), // MinimumStructSize\r
436 FALSE, // IsSizeByInfoPresent\r
437 Buffer\r
438 );\r
439 if (EFI_ERROR (Status)) {\r
440 return Status;\r
441 }\r
442 FileSystemVolumeLabel = Buffer;\r
443\r
444 //\r
445 // If the label is being changed to its current value, report success;\r
446 // otherwise, reject the request, as the Virtio Filesystem device does not\r
447 // support changing the label.\r
448 //\r
449 if (StrCmp (FileSystemVolumeLabel->VolumeLabel, VirtioFs->Label) == 0) {\r
450 return EFI_SUCCESS;\r
451 }\r
452 return EFI_WRITE_PROTECTED;\r
453}\r
454\r
334c13e1
LE
455EFI_STATUS\r
456EFIAPI\r
457VirtioFsSimpleFileSetInfo (\r
458 IN EFI_FILE_PROTOCOL *This,\r
459 IN EFI_GUID *InformationType,\r
460 IN UINTN BufferSize,\r
461 IN VOID *Buffer\r
462 )\r
463{\r
c9f473df 464 if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {\r
dd091185 465 return SetFileInfo (This, BufferSize, Buffer);\r
c9f473df
LE
466 }\r
467\r
468 if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {\r
469 return SetFileSystemInfo (This, BufferSize, Buffer);\r
470 }\r
471\r
472 if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {\r
473 return SetFileSystemVolumeLabelInfo (This, BufferSize, Buffer);\r
474 }\r
475\r
476 return EFI_UNSUPPORTED;\r
334c13e1 477}\r