Coverage Report

Created: 2025-07-12 06:33

/src/fwupd/libfwupdplugin/fu-volume.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2018 Richard Hughes <richard@hughsie.com>
3
 * Copyright 2019 Mario Limonciello <mario.limonciello@dell.com>
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 */
7
8
0
#define G_LOG_DOMAIN "FuVolume"
9
10
#include "config.h"
11
12
#include <gio/gio.h>
13
#include <glib/gstdio.h>
14
15
#if defined(HAVE_IOCTL_H) && defined(HAVE_BLKSSZGET)
16
#include <fcntl.h>
17
#include <sys/ioctl.h>
18
#include <sys/mount.h>
19
#endif
20
21
#include "fwupd-error.h"
22
23
#include "fu-common-private.h"
24
#include "fu-volume-private.h"
25
26
/**
27
 * FuVolume:
28
 *
29
 * Volume abstraction that uses UDisks
30
 */
31
32
struct _FuVolume {
33
  GObject parent_instance;
34
  GDBusProxy *proxy_blk;
35
  GDBusProxy *proxy_fs;
36
  GDBusProxy *proxy_part;
37
  gchar *mount_path;     /* only when mounted ourselves */
38
  gchar *partition_kind; /* only for tests */
39
  gchar *partition_uuid; /* only for tests */
40
  guint64 fs_free;       /* only for tests */
41
};
42
43
enum {
44
  PROP_0,
45
  PROP_MOUNT_PATH,
46
  PROP_PROXY_BLOCK,
47
  PROP_PROXY_FILESYSTEM,
48
  PROP_PROXY_PARTITION,
49
  PROP_LAST
50
};
51
52
static void
53
fu_volume_codec_iface_init(FwupdCodecInterface *iface);
54
55
G_DEFINE_TYPE_EXTENDED(FuVolume,
56
           fu_volume,
57
           G_TYPE_OBJECT,
58
           0,
59
           G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC, fu_volume_codec_iface_init))
