summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/VirtioFsDxe/SimpleFsSetInfo.c
blob: e7cc3d5dc3990fb0e182e631ba251655d5f1486a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
/** @file
  EFI_FILE_PROTOCOL.SetInfo() member function for the Virtio Filesystem driver.

  Copyright (C) 2020, Red Hat, Inc.

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Guid/FileSystemInfo.h>            // gEfiFileSystemInfoGuid
#include <Guid/FileSystemVolumeLabelInfo.h> // gEfiFileSystemVolumeLabelInfo...
#include <Library/BaseLib.h>                // StrCmp()
#include <Library/BaseMemoryLib.h>          // CompareGuid()
#include <Library/MemoryAllocationLib.h>    // FreePool()

#include "VirtioFsDxe.h"

/**
  Validate a buffer that the EFI_FILE_PROTOCOL.SetInfo() caller passes in for a
  particular InformationType GUID.

  The structure to be validated is supposed to end with a variable-length,
  NUL-terminated CHAR16 Name string.

  @param[in] SizeByProtocolCaller  The BufferSize parameter as provided by the
                                   EFI_FILE_PROTOCOL.SetInfo() caller.

  @param[in] MinimumStructSize     The minimum structure size that is required
                                   for the given InformationType GUID,
                                   including a single CHAR16 element from the
                                   trailing Name field.

  @param[in] IsSizeByInfoPresent   TRUE if and only if the expected structure
                                   starts with a UINT64 Size field that reports
                                   the actual structure size.

  @param[in] Buffer                The Buffer parameter as provided by the
                                   EFI_FILE_PROTOCOL.SetInfo() caller.

  @retval EFI_SUCCESS            Validation successful, Buffer is well-formed.

  @retval EFI_BAD_BUFFER_SIZE    The EFI_FILE_PROTOCOL.SetInfo()
                                 caller provided a BufferSize that is smaller
                                 than the minimum structure size required for
                                 the given InformationType GUID.

  @retval EFI_INVALID_PARAMETER  IsSizeByInfoPresent is TRUE, and the leading
                                 UINT64 Size field does not match the
                                 EFI_FILE_PROTOCOL.SetInfo() caller-provided
                                 BufferSize.

  @retval EFI_INVALID_PARAMETER  The trailing Name field does not consist of a
                                 whole multiple of CHAR16 elements.

  @retval EFI_INVALID_PARAMETER  The trailing Name field is not NUL-terminated.
**/
STATIC
EFI_STATUS
ValidateInfoStructure (
  IN UINTN   SizeByProtocolCaller,
  IN UINTN   MinimumStructSize,
  IN BOOLEAN IsSizeByInfoPresent,
  IN VOID    *Buffer
  )
{
  UINTN  NameFieldByteOffset;
  UINTN  NameFieldBytes;
  UINTN  NameFieldChar16s;
  CHAR16 *NameField;

  //
  // Make sure the internal function asking for validation passes in sane
  // values.
  //
  ASSERT (MinimumStructSize >= sizeof (CHAR16));
  NameFieldByteOffset = MinimumStructSize - sizeof (CHAR16);

  if (IsSizeByInfoPresent) {
    ASSERT (MinimumStructSize >= sizeof (UINT64) + sizeof (CHAR16));
    ASSERT (NameFieldByteOffset >= sizeof (UINT64));
  }

  //
  // Check whether the protocol caller provided enough bytes for the minimum
  // size of this info structure.
  //
  if (SizeByProtocolCaller < MinimumStructSize) {
    return EFI_BAD_BUFFER_SIZE;
  }

  //
  // If the info structure starts with a UINT64 Size field, check if that
  // agrees with the protocol caller-provided size.
  //
  if (IsSizeByInfoPresent) {
    UINT64 *SizeByInfo;

    SizeByInfo = Buffer;
    if (*SizeByInfo != SizeByProtocolCaller) {
      return EFI_INVALID_PARAMETER;
    }
  }

  //
  // The CHAR16 Name field at the end of the structure must have an even number
  // of bytes.
  //
  // The subtraction below cannot underflow, and yields at least
  // sizeof(CHAR16).
  //
  ASSERT (SizeByProtocolCaller >= NameFieldByteOffset);
  NameFieldBytes = SizeByProtocolCaller - NameFieldByteOffset;
  ASSERT (NameFieldBytes >= sizeof (CHAR16));
  if (NameFieldBytes % sizeof (CHAR16) != 0) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // The CHAR16 Name field at the end of the structure must be NUL-terminated.
  //
  NameFieldChar16s = NameFieldBytes / sizeof (CHAR16);
  ASSERT (NameFieldChar16s >= 1);

  NameField = (CHAR16 *)((UINT8 *)Buffer + NameFieldByteOffset);
  if (NameField[NameFieldChar16s - 1] != L'\0') {
    return EFI_INVALID_PARAMETER;
  }

  return EFI_SUCCESS;
}

