]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/VirtioFsDxe/SimpleFsOpen.c
2649c796ac970dd85413eec277605e70831767de
[mirror_edk2.git] / OvmfPkg / VirtioFsDxe / SimpleFsOpen.c
1 /** @file
2 EFI_FILE_PROTOCOL.Open() 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 <Library/BaseLib.h> // AsciiStrCmp()
10 #include <Library/MemoryAllocationLib.h> // AllocatePool()
11
12 #include "VirtioFsDxe.h"
13
14 /**
15 Open the root directory, possibly for writing.
16
17 @param[in,out] VirtioFs The Virtio Filesystem device whose root directory
18 should be opened.
19
20 @param[out] NewHandle The new EFI_FILE_PROTOCOL instance through which
21 the root directory can be accessed.
22
23 @param[in] OpenForWriting TRUE if the root directory should be opened for
24 read-write access. FALSE if the root directory
25 should be opened for read-only access. Opening the
26 root directory for read-write access is useful for
27 calling EFI_FILE_PROTOCOL.Flush() or
28 EFI_FILE_PROTOCOL.SetInfo() later, for syncing or
29 touching the root directory, respectively.
30
31 @retval EFI_SUCCESS The root directory has been opened successfully.
32
33 @retval EFI_ACCESS_DENIED OpenForWriting is TRUE, but the root directory is
34 marked as read-only.
35
36 @return Error codes propagated from underlying functions.
37 **/
38 STATIC
39 EFI_STATUS
40 OpenRootDirectory (
41 IN OUT VIRTIO_FS *VirtioFs,
42 OUT EFI_FILE_PROTOCOL **NewHandle,
43 IN BOOLEAN OpenForWriting
44 )
45 {
46 EFI_STATUS Status;
47 VIRTIO_FS_FILE *NewVirtioFsFile;
48
49 //
50 // VirtioFsOpenVolume() opens the root directory for read-only access. If the
51 // current request is to open the root directory for read-write access, so
52 // that EFI_FILE_PROTOCOL.Flush() or EFI_FILE_PROTOCOL.SetInfo()+timestamps
53 // can be used on the root directory later, then we have to check for write
54 // permission first.
55 //
56 if (OpenForWriting) {
57 VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr;
58 EFI_FILE_INFO FileInfo;
59
60 Status = VirtioFsFuseGetAttr (VirtioFs, VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID,
61 &FuseAttr);
62 if (EFI_ERROR (Status)) {
63 return Status;
64 }
65 Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo);
66 if (EFI_ERROR (Status)) {
67 return Status;
68 }
69 if ((FileInfo.Attribute & EFI_FILE_READ_ONLY) != 0) {
70 return EFI_ACCESS_DENIED;
71 }
72 }
73
74 Status = VirtioFsOpenVolume (&VirtioFs->SimpleFs, NewHandle);
75 if (EFI_ERROR (Status)) {
76 return Status;
77 }
78
79 NewVirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (*NewHandle);
80 NewVirtioFsFile->IsOpenForWriting = OpenForWriting;
81 return EFI_SUCCESS;
82 }
83
84 /**
85 Open an existent regular file or non-root directory.
86
87 @param[in,out] VirtioFs The Virtio Filesystem device on which the
88 regular file or directory should be opened.
89
90 @param[in] DirNodeId The inode number of the immediate parent
91 directory of the regular file or directory to
92 open.
93
94 @param[in] Name The single-component filename of the regular
95 file or directory to open, under the immediate
96 parent directory identified by DirNodeId.
97
98 @param[in] OpenForWriting TRUE if the regular file or directory should be
99 opened for read-write access. FALSE if the
100 regular file or directory should be opened for
101 read-only access. Opening a directory for
102 read-write access is useful for deleting,
103 renaming, syncing or touching the directory
104 later.
105
106 @param[out] NodeId The inode number of the regular file or
107 directory, returned by the Virtio Filesystem
108 device.
109
110 @param[out] FuseHandle The open handle to the regular file or
111 directory, returned by the Virtio Filesystem
112 device.
113
114 @param[out] NodeIsDirectory Set to TRUE on output if Name was found to refer
115 to a directory. Set to FALSE if Name was found
116 to refer to a regular file.
117
118 @retval EFI_SUCCESS The regular file or directory has been looked up
119 and opened successfully.
120
121 @retval EFI_ACCESS_DENIED OpenForWriting is TRUE, but the regular file or
122 directory is marked read-only.
123
124 @retval EFI_NOT_FOUND A directory entry called Name was not found in the
125 directory identified by DirNodeId. (EFI_NOT_FOUND
126 is not returned for any other condition.)
127
128 @return Errors propagated from underlying functions. If
129 the error code to propagate were EFI_NOT_FOUND, it
130 is remapped to EFI_DEVICE_ERROR.
131 **/
132 STATIC
133 EFI_STATUS
134 OpenExistentFileOrDirectory (
135 IN OUT VIRTIO_FS *VirtioFs,
136 IN UINT64 DirNodeId,
137 IN CHAR8 *Name,
138 IN BOOLEAN OpenForWriting,
139 OUT UINT64 *NodeId,
140 OUT UINT64 *FuseHandle,
141 OUT BOOLEAN *NodeIsDirectory
142 )
143 {
144 EFI_STATUS Status;
145 UINT64 ResolvedNodeId;
146 VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr;
147 EFI_FILE_INFO FileInfo;
148 BOOLEAN IsDirectory;
149 UINT64 NewFuseHandle;
150
151 Status = VirtioFsFuseLookup (VirtioFs, DirNodeId, Name, &ResolvedNodeId,
152 &FuseAttr);
153 if (EFI_ERROR (Status)) {
154 return Status;
155 }
156 Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo);
157 if (EFI_ERROR (Status)) {
158 goto ForgetResolvedNodeId;
159 }
160
161 if (OpenForWriting && (FileInfo.Attribute & EFI_FILE_READ_ONLY) != 0) {
162 Status = EFI_ACCESS_DENIED;
163 goto ForgetResolvedNodeId;
164 }
165
166 IsDirectory = (BOOLEAN)((FileInfo.Attribute & EFI_FILE_DIRECTORY) != 0);
167 if (IsDirectory) {
168 //
169 // If OpenForWriting is TRUE here, that's not passed to
170 // VirtioFsFuseOpenDir(); it does not affect the FUSE_OPENDIR request we
171 // send. OpenForWriting=TRUE will only permit attempts to delete, rename,
172 // flush (sync), and touch the directory.
173 //
174 Status = VirtioFsFuseOpenDir (VirtioFs, ResolvedNodeId, &NewFuseHandle);
175 } else {
176 Status = VirtioFsFuseOpen (VirtioFs, ResolvedNodeId, OpenForWriting,
177 &NewFuseHandle);
178 }
179 if (EFI_ERROR (Status)) {
180 goto ForgetResolvedNodeId;
181 }
182
183 *NodeId = ResolvedNodeId;
184 *FuseHandle = NewFuseHandle;
185 *NodeIsDirectory = IsDirectory;
186 return EFI_SUCCESS;
187
188 ForgetResolvedNodeId:
189 VirtioFsFuseForget (VirtioFs, ResolvedNodeId);
190 return (Status == EFI_NOT_FOUND) ? EFI_DEVICE_ERROR : Status;
191 }
192
193 /**
194 Create a directory.
195
196 @param[in,out] VirtioFs The Virtio Filesystem device on which the directory
197 should be created.
198
199 @param[in] DirNodeId The inode number of the immediate parent directory
200 of the directory to create.
201
202 @param[in] Name The single-component filename of the directory to
203 create, under the immediate parent directory
204 identified by DirNodeId.
205
206 @param[out] NodeId The inode number of the directory created, returned
207 by the Virtio Filesystem device.
208
209 @param[out] FuseHandle The open handle to the directory created, returned
210 by the Virtio Filesystem device.
211
212 @retval EFI_SUCCESS The directory has been created successfully.
213
214 @return Errors propagated from underlying functions.
215 **/
216 STATIC
217 EFI_STATUS
218 CreateDirectory (
219 IN OUT VIRTIO_FS *VirtioFs,
220 IN UINT64 DirNodeId,
221 IN CHAR8 *Name,
222 OUT UINT64 *NodeId,
223 OUT UINT64 *FuseHandle
224 )
225 {
226 EFI_STATUS Status;
227 UINT64 NewChildDirNodeId;
228 UINT64 NewFuseHandle;
229
230 Status = VirtioFsFuseMkDir (VirtioFs, DirNodeId, Name, &NewChildDirNodeId);
231 if (EFI_ERROR (Status)) {
232 return Status;
233 }
234
235 Status = VirtioFsFuseOpenDir (VirtioFs, NewChildDirNodeId, &NewFuseHandle);
236 if (EFI_ERROR (Status)) {
237 goto RemoveNewChildDir;
238 }
239
240 *NodeId = NewChildDirNodeId;
241 *FuseHandle = NewFuseHandle;
242 return EFI_SUCCESS;
243
244 RemoveNewChildDir:
245 VirtioFsFuseRemoveFileOrDir (VirtioFs, DirNodeId, Name, TRUE /* IsDir */);
246 VirtioFsFuseForget (VirtioFs, NewChildDirNodeId);
247 return Status;
248 }
249
250 /**
251 Create a regular file.
252
253 @param[in,out] VirtioFs The Virtio Filesystem device on which the regular
254 file should be created.
255
256 @param[in] DirNodeId The inode number of the immediate parent directory
257 of the regular file to create.
258
259 @param[in] Name The single-component filename of the regular file to
260 create, under the immediate parent directory
261 identified by DirNodeId.
262
263 @param[out] NodeId The inode number of the regular file created,
264 returned by the Virtio Filesystem device.
265
266 @param[out] FuseHandle The open handle to the regular file created,
267 returned by the Virtio Filesystem device.
268
269 @retval EFI_SUCCESS The regular file has been created successfully.
270
271 @return Errors propagated from underlying functions.
272 **/
273 STATIC
274 EFI_STATUS
275 CreateRegularFile (
276 IN OUT VIRTIO_FS *VirtioFs,
277 IN UINT64 DirNodeId,
278 IN CHAR8 *Name,
279 OUT UINT64 *NodeId,
280 OUT UINT64 *FuseHandle
281 )
282 {
283 return VirtioFsFuseOpenOrCreate (VirtioFs, DirNodeId, Name, NodeId,
284 FuseHandle);
285 }
286
287 EFI_STATUS
288 EFIAPI
289 VirtioFsSimpleFileOpen (
290 IN EFI_FILE_PROTOCOL *This,
291 OUT EFI_FILE_PROTOCOL **NewHandle,
292 IN CHAR16 *FileName,
293 IN UINT64 OpenMode,
294 IN UINT64 Attributes
295 )
296 {
297 VIRTIO_FS_FILE *VirtioFsFile;
298 VIRTIO_FS *VirtioFs;
299 BOOLEAN OpenForWriting;
300 BOOLEAN PermitCreation;
301 BOOLEAN CreateDirectoryIfCreating;
302 VIRTIO_FS_FILE *NewVirtioFsFile;
303 EFI_STATUS Status;
304 CHAR8 *NewCanonicalPath;
305 BOOLEAN RootEscape;
306 UINT64 DirNodeId;
307 CHAR8 *LastComponent;
308 UINT64 NewNodeId;
309 UINT64 NewFuseHandle;
310 BOOLEAN NewNodeIsDirectory;
311
312 VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
313 VirtioFs = VirtioFsFile->OwnerFs;
314
315 //
316 // Validate OpenMode.
317 //
318 switch (OpenMode) {
319 case EFI_FILE_MODE_READ:
320 OpenForWriting = FALSE;
321 PermitCreation = FALSE;
322 break;
323 case EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE:
324 OpenForWriting = TRUE;
325 PermitCreation = FALSE;
326 break;
327 case EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE:
328 OpenForWriting = TRUE;
329 PermitCreation = TRUE;
330 break;
331 default:
332 return EFI_INVALID_PARAMETER;
333 }
334
335 //
336 // Validate the Attributes requested for the case when the file ends up being
337 // created, provided creation is permitted.
338 //
339 if (PermitCreation) {
340 if ((Attributes & ~EFI_FILE_VALID_ATTR) != 0) {
341 //
342 // Unknown attribute requested.
343 //
344 return EFI_INVALID_PARAMETER;
345 }
346
347 ASSERT (OpenForWriting);
348 if ((Attributes & EFI_FILE_READ_ONLY) != 0) {
349 DEBUG ((
350 DEBUG_ERROR,
351 ("%a: Label=\"%s\" CanonicalPathname=\"%a\" FileName=\"%s\" "
352 "OpenMode=0x%Lx Attributes=0x%Lx: nonsensical request to possibly "
353 "create a file marked read-only, for read-write access\n"),
354 __FUNCTION__,
355 VirtioFs->Label,
356 VirtioFsFile->CanonicalPathname,
357 FileName,
358 OpenMode,
359 Attributes
360 ));
361 return EFI_INVALID_PARAMETER;
362 }
363 CreateDirectoryIfCreating = (BOOLEAN)((Attributes &
364 EFI_FILE_DIRECTORY) != 0);
365 }
366
367 //
368 // Referring to a file relative to a regular file makes no sense (or at least
369 // it cannot be implemented consistently with how a file is referred to
370 // relative to a directory).
371 //
372 if (!VirtioFsFile->IsDirectory) {
373 DEBUG ((
374 DEBUG_ERROR,
375 ("%a: Label=\"%s\" CanonicalPathname=\"%a\" FileName=\"%s\": "
376 "nonsensical request to open a file or directory relative to a regular "
377 "file\n"),
378 __FUNCTION__,
379 VirtioFs->Label,
380 VirtioFsFile->CanonicalPathname,
381 FileName
382 ));
383 return EFI_INVALID_PARAMETER;
384 }
385
386 //
387 // Allocate the new VIRTIO_FS_FILE object.
388 //
389 NewVirtioFsFile = AllocatePool (sizeof *NewVirtioFsFile);
390 if (NewVirtioFsFile == NULL) {
391 return EFI_OUT_OF_RESOURCES;
392 }
393
394 //
395 // Create the canonical pathname at which the desired file is expected to
396 // exist.
397 //
398 Status = VirtioFsAppendPath (VirtioFsFile->CanonicalPathname, FileName,
399 &NewCanonicalPath, &RootEscape);
400 if (EFI_ERROR (Status)) {
401 goto FreeNewVirtioFsFile;
402 }
403 if (RootEscape) {
404 Status = EFI_ACCESS_DENIED;
405 goto FreeNewCanonicalPath;
406 }
407
408 //
409 // If the desired file is the root directory, just open the volume one more
410 // time, without looking up anything.
411 //
412 if (AsciiStrCmp (NewCanonicalPath, "/") == 0) {
413 FreePool (NewCanonicalPath);
414 FreePool (NewVirtioFsFile);
415 return OpenRootDirectory (VirtioFs, NewHandle, OpenForWriting);
416 }
417
418 //
419 // Split the new canonical pathname into most specific parent directory
420 // (given by DirNodeId) and last pathname component (i.e., immediate child
421 // within that parent directory).
422 //
423 Status = VirtioFsLookupMostSpecificParentDir (VirtioFs, NewCanonicalPath,
424 &DirNodeId, &LastComponent);
425 if (EFI_ERROR (Status)) {
426 goto FreeNewCanonicalPath;
427 }
428
429 //
430 // Try to open LastComponent directly under DirNodeId, as an existent regular
431 // file or directory.
432 //
433 Status = OpenExistentFileOrDirectory (VirtioFs, DirNodeId, LastComponent,
434 OpenForWriting, &NewNodeId, &NewFuseHandle, &NewNodeIsDirectory);
435 //
436 // If LastComponent could not be found under DirNodeId, but the request
437 // allows us to create a new entry, attempt creating the requested regular
438 // file or directory.
439 //
440 if (Status == EFI_NOT_FOUND && PermitCreation) {
441 ASSERT (OpenForWriting);
442 if (CreateDirectoryIfCreating) {
443 Status = CreateDirectory (VirtioFs, DirNodeId, LastComponent, &NewNodeId,
444 &NewFuseHandle);
445 } else {
446 Status = CreateRegularFile (VirtioFs, DirNodeId, LastComponent,
447 &NewNodeId, &NewFuseHandle);
448 }
449 NewNodeIsDirectory = CreateDirectoryIfCreating;
450 }
451
452 //
453 // Regardless of the branch taken, we're done with DirNodeId.
454 //
455 if (DirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {
456 VirtioFsFuseForget (VirtioFs, DirNodeId);
457 }
458
459 if (EFI_ERROR (Status)) {
460 goto FreeNewCanonicalPath;
461 }
462
463 //
464 // Populate the new VIRTIO_FS_FILE object.
465 //
466 NewVirtioFsFile->Signature = VIRTIO_FS_FILE_SIG;
467 NewVirtioFsFile->SimpleFile.Revision = EFI_FILE_PROTOCOL_REVISION;
468 NewVirtioFsFile->SimpleFile.Open = VirtioFsSimpleFileOpen;
469 NewVirtioFsFile->SimpleFile.Close = VirtioFsSimpleFileClose;
470 NewVirtioFsFile->SimpleFile.Delete = VirtioFsSimpleFileDelete;
471 NewVirtioFsFile->SimpleFile.Read = VirtioFsSimpleFileRead;
472 NewVirtioFsFile->SimpleFile.Write = VirtioFsSimpleFileWrite;
473 NewVirtioFsFile->SimpleFile.GetPosition = VirtioFsSimpleFileGetPosition;
474 NewVirtioFsFile->SimpleFile.SetPosition = VirtioFsSimpleFileSetPosition;
475 NewVirtioFsFile->SimpleFile.GetInfo = VirtioFsSimpleFileGetInfo;
476 NewVirtioFsFile->SimpleFile.SetInfo = VirtioFsSimpleFileSetInfo;
477 NewVirtioFsFile->SimpleFile.Flush = VirtioFsSimpleFileFlush;
478 NewVirtioFsFile->IsDirectory = NewNodeIsDirectory;
479 NewVirtioFsFile->IsOpenForWriting = OpenForWriting;
480 NewVirtioFsFile->OwnerFs = VirtioFs;
481 NewVirtioFsFile->CanonicalPathname = NewCanonicalPath;
482 NewVirtioFsFile->NodeId = NewNodeId;
483 NewVirtioFsFile->FuseHandle = NewFuseHandle;
484
485 //
486 // One more file is now open for the filesystem.
487 //
488 InsertTailList (&VirtioFs->OpenFiles, &NewVirtioFsFile->OpenFilesEntry);
489
490 *NewHandle = &NewVirtioFsFile->SimpleFile;
491 return EFI_SUCCESS;
492
493 FreeNewCanonicalPath:
494 FreePool (NewCanonicalPath);
495
496 FreeNewVirtioFsFile:
497 FreePool (NewVirtioFsFile);
498
499 return Status;
500 }