60
61
static void
62
fu_volume_add_json(FwupdCodec *codec, JsonBuilder *builder, FwupdCodecFlags flags)
63
0
{
64
0
  FuVolume *self = FU_VOLUME(codec);
65
0
  g_autofree gchar *mount_point = fu_volume_get_mount_point(self);
66
0
  g_autofree gchar *partition_kind = fu_volume_get_partition_kind(self);
67
0
  g_autofree gchar *partition_name = fu_volume_get_partition_name(self);
68
0
  g_autofree gchar *partition_uuid = fu_volume_get_partition_uuid(self);
69
70
0
  fwupd_codec_json_append_bool(builder, "IsMounted", fu_volume_is_mounted(self));
71
0
  fwupd_codec_json_append_bool(builder, "IsEncrypted", fu_volume_is_encrypted(self));
72
0
  fwupd_codec_json_append_int(builder, "Size", fu_volume_get_size(self));
73
0
  fwupd_codec_json_append_int(builder, "BlockSize", fu_volume_get_block_size(self, NULL));
74
0
  fwupd_codec_json_append(builder, "MountPoint", mount_point);
75
0
  fwupd_codec_json_append(builder, "PartitionKind", partition_kind);
76
0
  fwupd_codec_json_append(builder, "PartitionName", partition_name);
77
0
  fwupd_codec_json_append_int(builder, "PartitionSize", fu_volume_get_partition_size(self));
78
0
  fwupd_codec_json_append_int(builder,
79
0
            "PartitionOffset",
80
0
            fu_volume_get_partition_offset(self));
81
0
  fwupd_codec_json_append_int(builder,
82
0
            "PartitionNumber",
83
0
            fu_volume_get_partition_number(self));
84
0
  fwupd_codec_json_append(builder, "PartitionUuid", partition_uuid);
85
0
}
86
87
static void
88
fu_volume_finalize(GObject *obj)
89
0
{
90
0
  FuVolume *self = FU_VOLUME(obj);
91
0
  g_free(self->mount_path);
92
0
  g_free(self->partition_kind);
93
0
  g_free(self->partition_uuid);
94
0
  if (self->proxy_blk != NULL)
95
0
    g_object_unref(self->proxy_blk);
96
0
  if (self->proxy_fs != NULL)
97
0
    g_object_unref(self->proxy_fs);
98
0
  if (self->proxy_part != NULL)
99
0
    g_object_unref(self->proxy_part);
100
0
  G_OBJECT_CLASS(fu_volume_parent_class)->finalize(obj);
101
0
}
102
103
static void
104
fu_volume_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
105
0
{
106
0
  FuVolume *self = FU_VOLUME(object);
107
0
  switch (prop_id) {
108
0
  case PROP_MOUNT_PATH:
109
0
    g_value_set_string(value, self->mount_path);
110
0
    break;
111
0
  case PROP_PROXY_BLOCK:
112
0
    g_value_set_object(value, self->proxy_blk);
113
0
    break;
114
0
  case PROP_PROXY_FILESYSTEM:
115
0
    g_value_set_object(value, self->proxy_fs);
116
0
    break;
117
0
  case PROP_PROXY_PARTITION:
118
0
    g_value_set_object(value, self->proxy_part);
119
0
    break;
120
0
  default:
121
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
122
0
    break;
123
0
  }
124
0
}
125
126
static void
127
fu_volume_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
128
0
{
129
0
  FuVolume *self = FU_VOLUME(object);
130
0
  switch (prop_id) {
131
0
  case PROP_MOUNT_PATH:
132
0
    self->mount_path = g_value_dup_string(value);
133
0
    break;
134
0
  case PROP_PROXY_BLOCK:
135
0
    self->proxy_blk = g_value_dup_object(value);
136
0
    break;
137
0
  case PROP_PROXY_FILESYSTEM:
138
0
    self->proxy_fs = g_value_dup_object(value);
139
0
    break;
140
0
  case PROP_PROXY_PARTITION:
141
0
    self->proxy_part = g_value_dup_object(value);
142
0
    break;
143
0
  default:
144
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
145
0
    break;
146
0
  }
147
0
}
148
149
static void
150
fu_volume_class_init(FuVolumeClass *klass)
151
0
{
152
0
  GParamSpec *pspec;
153
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
154
155
0
  object_class->finalize = fu_volume_finalize;
156
0
  object_class->get_property = fu_volume_get_property;
157
0
  object_class->set_property = fu_volume_set_property;
158
159
  /**
160
   * FuVolume:proxy-block:
161
   *
162
   * The proxy for the block interface.
163
   *
164
   * Since: 1.4.6
165
   */
166
0
  pspec =
167
0
      g_param_spec_object("proxy-block",
168
0
        NULL,
169
0
        NULL,
170
0
        G_TYPE_DBUS_PROXY,
171
0
        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
172
0
  g_object_class_install_property(object_class, PROP_PROXY_BLOCK, pspec);
173
174
  /**
175
   * FuVolume:proxy-filesystem:
176
   *
177
   * The proxy for the filesystem interface.
178
   *
179
   * Since: 1.4.6
180
   */
181
0
  pspec =
182
0
      g_param_spec_object("proxy-filesystem",
183
0
        NULL,
184
0
        NULL,
185
0
        G_TYPE_DBUS_PROXY,
186
0
        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
187
0
  g_object_class_install_property(object_class, PROP_PROXY_FILESYSTEM, pspec);
188
189
  /**
190
   * FuVolume:mount-path:
191
   *
192
   * The UNIX mount path.
193
   *
194
   * Since: 1.4.6
195
   */
196
0
  pspec =
197
0
      g_param_spec_string("mount-path",
198
0
        NULL,
199
0
        NULL,
200
0
        NULL,
201
0
        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
202
0
  g_object_class_install_property(object_class, PROP_MOUNT_PATH, pspec);
203
204
  /**
205
   * FuVolume:proxy-partition:
206
   *
207
   * The proxy for the filesystem interface.
208
   *
209
   * Since: 1.9.3
210
   */
211
0
  pspec =
212
0
      g_param_spec_object("proxy-partition",
213
0
        NULL,
214
0
        NULL,
215
0
        G_TYPE_DBUS_PROXY,
216
0
        G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
217
0
  g_object_class_install_property(object_class, PROP_PROXY_PARTITION, pspec);
218
0
}
219
220
static void
221
fu_volume_init(FuVolume *self)
222
0
{
223
0
}
224
225
/**
226
 * fu_volume_get_id:
227
 * @self: a @FuVolume
228
 *
229
 * Gets the D-Bus path of the mount point.
230
 *
231
 * Returns: string ID, or %NULL
232
 *
233
 * Since: 1.4.6
234
 **/
235
const gchar *
236
fu_volume_get_id(FuVolume *self)
237
0
{
238
0
  g_return_val_if_fail(FU_IS_VOLUME(self), NULL);
239
0
  if (self->proxy_fs != NULL)
240
0
    return g_dbus_proxy_get_object_path(self->proxy_fs);
241
0
  if (self->proxy_blk != NULL)
242
0
    return g_dbus_proxy_get_object_path(self->proxy_blk);
243
0
  if (self->proxy_part != NULL)
244
0
    return g_dbus_proxy_get_object_path(self->proxy_part);
245
0
  return NULL;
246
0
}
247
248
/**
249
 * fu_volume_get_size:
250
 * @self: a @FuVolume
251
 *
252
 * Gets the size of the block device pointed to by the volume.
253
 *
254
 * Returns: size in bytes, or 0 on error
255
 *
256
 * Since: 1.9.3
257
 **/
258
guint64
259
fu_volume_get_size(FuVolume *self)
260
0
{
261
0
  g_autoptr(GVariant) val = NULL;
262
263
0
  g_return_val_if_fail(FU_IS_VOLUME(self), 0);
264
265
0
  if (self->proxy_blk == NULL)
266
0
    return 0;
267
0
  val = g_dbus_proxy_get_cached_property(self->proxy_blk, "Size");
268
0
  if (val == NULL)
269
0
    return 0;
270
0
  return g_variant_get_uint64(val);
271
0
}
272
273
/**
274
 * fu_volume_get_partition_size:
275
 * @self: a @FuVolume
276
 *
277
 * Gets the size of the partition.
278
 *
279
 * Returns: size in bytes, or 0 on error
280
 *
281
 * Since: 1.9.3
282
 **/
283
guint64
284
fu_volume_get_partition_size(FuVolume *self)
285
0
{
286
0
  g_autoptr(GVariant) val = NULL;
287
288
0
  g_return_val_if_fail(FU_IS_VOLUME(self), 0);
289
290
0
  if (self->proxy_part == NULL)
291
0
    return 0;
292
0
  val = g_dbus_proxy_get_cached_property(self->proxy_part, "Size");
293
0
  if (val == NULL)
294
0
    return 0;
295
0
  return g_variant_get_uint64(val);
296
0
}
297
298
/**
299
 * fu_volume_get_partition_offset:
300
 * @self: a @FuVolume
301
 *
302
 * Gets the offset of the partition.
303
 *
304
 * Returns: offset in bytes, or 0 on error
305
 *
306
 * Since: 1.9.3
307
 **/
308
guint64
309
fu_volume_get_partition_offset(FuVolume *self)
310
0
{
311
0
  g_autoptr(GVariant) val = NULL;
312
313
0
  g_return_val_if_fail(FU_IS_VOLUME(self), 0);
314
315
0
  if (self->proxy_part == NULL)
316
0
    return 0;
317
0
  val = g_dbus_proxy_get_cached_property(self->proxy_part, "Offset");
318
0
  if (val == NULL)
319
0
    return 0;
320
0
  return g_variant_get_uint64(val);
321
0
}
322
323
/**
324
 * fu_volume_get_partition_number:
325
 * @self: a @FuVolume
326
 *
327
 * Gets the number of the partition.
328
 *
329
 * Returns: size in bytes, or 0 on error
330
 *
331
 * Since: 1.9.3
332
 **/
333
guint32
334
fu_volume_get_partition_number(FuVolume *self)
335
0
{
336
0
  g_autoptr(GVariant) val = NULL;
337
338
0
  g_return_val_if_fail(FU_IS_VOLUME(self), 0);
339
340
0
  if (self->proxy_part == NULL)
341
0
    return 0;
342
0
  val = g_dbus_proxy_get_cached_property(self->proxy_part, "Number");
343
0
  if (val == NULL)
344
0
    return 0;
345
0
  return g_variant_get_uint32(val);
346
0
}
347
348
/**
349
 * fu_volume_get_partition_uuid:
350
 * @self: a @FuVolume
351
 *
352
 * Gets the UUID of the partition.
353
 *
354
 * Returns: size in bytes, or 0 on error
355
 *
356
 * Since: 1.9.3
357
 **/
358
gchar *
359
fu_volume_get_partition_uuid(FuVolume *self)
360
0
{
361
0
  g_autoptr(GVariant) val = NULL;
362
363
0
  g_return_val_if_fail(FU_IS_VOLUME(self), NULL);
364
365
0
  if (self->partition_uuid != NULL)
366
0
    return g_strdup(self->partition_uuid);
367
0
  if (self->proxy_part == NULL)
368
0
    return NULL;
369
0
  val = g_dbus_proxy_get_cached_property(self->proxy_part, "UUID");
370
0
  if (val == NULL)
371
0
    return NULL;
372
0
  return g_variant_dup_string(val, NULL);
373
0
}
374
375
/**
376
 * fu_volume_get_partition_kind:
377
 * @self: a @FuVolume
378
 *
379
 * Gets the partition kind of the volume mount point.
380
 *
381
 * NOTE: If you want this to be converted to a GPT-style GUID then use
382
 * fu_volume_kind_convert_to_gpt() on the return value of this function.
383
 *
384
 * Returns: (transfer full): partition kind, e.g. `0x06`, `vfat` or a GUID like `FU_VOLUME_KIND_ESP`
385
 *
386
 * Since: 1.8.13
387
 **/
388
gchar *
389
fu_volume_get_partition_kind(FuVolume *self)
390
0
{
391
0
  g_autoptr(GVariant) val = NULL;
392
393
0
  g_return_val_if_fail(FU_IS_VOLUME(self), NULL);
394
395
0
  if (self->partition_kind != NULL)
396
0
    return g_strdup(self->partition_kind);
397
0
  if (self->proxy_part == NULL)
398
0
    return NULL;
399
0
  val = g_dbus_proxy_get_cached_property(self->proxy_part, "Type");
400
0
  if (val == NULL)
401
0
    return NULL;
402
0
  return g_variant_dup_string(val, NULL);
403
0
}
404
405
/**
406
 * fu_volume_set_partition_kind:
407
 * @self: a @FuVolume
408
 * @partition_kind: a partition kind, e.g. %FU_VOLUME_KIND_ESP
409
 *
410
 * Sets the partition name of the volume mount point.
411
 *
412
 * Since: 2.0.0
413
 **/
414
void
415
fu_volume_set_partition_kind(FuVolume *self, const gchar *partition_kind)
416
0
{
417
0
  g_return_if_fail(FU_IS_VOLUME(self));
418
0
  g_return_if_fail(partition_kind != NULL);
419
0
  g_return_if_fail(self->partition_kind == NULL);
420
0
  self->partition_kind = g_strdup(partition_kind);
421
0
}
422
423
/**
424
 * fu_volume_set_partition_uuid:
425
 * @self: a @FuVolume
426
 * @partition_uuid: a UUID
427
 *
428
 * Sets the partition UUID of the volume mount point.
429
 *
430
 * Since: 2.0.0
431
 **/
432
void
433
fu_volume_set_partition_uuid(FuVolume *self, const gchar *partition_uuid)
434
0
{
435
0
  g_return_if_fail(FU_IS_VOLUME(self));
436
0
  g_return_if_fail(partition_uuid != NULL);
437
0
  g_return_if_fail(self->partition_uuid == NULL);
438
0
  self->partition_uuid = g_strdup(partition_uuid);
439
0
}
440
441
/**
442
 * fu_volume_get_partition_name:
443
 * @self: a @FuVolume
444
 *
445
 * Gets the partition name of the volume mount point.
446
 *
447
 * Returns: (transfer full): partition name, e.g 'Recovery Partition'
448
 *
449
 * Since: 1.9.10
450
 **/
451
gchar *
452
fu_volume_get_partition_name(FuVolume *self)
453
0
{
454
0
  g_autofree gchar *name = NULL;
455
0
  g_autoptr(GVariant) val = NULL;
456
0
  gsize namesz = 0;
457
458
0
  g_return_val_if_fail(FU_IS_VOLUME(self), NULL);
459
460
0
  if (self->proxy_part == NULL)
461
0
    return NULL;
462
0
  val = g_dbus_proxy_get_cached_property(self->proxy_part, "Name");
463
0
  if (val == NULL)
464
0
    return NULL;
465
466
  /* only return if non-zero length */
467
0
  name = g_variant_dup_string(val, &namesz);
468
0
  if (namesz == 0)
469
0
    return NULL;
470
0
  return g_steal_pointer(&name);
471
0
}
472
473
/**
474
 * fu_volume_is_mdraid:
475
 * @self: a @FuVolume
476
 *
477
 * Determines if a volume is part of an MDRAID array.
478
 *
479
 * Returns: %TRUE if the volume is part of an MDRAID array
480
 *
481
 * Since: 1.9.17
482
 **/
483
gboolean
484
fu_volume_is_mdraid(FuVolume *self)
485
0
{
486
0
  g_autoptr(GVariant) val = NULL;
487
488
0
  g_return_val_if_fail(FU_IS_VOLUME(self), FALSE);
489
490
0
  if (self->proxy_blk == NULL)
491
0
    return FALSE;
492
0
  val = g_dbus_proxy_get_cached_property(self->proxy_blk, "MDRaid");
493
0
  if (val == NULL)
494
0
    return FALSE;
495
0
  return g_strcmp0(g_variant_get_string(val, NULL), "/") != 0;
496
0
}
497
498
static guint32
499
fu_volume_get_block_size_from_device_name(const gchar *device_name, GError **error)
500
0
{
501
#if defined(HAVE_IOCTL_H) && defined(HAVE_BLKSSZGET)
502
  gint fd;
503
  gint rc;
504
  gint sector_size = 0;
505
506
  fd = g_open(device_name, O_RDONLY, 0);
507
  if (fd < 0) {
508
    g_set_error_literal(error,
509
            G_IO_ERROR, /* nocheck:error */
510
            g_io_error_from_errno(errno),
511
            fwupd_strerror(errno));
512
    fwupd_error_convert(error);
513
    return 0;
514
  }
515
  rc = ioctl(fd, BLKSSZGET, &sector_size); /* nocheck:blocked */
516
  if (rc < 0) {
517
    g_set_error_literal(error,
518
            G_IO_ERROR, /* nocheck:error */
519
            g_io_error_from_errno(errno),
520
            fwupd_strerror(errno));
521
    fwupd_error_convert(error);
522
  } else if (sector_size == 0) {
523
    g_set_error_literal(error,
524
            FWUPD_ERROR,
525
            FWUPD_ERROR_NOT_SUPPORTED,
526
            "failed to get non-zero logical sector size");
527
  }
528
  g_close(fd, NULL);
529
  return sector_size;
530
#else
531
0
  g_set_error(error,
532
0
        FWUPD_ERROR,
533
0
        FWUPD_ERROR_NOT_SUPPORTED,
534
0
        "Not supported as <sys/ioctl.h> or BLKSSZGET not found");
535
0
  return 0;
536
0
#endif
537
0
}
538
539
/**
540
 * fu_volume_get_block_label:
541
 * @self: a @FuVolume
542
 *
543
 * Gets the block name of the volume
544
 *
545
 * Returns: (transfer full): block device name, e.g 'Recovery Partition'
546
 *
547
 * Since: 1.9.24
548
 **/
549
gchar *
550
fu_volume_get_block_name(FuVolume *self)
551
0
{
552
0
  gsize namesz = 0;
553
0
  g_autofree gchar *name = NULL;
554
0
  g_autoptr(GVariant) val = NULL;
555
556
0
  g_return_val_if_fail(FU_IS_VOLUME(self), NULL);
557
558
0
  if (self->proxy_blk == NULL)
559
0
    return NULL;
560
561
0
  val = g_dbus_proxy_get_cached_property(self->proxy_blk, "IdLabel");
562
0
  if (val == NULL)
563
0
    return NULL;
564
565
  /* only return if non-zero length */
566
0
  name = g_variant_dup_string(val, &namesz);
567
0
  if (namesz == 0)
568
0
    return NULL;
569
0
  return g_steal_pointer(&name);
570
0
}
571
572
/**
573
 * fu_volume_get_block_size:
574
 * @self: a @FuVolume
575
 *
576
 * Gets the logical block size of the volume mount point.
577
 *
578
 * Returns: block size in bytes or 0 on error
579
 *
580
 * Since: 1.9.4
581
 **/
582
gsize
583
fu_volume_get_block_size(FuVolume *self, GError **error)
584
0
{
585
0
  g_autoptr(GVariant) val = NULL;
586
587
0
  g_return_val_if_fail(FU_IS_VOLUME(self), 0);
588
589
0
  if (self->proxy_blk == NULL) {
590
0
    g_set_error_literal(error,
591
0
            FWUPD_ERROR,
592
0
            FWUPD_ERROR_NOT_SUPPORTED,
593
0
            "no udisks proxy");
594
0
    return 0;
595
0
  }
596
597
0
  val = g_dbus_proxy_get_cached_property(self->proxy_blk, "Device");
598
0
  if (val == NULL) {
599
0
    g_set_error_literal(error,
600
0
            FWUPD_ERROR,
601
0
            FWUPD_ERROR_NOT_SUPPORTED,
602
0
            "no device property");
603
0
    return 0;
604
0
  }
605
0
  return fu_volume_get_block_size_from_device_name(g_variant_get_bytestring(val), error);
606
0
}
607
608
/**
609
 * fu_volume_get_mount_point:
610
 * @self: a @FuVolume
611
 *
612
 * Gets the location of the volume mount point.
613
 *
614
 * Returns: UNIX path, or %NULL
615
 *
616
 * Since: 1.4.6
617
 **/
618
gchar *
619
fu_volume_get_mount_point(FuVolume *self)
620
0
{
621
0
  g_autofree const gchar **mountpoints = NULL;
622
0
  g_autoptr(GVariant) val = NULL;
623
624
0
  g_return_val_if_fail(FU_IS_VOLUME(self), NULL);
625
626
  /* we mounted it */
627
0
  if (self->mount_path != NULL)
628
0
    return g_strdup(self->mount_path);
629
630
  /* something else mounted it */
631
0
  if (self->proxy_fs == NULL)
632
0
    return NULL;
633
0
  val = g_dbus_proxy_get_cached_property(self->proxy_fs, "MountPoints");
634
0
  if (val == NULL)
635
0
    return NULL;
636
0
  mountpoints = g_variant_get_bytestring_array(val, NULL);
637
0
  return g_strdup(mountpoints[0]);
638
0
}
639
640
/* private: for self tests only */
641
void
642
fu_volume_set_filesystem_free(FuVolume *self, guint64 fs_free)
643
0
{
644
0
  g_return_if_fail(FU_IS_VOLUME(self));
645
0
  self->fs_free = fs_free;
646
0
}
647
648
/**
649
 * fu_volume_check_free_space:
650
 * @self: a @FuVolume
651
 * @required: size in bytes
652
 * @error: (nullable): optional return location for an error
653
 *
654
 * Checks the volume for required space.
655
 *
656
 * Returns: %TRUE for success
657
 *
658
 * Since: 1.4.6
659
 **/
660
gboolean
661
fu_volume_check_free_space(FuVolume *self, guint64 required, GError **error)
662
0
{
663
0
  guint64 fs_free;
664
0
  g_autofree gchar *path = NULL;
665
666
0
  g_return_val_if_fail(FU_IS_VOLUME(self), FALSE);
667
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
668
669
  /* skip the checks for unmounted disks */
670
0
  path = fu_volume_get_mount_point(self);
671
0
  if (path == NULL)
672
0
    return TRUE;
673
674
0
  if (self->fs_free > 0) {
675
0
    fs_free = self->fs_free;
676
0
  } else {
677
0
    g_autoptr(GFile) file = g_file_new_for_path(path);
678
0
    g_autoptr(GFileInfo) info =
679
0
        g_file_query_filesystem_info(file,
680
0
             G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
681
0
             NULL,
682
0
             error);
683
0
    if (info == NULL)
684
0
      return FALSE;
685
0
    fs_free = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
686
0
  }
687
0
  if (fs_free < required) {
688
0
    g_autofree gchar *str_free = g_format_size(required - fs_free);
689
0
    g_autofree gchar *str_reqd = g_format_size(required);
690
0
    g_set_error(error,
691
0
          FWUPD_ERROR,
692
0
          FWUPD_ERROR_NOT_SUPPORTED,
693
0
          "%s does not have sufficient space, required %s, need additional %s",
694
0
          path,
695
0
          str_reqd,
696
0
          str_free);
697
0
    return FALSE;
698
0
  }
699
0
  return TRUE;
700
0
}
701
702
/**
703
 * fu_volume_is_mounted:
704
 * @self: a @FuVolume
705
 *
706
 * Checks if the VOLUME is already mounted.
707
 *
708
 * Returns: %TRUE for success
709
 *
710
 * Since: 1.4.6
711
 **/
712
gboolean
713
fu_volume_is_mounted(FuVolume *self)
714
0
{
715
0
  g_autofree gchar *mount_point = NULL;
716
0
  g_return_val_if_fail(FU_IS_VOLUME(self), FALSE);
717
0
  mount_point = fu_volume_get_mount_point(self);
718
0
  return mount_point != NULL;
719
0
}
720
721
/**
722
 * fu_volume_is_encrypted:
723
 * @self: a @FuVolume
724
 *
725
 * Checks if the VOLUME is currently encrypted.
726
 *
727
 * Returns: %TRUE for success
728
 *
729
 * Since: 1.5.1
730
 **/
731
gboolean
732
fu_volume_is_encrypted(FuVolume *self)
733
0
{
734
0
  g_autoptr(GVariant) val = NULL;
735
736
0
  g_return_val_if_fail(FU_IS_VOLUME(self), FALSE);
737
738
0
  if (self->proxy_blk == NULL)
739
0
    return FALSE;
740
0
  val = g_dbus_proxy_get_cached_property(self->proxy_blk, "CryptoBackingDevice");
741
0
  if (val == NULL)
742
0
    return FALSE;
743
0
  if (g_strcmp0(g_variant_get_string(val, NULL), "/") == 0)
744
0
    return FALSE;
745
0
  return TRUE;
746
0
}
747
748
/**
749
 * fu_volume_mount:
750
 * @self: a @FuVolume
751
 * @error: (nullable): optional return location for an error
752
 *
753
 * Mounts the VOLUME ready for use.
754
 *
755
 * Returns: %TRUE for success
756
 *
757
 * Since: 1.4.6
758
 **/
759
gboolean
760
fu_volume_mount(FuVolume *self, GError **error)
761
0
{
762
0
  GVariantBuilder builder;
763
0
  g_autoptr(GError) error_local = NULL;
764
0
  g_autoptr(GVariant) val = NULL;
765
766
0
  g_return_val_if_fail(FU_IS_VOLUME(self), FALSE);
767
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
768
769
  /* device from the self tests */
770
0
  if (self->proxy_fs == NULL)
771
0
    return TRUE;
772
773
0
  g_debug("mounting %s", fu_volume_get_id(self));
774
0
  g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
775
0
  val = g_dbus_proxy_call_sync(self->proxy_fs,
776
0
             "Mount",
777
0
             g_variant_new("(a{sv})", &builder),
778
0
             G_DBUS_CALL_FLAGS_NONE,
779
0
             -1,
780
0
             NULL,
781
0
             &error_local);
782
0
  if (val == NULL) {
783
0
    if (g_error_matches(error_local, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_INTERFACE) ||
784
0
        g_error_matches(error_local, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
785
0
      g_set_error_literal(error,
786
0
              FWUPD_ERROR,
787
0
              FWUPD_ERROR_NOT_SUPPORTED,
788
0
              error_local->message);
789
0
      return FALSE;
790
0
    }
791
0
    g_propagate_error(error, g_steal_pointer(&error_local));
792
0
    return FALSE;
793
0
  }
794
0
  g_variant_get(val, "(s)", &self->mount_path);
795
0
  return TRUE;
796
0
}
797
798
/**
799
 * fu_volume_is_internal:
800
 * @self: a @FuVolume
801
 *
802
 * Guesses if the drive is internal to the system
803
 *
804
 * Returns: %TRUE for success
805
 *
806
 * Since: 1.5.2
807
 **/
808
gboolean
809
fu_volume_is_internal(FuVolume *self)
810
0
{
811
0
  g_autoptr(GVariant) val_system = NULL;
812
0
  g_return_val_if_fail(FU_IS_VOLUME(self), FALSE);
813
814
0
  val_system = g_dbus_proxy_get_cached_property(self->proxy_blk, "HintSystem");
815
0
  if (val_system == NULL)
816
0
    return FALSE;
817
818
0
  return g_variant_get_boolean(val_system);
819
0
}
820
821
/**
822
 * fu_volume_get_id_type:
823
 * @self: a @FuVolume
824
 *
825
 * Return the IdType of the volume
826
 *
827
 * Returns: string for type or NULL
828
 *
829
 * Since: 1.5.2
830
 **/
831
gchar *
832
fu_volume_get_id_type(FuVolume *self)
833
0
{
834
0
  g_autoptr(GVariant) val = NULL;
835
0
  g_return_val_if_fail(FU_IS_VOLUME(self), NULL);
836
837
0
  val = g_dbus_proxy_get_cached_property(self->proxy_blk, "IdType");
838
0
  if (val == NULL)
839
0
    return NULL;
840
841
0
  return g_strdup(g_variant_get_string(val, NULL));
842
0
}
843
844
/**
845
 * fu_volume_unmount:
846
 * @self: a @FuVolume
847
 * @error: (nullable): optional return location for an error
848
 *
849
 * Unmounts the volume after use.
850
 *
851
 * Returns: %TRUE for success
852
 *
853
 * Since: 1.4.6
854
 **/
855
gboolean
856
fu_volume_unmount(FuVolume *self, GError **error)
857
0
{
858
0
  GVariantBuilder builder;
859
0
  g_autoptr(GVariant) val = NULL;
860
861
0
  g_return_val_if_fail(FU_IS_VOLUME(self), FALSE);
862
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
863
864
  /* device from the self tests */
865
0
  if (self->proxy_fs == NULL)
866
0
    return TRUE;
867
868
0
  g_debug("unmounting %s", fu_volume_get_id(self));
869
0
  g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
870
0
  val = g_dbus_proxy_call_sync(self->proxy_fs,
871
0
             "Unmount",
872
0
             g_variant_new("(a{sv})", &builder),
873
0
             G_DBUS_CALL_FLAGS_NONE,
874
0
             -1,
875
0
             NULL,
876
0
             error);
877
0
  if (val == NULL)
878
0
    return FALSE;
879
0
  g_free(self->mount_path);
880
0
  self->mount_path = NULL;
881
0
  return TRUE;
882
0
}
883
884
/**
885
 * fu_volume_locker:
886
 * @self: a @FuVolume
887
 * @error: (nullable): optional return location for an error
888
 *
889
 * Locks the volume, mounting it and unmounting it as required. If the volume is
890
 * already mounted then it is is _not_ unmounted when the locker is closed.
891
 *
892
 * Returns: (transfer full): a device locker for success, or %NULL
893
 *
894
 * Since: 1.4.6
895
 **/
896
FuDeviceLocker *
897
fu_volume_locker(FuVolume *self, GError **error)
898
0
{
899
0
  g_return_val_if_fail(FU_IS_VOLUME(self), NULL);
900
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
901
902
  /* already open, so NOP */
903
0
  if (fu_volume_is_mounted(self))
904
0
    return g_object_new(FU_TYPE_DEVICE_LOCKER, NULL);
905
0
  return fu_device_locker_new_full(self,
906
0
           (FuDeviceLockerFunc)fu_volume_mount,
907
0
           (FuDeviceLockerFunc)fu_volume_unmount,
908
0
           error);
909
0
}
910
911
/* private */
912
FuVolume *
913
fu_volume_new_from_mount_path(const gchar *mount_path)
914
0
{
915
0
  g_autoptr(FuVolume) self = g_object_new(FU_TYPE_VOLUME, NULL);
916
0
  g_return_val_if_fail(mount_path != NULL, NULL);
917
0
  self->mount_path = g_strdup(mount_path);
918
0
  return g_steal_pointer(&self);
919
0
}
920
921
/**
922
 * fu_volume_kind_convert_to_gpt:
923
 * @kind: UDisk reported type string, e.g. `efi` or `0xef`
924
 *
925
 * Converts a MBR type to a GPT type.
926
 *
927
 * Returns: the GPT type, usually a GUID. If not known @kind is returned.
928
 *
929
 * Since: 1.8.6
930
 **/
931
const gchar *
932
fu_volume_kind_convert_to_gpt(const gchar *kind)
933
0
{
934
0
  struct {
935
0
    const gchar *gpt;
936
0
    const gchar *mbrs[6];
937
0
  } typeguids[] = {{FU_VOLUME_KIND_ESP,
938
0
        {
939
0
            "0xef",
940
0
            "efi",
941
0
            NULL,
942
0
        }},
943
0
       {FU_VOLUME_KIND_BDP,
944
0
        {
945
0
            "0x0b",
946
0
            "0x06",
947
0
            "vfat",
948
0
            "fat32",
949
0
            "fat32lba",
950
0
            NULL,
951
0
        }},
952
0
       {NULL, {NULL}}};
953
0
  for (guint i = 0; typeguids[i].gpt != NULL; i++) {
954
0
    for (guint j = 0; typeguids[i].mbrs[j] != NULL; j++) {
955
0
      if (g_strcmp0(kind, typeguids[i].mbrs[j]) == 0)
956
0
        return typeguids[i].gpt;
957
0
    }
958
0
  }
959
0
  return kind;
960
0
}
961
962
static gboolean
963
fu_volume_check_block_device_symlinks(const gchar *const *symlinks, GError **error)
964
0
{
965
0
  for (guint i = 0; symlinks[i] != NULL; i++) {
966
0
    if (g_str_has_prefix(symlinks[i], "/dev/zvol")) {
967
0
      g_set_error_literal(error,
968
0
              FWUPD_ERROR,
969
0
              FWUPD_ERROR_NOT_SUPPORTED,
970
0
              "detected zfs zvol");
971
0
      return FALSE;
972
0
    }
973
0
  }
974
975
  /* success */
976
0
  return TRUE;
977
0
}
978
979
static gboolean
980
fu_volume_check_is_recovery(const gchar *name)
981
0
{
982
0
  g_autoptr(GString) name_safe = g_string_new(name);
983
0
  const gchar *recovery_partitions[] = {
984
0
      "DELLRESTORE",
985
0
      "DELLUTILITY",
986
0
      "DIAGS",
987
0
      "HP_RECOVERY",
988
0
      "IBM_SERVICE",
989
0
      "INTELRST",
990
0
      "LENOVO_RECOVERY",
991
0
      "OS",
992
0
      "PQSERVICE",
993
0
      "RECOVERY",
994
0
      "RECOVERY_PARTITION",
995
0
      "SERVICEV001",
996
0
      "SERVICEV002",
997
0
      "SYSTEM_RESERVED",
998
0
      "WINRE_DRV",
999
0
      NULL,
1000
0
  }; /* from https://github.com/storaged-project/udisks/blob/master/data/80-udisks2.rules */
1001
1002
0
  g_string_replace(name_safe, " ", "_", 0);
1003
0
  g_string_replace(name_safe, "\"", "", 0);
1004
0
  g_string_ascii_up(name_safe);
1005
0
  return g_strv_contains(recovery_partitions, name_safe->str);
1006
0
}
1007
1008
static void
1009
fu_volume_codec_iface_init(FwupdCodecInterface *iface)
1010
0
{
1011
0
  iface->add_json = fu_volume_add_json;
1012
0
}
1013
1014
/**
1015
 * fu_volume_new_by_kind:
1016
 * @kind: a volume kind, typically a GUID
1017
 * @error: (nullable): optional return location for an error
1018
 *
1019
 * Finds all volumes of a specific partition type.
1020
 * For ESP type partitions exclude any known partitions names that
1021
 * correspond to recovery partitions.
1022
 *
1023
 * Returns: (transfer container) (element-type FuVolume): a #GPtrArray, or %NULL if the kind was not
1024
 *found
1025
 *
1026
 * Since: 1.8.2
1027
 **/
1028
GPtrArray *
1029
fu_volume_new_by_kind(const gchar *kind, GError **error)
1030
0
{
1031
0
  g_autoptr(GPtrArray) devices = NULL;
1032
0
  g_autoptr(GPtrArray) volumes = NULL;
1033
1034
0
  g_return_val_if_fail(kind != NULL, NULL);
1035
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1036
1037
0
  devices = fu_common_get_block_devices(error);
1038
0
  if (devices == NULL)
1039
0
    return NULL;
1040
0
  g_info("Looking for volumes of type %s", kind);
1041
0
  volumes = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
1042
0
  for (guint i = 0; i < devices->len; i++) {
1043
0
    GDBusProxy *proxy_blk = g_ptr_array_index(devices, i);
1044
0
    const gchar *type_str;
1045
0
    g_autofree gchar *id_type = NULL;
1046
0
    g_autofree gchar *part_type = NULL;
1047
0
    g_autoptr(FuVolume) vol = NULL;
1048
0
    g_autoptr(GDBusProxy) proxy_part = NULL;
1049
0
    g_autoptr(GDBusProxy) proxy_fs = NULL;
1050
0
    g_autoptr(GError) error_local = NULL;
1051
0
    g_autoptr(GVariant) symlinks = NULL;
1052
1053
    /* ignore anything in a zfs zvol */
1054
0
    symlinks = g_dbus_proxy_get_cached_property(proxy_blk, "Symlinks");
1055
0
    if (symlinks != NULL) {
1056
0
      g_autofree const gchar **symlinks_strv =
1057
0
          g_variant_get_bytestring_array(symlinks, NULL);
1058
0
      if (!fu_volume_check_block_device_symlinks(symlinks_strv, &error_local)) {
1059
0
        g_debug("ignoring due to symlink: %s", error_local->message);
1060
0
        continue;
1061
0
      }
1062
0
    }
1063
1064
0
    proxy_part = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk),
1065
0
               G_DBUS_PROXY_FLAGS_NONE,
1066
0
               NULL,
1067
0
               UDISKS_DBUS_SERVICE,
1068
0
               g_dbus_proxy_get_object_path(proxy_blk),
1069
0
               UDISKS_DBUS_INTERFACE_PARTITION,
1070
0
               NULL,
1071
0
               error);
1072
0
    if (proxy_part == NULL) {
1073
0
      g_prefix_error(error,
1074
0
               "failed to initialize d-bus proxy %s: ",
1075
0
               g_dbus_proxy_get_object_path(proxy_blk));
1076
0
      return NULL;
1077
0
    }
1078
0
    proxy_fs = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk),
1079
0
             G_DBUS_PROXY_FLAGS_NONE,
1080
0
             NULL,
1081
0
             UDISKS_DBUS_SERVICE,
1082
0
             g_dbus_proxy_get_object_path(proxy_blk),
1083
0
             UDISKS_DBUS_INTERFACE_FILESYSTEM,
1084
0
             NULL,
1085
0
             &error_local);
1086
0
    if (proxy_fs == NULL) {
1087
0
      g_debug("failed to get filesystem for %s: %s",
1088
0
        g_dbus_proxy_get_object_path(proxy_blk),
1089
0
        error_local->message);
1090
0
      continue;
1091
0
    }
1092
0
    vol = g_object_new(FU_TYPE_VOLUME,
1093
0
           "proxy-block",
1094
0
           proxy_blk,
1095
0
           "proxy-filesystem",
1096
0
           proxy_fs,
1097
0
           "proxy-partition",
1098
0
           proxy_part,
1099
0
           NULL);
1100
1101
0
    if (fu_volume_is_mdraid(vol))
1102
0
      part_type = g_strdup(kind);
1103
1104
0
    if (part_type == NULL)
1105
0
      part_type = fu_volume_get_partition_kind(vol);
1106
1107
    /* convert reported type to GPT type */
1108
0
    if (part_type == NULL)
1109
0
      continue;
1110
1111
0
    type_str = fu_volume_kind_convert_to_gpt(part_type);
1112
0
    id_type = fu_volume_get_id_type(vol);
1113
0
    g_info("device %s, type: %s, internal: %d, fs: %s",
1114
0
           g_dbus_proxy_get_object_path(proxy_blk),
1115
0
           fu_volume_is_mdraid(vol) ? "mdraid" : type_str,
1116
0
           fu_volume_is_internal(vol),
1117
0
           id_type);
1118
0
    if (g_strcmp0(type_str, kind) != 0)
1119
0
      continue;
1120
0
    if (g_strcmp0(id_type, "linux_raid_member") == 0) {
1121
0
      g_debug("ignoring linux_raid_member device %s",
1122
0
        g_dbus_proxy_get_object_path(proxy_blk));
1123
0
      continue;
1124
0
    }
1125
1126
    /* ignore a partition that claims to be a recovery partition */
1127
0
    if (g_strcmp0(kind, FU_VOLUME_KIND_BDP) == 0 ||
1128
0
        g_strcmp0(kind, FU_VOLUME_KIND_ESP) == 0) {
1129
0
      g_autofree gchar *name = fu_volume_get_partition_name(vol);
1130
1131
0
      if (name == NULL)
1132
0
        name = fu_volume_get_block_name(vol);
1133
0
      if (name != NULL) {
1134
0
        if (fu_volume_check_is_recovery(name)) {
1135
0
          g_debug("skipping partition '%s'", name);
1136
0
          continue;
1137
0
        }
1138
0
        g_debug("adding partition '%s'", name);
1139
0
      }
1140
0
    }
1141
0
    g_ptr_array_add(volumes, g_steal_pointer(&vol));
1142
0
  }
1143
0
  if (volumes->len == 0) {
1144
0
    g_set_error(error,
1145
0
          FWUPD_ERROR,
1146
0
          FWUPD_ERROR_NOT_FOUND,
1147
0
          "no volumes of type %s",
1148
0
          kind);
1149
0
    return NULL;
1150
0
  }
1151
0
  return g_steal_pointer(&volumes);
1152
0
}
1153
1154
/**
1155
 * fu_volume_new_by_device:
1156
 * @device: a device string, typically starting with `/dev/`
1157
 * @error: (nullable): optional return location for an error
1158
 *
1159
 * Finds the first volume from the specified device.
1160
 *
1161
 * Returns: (transfer full): a volume, or %NULL if the device was not found
1162
 *
1163
 * Since: 1.8.2
1164
 **/
1165
FuVolume *
1166
fu_volume_new_by_device(const gchar *device, GError **error)
1167
0
{
1168
0
  g_autoptr(GPtrArray) devices = NULL;
1169
1170
0
  g_return_val_if_fail(device != NULL, NULL);
1171
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1172
1173
  /* find matching block device */
1174
0
  devices = fu_common_get_block_devices(error);
1175
0
  if (devices == NULL)
1176
0
    return NULL;
1177
0
  for (guint i = 0; i < devices->len; i++) {
1178
0
    GDBusProxy *proxy_blk = g_ptr_array_index(devices, i);
1179
0
    g_autoptr(GVariant) val = NULL;
1180
0
    val = g_dbus_proxy_get_cached_property(proxy_blk, "Device");
1181
0
    if (val == NULL)
1182
0
      continue;
1183
0
    if (g_strcmp0(g_variant_get_bytestring(val), device) == 0) {
1184
0
      g_autoptr(GDBusProxy) proxy_fs = NULL;
1185
0
      g_autoptr(GDBusProxy) proxy_part = NULL;
1186
0
      g_autoptr(GError) error_local = NULL;
1187
0
      proxy_fs = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk),
1188
0
               G_DBUS_PROXY_FLAGS_NONE,
1189
0
               NULL,
1190
0
               UDISKS_DBUS_SERVICE,
1191
0
               g_dbus_proxy_get_object_path(proxy_blk),
1192
0
               UDISKS_DBUS_INTERFACE_FILESYSTEM,
1193
0
               NULL,
1194
0
               &error_local);
1195
0
      if (proxy_fs == NULL)
1196
0
        g_debug("ignoring: %s", error_local->message);
1197
0
      proxy_part = g_dbus_proxy_new_sync(g_dbus_proxy_get_connection(proxy_blk),
1198
0
                 G_DBUS_PROXY_FLAGS_NONE,
1199
0
                 NULL,
1200
0
                 UDISKS_DBUS_SERVICE,
1201
0
                 g_dbus_proxy_get_object_path(proxy_blk),
1202
0
                 UDISKS_DBUS_INTERFACE_PARTITION,
1203
0
                 NULL,
1204
0
                 &error_local);
1205
0
      if (proxy_part == NULL)
1206
0
        g_debug("ignoring: %s", error_local->message);
1207
0
      return g_object_new(FU_TYPE_VOLUME,
1208
0
              "proxy-block",
1209
0
              proxy_blk,
1210
0
              "proxy-filesystem",
1211
0
              proxy_fs,
1212
0
              "proxy-partition",
1213
0
              proxy_part,
1214
0
              NULL);
1215
0
    }
1216
0
  }
