chiark / gitweb /
use strneq instead of strncmp
[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
38 static char **arg_prefixes = NULL;
39
40 static const char conf_file_dirs[] =
41         "/etc/sysctl.d\0"
42         "/run/sysctl.d\0"
43         "/usr/local/lib/sysctl.d\0"
44         "/usr/lib/sysctl.d\0"
45 #ifdef HAVE_SPLIT_USR
46         "/lib/sysctl.d\0"
47 #endif
48         ;
49
50 static char *normalize_sysctl(char *s) {
51         char *n;
52
53         for (n = s; *n; n++)
54                 if (*n == '.')
55                         *n = '/';
56
57         return s;
58 }
59
60 static int apply_sysctl(const char *property, const char *value) {
61         _cleanup_free_ char *p = NULL;
62         char *n;
63         int r = 0, k;
64
65         log_debug("Setting '%s' to '%s'", property, value);
66
67         p = new(char, sizeof("/proc/sys/") + strlen(property));
68         if (!p)
69                 return log_oom();
70
71         n = stpcpy(p, "/proc/sys/");
72         strcpy(n, property);
73
74         if (!strv_isempty(arg_prefixes)) {
75                 char **i;
76                 bool good = false;
77
78                 STRV_FOREACH(i, arg_prefixes)
79                         if (path_startswith(p, *i)) {
80                                 good = true;
81                                 break;
82                         }
83
84                 if (!good) {
85                         log_debug("Skipping %s", p);
86                         return 0;
87                 }
88         }
89
90         k = write_one_line_file(p, value);
91         if (k < 0) {
92                 log_full(k == -ENOENT ? LOG_DEBUG : LOG_WARNING,
93                          "Failed to write '%s' to '%s': %s", value, p, strerror(-k));
94
95                 if (k != -ENOENT && r == 0)
96                         r = k;
97         }
98
99         return r;
100 }
101
102 static int apply_all(Hashmap *sysctl_options) {
103         int r = 0;
104         char *property, *value;
105         Iterator i;
106
107         assert(sysctl_options);
108
109         HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) {
110                 int k;
111
112                 k = apply_sysctl(property, value);
113                 if (k < 0 && r == 0)
114                         r = k;
115         }
116         return r;
117 }
118
119 static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_enoent) {
120         _cleanup_fclose_ FILE *f = NULL;
121         int r;
122
123         assert(path);
124
125         r = search_and_fopen_nulstr(path, "re", conf_file_dirs, &f);
126         if (r < 0) {
127                 if (ignore_enoent && errno == -ENOENT)
128                         return 0;
129
130                 log_error("Failed to open file '%s', ignoring: %s", path, strerror(-r));
131                 return r;
132         }
133
134         log_debug("parse: %s\n", path);
135         while (!feof(f)) {
136                 char l[LINE_MAX], *p, *value, *new_value, *property, *existing;
137                 int k;
138
139                 if (!fgets(l, sizeof(l), f)) {
140                         if (feof(f))
141                                 break;
142
143                         log_error("Failed to read file '%s', ignoring: %m", path);
144                         return -errno;
145                 }
146
147                 p = strstrip(l);
148                 if (!*p)
149                         continue;
150
151                 if (strchr(COMMENTS, *p))
152                         continue;
153
154                 value = strchr(p, '=');
155                 if (!value) {
156                         log_error("Line is not an assignment in file '%s': %s", path, value);
157
158                         if (r == 0)
159                                 r = -EINVAL;
160                         continue;
161                 }
162
163                 *value = 0;
164                 value++;
165
166                 p = normalize_sysctl(strstrip(p));
167                 value = strstrip(value);
168
169                 existing = hashmap_get(sysctl_options, p);
170                 if (existing) {
171                         if (!streq(value, existing))
172                                 log_warning("Two ore more conflicting assignments of %s, ignoring.", property);
173
174                         continue;
175                 }
176
177                 property = strdup(p);
178                 if (!property)
179                         return log_oom();
180
181                 new_value = strdup(value);
182                 if (!new_value) {
183                         free(property);
184                         return log_oom();
185                 }
186
187                 k = hashmap_put(sysctl_options, property, new_value);
188                 if (k < 0) {
189                         log_error("Failed to add sysctl variable %s to hashmap: %s", property, strerror(-r));
190                         free(property);
191                         free(new_value);
192                         return k;
193                 }
194         }
195
196         return r;
197 }
198
199 static int help(void) {
200
201         printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
202                "Applies kernel sysctl settings.\n\n"
203                "  -h --help             Show this help\n"
204                "     --prefix=PATH      Only apply rules that apply to paths with the specified prefix\n",
205                program_invocation_short_name);
206
207         return 0;
208 }
209
210 static int parse_argv(int argc, char *argv[]) {
211
212         enum {
213                 ARG_PREFIX
214         };
215
216         static const struct option options[] = {
217                 { "help",      no_argument,       NULL, 'h'           },
218                 { "prefix",    required_argument, NULL, ARG_PREFIX    },
219                 { NULL,        0,                 NULL, 0             }
220         };
221
222         int c;
223
224         assert(argc >= 0);
225         assert(argv);
226
227         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
228
229                 switch (c) {
230
231                 case 'h':
232                         help();
233                         return 0;
234
235                 case ARG_PREFIX: {
236                         char *p;
237                         char **l;
238
239                         for (p = optarg; *p; p++)
240                                 if (*p == '.')
241                                         *p = '/';
242
243                         l = strv_append(arg_prefixes, optarg);
244                         if (!l)
245                                 return log_oom();
246
247                         strv_free(arg_prefixes);
248                         arg_prefixes = l;
249
250                         break;
251                 }
252
253                 case '?':
254                         return -EINVAL;
255
256                 default:
257                         log_error("Unknown option code %c", c);
258                         return -EINVAL;
259                 }
260         }
261
262         return 1;
263 }
264
265 int main(int argc, char *argv[]) {
266         int r = 0, k;
267         Hashmap *sysctl_options;
268
269         r = parse_argv(argc, argv);
270         if (r <= 0)
271                 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
272
273         log_set_target(LOG_TARGET_AUTO);
274         log_parse_environment();
275         log_open();
276
277         umask(0022);
278
279         sysctl_options = hashmap_new(string_hash_func, string_compare_func);
280         if (!sysctl_options) {
281                 r = log_oom();
282                 goto finish;
283         }
284
285         r = 0;
286
287         if (argc > optind) {
288                 int i;
289
290                 for (i = optind; i < argc; i++) {
291                         k = parse_file(sysctl_options, argv[i], false);
292                         if (k < 0 && r == 0)
293                                 r = k;
294                 }
295         } else {
296                 _cleanup_strv_free_ char **files = NULL;
297                 char **f;
298
299                 r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
300                 if (r < 0) {
301                         log_error("Failed to enumerate sysctl.d files: %s", strerror(-r));
302                         goto finish;
303                 }
304
305                 r = parse_file(sysctl_options, "/etc/sysctl.conf", true);
306
307                 STRV_FOREACH(f, files) {
308                         k = parse_file(sysctl_options, *f, true);
309                         if (k < 0 && r == 0)
310                                 r = k;
311                 }
312         }
313
314         k = apply_all(sysctl_options);
315         if (k < 0 && r == 0)
316                 r = k;
317
318 finish:
319         hashmap_free_free_free(sysctl_options);
320         strv_free(arg_prefixes);
321
322         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
323 }