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