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

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

0 of 1 new or added line in 1 file covered. (0.0%)

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 hits per line

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

74.47
/src/sysctl/sysctl.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <errno.h>
4
#include <getopt.h>
5
#include <limits.h>
6
#include <stdbool.h>
7
#include <stdio.h>
8
#include <stdlib.h>
9
#include <sys/stat.h>
10
#include <sys/types.h>
11

12
#include "alloc-util.h"
13
#include "build.h"
14
#include "conf-files.h"
15
#include "constants.h"
16
#include "creds-util.h"
17
#include "errno-util.h"
18
#include "fd-util.h"
19
#include "fileio.h"
20
#include "glob-util.h"
21
#include "hashmap.h"
22
#include "log.h"
23
#include "main-func.h"
24
#include "pager.h"
25
#include "path-util.h"
26
#include "pretty-print.h"
27
#include "string-util.h"
28
#include "strv.h"
29
#include "sysctl-util.h"
30

31
static char **arg_prefixes = NULL;
32
static CatFlags arg_cat_flags = CAT_CONFIG_OFF;
33
static bool arg_strict = false;
34
static PagerFlags arg_pager_flags = 0;
35

36
STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep);
1,039✔
37

38
typedef struct Option {
39
        char *key;
40
        char *value;
41
        bool ignore_failure;
42
} Option;
43

44
static Option *option_free(Option *o) {
5,977✔
45
        if (!o)
5,977✔
46
                return NULL;
47

48
        free(o->key);
5,977✔
49
        free(o->value);
5,977✔
50

51
        return mfree(o);
5,977✔
52
}
53

54
DEFINE_TRIVIAL_CLEANUP_FUNC(Option*, option_free);
140,505✔
55
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(option_hash_ops, char, string_hash_func, string_compare_func, Option, option_free);
5,977✔
56

57
static bool test_prefix(const char *p) {
24,832✔
58
        if (strv_isempty(arg_prefixes))
24,832✔
59
                return true;
60

61
        return path_startswith_strv(p, arg_prefixes);
21,960✔
62
}
63

64
static Option *option_new(
5,977✔
65
                const char *key,
66
                const char *value,
67
                bool ignore_failure) {
68

69
        _cleanup_(option_freep) Option *o = NULL;
5,977✔
70

71
        assert(key);
5,977✔
72

73
        o = new(Option, 1);
5,977✔
74
        if (!o)
5,977✔
75
                return NULL;
76

77
        *o = (Option) {
11,954✔
78
                .key = strdup(key),
5,977✔
79
                .value = value ? strdup(value) : NULL,
5,977✔
80
                .ignore_failure = ignore_failure,
81
        };
82

83
        if (!o->key)
5,977✔
84
                return NULL;
85
        if (value && !o->value)
5,977✔
86
                return NULL;
87

88
        return TAKE_PTR(o);
5,977✔
89
}
90

91
static int sysctl_write_or_warn(const char *key, const char *value, bool ignore_failure, bool ignore_enoent) {
5,617✔
92
        int r;
5,617✔
93

94
        r = sysctl_write(key, value);
5,617✔
95
        if (r < 0) {
5,617✔
96
                /* Proceed without failing if ignore_failure is true.
97
                 * If the sysctl is not available in the kernel or we are running with reduced privileges and
98
                 * cannot write it, then log about the issue, and proceed without failing. Unless strict mode
99
                 * (arg_strict = true) is enabled, in which case we should fail. (EROFS is treated as a
100
                 * permission problem here, since that's how container managers usually protected their
101
                 * sysctls.)
102
                 * In all other cases log an error and make the tool fail. */
103
                if (ignore_failure || (!arg_strict && (r == -EROFS || ERRNO_IS_PRIVILEGE(r))))
1,070✔
104
                        log_debug_errno(r, "Couldn't write '%s' to '%s', ignoring: %m", value, key);
434✔
105
                else if (ignore_enoent && r == -ENOENT)
636✔
106
                        log_warning_errno(r, "Couldn't write '%s' to '%s', ignoring: %m", value, key);
635✔
107
                else
108
                        return log_error_errno(r, "Couldn't write '%s' to '%s': %m", value, key);
1✔
109
        }
110

111
        return 0;
112
}
113

