]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
8e1bd70d | 2 | |
7a2a0b90 | 3 | #include <getopt.h> |
3f6fd1ba | 4 | #include <stdio.h> |
ca78ad1d | 5 | #include <sys/stat.h> |
8e1bd70d | 6 | |
b78d73fa | 7 | #include "alloc-util.h" |
d6b4d1c7 | 8 | #include "build.h" |
2c21044f | 9 | #include "conf-files.h" |
28db6fbf | 10 | #include "constants.h" |
39f0d1d2 | 11 | #include "creds-util.h" |
32458cc9 | 12 | #include "errno-util.h" |
3ffd4af2 | 13 | #include "fd-util.h" |
a5c32cff | 14 | #include "fileio.h" |
e0f42479 | 15 | #include "glob-util.h" |
3f6fd1ba LP |
16 | #include "hashmap.h" |
17 | #include "log.h" | |
8eb42d90 | 18 | #include "main-func.h" |
dcd5c891 | 19 | #include "pager.h" |
3f6fd1ba | 20 | #include "path-util.h" |
294bf0c3 | 21 | #include "pretty-print.h" |
07630cea | 22 | #include "string-util.h" |
3f6fd1ba | 23 | #include "strv.h" |
88a60da0 | 24 | #include "sysctl-util.h" |
8e1bd70d | 25 | |
fabe5c0e | 26 | static char **arg_prefixes = NULL; |
f80f5dd6 | 27 | static CatFlags arg_cat_flags = CAT_CONFIG_OFF; |
e88748c1 | 28 | static bool arg_strict = false; |
0221d68a | 29 | static PagerFlags arg_pager_flags = 0; |
8e1bd70d | 30 | |
fd8bdbc7 LP |
31 | STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep); |
32 | ||
dec02d6e LP |
33 | typedef struct Option { |
34 | char *key; | |
35 | char *value; | |
36 | bool ignore_failure; | |
37 | } Option; | |
38 | ||
39 | static Option *option_free(Option *o) { | |
40 | if (!o) | |
41 | return NULL; | |
42 | ||
43 | free(o->key); | |
44 | free(o->value); | |
45 | ||
46 | return mfree(o); | |
47 | } | |
48 | ||
49 | DEFINE_TRIVIAL_CLEANUP_FUNC(Option*, option_free); | |
50 | DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(option_hash_ops, char, string_hash_func, string_compare_func, Option, option_free); | |
51 | ||
e0f42479 | 52 | static bool test_prefix(const char *p) { |
e0f42479 ZJS |
53 | if (strv_isempty(arg_prefixes)) |
54 | return true; | |
55 | ||
c01404fd | 56 | return path_startswith_strv(p, arg_prefixes); |
e0f42479 ZJS |
57 | } |
58 | ||
dec02d6e LP |
59 | static Option *option_new( |
60 | const char *key, | |
61 | const char *value, | |
62 | bool ignore_failure) { | |
63 | ||
64 | _cleanup_(option_freep) Option *o = NULL; | |
65 | ||
66 | assert(key); | |
dec02d6e LP |
67 | |
68 | o = new(Option, 1); | |
69 | if (!o) | |
70 | return NULL; | |
71 | ||
72 | *o = (Option) { | |
73 | .key = strdup(key), | |
e0f42479 | 74 | .value = value ? strdup(value) : NULL, |
dec02d6e LP |
75 | .ignore_failure = ignore_failure, |
76 | }; | |
77 | ||
e0f42479 ZJS |
78 | if (!o->key) |
79 | return NULL; | |
80 | if (value && !o->value) | |
dec02d6e LP |
81 | return NULL; |
82 | ||
83 | return TAKE_PTR(o); | |
84 | } | |
85 | ||
9ec8c82b | 86 | static int sysctl_write_or_warn(const char *key, const char *value, bool ignore_failure, bool ignore_enoent) { |
e0f42479 ZJS |
87 | int r; |
88 | ||
89 | r = sysctl_write(key, value); | |
90 | if (r < 0) { | |
e88748c1 QD |
91 | /* Proceed without failing if ignore_failure is true. |
92 | * If the sysctl is not available in the kernel or we are running with reduced privileges and | |
93 | * cannot write it, then log about the issue, and proceed without failing. Unless strict mode | |
94 | * (arg_strict = true) is enabled, in which case we should fail. (EROFS is treated as a | |
95 | * permission problem here, since that's how container managers usually protected their | |
96 | * sysctls.) | |
97 | * In all other cases log an error and make the tool fail. */ | |
98 | if (ignore_failure || (!arg_strict && (r == -EROFS || ERRNO_IS_PRIVILEGE(r)))) | |
e0f42479 | 99 | log_debug_errno(r, "Couldn't write '%s' to '%s', ignoring: %m", value, key); |
9ec8c82b | 100 | else if (ignore_enoent && r == -ENOENT) |
1805fbcf | 101 | log_warning_errno(r, "Couldn't write '%s' to '%s', ignoring: %m", value, key); |
e0f42479 ZJS |
102 | else |
103 | return log_error_errno(r, "Couldn't write '%s' to '%s': %m", value, key); | |
104 | } | |
105 | ||
106 | return 0; | |
107 | } | |
108 | ||
9ec8c82b | 109 | static int apply_glob_option_with_prefix(OrderedHashmap *sysctl_options, Option *option, const char *prefix) { |
7177ac45 YW |
110 | _cleanup_strv_free_ char **paths = NULL; |
111 | _cleanup_free_ char *pattern = NULL; | |
9ef648cc | 112 | int r; |
fabe5c0e | 113 | |
7177ac45 YW |
114 | assert(sysctl_options); |
115 | assert(option); | |
86fc77c4 | 116 | |
9ec8c82b YW |
117 | if (prefix) { |
118 | _cleanup_free_ char *key = NULL; | |
119 | ||
120 | r = path_glob_can_match(option->key, prefix, &key); | |
121 | if (r < 0) | |
122 | return log_error_errno(r, "Failed to check if the glob '%s' matches prefix '%s': %m", | |
123 | option->key, prefix); | |
124 | if (r == 0) { | |
125 | log_debug("The glob '%s' does not match prefix '%s'.", option->key, prefix); | |
126 | return 0; | |
127 | } | |
128 | ||
129 | log_debug("The glob '%s' is prefixed with '%s': '%s'", option->key, prefix, key); | |
130 | ||
131 | if (!string_is_glob(key)) { | |
132 | /* The prefixed pattern is not glob anymore. Let's skip to call glob(). */ | |
133 | if (ordered_hashmap_contains(sysctl_options, key)) { | |
134 | log_debug("Not setting %s (explicit setting exists).", key); | |
135 | return 0; | |
136 | } | |
137 | ||
138 | return sysctl_write_or_warn(key, option->value, | |
139 | /* ignore_failure = */ option->ignore_failure, | |
140 | /* ignore_enoent = */ true); | |
141 | } | |
142 | ||
143 | pattern = path_join("/proc/sys", key); | |
144 | } else | |
145 | pattern = path_join("/proc/sys", option->key); | |
7177ac45 YW |
146 | if (!pattern) |
147 | return log_oom(); | |
e50b33be | 148 | |
7177ac45 YW |
149 | r = glob_extend(&paths, pattern, GLOB_NOCHECK); |
150 | if (r < 0) { | |
151 | if (r == -ENOENT) { | |
152 | log_debug("No match for glob: %s", option->key); | |
153 | return 0; | |
154 | } | |
155 | if (option->ignore_failure || ERRNO_IS_PRIVILEGE(r)) { | |
156 | log_debug_errno(r, "Failed to resolve glob '%s', ignoring: %m", option->key); | |
157 | return 0; | |
158 | } else | |
159 | return log_error_errno(r, "Couldn't resolve glob '%s': %m", option->key); | |
160 | } | |
161 | ||
162 | STRV_FOREACH(s, paths) { | |
163 | const char *key; | |
86fc77c4 | 164 | |
7177ac45 | 165 | assert_se(key = path_startswith(*s, "/proc/sys")); |
9c37b41c | 166 | |
7177ac45 YW |
167 | if (ordered_hashmap_contains(sysctl_options, key)) { |
168 | log_debug("Not setting %s (explicit setting exists).", key); | |
169 | continue; | |
170 | } | |
9c37b41c | 171 | |
9ef648cc ZJS |
172 | RET_GATHER(r, |
173 | sysctl_write_or_warn(key, option->value, | |
174 | /* ignore_failure = */ option->ignore_failure, | |
175 | /* ignore_enoent = */ !arg_strict)); | |
9ec8c82b YW |
176 | } |
177 | ||
178 | return r; | |
179 | } | |
180 | ||
181 | static int apply_glob_option(OrderedHashmap *sysctl_options, Option *option) { | |
9ef648cc | 182 | int r = 0; |
9ec8c82b YW |
183 | |
184 | if (strv_isempty(arg_prefixes)) | |
185 | return apply_glob_option_with_prefix(sysctl_options, option, NULL); | |
186 | ||
9ef648cc ZJS |
187 | STRV_FOREACH(i, arg_prefixes) |
188 | RET_GATHER(r, apply_glob_option_with_prefix(sysctl_options, option, *i)); | |
7177ac45 YW |
189 | return r; |
190 | } | |
e0f42479 | 191 | |
7177ac45 YW |
192 | static int apply_all(OrderedHashmap *sysctl_options) { |
193 | Option *option; | |
194 | int r = 0; | |
e0f42479 | 195 | |
7177ac45 YW |
196 | ORDERED_HASHMAP_FOREACH(option, sysctl_options) { |
197 | int k; | |
e0f42479 | 198 | |
7177ac45 YW |
199 | /* Ignore "negative match" options, they are there only to exclude stuff from globs. */ |
200 | if (!option->value) | |
201 | continue; | |
e0f42479 | 202 | |
7177ac45 YW |
203 | if (string_is_glob(option->key)) |
204 | k = apply_glob_option(sysctl_options, option); | |
205 | else | |
9ec8c82b YW |
206 | k = sysctl_write_or_warn(option->key, option->value, |
207 | /* ignore_failure = */ option->ignore_failure, | |
208 | /* ignore_enoent = */ !arg_strict); | |
9ef648cc | 209 | RET_GATHER(r, k); |
9c37b41c LP |
210 | } |
211 | ||
e0f42479 | 212 | return r; |
9c37b41c LP |
213 | } |
214 | ||
b2ae4d9e | 215 | static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ignore_enoent) { |
fabe5c0e | 216 | _cleanup_fclose_ FILE *f = NULL; |
2708160c | 217 | _cleanup_free_ char *pp = NULL; |
98bf5011 | 218 | unsigned c = 0; |
fabe5c0e | 219 | int r; |
8e1bd70d LP |
220 | |
221 | assert(path); | |
222 | ||
2708160c | 223 | r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("sysctl.d"), &f, &pp); |
fabe5c0e | 224 | if (r < 0) { |
6f6fad96 | 225 | if (ignore_enoent && r == -ENOENT) |
c1b664d0 LP |
226 | return 0; |
227 | ||
8d3d7072 | 228 | return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path); |
8e1bd70d LP |
229 | } |
230 | ||
2708160c | 231 | log_debug("Parsing %s", pp); |
4f14f2bb | 232 | for (;;) { |
dec02d6e | 233 | _cleanup_(option_freep) Option *new_option = NULL; |
a668bfe8 | 234 | _cleanup_free_ char *l = NULL; |
e0f42479 | 235 | bool ignore_failure = false; |
dec02d6e | 236 | Option *existing; |
0ff6ff2b | 237 | char *value; |
fabe5c0e | 238 | int k; |
548f6937 | 239 | |
0ff6ff2b | 240 | k = read_stripped_line(f, LONG_LINE_MAX, &l); |
a668bfe8 TSH |
241 | if (k == 0) |
242 | break; | |
a668bfe8 | 243 | if (k < 0) |
2708160c | 244 | return log_error_errno(k, "Failed to read file '%s', ignoring: %m", pp); |
8e1bd70d | 245 | |
98bf5011 LP |
246 | c++; |
247 | ||
0ff6ff2b | 248 | if (isempty(l)) |
548f6937 | 249 | continue; |
0ff6ff2b | 250 | if (strchr(COMMENTS, l[0])) |
8e1bd70d LP |
251 | continue; |
252 | ||
0ff6ff2b | 253 | char *p = l; |
86fc77c4 | 254 | value = strchr(p, '='); |
e0f42479 ZJS |
255 | if (value) { |
256 | if (p[0] == '-') { | |
257 | ignore_failure = true; | |
258 | p++; | |
259 | } | |
8e1bd70d | 260 | |
e0f42479 ZJS |
261 | *value = 0; |
262 | value++; | |
263 | value = strstrip(value); | |
8e1bd70d | 264 | |
e0f42479 ZJS |
265 | } else { |
266 | if (p[0] == '-') | |
267 | /* We have a "negative match" option. Let's continue with value==NULL. */ | |
268 | p++; | |
269 | else { | |
2708160c | 270 | log_syntax(NULL, LOG_WARNING, pp, c, 0, |
e0f42479 ZJS |
271 | "Line is not an assignment, ignoring: %s", p); |
272 | if (r == 0) | |
273 | r = -EINVAL; | |
274 | continue; | |
275 | } | |
276 | } | |
dec02d6e | 277 | |
e0f42479 | 278 | p = strstrip(p); |
dec02d6e | 279 | p = sysctl_normalize(p); |
fabe5c0e | 280 | |
e0f42479 ZJS |
281 | /* We can't filter out globs at this point, we'll need to do that later. */ |
282 | if (!string_is_glob(p) && | |
283 | !test_prefix(p)) | |
b99802f7 | 284 | continue; |
b99802f7 | 285 | |
b2ae4d9e | 286 | existing = ordered_hashmap_get(*sysctl_options, p); |
fabe5c0e | 287 | if (existing) { |
db99904b | 288 | if (streq_ptr(value, existing->value)) { |
dec02d6e | 289 | existing->ignore_failure = existing->ignore_failure || ignore_failure; |
04bf3c1a | 290 | continue; |
dec02d6e | 291 | } |
fabe5c0e | 292 | |
2708160c | 293 | log_debug("Overwriting earlier assignment of %s at '%s:%u'.", p, pp, c); |
b2ae4d9e | 294 | option_free(ordered_hashmap_remove(*sysctl_options, p)); |
86fc77c4 MS |
295 | } |
296 | ||
dec02d6e LP |
297 | new_option = option_new(p, value, ignore_failure); |
298 | if (!new_option) | |
fabe5c0e LP |
299 | return log_oom(); |
300 | ||
350ffa97 | 301 | k = ordered_hashmap_ensure_put(sysctl_options, &option_hash_ops, new_option->key, new_option); |
dec02d6e LP |
302 | if (k < 0) |
303 | return log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", p); | |
86fc77c4 | 304 | |
dec02d6e | 305 | TAKE_PTR(new_option); |
8e1bd70d LP |
306 | } |
307 | ||
c1b664d0 | 308 | return r; |
8e1bd70d LP |
309 | } |
310 | ||
39f0d1d2 LP |
311 | static int read_credential_lines(OrderedHashmap **sysctl_options) { |
312 | _cleanup_free_ char *j = NULL; | |
313 | const char *d; | |
314 | int r; | |
315 | ||
316 | r = get_credentials_dir(&d); | |
317 | if (r == -ENXIO) | |
318 | return 0; | |
319 | if (r < 0) | |
320 | return log_error_errno(r, "Failed to get credentials directory: %m"); | |
321 | ||
322 | j = path_join(d, "sysctl.extra"); | |
323 | if (!j) | |
324 | return log_oom(); | |
325 | ||
326 | (void) parse_file(sysctl_options, j, /* ignore_enoent= */ true); | |
327 | return 0; | |
328 | } | |
329 | ||
f80f5dd6 ZJS |
330 | static int cat_config(char **files) { |
331 | pager_open(arg_pager_flags); | |
332 | ||
333 | return cat_files(NULL, files, arg_cat_flags); | |
334 | } | |
335 | ||
37ec0fdd LP |
336 | static int help(void) { |
337 | _cleanup_free_ char *link = NULL; | |
338 | int r; | |
339 | ||
340 | r = terminal_urlify_man("systemd-sysctl.service", "8", &link); | |
341 | if (r < 0) | |
342 | return log_oom(); | |
343 | ||
7a2a0b90 LP |
344 | printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" |
345 | "Applies kernel sysctl settings.\n\n" | |
346 | " -h --help Show this help\n" | |
eb9da376 | 347 | " --version Show package version\n" |
3c51c626 | 348 | " --cat-config Show configuration files\n" |
f80f5dd6 | 349 | " --tldr Show non-comment parts of configuration\n" |
0e1f5792 | 350 | " --prefix=PATH Only apply rules with the specified prefix\n" |
dcd5c891 | 351 | " --no-pager Do not pipe output into a pager\n" |
bc556335 DDM |
352 | "\nSee the %s for details.\n", |
353 | program_invocation_short_name, | |
354 | link); | |
37ec0fdd LP |
355 | |
356 | return 0; | |
7a2a0b90 LP |
357 | } |
358 | ||
359 | static int parse_argv(int argc, char *argv[]) { | |
360 | ||
361 | enum { | |
eb9da376 | 362 | ARG_VERSION = 0x100, |
3c51c626 | 363 | ARG_CAT_CONFIG, |
f80f5dd6 | 364 | ARG_TLDR, |
3c51c626 | 365 | ARG_PREFIX, |
dcd5c891 | 366 | ARG_NO_PAGER, |
e88748c1 | 367 | ARG_STRICT, |
7a2a0b90 LP |
368 | }; |
369 | ||
370 | static const struct option options[] = { | |
3c51c626 ZJS |
371 | { "help", no_argument, NULL, 'h' }, |
372 | { "version", no_argument, NULL, ARG_VERSION }, | |
373 | { "cat-config", no_argument, NULL, ARG_CAT_CONFIG }, | |
f80f5dd6 | 374 | { "tldr", no_argument, NULL, ARG_TLDR }, |
3c51c626 | 375 | { "prefix", required_argument, NULL, ARG_PREFIX }, |
dcd5c891 | 376 | { "no-pager", no_argument, NULL, ARG_NO_PAGER }, |
e88748c1 | 377 | { "strict", no_argument, NULL, ARG_STRICT }, |
eb9da376 | 378 | {} |
7a2a0b90 LP |
379 | }; |
380 | ||
381 | int c; | |
382 | ||
383 | assert(argc >= 0); | |
384 | assert(argv); | |
385 | ||
601185b4 | 386 | while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) |
7a2a0b90 LP |
387 | |
388 | switch (c) { | |
389 | ||
390 | case 'h': | |
37ec0fdd | 391 | return help(); |
eb9da376 LP |
392 | |
393 | case ARG_VERSION: | |
3f6fd1ba | 394 | return version(); |
7a2a0b90 | 395 | |
3c51c626 | 396 | case ARG_CAT_CONFIG: |
f80f5dd6 ZJS |
397 | arg_cat_flags = CAT_CONFIG_ON; |
398 | break; | |
399 | ||
400 | case ARG_TLDR: | |
401 | arg_cat_flags = CAT_TLDR; | |
3c51c626 ZJS |
402 | break; |
403 | ||
7a2a0b90 | 404 | case ARG_PREFIX: { |
c01404fd | 405 | const char *s; |
7a2a0b90 LP |
406 | char *p; |
407 | ||
0e1f5792 DH |
408 | /* We used to require people to specify absolute paths |
409 | * in /proc/sys in the past. This is kinda useless, but | |
410 | * we need to keep compatibility. We now support any | |
411 | * sysctl name available. */ | |
88a60da0 | 412 | sysctl_normalize(optarg); |
e50b33be | 413 | |
c01404fd YW |
414 | s = path_startswith(optarg, "/proc/sys"); |
415 | p = strdup(s ?: optarg); | |
0e1f5792 DH |
416 | if (!p) |
417 | return log_oom(); | |
e50b33be | 418 | |
0e1f5792 | 419 | if (strv_consume(&arg_prefixes, p) < 0) |
0d0f0c50 | 420 | return log_oom(); |
f68c5a70 | 421 | |
7a2a0b90 LP |
422 | break; |
423 | } | |
424 | ||
dcd5c891 | 425 | case ARG_NO_PAGER: |
0221d68a | 426 | arg_pager_flags |= PAGER_DISABLE; |
dcd5c891 LP |
427 | break; |
428 | ||
e88748c1 QD |
429 | case ARG_STRICT: |
430 | arg_strict = true; | |
431 | break; | |
432 | ||
7a2a0b90 LP |
433 | case '?': |
434 | return -EINVAL; | |
435 | ||
436 | default: | |
04499a70 | 437 | assert_not_reached(); |
7a2a0b90 | 438 | } |
7a2a0b90 | 439 | |
f80f5dd6 | 440 | if (arg_cat_flags != CAT_CONFIG_OFF && argc > optind) |
baaa35ad | 441 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
f80f5dd6 | 442 | "Positional arguments are not allowed with --cat-config/--tldr."); |
3c51c626 | 443 | |
7a2a0b90 LP |
444 | return 1; |
445 | } | |
446 | ||
8eb42d90 | 447 | static int run(int argc, char *argv[]) { |
5d2a48da | 448 | _cleanup_ordered_hashmap_free_ OrderedHashmap *sysctl_options = NULL; |
9ef648cc | 449 | int r; |
8e1bd70d | 450 | |
7a2a0b90 LP |
451 | r = parse_argv(argc, argv); |
452 | if (r <= 0) | |
a34c79d0 | 453 | return r; |
7a2a0b90 | 454 | |
d2acb93d | 455 | log_setup(); |
8e1bd70d | 456 | |
4c12626c LP |
457 | umask(0022); |
458 | ||
de19ece7 | 459 | if (argc > optind) { |
2de30233 LP |
460 | r = 0; |
461 | ||
9ef648cc ZJS |
462 | for (int i = optind; i < argc; i++) |
463 | RET_GATHER(r, parse_file(&sysctl_options, argv[i], false)); | |
464 | ||
de19ece7 | 465 | } else { |
fabe5c0e | 466 | _cleanup_strv_free_ char **files = NULL; |
c1b664d0 | 467 | |
a826d4f7 | 468 | r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) CONF_PATHS_STRV("sysctl.d")); |
a34c79d0 ZJS |
469 | if (r < 0) |
470 | return log_error_errno(r, "Failed to enumerate sysctl.d files: %m"); | |
db1413d7 | 471 | |
f80f5dd6 ZJS |
472 | if (arg_cat_flags != CAT_CONFIG_OFF) |
473 | return cat_config(files); | |
3c51c626 | 474 | |
9ef648cc ZJS |
475 | STRV_FOREACH(f, files) |
476 | RET_GATHER(r, parse_file(&sysctl_options, *f, true)); | |
39f0d1d2 | 477 | |
9ef648cc | 478 | RET_GATHER(r, read_credential_lines(&sysctl_options)); |
8e1bd70d | 479 | } |
86fc77c4 | 480 | |
9ef648cc | 481 | RET_GATHER(r, apply_all(sysctl_options)); |
0187f62b | 482 | |
8eb42d90 | 483 | return r; |
8e1bd70d | 484 | } |
8eb42d90 LP |
485 | |
486 | DEFINE_MAIN_FUNCTION(run); |