]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysctl/sysctl.c
tree-wide: remove Lennart's copyright lines
[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 bool arg_no_pager = false;
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 void help(void) {
164 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
165 "Applies kernel sysctl settings.\n\n"
166 " -h --help Show this help\n"
167 " --version Show package version\n"
168 " --cat-config Show configuration files\n"
169 " --prefix=PATH Only apply rules with the specified prefix\n"
170 " --no-pager Do not pipe output into a pager\n"
171 , program_invocation_short_name);
172 }
173
174 static int parse_argv(int argc, char *argv[]) {
175
176 enum {
177 ARG_VERSION = 0x100,
178 ARG_CAT_CONFIG,
179 ARG_PREFIX,
180 ARG_NO_PAGER,
181 };
182
183 static const struct option options[] = {
184 { "help", no_argument, NULL, 'h' },
185 { "version", no_argument, NULL, ARG_VERSION },
186 { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
187 { "prefix", required_argument, NULL, ARG_PREFIX },
188 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
189 {}
190 };
191
192 int c;
193
194 assert(argc >= 0);
195 assert(argv);
196
197 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
198
199 switch (c) {
200
201 case 'h':
202 help();
203 return 0;
204
205 case ARG_VERSION:
206 return version();
207
208 case ARG_CAT_CONFIG:
209 arg_cat_config = true;
210 break;
211
212 case ARG_PREFIX: {
213 char *p;
214
215 /* We used to require people to specify absolute paths
216 * in /proc/sys in the past. This is kinda useless, but
217 * we need to keep compatibility. We now support any
218 * sysctl name available. */
219 sysctl_normalize(optarg);
220
221 if (path_startswith(optarg, "/proc/sys"))
222 p = strdup(optarg);
223 else
224 p = strappend("/proc/sys/", optarg);
225 if (!p)
226 return log_oom();
227
228 if (strv_consume(&arg_prefixes, p) < 0)
229 return log_oom();
230
231 break;
232 }
233
234 case ARG_NO_PAGER:
235 arg_no_pager = true;
236 break;
237
238 case '?':
239 return -EINVAL;
240
241 default:
242 assert_not_reached("Unhandled option");
243 }
244
245 if (arg_cat_config && argc > optind) {
246 log_error("Positional arguments are not allowed with --cat-config");
247 return -EINVAL;
248 }
249
250 return 1;
251 }
252
253 int main(int argc, char *argv[]) {
254 OrderedHashmap *sysctl_options = NULL;
255 int r = 0, k;
256
257 r = parse_argv(argc, argv);
258 if (r <= 0)
259 goto finish;
260
261 log_set_target(LOG_TARGET_AUTO);
262 log_parse_environment();
263 log_open();
264
265 umask(0022);
266
267 sysctl_options = ordered_hashmap_new(&path_hash_ops);
268 if (!sysctl_options) {
269 r = log_oom();
270 goto finish;
271 }
272
273 r = 0;
274
275 if (argc > optind) {
276 int i;
277
278 for (i = optind; i < argc; i++) {
279 k = parse_file(sysctl_options, argv[i], false);
280 if (k < 0 && r == 0)
281 r = k;
282 }
283 } else {
284 _cleanup_strv_free_ char **files = NULL;
285 char **f;
286
287 r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) CONF_PATHS_STRV("sysctl.d"));
288 if (r < 0) {
289 log_error_errno(r, "Failed to enumerate sysctl.d files: %m");
290 goto finish;
291 }
292
293 if (arg_cat_config) {
294 (void) pager_open(arg_no_pager, false);
295
296 r = cat_files(NULL, files, 0);
297 goto finish;
298 }
299
300 STRV_FOREACH(f, files) {
301 k = parse_file(sysctl_options, *f, true);
302 if (k < 0 && r == 0)
303 r = k;
304 }
305 }
306
307 k = apply_all(sysctl_options);
308 if (k < 0 && r == 0)
309 r = k;
310
311 finish:
312 pager_close();
313
314 ordered_hashmap_free_free_free(sysctl_options);
315 strv_free(arg_prefixes);
316
317 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
318 }