chiark / gitweb /
strv: multiple cleanups
[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                         char **l;
249
250                         for (p = optarg; *p; p++)
251                                 if (*p == '.')
252                                         *p = '/';
253
254                         if (strv_extend(&arg_prefixes, optarg) < 0)
255                                 return log_oom();
256
257                         break;
258                 }
259
260                 case '?':
261                         return -EINVAL;
262
263                 default:
264                         assert_not_reached("Unhandled option");
265                 }
266         }
267
268         return 1;
269 }
270
271 int main(int argc, char *argv[]) {
272         int r = 0, k;
273         Hashmap *sysctl_options;
274
275         r = parse_argv(argc, argv);
276         if (r <= 0)
277                 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
278
279         log_set_target(LOG_TARGET_AUTO);
280         log_parse_environment();
281         log_open();
282
283         umask(0022);
284
285         sysctl_options = hashmap_new(string_hash_func, string_compare_func);
286         if (!sysctl_options) {
287                 r = log_oom();
288                 goto finish;
289         }
290
291         r = 0;
292
293         if (argc > optind) {
294                 int i;
295
296                 for (i = optind; i < argc; i++) {
297                         k = parse_file(sysctl_options, argv[i], false);
298                         if (k < 0 && r == 0)
299                                 r = k;
300                 }
301         } else {
302                 _cleanup_strv_free_ char **files = NULL;
303                 char **f;
304
305                 r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
306                 if (r < 0) {
307                         log_error("Failed to enumerate sysctl.d files: %s", strerror(-r));
308                         goto finish;
309                 }
310
311                 STRV_FOREACH(f, files) {
312                         k = parse_file(sysctl_options, *f, true);
313                         if (k < 0 && r == 0)
314                                 r = k;
315                 }
316         }
317
318         k = apply_all(sysctl_options);
319         if (k < 0 && r == 0)
320                 r = k;
321
322 finish:
323         hashmap_free_free_free(sysctl_options);
324         strv_free(arg_prefixes);
325
326         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
327 }