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

systemd / systemd / 25196166722

30 Apr 2026 07:30PM UTC coverage: 72.134% (+0.3%) from 71.849%
25196166722

push

github

bluca
mkosi: update debian commit reference to 1302f123d

* 1302f123d9 Restrict wildcard for new files
* a6d0098d10 Install new files for upstream build
* ce07fd7616 d/t/boot-and-services: use coreutils tunable in apparmor test (LP: #2125614)

324804 of 450278 relevant lines covered (72.13%)

1187716.32 hits per line

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

92.03
/src/shared/options.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include "format-table.h"
4
#include "log.h"
5
#include "options.h"
6
#include "stdio-util.h"
7
#include "string-util.h"
8
#include "strv.h"
9

10
static bool option_takes_arg(const Option *opt) {
125,673✔
11
        return ASSERT_PTR(opt)->metavar;
125,673✔
12
}
13

14
static bool option_arg_optional(const Option *opt) {
1,224✔
15
        return option_takes_arg(opt) && FLAGS_SET(opt->flags, OPTION_OPTIONAL_ARG);
1,224✔
16
}
17

18
static bool option_arg_required(const Option *opt) {
72,649✔
19
        return option_takes_arg(opt) && !FLAGS_SET(opt->flags, OPTION_OPTIONAL_ARG);
72,649✔
20
}
21

22
static bool option_is_metadata(const Option *opt) {
741,579✔
23
        /* A metadata entry that is not a real option, like the group marker */
24
        return ASSERT_PTR(opt)->flags & (OPTION_NAMESPACE_MARKER |
741,579✔
25
                                         OPTION_GROUP_MARKER |
26
                                         OPTION_POSITIONAL_ENTRY |
27
                                         OPTION_HELP_ENTRY |
28
                                         OPTION_HELP_ENTRY_VERBATIM);
29
}
30

31
static void shift_arg(char* argv[], int target, int source) {
154,484✔
32
        assert(argv);
154,484✔
33
        assert(target <= source);
154,484✔
34

35
        /* Move argv[source] before argv[target], shifting arguments inbetween */
36
        char *saved = argv[source];
154,484✔
37
        memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*));
154,484✔
38
        argv[target] = saved;
154,484✔
39
}
154,484✔
40

41
static int partial_match_error(
1✔
42
                const Option options[],
43
                const Option options_end[],
44
                const char *optname,
45
                unsigned n_partial_matches) {
46
        int r;
1✔
47

48
        assert(startswith(ASSERT_PTR(optname), "--"));
1✔
49
        assert(n_partial_matches >= 2);
1✔
50

51
        /* Find options that match the prefix */
52
        _cleanup_strv_free_ char **s = NULL;
1✔
53
        for (const Option* option = options; option < options_end; option++)
7✔
54
                if (!option_is_metadata(option) &&
6✔
55
                    option->long_code &&
10✔
56
                    startswith(option->long_code, optname + 2)) {
5✔
57

58
                        r = strv_extendf(&s, "--%s", option->long_code);
2✔
59
                        if (r < 0)
2✔
60
                                return log_error_errno(r, "Failed to format message: %m");
×
61
                }
62

63
        assert(strv_length(s) == n_partial_matches);
1✔
64

65
        _cleanup_free_ char *p = strv_join_full(s, ", ", /* prefix= */ NULL, /* escape_separator= */ false);
2✔
66
        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1✔
67
                               "%s: option '%s' is ambiguous; possibilities: %s",
68
                               program_invocation_short_name, optname, strnull(p));
69
}
70

