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