chiark / gitweb /
timer: timer can be a transient unit
[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         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_errno(errno, "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 }