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

systemd / systemd / 29132483780

10 Jul 2026 08:43PM UTC coverage: 72.912% (+0.2%) from 72.702%
29132483780

push

github

bluca
man: run forgotten 'update-man-rules'

344600 of 472622 relevant lines covered (72.91%)

1365091.83 hits per line

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

43.84
/src/mstack/mstack-tool.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <unistd.h>
5

6
#include "argv-util.h"
7
#include "build.h"
8
#include "chase.h"
9
#include "dlopen-note.h"
10
#include "errno-util.h"
11
#include "extract-word.h"
12
#include "fd-util.h"
13
#include "format-table.h"
14
#include "help-util.h"
15
#include "image-policy.h"
16
#include "main-func.h"
17
#include "mount-util.h"
18
#include "mountpoint-util.h"
19
#include "mstack.h"
20
#include "options.h"
21
#include "parse-argument.h"
22
#include "string-util.h"
23

24
static enum {
25
        ACTION_INSPECT,
26
        ACTION_MOUNT,
27
        ACTION_UMOUNT,
28
} arg_action = ACTION_INSPECT;
29
static char *arg_what = NULL;
30
static char *arg_where = NULL;
31
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
32
static PagerFlags arg_pager_flags = 0;
33
static int arg_legend = true;
34
static MStackFlags arg_mstack_flags = 0;
35
static bool arg_rmdir = false;
36
static ImagePolicy *arg_image_policy = NULL;
37
static ImageFilter *arg_image_filter = NULL;
38

39
STATIC_DESTRUCTOR_REGISTER(arg_what, freep);
3✔
40
STATIC_DESTRUCTOR_REGISTER(arg_where, freep);
3✔
41
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
3✔
42
STATIC_DESTRUCTOR_REGISTER(arg_image_filter, image_filter_freep);
3✔
43

44
static int help(void) {
×
45
        _cleanup_(table_unrefp) Table *options = NULL, *commands = NULL;
×
46
        int r;
×
47

48
        r = option_parser_get_help_table_ns("systemd-mstack", &options);
×
49
        if (r < 0)
×
50
                return r;
51

52
        r = option_parser_get_help_table_full("systemd-mstack", "Commands", &commands);
×
53
        if (r < 0)
×
54
                return r;
55

56
        (void) table_sync_column_widths(0, options, commands);
×
57

58
        help_cmdline("[OPTIONS...] WHAT");
×
59
        help_cmdline("[OPTIONS...] --mount WHAT WHERE");
×
60
        help_cmdline("[OPTIONS...] --umount WHERE");
×
61
        help_abstract("Inspect or apply mount stack.");
×
62

63
        help_section("Commands");
×
64
        r = table_print_or_warn(commands);
×
65
        if (r < 0)
×
66
                return r;
67

68
        help_section("Options");
×
69
        r = table_print_or_warn(options);
×
70
        if (r < 0)
×
71
                return r;
72

73
        help_man_page_reference("systemd-mstack", "1");
×
74
        return 0;
75
}
76

