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