71
int option_parse(
201,682✔
72
                const Option options[],
73
                const Option options_end[],
74
                OptionParser *state) {
75

76
        /* We define this one early, since we use goto below, and need to guarantee its initialization */
77
        _cleanup_free_ char *_optname = NULL;  /* allocated option name */
201,682✔
78
        int r;
201,682✔
79

80
        assert(state);
201,682✔
81

82
        /* Check and initialize */
83
        switch (state->state) {
201,682✔
84

85
        case OPTION_PARSER_INIT: {
102,948✔
86
                assert(state->mode >= 0 && state->mode < _OPTION_PARSER_MODE_MAX);
102,948✔
87

88
                if (state->argc < 1) {
102,948✔
89
                        r = log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty");
×
90
                        goto fail;
×
91
                }
92

93
                assert_se((size_t) state->argc == strv_length(state->argv)); /* Make sure argc/argv are consistent */
102,948✔
94

95
                /* Figure out the right range of options */
96
                bool in_ns = state->namespace == NULL;  /* Are we currently in the section of the array that
102,948✔
97
                                                         * forms namespace <namespace>? The first part is the
98
                                                         * default unnamed namespace, so if the namespace was
99
                                                         * not specified, we are in it. */
100
                if (in_ns)
102,948✔
101
                        state->namespace_start = options;
23,795✔
102

103
                const Option *opt;
102,948✔
104

105
                /* Verify that the option array didn't get mangled within a namespace. */
106
                for (opt = options; opt < options_end; opt++)
13,700,257✔
107
                        if (opt + 1 < options_end && !FLAGS_SET((opt + 1)->flags, OPTION_NAMESPACE_MARKER))
13,597,309✔
108
                                assert_se(opt->id < (opt + 1)->id);
12,328,062✔
109

110
                for (opt = options; opt < options_end; opt++) {
11,475,797✔
111
                        bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER);
11,422,039✔
112
                        if (!in_ns) {
11,422,039✔
113
                                in_ns = ns_marker && streq(state->namespace, opt->long_code);
10,209,693✔
114
                                if (in_ns)
79,153✔
115
                                        state->namespace_start = opt + 1;
79,153✔
116
                                continue;
10,209,693✔
117
                        }
118
                        if (ns_marker)
1,212,346✔
119
                                break;  /* End of namespace */
120
                }
121
                assert(state->namespace_start);
102,948✔
122
                state->namespace_end = opt;
102,948✔
123

124
                state->optind = state->positional_offset = 1;
102,948✔
125
                state->state = OPTION_PARSER_RUNNING;
102,948✔
126
                break;
102,948✔
127
        }
128

129
        case OPTION_PARSER_RUNNING:
130
        case OPTION_PARSER_STOPPING:
131
                break;
132

133
        case OPTION_PARSER_DONE:
×
134
                goto done;
×
135

136
        case OPTION_PARSER_FAILED:
137
                return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Option parser failed before, refusing.");
×
138

139
        default:
×
140
                assert_not_reached();
×
141
        }
142

143
        /* Look for the next option */
144

145
        const Option *option = NULL;  /* initialization to appease gcc 13 */
201,682✔
146
        const char *optname = NULL, *optval = NULL;
201,682✔
147
        bool separate_optval = false;
201,682✔
148
        bool handling_positional_arg = false;
201,682✔
149

150
        if (state->short_option_offset == 0) {
201,682✔
151
                /* Handle non-option parameters */
152
                for (;;) {
262,071✔
153
                        if (state->optind == state->argc)
231,859✔
154
                                goto done;
82,398✔
155

156
                        if (streq(state->argv[state->optind], "--")) {
149,461✔
157
                                /* No more options. Move "--" before positional args so that
158
                                 * the list of positional args is clean. */
159
                                shift_arg(state->argv, state->positional_offset++, state->optind++);
151✔
160
                                goto done;
151✔
161
                        }
162

163
                        /* If we are in OPTION_PARSER_STOPPING state we only wanted to read one more "--" if
164
                         * there is one, nothing else, hence it's time to say goodbye now. */
165
                        if (state->state == OPTION_PARSER_STOPPING)
149,310✔
166
                                goto done;
9✔
167

168
                        if (state->argv[state->optind][0] == '-' &&
149,301✔
169
                            state->argv[state->optind][1] != '\0')
99,338✔
170
                                /* Looks like we found an option parameter */
171
                                break;
172

173
                        if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION)
50,343✔
174
                                goto done;
20,117✔
175

176
                        if (state->mode == OPTION_PARSER_RETURN_POSITIONAL_ARGS) {
30,226✔
177
                                handling_positional_arg = true;
14✔
178
                                optval = state->argv[state->optind];
14✔
179
                                break;
14✔
180
                        }
181

182
                        state->optind++;
30,212✔
183
                }
184

185
                /* Find matching option entry.
186
                 * First, figure out if we have a long option or a short option. */
187
                assert(handling_positional_arg || state->argv[state->optind][0] == '-');
98,972✔
188

189
                if (handling_positional_arg)
98,972✔
190
                        /* We are supposed to return the positional arg to be handled. */
191
                        for (option = state->namespace_start;; option++) {
14✔
192
                                /* If OPTION_PARSER_RETURN_POSITIONAL_ARGS is specified,
193
                                 * OPTION_POSITIONAL must be used. */
194
                                assert(option < state->namespace_end);
184✔
195

196
                                if (FLAGS_SET(option->flags, OPTION_POSITIONAL_ENTRY))
184✔
197
                                        break;
198
                        }
