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