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