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