]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysctl/sysctl.c
edebe501d198bb2f051983d66c3f92d3f5b98764
[thirdparty/systemd.git] / src / sysctl / sysctl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <limits.h>
28 #include <getopt.h>
29
30 #include "log.h"
31 #include "strv.h"
32 #include "util.h"
33 #include "hashmap.h"
34 #include "path-util.h"
35 #include "conf-files.h"
36 #include "fileio.h"
37 #include "build.h"
38
39 static char **arg_prefixes = NULL;
40
41 static const char conf_file_dirs[] = CONF_DIRS_NULSTR("sysctl");
42
43 static char* normalize_sysctl(char *s) {
44 char *n;
45
46 n = strpbrk(s, "/.");
47 /* If the first separator is a slash, the path is
48 * assumed to be normalized and slashes remain slashes
49 * and dots remains dots. */
50 if (!n || *n == '/')
51 return s;
52
53 /* Otherwise, dots become slashes and slashes become
54 * dots. Fun. */
55 while (n) {
56 if (*n == '.')
57 *n = '/';
58 else
59 *n = '.';
60
61 n = strpbrk(n + 1, "/.");
62 }
63
64 return s;
65 }
66
67 static int apply_sysctl(const char *property, const char *value) {
68 _cleanup_free_ char *p = NULL;
69 char *n;
70 int r = 0, k;
71
72 log_debug("Setting '%s' to '%s'", property, value);
73
74 p = new(char, strlen("/proc/sys/") + strlen(property) + 1);
75 if (!p)
76 return log_oom();
77
78 n = stpcpy(p, "/proc/sys/");
79 strcpy(n, property);
80
81 if (!strv_isempty(arg_prefixes)) {
82 char **i;
83 bool good = false;
84
85 STRV_FOREACH(i, arg_prefixes)
86 if (path_startswith(p, *i)) {
87 good = true;
88 break;
89 }
90
91 if (!good) {
92 log_debug("Skipping %s", p);
93 return 0;
94 }
95 }
96
97 k = write_string_file(p, value);
98 if (k < 0) {
99 log_full(k == -ENOENT ? LOG_DEBUG : LOG_WARNING,
100 "Failed to write '%s' to '%s': %s", value, p, strerror(-k));
101
102 if (k != -ENOENT && r == 0)
103 r = k;
104 }
105
106 return r;
107 }
108
109 static int apply_all(Hashmap *sysctl_options) {
110 int r = 0;
111 char *property, *value;
112 Iterator i;
113
114 assert(sysctl_options);
115
116 HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) {
117 int k;
118
119 k = apply_sysctl(property, value);
120 if (k < 0 && r == 0)
121 r = k;
122 }
123 return r;
124 }
125
126 static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_enoent) {
127 _cleanup_fclose_ FILE *f = NULL;
128 int r;
129
130 assert(path);
131
132 r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f);
133 if (r < 0) {
134 if (ignore_enoent && r == -ENOENT)
135 return 0;
136
137 log_error("Failed to open file '%s', ignoring: %s", path, strerror(-r));
138 return r;
139 }
140
141 log_debug("parse: %s", path);
142 while (!feof(f)) {
143 char l[LINE_MAX], *p, *value, *new_value, *property, *existing;
144 void *v;
145 int k;
146
147 if (!fgets(l, sizeof(l), f)) {
148 if (feof(f))
149 break;
150
151 log_error("Failed to read file '%s', ignoring: %m", path);
152 return -errno;
153 }
154
155 p = strstrip(l);
156 if (!*p)
157 continue;
158
159 if (strchr(COMMENTS "\n", *p))
160 continue;
161
162 value = strchr(p, '=');
163 if (!value) {
164 log_error("Line is not an assignment in file '%s': %s", path, value);
165
166 if (r == 0)
167 r = -EINVAL;
168 continue;
169 }
170
171 *value = 0;
172 value++;
173
174 p = normalize_sysctl(strstrip(p));
175 value = strstrip(value);
176
177 existing = hashmap_get2(sysctl_options, p, &v);
178 if (existing) {
179 if (streq(value, existing))
180 continue;
181
182 log_info("Overwriting earlier assignment of %s in file '%s'.", p, path);
183 free(hashmap_remove(sysctl_options, p));
184 free(v);
185 }
186
187 property = strdup(p);
188 if (!property)
189 return log_oom();
190
191 new_value = strdup(value);
192 if (!new_value) {
193 free(property);
194 return log_oom();
195 }
196
197 k = hashmap_put(sysctl_options, property, new_value);
198 if (k < 0) {
199 log_error("Failed to add sysctl variable %s to hashmap: %s", property, strerror(-k));
200 free(property);
201 free(new_value);
202 return k;
203 }
204 }
205
206 return r;
207 }
208
209 static void help(void) {
210 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
211 "Applies kernel sysctl settings.\n\n"
212 " -h --help Show this help\n"
213 " --version Show package version\n"
214 " --prefix=PATH Only apply rules with the specified prefix\n"
215 , program_invocation_short_name);
216 }
217
218 static int parse_argv(int argc, char *argv[]) {
219
220 enum {
221 ARG_VERSION = 0x100,
222 ARG_PREFIX
223 };
224
225 static const struct option options[] = {
226 { "help", no_argument, NULL, 'h' },
227 { "version", no_argument, NULL, ARG_VERSION },
228 { "prefix", required_argument, NULL, ARG_PREFIX },
229 {}
230 };
231
232 int c;
233
234 assert(argc >= 0);
235 assert(argv);
236
237 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
238
239 switch (c) {
240
241 case 'h':
242 help();
243 return 0;
244
245 case ARG_VERSION:
246 puts(PACKAGE_STRING);
247 puts(SYSTEMD_FEATURES);
248 return 0;
249
250 case ARG_PREFIX: {
251 char *p;
252
253 /* We used to require people to specify absolute paths
254 * in /proc/sys in the past. This is kinda useless, but
255 * we need to keep compatibility. We now support any
256 * sysctl name available. */
257 normalize_sysctl(optarg);
258 if (startswith(optarg, "/proc/sys"))
259 p = strdup(optarg);
260 else
261 p = strappend("/proc/sys/", optarg);
262
263 if (!p)
264 return log_oom();
265 if (strv_consume(&arg_prefixes, p) < 0)
266 return log_oom();
267
268 break;
269 }
270
271 case '?':
272 return -EINVAL;
273
274 default:
275 assert_not_reached("Unhandled option");
276 }
277
278 return 1;
279 }
280
281 int main(int argc, char *argv[]) {
282 int r = 0, k;
283 Hashmap *sysctl_options;
284
285 r = parse_argv(argc, argv);
286 if (r <= 0)
287 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
288
289 log_set_target(LOG_TARGET_AUTO);
290 log_parse_environment();
291 log_open();
292
293 umask(0022);
294
295 sysctl_options = hashmap_new(&string_hash_ops);
296 if (!sysctl_options) {
297 r = log_oom();
298 goto finish;
299 }
300
301 r = 0;
302
303 if (argc > optind) {
304 int i;
305
306 for (i = optind; i < argc; i++) {
307 k = parse_file(sysctl_options, argv[i], false);
308 if (k < 0 && r == 0)
309 r = k;
310 }
311 } else {
312 _cleanup_strv_free_ char **files = NULL;
313 char **f;
314
315 r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
316 if (r < 0) {
317 log_error("Failed to enumerate sysctl.d files: %s", strerror(-r));
318 goto finish;
319 }
320
321 STRV_FOREACH(f, files) {
322 k = parse_file(sysctl_options, *f, true);
323 if (k < 0 && r == 0)
324 r = k;
325 }
326 }
327
328 k = apply_all(sysctl_options);
329 if (k < 0 && r == 0)
330 r = k;
331
332 finish:
333 hashmap_free_free_free(sysctl_options);
334 strv_free(arg_prefixes);
335
336 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
337 }