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

systemd / systemd / 23927985597

02 Apr 2026 07:45PM UTC coverage: 72.362% (+0.02%) from 72.343%
23927985597

push

github

daandemeyer
ci: Drop base64 encoding in claude review workflow

Doesn't seem to work nearly as good as the previous solution which
just told claude not to escape stuff.

319121 of 441004 relevant lines covered (72.36%)

1167673.48 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) {
920✔
11
        return ASSERT_PTR(opt)->metavar;
920✔
12
}
13

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

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

22
static bool option_is_metadata(const Option *opt) {
7,212✔
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) ||
7,212✔
25
                FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_HELP_ENTRY);
7,067✔
26
}
27

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

32
        /* Move argv[source] before argv[target], shifting arguments inbetween */
33
        char *saved = argv[source];
704✔
34
        memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*));
704✔
35
        argv[target] = saved;
704✔
36
}
704✔
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,287✔
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,287✔
77
                if (state->argc < 1 || strv_isempty(state->argv))
622✔
78
                        return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty");
1,287✔
79

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

83
        /* Look for the next option */
84

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

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

96
                        if (streq(state->argv[state->optind], "--")) {
1,125✔
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,125✔
104
                                return 0;
105

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

111
                        state->optind++;
449✔
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] == '-');
645✔
117

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

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

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

135
                        for (option = options;; option++) {
6,344✔
136
                                if (option >= options_end) {
6,344✔
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)
6,339✔
150
                                        continue;
405✔
151

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

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

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

175
                for (option = options;; option++) {
140✔
176
                        if (option >= options_end)
867✔
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)
867✔
182
                                continue;
727✔
183

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

186
                        if (option_takes_arg(option) && !isempty(rest)) {
140✔
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))
129✔
191
                                state->short_option_offset = 0;
101✔
192
                        else
193
                                state->short_option_offset++;
28✔
194

195
                        break;
196
                }
197
        }
198

199
        assert(option);
669✔
200

201
        if (optval && !option_takes_arg(option))
669✔
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)) {
540✔
206
                if (!state->argv[state->optind + 1])
40✔
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) {
669✔
215
                /* We're done with this option. Adjust the array and position. */
216
                shift_arg(state->argv, state->positional_offset++, state->optind++);
641✔
217
                if (separate_optval)
641✔
218
                        shift_arg(state->argv, state->positional_offset++, state->optind++);
40✔
219
        }
220

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

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

228
        if (ret_arg)
669✔
229
                *ret_arg = optval;
669✔
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;
669✔
235
}
236

237
char** option_parser_get_args(const OptionParser *state) {
533✔
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. */
242

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

247
        return state->argv + state->positional_offset;
533✔
248
}
249

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

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

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

265
        assert(ret);
3✔
266

267
        _cleanup_(table_unrefp) Table *table = table_new("names", "help");
6✔
268
        if (!table)
3✔
269
                return log_oom();
×
270

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

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

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

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

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

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

316
                _cleanup_strv_free_ char **t = strv_split(opt->help, /* separators= */ NULL);
74✔
317
                if (!t)
37✔
318
                        return log_oom();
×
319

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

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