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

systemd / systemd / 23990547145

04 Apr 2026 09:30PM UTC coverage: 72.373% (+0.3%) from 72.107%
23990547145

push

github

web-flow
shutdown: enforce a minimum uptime to make boot loops less annoying (#41215)

Fixes: #9453

Split out of #41016

3 of 39 new or added lines in 2 files covered. (7.69%)

2565 existing lines in 66 files now uncovered.

319531 of 441505 relevant lines covered (72.37%)

1187721.39 hits per line

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

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

3
#include "sd-device.h"
4

5
#include "alloc-util.h"
6
#include "blkid-util.h"
7
#include "blockdev-util.h"
8
#include "build.h"
9
#include "chase.h"
10
#include "device-util.h"
11
#include "errno-util.h"
12
#include "fd-util.h"
13
#include "format-table.h"
14
#include "gpt.h"
15
#include "initrd-util.h"
16
#include "log.h"
17
#include "main-func.h"
18
#include "mountpoint-util.h"
19
#include "options.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
        _cleanup_free_ char *link = NULL;
×
36
        _cleanup_(table_unrefp) Table *options = NULL;
×
37
        int r;
×
38

39
        r = terminal_urlify_man("systemd-validatefs@.service", "8", &link);
×
40
        if (r < 0)
×
41
                return log_oom();
×
42

43
        r = option_parser_get_help_table(&options);
×
44
        if (r < 0)
×
45
                return r;
46

47
        printf("%s [OPTIONS...] /path/to/mountpoint\n"
×
48
               "\n%sCheck file system validation constraints.%s\n"
49
               "\nOptions:\n",
50
               program_invocation_short_name,
51
               ansi_highlight(),
52
               ansi_normal());
53
        r = table_print_or_warn(options);
×
UNCOV
54
        if (r < 0)
×
55
                return r;
56

UNCOV
57
        printf("\nSee the %s for details.\n", link);
×
58
        return 0;
59
}
60

61
static int parse_argv(int argc, char *argv[]) {
12✔
62
        int r;
12✔
63

64
        assert(argc >= 0);
12✔
65
        assert(argv);
12✔
66

67
        OptionParser state = { argc, argv };
12✔
68
        const char *arg;
12✔
69

70
        FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
29✔
71
                switch (c) {
5✔
UNCOV
72
                OPTION_COMMON_HELP:
×
73
                        return help();
×
74

UNCOV
75
                OPTION_COMMON_VERSION:
×
UNCOV
76
                        return version();
×
77

78
                OPTION_LONG("root", "PATH|auto", "Operate relative to the specified path"):
5✔
79
                        if (streq(arg, "auto"))
5✔
UNCOV
80
                                r = free_and_strdup_warn(&arg_root, in_initrd() ? "/sysroot" : NULL);
×
81
                        else {
82
                                if (!path_is_absolute(arg))
5✔
UNCOV
83
                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
84
                                                               "--root= argument must be 'auto' or absolute path, got: %s", arg);
85

86
                                r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root);
5✔
87
                        }
88
                        if (r < 0)
5✔
89
                                return r;
90
                        break;
91
                }
92

93
        char **args = option_parser_get_args(&state);
12✔
94

95
        if (strv_length(args) != 1)
12✔
UNCOV
96
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
97
                                       "%s expects exactly one argument (the mount point).",
98
                                       program_invocation_short_name);
99

100
        arg_target = strdup(args[0]);
12✔
101
        if (!arg_target)
12✔
UNCOV
102
                return log_oom();
×
103

104
        if (arg_root && !path_startswith(arg_target, arg_root))
12✔
UNCOV
105
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
106
                                       "Specified path '%s' does not start with specified root '%s', refusing.",
107
                                       arg_target, arg_root);
108
        return 1;
109
}
110

111
typedef struct ValidateFields {
112
        sd_id128_t *gpt_type_uuid;
113
        size_t n_gpt_type_uuid;
114
        char **gpt_label;
115
        char **mount_point;
116
} ValidateFields;
117

118
static void validate_fields_done(ValidateFields *f) {
24✔
119
        assert(f);
24✔
120

121
        free(f->gpt_type_uuid);
24✔
122
        strv_free(f->gpt_label);
24✔
123
        strv_free(f->mount_point);
24✔
124
}
24✔
125

