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

systemd / systemd / 25084703852

28 Apr 2026 09:34PM UTC coverage: 71.849% (-0.02%) from 71.865%
25084703852

push

github

daandemeyer
ci: Reduce noise from claude-review workflow

322528 of 448894 relevant lines covered (71.85%)

1177215.84 hits per line

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

91.87
/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) {
82,956✔
11
        return ASSERT_PTR(opt)->metavar;
82,956✔
12
}
13

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

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

22
static bool option_is_metadata(const Option *opt) {
643,812✔
23
        /* A metadata entry that is not a real option, like the group marker */
24
        return ASSERT_PTR(opt)->flags & (OPTION_NAMESPACE_MARKER |
643,812✔
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) {
111,805✔
32
        assert(argv);
111,805✔
33
        assert(target <= source);
111,805✔
34

35
        /* Move argv[source] before argv[target], shifting arguments inbetween */
36
        char *saved = argv[source];
111,805✔
37
        memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*));
111,805✔
38
        argv[target] = saved;
111,805✔
39
}
111,805✔
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(
96,501✔
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 */
96,501✔
78
        int r;
96,501✔
79

80
        assert(state);
96,501✔
81

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

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

88
                if (state->argc < 1) {
25,134✔
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 */
25,134✔
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
25,134✔
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)
25,134✔
101
                        state->namespace_start = options;
23,627✔
102

103
                const Option *opt;
25,134✔
104

105
                /* Verify that the option array didn't get mangled within a namespace. */
106
                for (opt = options; opt < options_end; opt++)
498,823✔
107
                        if (opt + 1 < options_end && !FLAGS_SET((opt + 1)->flags, OPTION_NAMESPACE_MARKER))
473,689✔
108
                                assert_se(opt->id < (opt + 1)->id);
446,974✔
109

110
                for (opt = options; opt < options_end; opt++) {
465,285✔
111
                        bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER);
441,436✔
112
                        if (!in_ns) {
441,436✔
113
                                in_ns = ns_marker && streq(state->namespace, opt->long_code);
16,818✔
114
                                if (in_ns)
1,507✔
115
                                        state->namespace_start = opt + 1;
1,507✔
116
                                continue;
16,818✔
117
                        }
118
                        if (ns_marker)
424,618✔
119
                                break;  /* End of namespace */
120
                }
121
                assert(state->namespace_start);
25,134✔
122
                state->namespace_end = opt;
25,134✔
123

124
                state->optind = state->positional_offset = 1;
25,134✔
125
                state->state = OPTION_PARSER_RUNNING;
25,134✔
126
                break;
25,134✔
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 */
96,501✔
146
        const char *optname = NULL, *optval = NULL;
96,501✔
147
        bool separate_optval = false;
96,501✔
148
        bool handling_positional_arg = false;
96,501✔
149

150
        if (state->short_option_offset == 0) {
96,501✔
151
                /* Handle non-option parameters */
152
                for (;;) {
110,613✔
153
                        if (state->optind == state->argc)
103,540✔
154
                                goto done;
22,933✔
155

156
                        if (streq(state->argv[state->optind], "--")) {
80,607✔
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)
80,456✔
166
                                goto done;
9✔
167

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

173
                        if (state->mode == OPTION_PARSER_STOP_AT_FIRST_NONOPTION)
8,893✔
174
                                goto done;
1,806✔
175

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

182
                        state->optind++;
7,073✔
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] == '-');
71,568✔
188

189
                if (handling_positional_arg)
71,568✔
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] == '-') {
71,554✔
201
                        /* We have a long option. */
202
                        char *eq = strchr(state->argv[state->optind], '=');
61,655✔
203
                        if (eq) {
61,655✔
204
                                optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]);
12,201✔
205
                                if (!_optname) {
12,201✔
206
                                        r = log_oom();
×
207
                                        goto fail;
×
208
                                }
209

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

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

