• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 17992912793

24 Sep 2025 07:15PM UTC coverage: 72.205% (-0.08%) from 72.283%
17992912793

push

github

web-flow
libblkid → turn into dlopen() dependency (#39084)

Split out of #38861

153 of 207 new or added lines in 10 files covered. (73.91%)

1717 existing lines in 53 files now uncovered.

302842 of 419419 relevant lines covered (72.21%)

1052332.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.47
/src/validatefs/validatefs.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <getopt.h>
4

5
#include "sd-device.h"
6

7
#include "alloc-util.h"
8
#include "blkid-util.h"
9
#include "blockdev-util.h"
10
#include "build.h"
11
#include "chase.h"
12
#include "device-util.h"
13
#include "errno-util.h"
14
#include "fd-util.h"
15
#include "gpt.h"
16
#include "initrd-util.h"
17
#include "log.h"
18
#include "main-func.h"
19
#include "mountpoint-util.h"
20
#include "parse-argument.h"
21
#include "path-util.h"
22
#include "pretty-print.h"
23
#include "string-util.h"
24
#include "strv.h"
25
#include "utf8.h"
26
#include "xattr-util.h"
27

28
static char *arg_target = NULL;
29
static char *arg_root = NULL;
30

31
STATIC_DESTRUCTOR_REGISTER(arg_target, freep);
12✔
32
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
12✔
33

34
static int help(void) {
×
35
        int r;
×
36

37
        _cleanup_free_ char *link = NULL;
×
38
        r = terminal_urlify_man("systemd-validatefs@.service", "8", &link);
×
39
        if (r < 0)
×
40
                return log_oom();
×
41

42
        printf("%1$s [OPTIONS...] /path/to/mountpoint\n"
×
43
               "\n%3$sCheck file system validation constraints.%4$s\n\n"
44
               "  -h --help            Show this help and exit\n"
45
               "     --version         Print version string and exit\n"
46
               "     --root=PATH|auto  Operate relative to the specified path\n"
47
               "\nSee the %2$s for details.\n",
48
               program_invocation_short_name,
49
               link,
50
               ansi_highlight(),
51
               ansi_normal());
52

53
        return 0;
54
}
55

56
static int parse_argv(int argc, char *argv[]) {
12✔
57
        enum {
12✔
58
                ARG_VERSION = 0x100,
59
                ARG_ROOT,
60
        };
61

62
        int c, r;
12✔
63

64
        static const struct option options[] = {
12✔
65
                { "help",     no_argument,       NULL, 'h'         },
66
                { "version" , no_argument,       NULL, ARG_VERSION },
67
                { "root",     required_argument, NULL, ARG_ROOT    },
68
                {}
69
        };
70

71
        assert(argc >= 0);
12✔
72
        assert(argv);
12✔
73

74
        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
17✔
75
                switch (c) {
5✔
76
                case 'h':
×
77
                        return help();
×
78

79
                case ARG_VERSION:
×
80
                        return version();
×
81

82
                case ARG_ROOT:
5✔
83
                        if (streq(optarg, "auto")) {
5✔
84
                                arg_root = mfree(arg_root);
×
85

86
                                if (in_initrd()) {
×
87
                                        arg_root = strdup("/sysroot");
×
88
                                        if (!arg_root)
×
89
                                                return log_oom();
×
90
                                }
91

92
                                break;
93
                        }
94

95
                        if (!path_is_absolute(optarg))
5✔
96
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--root= argument must be 'auto' or absolute path, got: %s", optarg);
×
97

98
                        r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
5✔
99
                        if (r < 0)
5✔
100
                                return r;
101
                        break;
102

103
                case '?':
104
                        return -EINVAL;
105

106
                default:
×
107
                        assert_not_reached();
×
108
                }
109

110
        if (optind + 1 != argc)
12✔
111
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
112
                                       "%s excepts exactly one argument (the mount point).",
113
                                       program_invocation_short_name);
114

115
        arg_target = strdup(argv[optind]);
12✔
116
        if (!arg_target)
12✔
117
                return log_oom();
×
118

119
        if (arg_root && !path_startswith(arg_target, arg_root))
12✔
120
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path '%s' does not start with specified root '%s', refusing.", arg_target, arg_root);
×
121

122
        return 1;
123
}
124

125
typedef struct ValidateFields {
126
        sd_id128_t *gpt_type_uuid;
127
        size_t n_gpt_type_uuid;
128
        char **gpt_label;
129
        char **mount_point;
130
} ValidateFields;
131

132
static void validate_fields_done(ValidateFields *f) {
24✔
133
        assert(f);
24✔
134

135
        free(f->gpt_type_uuid);
24✔
136
        strv_free(f->gpt_label);
24✔
137
        strv_free(f->mount_point);
24✔
138
}
24✔
139