UNCOV
126
static char* validate_fields_gpt_type_uuid_as_string(const ValidateFields *f) {
×
127
        _cleanup_free_ char *joined = NULL;
×
128

129
        assert(f);
×
130

131
        FOREACH_ARRAY(u, f->gpt_type_uuid, f->n_gpt_type_uuid) {
×
UNCOV
132
                if (!strextend_with_separator(&joined, ", ", SD_ID128_TO_UUID_STRING(*u)))
×
133
                        return NULL;
×
134

135
                const char *id = gpt_partition_type_uuid_to_string(*u);
×
UNCOV
136
                if (id)
×
UNCOV
137
                        (void) strextend(&joined, " (", id, ")");
×
138
        }
139

UNCOV
140
        return TAKE_PTR(joined);
×
141
}
142

143
static int validate_fields_read(int fd, ValidateFields *ret) {
12✔
144
        _cleanup_(validate_fields_done) ValidateFields f = {};
12✔
145
        int r;
12✔
146

147
        assert(fd >= 0);
12✔
148
        assert(ret);
12✔
149

150
        _cleanup_strv_free_ char **l = NULL;
12✔
151
        r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.gpt_type_uuid", AT_EMPTY_PATH, &l);
12✔
152
        if (r < 0) {
12✔
153
                if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
5✔
UNCOV
154
                        return log_error_errno(r, "Failed to read 'user.validatefs.gpt_type_uuid' xattr: %m");
×
155
        } else {
156
                STRV_FOREACH(i, l) {
16✔
157
                        if (!GREEDY_REALLOC(f.gpt_type_uuid, f.n_gpt_type_uuid+1))
9✔
UNCOV
158
                                return log_oom();
×
159

160
                        r = sd_id128_from_string(*i, f.gpt_type_uuid + f.n_gpt_type_uuid);
9✔
161
                        if (r < 0)
9✔
UNCOV
162
                                return log_error_errno(r, "Failed to parse 'user.validatefs.gpt_type_uuid' xattr: %s", *i);
×
163

164
                        f.n_gpt_type_uuid++;
9✔
165
                }
166
        }
167

168
        l = strv_free(l);
12✔
169
        r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.gpt_label", AT_EMPTY_PATH, &l);
12✔
170
        if (r < 0) {
12✔
171
                if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
5✔
UNCOV
172
                        return log_error_errno(r, "Failed to read 'user.validatefs.gpt_label' xattr: %m");
×
173
        } else {
174
                STRV_FOREACH(i, l)
16✔
175
                        if (!utf8_is_valid(*i) ||
18✔
176
                            string_has_cc(*i, /* ok= */ NULL))
9✔
UNCOV
177
                                return log_error_errno(
×
178
                                                SYNTHETIC_ERRNO(EINVAL),
179
                                                "Extended attribute 'user.validatefs.gpt_label' contains invalid characters, refusing: %s", *i);
180

181
                f.gpt_label = TAKE_PTR(l);
7✔
182
        }
183

184
        l = strv_free(l);
12✔
185
        r = getxattr_at_strv(fd, /* path= */ NULL, "user.validatefs.mount_point", AT_EMPTY_PATH, &l);
12✔
186
        if (r < 0) {
12✔
187
                if (r != -ENODATA && !ERRNO_IS_NOT_SUPPORTED(r))
3✔
UNCOV
188
                        return log_error_errno(r, "Failed to read 'user.validatefs.mount_point' xattr: %m");
×
189
        } else {
190
                STRV_FOREACH(i, l)
23✔
191
                        if (!utf8_is_valid(*i) ||
28✔
192
                            string_has_cc(*i, /* ok= */ NULL) ||
28✔
193
                            !path_is_absolute(*i) ||
28✔
194
                            !path_is_normalized(*i))
14✔
UNCOV
195
                                return log_error_errno(
×
196
                                                SYNTHETIC_ERRNO(EINVAL),
197
                                                "Path listed in extended attribute 'user.validatefs.mount_point' is not a valid, normalized, absolute path or contains invalid characters, refusing: %s", *i);
198

199
                f.mount_point = TAKE_PTR(l);
9✔
200
        }
201

202
        r = f.n_gpt_type_uuid > 0 || !strv_isempty(f.gpt_label) || !strv_isempty(f.mount_point);
12✔
203
        *ret = TAKE_STRUCT(f);
12✔
204
        return r;
12✔
205
}
206