219
                        for (option = state->namespace_start;; option++) {
562,052✔
220
                                if (option >= state->namespace_end) {
562,052✔
221
                                        if (n_partial_matches == 0) {
18✔
222
                                                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
10✔
223
                                                                    "%s: unrecognized option '%s'",
224
                                                                    program_invocation_short_name, optname);
225
                                                goto fail;
10✔
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)
562,034✔
242
                                        continue;
31,141✔
243

244
                                /* Check if the parameter forms a prefix of the option name */
245
                                const char *rest = startswith(option->long_code, optname + 2);
530,893✔
246
                                if (!rest)
530,893✔
247
                                        continue;
469,227✔
248
                                if (isempty(rest))
61,666✔
249
                                        /* exact match */
250
                                        break;
251
                                /* partial match */
252
                                last_partial = option;
29✔
253
                                n_partial_matches++;
29✔
254
                        }
255
                } else
256
                        /* We have a short option */
257
                        state->short_option_offset = 1;
9,899✔
258
        }
259

260
        if (state->short_option_offset > 0) {
71,591✔
261
                char optchar = state->argv[state->optind][state->short_option_offset];
9,933✔
262

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

269
                for (option = state->namespace_start;; option++) {
9,933✔
270
                        if (option >= state->namespace_end) {
81,773✔
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)
81,772✔
278
                                continue;
71,840✔
279

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

282
                        if (option_takes_arg(option) && !isempty(rest)) {
9,932✔
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))
6,917✔
287
                                state->short_option_offset = 0;
6,883✔
288
                        else
289
                                state->short_option_offset++;
34✔
290

291
                        break;
292
                }
293
        }
294

295
        assert(option);
71,590✔
296

297
        if (!handling_positional_arg && optval && !option_takes_arg(option)) {
71,590✔
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)) {
71,590✔
304
                if (!state->argv[state->optind + 1]) {
40,162✔
305
                        r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
34✔
306
                                            "%s: option '%s' requires an argument",
307
                                            program_invocation_short_name, optname);
308
                        goto fail;
34✔
309
                }
310
                optval = state->argv[state->optind + 1];
311
                separate_optval = true;
312
        }
313

314
        if (state->short_option_offset == 0) {
71,556✔
315
                /* We're done with this parameter. Adjust the array and position. */
316
                if (handling_positional_arg) {
71,522✔
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++);
71,522✔
323
                if (separate_optval)
71,522✔
324
                        shift_arg(state->argv, state->positional_offset++, state->optind++);
40,128✔
325
        }
326

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

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

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

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

347
char* option_parser_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_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) {
13,752✔
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);
13,752✔
377
        assert(state->state == OPTION_PARSER_DONE);
13,752✔
378
        assert(state->positional_offset <= state->argc);
13,752✔
379

380
        return state->argv + state->positional_offset;
13,752✔
381
}
382

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

388
        return state->argc - state->positional_offset;
1,757✔
389
}
390

391
char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar) {
493✔
392
        assert(opt);
493✔
393
        assert(!(opt->flags & (OPTION_NAMESPACE_MARKER |
493✔
394
                               OPTION_GROUP_MARKER)));  /* The markers should not be displayed */
395

396
        if (opt->flags & (OPTION_HELP_ENTRY_VERBATIM | OPTION_POSITIONAL_ENTRY))
493✔
397
                return strdup(ASSERT_PTR(opt->long_code));
5✔
398

399
        /* The option formatted appropriately for --help strings, error messages, and similar:
400
         *   -<short><joiner>--<long>=[<metavar>]
401
         * "=" is shown only when a long form is defined: -l --long=ARG, --long=ARG, -s ARG.
402
         * The joiner arg is used between the short and long forms.
403
         * As a special case, if the option has no long form and show_metavar is true,
404
         * a space is used ('-a ARG' or '-a [ARG]').
405
         */
406
        assert(opt->short_code != 0 || opt->long_code);
488✔
407

408
        char sc[3] = "";
488✔
409
        if (opt->short_code != 0)
488✔
410
                xsprintf(sc, "-%c", opt->short_code);
134✔
411

412
        if (show_metavar && opt->metavar && !opt->long_code)
488✔
413
                joiner = " ";  /* Return '-x ARG', no matter what joiner was specified. */
414
        else if (opt->short_code == 0 || !opt->long_code)
483✔
415
                joiner = "";
416
        else if (!joiner)
108✔
417
                joiner = " ";
2✔
418

419
        bool need_eq = option_takes_arg(opt) && opt->long_code;
488✔
420
        if (!show_metavar)
488✔
421
                return strjoin(sc,
14✔
422
                               joiner,
423
                               opt->long_code ? "--" : "",
424
                               strempty(opt->long_code),
425
                               need_eq ? "=" : "");
426

427
        bool need_quote = opt->metavar && strchr(opt->metavar, ' ');
480✔
428
        return strjoin(sc,
2,366✔
429
                       joiner,
430
                       opt->long_code ? "--" : "",
431
                       strempty(opt->long_code),
432
                       option_arg_optional(opt) ? "[" : "",
433
                       need_eq ? "=" : "",
434
                       need_quote ? "'" : "",
435
                       strempty(opt->metavar),
436
                       need_quote ? "'" : "",
437
                       option_arg_optional(opt) ? "]" : "");
438
}
439