199

200
                else if (state->argv[state->optind][1] == '-') {
98,958✔
201
                        /* We have a long option. */
202
                        char *eq = strchr(state->argv[state->optind], '=');
74,142✔
203
                        if (eq) {
74,142✔
204
                                optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]);
23,319✔
205
                                if (!_optname) {
23,319✔
206
                                        r = log_oom();
×
207
                                        goto fail;
×
208
                                }
209

210
                                /* joined argument */
211
                                optval = eq + 1;
23,319✔
212
                        } else
213
                                /* argument (if any) is separate */
214
                                optname = state->argv[state->optind];
215

216
                        const Option *last_partial = NULL;
74,142✔
217
                        unsigned n_partial_matches = 0;  /* The commandline option matches a defined prefix. */
74,142✔
218

219
                        for (option = state->namespace_start;; option++) {
613,077✔
220
                                if (option >= state->namespace_end) {
613,077✔
221
                                        if (n_partial_matches == 0) {
20✔
222
                                                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
12✔
223
                                                                    "%s: unrecognized option '%s'",
224
                                                                    program_invocation_short_name, optname);
225
                                                goto fail;
12✔
226
                                        }
227
                                        if (n_partial_matches > 1) {
8✔
228
                                                r = partial_match_error(
1✔
229
                                                                state->namespace_start,
230
                                                                state->namespace_end,
231
                                                                optname,
232
                                                                n_partial_matches);
233
                                                goto fail;
1✔
234
                                        }
235

236
                                        /* just one partial — good */
237
                                        option = last_partial;
238
                                        break;
239
                                }
240

241
                                if (option_is_metadata(option) || !option->long_code)
613,057✔
242
                                        continue;
32,502✔
243

244
                                /* Check if the parameter forms a prefix of the option name */
245
                                const char *rest = startswith(option->long_code, optname + 2);
580,555✔
246
                                if (!rest)
580,555✔
247
                                        continue;
506,312✔
248
                                if (isempty(rest))
74,243✔
249
                                        /* exact match */
250
                                        break;
251
                                /* partial match */
252
                                last_partial = option;
121✔
253
                                n_partial_matches++;
121✔
254
                        }
255
                } else
256
                        /* We have a short option */
257
                        state->short_option_offset = 1;
24,816✔
258
        }
259

260
        if (state->short_option_offset > 0) {
98,994✔
261
                char optchar = state->argv[state->optind][state->short_option_offset];
24,851✔
262

263
                if (asprintf(&_optname, "-%c", optchar) < 0) {
24,851✔
264
                        r = log_oom();
×
265
                        goto fail;
×
266
                }
267
                optname = _optname;
24,851✔
268

269
                for (option = state->namespace_start;; option++) {
24,851✔
270
                        if (option >= state->namespace_end) {
128,517✔
271
                                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1✔
272
                                                    "%s: unrecognized option '%s'",
273
                                                    program_invocation_short_name, optname);
274
                                goto fail;
1✔
275
                        }
276

277
                        if (option_is_metadata(option) || optchar != option->short_code)
128,516✔
278
                                continue;
103,666✔
279

280
                        const char *rest = state->argv[state->optind] + state->short_option_offset + 1;
24,850✔
281

282
                        if (option_takes_arg(option) && !isempty(rest)) {
24,850✔
283
                                /* The rest of this parameter is the value. */
284
                                optval = rest;
3,015✔
285
                                state->short_option_offset = 0;
3,015✔
286
                        } else if (isempty(rest))
21,835✔
287
                                state->short_option_offset = 0;
21,800✔
288
                        else
289
                                state->short_option_offset++;
35✔
290

291
                        break;
292
                }
293
        }
294

295
        assert(option);
98,993✔
296

297
        if (!handling_positional_arg && optval && !option_takes_arg(option)) {
98,993✔
298
                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
299
                                    "%s: option '%s' doesn't allow an argument",
300
                                    program_invocation_short_name, optname);
301
                goto fail;
×
302
        }
303
        if (!handling_positional_arg && !optval && option_arg_required(option)) {
98,993✔
304
                if (!state->argv[state->optind + 1]) {
55,443✔
305
                        r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
36✔
306
                                            "%s: option '%s' requires an argument",
307
                                            program_invocation_short_name, optname);
308
                        goto fail;
36✔
309
                }
310
                optval = state->argv[state->optind + 1];
311
                separate_optval = true;
312
        }
313