77
static int parse_argv(int argc, char *argv[]) {
7✔
78
        int r;
7✔
79

80
        assert(argc >= 0);
7✔
81
        assert(argv);
7✔
82

83
        OptionParser opts = { argc, argv, .namespace = "systemd-mstack" };
7✔
84

85
        FOREACH_OPTION_OR_RETURN(c, &opts)
32✔
86
                switch (c) {
11✔
87

88
                OPTION_NAMESPACE("systemd-mstack"): {}
5✔
89

90
                OPTION('r', "read-only", NULL, "Mount read-only"):
5✔
91
                        arg_mstack_flags |= MSTACK_RDONLY;
5✔
92
                        break;
5✔
93

94
                OPTION_LONG("mkdir", NULL, "Make mount directory before mounting, if missing"):
×
95
                        arg_mstack_flags |= MSTACK_MKDIR;
×
96
                        break;
×
97

98
                OPTION_LONG("rmdir", NULL, "Remove mount directory after unmounting"):
×
99
                        arg_rmdir = true;
×
100
                        break;
×
101

102
                OPTION_LONG("image-policy", "POLICY", "Specify image dissection policy"):
×
103
                        r = parse_image_policy_argument(opts.arg, &arg_image_policy);
×
104
                        if (r < 0)
×
105
                                return r;
106
                        break;
107

108
                OPTION_LONG("image-filter", "FILTER", "Specify image dissection filter"): {
×
109
                        _cleanup_(image_filter_freep) ImageFilter *f = NULL;
×
110
                        r = image_filter_parse(opts.arg, &f);
×
111
                        if (r < 0)
×
112
                                return log_error_errno(r, "Failed to parse image filter expression: %s", opts.arg);
×
113

114
                        image_filter_free(arg_image_filter);
×
115
                        arg_image_filter = TAKE_PTR(f);
×
116
                        break;
×
117
                }
118

119
                OPTION_COMMON_NO_PAGER:
×
120
                        arg_pager_flags |= PAGER_DISABLE;
×
121
                        break;
×
122

123
                OPTION_COMMON_NO_LEGEND:
×
124
                        arg_legend = false;
×
125
                        break;
×
126

127
                OPTION_COMMON_JSON:
×
128
                        r = parse_json_argument(opts.arg, &arg_json_format_flags);
×
129
                        if (r <= 0)
×
130
                                return r;
131
                        break;
132

133
                OPTION_GROUP("Commands"): {}
×
134

135
                OPTION_COMMON_HELP:
×
136
                        return help();
×
137

138
                OPTION_COMMON_VERSION:
×
139
                        return version();
×
140

141
                OPTION('m', "mount", NULL, "Mount the mstack to the specified directory"):
×
142
                        arg_action = ACTION_MOUNT;
×
143
                        break;
×
144

145
                OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"):
5✔
146
                        arg_action = ACTION_MOUNT;
5✔
147
                        arg_mstack_flags |= MSTACK_MKDIR;
5✔
148
                        break;
5✔
149

150
                OPTION('u', "umount", NULL, "Unmount the image from the specified directory"):
×
151
                        arg_action = ACTION_UMOUNT;
×
152
                        break;
×
153

154
                OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"):
1✔
155
                        arg_action = ACTION_UMOUNT;
1✔
156
                        arg_rmdir = true;
1✔
157
                        break;
1✔
158
                }
159

160
        char **args = option_parser_get_args(&opts);
7✔
161
        size_t n_args = option_parser_get_n_args(&opts);
7✔
162

163
        switch (arg_action) {
7✔
164

165
        case ACTION_INSPECT:
1✔
166
                if (n_args != 1)
1✔
167
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one argument.");
×
168

169
                r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what);
1✔
170
                if (r < 0)
1✔
171
                        return r;
×
172

173
                break;
174

175
        case ACTION_MOUNT:
5✔
176
                if (n_args != 2)
5✔
177
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments.");
×
178

179
                r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what);
5✔
180
                if (r < 0)
5✔
181
                        return r;
182

183
                r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_where);
5✔
184
                if (r < 0)
5✔
185
                        return r;
×
186

187
                break;
188

189
        case ACTION_UMOUNT:
1✔
190
                if (n_args != 1)
1✔
191
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one argument.");
×
192

193
                r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_where);
1✔
194
                if (r < 0)
1✔
195
                        return r;
×
196

197
                break;
198

199
        default:
×
200
                assert_not_reached();
×
201
        }
202

203
        return 1;
204
}
205