/**
  Rename a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.FileName.

  @param[in,out] VirtioFsFile  The VIRTIO_FS_FILE to rename.

  @param[in] NewFileName       The new file name requested by
                               EFI_FILE_PROTOCOL.SetInfo().

  @retval EFI_SUCCESS        The canonical format destination path that is
                             determined from the input value of
                             VirtioFsFile->CanonicalPathname and from
                             NewFileName is identical to the input value of
                             VirtioFsFile->CanonicalPathname. This means that
                             EFI_FILE_INFO does not constitute a rename
                             request. VirtioFsFile has not been changed.

  @retval EFI_SUCCESS        VirtioFsFile has been renamed.
                             VirtioFsFile->CanonicalPathname has assumed the
                             destination pathname in canonical format.

  @retval EFI_ACCESS_DENIED  VirtioFsFile refers to the root directory, and
                             NewFileName expresses an actual rename/move
                             request.

  @retval EFI_ACCESS_DENIED  VirtioFsFile is the (possibly indirect) parent
                             directory of at least one other VIRTIO_FS_FILE
                             that is open for the same Virtio Filesystem
                             (identified by VirtioFsFile->OwnerFs). Renaming
                             VirtioFsFile would invalidate the canonical
                             pathnames of those VIRTIO_FS_FILE instances;
                             therefore the request has been rejected.

  @retval EFI_ACCESS_DENIED  VirtioFsFile is not open for writing, but
                             NewFileName expresses an actual rename/move
                             request.

  @retval EFI_NOT_FOUND      At least one dot-dot component in NewFileName
                             attempted to escape the root directory.

  @return                    Error codes propagated from underlying functions.
**/
STATIC
EFI_STATUS
Rename (
  IN OUT VIRTIO_FS_FILE *VirtioFsFile,
  IN     CHAR16         *NewFileName
  )
{

  VIRTIO_FS  *VirtioFs;
  EFI_STATUS Status;
  CHAR8      *Destination;
  BOOLEAN    RootEscape;
  UINT64     OldParentDirNodeId;
  CHAR8      *OldLastComponent;
  UINT64     NewParentDirNodeId;
  CHAR8      *NewLastComponent;

  VirtioFs = VirtioFsFile->OwnerFs;

  //
  // The root directory cannot be renamed.
  //
  if (AsciiStrCmp (VirtioFsFile->CanonicalPathname, "/") == 0) {
    if (StrCmp (NewFileName, L"") == 0) {
      //
      // Not a rename request anyway.
      //
      return EFI_SUCCESS;
    }
    return EFI_ACCESS_DENIED;
  }

  //
  // Compose the canonical pathname for the destination.
  //
  Status = VirtioFsComposeRenameDestination (VirtioFsFile->CanonicalPathname,
             NewFileName, &Destination, &RootEscape);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  if (RootEscape) {
    Status = EFI_NOT_FOUND;
    goto FreeDestination;
  }
  //
  // If the rename would leave VirtioFsFile->CanonicalPathname unchanged, then
  // EFI_FILE_PROTOCOL.SetInfo() isn't asking for a rename actually.
  //
  if (AsciiStrCmp (VirtioFsFile->CanonicalPathname, Destination) == 0) {
    Status = EFI_SUCCESS;
    goto FreeDestination;
  }
  //
  // Check if the rename would break the canonical pathnames of other
  // VIRTIO_FS_FILE instances of the same VIRTIO_FS.
  //
  if (VirtioFsFile->IsDirectory) {
    UINTN      PathLen;
    LIST_ENTRY *OpenFilesEntry;

    PathLen = AsciiStrLen (VirtioFsFile->CanonicalPathname);
    BASE_LIST_FOR_EACH (OpenFilesEntry, &VirtioFs->OpenFiles) {
      VIRTIO_FS_FILE *OtherFile;

      OtherFile = VIRTIO_FS_FILE_FROM_OPEN_FILES_ENTRY (OpenFilesEntry);
      if (OtherFile != VirtioFsFile &&
          AsciiStrnCmp (VirtioFsFile->CanonicalPathname,
            OtherFile->CanonicalPathname, PathLen) == 0 &&
          (OtherFile->CanonicalPathname[PathLen] == '\0' ||
           OtherFile->CanonicalPathname[PathLen] == '/')) {
        //
        // OtherFile refers to the same directory as VirtioFsFile, or is a
        // (possibly indirect) child of the directory referred to by
        // VirtioFsFile.
        //
        Status = EFI_ACCESS_DENIED;
        goto FreeDestination;
      }
    }
  }
  //
  // From this point on, the file needs to be open for writing.
  //
  if (!VirtioFsFile->IsOpenForWriting) {
    Status = EFI_ACCESS_DENIED;
    goto FreeDestination;
  }
  //
  // Split both source and destination canonical pathnames into (most specific
  // parent directory, last component) pairs.
  //
  Status = VirtioFsLookupMostSpecificParentDir (VirtioFs,
             VirtioFsFile->CanonicalPathname, &OldParentDirNodeId,
             &OldLastComponent);
  if (EFI_ERROR (Status)) {
    goto FreeDestination;
  }
  Status = VirtioFsLookupMostSpecificParentDir (VirtioFs, Destination,
             &NewParentDirNodeId, &NewLastComponent);
  if (EFI_ERROR (Status)) {
    goto ForgetOldParentDirNodeId;
  }
  //
  // Perform the rename. If the destination path exists, the rename will fail.
  //
  Status = VirtioFsFuseRename (VirtioFs, OldParentDirNodeId, OldLastComponent,
             NewParentDirNodeId, NewLastComponent);
  if (EFI_ERROR (Status)) {
    goto ForgetNewParentDirNodeId;
  }

  //
  // Swap in the new canonical pathname.
  //
  FreePool (VirtioFsFile->CanonicalPathname);
  VirtioFsFile->CanonicalPathname = Destination;
  Destination = NULL;
  Status = EFI_SUCCESS;

  //
  // Fall through.
  //
ForgetNewParentDirNodeId:
  if (NewParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {
    VirtioFsFuseForget (VirtioFs, NewParentDirNodeId);
  }

ForgetOldParentDirNodeId:
  if (OldParentDirNodeId != VIRTIO_FS_FUSE_ROOT_DIR_NODE_ID) {
    VirtioFsFuseForget (VirtioFs, OldParentDirNodeId);
  }

FreeDestination:
  if (Destination != NULL) {
    FreePool (Destination);
  }
  return Status;
}

/**
  Update the attributes of a VIRTIO_FS_FILE as requested in EFI_FILE_INFO.

  @param[in,out] VirtioFsFile  The VIRTIO_FS_FILE to update the attributes of.

  @param[in] NewFileInfo       The new attributes requested by
                               EFI_FILE_PROTOCOL.SetInfo(). NewFileInfo->Size
                               and NewFileInfo->FileName are ignored.

  @retval EFI_SUCCESS        No attributes had to be updated.

  @retval EFI_SUCCESS        The required set of attribute updates has been
                             determined and performed successfully.

  @retval EFI_ACCESS_DENIED  NewFileInfo requests an update to a property
                             different from the EFI_FILE_READ_ONLY bit in the
                             Attribute field, but VirtioFsFile is not open for
                             writing.

  @return                    Error codes propagated from underlying functions.
**/
STATIC
EFI_STATUS
UpdateAttributes (
  IN OUT VIRTIO_FS_FILE *VirtioFsFile,
  IN     EFI_FILE_INFO  *NewFileInfo
  )
{
  VIRTIO_FS                          *VirtioFs;
  EFI_STATUS                         Status;
  VIRTIO_FS_FUSE_ATTRIBUTES_RESPONSE FuseAttr;
  EFI_FILE_INFO                      FileInfo;
  BOOLEAN                            UpdateFileSize;
  UINT64                             FileSize;
  BOOLEAN                            UpdateAtime;
  BOOLEAN                            UpdateMtime;
  UINT64                             Atime;
  UINT64                             Mtime;
  BOOLEAN                            UpdateMode;
  UINT32                             Mode;

  VirtioFs = VirtioFsFile->OwnerFs;

  //
  // Fetch the current attributes first, so we can build the difference between
  // them and NewFileInfo.
  //
  Status = VirtioFsFuseGetAttr (VirtioFs, VirtioFsFile->NodeId, &FuseAttr);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  Status = VirtioFsFuseAttrToEfiFileInfo (&FuseAttr, &FileInfo);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Collect the updates.
  //
  if (VirtioFsFile->IsDirectory) {
    UpdateFileSize = FALSE;
  } else {
    VirtioFsGetFuseSizeUpdate (&FileInfo, NewFileInfo, &UpdateFileSize,
      &FileSize);
  }

  Status = VirtioFsGetFuseTimeUpdates (&FileInfo, NewFileInfo, &UpdateAtime,
             &UpdateMtime, &Atime, &Mtime);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = VirtioFsGetFuseModeUpdate (&FileInfo, NewFileInfo, &UpdateMode,
             &Mode);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // If no attribute updates are necessary, we're done.
  //
  if (!UpdateFileSize && !UpdateAtime && !UpdateMtime && !UpdateMode) {
    return EFI_SUCCESS;
  }
  //
  // If the file is not open for writing, then only Mode may be updated (for
  // toggling EFI_FILE_READ_ONLY).
  //
  if (!VirtioFsFile->IsOpenForWriting &&
      (UpdateFileSize || UpdateAtime || UpdateMtime)) {
    return EFI_ACCESS_DENIED;
  }
  //
  // Send the FUSE_SETATTR request now.
  //
  Status = VirtioFsFuseSetAttr (
             VirtioFs,
             VirtioFsFile->NodeId,
             UpdateFileSize ? &FileSize : NULL,
             UpdateAtime    ? &Atime    : NULL,
             UpdateMtime    ? &Mtime    : NULL,
             UpdateMode     ? &Mode     : NULL
             );
  return Status;
}

/**
  Process an EFI_FILE_INFO setting request.
**/
STATIC
EFI_STATUS
SetFileInfo (
  IN EFI_FILE_PROTOCOL *This,
  IN UINTN             BufferSize,
  IN VOID              *Buffer
  )
{
  VIRTIO_FS_FILE *VirtioFsFile;
  EFI_STATUS     Status;
  EFI_FILE_INFO  *FileInfo;

  VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);

  //
  // Validate if Buffer passes as EFI_FILE_INFO.
  //
  Status = ValidateInfoStructure (
             BufferSize,                    // SizeByProtocolCaller
             OFFSET_OF (EFI_FILE_INFO,
               FileName) + sizeof (CHAR16), // MinimumStructSize
             TRUE,                          // IsSizeByInfoPresent
             Buffer
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  FileInfo = Buffer;

  //
  // Perform the rename/move request, if any.
  //
  Status = Rename (VirtioFsFile, FileInfo->FileName);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Update any attributes requested.
  //
  Status = UpdateAttributes (VirtioFsFile, FileInfo);
  //
  // The UEFI spec does not speak about partial failure in
  // EFI_FILE_PROTOCOL.SetInfo(); we won't try to roll back the rename (if
  // there was one) in case the attribute updates fail.
  //
  return Status;
}

/**
  Process an EFI_FILE_SYSTEM_INFO setting request.
**/
STATIC
EFI_STATUS
SetFileSystemInfo (
  IN EFI_FILE_PROTOCOL *This,
  IN UINTN             BufferSize,
  IN VOID              *Buffer
  )
{
  VIRTIO_FS_FILE       *VirtioFsFile;
  VIRTIO_FS            *VirtioFs;
  EFI_STATUS           Status;
  EFI_FILE_SYSTEM_INFO *FileSystemInfo;

  VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
  VirtioFs     = VirtioFsFile->OwnerFs;

  //
  // Validate if Buffer passes as EFI_FILE_SYSTEM_INFO.
  //
  Status = ValidateInfoStructure (
             BufferSize,                       // SizeByProtocolCaller
             OFFSET_OF (EFI_FILE_SYSTEM_INFO,
               VolumeLabel) + sizeof (CHAR16), // MinimumStructSize
             TRUE,                             // IsSizeByInfoPresent
             Buffer
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  FileSystemInfo = Buffer;

  //
  // EFI_FILE_SYSTEM_INFO fields other than VolumeLabel cannot be changed, per
  // spec.
  //
  // If the label is being changed to its current value, report success;
  // otherwise, reject the request, as the Virtio Filesystem device does not
  // support changing the label.
  //
  if (StrCmp (FileSystemInfo->VolumeLabel, VirtioFs->Label) == 0) {
    return EFI_SUCCESS;
  }
  return EFI_WRITE_PROTECTED;
}

/**
  Process an EFI_FILE_SYSTEM_VOLUME_LABEL setting request.
**/
STATIC
EFI_STATUS
SetFileSystemVolumeLabelInfo (
  IN EFI_FILE_PROTOCOL *This,
  IN UINTN             BufferSize,
  IN VOID              *Buffer
  )
{
  VIRTIO_FS_FILE               *VirtioFsFile;
  VIRTIO_FS                    *VirtioFs;
  EFI_STATUS                   Status;
  EFI_FILE_SYSTEM_VOLUME_LABEL *FileSystemVolumeLabel;

  VirtioFsFile = VIRTIO_FS_FILE_FROM_SIMPLE_FILE (This);
  VirtioFs     = VirtioFsFile->OwnerFs;

  //
  // Validate if Buffer passes as EFI_FILE_SYSTEM_VOLUME_LABEL.
  //
  Status = ValidateInfoStructure (
             BufferSize,                              // SizeByProtocolCaller
             OFFSET_OF (EFI_FILE_SYSTEM_VOLUME_LABEL,
               VolumeLabel) + sizeof (CHAR16),        // MinimumStructSize
             FALSE,                                   // IsSizeByInfoPresent
             Buffer
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  FileSystemVolumeLabel = Buffer;

  //
  // If the label is being changed to its current value, report success;
  // otherwise, reject the request, as the Virtio Filesystem device does not
  // support changing the label.
  //
  if (StrCmp (FileSystemVolumeLabel->VolumeLabel, VirtioFs->Label) == 0) {
    return EFI_SUCCESS;
  }
  return EFI_WRITE_PROTECTED;
}

EFI_STATUS
EFIAPI
VirtioFsSimpleFileSetInfo (
  IN EFI_FILE_PROTOCOL *This,
  IN EFI_GUID          *InformationType,
  IN UINTN             BufferSize,
  IN VOID              *Buffer
  )
{
  if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
    return SetFileInfo (This, BufferSize, Buffer);
  }

  if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
    return SetFileSystemInfo (This, BufferSize, Buffer);
  }

  if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
    return SetFileSystemVolumeLabelInfo (This, BufferSize, Buffer);
  }

  return EFI_UNSUPPORTED;
}