314
        if (state->short_option_offset == 0) {
98,957✔
315
                /* We're done with this parameter. Adjust the array and position. */
316
                if (handling_positional_arg) {
98,922✔
317
                        /* Sanity check */
318
                        assert(state->positional_offset == state->optind);
14✔
319
                        assert(!separate_optval);
14✔
320
                }
321

322
                shift_arg(state->argv, state->positional_offset++, state->optind++);
98,922✔
323
                if (separate_optval)
98,922✔
324
                        shift_arg(state->argv, state->positional_offset++, state->optind++);
55,407✔
325
        }
326

327
        if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING))
98,957✔
328
                state->state = OPTION_PARSER_STOPPING;
14✔
329

330
        state->opt = option;
98,957✔
331
        state->arg = optval;
98,957✔
332
        return option->id;
98,957✔
333

334
 done:
102,675✔
335
        state->state = OPTION_PARSER_DONE;
102,675✔
336
        state->opt = NULL;
102,675✔
337
        state->arg = NULL;
102,675✔
338
        return 0;
102,675✔
339

340
 fail:
1✔
341
        /* Invalidate the object for good on the first error */
342
        assert(r < 0);
50✔
343
        state->state = OPTION_PARSER_FAILED;
50✔
344
        return r;
50✔
345
}
346

347
char* option_parser_peek_next_arg(const OptionParser *state) {
15✔
348
        /* Peek at the next argument, whatever it is (option or position arg).
349
         * May return NULL. */
350

351
        assert(state->optind > 0);
15✔
352
        assert(state->positional_offset <= state->argc);
15✔
353

354
        return state->optind < state->argc ? state->argv[state->optind] : NULL;
15✔
355
}
356

357
char* option_parser_consume_next_arg(OptionParser *state) {
6✔
358
        /* "Take" the next argument, whatever it is (option or position arg).
359
         * The argument remains in the array, but the optind pointer is moved
360
         * so we won't try to interpret it as an option.
361
         * May return NULL. */
362

363
        char *t = option_parser_peek_next_arg(state);
6✔
364
        if (t)
6✔
365
                shift_arg(state->argv, state->positional_offset++, state->optind++);
4✔
366
        return t;
6✔
367
}
368

369
char** option_parser_get_args(const OptionParser *state) {
47,693✔
370
        /* Returns positional args as a strv.
371
         * If "--" was found, it has been moved before state->positional_offset.
372
         * The array is only valid, i.e. clean without any options, after parsing
373
         * has naturally finished. The array that is returned is a slice of the
374
         * original argv array, so it must not be freed or modified. */
375

376
        assert(state->optind > 0);
47,693✔
377
        assert(state->state == OPTION_PARSER_DONE);
47,693✔
378
        assert(state->positional_offset <= state->argc);
47,693✔
379

380
        return state->argv + state->positional_offset;
47,693✔
381
}
382

383
size_t option_parser_get_n_args(const OptionParser *state) {
2,189✔
384
        assert(state->optind > 0);
2,189✔
385
        assert(state->state == OPTION_PARSER_DONE);
2,189✔
386
        assert(state->positional_offset <= state->argc);
2,189✔
387

388
        return state->argc - state->positional_offset;
2,189✔
389
}
390

391
char* option_parser_get_arg(const OptionParser *state, size_t i) {
13,745✔
392
        assert(state->optind > 0);
13,745✔
393
        assert(state->state == OPTION_PARSER_DONE);
13,745✔
394
        assert(state->positional_offset <= state->argc);
13,745✔
395

396
        return (size_t) (state->argc - state->positional_offset) > i ? state->argv[state->positional_offset + i] : NULL;
13,745✔
397
}
398

