Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <errno.h>
4 : : #include <getopt.h>
5 : : #include <limits.h>
6 : : #include <stdbool.h>
7 : : #include <stdio.h>
8 : : #include <stdlib.h>
9 : : #include <string.h>
10 : : #include <sys/stat.h>
11 : : #include <sys/types.h>
12 : :
13 : : #include "conf-files.h"
14 : : #include "def.h"
15 : : #include "fd-util.h"
16 : : #include "fileio.h"
17 : : #include "hashmap.h"
18 : : #include "log.h"
19 : : #include "main-func.h"
20 : : #include "pager.h"
21 : : #include "path-util.h"
22 : : #include "pretty-print.h"
23 : : #include "string-util.h"
24 : : #include "strv.h"
25 : : #include "sysctl-util.h"
26 : :
27 : : static char **arg_prefixes = NULL;
28 : : static bool arg_cat_config = false;
29 : : static PagerFlags arg_pager_flags = 0;
30 : :
31 : 16 : STATIC_DESTRUCTOR_REGISTER(arg_prefixes, strv_freep);
32 : :
33 : : typedef struct Option {
34 : : char *key;
35 : : char *value;
36 : : bool ignore_failure;
37 : : } Option;
38 : :
39 : 0 : static Option *option_free(Option *o) {
40 [ # # ]: 0 : if (!o)
41 : 0 : return NULL;
42 : :
43 : 0 : free(o->key);
44 : 0 : free(o->value);
45 : :
46 : 0 : return mfree(o);
47 : : }
48 : :
49 [ # # ]: 0 : DEFINE_TRIVIAL_CLEANUP_FUNC(Option*, option_free);
50 : 0 : DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(option_hash_ops, char, string_hash_func, string_compare_func, Option, option_free);
51 : :
52 : 0 : static Option *option_new(
53 : : const char *key,
54 : : const char *value,
55 : : bool ignore_failure) {
56 : :
57 : 0 : _cleanup_(option_freep) Option *o = NULL;
58 : :
59 [ # # ]: 0 : assert(key);
60 [ # # ]: 0 : assert(value);
61 : :
62 : 0 : o = new(Option, 1);
63 [ # # ]: 0 : if (!o)
64 : 0 : return NULL;
65 : :
66 : 0 : *o = (Option) {
67 : 0 : .key = strdup(key),
68 : 0 : .value = strdup(value),
69 : : .ignore_failure = ignore_failure,
70 : : };
71 : :
72 [ # # # # ]: 0 : if (!o->key || !o->value)
73 : 0 : return NULL;
74 : :
75 : 0 : return TAKE_PTR(o);
76 : : }
77 : :
78 : 0 : static int apply_all(OrderedHashmap *sysctl_options) {
79 : : Option *option;
80 : : Iterator i;
81 : 0 : int r = 0;
82 : :
83 [ # # ]: 0 : ORDERED_HASHMAP_FOREACH(option, sysctl_options, i) {
84 : : int k;
85 : :
86 : 0 : k = sysctl_write(option->key, option->value);
87 [ # # ]: 0 : if (k < 0) {
88 : : /* If the sysctl is not available in the kernel or we are running with reduced
89 : : * privileges and cannot write it, then log about the issue at LOG_NOTICE level, and
90 : : * proceed without failing. (EROFS is treated as a permission problem here, since
91 : : * that's how container managers usually protected their sysctls.) In all other cases
92 : : * log an error and make the tool fail. */
93 : :
94 [ # # # # : 0 : if (IN_SET(k, -EPERM, -EACCES, -EROFS, -ENOENT) || option->ignore_failure)
# # ]
95 [ # # ]: 0 : log_notice_errno(k, "Couldn't write '%s' to '%s', ignoring: %m", option->value, option->key);
96 : : else {
97 [ # # ]: 0 : log_error_errno(k, "Couldn't write '%s' to '%s': %m", option->value, option->key);
98 [ # # ]: 0 : if (r == 0)
99 : 0 : r = k;
100 : : }
101 : : }
102 : : }
103 : :
104 : 0 : return r;
105 : : }
106 : :
107 : 0 : static bool test_prefix(const char *p) {
108 : : char **i;
109 : :
110 [ # # ]: 0 : if (strv_isempty(arg_prefixes))
111 : 0 : return true;
112 : :
113 [ # # # # ]: 0 : STRV_FOREACH(i, arg_prefixes) {
114 : : const char *t;
115 : :
116 : 0 : t = path_startswith(*i, "/proc/sys/");
117 [ # # ]: 0 : if (!t)
118 : 0 : t = *i;
119 [ # # ]: 0 : if (path_startswith(p, t))
120 : 0 : return true;
121 : : }
122 : :
123 : 0 : return false;
124 : : }
125 : :
126 : 0 : static int parse_file(OrderedHashmap *sysctl_options, const char *path, bool ignore_enoent) {
127 : 0 : _cleanup_fclose_ FILE *f = NULL;
128 : 0 : unsigned c = 0;
129 : : int r;
130 : :
131 [ # # ]: 0 : assert(path);
132 : :
133 : 0 : r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("sysctl.d"), &f);
134 [ # # ]: 0 : if (r < 0) {
135 [ # # # # ]: 0 : if (ignore_enoent && r == -ENOENT)
136 : 0 : return 0;
137 : :
138 [ # # ]: 0 : return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path);
139 : : }
140 : :
141 [ # # ]: 0 : log_debug("Parsing %s", path);
142 : 0 : for (;;) {
143 [ # # # # ]: 0 : _cleanup_(option_freep) Option *new_option = NULL;
144 [ # # # # ]: 0 : _cleanup_free_ char *l = NULL;
145 : : bool ignore_failure;
146 : : Option *existing;
147 : : char *p, *value;
148 : : int k;
149 : :
150 : 0 : k = read_line(f, LONG_LINE_MAX, &l);
151 [ # # ]: 0 : if (k == 0)
152 : 0 : break;
153 [ # # ]: 0 : if (k < 0)
154 [ # # ]: 0 : return log_error_errno(k, "Failed to read file '%s', ignoring: %m", path);
155 : :
156 : 0 : c++;
157 : :
158 : 0 : p = strstrip(l);
159 : :
160 [ # # ]: 0 : if (isempty(p))
161 : 0 : continue;
162 [ # # ]: 0 : if (strchr(COMMENTS "\n", *p))
163 : 0 : continue;
164 : :
165 : 0 : value = strchr(p, '=');
166 [ # # ]: 0 : if (!value) {
167 [ # # ]: 0 : log_syntax(NULL, LOG_WARNING, path, c, 0, "Line is not an assignment, ignoring: %s", p);
168 [ # # ]: 0 : if (r == 0)
169 : 0 : r = -EINVAL;
170 : 0 : continue;
171 : : }
172 : :
173 : 0 : *value = 0;
174 : 0 : value++;
175 : :
176 : 0 : p = strstrip(p);
177 : 0 : ignore_failure = p[0] == '-';
178 [ # # ]: 0 : if (ignore_failure)
179 : 0 : p++;
180 : :
181 : 0 : p = sysctl_normalize(p);
182 : 0 : value = strstrip(value);
183 : :
184 [ # # ]: 0 : if (!test_prefix(p))
185 : 0 : continue;
186 : :
187 : 0 : existing = ordered_hashmap_get(sysctl_options, p);
188 [ # # ]: 0 : if (existing) {
189 [ # # ]: 0 : if (streq(value, existing->value)) {
190 [ # # # # ]: 0 : existing->ignore_failure = existing->ignore_failure || ignore_failure;
191 : 0 : continue;
192 : : }
193 : :
194 [ # # ]: 0 : log_debug("Overwriting earlier assignment of %s at '%s:%u'.", p, path, c);
195 : 0 : option_free(ordered_hashmap_remove(sysctl_options, p));
196 : : }
197 : :
198 : 0 : new_option = option_new(p, value, ignore_failure);
199 [ # # ]: 0 : if (!new_option)
200 : 0 : return log_oom();
201 : :
202 : 0 : k = ordered_hashmap_put(sysctl_options, new_option->key, new_option);
203 [ # # ]: 0 : if (k < 0)
204 [ # # ]: 0 : return log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", p);
205 : :
206 : 0 : TAKE_PTR(new_option);
207 : : }
208 : :
209 : 0 : return r;
210 : : }
211 : :
212 : 12 : static int help(void) {
213 : 12 : _cleanup_free_ char *link = NULL;
214 : : int r;
215 : :
216 : 12 : r = terminal_urlify_man("systemd-sysctl.service", "8", &link);
217 [ - + ]: 12 : if (r < 0)
218 : 0 : return log_oom();
219 : :
220 : 12 : printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
221 : : "Applies kernel sysctl settings.\n\n"
222 : : " -h --help Show this help\n"
223 : : " --version Show package version\n"
224 : : " --cat-config Show configuration files\n"
225 : : " --prefix=PATH Only apply rules with the specified prefix\n"
226 : : " --no-pager Do not pipe output into a pager\n"
227 : : "\nSee the %s for details.\n"
228 : : , program_invocation_short_name
229 : : , link
230 : : );
231 : :
232 : 12 : return 0;
233 : : }
234 : :
235 : 16 : static int parse_argv(int argc, char *argv[]) {
236 : :
237 : : enum {
238 : : ARG_VERSION = 0x100,
239 : : ARG_CAT_CONFIG,
240 : : ARG_PREFIX,
241 : : ARG_NO_PAGER,
242 : : };
243 : :
244 : : static const struct option options[] = {
245 : : { "help", no_argument, NULL, 'h' },
246 : : { "version", no_argument, NULL, ARG_VERSION },
247 : : { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
248 : : { "prefix", required_argument, NULL, ARG_PREFIX },
249 : : { "no-pager", no_argument, NULL, ARG_NO_PAGER },
250 : : {}
251 : : };
252 : :
253 : : int c;
254 : :
255 [ - + ]: 16 : assert(argc >= 0);
256 [ - + ]: 16 : assert(argv);
257 : :
258 [ + - ]: 16 : while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
259 : :
260 [ + - - - : 16 : switch (c) {
- + - ]
261 : :
262 : 12 : case 'h':
263 : 12 : return help();
264 : :
265 : 0 : case ARG_VERSION:
266 : 0 : return version();
267 : :
268 : 0 : case ARG_CAT_CONFIG:
269 : 0 : arg_cat_config = true;
270 : 0 : break;
271 : :
272 : 0 : case ARG_PREFIX: {
273 : : char *p;
274 : :
275 : : /* We used to require people to specify absolute paths
276 : : * in /proc/sys in the past. This is kinda useless, but
277 : : * we need to keep compatibility. We now support any
278 : : * sysctl name available. */
279 : 0 : sysctl_normalize(optarg);
280 : :
281 [ # # ]: 0 : if (path_startswith(optarg, "/proc/sys"))
282 : 0 : p = strdup(optarg);
283 : : else
284 : 0 : p = path_join("/proc/sys", optarg);
285 [ # # ]: 0 : if (!p)
286 : 0 : return log_oom();
287 : :
288 [ # # ]: 0 : if (strv_consume(&arg_prefixes, p) < 0)
289 : 0 : return log_oom();
290 : :
291 : 0 : break;
292 : : }
293 : :
294 : 0 : case ARG_NO_PAGER:
295 : 0 : arg_pager_flags |= PAGER_DISABLE;
296 : 0 : break;
297 : :
298 : 4 : case '?':
299 : 4 : return -EINVAL;
300 : :
301 : 0 : default:
302 : 0 : assert_not_reached("Unhandled option");
303 : : }
304 : :
305 [ # # # # ]: 0 : if (arg_cat_config && argc > optind)
306 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
307 : : "Positional arguments are not allowed with --cat-config");
308 : :
309 : 0 : return 1;
310 : : }
311 : :
312 : 16 : static int run(int argc, char *argv[]) {
313 : 16 : _cleanup_(ordered_hashmap_freep) OrderedHashmap *sysctl_options = NULL;
314 : : int r, k;
315 : :
316 : 16 : r = parse_argv(argc, argv);
317 [ + - ]: 16 : if (r <= 0)
318 : 16 : return r;
319 : :
320 : 0 : log_setup_service();
321 : :
322 : 0 : umask(0022);
323 : :
324 : 0 : sysctl_options = ordered_hashmap_new(&option_hash_ops);
325 [ # # ]: 0 : if (!sysctl_options)
326 : 0 : return log_oom();
327 : :
328 [ # # ]: 0 : if (argc > optind) {
329 : : int i;
330 : :
331 : 0 : r = 0;
332 : :
333 [ # # ]: 0 : for (i = optind; i < argc; i++) {
334 : 0 : k = parse_file(sysctl_options, argv[i], false);
335 [ # # # # ]: 0 : if (k < 0 && r == 0)
336 : 0 : r = k;
337 : : }
338 : : } else {
339 [ # # ]: 0 : _cleanup_strv_free_ char **files = NULL;
340 : : char **f;
341 : :
342 : 0 : r = conf_files_list_strv(&files, ".conf", NULL, 0, (const char**) CONF_PATHS_STRV("sysctl.d"));
343 [ # # ]: 0 : if (r < 0)
344 [ # # ]: 0 : return log_error_errno(r, "Failed to enumerate sysctl.d files: %m");
345 : :
346 [ # # ]: 0 : if (arg_cat_config) {
347 : 0 : (void) pager_open(arg_pager_flags);
348 : :
349 : 0 : return cat_files(NULL, files, 0);
350 : : }
351 : :
352 [ # # # # ]: 0 : STRV_FOREACH(f, files) {
353 : 0 : k = parse_file(sysctl_options, *f, true);
354 [ # # # # ]: 0 : if (k < 0 && r == 0)
355 : 0 : r = k;
356 : : }
357 : : }
358 : :
359 : 0 : k = apply_all(sysctl_options);
360 [ # # # # ]: 0 : if (k < 0 && r == 0)
361 : 0 : r = k;
362 : :
363 : 0 : return r;
364 : : }
365 : :
366 : 16 : DEFINE_MAIN_FUNCTION(run);
|