• 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

90.26
/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) {
1,084✔
11
        return ASSERT_PTR(opt)->metavar;
1,084✔
12
}
13

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

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

22
static bool option_is_metadata(const Option *opt) {
8,418✔
23
        /* A metadata entry that is not a real option, like the group marker */
24
        return  FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER) ||
8,418✔
25
                FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_HELP_ENTRY);
8,289✔
26
}
27

28
static void shift_arg(char* argv[], int target, int source) {
853✔
29
        assert(argv);
853✔
30
        assert(target <= source);
853✔
31

32
        /* Move argv[source] before argv[target], shifting arguments inbetween */
33
        char *saved = argv[source];
853✔
34
        memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*));
853✔
35
        argv[target] = saved;
853✔
36
}
853✔
37

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

45
        assert(startswith(ASSERT_PTR(optname), "--"));
1✔
46
        assert(n_partial_matches >= 2);
1✔
47

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

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

60
        assert(strv_length(s) == n_partial_matches);
1✔
61

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

68
int option_parse(
1,514✔
69
                const Option options[],
70
                const Option options_end[],
71
                OptionParser *state,
72
                const Option **ret_option,
73
                const char **ret_arg) {
74

75
        /* Check and initialize */
76
        if (state->optind == 0) {
1,514✔
77
                if (state->argc < 1 || strv_isempty(state->argv))
748✔
78
                        return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty");
1,514✔
79

80
                state->optind = state->positional_offset = 1;
748✔
81
        }
82

83
        /* Look for the next option */
84

85
        const Option *option = NULL;  /* initialization to appease gcc 13 */
1,514✔
86
        const char *optname = NULL, *optval = NULL;
1,514✔
87
        _cleanup_free_ char *_optname = NULL;  /* allocated option name */
1,514✔
88
        bool separate_optval = false;
1,514✔
89

90
        if (state->short_option_offset == 0) {
1,514✔
91
                /* Skip over non-option parameters */
92
                for (;;) {
2,736✔
93
                        if (state->optind == state->argc)
2,111✔
94
                                return 0;
95

96
                        if (streq(state->argv[state->optind], "--")) {
1,404✔
97
                                /* No more options. Move "--" before positional args so that
98
                                 * the list of positional args is clean. */
99
                                shift_arg(state->argv, state->positional_offset++, state->optind++);
23✔
100
                                state->parsing_stopped = true;
23✔
101
                        }
102

103
                        if (state->parsing_stopped)
1,404✔
104
                                return 0;
105

106
                        if (state->argv[state->optind][0] == '-' &&
1,373✔
107
                            state->argv[state->optind][1] != '\0')
748✔
108
                                /* Looks like we found an option parameter */
109
                                break;
110

111
                        state->optind++;
625✔
112
                }
113

114
                /* Find matching option entry.
115
                 * First, figure out if we have a long option or a short option. */
116
                assert(state->argv[state->optind][0] == '-');
748✔
117

118
                if (state->argv[state->optind][1] == '-') {
748✔
119
                        /* We have a long option. */
120
                        char *eq = strchr(state->argv[state->optind], '=');
611✔
121
                        if (eq) {
611✔
122
                                optname = _optname = strndup(state->argv[state->optind], eq - state->argv[state->optind]);
143✔
123
                                if (!_optname)
143✔
124
                                        return log_oom();
×
125

126
                                /* joined argument */
127
                                optval = eq + 1;
143✔
128
                        } else
129
                                /* argument (if any) is separate */
130
                                optname = state->argv[state->optind];
131

132
                        const Option *last_partial = NULL;
611✔
133
                        unsigned n_partial_matches = 0;  /* The commandline option matches a defined prefix. */
611✔
134

135
                        for (option = options;; option++) {
7,143✔
136
                                if (option >= options_end) {
7,143✔
137
                                        if (n_partial_matches == 0)
5✔
138
                                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
3✔
139
                                                                       "%s: unrecognized option '%s'",
140
                                                                       program_invocation_short_name, optname);
141
                                        if (n_partial_matches > 1)
2✔
142
                                                return partial_match_error(options, options_end, optname, n_partial_matches);
1✔
143

144
                                        /* just one partial — good */
145
                                        option = last_partial;
146
                                        break;
147
                                }
148

149
                                if (option_is_metadata(option) || !option->long_code)
7,138✔
150
                                        continue;
368✔
151

152
                                /* Check if the parameter forms a prefix of the option name */
153
                                const char *rest = startswith(option->long_code, optname + 2);
6,770✔
154
                                if (!rest)
6,770✔
155
                                        continue;
6,146✔
156
                                if (isempty(rest))
624✔
157
                                        /* exact match */
158
                                        break;
159
                                /* partial match */
160
                                last_partial = option;
18✔
161
                                n_partial_matches++;
18✔
162
                        }
163
                } else
164
                        /* We have a short option */
165
                        state->short_option_offset = 1;
137✔
166
        }
167

168
        if (state->short_option_offset > 0) {
772✔
169
                char optchar = state->argv[state->optind][state->short_option_offset];
165✔
170

171
                if (asprintf(&_optname, "-%c", optchar) < 0)
165✔
172
                        return log_oom();
×
173
                optname = _optname;
165✔
174

175
                for (option = options;; option++) {
165✔
176
                        if (option >= options_end)
1,274✔
177
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
178
                                                       "%s: unrecognized option '%s'",
179
                                                       program_invocation_short_name, optname);
180

181
                        if (option_is_metadata(option) || optchar != option->short_code)
1,274✔
182
                                continue;
1,109✔
183

184
                        const char *rest = state->argv[state->optind] + state->short_option_offset + 1;
165✔
185

186
                        if (option_takes_arg(option) && !isempty(rest)) {
165✔
187
                                /* The rest of this parameter is the value. */
188
                                optval = rest;
11✔
189
                                state->short_option_offset = 0;
11✔
190
                        } else if (isempty(rest))
154✔
191
                                state->short_option_offset = 0;
126✔
192
                        else
193
                                state->short_option_offset++;
28✔
194

195
                        break;
196
                }
