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