/src/systemd/src/basic/conf-files.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include <stdio.h> |
4 | | #include <stdlib.h> |
5 | | |
6 | | #include "alloc-util.h" |
7 | | #include "chase.h" |
8 | | #include "conf-files.h" |
9 | | #include "dirent-util.h" |
10 | | #include "errno-util.h" |
11 | | #include "fd-util.h" |
12 | | #include "fileio.h" |
13 | | #include "glyph-util.h" |
14 | | #include "hashmap.h" |
15 | | #include "log.h" |
16 | | #include "nulstr-util.h" |
17 | | #include "path-util.h" |
18 | | #include "set.h" |
19 | | #include "stat-util.h" |
20 | | #include "string-util.h" |
21 | | #include "strv.h" |
22 | | |
23 | 7.56k | ConfFile* conf_file_free(ConfFile *c) { |
24 | 7.56k | if (!c) |
25 | 0 | return NULL; |
26 | | |
27 | 7.56k | free(c->filename); |
28 | 7.56k | free(c->result); |
29 | 7.56k | free(c->original_path); |
30 | 7.56k | free(c->resolved_path); |
31 | 7.56k | safe_close(c->fd); |
32 | | |
33 | 7.56k | return mfree(c); |
34 | 7.56k | } |
35 | | |
36 | 0 | DEFINE_POINTER_ARRAY_FREE_FUNC(ConfFile*, conf_file_free); |
37 | | |
38 | 88.3k | static int conf_files_log_level(ConfFilesFlags flags) { |
39 | 88.3k | return FLAGS_SET(flags, CONF_FILES_WARN) ? LOG_WARNING : LOG_DEBUG; |
40 | 88.3k | } |
41 | | |
42 | | static int prepare_dirs( |
43 | | const char *root, |
44 | | ConfFilesFlags flags, |
45 | | char * const *dirs, |
46 | | char **ret_root, |
47 | | int *ret_rfd, |
48 | 7.56k | char ***ret_dirs) { |
49 | | |
50 | 7.56k | _cleanup_free_ char *root_abs = NULL; |
51 | 7.56k | _cleanup_strv_free_ char **dirs_abs = NULL; |
52 | 7.56k | int r; |
53 | | |
54 | 7.56k | assert(ret_root); |
55 | 7.56k | assert(ret_rfd); |
56 | 7.56k | assert(ret_dirs || strv_isempty(dirs)); |
57 | | |
58 | 7.56k | int log_level = conf_files_log_level(flags); |
59 | | |
60 | 7.56k | r = empty_or_root_harder_to_null(&root); |
61 | 7.56k | if (r < 0) |
62 | 7.56k | return log_full_errno(log_level, r, "Failed to determine if '%s' points to the root directory: %m", strempty(root)); |
63 | | |
64 | 7.56k | if (ret_dirs) { |
65 | 0 | dirs_abs = strv_copy(dirs); |
66 | 0 | if (!dirs_abs) |
67 | 0 | return log_oom_full(log_level); |
68 | 0 | } |
69 | | |
70 | 7.56k | _cleanup_close_ int rfd = XAT_FDROOT; |
71 | 7.56k | if (root) { |
72 | | /* When a non-trivial root is specified, we will prefix the result later. Hence, it is not |
73 | | * necessary to modify each config directories here. but needs to normalize the root directory. */ |
74 | 0 | r = path_make_absolute_cwd(root, &root_abs); |
75 | 0 | if (r < 0) |
76 | 0 | return log_full_errno(log_level, r, "Failed to make '%s' absolute: %m", root); |
77 | | |
78 | 0 | path_simplify(root_abs); |
79 | |
|
80 | 0 | rfd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH); |
81 | 0 | if (rfd < 0) |
82 | 0 | return log_full_errno(log_level, errno, "Failed to open '%s': %m", root_abs); |
83 | |
|
84 | 7.56k | } else if (ret_dirs) { |
85 | | /* When an empty root or "/" is specified, we will open "/" below, hence we need to make |
86 | | * each config directory absolute if relative. */ |
87 | 0 | r = path_strv_make_absolute_cwd(dirs_abs); |
88 | 0 | if (r < 0) |
89 | 0 | return log_full_errno(log_level, r, "Failed to make directories absolute: %m"); |
90 | 0 | } |
91 | | |
92 | 7.56k | *ret_root = TAKE_PTR(root_abs); |
93 | 7.56k | *ret_rfd = TAKE_FD(rfd); |
94 | 7.56k | if (ret_dirs) |
95 | 0 | *ret_dirs = TAKE_PTR(dirs_abs); |
96 | 7.56k | return 0; |
97 | 7.56k | } |
98 | | |
99 | 7.56k | static int conf_file_prefix_root(ConfFile *c, const char *root, ConfFilesFlags flags) { |
100 | 7.56k | char *p; |
101 | 7.56k | int r; |
102 | | |
103 | 7.56k | assert(c); |
104 | | |
105 | 7.56k | if (FLAGS_SET(flags, CONF_FILES_DONT_PREFIX_ROOT)) |
106 | 0 | return 0; |
107 | | |
108 | 7.56k | int log_level = conf_files_log_level(flags); |
109 | | |
110 | 7.56k | r = chaseat_prefix_root(c->result, root, &p); |
111 | 7.56k | if (r < 0) |
112 | 7.56k | return log_full_errno(log_level, r, "Failed to prefix '%s' with root '%s': %m", c->result, root); |
113 | 7.56k | free_and_replace(c->result, p); |
114 | | |
115 | 7.56k | r = chaseat_prefix_root(c->resolved_path, root, &p); |
116 | 7.56k | if (r < 0) |
117 | 7.56k | return log_full_errno(log_level, r, "Failed to prefix '%s' with root '%s': %m", c->resolved_path, root); |
118 | 7.56k | free_and_replace(c->resolved_path, p); |
119 | | |
120 | | /* Do not use chaseat_prefix_root(), as it is for the result of chaseat(), but the path is not chased. */ |
121 | 7.56k | p = path_join(empty_to_root(root), skip_leading_slash(c->original_path)); |
122 | 7.56k | if (!p) |
123 | 0 | return log_oom_full(log_level); |
124 | | |
125 | 7.56k | path_simplify(p); |
126 | 7.56k | return free_and_replace(c->original_path, p); |
127 | 7.56k | } |
128 | | |
129 | 15.1k | static bool conf_files_need_stat(ConfFilesFlags flags) { |
130 | 15.1k | return (flags & (CONF_FILES_FILTER_MASKED | CONF_FILES_REGULAR | CONF_FILES_DIRECTORY | CONF_FILES_EXECUTABLE)) != 0; |
131 | 15.1k | } |
132 | | |
133 | 247k | static ChaseFlags conf_files_extra_chase_flags(ConfFilesFlags flags) { |
134 | | /* Executables never fit under the 0664 file ceiling */ |
135 | 247k | assert(!FLAGS_SET(flags, CONF_FILES_EXECUTABLE|CONF_FILES_CHASE_MAX_MODE)); |
136 | | |
137 | 247k | return (FLAGS_SET(flags, CONF_FILES_CHASE_SAFE) ? CHASE_SAFE : 0) | |
138 | 247k | (FLAGS_SET(flags, CONF_FILES_CHASE_MAX_MODE) ? CHASE_MAX_MODE : 0); |
139 | 247k | } |
140 | | |
141 | 15.1k | static ChaseFlags conf_files_chase_flags(ConfFilesFlags flags) { |
142 | 15.1k | ChaseFlags chase_flags = conf_files_extra_chase_flags(flags); |
143 | | |
144 | 15.1k | if (!conf_files_need_stat(flags) || FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) |
145 | | /* Even if no verification is requested, let's unconditionally call chaseat(), |
146 | | * to drop unsafe symlinks. */ |
147 | 0 | chase_flags |= CHASE_NONEXISTENT; |
148 | | |
149 | 15.1k | return chase_flags; |
150 | 15.1k | } |
151 | | |
152 | | static int conf_file_chase_and_verify( |
153 | | const char *root, /* for logging, can be NULL */ |
154 | | int rfd, |
155 | | const char *original_path, /* for logging */ |
156 | | const char *path, |
157 | | const char *name, |
158 | | Set **masked, /* optional */ |
159 | | ConfFilesFlags flags, |
160 | | char **ret_path, |
161 | | int *ret_fd, |
162 | 7.56k | struct stat *ret_stat) { |
163 | | |
164 | 7.56k | _cleanup_free_ char *resolved_path = NULL; |
165 | 7.56k | _cleanup_close_ int fd = -EBADF; |
166 | 7.56k | struct stat st = {}; |
167 | 7.56k | int r; |
168 | | |
169 | 7.56k | assert(wildcard_fd_is_valid(rfd)); |
170 | 7.56k | assert(original_path); |
171 | 7.56k | assert(path); |
172 | 7.56k | assert(name); |
173 | | |
174 | 7.56k | int log_level = conf_files_log_level(flags); |
175 | | |
176 | 7.56k | root = empty_to_root(root); |
177 | | |
178 | 7.56k | r = chaseat(rfd, rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd); |
179 | 7.56k | if (r < 0) |
180 | 7.56k | return log_full_errno(log_level, r, "Failed to chase '%s%s': %m", |
181 | 7.56k | root, skip_leading_slash(original_path)); |
182 | 7.56k | if (r == 0) { |
183 | 0 | if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) { |
184 | | /* If the path points to /dev/null in a image or so, then the device node may not exist. */ |
185 | 0 | if (path_equal(skip_leading_slash(resolved_path), "dev/null")) { |
186 | 0 | if (masked) { |
187 | | /* Mark this one as masked */ |
188 | 0 | r = set_put_strdup(masked, name); |
189 | 0 | if (r < 0) |
190 | 0 | return log_oom_full(log_level); |
191 | 0 | } |
192 | | |
193 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), |
194 | 0 | "File '%s%s' is a mask (symlink to /dev/null).", |
195 | 0 | root, skip_leading_slash(original_path)); |
196 | 0 | } |
197 | 0 | } |
198 | | |
199 | 0 | if (conf_files_need_stat(flags)) |
200 | | /* If we need to have stat, skip the entry. */ |
201 | 0 | return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOENT), "Failed to chase '%s%s': %m", |
202 | 0 | root, skip_leading_slash(original_path)); |
203 | 0 | } |
204 | | |
205 | | /* Even if we do not need stat, let's take stat now. The caller may use the info later. */ |
206 | 7.56k | if (fd >= 0 && fstat(fd, &st) < 0) |
207 | 7.56k | return log_full_errno(log_level, errno, "Failed to stat '%s%s': %m", |
208 | 7.56k | root, skip_leading_slash(original_path)); |
209 | | |
210 | | /* Is this a masking entry? */ |
211 | 7.56k | if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK) && stat_may_be_dev_null(&st)) { |
212 | 0 | if (masked) { |
213 | | /* Mark this one as masked */ |
214 | 0 | r = set_put_strdup(masked, name); |
215 | 0 | if (r < 0) |
216 | 0 | return log_oom_full(log_level); |
217 | 0 | } |
218 | | |
219 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), |
220 | 0 | "File '%s%s' is a mask (symlink to /dev/null).", |
221 | 0 | root, skip_leading_slash(original_path)); |
222 | 0 | } |
223 | | |
224 | 7.56k | if (FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_EMPTY) && stat_is_empty(&st)) { |
225 | 0 | if (masked) { |
226 | | /* Mark this one as masked */ |
227 | 0 | r = set_put_strdup(masked, name); |
228 | 0 | if (r < 0) |
229 | 0 | return log_oom_full(log_level); |
230 | 0 | } |
231 | | |
232 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL), |
233 | 0 | "File '%s%s' is a mask (an empty file).", |
234 | 0 | root, skip_leading_slash(original_path)); |
235 | 0 | } |
236 | | |
237 | 7.56k | if (FLAGS_SET(flags, CONF_FILES_REGULAR|CONF_FILES_DIRECTORY)) { |
238 | 0 | if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) |
239 | 0 | return log_debug_errno(SYNTHETIC_ERRNO(EBADFD), |
240 | 0 | "File '%s%s' is neither a regular file or directory.", |
241 | 0 | root, skip_leading_slash(original_path)); |
242 | 7.56k | } else { |
243 | | /* Is this node a regular file? */ |
244 | 7.56k | if (FLAGS_SET(flags, CONF_FILES_REGULAR)) { |
245 | 7.56k | r = stat_verify_regular(&st); |
246 | 7.56k | if (r < 0) |
247 | 7.56k | return log_debug_errno(r, "File '%s%s' is not a regular file: %m", |
248 | 7.56k | root, skip_leading_slash(original_path)); |
249 | 7.56k | } |
250 | | |
251 | | /* Is this node a directory? */ |
252 | 7.56k | if (FLAGS_SET(flags, CONF_FILES_DIRECTORY)) { |
253 | 0 | r = stat_verify_directory(&st); |
254 | 0 | if (r < 0) |
255 | 0 | return log_debug_errno(r, "File '%s%s' is not a directory: %m", |
256 | 0 | root, skip_leading_slash(original_path)); |
257 | 0 | } |
258 | 7.56k | } |
259 | | |
260 | | /* Does this node have the executable bit set? |
261 | | * As requested: check if the file is marked executable. Note that we don't check access(X_OK) here, |
262 | | * as we care about whether the file is marked executable at all, and not whether it is executable |
263 | | * for us, because if so, such errors are stuff we should log about. */ |
264 | 7.56k | if (FLAGS_SET(flags, CONF_FILES_EXECUTABLE) && (st.st_mode & 0111) == 0) |
265 | 7.56k | return log_debug_errno(SYNTHETIC_ERRNO(ENOEXEC), |
266 | 7.56k | "File '%s%s' is not marked executable.", |
267 | 7.56k | root, skip_leading_slash(original_path)); |
268 | | |
269 | 7.56k | if (ret_path) |
270 | 7.56k | *ret_path = TAKE_PTR(resolved_path); |
271 | 7.56k | if (ret_fd) |
272 | 7.56k | *ret_fd = TAKE_FD(fd); |
273 | 7.56k | if (ret_stat) |
274 | 7.56k | *ret_stat = st; |
275 | | |
276 | 7.56k | return 0; |
277 | 7.56k | } |
278 | | |
279 | | int conf_file_new_at( |
280 | | const char *path, |
281 | | const char *root, |
282 | | int rfd, |
283 | | ConfFilesFlags flags, |
284 | 7.56k | ConfFile **ret) { |
285 | 7.56k | int r; |
286 | | |
287 | 7.56k | assert(path); |
288 | 7.56k | assert(wildcard_fd_is_valid(rfd)); |
289 | 7.56k | assert(ret); |
290 | | |
291 | 7.56k | int log_level = conf_files_log_level(flags); |
292 | | |
293 | 7.56k | _cleanup_free_ char *_root = NULL; |
294 | 7.56k | if (DEBUG_LOGGING && !root) { |
295 | 0 | (void) fd_get_path(rfd, &_root); |
296 | 0 | root = _root; |
297 | 0 | } |
298 | | |
299 | 7.56k | _cleanup_(conf_file_freep) ConfFile *c = new(ConfFile, 1); |
300 | 7.56k | if (!c) |
301 | 0 | return log_oom_full(log_level); |
302 | | |
303 | 7.56k | *c = (ConfFile) { |
304 | 7.56k | .original_path = strdup(path), |
305 | 7.56k | .fd = -EBADF, |
306 | 7.56k | }; |
307 | | |
308 | 7.56k | if (!c->original_path) |
309 | 0 | return log_oom_full(log_level); |
310 | | |
311 | 7.56k | r = path_extract_filename(path, &c->filename); |
312 | 7.56k | if (r < 0) |
313 | 7.56k | return log_full_errno(log_level, r, "Failed to extract filename from '%s': %m", path); |
314 | | |
315 | 7.56k | _cleanup_free_ char *dirpath = NULL, *resolved_dirpath = NULL; |
316 | 7.56k | r = path_extract_directory(path, &dirpath); |
317 | 7.56k | if (r < 0 && r != -EDESTADDRREQ) |
318 | 7.56k | return log_full_errno(log_level, r, "Failed to extract directory from '%s': %m", path); |
319 | 7.56k | if (r >= 0) { |
320 | 7.56k | r = chaseat(rfd, rfd, dirpath, |
321 | 7.56k | CHASE_MUST_BE_DIRECTORY | conf_files_chase_flags(flags), |
322 | 7.56k | &resolved_dirpath, /* ret_fd= */ NULL); |
323 | 7.56k | if (r < 0) |
324 | 7.56k | return log_full_errno(log_level, r, "Failed to chase '%s%s': %m", empty_to_root(root), skip_leading_slash(dirpath)); |
325 | 7.56k | } |
326 | | |
327 | 7.56k | c->result = path_join(resolved_dirpath, c->filename); |
328 | 7.56k | if (!c->result) |
329 | 0 | return log_oom_full(log_level); |
330 | | |
331 | 7.56k | r = conf_file_chase_and_verify( |
332 | 7.56k | root, |
333 | 7.56k | rfd, |
334 | 7.56k | c->original_path, |
335 | 7.56k | c->result, |
336 | 7.56k | c->filename, |
337 | 7.56k | /* masked= */ NULL, |
338 | 7.56k | flags, |
339 | 7.56k | &c->resolved_path, |
340 | 7.56k | &c->fd, |
341 | 7.56k | &c->st); |
342 | 7.56k | if (r < 0) |
343 | 0 | return r; |
344 | | |
345 | 7.56k | *ret = TAKE_PTR(c); |
346 | 7.56k | return 0; |
347 | 7.56k | } |
348 | | |
349 | 7.56k | int conf_file_new(const char *path, const char *root, ConfFilesFlags flags, ConfFile **ret) { |
350 | 7.56k | int r; |
351 | | |
352 | 7.56k | assert(path); |
353 | 7.56k | assert(ret); |
354 | | |
355 | 7.56k | _cleanup_free_ char *root_abs = NULL; |
356 | 7.56k | _cleanup_close_ int rfd = -EBADF; |
357 | 7.56k | r = prepare_dirs(root, flags, /* dirs= */ NULL, &root_abs, &rfd, /* ret_dirs= */ NULL); |
358 | 7.56k | if (r < 0) |
359 | 0 | return r; |
360 | | |
361 | 7.56k | _cleanup_free_ char *path_abs = NULL; |
362 | 7.56k | if (!root_abs) { |
363 | 7.56k | r = path_make_absolute_cwd(path, &path_abs); |
364 | 7.56k | if (r < 0) |
365 | 7.56k | return log_full_errno(conf_files_log_level(flags), r, "Failed to make '%s' absolute: %m", path); |
366 | | |
367 | 7.56k | path = path_abs; |
368 | 7.56k | } |
369 | | |
370 | 7.56k | _cleanup_(conf_file_freep) ConfFile *c = NULL; |
371 | 7.56k | r = conf_file_new_at(path, root_abs, rfd, flags, &c); |
372 | 7.56k | if (r < 0) |
373 | 0 | return r; |
374 | | |
375 | 7.56k | r = conf_file_prefix_root(c, root_abs, flags); |
376 | 7.56k | if (r < 0) |
377 | 0 | return r; |
378 | | |
379 | 7.56k | *ret = TAKE_PTR(c); |
380 | 7.56k | return 0; |
381 | 7.56k | } |
382 | | |
383 | | DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( |
384 | | conf_file_hash_ops, |
385 | | char, string_hash_func, string_compare_func, |
386 | | ConfFile, conf_file_free); |
387 | | |
388 | | static int files_add( |
389 | | DIR *dir, |
390 | | const char *original_dirpath, |
391 | | const char *resolved_dirpath, |
392 | | const char *root, /* for logging, can be NULL */ |
393 | | int rfd, |
394 | | Hashmap **files, |
395 | | Set **masked, |
396 | | const char *suffix, |
397 | 0 | ConfFilesFlags flags) { |
398 | |
|
399 | 0 | int r; |
400 | |
|
401 | 0 | assert(dir); |
402 | 0 | assert(original_dirpath); |
403 | 0 | assert(resolved_dirpath); |
404 | 0 | assert(wildcard_fd_is_valid(rfd)); |
405 | 0 | assert(files); |
406 | 0 | assert(masked); |
407 | |
|
408 | 0 | int log_level = conf_files_log_level(flags); |
409 | |
|
410 | 0 | root = empty_to_root(root); |
411 | |
|
412 | 0 | FOREACH_DIRENT(de, dir, return log_full_errno(log_level, errno, "Failed to read directory '%s%s': %m", |
413 | 0 | root, skip_leading_slash(original_dirpath))) { |
414 | |
|
415 | 0 | _cleanup_free_ char *original_path = path_join(original_dirpath, de->d_name); |
416 | 0 | if (!original_path) |
417 | 0 | return log_oom_full(log_level); |
418 | | |
419 | | /* Does this match the suffix? */ |
420 | 0 | if (suffix && !endswith(de->d_name, suffix)) { |
421 | 0 | log_debug("Skipping file '%s%s', suffix is not '%s'.", root, skip_leading_slash(original_path), suffix); |
422 | 0 | continue; |
423 | 0 | } |
424 | | |
425 | | /* Has this file already been found in an earlier directory? */ |
426 | 0 | if (hashmap_contains(*files, de->d_name)) { |
427 | 0 | log_debug("Skipping overridden file '%s%s'.", root, skip_leading_slash(original_path)); |
428 | 0 | continue; |
429 | 0 | } |
430 | | |
431 | | /* Has this been masked in an earlier directory? */ |
432 | 0 | if ((flags & CONF_FILES_FILTER_MASKED) != 0 && set_contains(*masked, de->d_name)) { |
433 | 0 | log_debug("File '%s%s' is masked by previous entry.", root, skip_leading_slash(original_path)); |
434 | 0 | continue; |
435 | 0 | } |
436 | | |
437 | 0 | _cleanup_free_ char *p = path_join(resolved_dirpath, de->d_name); |
438 | 0 | if (!p) |
439 | 0 | return log_oom_full(log_level); |
440 | | |
441 | 0 | _cleanup_free_ char *resolved_path = NULL; |
442 | 0 | _cleanup_close_ int fd = -EBADF; |
443 | 0 | struct stat st; |
444 | 0 | r = conf_file_chase_and_verify( |
445 | 0 | root, |
446 | 0 | rfd, |
447 | 0 | original_path, |
448 | 0 | p, |
449 | 0 | de->d_name, |
450 | 0 | masked, |
451 | 0 | flags, |
452 | 0 | &resolved_path, |
453 | 0 | &fd, |
454 | 0 | &st); |
455 | 0 | if (r == -ENOMEM) |
456 | 0 | return r; |
457 | 0 | if (r < 0) |
458 | 0 | continue; |
459 | | |
460 | 0 | _cleanup_(conf_file_freep) ConfFile *c = new(ConfFile, 1); |
461 | 0 | if (!c) |
462 | 0 | return log_oom_full(log_level); |
463 | | |
464 | 0 | *c = (ConfFile) { |
465 | 0 | .filename = strdup(de->d_name), |
466 | 0 | .result = TAKE_PTR(p), |
467 | 0 | .original_path = TAKE_PTR(original_path), |
468 | 0 | .resolved_path = TAKE_PTR(resolved_path), |
469 | 0 | .fd = TAKE_FD(fd), |
470 | 0 | .st = st, |
471 | 0 | }; |
472 | |
|
473 | 0 | if (!c->filename) |
474 | 0 | return log_oom_full(log_level); |
475 | | |
476 | 0 | r = hashmap_ensure_put(files, &conf_file_hash_ops, c->filename, c); |
477 | 0 | if (r < 0) { |
478 | 0 | assert(r == -ENOMEM); |
479 | 0 | return log_oom_full(log_level); |
480 | 0 | } |
481 | 0 | assert(r > 0); |
482 | |
|
483 | 0 | TAKE_PTR(c); |
484 | 0 | } |
485 | | |
486 | 0 | return 0; |
487 | 0 | } |
488 | | |
489 | 0 | static int dump_files(Hashmap *fh, const char *root, ConfFilesFlags flags, ConfFile ***ret_files, size_t *ret_n_files) { |
490 | 0 | ConfFile **files = NULL; |
491 | 0 | size_t n_files = 0; |
492 | 0 | int r; |
493 | |
|
494 | 0 | CLEANUP_ARRAY(files, n_files, conf_file_free_array); |
495 | |
|
496 | 0 | assert(ret_files); |
497 | 0 | assert(ret_n_files); |
498 | | |
499 | | /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. */ |
500 | 0 | r = hashmap_dump_sorted(fh, (void***) &files, &n_files); |
501 | 0 | if (r < 0) |
502 | 0 | return log_oom_full(conf_files_log_level(flags)); |
503 | | |
504 | | /* Hence, we need to remove them from the hashmap. */ |
505 | 0 | FOREACH_ARRAY(i, files, n_files) |
506 | 0 | assert_se(hashmap_remove(fh, (*i)->filename) == *i); |
507 | |
|
508 | 0 | if (root) |
509 | 0 | FOREACH_ARRAY(i, files, n_files) { |
510 | 0 | r = conf_file_prefix_root(*i, root, flags); |
511 | 0 | if (r < 0) |
512 | 0 | return r; |
513 | 0 | } |
514 | | |
515 | 0 | *ret_files = TAKE_PTR(files); |
516 | 0 | *ret_n_files = n_files; |
517 | 0 | return 0; |
518 | 0 | } |
519 | | |
520 | | static int copy_and_sort_files_from_hashmap( |
521 | | Hashmap *fh, |
522 | | const char *suffix, |
523 | | const char *root, |
524 | | ConfFilesFlags flags, |
525 | 58.0k | char ***ret) { |
526 | | |
527 | 58.0k | _cleanup_strv_free_ char **results = NULL; |
528 | 58.0k | _cleanup_free_ ConfFile **files = NULL; |
529 | 58.0k | size_t n_files = 0, n_results = 0; |
530 | 58.0k | int r; |
531 | | |
532 | 58.0k | assert(ret); |
533 | | |
534 | 58.0k | int log_level = conf_files_log_level(flags); |
535 | | |
536 | | /* The entries in the array given by hashmap_dump_sorted() are still owned by the hashmap. |
537 | | * Hence, do not use conf_file_free_array() for 'entries' */ |
538 | 58.0k | r = hashmap_dump_sorted(fh, (void***) &files, &n_files); |
539 | 58.0k | if (r < 0) |
540 | 0 | return log_oom_full(log_level); |
541 | | |
542 | 58.0k | FOREACH_ARRAY(i, files, n_files) { |
543 | 0 | ConfFile *c = *i; |
544 | 0 | const char *add = NULL; |
545 | |
|
546 | 0 | if (FLAGS_SET(flags, CONF_FILES_BASENAME)) |
547 | 0 | add = c->filename; |
548 | 0 | else if (root && !FLAGS_SET(flags, CONF_FILES_DONT_PREFIX_ROOT)) { |
549 | 0 | _cleanup_free_ char *p = NULL; |
550 | |
|
551 | 0 | r = chaseat_prefix_root(c->result, root, &p); |
552 | 0 | if (r < 0) |
553 | 0 | return log_full_errno(log_level, r, "Failed to prefix '%s' with root '%s': %m", c->result, root); |
554 | | |
555 | 0 | if (FLAGS_SET(flags, CONF_FILES_TRUNCATE_SUFFIX) && suffix) { |
556 | 0 | char *e = endswith(p, suffix); |
557 | 0 | if (!e) |
558 | 0 | continue; |
559 | | |
560 | 0 | *e = 0; |
561 | 0 | } |
562 | | |
563 | 0 | if (strv_consume_with_size(&results, &n_results, TAKE_PTR(p)) < 0) |
564 | 0 | return log_oom_full(log_level); |
565 | | |
566 | 0 | continue; |
567 | 0 | } else |
568 | 0 | add = c->result; |
569 | | |
570 | 0 | if (FLAGS_SET(flags, CONF_FILES_TRUNCATE_SUFFIX)) { |
571 | 0 | const char *e = endswith(add, suffix); |
572 | 0 | if (!e) |
573 | 0 | continue; |
574 | | |
575 | 0 | _cleanup_free_ char *n = strndup(add, e - add); |
576 | 0 | if (!n) |
577 | 0 | return log_oom_full(log_level); |
578 | | |
579 | 0 | r = strv_consume_with_size(&results, &n_results, TAKE_PTR(n)); |
580 | 0 | } else |
581 | 0 | r = strv_extend_with_size(&results, &n_results, add); |
582 | 0 | if (r < 0) |
583 | 0 | return log_oom_full(log_level); |
584 | 0 | } |
585 | | |
586 | 58.0k | *ret = TAKE_PTR(results); |
587 | 58.0k | return 0; |
588 | 58.0k | } |
589 | | |
590 | 0 | static int insert_replacement(Hashmap **fh, ConfFile *replacement, ConfFilesFlags flags, const ConfFile **ret) { |
591 | 0 | _cleanup_(conf_file_freep) ConfFile *c = ASSERT_PTR(replacement); |
592 | 0 | int r; |
593 | |
|
594 | 0 | assert(fh); |
595 | 0 | assert(ret); |
596 | | |
597 | | /* This consumes the input ConfFile. */ |
598 | |
|
599 | 0 | ConfFile *existing = hashmap_get(*fh, c->filename); |
600 | 0 | if (existing) { |
601 | 0 | log_debug("An entry with higher priority '%s' -> '%s' already exists, ignoring the replacement: %s", |
602 | 0 | existing->filename, existing->result, c->original_path); |
603 | 0 | *ret = NULL; |
604 | 0 | return 0; |
605 | 0 | } |
606 | | |
607 | 0 | r = hashmap_ensure_put(fh, &conf_file_hash_ops, c->filename, c); |
608 | 0 | if (r < 0) { |
609 | 0 | assert(r == -ENOMEM); |
610 | 0 | return log_oom_full(conf_files_log_level(flags)); |
611 | 0 | } |
612 | 0 | assert(r > 0); |
613 | |
|
614 | 0 | log_debug("Inserted replacement: '%s' -> '%s'", c->filename, c->result); |
615 | |
|
616 | 0 | *ret = TAKE_PTR(c); |
617 | 0 | return 0; |
618 | 0 | } |
619 | | |
620 | | static int conf_files_list_impl( |
621 | | const char *suffix, |
622 | | const char *root, /* for logging, can be NULL */ |
623 | | int rfd, |
624 | | ConfFilesFlags flags, |
625 | | const char * const *dirs, |
626 | | const char *replacement, |
627 | | Hashmap **ret, |
628 | 58.0k | const ConfFile **ret_inserted) { |
629 | | |
630 | 58.0k | _cleanup_hashmap_free_ Hashmap *fh = NULL; |
631 | 58.0k | _cleanup_set_free_ Set *masked = NULL; |
632 | 58.0k | _cleanup_(conf_file_freep) ConfFile *c = NULL; |
633 | 58.0k | const ConfFile *inserted = NULL; |
634 | 58.0k | int r; |
635 | | |
636 | 58.0k | assert(wildcard_fd_is_valid(rfd)); |
637 | 58.0k | assert(ret); |
638 | | |
639 | 58.0k | root = empty_to_root(root); |
640 | | |
641 | 58.0k | if (replacement) { |
642 | 0 | r = conf_file_new_at( |
643 | 0 | replacement, root, rfd, |
644 | 0 | flags & (CONF_FILES_WARN|CONF_FILES_CHASE_SAFE|CONF_FILES_CHASE_MAX_MODE), |
645 | 0 | &c); |
646 | 0 | if (r < 0) |
647 | 0 | return r; |
648 | 0 | } |
649 | | |
650 | 232k | STRV_FOREACH(p, dirs) { |
651 | 232k | _cleanup_closedir_ DIR *dir = NULL; |
652 | 232k | _cleanup_free_ char *path = NULL; |
653 | | |
654 | 232k | r = chase_and_opendirat(rfd, rfd, *p, conf_files_extra_chase_flags(flags), &path, &dir); |
655 | 232k | if (r < 0) { |
656 | 232k | if (r != -ENOENT) |
657 | 0 | log_full_errno(conf_files_log_level(flags), r, |
658 | 232k | "Failed to chase and open directory '%s%s', ignoring: %m", |
659 | 232k | root, skip_leading_slash(*p)); |
660 | 232k | continue; |
661 | 232k | } |
662 | | |
663 | 0 | if (c && streq_ptr(path_startswith(c->result, path), c->filename)) { |
664 | 0 | r = insert_replacement(&fh, TAKE_PTR(c), flags, &inserted); |
665 | 0 | if (r < 0) |
666 | 0 | return r; |
667 | 0 | } |
668 | | |
669 | 0 | r = files_add(dir, *p, path, root, rfd, &fh, &masked, suffix, flags); |
670 | 0 | if (r == -ENOMEM) |
671 | 0 | return r; |
672 | 0 | } |
673 | | |
674 | 58.0k | if (c) { |
675 | 0 | r = insert_replacement(&fh, TAKE_PTR(c), flags, &inserted); |
676 | 0 | if (r < 0) |
677 | 0 | return r; |
678 | 0 | } |
679 | | |
680 | 58.0k | *ret = TAKE_PTR(fh); |
681 | 58.0k | if (ret_inserted) |
682 | 0 | *ret_inserted = inserted; |
683 | 58.0k | return 0; |
684 | 58.0k | } |
685 | | |
686 | | int conf_files_list_strv( |
687 | | char ***ret, |
688 | | const char *suffix, |
689 | | const char *root, |
690 | | ConfFilesFlags flags, |
691 | 0 | const char * const *dirs) { |
692 | |
|
693 | 0 | _cleanup_hashmap_free_ Hashmap *fh = NULL; |
694 | 0 | _cleanup_close_ int rfd = -EBADF; |
695 | 0 | _cleanup_free_ char *root_abs = NULL; |
696 | 0 | _cleanup_strv_free_ char **dirs_abs = NULL; |
697 | 0 | int r; |
698 | |
|
699 | 0 | assert(ret); |
700 | |
|
701 | 0 | r = prepare_dirs(root, flags, (char**) dirs, &root_abs, &rfd, &dirs_abs); |
702 | 0 | if (r < 0) |
703 | 0 | return r; |
704 | | |
705 | 0 | r = conf_files_list_impl(suffix, root_abs, rfd, flags, (const char * const *) dirs_abs, |
706 | 0 | /* replacement= */ NULL, &fh, /* ret_inserted= */ NULL); |
707 | 0 | if (r < 0) |
708 | 0 | return r; |
709 | | |
710 | 0 | return copy_and_sort_files_from_hashmap(fh, suffix, empty_to_root(root_abs), flags, ret); |
711 | 0 | } |
712 | | |
713 | | int conf_files_list_strv_full( |
714 | | const char *suffix, |
715 | | const char *root, |
716 | | ConfFilesFlags flags, |
717 | | const char * const *dirs, |
718 | | ConfFile ***ret_files, |
719 | 0 | size_t *ret_n_files) { |
720 | |
|
721 | 0 | _cleanup_hashmap_free_ Hashmap *fh = NULL; |
722 | 0 | _cleanup_close_ int rfd = -EBADF; |
723 | 0 | _cleanup_free_ char *root_abs = NULL; |
724 | 0 | _cleanup_strv_free_ char **dirs_abs = NULL; |
725 | 0 | int r; |
726 | |
|
727 | 0 | assert(ret_files); |
728 | 0 | assert(ret_n_files); |
729 | |
|
730 | 0 | r = prepare_dirs(root, flags, (char**) dirs, &root_abs, &rfd, &dirs_abs); |
731 | 0 | if (r < 0) |
732 | 0 | return r; |
733 | | |
734 | 0 | r = conf_files_list_impl(suffix, root_abs, rfd, flags, (const char * const *) dirs_abs, |
735 | 0 | /* replacement= */ NULL, &fh, /* ret_inserted= */ NULL); |
736 | 0 | if (r < 0) |
737 | 0 | return r; |
738 | | |
739 | 0 | return dump_files(fh, empty_to_root(root_abs), flags, ret_files, ret_n_files); |
740 | 0 | } |
741 | | |
742 | | int conf_files_list_strv_at( |
743 | | char ***ret, |
744 | | const char *suffix, |
745 | | int rfd, |
746 | | ConfFilesFlags flags, |
747 | 58.0k | const char * const *dirs) { |
748 | | |
749 | 58.0k | _cleanup_hashmap_free_ Hashmap *fh = NULL; |
750 | 58.0k | _cleanup_free_ char *root = NULL; |
751 | 58.0k | int r; |
752 | | |
753 | 58.0k | assert(wildcard_fd_is_valid(rfd)); |
754 | 58.0k | assert(ret); |
755 | | |
756 | 58.0k | if (DEBUG_LOGGING) |
757 | 0 | (void) fd_get_path(rfd, &root); /* for logging */ |
758 | | |
759 | 58.0k | r = conf_files_list_impl(suffix, root, rfd, flags, dirs, /* replacement= */ NULL, &fh, /* ret_inserted= */ NULL); |
760 | 58.0k | if (r < 0) |
761 | 0 | return r; |
762 | | |
763 | 58.0k | return copy_and_sort_files_from_hashmap(fh, suffix, /* root= */ NULL, flags, ret); |
764 | 58.0k | } |
765 | | |
766 | | int conf_files_list_strv_at_full( |
767 | | const char *suffix, |
768 | | int rfd, |
769 | | ConfFilesFlags flags, |
770 | | const char * const *dirs, |
771 | | ConfFile ***ret_files, |
772 | 0 | size_t *ret_n_files) { |
773 | |
|
774 | 0 | _cleanup_hashmap_free_ Hashmap *fh = NULL; |
775 | 0 | _cleanup_free_ char *root = NULL; |
776 | 0 | int r; |
777 | |
|
778 | 0 | assert(wildcard_fd_is_valid(rfd)); |
779 | 0 | assert(ret_files); |
780 | 0 | assert(ret_n_files); |
781 | |
|
782 | 0 | if (DEBUG_LOGGING) |
783 | 0 | (void) fd_get_path(rfd, &root); /* for logging */ |
784 | |
|
785 | 0 | r = conf_files_list_impl(suffix, root, rfd, flags, dirs, /* replacement= */ NULL, &fh, /* ret_inserted= */ NULL); |
786 | 0 | if (r < 0) |
787 | 0 | return r; |
788 | | |
789 | 0 | return dump_files(fh, /* root= */ NULL, flags, ret_files, ret_n_files); |
790 | 0 | } |
791 | | |
792 | 0 | int conf_files_list(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dir) { |
793 | 0 | return conf_files_list_strv(ret, suffix, root, flags, STRV_MAKE_CONST(dir)); |
794 | 0 | } |
795 | | |
796 | 0 | int conf_files_list_full(const char *suffix, const char *root, ConfFilesFlags flags, const char *dir, ConfFile ***ret_files, size_t *ret_n_files) { |
797 | 0 | return conf_files_list_strv_full(suffix, root, flags, STRV_MAKE_CONST(dir), ret_files, ret_n_files); |
798 | 0 | } |
799 | | |
800 | 0 | int conf_files_list_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dir) { |
801 | 0 | return conf_files_list_strv_at(ret, suffix, rfd, flags, STRV_MAKE_CONST(dir)); |
802 | 0 | } |
803 | | |
804 | 0 | int conf_files_list_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char *dir, ConfFile ***ret_files, size_t *ret_n_files) { |
805 | 0 | return conf_files_list_strv_at_full(suffix, rfd, flags, STRV_MAKE_CONST(dir), ret_files, ret_n_files); |
806 | 0 | } |
807 | | |
808 | 0 | int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs) { |
809 | 0 | _cleanup_strv_free_ char **d = NULL; |
810 | |
|
811 | 0 | assert(ret); |
812 | |
|
813 | 0 | d = strv_split_nulstr(dirs); |
814 | 0 | if (!d) |
815 | 0 | return log_oom_full(conf_files_log_level(flags)); |
816 | | |
817 | 0 | return conf_files_list_strv(ret, suffix, root, flags, (const char**) d); |
818 | 0 | } |
819 | | |
820 | 0 | int conf_files_list_nulstr_full(const char *suffix, const char *root, ConfFilesFlags flags, const char *dirs, ConfFile ***ret_files, size_t *ret_n_files) { |
821 | 0 | _cleanup_strv_free_ char **d = NULL; |
822 | |
|
823 | 0 | assert(ret_files); |
824 | 0 | assert(ret_n_files); |
825 | |
|
826 | 0 | d = strv_split_nulstr(dirs); |
827 | 0 | if (!d) |
828 | 0 | return log_oom_full(conf_files_log_level(flags)); |
829 | | |
830 | 0 | return conf_files_list_strv_full(suffix, root, flags, (const char**) d, ret_files, ret_n_files); |
831 | 0 | } |
832 | | |
833 | 0 | int conf_files_list_nulstr_at(char ***ret, const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs) { |
834 | 0 | _cleanup_strv_free_ char **d = NULL; |
835 | |
|
836 | 0 | assert(ret); |
837 | |
|
838 | 0 | d = strv_split_nulstr(dirs); |
839 | 0 | if (!d) |
840 | 0 | return log_oom_full(conf_files_log_level(flags)); |
841 | | |
842 | 0 | return conf_files_list_strv_at(ret, suffix, rfd, flags, (const char**) d); |
843 | 0 | } |
844 | | |
845 | 0 | int conf_files_list_nulstr_at_full(const char *suffix, int rfd, ConfFilesFlags flags, const char *dirs, ConfFile ***ret_files, size_t *ret_n_files) { |
846 | 0 | _cleanup_strv_free_ char **d = NULL; |
847 | |
|
848 | 0 | assert(ret_files); |
849 | 0 | assert(ret_n_files); |
850 | |
|
851 | 0 | d = strv_split_nulstr(dirs); |
852 | 0 | if (!d) |
853 | 0 | return log_oom_full(conf_files_log_level(flags)); |
854 | | |
855 | 0 | return conf_files_list_strv_at_full(suffix, rfd, flags, (const char**) d, ret_files, ret_n_files); |
856 | 0 | } |
857 | | |
858 | | int conf_files_list_with_replacement( |
859 | | const char *root, |
860 | | char **config_dirs, |
861 | | const char *replacement, |
862 | | char ***ret_files, |
863 | 0 | char **ret_inserted) { |
864 | |
|
865 | 0 | _cleanup_hashmap_free_ Hashmap *fh = NULL; |
866 | 0 | _cleanup_free_ char *inserted = NULL; |
867 | 0 | ConfFilesFlags flags = CONF_FILES_REGULAR | CONF_FILES_FILTER_MASKED_BY_SYMLINK | CONF_FILES_WARN; |
868 | 0 | _cleanup_close_ int rfd = -EBADF; |
869 | 0 | _cleanup_free_ char *root_abs = NULL; |
870 | 0 | _cleanup_strv_free_ char **dirs_abs = NULL; |
871 | 0 | const ConfFile *c = NULL; |
872 | 0 | int r; |
873 | |
|
874 | 0 | assert(ret_files); |
875 | |
|
876 | 0 | r = prepare_dirs(root, flags, config_dirs, &root_abs, &rfd, &dirs_abs); |
877 | 0 | if (r < 0) |
878 | 0 | return r; |
879 | | |
880 | 0 | r = conf_files_list_impl(".conf", root_abs, rfd, flags, (const char * const *) dirs_abs, |
881 | 0 | replacement, &fh, ret_inserted ? &c : NULL); |
882 | 0 | if (r < 0) |
883 | 0 | return r; |
884 | | |
885 | 0 | if (c) { |
886 | 0 | r = chaseat_prefix_root(c->result, root_abs, &inserted); |
887 | 0 | if (r < 0) |
888 | 0 | return log_full_errno(conf_files_log_level(flags), r, |
889 | 0 | "Failed to prefix '%s' with root '%s': %m", |
890 | 0 | c->result, empty_to_root(root_abs)); |
891 | 0 | } |
892 | | |
893 | 0 | r = copy_and_sort_files_from_hashmap(fh, ".conf", empty_to_root(root_abs), flags, ret_files); |
894 | 0 | if (r < 0) |
895 | 0 | return r; |
896 | | |
897 | 0 | if (ret_inserted) |
898 | 0 | *ret_inserted = TAKE_PTR(inserted); |
899 | 0 | return 0; |
900 | 0 | } |
901 | | |
902 | | int conf_files_list_dropins( |
903 | | char ***ret, |
904 | | const char *dropin_dirname, |
905 | | const char *root, |
906 | | int root_fd, |
907 | | ConfFilesFlags flags, |
908 | 58.0k | const char * const *dirs) { |
909 | | |
910 | 58.0k | _cleanup_strv_free_ char **dropin_dirs = NULL; |
911 | 58.0k | const char *suffix; |
912 | 58.0k | int r; |
913 | | |
914 | 58.0k | assert(ret); |
915 | 58.0k | assert(dropin_dirname); |
916 | 58.0k | assert(dirs); |
917 | | |
918 | 58.0k | suffix = strjoina("/", dropin_dirname); |
919 | 58.0k | r = strv_extend_strv_concat(&dropin_dirs, dirs, suffix); |
920 | 58.0k | if (r < 0) |
921 | 0 | return log_oom_full(conf_files_log_level(flags)); |
922 | | |
923 | 58.0k | return conf_files_list_strv_at(ret, ".conf", root_fd, flags, (const char* const*) dropin_dirs); |
924 | 58.0k | } |
925 | | |
926 | | /** |
927 | | * Open and read a config file. |
928 | | * |
929 | | * The <fn> argument may be: |
930 | | * - '-', meaning stdin. |
931 | | * - a file name without a path. In this case <config_dirs> are searched. |
932 | | * - a path, either relative or absolute. In this case <fn> is opened directly. |
933 | | * |
934 | | * This method is only suitable for configuration files which have a flat layout without dropins. |
935 | | */ |
936 | | int conf_file_read( |
937 | | const char *root, |
938 | | const char **config_dirs, |
939 | | const char *fn, |
940 | | parse_line_t parse_line, |
941 | | void *userdata, |
942 | | bool ignore_enoent, |
943 | 0 | bool *invalid_config) { |
944 | |
|
945 | 0 | _cleanup_fclose_ FILE *_f = NULL; |
946 | 0 | _cleanup_free_ char *_fn = NULL; |
947 | 0 | unsigned v = 0; |
948 | 0 | FILE *f; |
949 | 0 | int r = 0; |
950 | |
|
951 | 0 | assert(fn); |
952 | |
|
953 | 0 | if (streq(fn, "-")) { |
954 | 0 | f = stdin; |
955 | 0 | fn = "<stdin>"; |
956 | |
|
957 | 0 | log_debug("Reading config from stdin%s", glyph(GLYPH_ELLIPSIS)); |
958 | |
|
959 | 0 | } else if (is_path(fn)) { |
960 | 0 | r = path_make_absolute_cwd(fn, &_fn); |
961 | 0 | if (r < 0) |
962 | 0 | return log_error_errno(r, "Failed to make path absolute: %m"); |
963 | 0 | fn = _fn; |
964 | |
|
965 | 0 | f = _f = fopen(fn, "re"); |
966 | 0 | if (!_f) |
967 | 0 | r = -errno; |
968 | 0 | else |
969 | 0 | log_debug("Reading config file \"%s\"%s", fn, glyph(GLYPH_ELLIPSIS)); |
970 | |
|
971 | 0 | } else { |
972 | 0 | r = search_and_fopen(fn, "re", root, config_dirs, &_f, &_fn); |
973 | 0 | if (r >= 0) { |
974 | 0 | f = _f; |
975 | 0 | fn = _fn; |
976 | 0 | log_debug("Reading config file \"%s\"%s", fn, glyph(GLYPH_ELLIPSIS)); |
977 | 0 | } |
978 | 0 | } |
979 | | |
980 | 0 | if (r == -ENOENT && ignore_enoent) { |
981 | 0 | log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn); |
982 | 0 | return 0; /* No error, but nothing happened. */ |
983 | 0 | } |
984 | 0 | if (r < 0) |
985 | 0 | return log_error_errno(r, "Failed to read '%s': %m", fn); |
986 | | |
987 | 0 | r = 1; /* We entered the part where we may modify state. */ |
988 | |
|
989 | 0 | for (;;) { |
990 | 0 | _cleanup_free_ char *line = NULL; |
991 | 0 | bool invalid_line = false; |
992 | 0 | int k; |
993 | |
|
994 | 0 | k = read_stripped_line(f, LONG_LINE_MAX, &line); |
995 | 0 | if (k < 0) |
996 | 0 | return log_error_errno(k, "Failed to read '%s': %m", fn); |
997 | 0 | if (k == 0) |
998 | 0 | break; |
999 | | |
1000 | 0 | v++; |
1001 | |
|
1002 | 0 | if (IN_SET(line[0], 0, '#')) |
1003 | 0 | continue; |
1004 | | |
1005 | 0 | k = parse_line(fn, v, line, invalid_config ? &invalid_line : NULL, userdata); |
1006 | 0 | if (k < 0 && invalid_line) |
1007 | | /* Allow reporting with a special code if the caller requested this. */ |
1008 | 0 | *invalid_config = true; |
1009 | 0 | else |
1010 | | /* The first error, if any, becomes our return value. */ |
1011 | 0 | RET_GATHER(r, k); |
1012 | 0 | } |
1013 | | |
1014 | 0 | if (ferror(f)) |
1015 | 0 | RET_GATHER(r, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read from file %s.", fn)); |
1016 | |
|
1017 | 0 | return r; |
1018 | 0 | } |