440
int _option_parser_get_help_table_full(
44✔
441
                const Option options[],
442
                const Option options_end[],
443
                const char *namespace,
444
                const char *group,
445
                Table **ret) {
446
        int r;
44✔
447

448
        assert(ret);
44✔
449

450
        _cleanup_(table_unrefp) Table *table = table_new("names", "help");
88✔
451
        if (!table)
44✔
452
                return log_oom();
×
453

454
        bool in_ns = namespace == NULL;  /* Are we currently in the section of the array that forms namespace
44✔
455
                                          * <namespace>? The first part is the default unnamed namespace, so
456
                                          * if the namespace was not specified, we are in it. */
457

458
        bool in_group = group == NULL;  /* Are we currently in the section of the array that forms group
44✔
459
                                         * <group>? The first part is the default group, so if the group was
460
                                         * not specified, we are in it. */
461

462
        for (const Option *opt = options; opt < options_end; opt++) {
1,441✔
463
                bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER);
1,418✔
464
                if (!in_ns) {
1,418✔
465
                        in_ns = ns_marker && streq(namespace, opt->long_code);
4✔
466
                        continue;
933✔
467
                }
468
                if (ns_marker)
1,414✔
469
                        break;  /* End of namespace */
470

471
                bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER);
1,413✔
472
                if (!in_group) {
1,413✔
473
                        in_group = group_marker && streq(group, opt->long_code);
913✔
474
                        continue;
913✔
475
                }
476
                if (group_marker)
500✔
477
                        break;  /* End of group */
478

479
                if (!opt->help)
480✔
480
                        /* No help string — we do not show the option */
481
                        continue;
16✔
482

483
                _cleanup_free_ char *s = option_get_synopsis(opt, " ", /* show_metavar= */ true);
464✔
484
                if (!s)
464✔
485
                        return log_oom();
×
486

487
                /* We indent the option string by two spaces. We could set the minimum cell width and
488
                 * right-align for a similar result, but that'd be more work. This is only used for
489
                 * display. */
490
                const char *prefix = opt->short_code != 0 ? "  " : "     ";
464✔
491
                _cleanup_free_ char *t = strjoin(prefix, s);
928✔
492
                if (!t)
464✔
493
                        return log_oom();
×
494

495
                r = table_add_many(table, TABLE_STRING, t);
464✔
496
                if (r < 0)
464✔
497
                        return table_log_add_error(r);
×
498

499
                _cleanup_strv_free_ char **split = strv_split(opt->help, /* separators= */ NULL);
928✔
500
                if (!split)
464✔
501
                        return log_oom();
×
502

503
                r = table_add_many(table, TABLE_STRV_WRAPPED, split);
464✔
504
                if (r < 0)
464✔
505
                        return table_log_add_error(r);
×
506
        }
507

508
        assert(!table_isempty(table));  /* The namespace or group were not found. Something is off. */
88✔
509

510
        table_set_header(table, false);
44✔
511
        *ret = TAKE_PTR(table);
44✔
512
        return 0;
44✔
513
}
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