chiark / gitweb /
sysctl: always return the last error we encountered
[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                                 log_debug("Skipping previously assigned sysctl variable %s", property);
172                         else
173                                 log_error("Failed to add sysctl variable %s to hashmap: %s", property, strerror(-r));
174
175                         free(property);
176                         free(new_value);
177                         if (r != -EEXIST)
178                                 goto finish;
179                 }
180         }
181
182 finish:
183         fclose(f);
184
185         return r;
186 }
187
188 static int help(void) {
189
190         printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
191                "Applies kernel sysctl settings.\n\n"
192                "  -h --help             Show this help\n"
193                "     --prefix=PATH      Only apply rules that apply to paths with the specified prefix\n",
194                program_invocation_short_name);
195
196         return 0;
197 }
198
199 static int parse_argv(int argc, char *argv[]) {
200
201         enum {
202                 ARG_PREFIX
203         };
204
205         static const struct option options[] = {
206                 { "help",      no_argument,       NULL, 'h'           },
207                 { "prefix",    required_argument, NULL, ARG_PREFIX    },
208                 { NULL,        0,                 NULL, 0             }
209         };
210
211         int c;
212
213         assert(argc >= 0);
214         assert(argv);
215
216         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
217
218                 switch (c) {
219
220                 case 'h':
221                         help();
222                         return 0;
223
224                 case ARG_PREFIX: {
225                         char *p;
226                         char **l;
227
228                         for (p = optarg; *p; p++)
229                                 if (*p == '.')
230                                         *p = '/';
231
232                         l = strv_append(arg_prefixes, optarg);
233                         if (!l)
234                                 return log_oom();
235
236                         strv_free(arg_prefixes);
237                         arg_prefixes = l;
238
239                         break;
240                 }
241
242                 case '?':
243                         return -EINVAL;
244
245                 default:
246                         log_error("Unknown option code %c", c);
247                         return -EINVAL;
248                 }
249         }
250
251         return 1;
252 }
253
254 int main(int argc, char *argv[]) {
255         int r = 0, k;
256         char *property, *value;
257         Iterator it;
258
259         r = parse_argv(argc, argv);
260         if (r <= 0)
261                 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
262
263         log_set_target(LOG_TARGET_AUTO);
264         log_parse_environment();
265         log_open();
266
267         umask(0022);
268
269         sysctl_options = hashmap_new(string_hash_func, string_compare_func);
270         if (!sysctl_options) {
271                 r = log_oom();
272                 goto finish;
273         }
274
275         r = 0;
276
277         if (argc > optind) {
278                 int i;
279
280                 for (i = optind; i < argc; i++) {
281                         k = parse_file(argv[i], false);
282                         if (k < 0)
283                                 r = k;
284                 }
285         } else {
286                 char **files, **f;
287
288                 r = conf_files_list(&files, ".conf",
289                                     "/etc/sysctl.d",
290                                     "/run/sysctl.d",
291                                     "/usr/local/lib/sysctl.d",
292                                     "/usr/lib/sysctl.d",
293 #ifdef HAVE_SPLIT_USR
294                                     "/lib/sysctl.d",
295 #endif
296                                     NULL);
297                 if (r < 0) {
298                         log_error("Failed to enumerate sysctl.d files: %s", strerror(-r));
299                         goto finish;
300                 }
301
302                 /* We parse the files in decreasing order of precedence.
303                  * parse_file() will skip keys that were already assigned. */
304
305                 r = parse_file("/etc/sysctl.conf", true);
306
307                 f = files + strv_length(files) - 1;
308                 STRV_FOREACH_BACKWARDS(f, files) {
309                         k = parse_file(*f, true);
310                         if (k < 0)
311                                 r = k;
312                 }
313
314                 strv_free(files);
315         }
316
317         k = apply_all();
318         if (k < 0)
319                 r = k;
320
321 finish:
322         HASHMAP_FOREACH_KEY(value, property, sysctl_options, it) {
323                 hashmap_remove(sysctl_options, property);
324                 free(property);
325                 free(value);
326         }
327         hashmap_free(sysctl_options);
328
329         strv_free(arg_prefixes);
330
331         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
332 }