114
static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, Option *option, const char *prefix) {
11,340✔
UNCOV
115
        _cleanup_strv_free_ char **paths = NULL;
×
116
        _cleanup_free_ char *pattern = NULL;
11,340✔
117
        int r;
11,340✔
118

119
        assert(sysctl_options);
11,340✔
120
        assert(option);
11,340✔
121

122
        if (prefix) {
11,340✔
123
                _cleanup_free_ char *key = NULL;
10,980✔
124

125
                r = path_glob_can_match(option->key, prefix, &key);
10,980✔
126
                if (r < 0)
10,980✔
UNCOV
127
                        return log_error_errno(r, "Failed to check if the glob '%s' matches prefix '%s': %m",
×
128
                                               option->key, prefix);
129
                if (r == 0) {
10,980✔
130
                        log_debug("The glob '%s' does not match prefix '%s'.", option->key, prefix);
8,235✔
131
                        return 0;
8,235✔
132
                }
133

134
                log_debug("The glob '%s' is prefixed with '%s': '%s'", option->key, prefix, key);
2,745✔
135

136
                if (!string_is_glob(key)) {
2,745✔
137
                        /* The prefixed pattern is not glob anymore. Let's skip to call glob(). */
138
                        if (ordered_hashmap_contains(sysctl_options, key)) {
2,745✔
139
                                log_debug("Not setting %s (explicit setting exists).", key);
×
UNCOV
140
                                return 0;
×
141
                        }
142

143
                        return sysctl_write_or_warn(key, option->value,
2,745✔
144
                                                    /* ignore_failure = */ option->ignore_failure,
2,745✔
145
                                                    /* ignore_enoent = */ true);
146
                }
147

UNCOV
148
                pattern = path_join("/proc/sys", key);
×
149
        } else
150
                pattern = path_join("/proc/sys", option->key);
360✔
151
        if (!pattern)
360✔
UNCOV
152
                return log_oom();
×
153

154
        r = glob_extend(&paths, pattern, GLOB_NOCHECK);
360✔
155
        if (r < 0) {
360✔
156
                if (r == -ENOENT) {
×
157
                        log_debug("No match for glob: %s", option->key);
×
UNCOV
158
                        return 0;
×
159
                }
160
                if (option->ignore_failure || ERRNO_IS_PRIVILEGE(r)) {
×
161
                        log_debug_errno(r, "Failed to resolve glob '%s', ignoring: %m", option->key);
×
UNCOV
162
                        return 0;
×
163
                } else
UNCOV
164
                        return log_error_errno(r, "Couldn't resolve glob '%s': %m", option->key);
×
165
        }
166

167
        STRV_FOREACH(s, paths) {
1,440✔
168
                const char *key;
1,080✔
169

170
                assert_se(key = path_startswith(*s, "/proc/sys"));
1,080✔
171

172
                if (ordered_hashmap_contains(sysctl_options, key)) {
1,080✔
173
                        log_debug("Not setting %s (explicit setting exists).", key);
720✔
174
                        continue;
720✔
175
                }
176

177
                RET_GATHER(r,
360✔
178
                           sysctl_write_or_warn(key, option->value,
179
                                                /* ignore_failure = */ option->ignore_failure,
180
                                                /* ignore_enoent = */ !arg_strict));
181
        }
182

183
        return r;
184
}
185

186
static int apply_glob_option(OrderedHashmap *sysctl_options, Option *option) {
3,105✔
187
        int r = 0;
3,105✔
188

189
        if (strv_isempty(arg_prefixes))
3,105✔
190
                return apply_glob_option_with_prefix(sysctl_options, option, NULL);
360✔
191

192
        STRV_FOREACH(i, arg_prefixes)
13,725✔
193
                RET_GATHER(r, apply_glob_option_with_prefix(sysctl_options, option, *i));
10,980✔
194
        return r;
195
}
196

