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