140
static char* validate_fields_gpt_type_uuid_as_string(const ValidateFields *f) {
×
141
        _cleanup_free_ char *joined = NULL;
×
142

143
        assert(f);
×
144

145
        FOREACH_ARRAY(u, f->gpt_type_uuid, f->n_gpt_type_uuid) {
×
146
                if (!strextend_with_separator(&joined, ", ", SD_ID128_TO_UUID_STRING(*u)))
×
147
                        return NULL;
×
148

149
                const char *id = gpt_partition_type_uuid_to_string(*u);
×
150
                if (id)
×
151
                        (void) strextend(&joined, " (", id, ")");
×
152
        }
153

154
        return TAKE_PTR(joined);
×
155
}
156

157
static int validate_fields_read(int fd, ValidateFields *ret) {
12✔
158
        _cleanup_(validate_fields_done) ValidateFields f = {};
12✔
159
        int r;
12✔
160

161
        assert(fd >= 0);
12✔
162
        assert(ret);
12✔
163

164
        _cleanup_strv_free_ char **l = NULL;
12✔
165
        r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.gpt_type_uuid", AT_EMPTY_PATH, &l);
12✔
166
        if (r < 0) {
12✔
167
                if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
5✔
168
                        return log_error_errno(r, "Failed to read 'user.validatefs.gpt_type_uuid' xattr: %m");
×
169
        } else {
170
                STRV_FOREACH(i, l) {
16✔
171
                        if (!GREEDY_REALLOC(f.gpt_type_uuid, f.n_gpt_type_uuid+1))
9✔
172
                                return log_oom();
×
173

174
                        r = sd_id128_from_string(*i, f.gpt_type_uuid + f.n_gpt_type_uuid);
9✔
175
                        if (r < 0)
9✔
176
                                return log_error_errno(r, "Failed to parse 'user.validatefs.gpt_type_uuid' xattr: %s", *i);
×
177

178
                        f.n_gpt_type_uuid++;
9✔
179
                }
180
        }
181

182
        l = strv_free(l);
12✔
183
        r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.gpt_label", AT_EMPTY_PATH, &l);
12✔
184
        if (r < 0) {
12✔
185
                if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
5✔
186
                        return log_error_errno(r, "Failed to read 'user.validatefs.gpt_label' xattr: %m");
×
187
        } else {
188
                STRV_FOREACH(i, l)
16✔
189
                        if (!utf8_is_valid(*i) ||
18✔
190
                            string_has_cc(*i, /* ok= */ NULL))
9✔
191
                                return log_error_errno(
×
192
                                                SYNTHETIC_ERRNO(EINVAL),
193
                                                "Extended attribute 'user.validatefs.gpt_label' contains invalid characters, refusing: %s", *i);
194

195
                f.gpt_label = TAKE_PTR(l);
7✔
196
        }
197

198
        l = strv_free(l);
12✔
199
        r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.mount_point", AT_EMPTY_PATH, &l);
12✔
200
        if (r < 0) {
12✔
201
                if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
3✔
202
                        return log_error_errno(r, "Failed to read 'user.validatefs.mount_point' xattr: %m");
×
203
        } else {
204
                STRV_FOREACH(i, l)
23✔
205
                        if (!utf8_is_valid(*i) ||
28✔
206
                            string_has_cc(*i, /* ok= */ NULL) ||
28✔
207
                            !path_is_absolute(*i) ||
28✔
208
                            !path_is_normalized(*i))
14✔
209
                                return log_error_errno(
×
210
                                                SYNTHETIC_ERRNO(EINVAL),
211
                                                "Path listed in extended attribute 'user.validatefs.mount_point' is not a valid, normalized, absolute path or contains invalid characters, refusing: %s", *i);
212

213
                f.mount_point = TAKE_PTR(l);
9✔
214
        }
215

216
        r = f.n_gpt_type_uuid > 0 || !strv_isempty(f.gpt_label) || !strv_isempty(f.mount_point);
12✔
217
        *ret = TAKE_STRUCT(f);
12✔
218
        return r;
12✔
219
}
220