197
static int apply_all(OrderedHashmap *sysctl_options) {
1,039✔
198
        Option *option;
1,039✔
199
        int r = 0;
1,039✔
200

201
        ORDERED_HASHMAP_FOREACH(option, sysctl_options) {
7,016✔
202
                int k;
5,977✔
203

204
                /* Ignore "negative match" options, they are there only to exclude stuff from globs. */
205
                if (!option->value)
5,977✔
206
                        continue;
360✔
207

208
                if (string_is_glob(option->key))
5,617✔
209
                        k = apply_glob_option(sysctl_options, option);
3,105✔
210
                else
211
                        k = sysctl_write_or_warn(option->key, option->value,
2,512✔
212
                                                 /* ignore_failure = */ option->ignore_failure,
2,512✔
213
                                                 /* ignore_enoent = */ !arg_strict);
2,512✔
214
                RET_GATHER(r, k);
5,617✔
215
        }
216

217
        return r;
1,039✔
218
}
219

220
static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ignore_enoent) {
5,293✔
221
        _cleanup_fclose_ FILE *f = NULL;
5,293✔
222
        _cleanup_free_ char *pp = NULL;
5,293✔
223
        unsigned c = 0;
5,293✔
224
        int r;
5,293✔
225

226
        assert(path);
5,293✔
227

228
        r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("sysctl.d"), &f, &pp);
5,293✔
229
        if (r < 0) {
5,293✔
230
                if (ignore_enoent && r == -ENOENT)
120✔
231
                        return 0;
232

UNCOV
233
                return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path);
×
234
        }
235

236
        log_debug("Parsing %s", pp);
5,173✔
237
        for (;;) {
134,528✔
UNCOV
238
                _cleanup_(option_freep) Option *new_option = NULL;
×
239
                _cleanup_free_ char *l = NULL;
134,528✔
240
                bool ignore_failure = false;
134,528✔
241
                Option *existing;
134,528✔
242
                char *value;
134,528✔
243
                int k;
134,528✔
244

245
                k = read_stripped_line(f, LONG_LINE_MAX, &l);
134,528✔
246
                if (k == 0)
134,528✔
247
                        break;
248
                if (k < 0)
129,355✔
UNCOV
249
                        return log_error_errno(k, "Failed to read file '%s', ignoring: %m", pp);
×
250

251
                c++;
129,355✔
252

253
                if (isempty(l))
129,355✔
254
                        continue;
20,700✔
255
                if (strchr(COMMENTS, l[0]))
108,655✔
256
                        continue;
80,718✔
257

258
                char *p = l;
27,937✔
259
                value = strchr(p, '=');
27,937✔
260
                if (value) {
27,937✔
261
                        if (p[0] == '-') {
24,832✔
262
                                ignore_failure = true;
2,072✔
263
                                p++;
2,072✔
264
                        }
265

266
                        *value = 0;
24,832✔
267
                        value++;
24,832✔
268
                        value = strstrip(value);
24,832✔
269

270
                } else {
271
                        if (p[0] == '-')
3,105✔
272
                                /* We have a "negative match" option. Let's continue with value==NULL. */
273
                                p++;
3,105✔
274
                        else {
UNCOV
275
                                log_syntax(NULL, LOG_WARNING, pp, c, 0,
×
276
                                           "Line is not an assignment, ignoring: %s", p);
277
                                if (r == 0)
×
278
                                        r = -EINVAL;
×
UNCOV
279
                                continue;
×
280
                        }
281
                }
282

283
                p = strstrip(p);
27,937✔
284
                p = sysctl_normalize(p);
27,937✔
285

286
                /* We can't filter out globs at this point, we'll need to do that later. */
287
                if (!string_is_glob(p) &&
52,769✔
288
                    !test_prefix(p))
24,832✔
289
                        continue;
21,960✔
290

291
                existing = ordered_hashmap_get(*sysctl_options, p);
5,977✔
292
                if (existing) {
5,977✔
293
                        if (streq_ptr(value, existing->value)) {
×
294
                                existing->ignore_failure = existing->ignore_failure || ignore_failure;
×
UNCOV
295
                                continue;
×
296
                        }
297

298
                        log_debug("Overwriting earlier assignment of %s at '%s:%u'.", p, pp, c);
×
UNCOV
299
                        option_free(ordered_hashmap_remove(*sysctl_options, p));
×
300
                }
301

302
                new_option = option_new(p, value, ignore_failure);
5,977✔
303
                if (!new_option)
5,977✔
UNCOV
304
                        return log_oom();
×
305

306
                k = ordered_hashmap_ensure_put(sysctl_options, &option_hash_ops, new_option->key, new_option);
5,977✔
307
                if (k < 0)
5,977✔
UNCOV
308
                        return log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", p);
×
309

310
                TAKE_PTR(new_option);
5,977✔
311
        }
