]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysctl/sysctl.c
Merge pull request #14585 from keszybz/sysctl-downgrade-messages
[thirdparty/systemd.git] / src / sysctl / sysctl.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
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 "conf-files.h"
13 #include "def.h"
14 #include "errno-util.h"
15 #include "fd-util.h"
16 #include "fileio.h"
17 #include "hashmap.h"
18 #include "log.h"
19 #include "main-func.h"
20 #include "pager.h"
21 #include "path-util.h"
22 #include "pretty-print.h"
23 #include "string-util.h"
24 #include "strv.h"
25 #include "sysctl-util.h"
26
27 static char **arg_prefixes = NULL;
28 static bool arg_cat_config = false;
29 static PagerFlags arg_pager_flags = 0;
30
31 STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep);
32
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
52 static Option *option_new(
53 const char *key,
54 const char *value,
55 bool ignore_failure) {
56
57 _cleanup_(option_freep) Option *o = NULL;
58
59 assert(key);
60 assert(value);
61
62 o = new(Option, 1);
63 if (!o)
64 return NULL;
65
66 *o = (Option) {
67 .key = strdup(key),
68 .value = strdup(value),
69 .ignore_failure = ignore_failure,
70 };
71
72 if (!o->key || !o->value)
73 return NULL;
74
75 return TAKE_PTR(o);
76 }
77
78 static int apply_all(OrderedHashmap *sysctl_options) {
79 Option *option;
80 Iterator i;
81 int r = 0;
82
83 ORDERED_HASHMAP_FOREACH(option, sysctl_options, i) {
84 int k;
85
86 k = sysctl_write(option->key, option->value);
87 if (k < 0) {
88 /* If the sysctl is not available in the kernel or we are running with reduced
89 * privileges and cannot write it, then log about the issue, and proceed without
90 * failing. (EROFS is treated as a permission problem here, since that's how
91 * container managers usually protected their sysctls.) In all other cases log an
92 * error and make the tool fail. */
93
94 if (option->ignore_failure || k == -EROFS || ERRNO_IS_PRIVILEGE(k))
95 log_debug_errno(k, "Couldn't write '%s' to '%s', ignoring: %m", option->value, option->key);
96 else if (k == -ENOENT)
97 log_info_errno(k, "Couldn't write '%s' to '%s', ignoring: %m", option->value, option->key);
98 else {
99 log_error_errno(k, "Couldn't write '%s' to '%s': %m", option->value, option->key);
100 if (r == 0)
101 r = k;
102 }
103 }
104 }
105
106 return r;
107 }
108
109 static bool test_prefix(const char *p) {
110 char **i;
111
112 if (strv_isempty(arg_prefixes))
113 return true;
114
115 STRV_FOREACH(i, arg_prefixes) {
116 const char *t;
117
118 t = path_startswith(*i, "/proc/sys/");
119 if (!t)
120 t = *i;
121 if (path_startswith(p, t))
122 return true;
123 }
124
125 return false;
126 }
127
128 static int parse_file(OrderedHashmap **sysctl_options, const char *path, bool ignore_enoent) {
129 _cleanup_fclose_ FILE *f = NULL;
130 unsigned c = 0;
131 int r;
132
133 assert(path);
134
135 r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("sysctl.d"), &f);
136 if (r < 0) {
137 if (ignore_enoent && r == -ENOENT)
138 return 0;
139
140 return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path);
141 }
142
143 log_debug("Parsing %s", path);
144 for (;;) {
145 _cleanup_(option_freep) Option *new_option = NULL;
146 _cleanup_free_ char *l = NULL;
147 bool ignore_failure;
148 Option *existing;
149 char *p, *value;
150 int k;
151
152 k = read_line(f, LONG_LINE_MAX, &l);
153 if (k == 0)
154 break;
155 if (k < 0)
156 return log_error_errno(k, "Failed to read file '%s', ignoring: %m", path);
157
158 c++;
159
160 p = strstrip(l);
161
162 if (isempty(p))
163 continue;
164 if (strchr(COMMENTS "\n", *p))
165 continue;
166
167 value = strchr(p, '=');
168 if (!value) {
169 log_syntax(NULL, LOG_WARNING, path, c, 0, "Line is not an assignment, ignoring: %s", p);
170 if (r == 0)
171 r = -EINVAL;
172 continue;
173 }
174
175 *value = 0;
176 value++;
177
178 p = strstrip(p);
179 ignore_failure = p[0] == '-';
180 if (ignore_failure)
181 p++;
182
183 p = sysctl_normalize(p);
184 value = strstrip(value);
185
186 if (!test_prefix(p))
187 continue;
188
189 if (ordered_hashmap_ensure_allocated(sysctl_options, &option_hash_ops) < 0)
190 return log_oom();
191
192 existing = ordered_hashmap_get(*sysctl_options, p);
193 if (existing) {
194 if (streq(value, existing->value)) {
195 existing->ignore_failure = existing->ignore_failure || ignore_failure;
196 continue;
197 }
198
199 log_debug("Overwriting earlier assignment of %s at '%s:%u'.", p, path, c);
200 option_free(ordered_hashmap_remove(*sysctl_options, p));
201 }
202
203 new_option = option_new(p, value, ignore_failure);
204 if (!new_option)
205 return log_oom();
206
207 k = ordered_hashmap_put(*sysctl_options, new_option->key, new_option);
208 if (k < 0)
209 return log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", p);
210
211 TAKE_PTR(new_option);
212 }
213
214 return r;
215 }
216
217 static int help(void) {
218 _cleanup_free_ char *link = NULL;
219 int r;
220
221 r = terminal_urlify_man("systemd-sysctl.service", "8", &link);
222 if (r < 0)
223 return log_oom();
224
225 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
226 "Applies kernel sysctl settings.\n\n"
227 " -h --help Show this help\n"
228 " --version Show package version\n"
229 " --cat-config Show configuration files\n"
230 " --prefix=PATH Only apply rules with the specified prefix\n"
231 " --no-pager Do not pipe output into a pager\n"
232 "\nSee the %s for details.\n"
233 , program_invocation_short_name
234 , link
235 );
236
237 return 0;
238 }
239
240 static int parse_argv(int argc, char *argv[]) {
241
242 enum {
243 ARG_VERSION = 0x100,
244 ARG_CAT_CONFIG,
245 ARG_PREFIX,
246 ARG_NO_PAGER,
247 };
248
249 static const struct option options[] = {
250 { "help", no_argument, NULL, 'h' },
251 { "version", no_argument, NULL, ARG_VERSION },
252 { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
253 { "prefix", required_argument, NULL, ARG_PREFIX },
254 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
255 {}
256 };
257
258 int c;
259
260 assert(argc >= 0);
261 assert(argv);
262
263 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
264
265 switch (c) {
266
267 case 'h':
268 return help();
269
270 case ARG_VERSION:
271 return version();
272
273 case ARG_CAT_CONFIG:
274 arg_cat_config = true;
275 break;
276
277 case ARG_PREFIX: {
278 char *p;
279
280 /* We used to require people to specify absolute paths
281 * in /proc/sys in the past. This is kinda useless, but
282 * we need to keep compatibility. We now support any
283 * sysctl name available. */
284 sysctl_normalize(optarg);
285
286 if (path_startswith(optarg, "/proc/sys"))
287 p = strdup(optarg);
288 else
289 p = path_join("/proc/sys", optarg);
290 if (!p)
291 return log_oom();
292
293 if (strv_consume(&arg_prefixes, p) < 0)
294 return log_oom();
295
296 break;
297 }
298
299 case ARG_NO_PAGER:
300 arg_pager_flags |= PAGER_DISABLE;
301 break;
302
303 case '?':
304 return -EINVAL;
305
306 default:
307 assert_not_reached("Unhandled option");
308 }
309
310 if (arg_cat_config && argc > optind)
311 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
312 "Positional arguments are not allowed with --cat-config");
313
314 return 1;
315 }
316
317 static int run(int argc, char *argv[]) {
318 _cleanup_(ordered_hashmap_freep) OrderedHashmap *sysctl_options = NULL;
319 int r, k;
320
321 r = parse_argv(argc, argv);
322 if (r <= 0)
323 return r;
324
325 log_setup_service();
326
327 umask(0022);
328
329 if (argc > optind) {
330 int i;
331
332 r = 0;
333
334 for (i = optind; i < argc; i++) {
335 k = parse_file(&sysctl_options, argv[i], false);
336 if (k < 0 && r == 0)
337 r = k;
338 }
339 } else {
340 _cleanup_strv_free_ char **files = NULL;
341 char **f;
342
343 r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) CONF_PATHS_STRV("sysctl.d"));
344 if (r < 0)
345 return log_error_errno(r, "Failed to enumerate sysctl.d files: %m");
346
347 if (arg_cat_config) {
348 (void) pager_open(arg_pager_flags);
349
350 return cat_files(NULL, files, 0);
351 }
352
353 STRV_FOREACH(f, files) {
354 k = parse_file(&sysctl_options, *f, true);
355 if (k < 0 && r == 0)
356 r = k;
357 }
358 }
359
360 k = apply_all(sysctl_options);
361 if (k < 0 && r == 0)
362 r = k;
363
364 return r;
365 }
366
367 DEFINE_MAIN_FUNCTION(run);