1 | /** @file
|
---|
2 | EFI_FILE_PROTOCOL.Read() 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/BaseMemoryLib.h> // CopyMem()
|
---|
10 | #include <Library/MemoryAllocationLib.h> // AllocatePool()
|
---|
11 |
|
---|
12 | #include "VirtioFsDxe.h"
|
---|
13 |
|
---|
14 | /**
|
---|
15 | Populate a caller-allocated EFI_FILE_INFO object from
|
---|
16 | VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE.
|
---|
17 |
|
---|
18 | @param[in] Dirent The entry read from the directory stream. The
|
---|
19 | caller is responsible for ensuring that
|
---|
20 | Dirent->Namelen describe valid storage.
|
---|
21 |
|
---|
22 | @param[in] SingleFileInfoSize The allocated size of FileInfo.
|
---|
23 |
|
---|
24 | @param[out] FileInfo The EFI_FILE_INFO object to populate. On
|
---|
25 | success, all fields in FileInfo will be
|
---|
26 | updated, setting FileInfo->Size to the
|
---|
27 | actually used size (which will not exceed
|
---|
28 | SingleFileInfoSize).
|
---|
29 |
|
---|
30 | @retval EFI_SUCCESS FileInfo has been filled in.
|
---|
31 |
|
---|
32 | @return Error codes propagated from
|
---|
33 | VirtioFsFuseDirentPlusToEfiFileInfo() and
|
---|
34 | VirtioFsFuseAttrToEfiFileInfo(). The contents of
|
---|
35 | FileInfo are indeterminate.
|
---|
36 | **/
|
---|
37 | STATIC
|
---|
38 | EFI_STATUS
|
---|
39 | PopulateFileInfo (
|
---|
40 | IN VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *Dirent,
|
---|
41 | IN UINTN SingleFileInfoSize,
|
---|
42 | OUT EFI_FILE_INFO *FileInfo
|
---|
43 | )
|
---|
44 | {
|
---|
45 | EFI_STATUS Status;
|
---|
46 |
|
---|
47 | //
|
---|
48 | // Convert the name, set the actual size.
|
---|
49 | //
|
---|
50 | FileInfo->Size = SingleFileInfoSize;
|
---|
51 | Status = VirtioFsFuseDirentPlusToEfiFileInfo (Dirent, FileInfo);
|
---|
52 | if (EFI_ERROR (Status)) {
|
---|
53 | return Status;
|
---|
54 | }
|
---|
55 |
|
---|
56 | //
|
---|
57 | // Populate the scalar fields.
|
---|
58 | //
|
---|
59 | Status = VirtioFsFuseAttrToEfiFileInfo (&Dirent->AttrResp, FileInfo);
|
---|
60 | return Status;
|
---|
61 | }
|
---|
62 |
|
---|
63 | /**
|
---|
64 | Refill the EFI_FILE_INFO cache from the directory stream.
|
---|
65 | **/
|
---|
66 | STATIC
|
---|
67 | EFI_STATUS
|
---|
68 | RefillFileInfoCache (
|
---|
69 | IN OUT VIRTIO_FS_FILE *VirtioFsFile
|
---|
70 | )
|
---|
71 | {
|
---|
72 | VIRTIO_FS *VirtioFs;
|
---|
73 | EFI_STATUS Status;
|
---|
74 | VIRTIO_FS_FUSE_STATFS_RESPONSE FilesysAttr;
|
---|
75 | UINT32 DirentBufSize;
|
---|
76 | UINT8 *DirentBuf;
|
---|
77 | UINTN SingleFileInfoSize;
|
---|
78 | UINT8 *FileInfoArray;
|
---|
79 | UINT64 DirStreamCookie;
|
---|
80 | UINT64 CacheEndsAtCookie;
|
---|
81 | UINTN NumFileInfo;
|
---|
82 |
|
---|
83 | //
|
---|
84 | // Allocate a DirentBuf that can receive at least
|
---|
85 | // VIRTIO_FS_FILE_MAX_FILE_INFO directory entries, based on the maximum
|
---|
86 | // filename length supported by the filesystem. Note that the multiplication
|
---|
87 | // is safe from overflow due to the VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE()
|
---|
88 | // check.
|
---|
89 | //
|
---|
90 | VirtioFs = VirtioFsFile->OwnerFs;
|
---|
91 | Status = VirtioFsFuseStatFs (VirtioFs, VirtioFsFile->NodeId, &FilesysAttr);
|
---|
92 | if (EFI_ERROR (Status)) {
|
---|
93 | return Status;
|
---|
94 | }
|
---|
95 |
|
---|
96 | DirentBufSize = (UINT32)VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (
|
---|
97 | FilesysAttr.Namelen
|
---|
98 | );
|
---|
99 | if (DirentBufSize == 0) {
|
---|
100 | return EFI_UNSUPPORTED;
|
---|
101 | }
|
---|
102 |
|
---|
103 | DirentBufSize *= VIRTIO_FS_FILE_MAX_FILE_INFO;
|
---|
104 | DirentBuf = AllocatePool (DirentBufSize);
|
---|
105 | if (DirentBuf == NULL) {
|
---|
106 | return EFI_OUT_OF_RESOURCES;
|
---|
107 | }
|
---|
108 |
|
---|
109 | //
|
---|
110 | // Allocate the EFI_FILE_INFO cache. A single EFI_FILE_INFO element is sized
|
---|
111 | // accordingly to the maximum filename length supported by the filesystem.
|
---|
112 | //
|
---|
113 | // Note that the calculation below cannot overflow, due to the filename limit
|
---|
114 | // imposed by the VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE() check above. The
|
---|
115 | // calculation takes the L'\0' character that we'll need to append into
|
---|
116 | // account.
|
---|
117 | //
|
---|
118 | SingleFileInfoSize = (OFFSET_OF (EFI_FILE_INFO, FileName) +
|
---|
119 | ((UINTN)FilesysAttr.Namelen + 1) * sizeof (CHAR16));
|
---|
120 | FileInfoArray = AllocatePool (
|
---|
121 | VIRTIO_FS_FILE_MAX_FILE_INFO * SingleFileInfoSize
|
---|
122 | );
|
---|
123 | if (FileInfoArray == NULL) {
|
---|
124 | Status = EFI_OUT_OF_RESOURCES;
|
---|
125 | goto FreeDirentBuf;
|
---|
126 | }
|
---|
127 |
|
---|
128 | //
|
---|
129 | // Pick up reading the directory stream where the previous cache ended.
|
---|
130 | //
|
---|
131 | DirStreamCookie = VirtioFsFile->FilePosition;
|
---|
132 | CacheEndsAtCookie = VirtioFsFile->FilePosition;
|
---|
133 | NumFileInfo = 0;
|
---|
134 | do {
|
---|
135 | UINT32 Remaining;
|
---|
136 | UINT32 Consumed;
|
---|
137 |
|
---|
138 | //
|
---|
139 | // Fetch a chunk of the directory stream. The chunk may hold more entries
|
---|
140 | // than what we can fit in the cache. The chunk may also not entirely fill
|
---|
141 | // the cache, especially after filtering out entries that cannot be
|
---|
142 | // supported under UEFI (sockets, FIFOs, filenames with backslashes, etc).
|
---|
143 | //
|
---|
144 | Remaining = DirentBufSize;
|
---|
145 | Status = VirtioFsFuseReadFileOrDir (
|
---|
146 | VirtioFs,
|
---|
147 | VirtioFsFile->NodeId,
|
---|
148 | VirtioFsFile->FuseHandle,
|
---|
149 | TRUE, // IsDir
|
---|
150 | DirStreamCookie, // Offset
|
---|
151 | &Remaining, // Size
|
---|
152 | DirentBuf // Data
|
---|
153 | );
|
---|
154 | if (EFI_ERROR (Status)) {
|
---|
155 | goto FreeFileInfoArray;
|
---|
156 | }
|
---|
157 |
|
---|
158 | if (Remaining == 0) {
|
---|
159 | //
|
---|
160 | // The directory stream ends.
|
---|
161 | //
|
---|
162 | break;
|
---|
163 | }
|
---|
164 |
|
---|
165 | //
|
---|
166 | // Iterate over all records in DirentBuf. Primarily, forget them all.
|
---|
167 | // Secondarily, if a record proves transformable to EFI_FILE_INFO, add it
|
---|
168 | // to the EFI_FILE_INFO cache (unless the cache is full).
|
---|
169 | //
|
---|
170 | Consumed = 0;
|
---|
171 | while (Remaining >= sizeof (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE)) {
|
---|
172 | VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *Dirent;
|
---|
173 | UINT32 DirentSize;
|
---|
174 |
|
---|
175 | Dirent = (VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE *)(DirentBuf + Consumed);
|
---|
176 | DirentSize = (UINT32)VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE (
|
---|
177 | Dirent->Namelen
|
---|
178 | );
|
---|
179 | if (DirentSize == 0) {
|
---|
180 | //
|
---|
181 | // This means one of two things: (a) Dirent->Namelen is zero, or (b)
|
---|
182 | // (b) Dirent->Namelen is unsupportably large. (a) is just invalid for
|
---|
183 | // the Virtio Filesystem device to send, while (b) shouldn't happen
|
---|
184 | // because "FilesysAttr.Namelen" -- the maximum filename length
|
---|
185 | // supported by the filesystem -- proved acceptable above.
|
---|
186 | //
|
---|
187 | Status = EFI_PROTOCOL_ERROR;
|
---|
188 | goto FreeFileInfoArray;
|
---|
189 | }
|
---|
190 |
|
---|
191 | if (DirentSize > Remaining) {
|
---|
192 | //
|
---|
193 | // Dirent->Namelen suggests that the filename byte array (plus any
|
---|
194 | // padding) are truncated. This should never happen; the Virtio
|
---|
195 | // Filesystem device is supposed to send complete entries only.
|
---|
196 | //
|
---|
197 | Status = EFI_PROTOCOL_ERROR;
|
---|
198 | goto FreeFileInfoArray;
|
---|
199 | }
|
---|
200 |
|
---|
201 | if (Dirent->Namelen > FilesysAttr.Namelen) {
|
---|
202 | //
|
---|
203 | // This is possible without tripping the truncation check above, due to
|
---|
204 | // how entries are padded. The condition means that Dirent->Namelen is
|
---|
205 | // reportedly larger than the filesystem limit, without spilling into
|
---|
206 | // the next alignment bucket. Should never happen.
|
---|
207 | //
|
---|
208 | Status = EFI_PROTOCOL_ERROR;
|
---|
209 | goto FreeFileInfoArray;
|
---|
210 | }
|
---|
211 |
|
---|
212 | //
|
---|
213 | // If we haven't filled the EFI_FILE_INFO cache yet, attempt transforming
|
---|
214 | // Dirent to EFI_FILE_INFO.
|
---|
215 | //
|
---|
216 | if (NumFileInfo < VIRTIO_FS_FILE_MAX_FILE_INFO) {
|
---|
217 | EFI_FILE_INFO *FileInfo;
|
---|
218 |
|
---|
219 | FileInfo = (EFI_FILE_INFO *)(FileInfoArray +
|
---|
220 | (NumFileInfo * SingleFileInfoSize));
|
---|
221 | Status = PopulateFileInfo (Dirent, SingleFileInfoSize, FileInfo);
|
---|
222 | if (!EFI_ERROR (Status)) {
|
---|
223 | //
|
---|
224 | // Dirent has been transformed and cached successfully.
|
---|
225 | //
|
---|
226 | NumFileInfo++;
|
---|
227 | //
|
---|
228 | // The next time we refill the cache, restart reading the directory
|
---|
229 | // stream right after the entry that we've just transformed and
|
---|
230 | // cached.
|
---|
231 | //
|
---|
232 | CacheEndsAtCookie = Dirent->CookieForNextEntry;
|
---|
233 | }
|
---|
234 |
|
---|
235 | //
|
---|
236 | // If Dirent wasn't transformable to an EFI_FILE_INFO, we'll just skip
|
---|
237 | // it.
|
---|
238 | //
|
---|
239 | }
|
---|
240 |
|
---|
241 | //
|
---|
242 | // Make the Virtio Filesystem device forget the NodeId in this directory
|
---|
243 | // entry, as we'll need it no more. (The "." and ".." entries need no
|
---|
244 | // FUSE_FORGET requests, when returned by FUSE_READDIRPLUS -- and so the
|
---|
245 | // Virtio Filesystem device reports their NodeId fields as zero.)
|
---|
246 | //
|
---|
247 | if (Dirent->NodeResp.NodeId != 0) {
|
---|
248 | VirtioFsFuseForget (VirtioFs, Dirent->NodeResp.NodeId);
|
---|
249 | }
|
---|
250 |
|
---|
251 | //
|
---|
252 | // Advance to the next entry in DirentBuf.
|
---|
253 | //
|
---|
254 | DirStreamCookie = Dirent->CookieForNextEntry;
|
---|
255 | Consumed += DirentSize;
|
---|
256 | Remaining -= DirentSize;
|
---|
257 | }
|
---|
258 |
|
---|
259 | if (Remaining > 0) {
|
---|
260 | //
|
---|
261 | // This suggests that a VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE header was
|
---|
262 | // truncated. This should never happen; the Virtio Filesystem device is
|
---|
263 | // supposed to send complete entries only.
|
---|
264 | //
|
---|
265 | Status = EFI_PROTOCOL_ERROR;
|
---|
266 | goto FreeFileInfoArray;
|
---|
267 | }
|
---|
268 |
|
---|
269 | //
|
---|
270 | // Fetch another DirentBuf from the directory stream, unless we've filled
|
---|
271 | // the EFI_FILE_INFO cache.
|
---|
272 | //
|
---|
273 | } while (NumFileInfo < VIRTIO_FS_FILE_MAX_FILE_INFO);
|
---|
274 |
|
---|
275 | //
|
---|
276 | // Commit the results. (Note that the result may be an empty cache.)
|
---|
277 | //
|
---|
278 | if (VirtioFsFile->FileInfoArray != NULL) {
|
---|
279 | FreePool (VirtioFsFile->FileInfoArray);
|
---|
280 | }
|
---|
281 |
|
---|
282 | VirtioFsFile->FileInfoArray = FileInfoArray;
|
---|
283 | VirtioFsFile->SingleFileInfoSize = SingleFileInfoSize;
|
---|
284 | VirtioFsFile->NumFileInfo = NumFileInfo;
|
---|
285 | VirtioFsFile->NextFileInfo = 0;
|
---|
286 | VirtioFsFile->FilePosition = CacheEndsAtCookie;
|
---|
287 |
|
---|
288 | FreePool (DirentBuf);
|
---|
289 | return EFI_SUCCESS;
|
---|
290 |
|
---|
291 | FreeFileInfoArray:
|
---|
292 | FreePool (FileInfoArray);
|
---|
293 |
|
---|
294 | FreeDirentBuf:
|
---|
295 | FreePool (DirentBuf);
|
---|
296 |
|
---|
297 | return Status;
|
---|
298 | }
|
---|
299 |
|
---|
300 | /**
|
---|
301 | Read an entry from the EFI_FILE_INFO cache.
|
---|
302 | **/
|
---|
303 | STATIC
|
---|
304 | EFI_STATUS
|
---|
305 | ReadFileInfoCache (
|
---|
306 | IN OUT VIRTIO_FS_FILE *VirtioFsFile,
|
---|
307 | IN OUT UINTN *BufferSize,
|
---|
308 | OUT VOID *Buffer
|
---|
309 | )
|
---|
310 | {
|
---|
311 | EFI_FILE_INFO *FileInfo;
|
---|
312 | UINTN CallerAllocated;
|
---|
313 |
|
---|
314 | //
|
---|
315 | // Refill the cache if needed. If the refill doesn't produce any new
|
---|
316 | // EFI_FILE_INFO, report End of Directory, by setting (*BufferSize) to 0.
|
---|
317 | //
|
---|
318 | if (VirtioFsFile->NextFileInfo == VirtioFsFile->NumFileInfo) {
|
---|
319 | EFI_STATUS Status;
|
---|
320 |
|
---|
321 | Status = RefillFileInfoCache (VirtioFsFile);
|
---|
322 | if (EFI_ERROR (Status)) {
|
---|
323 | return (Status == EFI_BUFFER_TOO_SMALL) ? EFI_DEVICE_ERROR : Status;
|
---|
324 | }
|
---|
325 |
|
---|
326 | if (VirtioFsFile->NumFileInfo == 0) {
|
---|
327 | *BufferSize = 0;
|
---|
328 | return EFI_SUCCESS;
|
---|
329 | }
|
---|
330 | }
|
---|
331 |
|
---|
332 | FileInfo = (EFI_FILE_INFO *)(VirtioFsFile->FileInfoArray +
|
---|
333 | (VirtioFsFile->NextFileInfo *
|
---|
334 | VirtioFsFile->SingleFileInfoSize));
|
---|
335 |
|
---|
336 | //
|
---|
337 | // Check if the caller is ready to accept FileInfo. If not, we'll just
|
---|
338 | // present the required size for now.
|
---|
339 | //
|
---|
340 | // (The (UINTN) cast below is safe because FileInfo->Size has been reduced
|
---|
341 | // from VirtioFsFile->SingleFileInfoSize, in
|
---|
342 | //
|
---|
343 | // RefillFileInfoCache()
|
---|
344 | // PopulateFileInfo()
|
---|
345 | // VirtioFsFuseDirentPlusToEfiFileInfo()
|
---|
346 | //
|
---|
347 | // and VirtioFsFile->SingleFileInfoSize was computed from
|
---|
348 | // FilesysAttr.Namelen, which had been accepted by
|
---|
349 | // VIRTIO_FS_FUSE_DIRENTPLUS_RESPONSE_SIZE().)
|
---|
350 | //
|
---|
351 | CallerAllocated = *BufferSize;
|
---|
352 | *BufferSize = (UINTN)FileInfo->Size;
|
---|
353 | if (CallerAllocated < *BufferSize) {
|
---|
354 | return EFI_BUFFER_TOO_SMALL;
|
---|
355 | }
|
---|
356 |
|
---|
357 | //
|
---|
358 | // Output FileInfo, and remove it from the cache.
|
---|
359 | //
|
---|
360 | CopyMem (Buffer, FileInfo, *BufferSize);
|
---|
361 | VirtioFsFile->NextFileInfo++;
|
---|
362 | return EFI_SUCCESS;
|
---|
363 | }
|
---|
364 |
|
---|
365 | /**
|
---|
366 | Read from a regular file.
|
---|
367 | **/
|
---|
368 | STATIC
|
---|
369 | EFI_STATUS
|
---|
370 | ReadRegularFile (
|
---|
371 | IN OUT VIRTIO_FS_FILE *VirtioFsFile,
|
---|
372 | IN OUT UINTN *BufferSize,
|
---|
373 | OUT VOID *Buffer
|
---|
374 | )
|
---|
375 | {
|
---|
376 | VIRTIO_FS *VirtioFs;
|
---|
377 | EFI_STATUS Status;
|
---|
378 | VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr;
|
---|
379 | UINTN Transferred;
|
---|
380 | UINTN Left;
|
---|
381 |
|
---|
382 | VirtioFs = VirtioFsFile->OwnerFs;
|
---|
383 | //
|
---|
384 | // The UEFI spec forbids reads that start beyond the end of the file.
|
---|
385 | //
|
---|
386 | Status = VirtioFsFuseGetAttr (VirtioFs, VirtioFsFile->NodeId, &FuseAttr);
|
---|
387 | if (EFI_ERROR (Status) || (VirtioFsFile->FilePosition > FuseAttr.Size)) {
|
---|
388 | return EFI_DEVICE_ERROR;
|
---|
389 | }
|
---|
390 |
|
---|
391 | Status = EFI_SUCCESS;
|
---|
392 | Transferred = 0;
|
---|
393 | Left = *BufferSize;
|
---|
394 | while (Left > 0) {
|
---|
395 | UINT32 ReadSize;
|
---|
396 |
|
---|
397 | //
|
---|
398 | // FUSE_READ cannot express a >=4GB buffer size.
|
---|
399 | //
|
---|
400 | ReadSize = (UINT32)MIN ((UINTN)MAX_UINT32, Left);
|
---|
401 | Status = VirtioFsFuseReadFileOrDir (
|
---|
402 | VirtioFs,
|
---|
403 | VirtioFsFile->NodeId,
|
---|
404 | VirtioFsFile->FuseHandle,
|
---|
405 | FALSE, // IsDir
|
---|
406 | VirtioFsFile->FilePosition + Transferred,
|
---|
407 | &ReadSize,
|
---|
408 | (UINT8 *)Buffer + Transferred
|
---|
409 | );
|
---|
410 | if (EFI_ERROR (Status) || (ReadSize == 0)) {
|
---|
411 | break;
|
---|
412 | }
|
---|
413 |
|
---|
414 | Transferred += ReadSize;
|
---|
415 | Left -= ReadSize;
|
---|
416 | }
|
---|
417 |
|
---|
418 | *BufferSize = Transferred;
|
---|
419 | VirtioFsFile->FilePosition += Transferred;
|
---|
420 | //
|
---|
421 | // If we managed to read some data, return success. If zero bytes were
|
---|
422 | // transferred due to zero-sized buffer on input or due to EOF on first read,
|
---|
423 | // return SUCCESS. Otherwise, return the error due to which zero bytes were
|
---|
424 | // transferred.
|
---|
425 | //
|
---|
426 | return (Transferred > 0) ? EFI_SUCCESS : Status;
|
---|
427 | }
|
---|
428 |
|
---|
429 | EFI_STATUS
|
---|
430 | EFIAPI
|
---|
431 | VirtioFsSimpleFileRead (
|
---|
432 | IN EFI_FILE_PROTOCOL *This,
|
---|
433 | IN OUT UINTN *BufferSize,
|
---|
434 | OUT VOID *Buffer
|
---|
435 | )
|
---|
436 | {
|
---|
437 | VIRTIO_FS_FILE *VirtioFsFile;
|
---|
438 | EFI_STATUS Status;
|
---|
439 |
|
---|
440 | VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
|
---|
441 |
|
---|
442 | if (VirtioFsFile->IsDirectory) {
|
---|
443 | Status = ReadFileInfoCache (VirtioFsFile, BufferSize, Buffer);
|
---|
444 | } else {
|
---|
445 | Status = ReadRegularFile (VirtioFsFile, BufferSize, Buffer);
|
---|
446 | }
|
---|
447 |
|
---|
448 | return Status;
|
---|
449 | }
|
---|