]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysctl/sysctl.c
treewide: a few more log_*_errno + return simplifications
[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 return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path);
138 }
139
140 log_debug("parse: %s", path);
141 while (!feof(f)) {
142 char l[LINE_MAX], *p, *value, *new_value, *property, *existing;
143 void *v;
144 int k;
145
146 if (!fgets(l, sizeof(l), f)) {
147 if (feof(f))
148 break;
149
150 log_error("Failed to read file '%s', ignoring: %m", path);
151 return -errno;
152 }
153
154 p = strstrip(l);
155 if (!*p)
156 continue;
157
158 if (strchr(COMMENTS "\n", *p))
159 continue;
160
161 value = strchr(p, '=');
162 if (!value) {
163 log_error("Line is not an assignment in file '%s': %s", path, value);
164
165 if (r == 0)
166 r = -EINVAL;
167 continue;
168 }
169
170 *value = 0;
171 value++;
172
173 p = normalize_sysctl(strstrip(p));
174 value = strstrip(value);
175
176 existing = hashmap_get2(sysctl_options, p, &v);
177 if (existing) {
178 if (streq(value, existing))
179 continue;
180
181 log_info("Overwriting earlier assignment of %s in file '%s'.", p, path);
182 free(hashmap_remove(sysctl_options, p));
183 free(v);
184 }
185
186 property = strdup(p);
187 if (!property)
188 return log_oom();
189
190 new_value = strdup(value);
191 if (!new_value) {
192 free(property);
193 return log_oom();
194 }
195
196 k = hashmap_put(sysctl_options, property, new_value);
197 if (k < 0) {
198 log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", property);
199 free(property);
200 free(new_value);
201 return k;
202 }
203 }
204
205 return r;
206 }
207
208 static void help(void) {
209 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
210 "Applies kernel sysctl settings.\n\n"
211 " -h --help Show this help\n"
212 " --version Show package version\n"
213 " --prefix=PATH Only apply rules with the specified prefix\n"
214 , program_invocation_short_name);
215 }
216
217 static int parse_argv(int argc, char *argv[]) {
218
219 enum {
220 ARG_VERSION = 0x100,
221 ARG_PREFIX
222 };
223
224 static const struct option options[] = {
225 { "help", no_argument, NULL, 'h' },
226 { "version", no_argument, NULL, ARG_VERSION },
227 { "prefix", required_argument, NULL, ARG_PREFIX },
228 {}
229 };
230
231 int c;
232
233 assert(argc >= 0);
234 assert(argv);
235
236 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
237
238 switch (c) {
239
240 case 'h':
241 help();
242 return 0;
243
244 case ARG_VERSION:
245 puts(PACKAGE_STRING);
246 puts(SYSTEMD_FEATURES);
247 return 0;
248
249 case ARG_PREFIX: {
250 char *p;
251
252 /* We used to require people to specify absolute paths
253 * in /proc/sys in the past. This is kinda useless, but
254 * we need to keep compatibility. We now support any
255 * sysctl name available. */
256 normalize_sysctl(optarg);
257 if (startswith(optarg, "/proc/sys"))
258 p = strdup(optarg);
259 else
260 p = strappend("/proc/sys/", optarg);
261
262 if (!p)
263 return log_oom();
264 if (strv_consume(&arg_prefixes, p) < 0)
265 return log_oom();
266
267 break;
268 }
269
270 case '?':
271 return -EINVAL;
272
273 default:
274 assert_not_reached("Unhandled option");
275 }
276
277 return 1;
278 }
279
280 int main(int argc, char *argv[]) {
281 int r = 0, k;
282 Hashmap *sysctl_options;
283
284 r = parse_argv(argc, argv);
285 if (r <= 0)
286 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
287
288 log_set_target(LOG_TARGET_AUTO);
289 log_parse_environment();
290 log_open();
291
292 umask(0022);
293
294 sysctl_options = hashmap_new(&string_hash_ops);
295 if (!sysctl_options) {
296 r = log_oom();
297 goto finish;
298 }
299
300 r = 0;
301
302 if (argc > optind) {
303 int i;
304
305 for (i = optind; i < argc; i++) {
306 k = parse_file(sysctl_options, argv[i], false);
307 if (k < 0 && r == 0)
308 r = k;
309 }
310 } else {
311 _cleanup_strv_free_ char **files = NULL;
312 char **f;
313
314 r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
315 if (r < 0) {
316 log_error_errno(r, "Failed to enumerate sysctl.d files: %m");
317 goto finish;
318 }
319
320 STRV_FOREACH(f, files) {
321 k = parse_file(sysctl_options, *f, true);
322 if (k < 0 && r == 0)
323 r = k;
324 }
325 }
326
327 k = apply_all(sysctl_options);
328 if (k < 0 && r == 0)
329 r = k;
330
331 finish:
332 hashmap_free_free_free(sysctl_options);
333 strv_free(arg_prefixes);
334
335 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
336 }