312

313
        return r;
5,173✔
314
}
315

316
static int read_credential_lines(OrderedHashmap **sysctl_options) {
1,035✔
317
        _cleanup_free_ char *j = NULL;
1,035✔
318
        const char *d;
1,035✔
319
        int r;
1,035✔
320

321
        r = get_credentials_dir(&d);
1,035✔
322
        if (r == -ENXIO)
1,035✔
323
                return 0;
324
        if (r < 0)
120✔
UNCOV
325
                return log_error_errno(r, "Failed to get credentials directory: %m");
×
326

327
        j = path_join(d, "sysctl.extra");
120✔
328
        if (!j)
120✔
UNCOV
329
                return log_oom();
×
330

331
        (void) parse_file(sysctl_options, j, /* ignore_enoent= */ true);
120✔
332
        return 0;
333
}
334

335
static int cat_config(char **files) {
×
UNCOV
336
        pager_open(arg_pager_flags);
×
337

UNCOV
338
        return cat_files(NULL, files, arg_cat_flags);
×
339
}
340

341
static int help(void) {
×
342
        _cleanup_free_ char *link = NULL;
×
UNCOV
343
        int r;
×
344

345
        r = terminal_urlify_man("systemd-sysctl.service", "8", &link);
×
346
        if (r < 0)
×
UNCOV
347
                return log_oom();
×
348

UNCOV
349
        printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
×
350
               "Applies kernel sysctl settings.\n\n"
351
               "  -h --help             Show this help\n"
352
               "     --version          Show package version\n"
353
               "     --cat-config       Show configuration files\n"
354
               "     --tldr             Show non-comment parts of configuration\n"
355
               "     --prefix=PATH      Only apply rules with the specified prefix\n"
356
               "     --no-pager         Do not pipe output into a pager\n"
357
               "\nSee the %s for details.\n",
358
               program_invocation_short_name,
359
               link);
360

361
        return 0;
362
}
363