221
static int validate_mount_point(const char *path, const ValidateFields *f) {
9✔
222
        assert(path);
9✔
223
        assert(f);
9✔
224

225
        if (strv_isempty(f->mount_point))
9✔
226
                return 0;
9✔
227

228
        STRV_FOREACH(i, f->mount_point) {
17✔
229
                _cleanup_free_ char *jj = NULL;
13✔
230
                const char *j;
13✔
231

232
                if (arg_root) {
13✔
233
                        jj = path_join(arg_root, *i);
4✔
234
                        if (!jj)
4✔
235
                                return log_oom();
×
236

237
                        j = jj;
238
                } else
239
                        j = *i;
240

241
                if (path_equal(path, j))
13✔
242
                        return 0;
243
        }
244

245
        _cleanup_free_ char *joined = strv_join(f->mount_point, ", ");
8✔
246
        return log_error_errno(
4✔
247
                        SYNTHETIC_ERRNO(EPERM),
248
                        "File system is supposed to be mounted on one of %s only, but is mounted on %s, refusing.",
249
                        strna(joined), path);
250
}
251

252
static int validate_gpt_label(blkid_probe b, const ValidateFields *f) {
5✔
253
        assert(b);
5✔
254
        assert(f);
5✔
255

256
        if (strv_isempty(f->gpt_label))
5✔
257
                return 0;
5✔
258

259
        const char *v = NULL;
5✔
260
        (void) sym_blkid_probe_lookup_value(b, "PART_ENTRY_NAME", &v, /* len= */ NULL);
5✔
261

262
        if (strv_contains(f->gpt_label, strempty(v)))
5✔
263
                return 0;
264

265
        _cleanup_free_ char *joined = strv_join(f->gpt_label, "', '");
×
266
        return log_error_errno(
×
267
                        SYNTHETIC_ERRNO(EPERM),
268
                        "File system is supposed to be placed in a partition with labels '%s' only, but is placed in one labelled '%s', refusing.",
269
                        strna(joined), strempty(v));
270
}
271

272
static int validate_gpt_type(blkid_probe b, const ValidateFields *f) {
5✔
273
        assert(b);
5✔
274
        assert(f);
5✔
275

276
        if (f->n_gpt_type_uuid == 0)
5✔
277
                return 0;
5✔
278

279
        const char *v = NULL;
5✔
280
        (void) sym_blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, /* len= */ NULL);
5✔
281

282
        sd_id128_t id;
5✔
283
        if (!v || sd_id128_from_string(v, &id) < 0) {
5✔
284
                _cleanup_free_ char *joined = validate_fields_gpt_type_uuid_as_string(f);
×
285
                return log_error_errno(
×
286
                                SYNTHETIC_ERRNO(EPERM),
287
                                "File system is supposed to be placed in a partition of type UUIDs %s only, but has no type, refusing.",
288
                                strna(joined));
289
        }
290

291
        FOREACH_ARRAY(u, f->gpt_type_uuid, f->n_gpt_type_uuid)
6✔
292
                if (sd_id128_equal(*u, id))
6✔
293
                        return 0;
5✔
294

295
        _cleanup_free_ char *joined = validate_fields_gpt_type_uuid_as_string(f);
×
296
        return log_error_errno(
×
297
                        SYNTHETIC_ERRNO(EPERM),
298
                        "File system is supposed to be placed in a partition of type UUIDs %s only, but has type '%s', refusing.",
299
                        strna(joined), SD_ID128_TO_UUID_STRING(id));
300
}
301

302
static int validate_gpt_metadata_one(sd_device *d, const char *path, const ValidateFields *f) {
5✔
303
        int r;
5✔
304

305
        assert(d);
5✔
306
        assert(f);
5✔
307

308
        r = dlopen_libblkid();
5✔
309
        if (r < 0)
5✔
NEW
310
                return log_error_errno(r, "Cannot validate GPT constraints, refusing.");
×
311

312
        _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
10✔
313
        if (block_fd < 0)
5✔
314
                return log_error_errno(block_fd, "Failed to open block device backing '%s': %m", path);
×
315

316
        _cleanup_(blkid_free_probep) blkid_probe b = sym_blkid_new_probe();
10✔
317
        if (!b)
5✔
318
                return log_oom();
×
319

320
        errno = 0;
5✔
321
        r = sym_blkid_probe_set_device(b, block_fd, 0, 0);
5✔
322
        if (r != 0)
5✔
323
                return log_error_errno(errno_or_else(ENOMEM), "Failed to set up block device prober for '%s': %m", path);
×
324

325
        (void) sym_blkid_probe_enable_superblocks(b, 1);
5✔
326
        (void) sym_blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_LABEL);
5✔
327
        (void) sym_blkid_probe_enable_partitions(b, 1);
5✔
328
        (void) sym_blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
5✔
329

330
        errno = 0;
5✔
331
        r = sym_blkid_do_safeprobe(b);
5✔
332
        if (r == _BLKID_SAFEPROBE_ERROR)
5✔
333
                return log_error_errno(errno_or_else(EIO), "Failed to probe block device of '%s': %m", path);
