Coverage Report

Created: 2026-01-17 07:04

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