206
static int parse_argv_as_mount_helper(int argc, char *argv[]) {
×
207
        const char *mount_options = NULL;
×
208
        bool fake = false;
×
209
        int r;
×
210

211
        /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */
212

213
        OptionParser opts = { argc, argv, .namespace = "mount.mstack" };
×
214

215
        FOREACH_OPTION_OR_RETURN(c, &opts)
×
216
                switch (c) {
×
217

218
                OPTION_NAMESPACE("mount.mstack"): {}
×
219

220
                OPTION_SHORT('f', NULL, NULL):
×
221
                        fake = true;
×
222
                        break;
×
223

224
                OPTION_SHORT('o', "OPTIONS", NULL):
×
225
                        mount_options = opts.arg;
×
226
                        break;
×
227

228
                OPTION_SHORT('t', "TYPE", NULL):
×
229
                        if (!streq(opts.arg, "mstack"))
×
230
                                log_debug("Unexpected file system type '%s', ignoring.", opts.arg);
×
231
                        break;
232

233
                OPTION_SHORT('s', NULL, NULL): {} /* sloppy mount options, fall-through */
×
234
                OPTION_SHORT('n', NULL, NULL): {} /* aka --no-mtab, fall-through */
×
235
                OPTION_SHORT('v', NULL, NULL):    /* aka --verbose */
×
236
                        log_debug("Ignoring option -%c, not implemented.", opts.opt->short_code);
×
237
                        break;
238

239
                OPTION_SHORT('N', "NAMESPACE", NULL): /* aka --namespace= */
×
240
                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
×
241
                                               "Option -%c is not implemented, refusing.", opts.opt->short_code);
242
                }
243

244
        char **args = option_parser_get_args(&opts);
×
245
        if (option_parser_get_n_args(&opts) != 2)
×
246
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
247
                                       "Expected an image file path and target directory as arguments.");
248

249
        for (const char *p = mount_options;;) {
×
250
                _cleanup_free_ char *word = NULL;
×
251

252
                r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE);
×
253
                if (r < 0)
×
254
                        return log_error_errno(r, "Failed to extract mount option: %m");
×
255
                if (r == 0)
×
256
                        break;
257

258
                if (streq(word, "ro"))
×
259
                        SET_FLAG(arg_mstack_flags, MSTACK_RDONLY, true);
×
260
                else if (streq(word, "rw"))
×
261
                        SET_FLAG(arg_mstack_flags, MSTACK_RDONLY, false);
×
262
                else
263
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
264
                                               "Unknown mount option '%s'.", word);
265
        }
266

267
        if (fake)
×
268
                return 0;
269

270
        r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what);
×
271
        if (r < 0)
×
272
                return r;
273

274
        r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_where);
×
275
        if (r < 0)
×
276
                return r;
277

278
        arg_action = ACTION_MOUNT;
×
279
        return 1;
×
280
}
281

282
static int inspect_mstack(void) {
1✔
283
        _cleanup_(mstack_freep) MStack *mstack = NULL;
1✔
284
        int r;
1✔
285

286
        assert(arg_what);
1✔
287

288
        r = mstack_load(arg_what, /* dir_fd= */ -EBADF, &mstack);
1✔
289
        if (r < 0)
1✔
290
                return log_debug_errno(r, "Failed to load .mstack/ directory '%s': %m", arg_what);
×
291

292
        _cleanup_(table_unrefp) Table *t = NULL;
1✔
293

294
        t = table_new("type", "name", "image", "what", "where", "sort");
1✔
295
        if (!t)
1✔
296
                return log_oom();
×
297

298
        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
1✔
299

300
        FOREACH_ARRAY(m, mstack->mounts, mstack->n_mounts) {
4✔
301
                _cleanup_free_ char *w = NULL;
3✔
302
                r = fd_get_path(m->what_fd, &w);
3✔
303
                if (r < 0)
3✔
304
                        return log_error_errno(r, "Failed to get path of what file descriptor: %m");
×
305

306
                r = table_add_many(
6✔
307
                                t,
308
                                TABLE_STRING, mstack_mount_type_to_string(m->mount_type),
309
                                TABLE_STRING, m->what,
310
                                TABLE_STRING, image_type_to_string(m->image_type),
311
                                TABLE_PATH, w,
312
                                TABLE_PATH, m->where ?: ((mstack->root_mount && mstack->root_mount != m) ? "/usr" : "/"),
313
                                TABLE_STRING, m->sort_key);
314
                if (r < 0)
3✔
315
                        return table_log_add_error(r);
×
316
        }
317

318
        return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
1✔
319
}
320