197
        }
198

199
        assert(option);
772✔
200

201
        if (optval && !option_takes_arg(option))
772✔
202
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
203
                                       "%s: option '%s' doesn't allow an argument",
204
                                       program_invocation_short_name, optname);
205
        if (!optval && option_arg_required(option)) {
619✔
206
                if (!state->argv[state->optind + 1])
86✔
207
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
208
                                               "%s: option '%s' requires an argument",
209
                                               program_invocation_short_name, optname);
210
                optval = state->argv[state->optind + 1];
211
                separate_optval = true;
212
        }
213

214
        if (state->short_option_offset == 0) {
772✔
215
                /* We're done with this option. Adjust the array and position. */
216
                shift_arg(state->argv, state->positional_offset++, state->optind++);
744✔
217
                if (separate_optval)
744✔
218
                        shift_arg(state->argv, state->positional_offset++, state->optind++);
86✔
219
        }
220

221
        if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING))
772✔
222
                state->parsing_stopped = true;
13✔
223

224
        if (ret_option)
772✔
225
                /* Return the matched Option structure to allow the caller to "know" what was matched */
226
                *ret_option = option;
373✔
227

228
        if (ret_arg)
772✔
229
                *ret_arg = optval;
772✔
230
        else
231
                /* It's fine to omit ret_arg, but only if no options return a value. */
232
                assert(!optval);
×
233

234
        return option->id;
772✔
235
}
236

237
char** option_parser_get_args(const OptionParser *state) {
657✔
238
        /* Returns positional args as a strv.
239
         * If "--" was found, it has been moved before state->positional_offset.
240
         * The array is only valid, i.e. clean without any options, after parsing
241
         * has naturally finished. The array that is returned is a slice of the
242
         * original argv array, so it must not be freed or modified. */
243

244
        assert(state->optind > 0);
657✔
245
        assert(state->optind == state->argc || state->parsing_stopped);
657✔
246
        assert(state->positional_offset <= state->argc);
657✔
247

248
        return state->argv + state->positional_offset;
657✔
249
}
250

251
size_t option_parser_get_n_args(const OptionParser *state) {
120✔
252
        assert(state->optind > 0);
120✔
253
        assert(state->optind == state->argc || state->parsing_stopped);
120✔
254
        assert(state->positional_offset <= state->argc);
120✔
255

256
        return state->argc - state->positional_offset;
120✔
257
}
258

259
int _option_parser_get_help_table(
4✔
260
                const Option options[],
261
                const Option options_end[],
262
                const char *group,
263
                Table **ret) {
264
        int r;
4✔
265

266
        assert(ret);
4✔
267

268
        _cleanup_(table_unrefp) Table *table = table_new("names", "help");
8✔
269
        if (!table)
4✔
UNCOV
270
                return log_oom();
×
271

272
        bool in_group = group == NULL;  /* Are we currently in the section on the array that forms
4✔
273
                                         * group <group>? The first part is the default group, so
274
                                         * if the group was not specified, we are in. */
275

276
        for (const Option *opt = options; opt < options_end; opt++) {
53✔
277
                bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER);
49✔
278
                if (!in_group) {
49✔
279
                        in_group = group_marker && streq(group, opt->long_code);
×
UNCOV
280
                        continue;
×
281
                }
282
                if (group_marker)
49✔
283
                        break;  /* End of group */
284

285
                if (!opt->help)
49✔
286
                        /* No help string — we do not show the option */
UNCOV
287
                        continue;
×
288

289
                char sc[3] = "  ";
49✔
290
                if (opt->short_code != 0)
49✔
291
                        xsprintf(sc, "-%c", opt->short_code);
21✔
292

293
                /* We indent the option string by two spaces. We could set the minimum cell width and
294
                 * right-align for a similar result, but that'd be more work. This is only used for
295
                 * display.
296
                 *
297
                 * "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG.
298
                 */
299
                bool need_eq = option_takes_arg(opt) && opt->long_code;
49✔
300
                _cleanup_free_ char *s = strjoin(
271✔
301
                                "  ",
302
                                sc,
303
                                " ",
304
                                opt->long_code ? "--" : "",
305
                                strempty(opt->long_code),
306
                                option_arg_optional(opt) ? "[" : "",
307
                                need_eq ? "=" : "",
308
                                strempty(opt->metavar),
309
                                option_arg_optional(opt) ? "]" : "");
310
                if (!s)
49✔
UNCOV
311
                        return log_oom();
×
312

313
                r = table_add_many(table, TABLE_STRING, s);
49✔
314
                if (r < 0)
49✔
UNCOV
315
                        return table_log_add_error(r);
×
316

317
                _cleanup_strv_free_ char **t = strv_split(opt->help, /* separators= */ NULL);
98✔
318
                if (!t)
49✔
UNCOV
319
                        return log_oom();
×
320

321
                r = table_add_many(table, TABLE_STRV_WRAPPED, t);
49✔
322
                if (r < 0)
49✔
UNCOV
323
                        return table_log_add_error(r);
×
324
        }
325

326
        table_set_header(table, false);
4✔
327
        *ret = TAKE_PTR(table);
4✔
328
        return 0;
4✔
329
}
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