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