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