]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysctl/sysctl.c
sysctl: allow overwriting of values specified in "later" files
[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 "strv.h"
34 #include "hashmap.h"
35 #include "path-util.h"
36 #include "conf-files.h"
37 #include "fileio.h"
38
39 static char **arg_prefixes = NULL;
40
41 static const char conf_file_dirs[] =
42 "/etc/sysctl.d\0"
43 "/run/sysctl.d\0"
44 "/usr/local/lib/sysctl.d\0"
45 "/usr/lib/sysctl.d\0"
46 #ifdef HAVE_SPLIT_USR
47 "/lib/sysctl.d\0"
48 #endif
49 ;
50
51 static char *normalize_sysctl(char *s) {
52 char *n;
53
54 for (n = s; *n; n++)
55 if (*n == '.')
56 *n = '/';
57
58 return s;
59 }
60
61 static int apply_sysctl(const char *property, const char *value) {
62 _cleanup_free_ char *p = NULL;
63 char *n;
64 int r = 0, k;
65
66 log_debug("Setting '%s' to '%s'", property, value);
67
68 p = new(char, sizeof("/proc/sys/") + strlen(property));
69 if (!p)
70 return log_oom();
71
72 n = stpcpy(p, "/proc/sys/");
73 strcpy(n, property);
74
75 if (!strv_isempty(arg_prefixes)) {
76 char **i;
77 bool good = false;
78
79 STRV_FOREACH(i, arg_prefixes)
80 if (path_startswith(p, *i)) {
81 good = true;
82 break;
83 }
84
85 if (!good) {
86 log_debug("Skipping %s", p);
87 return 0;
88 }
89 }
90
91 k = write_string_file(p, value);
92 if (k < 0) {
93 log_full(k == -ENOENT ? LOG_DEBUG : LOG_WARNING,
94 "Failed to write '%s' to '%s': %s", value, p, strerror(-k));
95
96 if (k != -ENOENT && r == 0)
97 r = k;
98 }
99
100 return r;
101 }
102
103 static int apply_all(Hashmap *sysctl_options) {
104 int r = 0;
105 char *property, *value;
106 Iterator i;
107
108 assert(sysctl_options);
109
110 HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) {
111 int k;
112
113 k = apply_sysctl(property, value);
114 if (k < 0 && r == 0)
115 r = k;
116 }
117 return r;
118 }
119
120 static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_enoent) {
121 _cleanup_fclose_ FILE *f = NULL;
122 int r;
123
124 assert(path);
125
126 r = search_and_fopen_nulstr(path, "re", conf_file_dirs, &f);
127 if (r < 0) {
128 if (ignore_enoent && r == -ENOENT)
129 return 0;
130
131 log_error("Failed to open file '%s', ignoring: %s", path, strerror(-r));
132 return r;
133 }
134
135 log_debug("parse: %s\n", path);
136 while (!feof(f)) {
137 char l[LINE_MAX], *p, *value, *new_value, *property, *existing;
138 void *v;
139 int k;
140
141 if (!fgets(l, sizeof(l), f)) {
142 if (feof(f))
143 break;
144
145 log_error("Failed to read file '%s', ignoring: %m", path);
146 return -errno;
147 }
148
149 p = strstrip(l);
150 if (!*p)
151 continue;
152
153 if (strchr(COMMENTS "\n", *p))
154 continue;
155
156 value = strchr(p, '=');
157 if (!value) {
158 log_error("Line is not an assignment in file '%s': %s", path, value);
159
160 if (r == 0)
161 r = -EINVAL;
162 continue;
163 }
164
165 *value = 0;
166 value++;
167
168 p = normalize_sysctl(strstrip(p));
169 value = strstrip(value);
170
171 existing = hashmap_get2(sysctl_options, p, &v);
172 if (existing) {
173 if (streq(value, existing))
174 continue;
175
176 log_info("Overwriting earlier assignment of %s in file '%s'.", p, path);
177 free(hashmap_remove(sysctl_options, p));
178 free(v);
179 }
180
181 property = strdup(p);
182 if (!property)
183 return log_oom();
184
185 new_value = strdup(value);
186 if (!new_value) {
187 free(property);
188 return log_oom();
189 }
190
191 k = hashmap_put(sysctl_options, property, new_value);
192 if (k < 0) {
193 log_error("Failed to add sysctl variable %s to hashmap: %s", property, strerror(-k));
194 free(property);
195 free(new_value);
196 return k;
197 }
198 }
199
200 return r;
201 }
202
203 static int help(void) {
204
205 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
206 "Applies kernel sysctl settings.\n\n"
207 " -h --help Show this help\n"
208 " --prefix=PATH Only apply rules that apply to paths with the specified prefix\n",
209 program_invocation_short_name);
210
211 return 0;
212 }
213
214 static int parse_argv(int argc, char *argv[]) {
215
216 enum {
217 ARG_PREFIX
218 };
219
220 static const struct option options[] = {
221 { "help", no_argument, NULL, 'h' },
222 { "prefix", required_argument, NULL, ARG_PREFIX },
223 { NULL, 0, NULL, 0 }
224 };
225
226 int c;
227
228 assert(argc >= 0);
229 assert(argv);
230
231 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
232
233 switch (c) {
234
235 case 'h':
236 help();
237 return 0;
238
239 case ARG_PREFIX: {
240 char *p;
241 char **l;
242
243 for (p = optarg; *p; p++)
244 if (*p == '.')
245 *p = '/';
246
247 l = strv_append(arg_prefixes, optarg);
248 if (!l)
249 return log_oom();
250
251 strv_free(arg_prefixes);
252 arg_prefixes = l;
253
254 break;
255 }
256
257 case '?':
258 return -EINVAL;
259
260 default:
261 log_error("Unknown option code %c", c);
262 return -EINVAL;
263 }
264 }
265
266 return 1;
267 }
268
269 int main(int argc, char *argv[]) {
270 int r = 0, k;
271 Hashmap *sysctl_options;
272
273 r = parse_argv(argc, argv);
274 if (r <= 0)
275 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
276
277 log_set_target(LOG_TARGET_AUTO);
278 log_parse_environment();
279 log_open();
280
281 umask(0022);
282
283 sysctl_options = hashmap_new(string_hash_func, string_compare_func);
284 if (!sysctl_options) {
285 r = log_oom();
286 goto finish;
287 }
288
289 r = 0;
290
291 if (argc > optind) {
292 int i;
293
294 for (i = optind; i < argc; i++) {
295 k = parse_file(sysctl_options, argv[i], false);
296 if (k < 0 && r == 0)
297 r = k;
298 }
299 } else {
300 _cleanup_strv_free_ char **files = NULL;
301 char **f;
302
303 r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
304 if (r < 0) {
305 log_error("Failed to enumerate sysctl.d files: %s", strerror(-r));
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 hashmap_free_free_free(sysctl_options);
322 strv_free(arg_prefixes);
323
324 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
325 }