1217
1218
  /* failed */
1219
0
  g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no volumes for device %s", device);
1220
0
  return NULL;
1221
0
}
1222
1223
/**
1224
 * fu_volume_new_by_devnum:
1225
 * @devnum: a device number
1226
 * @error: (nullable): optional return location for an error
1227
 *
1228
 * Finds the first volume from the specified device.
1229
 *
1230
 * Returns: (transfer full): a volume, or %NULL if the device was not found
1231
 *
1232
 * Since: 1.8.2
1233
 **/
1234
FuVolume *
1235
fu_volume_new_by_devnum(guint32 devnum, GError **error)
1236
0
{
1237
0
  g_autoptr(GPtrArray) devices = NULL;
1238
1239
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1240
1241
  /* find matching block device */
1242
0
  devices = fu_common_get_block_devices(error);
1243
0
  if (devices == NULL)
1244
0
    return NULL;
1245
0
  for (guint i = 0; i < devices->len; i++) {
1246
0
    GDBusProxy *proxy_blk = g_ptr_array_index(devices, i);
1247
0
    g_autoptr(GVariant) val = NULL;
1248
0
    val = g_dbus_proxy_get_cached_property(proxy_blk, "DeviceNumber");
1249
0
    if (val == NULL)
1250
0
      continue;
1251
0
    if (devnum == g_variant_get_uint64(val)) {
1252
0
      return g_object_new(FU_TYPE_VOLUME, "proxy-block", proxy_blk, NULL);
1253
0
    }
1254
0
  }
1255
1256
  /* failed */
1257
0
  g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no volumes for devnum %u", devnum);
1258
0
  return NULL;
1259
0
}