×
334
        if (r == _BLKID_SAFEPROBE_AMBIGUOUS)
5✔
335
                return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found multiple file system labels on block device '%s'.", path);
×
336
        if (r == _BLKID_SAFEPROBE_NOT_FOUND)
5✔
337
                return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found no file system label on block device '%s'.", path);
×
338

339
        assert(r == _BLKID_SAFEPROBE_FOUND);
5✔
340

341
        const char *v = NULL;
5✔
342
        (void) sym_blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, /* len= */ NULL);
5✔
343
        if (!streq_ptr(v, "gpt"))
5✔
344
                return log_error_errno(SYNTHETIC_ERRNO(EPERM), "File system is supposed to be on a GPT partition table, but is not, refusing.");
×
345

346
        r = validate_gpt_label(b, f);
5✔
347
        if (r < 0)
5✔
348
                return r;
349

350
        r = validate_gpt_type(b, f);
5✔
351
        if (r < 0)
5✔
352
                return r;
×
353

354
        return 0;
355
}
356

357
static int validate_gpt_metadata(int fd, const char *path, const ValidateFields *f) {
5✔
358
        int r;
5✔
359

360
        assert(fd >= 0);
5✔
361
        assert(path);
5✔
362
        assert(f);
5✔
363

364
        if (strv_isempty(f->gpt_label) && f->n_gpt_type_uuid == 0)
5✔
365
                return 0;
5✔
366

367
        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
5✔
368
        r = block_device_new_from_fd(fd, BLOCK_DEVICE_LOOKUP_BACKING, &d);
4✔
369
        if (r < 0)
4✔
370
                return log_error_errno(r, "Failed to find block device backing '%s': %m", path);
×
371

372
        /* Now validate all subordinate devices individually. */
373
        bool have_slaves = false;
4✔
374
        const char *suffix;
4✔
375
        FOREACH_DEVICE_CHILD_WITH_SUFFIX(d, child, suffix) {
11✔
376
                if (!path_startswith(suffix, "slaves"))
7✔
377
                        continue;
5✔
378

379
                have_slaves = true;
2✔
380

381
                r = validate_gpt_metadata_one(child, path, f);
2✔
382
                if (r < 0)
2✔
383
                        return r;
384
        }
385

386
        /* If this device has no subordinate devices, then validate the device itself instead */
387
        if (!have_slaves) {
4✔
388
                r = validate_gpt_metadata_one(d, path, f);
3✔
389
                if (r < 0)
3✔
390
                        return r;
×
391
        }
392

393
        return 0;
394
}
395

396
static int validate_fields_check(int fd, const char *path, const ValidateFields *f) {
9✔
397
        int r;
9✔
398

399
        assert(fd >= 0);
9✔
400
        assert(path);
9✔
401
        assert(f);
9✔
402

403
        r = validate_mount_point(path, f);
9✔
404
        if (r < 0)
9✔
405
                return r;
406

407
        r = validate_gpt_metadata(fd, path, f);
5✔
408
        if (r < 0)
5✔
409
                return r;
410

411
        log_info("File system '%s' passed validation constraints, proceeding.", path);
5✔
412
        return 0;
413
}
414

415
static int run(int argc, char *argv[]) {
12✔
416
        int r;
12✔
417

418
        log_setup();
12✔
419

420
        r = parse_argv(argc, argv);
12✔
421
        if (r <= 0)
12✔
422
                return r;
12✔
423

424
        _cleanup_free_ char *resolved = NULL;
12✔
425
        _cleanup_close_ int target_fd = chase_and_open(arg_target, arg_root, CHASE_MUST_BE_DIRECTORY, O_DIRECTORY|O_CLOEXEC, &resolved);
24✔
426
        if (target_fd < 0)
12✔
427
                return log_error_errno(target_fd, "Failed to open directory '%s': %m", arg_target);
×
428

429
        r = is_mount_point_at(target_fd, /* filename= */ NULL, /* flags= */ 0);
12✔
430
        if (r < 0)
12✔
431
                return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", resolved);
×
432
        if (!r)
12✔
433
                return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Directory '%s' is not a mount point.", resolved);
×
434

435
        _cleanup_(validate_fields_done) ValidateFields f = {};
12✔
436
        r = validate_fields_read(target_fd, &f);
12✔
437
        if (r < 0)
12✔
438
                return r;
439
        if (r == 0) {
12✔
440
                log_info("File system '%s' has no validation constraints set, not validating.", resolved);
3✔
441
                return 0;
3✔
442
        }
443

444
        return validate_fields_check(target_fd, resolved, &f);
9✔
445
}
446

447
DEFINE_MAIN_FUNCTION(run);
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc