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