207
static int validate_mount_point(const char *path, const ValidateFields *f) {
9✔
208
        assert(path);
9✔
209
        assert(f);
9✔
210

211
        if (strv_isempty(f->mount_point))
9✔
212
                return 0;
9✔
213

214
        STRV_FOREACH(i, f->mount_point) {
17✔
215
                _cleanup_free_ char *jj = NULL;
13✔
216
                const char *j;
13✔
217

218
                if (arg_root) {
13✔
219
                        jj = path_join(arg_root, *i);
4✔
220
                        if (!jj)
4✔
UNCOV
221
                                return log_oom();
×
222

223
                        j = jj;
224
                } else
225
                        j = *i;
226

227
                if (path_equal(path, j))
13✔
228
                        return 0;
229
        }
230

231
        _cleanup_free_ char *joined = strv_join(f->mount_point, ", ");
8✔
232
        return log_error_errno(
4✔
233
                        SYNTHETIC_ERRNO(EPERM),
234
                        "File system is supposed to be mounted on one of %s only, but is mounted on %s, refusing.",
235
                        strna(joined), path);
236
}
237

238
static int validate_gpt_label(blkid_probe b, const ValidateFields *f) {
5✔
239
        assert(b);
5✔
240
        assert(f);
5✔
241

242
        if (strv_isempty(f->gpt_label))
5✔
243
                return 0;
5✔
244

245
        const char *v = NULL;
5✔
246
        (void) sym_blkid_probe_lookup_value(b, "PART_ENTRY_NAME", &v, /* len= */ NULL);
5✔
247

248
        if (strv_contains(f->gpt_label, strempty(v)))
5✔
249
                return 0;
250

UNCOV
251
        _cleanup_free_ char *joined = strv_join(f->gpt_label, "', '");
×
UNCOV
252
        return log_error_errno(
×
253
                        SYNTHETIC_ERRNO(EPERM),
254
                        "File system is supposed to be placed in a partition with labels '%s' only, but is placed in one labelled '%s', refusing.",
255
                        strna(joined), strempty(v));
256
}
257

258
static int validate_gpt_type(blkid_probe b, const ValidateFields *f) {
5✔
259
        assert(b);
5✔
260
        assert(f);
5✔
261

262
        if (f->n_gpt_type_uuid == 0)
5✔
263
                return 0;
5✔
264

265
        const char *v = NULL;
5✔
266
        (void) sym_blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, /* len= */ NULL);
5✔
267

268
        sd_id128_t id;
5✔
269
        if (!v || sd_id128_from_string(v, &id) < 0) {
5✔
UNCOV
270
                _cleanup_free_ char *joined = validate_fields_gpt_type_uuid_as_string(f);
×
UNCOV
271
                return log_error_errno(
×
272
                                SYNTHETIC_ERRNO(EPERM),
273
                                "File system is supposed to be placed in a partition of type UUIDs %s only, but has no type, refusing.",
274
                                strna(joined));
275
        }
276

277
        FOREACH_ARRAY(u, f->gpt_type_uuid, f->n_gpt_type_uuid)
6✔
278
                if (sd_id128_equal(*u, id))
6✔
279
                        return 0;
5✔
280

UNCOV
281
        _cleanup_free_ char *joined = validate_fields_gpt_type_uuid_as_string(f);
×
UNCOV
282
        return log_error_errno(
×
283
                        SYNTHETIC_ERRNO(EPERM),
284
                        "File system is supposed to be placed in a partition of type UUIDs %s only, but has type '%s', refusing.",
285
                        strna(joined), SD_ID128_TO_UUID_STRING(id));
286
}
287

288
static int validate_gpt_metadata_one(sd_device *d, const char *path, const ValidateFields *f) {
5✔
289
        int r;
5✔
290

291
        assert(d);
5✔
292
        assert(f);
5✔
293

294
        r = dlopen_libblkid();
5✔
295
        if (r < 0)
5✔
UNCOV
296
                return log_error_errno(r, "Cannot validate GPT constraints, refusing.");
×
297

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

302
        _cleanup_(blkid_free_probep) blkid_probe b = sym_blkid_new_probe();
10✔
303
        if (!b)
5✔
UNCOV
304
                return log_oom();
×
305

306
        errno = 0;
5✔
307
        r = sym_blkid_probe_set_device(b, block_fd, 0, 0);
5✔
308
        if (r != 0)
5✔
UNCOV
309
                return log_error_errno(errno_or_else(ENOMEM), "Failed to set up block device prober for '%s': %m", path);
×
310

311
        (void) sym_blkid_probe_enable_superblocks(b, 1);
5✔
312
        (void) sym_blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_LABEL);
5✔
313
        (void) sym_blkid_probe_enable_partitions(b, 1);
5✔
314
        (void) sym_blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
5✔
315

316
        errno = 0;
5✔
317
        r = sym_blkid_do_safeprobe(b);
