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