364
static int parse_argv(int argc, char *argv[]) {
1,039✔
365

366
        enum {
1,039✔
367
                ARG_VERSION = 0x100,
368
                ARG_CAT_CONFIG,
369
                ARG_TLDR,
370
                ARG_PREFIX,
371
                ARG_NO_PAGER,
372
                ARG_STRICT,
373
        };
374

375
        static const struct option options[] = {
1,039✔
376
                { "help",       no_argument,       NULL, 'h'            },
377
                { "version",    no_argument,       NULL, ARG_VERSION    },
378
                { "cat-config", no_argument,       NULL, ARG_CAT_CONFIG },
379
                { "tldr",       no_argument,       NULL, ARG_TLDR       },
380
                { "prefix",     required_argument, NULL, ARG_PREFIX     },
381
                { "no-pager",   no_argument,       NULL, ARG_NO_PAGER   },
382
                { "strict",     no_argument,       NULL, ARG_STRICT     },
383
                {}
384
        };
385

386
        int c;
1,039✔
387

388
        assert(argc >= 0);
1,039✔
389
        assert(argv);
1,039✔
390

391
        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
4,701✔
392

393
                switch (c) {
3,662✔
394

395
                case 'h':
×
UNCOV
396
                        return help();
×
397

398
                case ARG_VERSION:
×
UNCOV
399
                        return version();
×
400

401
                case ARG_CAT_CONFIG:
×
402
                        arg_cat_flags = CAT_CONFIG_ON;
×
UNCOV
403
                        break;
×
404

405
                case ARG_TLDR:
×
406
                        arg_cat_flags = CAT_TLDR;
×
UNCOV
407
                        break;
×
408

409
                case ARG_PREFIX: {
3,660✔
410
                        const char *s;
3,660✔
411
                        char *p;
3,660✔
412

413
                        /* We used to require people to specify absolute paths
414
                         * in /proc/sys in the past. This is kinda useless, but
415
                         * we need to keep compatibility. We now support any
416
                         * sysctl name available. */
417
                        sysctl_normalize(optarg);
3,660✔
418

419
                        s = path_startswith(optarg, "/proc/sys");
3,660✔
420
                        p = strdup(s ?: optarg);
3,660✔
421
                        if (!p)
3,660✔
UNCOV
422
                                return log_oom();
×
423

424
                        if (strv_consume(&arg_prefixes, p) < 0)
3,660✔
UNCOV
425
                                return log_oom();
×
426

427
                        break;
428
                }
429

430
                case ARG_NO_PAGER:
×
431
                        arg_pager_flags |= PAGER_DISABLE;
×
UNCOV
432
                        break;
×
433

434
                case ARG_STRICT:
2✔
435
                        arg_strict = true;
2✔
436
                        break;
2✔
437

438
                case '?':
439
                        return -EINVAL;
440

441
                default:
×
UNCOV
442
                        assert_not_reached();
×
443
                }
444

445
        if (arg_cat_flags != CAT_CONFIG_OFF && argc > optind)
1,039✔
UNCOV
446
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
447
                                       "Positional arguments are not allowed with --cat-config/--tldr.");
448

449
        return 1;
450
}
451

452
static int run(int argc, char *argv[]) {
1,039✔
453
        _cleanup_ordered_hashmap_free_ OrderedHashmap *sysctl_options = NULL;
1,039✔
454
        int r;
1,039✔
455

456
        r = parse_argv(argc, argv);
1,039✔
457
        if (r <= 0)
1,039✔
458
                return r;
459

460
        log_setup();
1,039✔
461

462
        umask(0022);
1,039✔
463

464
        if (argc > optind) {
1,039✔
465
                r = 0;
466

467
                for (int i = optind; i < argc; i++)
8✔
468
                        RET_GATHER(r, parse_file(&sysctl_options, argv[i], false));
4✔
469

470
        } else {
UNCOV
471
                _cleanup_strv_free_ char **files = NULL;
×
472

473
                r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) CONF_PATHS_STRV("sysctl.d"));
1,035✔
474
                if (r < 0)
1,035✔
UNCOV
475
                        return log_error_errno(r, "Failed to enumerate sysctl.d files: %m");
×
476

477
                if (arg_cat_flags != CAT_CONFIG_OFF)
1,035✔
UNCOV
478
                        return cat_config(files);
×
479

480
                STRV_FOREACH(f, files)
6,204✔
481
                        RET_GATHER(r, parse_file(&sysctl_options, *f, true));
5,169✔
482

483
                RET_GATHER(r, read_credential_lines(&sysctl_options));
1,035✔
484
        }
485

486
        RET_GATHER(r, apply_all(sysctl_options));
1,039✔
487

488
        return r;
489
}
490

491
DEFINE_MAIN_FUNCTION(run);
1,039✔
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