399
char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar) {
633✔
400
        assert(opt);
633✔
401
        assert(!(opt->flags & (OPTION_NAMESPACE_MARKER |
633✔
402
                               OPTION_GROUP_MARKER)));  /* The markers should not be displayed */
403

404
        if (opt->flags & (OPTION_HELP_ENTRY_VERBATIM | OPTION_POSITIONAL_ENTRY))
633✔
405
                return strdup(ASSERT_PTR(opt->long_code));
13✔
406

407
        /* The option formatted appropriately for --help strings, error messages, and similar:
408
         *   -<short><joiner>--<long>=[<metavar>]
409
         * "=" is shown only when a long form is defined: -l --long=ARG, --long=ARG, -s ARG.
410
         * The joiner arg is used between the short and long forms.
411
         * As a special case, if the option has no long form and show_metavar is true,
412
         * a space is used ('-a ARG' or '-a [ARG]').
413
         */
414
        assert(opt->short_code != 0 || opt->long_code);
620✔
415

416
        char sc[3] = "";
620✔
417
        if (opt->short_code != 0)
620✔
418
                xsprintf(sc, "-%c", opt->short_code);
211✔
419

420
        if (show_metavar && opt->metavar && !opt->long_code)
620✔
421
                joiner = " ";  /* Return '-x ARG', no matter what joiner was specified. */
422
        else if (opt->short_code == 0 || !opt->long_code)
615✔
423
                joiner = "";
424
        else if (!joiner)
185✔
425
                joiner = " ";
2✔
426

427
        bool need_eq = option_takes_arg(opt) && opt->long_code;
620✔
428
        if (!show_metavar)
620✔
429
                return strjoin(sc,
14✔
430
                               joiner,
431
                               opt->long_code ? "--" : "",
432
                               strempty(opt->long_code),
433
                               need_eq ? "=" : "");
434

435
        bool need_quote = opt->metavar && strchr(opt->metavar, ' ');
612✔
436
        return strjoin(sc,
3,044✔
437
                       joiner,
438
                       opt->long_code ? "--" : "",
439
                       strempty(opt->long_code),
440
                       option_arg_optional(opt) ? "[" : "",
441
                       need_eq ? "=" : "",
442
                       need_quote ? "'" : "",
443
                       strempty(opt->metavar),
444
                       need_quote ? "'" : "",
445
                       option_arg_optional(opt) ? "]" : "");
446
}
447

448
int _option_parser_get_help_table_full(
58✔
449
                const Option options[],
450
                const Option options_end[],
451
                const char *namespace,
452
                const char *group,
453
                Table **ret) {
454
        int r;
58✔
455

456
        assert(ret);
58✔
457

458
        _cleanup_(table_unrefp) Table *table = table_new("names", "help");
116✔
459
        if (!table)
58✔
460
                return log_oom();
×
461

462
        bool in_ns = namespace == NULL;  /* Are we currently in the section of the array that forms namespace
58✔
463
                                          * <namespace>? The first part is the default unnamed namespace, so
464
                                          * if the namespace was not specified, we are in it. */
465

466
        bool in_group = group == NULL;  /* Are we currently in the section of the array that forms group
58✔
467
                                         * <group>? The first part is the default group, so if the group was
468
                                         * not specified, we are in it. */
469

470
        for (const Option *opt = options; opt < options_end; opt++) {
2,628✔
471
                bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER);
2,603✔
472
                if (!in_ns) {
2,603✔
473
                        in_ns = ns_marker && streq(namespace, opt->long_code);
1,028✔
474
                        continue;
1,966✔
475
                }
476
                if (ns_marker)
1,575✔
477
                        break;  /* End of namespace */
478

479
                bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER);
1,562✔
480
                if (!in_group) {
1,562✔
481
                        in_group = group_marker && streq(group, opt->long_code);
913✔
482
                        continue;
913✔
483
                }
484
                if (group_marker)
649✔
485
                        break;  /* End of group */
486

487
                if (!opt->help)
629✔
488
                        /* No help string — we do not show the option */
489
                        continue;
25✔
490

491
                _cleanup_free_ char *s = option_get_synopsis(opt, " ", /* show_metavar= */ true);
604✔
492
                if (!s)
604✔
493
                        return log_oom();
×
494

495
                /* We indent the option string by two spaces. We could set the minimum cell width and
496
                 * right-align for a similar result, but that'd be more work. This is only used for
497
                 * display. */
498
                const char *prefix = opt->short_code != 0 ? "  " : "     ";
604✔
499
                _cleanup_free_ char *t = strjoin(prefix, s);
1,208✔
500
                if (!t)
604✔
501
                        return log_oom();
×
502

503
                r = table_add_many(table, TABLE_STRING, t);
604✔
504
                if (r < 0)
604✔
505
                        return table_log_add_error(r);
×
506

507
                _cleanup_strv_free_ char **split = strv_split(opt->help, /* separators= */ NULL);
1,208✔
508
                if (!split)
604✔
509
                        return log_oom();
×
510

511
                r = table_add_many(table, TABLE_STRV_WRAPPED, split);
604✔
512
                if (r < 0)
604✔
513
                        return table_log_add_error(r);
×
514
        }
515

516
        assert(!table_isempty(table));  /* The namespace or group were not found. Something is off. */
116✔
517

518
        table_set_header(table, false);
58✔
519
        *ret = TAKE_PTR(table);
58✔
520
        return 0;
58✔
521
}
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