5✔
318
        if (r == _BLKID_SAFEPROBE_ERROR)
5✔
319
                return log_error_errno(errno_or_else(EIO), "Failed to probe block device of '%s': %m", path);
×
320
        if (r == _BLKID_SAFEPROBE_AMBIGUOUS)
5✔
321
                return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found multiple file system labels on block device '%s'.", path);
×
322
        if (r == _BLKID_SAFEPROBE_NOT_FOUND)
5✔
UNCOV
323
                return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Found no file system label on block device '%s'.", path);
×
324

325
        assert(r == _BLKID_SAFEPROBE_FOUND);
5✔
326

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

332
        r = validate_gpt_label(b, f);
5✔
333
        if (r < 0)
5✔
334
                return r;
335

336
        r = validate_gpt_type(b, f);
5✔
337
        if (r < 0)
5✔
UNCOV
338
                return r;
×
339

340
        return 0;
341
}
342

343
static int validate_gpt_metadata(int fd, const char *path, const ValidateFields *f) {
5✔
344
        int r;
5✔
345

346
        assert(fd >= 0);
5✔
347
        assert(path);
5✔
348
        assert(f);
5✔
349

350
        if (strv_isempty(f->gpt_label) && f->n_gpt_type_uuid == 0)
5✔
351
                return 0;
5✔
352

353
        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
5✔
354
        r = block_device_new_from_fd(fd, BLOCK_DEVICE_LOOKUP_BACKING, &d);
4✔
355
        if (r < 0)
4✔
UNCOV
356
                return log_error_errno(r, "Failed to find block device backing '%s': %m", path);
×
357

358
        /* Now validate all subordinate devices individually. */
359
        bool have_slaves = false;
4✔
360
        const char *suffix;
4✔
361
        FOREACH_DEVICE_CHILD_WITH_SUFFIX(d, child, suffix) {
11✔
362
                if (!path_startswith(suffix, "slaves"))
7✔
363
                        continue;
5✔
364

365
                have_slaves = true;
2✔
366

367
                r = validate_gpt_metadata_one(child, path, f);
2✔
368
                if (r < 0)
2✔
369
                        return r;
370
        }
371

372
        /* If this device has no subordinate devices, then validate the device itself instead */
373
        if (!have_slaves) {
4✔
374
                r = validate_gpt_metadata_one(d, path, f);
3✔
375
                if (r < 0)
3✔
UNCOV
376
                        return r;
×
377
        }
378

379
        return 0;
380
}
381

382
static int validate_fields_check(int fd, const char *path, const ValidateFields *f) {
9✔
383
        int r;
9✔
384

385
        assert(fd >= 0);
9✔
386
        assert(path);
9✔
387
        assert(f);
9✔
388

389
        r = validate_mount_point(path, f);
9✔
390
        if (r < 0)
9✔
391
                return r;
392

393
        r = validate_gpt_metadata(fd, path, f);
5✔
394
        if (r < 0)
5✔
395
                return r;
396

397
        log_info("File system '%s' passed validation constraints, proceeding.", path);
5✔
398
        return 0;
399
}
400

401
static int run(int argc, char *argv[]) {
12✔
402
        int r;
12✔
403

404
        log_setup();
12✔
405

406
        r = parse_argv(argc, argv);
12✔
407
        if (r <= 0)
12✔
408
                return r;
12✔
409

410
        _cleanup_free_ char *resolved = NULL;
12✔
411
        _cleanup_close_ int target_fd = chase_and_open(arg_target, arg_root, CHASE_MUST_BE_DIRECTORY, O_DIRECTORY|O_CLOEXEC, &resolved);
24✔
412
        if (target_fd < 0)
12✔
UNCOV
413
                return log_error_errno(target_fd, "Failed to open directory '%s': %m", arg_target);
×
414

415
        r = is_mount_point_at(target_fd, /* path= */ NULL, /* flags= */ 0);
12✔
416
        if (r < 0)
12✔
417
                return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", resolved);
×
418
        if (!r)
12✔
UNCOV
419
                return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Directory '%s' is not a mount point.", resolved);
×
420

421
        _cleanup_(validate_fields_done) ValidateFields f = {};
12✔
422
        r = validate_fields_read(target_fd, &f);
12✔
423
        if (r < 0)
12✔
424
                return r;
425
        if (r == 0) {
12✔
426
                log_info("File system '%s' has no validation constraints set, not validating.", resolved);
3✔
427
                return 0;
3✔
428
        }
429

430
        return validate_fields_check(target_fd, resolved, &f);
9✔
431
}
432

433
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