]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysctl/sysctl.c
Merge pull request #9193 from keszybz/coverity
[thirdparty/systemd.git] / src / sysctl / sysctl.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6 ***/
7
8 #include <errno.h>
9 #include <getopt.h>
10 #include <limits.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15
16 #include "conf-files.h"
17 #include "def.h"
18 #include "fd-util.h"
19 #include "fileio.h"
20 #include "hashmap.h"
21 #include "log.h"
22 #include "pager.h"
23 #include "path-util.h"
24 #include "string-util.h"
25 #include "strv.h"
26 #include "sysctl-util.h"
27 #include "terminal-util.h"
28 #include "util.h"
29
30 static char **arg_prefixes = NULL;
31 static bool arg_cat_config = false;
32 static bool arg_no_pager = false;
33
34 static int apply_all(OrderedHashmap *sysctl_options) {
35 char *property, *value;
36 Iterator i;
37 int r = 0;
38
39 ORDERED_HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) {
40 int k;
41
42 k = sysctl_write(property, value);
43 if (k < 0) {
44 /* If the sysctl is not available in the kernel or we are running with reduced privileges and
45 * cannot write it, then log about the issue at LOG_NOTICE level, and proceed without
46 * failing. (EROFS is treated as a permission problem here, since that's how container managers
47 * usually protected their sysctls.) In all other cases log an error and make the tool fail. */
48
49 if (IN_SET(k, -EPERM, -EACCES, -EROFS, -ENOENT))
50 log_notice_errno(k, "Couldn't write '%s' to '%s', ignoring: %m", value, property);
51 else {
52 log_error_errno(k, "Couldn't write '%s' to '%s': %m", value, property);
53 if (r == 0)
54 r = k;
55 }
56 }
57 }
58
59 return r;
60 }
61
62 static bool test_prefix(const char *p) {
63 char **i;
64
65 if (strv_isempty(arg_prefixes))
66 return true;
67
68 STRV_FOREACH(i, arg_prefixes) {
69 const char *t;
70
71 t = path_startswith(*i, "/proc/sys/");
72 if (!t)
73 t = *i;
74 if (path_startswith(p, t))
75 return true;
76 }
77
78 return false;
79 }
80
81 static int parse_file(OrderedHashmap *sysctl_options, const char *path, bool ignore_enoent) {
82 _cleanup_fclose_ FILE *f = NULL;
83 unsigned c = 0;
84 int r;
85
86 assert(path);
87
88 r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("sysctl.d"), &f);
89 if (r < 0) {
90 if (ignore_enoent && r == -ENOENT)
91 return 0;
92
93 return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path);
94 }
95
96 log_debug("Parsing %s", path);
97 for (;;) {
98 char *p, *value, *new_value, *property, *existing;
99 _cleanup_free_ char *l = NULL;
100 void *v;
101 int k;
102
103 k = read_line(f, LONG_LINE_MAX, &l);
104 if (k == 0)
105 break;
106 if (k < 0)
107 return log_error_errno(k, "Failed to read file '%s', ignoring: %m", path);
108
109 c++;
110
111 p = strstrip(l);
112
113 if (isempty(p))
114 continue;
115 if (strchr(COMMENTS "\n", *p))
116 continue;
117
118 value = strchr(p, '=');
119 if (!value) {
120 log_error("Line is not an assignment at '%s:%u': %s", path, c, value);
121
122 if (r == 0)
123 r = -EINVAL;
124 continue;
125 }
126
127 *value = 0;
128 value++;
129
130 p = sysctl_normalize(strstrip(p));
131 value = strstrip(value);
132
133 if (!test_prefix(p))
134 continue;
135
136 existing = ordered_hashmap_get2(sysctl_options, p, &v);
137 if (existing) {
138 if (streq(value, existing))
139 continue;
140
141 log_debug("Overwriting earlier assignment of %s at '%s:%u'.", p, path, c);
142 free(ordered_hashmap_remove(sysctl_options, p));
143 free(v);
144 }
145
146 property = strdup(p);
147 if (!property)
148 return log_oom();
149
150 new_value = strdup(value);
151 if (!new_value) {
152 free(property);
153 return log_oom();
154 }
155
156 k = ordered_hashmap_put(sysctl_options, property, new_value);
157 if (k < 0) {
158 log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", property);
159 free(property);
160 free(new_value);
161 return k;
162 }
163 }
164
165 return r;
166 }
167
168 static void help(void) {
169 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
170 "Applies kernel sysctl settings.\n\n"
171 " -h --help Show this help\n"
172 " --version Show package version\n"
173 " --cat-config Show configuration files\n"
174 " --prefix=PATH Only apply rules with the specified prefix\n"
175 " --no-pager Do not pipe output into a pager\n"
176 , program_invocation_short_name);
177 }
178
179 static int parse_argv(int argc, char *argv[]) {
180
181 enum {
182 ARG_VERSION = 0x100,
183 ARG_CAT_CONFIG,
184 ARG_PREFIX,
185 ARG_NO_PAGER,
186 };
187
188 static const struct option options[] = {
189 { "help", no_argument, NULL, 'h' },
190 { "version", no_argument, NULL, ARG_VERSION },
191 { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
192 { "prefix", required_argument, NULL, ARG_PREFIX },
193 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
194 {}
195 };
196
197 int c;
198
199 assert(argc >= 0);
200 assert(argv);
201
202 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
203
204 switch (c) {
205
206 case 'h':
207 help();
208 return 0;
209
210 case ARG_VERSION:
211 return version();
212
213 case ARG_CAT_CONFIG:
214 arg_cat_config = true;
215 break;
216
217 case ARG_PREFIX: {
218 char *p;
219
220 /* We used to require people to specify absolute paths
221 * in /proc/sys in the past. This is kinda useless, but
222 * we need to keep compatibility. We now support any
223 * sysctl name available. */
224 sysctl_normalize(optarg);
225
226 if (path_startswith(optarg, "/proc/sys"))
227 p = strdup(optarg);
228 else
229 p = strappend("/proc/sys/", optarg);
230 if (!p)
231 return log_oom();
232
233 if (strv_consume(&arg_prefixes, p) < 0)
234 return log_oom();
235
236 break;
237 }
238
239 case ARG_NO_PAGER:
240 arg_no_pager = true;
241 break;
242
243 case '?':
244 return -EINVAL;
245
246 default:
247 assert_not_reached("Unhandled option");
248 }
249
250 if (arg_cat_config && argc > optind) {
251 log_error("Positional arguments are not allowed with --cat-config");
252 return -EINVAL;
253 }
254
255 return 1;
256 }
257
258 int main(int argc, char *argv[]) {
259 OrderedHashmap *sysctl_options = NULL;
260 int r = 0, k;
261
262 r = parse_argv(argc, argv);
263 if (r <= 0)
264 goto finish;
265
266 log_set_target(LOG_TARGET_AUTO);
267 log_parse_environment();
268 log_open();
269
270 umask(0022);
271
272 sysctl_options = ordered_hashmap_new(&path_hash_ops);
273 if (!sysctl_options) {
274 r = log_oom();
275 goto finish;
276 }
277
278 r = 0;
279
280 if (argc > optind) {
281 int i;
282
283 for (i = optind; i < argc; i++) {
284 k = parse_file(sysctl_options, argv[i], false);
285 if (k < 0 && r == 0)
286 r = k;
287 }
288 } else {
289 _cleanup_strv_free_ char **files = NULL;
290 char **f;
291
292 r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) CONF_PATHS_STRV("sysctl.d"));
293 if (r < 0) {
294 log_error_errno(r, "Failed to enumerate sysctl.d files: %m");
295 goto finish;
296 }
297
298 if (arg_cat_config) {
299 (void) pager_open(arg_no_pager, false);
300
301 r = cat_files(NULL, files, 0);
302 goto finish;
303 }
304
305 STRV_FOREACH(f, files) {
306 k = parse_file(sysctl_options, *f, true);
307 if (k < 0 && r == 0)
308 r = k;
309 }
310 }
311
312 k = apply_all(sysctl_options);
313 if (k < 0 && r == 0)
314 r = k;
315
316 finish:
317 pager_close();
318
319 ordered_hashmap_free_free_free(sysctl_options);
320 strv_free(arg_prefixes);
321
322 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
323 }