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