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