chiark / gitweb /
2d43660bb5285a8eaf5435f04a7be2bd8f2ec45f
[elogind.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_one_line_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 && errno == -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                 int k;
139
140                 if (!fgets(l, sizeof(l), f)) {
141                         if (feof(f))
142                                 break;
143
144                         log_error("Failed to read file '%s', ignoring: %m", path);
145                         return -errno;
146                 }
147
148                 p = strstrip(l);
149                 if (!*p)
150                         continue;
151
152                 if (strchr(COMMENTS, *p))
153                         continue;
154
155                 value = strchr(p, '=');
156                 if (!value) {
157                         log_error("Line is not an assignment in file '%s': %s", path, value);
158
159                         if (r == 0)
160                                 r = -EINVAL;
161                         continue;
162                 }
163
164                 *value = 0;
165                 value++;
166
167                 p = normalize_sysctl(strstrip(p));
168                 value = strstrip(value);
169
170                 existing = hashmap_get(sysctl_options, p);
171                 if (existing) {
172                         if (!streq(value, existing))
173                                 log_warning("Duplicate assignment of %s in file '%s', ignoring.",
174                                             p, path);
175
176                         continue;
177                 }
178
179                 property = strdup(p);
180                 if (!property)
181                         return log_oom();
182
183                 new_value = strdup(value);
184                 if (!new_value) {
185                         free(property);
186                         return log_oom();
187                 }
188
189                 k = hashmap_put(sysctl_options, property, new_value);
190                 if (k < 0) {
191                         log_error("Failed to add sysctl variable %s to hashmap: %s", property, strerror(-r));
192                         free(property);
193                         free(new_value);
194                         return k;
195                 }
196         }
197
198         return r;
199 }
200
201 static int help(void) {
202
203         printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
204                "Applies kernel sysctl settings.\n\n"
205                "  -h --help             Show this help\n"
206                "     --prefix=PATH      Only apply rules that apply to paths with the specified prefix\n",
207                program_invocation_short_name);
208
209         return 0;
210 }
211
212 static int parse_argv(int argc, char *argv[]) {
213
214         enum {
215                 ARG_PREFIX
216         };
217
218         static const struct option options[] = {
219                 { "help",      no_argument,       NULL, 'h'           },
220                 { "prefix",    required_argument, NULL, ARG_PREFIX    },
221                 { NULL,        0,                 NULL, 0             }
222         };
223
224         int c;
225
226         assert(argc >= 0);
227         assert(argv);
228
229         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
230
231                 switch (c) {
232
233                 case 'h':
234                         help();
235                         return 0;
236
237                 case ARG_PREFIX: {
238                         char *p;
239                         char **l;
240
241                         for (p = optarg; *p; p++)
242                                 if (*p == '.')
243                                         *p = '/';
244
245                         l = strv_append(arg_prefixes, optarg);
246                         if (!l)
247                                 return log_oom();
248
249                         strv_free(arg_prefixes);
250                         arg_prefixes = l;
251
252                         break;
253                 }
254
255                 case '?':
256                         return -EINVAL;
257
258                 default:
259                         log_error("Unknown option code %c", c);
260                         return -EINVAL;
261                 }
262         }
263
264         return 1;
265 }
266
267 int main(int argc, char *argv[]) {
268         int r = 0, k;
269         Hashmap *sysctl_options;
270
271         r = parse_argv(argc, argv);
272         if (r <= 0)
273                 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
274
275         log_set_target(LOG_TARGET_AUTO);
276         log_parse_environment();
277         log_open();
278
279         umask(0022);
280
281         sysctl_options = hashmap_new(string_hash_func, string_compare_func);
282         if (!sysctl_options) {
283                 r = log_oom();
284                 goto finish;
285         }
286
287         r = 0;
288
289         if (argc > optind) {
290                 int i;
291
292                 for (i = optind; i < argc; i++) {
293                         k = parse_file(sysctl_options, argv[i], false);
294                         if (k < 0 && r == 0)
295                                 r = k;
296                 }
297         } else {
298                 _cleanup_strv_free_ char **files = NULL;
299                 char **f;
300
301                 r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
302                 if (r < 0) {
303                         log_error("Failed to enumerate sysctl.d files: %s", strerror(-r));
304                         goto finish;
305                 }
306
307                 r = parse_file(sysctl_options, "/etc/sysctl.conf", true);
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 }