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