321
static int mount_mstack(void) {
5✔
322
        int r;
5✔
323

324
        assert(arg_what);
5✔
325
        assert(arg_where);
5✔
326

327
        r = mstack_apply(
5✔
328
                        arg_what,
329
                        /* dir_fd= */ -EBADF,
330
                        arg_where,
331
                        /* temp_mount_dir= */ NULL,  /* auto-create temporary directory */
332
                        /* mountfsd_link= */ NULL,
333
                        /* userns_fd= */ -EBADF,
334
                        arg_image_policy,
335
                        arg_image_filter,
336
                        arg_mstack_flags,
337
                        /* ret_root_fd= */ NULL);
338
         if (r < 0)
1✔
339
                 return log_error_errno(r, "Failed to apply .mstack/ directory '%s': %m", arg_what);
×
340

341
         return 0;
342
}
343

344
static int umount_mstack(void) {
1✔
345
        int r;
1✔
346

347
        assert(arg_where);
1✔
348

349
        _cleanup_free_ char *canonical = NULL;
1✔
350
        _cleanup_close_ int fd = chase_and_open(arg_where, /* root= */ NULL, /* chase_flags= */ 0, O_DIRECTORY, &canonical);
2✔
351
        if (fd == -ENOTDIR)
1✔
352
                return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "'%s' is not a directory", arg_where);
×
353
        if (fd < 0)
1✔
354
                return log_error_errno(fd, "Failed to resolve path '%s': %m", arg_where);
×
355

356
        r = is_mount_point_at(fd, /* path= */ NULL, /* flags= */ 0);
1✔
357
        if (r == 0)
1✔
358
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'%s' is not a mount point", canonical);
×
359
        if (r < 0)
1✔
360
                return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", canonical);
×
361

362
        fd = safe_close(fd);
1✔
363

364
        r = umount_recursive(canonical, 0);
1✔
365
        if (r < 0)
1✔
366
                return log_error_errno(r, "Failed to unmount '%s': %m", canonical);
×
367

368
        if (arg_rmdir) {
1✔
369
                r = RET_NERRNO(rmdir(canonical));
1✔
370
                if (r < 0)
×
371
                        return log_error_errno(r, "Failed to remove mount directory '%s': %m", canonical);
×
372
        }
373

374
        return 0;
375
}
376

377
static int run(int argc, char *argv[]) {
7✔
378
        int r;
7✔
379

380
        LIBBLKID_NOTE(recommended);
7✔
381
        LIBCRYPTO_NOTE(suggested);
7✔
382
        LIBCRYPTSETUP_NOTE(suggested);
7✔
383
        LIBMOUNT_NOTE(recommended);
7✔
384

385
        log_setup();
7✔
386

387
        if (invoked_as(argv, "mount.mstack"))
7✔
388
                r = parse_argv_as_mount_helper(argc, argv);
×
389
        else
390
                r = parse_argv(argc, argv);
7✔
391
        if (r <= 0)
7✔
392
                return r;
393

394
        switch (arg_action) {
7✔
395

396
        case ACTION_INSPECT:
1✔
397
                return inspect_mstack();
1✔
398

399
        case ACTION_MOUNT:
5✔
400
                return mount_mstack();
5✔
401

402
        case ACTION_UMOUNT:
1✔
403
                return umount_mstack();
1✔
404

405
        default:
×
406
                assert_not_reached();
×
407
        }
408
}
409

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

© 2026 Coveralls, Inc