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