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
OpenRootDirectory(IN OUT VIRTIO_FS * VirtioFs,OUT EFI_FILE_PROTOCOL ** NewHandle,IN BOOLEAN OpenForWriting)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
OpenExistentFileOrDirectory(IN OUT VIRTIO_FS * VirtioFs,IN UINT64 DirNodeId,IN CHAR8 * Name,IN BOOLEAN OpenForWriting,OUT UINT64 * NodeId,OUT UINT64 * FuseHandle,OUT BOOLEAN * NodeIsDirectory)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
CreateDirectory(IN OUT VIRTIO_FS * VirtioFs,IN UINT64 DirNodeId,IN CHAR8 * Name,OUT UINT64 * NodeId,OUT UINT64 * FuseHandle)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
CreateRegularFile(IN OUT VIRTIO_FS * VirtioFs,IN UINT64 DirNodeId,IN CHAR8 * Name,OUT UINT64 * NodeId,OUT UINT64 * FuseHandle)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
VirtioFsSimpleFileOpen(IN EFI_FILE_PROTOCOL * This,OUT EFI_FILE_PROTOCOL ** NewHandle,IN CHAR16 * FileName,IN UINT64 OpenMode,IN UINT64 Attributes)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->FilePosition           = 0;
483   NewVirtioFsFile->NodeId                 = NewNodeId;
484   NewVirtioFsFile->FuseHandle             = NewFuseHandle;
485   NewVirtioFsFile->FileInfoArray          = NULL;
486   NewVirtioFsFile->SingleFileInfoSize     = 0;
487   NewVirtioFsFile->NumFileInfo            = 0;
488   NewVirtioFsFile->NextFileInfo           = 0;
489 
490   //
491   // One more file is now open for the filesystem.
492   //
493   InsertTailList (&VirtioFs->OpenFiles, &NewVirtioFsFile->OpenFilesEntry);
494 
495   *NewHandle = &NewVirtioFsFile->SimpleFile;
496   return EFI_SUCCESS;
497 
498 FreeNewCanonicalPath:
499   FreePool (NewCanonicalPath);
500 
501 FreeNewVirtioFsFile:
502   FreePool (NewVirtioFsFile);
503 
504   return Status;
505 }
506