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