]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
5cfa33e0 | 2 | |
c2911d48 ZJS |
3 | #include "sd-id128.h" |
4 | ||
f461a28d | 5 | #include "chase.h" |
e8630e69 ZJS |
6 | #include "dirent-util.h" |
7 | #include "fd-util.h" | |
8 | #include "fs-util.h" | |
baa6a42d | 9 | #include "initrd-util.h" |
5cfa33e0 | 10 | #include "macro.h" |
e8630e69 ZJS |
11 | #include "path-lookup.h" |
12 | #include "set.h" | |
da33cba0 | 13 | #include "special.h" |
e8630e69 | 14 | #include "stat-util.h" |
7d1e91d1 | 15 | #include "string-util.h" |
e8630e69 | 16 | #include "strv.h" |
5cfa33e0 ZJS |
17 | #include "unit-file.h" |
18 | ||
19 | bool unit_type_may_alias(UnitType type) { | |
20 | return IN_SET(type, | |
21 | UNIT_SERVICE, | |
22 | UNIT_SOCKET, | |
23 | UNIT_TARGET, | |
24 | UNIT_DEVICE, | |
25 | UNIT_TIMER, | |
26 | UNIT_PATH); | |
27 | } | |
28 | ||
29 | bool unit_type_may_template(UnitType type) { | |
30 | return IN_SET(type, | |
31 | UNIT_SERVICE, | |
32 | UNIT_SOCKET, | |
33 | UNIT_TARGET, | |
34 | UNIT_TIMER, | |
35 | UNIT_PATH); | |
36 | } | |
7d1e91d1 | 37 | |
1bf15585 | 38 | int unit_symlink_name_compatible(const char *symlink, const char *target, bool instance_propagation) { |
9a4f9e69 | 39 | _cleanup_free_ char *template = NULL; |
1bf15585 | 40 | int r, un_type1, un_type2; |
9a4f9e69 | 41 | |
1bf15585 ZJS |
42 | un_type1 = unit_name_classify(symlink); |
43 | ||
44 | /* The straightforward case: the symlink name matches the target and we have a valid unit */ | |
45 | if (streq(symlink, target) && | |
46 | (un_type1 & (UNIT_NAME_PLAIN | UNIT_NAME_INSTANCE))) | |
9a4f9e69 ZJS |
47 | return 1; |
48 | ||
49 | r = unit_name_template(symlink, &template); | |
50 | if (r == -EINVAL) | |
51 | return 0; /* Not a template */ | |
52 | if (r < 0) | |
53 | return r; | |
54 | ||
1bf15585 ZJS |
55 | un_type2 = unit_name_classify(target); |
56 | ||
9a4f9e69 | 57 | /* An instance name points to a target that is just the template name */ |
1bf15585 ZJS |
58 | if (un_type1 == UNIT_NAME_INSTANCE && |
59 | un_type2 == UNIT_NAME_TEMPLATE && | |
60 | streq(template, target)) | |
61 | return 1; | |
62 | ||
63 | /* foo@.target.requires/bar@.service: instance will be propagated */ | |
64 | if (instance_propagation && | |
65 | un_type1 == UNIT_NAME_TEMPLATE && | |
66 | un_type2 == UNIT_NAME_TEMPLATE && | |
67 | streq(template, target)) | |
68 | return 1; | |
69 | ||
70 | return 0; | |
9a4f9e69 ZJS |
71 | } |
72 | ||
cbfdbffb | 73 | int unit_validate_alias_symlink_or_warn(int log_level, const char *filename, const char *target) { |
de282060 | 74 | _cleanup_free_ char *src = NULL, *dst = NULL; |
7d1e91d1 ZJS |
75 | _cleanup_free_ char *src_instance = NULL, *dst_instance = NULL; |
76 | UnitType src_unit_type, dst_unit_type; | |
73ce91a0 | 77 | UnitNameFlags src_name_type, dst_name_type; |
de282060 | 78 | int r; |
7d1e91d1 ZJS |
79 | |
80 | /* Check if the *alias* symlink is valid. This applies to symlinks like | |
81 | * /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks | |
82 | * and such. Neither does this apply to symlinks which *link* units, i.e. symlinks to outside of the | |
83 | * unit lookup path. | |
84 | * | |
85 | * -EINVAL is returned if the something is wrong with the source filename or the source unit type is | |
86 | * not allowed to symlink, | |
f663e646 ZJS |
87 | * -EXDEV if the target filename is not a valid unit name or doesn't match the source, |
88 | * -ELOOP for an alias to self. | |
7d1e91d1 ZJS |
89 | */ |
90 | ||
de282060 OJ |
91 | r = path_extract_filename(filename, &src); |
92 | if (r < 0) | |
93 | return r; | |
94 | ||
95 | r = path_extract_filename(target, &dst); | |
96 | if (r < 0) | |
97 | return r; | |
7d1e91d1 ZJS |
98 | |
99 | /* src checks */ | |
100 | ||
101 | src_name_type = unit_name_to_instance(src, &src_instance); | |
102 | if (src_name_type < 0) | |
cbfdbffb ZJS |
103 | return log_full_errno(log_level, src_name_type, |
104 | "%s: not a valid unit name \"%s\": %m", filename, src); | |
7d1e91d1 ZJS |
105 | |
106 | src_unit_type = unit_name_to_type(src); | |
107 | assert(src_unit_type >= 0); /* unit_name_to_instance() checked the suffix already */ | |
108 | ||
109 | if (!unit_type_may_alias(src_unit_type)) | |
cbfdbffb ZJS |
110 | return log_full_errno(log_level, SYNTHETIC_ERRNO(EINVAL), |
111 | "%s: symlinks are not allowed for units of this type, rejecting.", | |
112 | filename); | |
7d1e91d1 ZJS |
113 | |
114 | if (src_name_type != UNIT_NAME_PLAIN && | |
115 | !unit_type_may_template(src_unit_type)) | |
cbfdbffb ZJS |
116 | return log_full_errno(log_level, SYNTHETIC_ERRNO(EINVAL), |
117 | "%s: templates not allowed for %s units, rejecting.", | |
118 | filename, unit_type_to_string(src_unit_type)); | |
7d1e91d1 ZJS |
119 | |
120 | /* dst checks */ | |
121 | ||
f663e646 ZJS |
122 | if (streq(src, dst)) |
123 | return log_debug_errno(SYNTHETIC_ERRNO(ELOOP), | |
124 | "%s: unit self-alias: %s → %s, ignoring.", | |
125 | filename, src, dst); | |
126 | ||
7d1e91d1 ZJS |
127 | dst_name_type = unit_name_to_instance(dst, &dst_instance); |
128 | if (dst_name_type < 0) | |
cbfdbffb ZJS |
129 | return log_full_errno(log_level, dst_name_type == -EINVAL ? SYNTHETIC_ERRNO(EXDEV) : dst_name_type, |
130 | "%s points to \"%s\" which is not a valid unit name: %m", | |
131 | filename, dst); | |
7d1e91d1 ZJS |
132 | |
133 | if (!(dst_name_type == src_name_type || | |
134 | (src_name_type == UNIT_NAME_INSTANCE && dst_name_type == UNIT_NAME_TEMPLATE))) | |
cbfdbffb ZJS |
135 | return log_full_errno(log_level, SYNTHETIC_ERRNO(EXDEV), |
136 | "%s: symlink target name type \"%s\" does not match source, rejecting.", | |
137 | filename, dst); | |
7d1e91d1 ZJS |
138 | |
139 | if (dst_name_type == UNIT_NAME_INSTANCE) { | |
140 | assert(src_instance); | |
141 | assert(dst_instance); | |
142 | if (!streq(src_instance, dst_instance)) | |
cbfdbffb ZJS |
143 | return log_full_errno(log_level, SYNTHETIC_ERRNO(EXDEV), |
144 | "%s: unit symlink target \"%s\" instance name doesn't match, rejecting.", | |
145 | filename, dst); | |
7d1e91d1 ZJS |
146 | } |
147 | ||
148 | dst_unit_type = unit_name_to_type(dst); | |
149 | if (dst_unit_type != src_unit_type) | |
cbfdbffb ZJS |
150 | return log_full_errno(log_level, SYNTHETIC_ERRNO(EXDEV), |
151 | "%s: symlink target \"%s\" has incompatible suffix, rejecting.", | |
152 | filename, dst); | |
7d1e91d1 ZJS |
153 | |
154 | return 0; | |
155 | } | |
e8630e69 ZJS |
156 | |
157 | #define FOLLOW_MAX 8 | |
158 | ||
159 | static int unit_ids_map_get( | |
160 | Hashmap *unit_ids_map, | |
161 | const char *unit_name, | |
162 | const char **ret_fragment_path) { | |
163 | ||
164 | /* Resolve recursively until we hit an absolute path, i.e. a non-aliased unit. | |
165 | * | |
166 | * We distinguish the case where unit_name was not found in the hashmap at all, and the case where | |
167 | * some symlink was broken. | |
168 | * | |
169 | * If a symlink target points to an instance name, then we also check for the template. */ | |
170 | ||
171 | const char *id = NULL; | |
172 | int r; | |
173 | ||
174 | for (unsigned n = 0; n < FOLLOW_MAX; n++) { | |
175 | const char *t = hashmap_get(unit_ids_map, id ?: unit_name); | |
176 | if (!t) { | |
177 | _cleanup_free_ char *template = NULL; | |
178 | ||
179 | if (!id) | |
180 | return -ENOENT; | |
181 | ||
182 | r = unit_name_template(id, &template); | |
183 | if (r == -EINVAL) | |
184 | return -ENXIO; /* we failed to find the symlink target */ | |
185 | if (r < 0) | |
186 | return log_error_errno(r, "Failed to determine template name for %s: %m", id); | |
187 | ||
188 | t = hashmap_get(unit_ids_map, template); | |
189 | if (!t) | |
190 | return -ENXIO; | |
191 | ||
192 | /* We successfully switched from instanced name to a template, let's continue */ | |
193 | } | |
194 | ||
195 | if (path_is_absolute(t)) { | |
196 | if (ret_fragment_path) | |
197 | *ret_fragment_path = t; | |
198 | return 0; | |
199 | } | |
200 | ||
201 | id = t; | |
202 | } | |
203 | ||
204 | return -ELOOP; | |
205 | } | |
206 | ||
91e0ee5f ZJS |
207 | static bool lookup_paths_mtime_exclude(const LookupPaths *lp, const char *path) { |
208 | /* Paths that are under our exclusive control. Users shall not alter those directly. */ | |
209 | ||
210 | return streq_ptr(path, lp->generator) || | |
211 | streq_ptr(path, lp->generator_early) || | |
212 | streq_ptr(path, lp->generator_late) || | |
213 | streq_ptr(path, lp->transient) || | |
214 | streq_ptr(path, lp->persistent_control) || | |
215 | streq_ptr(path, lp->runtime_control); | |
216 | } | |
217 | ||
c2911d48 ZJS |
218 | #define HASH_KEY SD_ID128_MAKE(4e,86,1b,e3,39,b3,40,46,98,5d,b8,11,34,8f,c3,c1) |
219 | ||
220 | bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_hash, uint64_t *ret_new) { | |
221 | struct siphash state; | |
91e0ee5f | 222 | |
c2911d48 ZJS |
223 | siphash24_init(&state, HASH_KEY.bytes); |
224 | ||
de010b0b | 225 | STRV_FOREACH(dir, lp->search_path) { |
91e0ee5f ZJS |
226 | struct stat st; |
227 | ||
228 | if (lookup_paths_mtime_exclude(lp, *dir)) | |
229 | continue; | |
230 | ||
231 | /* Determine the latest lookup path modification time */ | |
232 | if (stat(*dir, &st) < 0) { | |
233 | if (errno == ENOENT) | |
234 | continue; | |
235 | ||
236 | log_debug_errno(errno, "Failed to stat %s, ignoring: %m", *dir); | |
237 | continue; | |
238 | } | |
239 | ||
c2911d48 | 240 | siphash24_compress_usec_t(timespec_load(&st.st_mtim), &state); |
91e0ee5f ZJS |
241 | } |
242 | ||
c2911d48 ZJS |
243 | uint64_t updated = siphash24_finalize(&state); |
244 | if (ret_new) | |
245 | *ret_new = updated; | |
246 | if (updated != timestamp_hash) | |
247 | log_debug("Modification times have changed, need to update cache."); | |
248 | return updated == timestamp_hash; | |
91e0ee5f ZJS |
249 | } |
250 | ||
7f304b85 | 251 | static int directory_name_is_valid(const char *name) { |
7f304b85 | 252 | |
38f90179 MY |
253 | /* Accept a directory whose name is a valid unit file name ending in .wants/, .requires/, |
254 | * .upholds/ or .d/ */ | |
7f304b85 | 255 | |
38f90179 | 256 | FOREACH_STRING(suffix, ".wants", ".requires", ".upholds", ".d") { |
7f304b85 YW |
257 | _cleanup_free_ char *chopped = NULL; |
258 | const char *e; | |
259 | ||
260 | e = endswith(name, suffix); | |
261 | if (!e) | |
262 | continue; | |
263 | ||
264 | chopped = strndup(name, e - name); | |
265 | if (!chopped) | |
266 | return log_oom(); | |
267 | ||
268 | if (unit_name_is_valid(chopped, UNIT_NAME_ANY) || | |
269 | unit_type_from_string(chopped) >= 0) | |
270 | return true; | |
271 | } | |
272 | ||
273 | return false; | |
274 | } | |
275 | ||
047d37dc | 276 | int unit_file_resolve_symlink( |
98251811 ZJS |
277 | const char *root_dir, |
278 | char **search_path, | |
279 | const char *dir, | |
280 | int dirfd, | |
281 | const char *filename, | |
047d37dc | 282 | bool resolve_destination_target, |
98251811 ZJS |
283 | char **ret_destination) { |
284 | ||
047d37dc | 285 | _cleanup_free_ char *target = NULL, *simplified = NULL, *dst = NULL, *_dir = NULL, *_filename = NULL; |
98251811 ZJS |
286 | int r; |
287 | ||
047d37dc ZJS |
288 | /* This can be called with either dir+dirfd valid and filename just a name, |
289 | * or !dir && dirfd==AT_FDCWD, and filename being a full path. | |
290 | * | |
291 | * If resolve_destination_target is true, an absolute path will be returned. | |
292 | * If not, an absolute path is returned for linked unit files, and a relative | |
48ed75ad ZJS |
293 | * path otherwise. |
294 | * | |
295 | * Returns an error, false if this is an alias, true if it's a linked unit file. */ | |
047d37dc | 296 | |
98251811 ZJS |
297 | assert(filename); |
298 | assert(ret_destination); | |
047d37dc ZJS |
299 | assert(dir || path_is_absolute(filename)); |
300 | assert(dirfd >= 0 || dirfd == AT_FDCWD); | |
98251811 ZJS |
301 | |
302 | r = readlinkat_malloc(dirfd, filename, &target); | |
303 | if (r < 0) | |
304 | return log_warning_errno(r, "Failed to read symlink %s%s%s: %m", | |
305 | dir, dir ? "/" : "", filename); | |
306 | ||
047d37dc ZJS |
307 | if (!dir) { |
308 | r = path_extract_directory(filename, &_dir); | |
309 | if (r < 0) | |
310 | return r; | |
311 | dir = _dir; | |
312 | ||
313 | r = path_extract_filename(filename, &_filename); | |
314 | if (r < 0) | |
315 | return r; | |
316 | if (r == O_DIRECTORY) | |
317 | return log_warning_errno(SYNTHETIC_ERRNO(EISDIR), | |
318 | "Unexpected path to a directory \"%s\", refusing.", filename); | |
319 | filename = _filename; | |
320 | } | |
321 | ||
98251811 ZJS |
322 | bool is_abs = path_is_absolute(target); |
323 | if (root_dir || !is_abs) { | |
324 | char *target_abs = path_join(is_abs ? root_dir : dir, target); | |
325 | if (!target_abs) | |
326 | return log_oom(); | |
327 | ||
328 | free_and_replace(target, target_abs); | |
329 | } | |
330 | ||
331 | /* Get rid of "." and ".." components in target path */ | |
f461a28d | 332 | r = chase(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL); |
98251811 ZJS |
333 | if (r < 0) |
334 | return log_warning_errno(r, "Failed to resolve symlink %s/%s pointing to %s: %m", | |
335 | dir, filename, target); | |
336 | ||
047d37dc ZJS |
337 | assert(path_is_absolute(simplified)); |
338 | ||
30fd9a2d | 339 | /* Check if the symlink remain inside of our search path. |
bd177c62 ZJS |
340 | * If yes, it is an alias. Verify that it is valid. |
341 | * | |
342 | * If no, then this is a linked unit file or mask, and we don't care about the target name | |
047d37dc ZJS |
343 | * when loading units, and we return the link *source* (resolve_destination_target == false); |
344 | * When this is called for installation purposes, we want the final destination, | |
345 | * so we return the *target*. | |
047d37dc | 346 | */ |
98251811 | 347 | const char *tail = path_startswith_strv(simplified, search_path); |
bd177c62 | 348 | if (tail) { /* An alias */ |
047d37dc ZJS |
349 | _cleanup_free_ char *target_name = NULL; |
350 | ||
351 | r = path_extract_filename(simplified, &target_name); | |
98251811 ZJS |
352 | if (r < 0) |
353 | return r; | |
354 | ||
cbfdbffb | 355 | r = unit_validate_alias_symlink_or_warn(LOG_NOTICE, filename, simplified); |
98251811 ZJS |
356 | if (r < 0) |
357 | return r; | |
f663e646 | 358 | if (is_path(tail)) |
e2341b6b DT |
359 | log_warning("Suspicious symlink %s/%s %s %s, treating as alias.", |
360 | dir, filename, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), simplified); | |
98251811 | 361 | |
047d37dc | 362 | dst = resolve_destination_target ? TAKE_PTR(simplified) : TAKE_PTR(target_name); |
bd177c62 ZJS |
363 | |
364 | } else { | |
e2341b6b | 365 | log_debug("Linked unit file: %s/%s %s %s", dir, filename, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), simplified); |
bd177c62 ZJS |
366 | |
367 | if (resolve_destination_target) | |
368 | dst = TAKE_PTR(simplified); | |
369 | else { | |
370 | dst = path_join(dir, filename); | |
371 | if (!dst) | |
372 | return log_oom(); | |
373 | } | |
98251811 ZJS |
374 | } |
375 | ||
376 | *ret_destination = TAKE_PTR(dst); | |
48ed75ad | 377 | return !tail; /* true if linked unit file */ |
98251811 ZJS |
378 | } |
379 | ||
e8630e69 ZJS |
380 | int unit_file_build_name_map( |
381 | const LookupPaths *lp, | |
c2911d48 | 382 | uint64_t *cache_timestamp_hash, |
3fb2326f ZJS |
383 | Hashmap **unit_ids_map, |
384 | Hashmap **unit_names_map, | |
385 | Set **path_cache) { | |
e8630e69 ZJS |
386 | |
387 | /* Build two mappings: any name → main unit (i.e. the end result of symlink resolution), unit name → | |
fa027117 | 388 | * all aliases (i.e. the entry for a given key is a list of all names which point to this key). The |
e8630e69 ZJS |
389 | * key is included in the value iff we saw a file or symlink with that name. In other words, if we |
390 | * have a key, but it is not present in the value for itself, there was an alias pointing to it, but | |
391 | * the unit itself is not loadable. | |
392 | * | |
3fb2326f ZJS |
393 | * At the same, build a cache of paths where to find units. The non-const parameters are for input |
394 | * and output. Existing contents will be freed before the new contents are stored. | |
e8630e69 ZJS |
395 | */ |
396 | ||
397 | _cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL; | |
398 | _cleanup_set_free_free_ Set *paths = NULL; | |
66c38cd0 | 399 | _cleanup_strv_free_ char **expanded_search_path = NULL; |
c2911d48 | 400 | uint64_t timestamp_hash; |
e8630e69 | 401 | int r; |
91e0ee5f | 402 | |
c2911d48 ZJS |
403 | /* Before doing anything, check if the timestamp hash that was passed is still valid. |
404 | * If yes, do nothing. */ | |
405 | if (cache_timestamp_hash && | |
406 | lookup_paths_timestamp_hash_same(lp, *cache_timestamp_hash, ×tamp_hash)) | |
f6067186 | 407 | return 0; |
c2911d48 ZJS |
408 | |
409 | /* The timestamp hash is now set based on the mtimes from before when we start reading files. | |
410 | * If anything is modified concurrently, we'll consider the cache outdated. */ | |
e8630e69 | 411 | |
3fb2326f ZJS |
412 | if (path_cache) { |
413 | paths = set_new(&path_hash_ops_free); | |
e8630e69 ZJS |
414 | if (!paths) |
415 | return log_oom(); | |
416 | } | |
417 | ||
66c38cd0 AR |
418 | /* Go over all our search paths, chase their symlinks and store the result in the |
419 | * expanded_search_path list. | |
420 | * | |
421 | * This is important for cases where any of the unit directories itself are symlinks into other | |
422 | * directories and would therefore cause all of the unit files to be recognized as linked units. | |
423 | * | |
424 | * This is important for distributions such as NixOS where most paths in /etc/ are symlinks to some | |
425 | * other location on the filesystem (e.g. into /nix/store/). | |
426 | * | |
427 | * Search paths are ordered by priority (highest first), and we need to maintain this order. | |
428 | * If a resolved path is already in the list, we don't need to include. | |
429 | * | |
430 | * Note that we build a list that contains both the original paths and the resolved symlinks: | |
431 | * we need the latter for the case where the directory is symlinked, as described above, and | |
432 | * the former for the case where some unit file alias is a dangling symlink that points to one | |
433 | * of the "original" directories (and can't be followed). | |
434 | */ | |
435 | STRV_FOREACH(dir, lp->search_path) { | |
436 | _cleanup_free_ char *resolved_dir = NULL; | |
437 | ||
438 | r = strv_extend(&expanded_search_path, *dir); | |
439 | if (r < 0) | |
440 | return log_oom(); | |
441 | ||
f461a28d | 442 | r = chase(*dir, NULL, 0, &resolved_dir, NULL); |
66c38cd0 AR |
443 | if (r < 0) { |
444 | if (r != -ENOENT) | |
445 | log_warning_errno(r, "Failed to resolve symlink %s, ignoring: %m", *dir); | |
446 | continue; | |
447 | } | |
448 | ||
449 | if (strv_contains(expanded_search_path, resolved_dir)) | |
450 | continue; | |
451 | ||
452 | if (strv_consume(&expanded_search_path, TAKE_PTR(resolved_dir)) < 0) | |
453 | return log_oom(); | |
454 | } | |
455 | ||
de010b0b | 456 | STRV_FOREACH(dir, lp->search_path) { |
e8630e69 ZJS |
457 | _cleanup_closedir_ DIR *d = NULL; |
458 | ||
459 | d = opendir(*dir); | |
460 | if (!d) { | |
461 | if (errno != ENOENT) | |
462 | log_warning_errno(errno, "Failed to open \"%s\", ignoring: %m", *dir); | |
463 | continue; | |
464 | } | |
465 | ||
14bb7295 | 466 | FOREACH_DIRENT_ALL(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) { |
d7ac0952 | 467 | _unused_ _cleanup_free_ char *_filename_free = NULL; |
95ef0eaf | 468 | char *filename; |
98251811 ZJS |
469 | _cleanup_free_ char *dst = NULL; |
470 | bool symlink_to_dir = false; | |
890befcf ZJS |
471 | |
472 | /* We only care about valid units and dirs with certain suffixes, let's ignore the | |
473 | * rest. */ | |
95ef0eaf | 474 | |
7f304b85 | 475 | if (de->d_type == DT_REG) { |
95ef0eaf | 476 | |
7f304b85 | 477 | /* Accept a regular file whose name is a valid unit file name. */ |
95ef0eaf LP |
478 | if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) |
479 | continue; | |
480 | ||
95ef0eaf | 481 | } else if (de->d_type == DT_DIR) { |
95ef0eaf LP |
482 | |
483 | if (!paths) /* Skip directories early unless path_cache is requested */ | |
484 | continue; | |
485 | ||
7f304b85 YW |
486 | r = directory_name_is_valid(de->d_name); |
487 | if (r < 0) | |
488 | return r; | |
489 | if (r == 0) | |
490 | continue; | |
491 | ||
492 | } else if (de->d_type == DT_LNK) { | |
95ef0eaf | 493 | |
7f304b85 YW |
494 | /* Accept a symlink file whose name is a valid unit file name or |
495 | * ending in .wants/, .requires/ or .d/. */ | |
496 | ||
497 | if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) { | |
498 | _cleanup_free_ char *target = NULL; | |
499 | ||
500 | if (!paths) /* Skip symlink to a directory early unless path_cache is requested */ | |
95ef0eaf LP |
501 | continue; |
502 | ||
7f304b85 YW |
503 | r = directory_name_is_valid(de->d_name); |
504 | if (r < 0) | |
505 | return r; | |
506 | if (r == 0) | |
507 | continue; | |
95ef0eaf | 508 | |
7f304b85 YW |
509 | r = readlinkat_malloc(dirfd(d), de->d_name, &target); |
510 | if (r < 0) { | |
511 | log_warning_errno(r, "Failed to read symlink %s/%s, ignoring: %m", | |
512 | *dir, de->d_name); | |
513 | continue; | |
95ef0eaf | 514 | } |
7f304b85 YW |
515 | |
516 | r = is_dir(target, /* follow = */ true); | |
517 | if (r <= 0) | |
518 | continue; | |
519 | ||
520 | symlink_to_dir = true; | |
95ef0eaf LP |
521 | } |
522 | ||
95ef0eaf | 523 | } else |
890befcf | 524 | continue; |
e8630e69 ZJS |
525 | |
526 | filename = path_join(*dir, de->d_name); | |
527 | if (!filename) | |
528 | return log_oom(); | |
529 | ||
3fb2326f | 530 | if (paths) { |
7f1238bd | 531 | r = set_put(paths, filename); |
e8630e69 ZJS |
532 | if (r < 0) |
533 | return log_oom(); | |
7f1238bd YW |
534 | if (r == 0) |
535 | _filename_free = filename; /* Make sure we free the filename. */ | |
e8630e69 ZJS |
536 | } else |
537 | _filename_free = filename; /* Make sure we free the filename. */ | |
538 | ||
7f304b85 | 539 | if (de->d_type == DT_DIR || (de->d_type == DT_LNK && symlink_to_dir)) |
e8630e69 | 540 | continue; |
e8630e69 | 541 | |
7f304b85 YW |
542 | assert(IN_SET(de->d_type, DT_REG, DT_LNK)); |
543 | ||
e8630e69 ZJS |
544 | /* search_path is ordered by priority (highest first). If the name is already mapped |
545 | * to something (incl. itself), it means that we have already seen it, and we should | |
546 | * ignore it here. */ | |
547 | if (hashmap_contains(ids, de->d_name)) | |
548 | continue; | |
549 | ||
550 | if (de->d_type == DT_LNK) { | |
551 | /* We don't explicitly check for alias loops here. unit_ids_map_get() which | |
552 | * limits the number of hops should be used to access the map. */ | |
553 | ||
66c38cd0 | 554 | r = unit_file_resolve_symlink(lp->root_dir, expanded_search_path, |
98251811 | 555 | *dir, dirfd(d), de->d_name, |
047d37dc | 556 | /* resolve_destination_target= */ false, |
98251811 ZJS |
557 | &dst); |
558 | if (r == -ENOMEM) | |
559 | return r; | |
560 | if (r < 0) /* we ignore other errors here */ | |
e8630e69 | 561 | continue; |
e8630e69 | 562 | |
98251811 ZJS |
563 | } else { |
564 | dst = TAKE_PTR(_filename_free); /* Grab the copy we made previously, if available. */ | |
565 | if (!dst) { | |
566 | dst = strdup(filename); | |
567 | if (!dst) | |
e8630e69 | 568 | return log_oom(); |
e8630e69 ZJS |
569 | } |
570 | ||
e8630e69 ZJS |
571 | log_debug("%s: normal unit file: %s", __func__, dst); |
572 | } | |
573 | ||
98251811 ZJS |
574 | _cleanup_free_ char *key = strdup(de->d_name); |
575 | if (!key) | |
576 | return log_oom(); | |
577 | ||
578 | r = hashmap_ensure_put(&ids, &string_hash_ops_free_free, key, dst); | |
e8630e69 | 579 | if (r < 0) |
e2341b6b DT |
580 | return log_warning_errno(r, "Failed to add entry to hashmap (%s%s%s): %m", |
581 | de->d_name, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), dst); | |
98251811 | 582 | key = dst = NULL; |
e8630e69 ZJS |
583 | } |
584 | } | |
585 | ||
586 | /* Let's also put the names in the reverse db. */ | |
e8630e69 | 587 | const char *dummy, *src; |
90e74a66 | 588 | HASHMAP_FOREACH_KEY(dummy, src, ids) { |
fd228387 | 589 | _cleanup_free_ char *inst = NULL, *dst_inst = NULL; |
e8630e69 ZJS |
590 | const char *dst; |
591 | ||
592 | r = unit_ids_map_get(ids, src, &dst); | |
593 | if (r < 0) | |
594 | continue; | |
595 | ||
596 | if (null_or_empty_path(dst) != 0) | |
597 | continue; | |
598 | ||
fd228387 ZJS |
599 | dst = basename(dst); |
600 | ||
601 | /* If we have an symlink from an instance name to a template name, it is an alias just for | |
602 | * this specific instance, foo@id.service ↔ template@id.service. */ | |
603 | if (unit_name_is_valid(dst, UNIT_NAME_TEMPLATE)) { | |
604 | UnitNameFlags t = unit_name_to_instance(src, &inst); | |
605 | if (t < 0) | |
606 | return log_error_errno(t, "Failed to extract instance part from %s: %m", src); | |
607 | if (t == UNIT_NAME_INSTANCE) { | |
608 | r = unit_name_replace_instance(dst, inst, &dst_inst); | |
609 | if (r < 0) { | |
610 | /* This might happen e.g. if the combined length is too large. | |
611 | * Let's not make too much of a fuss. */ | |
612 | log_debug_errno(r, "Failed to build alias name (%s + %s), ignoring: %m", | |
613 | dst, inst); | |
614 | continue; | |
615 | } | |
e8630e69 | 616 | |
fd228387 ZJS |
617 | dst = dst_inst; |
618 | } | |
619 | } | |
620 | ||
621 | r = string_strv_hashmap_put(&names, dst, src); | |
e8630e69 | 622 | if (r < 0) |
e2341b6b DT |
623 | return log_warning_errno(r, "Failed to add entry to hashmap (%s%s%s): %m", |
624 | dst, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), src); | |
e8630e69 ZJS |
625 | } |
626 | ||
c2911d48 ZJS |
627 | if (cache_timestamp_hash) |
628 | *cache_timestamp_hash = timestamp_hash; | |
3fb2326f ZJS |
629 | |
630 | hashmap_free_and_replace(*unit_ids_map, ids); | |
631 | hashmap_free_and_replace(*unit_names_map, names); | |
632 | if (path_cache) | |
633 | set_free_and_replace(*path_cache, paths); | |
e8630e69 | 634 | |
91e0ee5f | 635 | return 1; |
e8630e69 ZJS |
636 | } |
637 | ||
fd228387 ZJS |
638 | static int add_name( |
639 | const char *unit_name, | |
640 | Set **names, | |
641 | const char *name) { | |
642 | int r; | |
643 | ||
644 | assert(names); | |
645 | assert(name); | |
646 | ||
647 | r = set_put_strdup(names, name); | |
648 | if (r < 0) | |
649 | return r; | |
650 | if (r > 0 && !streq(unit_name, name)) | |
651 | log_debug("Unit %s has alias %s.", unit_name, name); | |
652 | return r; | |
653 | } | |
654 | ||
655 | static int add_names( | |
656 | Hashmap *unit_ids_map, | |
657 | Hashmap *unit_name_map, | |
658 | const char *unit_name, | |
659 | const char *fragment_basename, /* Only set when adding additional names based on fragment path */ | |
660 | UnitNameFlags name_type, | |
661 | const char *instance, | |
662 | Set **names, | |
663 | const char *name) { | |
664 | ||
de010b0b | 665 | char **aliases; |
fd228387 ZJS |
666 | int r; |
667 | ||
668 | assert(name_type == UNIT_NAME_PLAIN || instance); | |
669 | ||
670 | /* The unit has its own name if it's not a template. If we're looking at a fragment, the fragment | |
671 | * name (possibly with instance inserted), is also always one of the unit names. */ | |
672 | if (name_type != UNIT_NAME_TEMPLATE) { | |
673 | r = add_name(unit_name, names, name); | |
674 | if (r < 0) | |
675 | return r; | |
676 | } | |
677 | ||
678 | /* Add any aliases of the name to the set of names. | |
679 | * | |
680 | * We don't even need to know which fragment we will use. The unit_name_map should return the same | |
681 | * set of names for any of the aliases. */ | |
682 | aliases = hashmap_get(unit_name_map, name); | |
683 | STRV_FOREACH(alias, aliases) { | |
684 | if (name_type == UNIT_NAME_INSTANCE && unit_name_is_valid(*alias, UNIT_NAME_TEMPLATE)) { | |
685 | _cleanup_free_ char *inst = NULL; | |
686 | const char *inst_fragment = NULL; | |
687 | ||
688 | r = unit_name_replace_instance(*alias, instance, &inst); | |
689 | if (r < 0) | |
690 | return log_debug_errno(r, "Cannot build instance name %s + %s: %m", | |
691 | *alias, instance); | |
692 | ||
693 | /* Exclude any aliases that point in some other direction. | |
694 | * | |
695 | * See https://github.com/systemd/systemd/pull/13119#discussion_r308145418. */ | |
696 | r = unit_ids_map_get(unit_ids_map, inst, &inst_fragment); | |
697 | if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO)) | |
698 | return log_debug_errno(r, "Cannot find instance fragment %s: %m", inst); | |
699 | ||
700 | if (inst_fragment && | |
6c279977 | 701 | fragment_basename && |
178a71d9 | 702 | !path_equal_filename(inst_fragment, fragment_basename)) { |
fd228387 ZJS |
703 | log_debug("Instance %s has fragment %s and is not an alias of %s.", |
704 | inst, inst_fragment, unit_name); | |
705 | continue; | |
706 | } | |
707 | ||
7c35b78a | 708 | r = add_name(unit_name, names, inst); |
fd228387 ZJS |
709 | } else |
710 | r = add_name(unit_name, names, *alias); | |
fd228387 ZJS |
711 | if (r < 0) |
712 | return r; | |
713 | } | |
714 | ||
715 | return 0; | |
716 | } | |
717 | ||
e8630e69 ZJS |
718 | int unit_file_find_fragment( |
719 | Hashmap *unit_ids_map, | |
720 | Hashmap *unit_name_map, | |
721 | const char *unit_name, | |
722 | const char **ret_fragment_path, | |
723 | Set **ret_names) { | |
724 | ||
725 | const char *fragment = NULL; | |
726 | _cleanup_free_ char *template = NULL, *instance = NULL; | |
fd228387 ZJS |
727 | _cleanup_set_free_ Set *names = NULL; |
728 | int r; | |
e8630e69 ZJS |
729 | |
730 | /* Finds a fragment path, and returns the set of names: | |
731 | * if we have …/foo.service and …/foo-alias.service→foo.service, | |
732 | * and …/foo@.service and …/foo-alias@.service→foo@.service, | |
733 | * and …/foo@inst.service, | |
734 | * this should return: | |
735 | * foo.service → …/foo.service, {foo.service, foo-alias.service}, | |
736 | * foo-alias.service → …/foo.service, {foo.service, foo-alias.service}, | |
737 | * foo@.service → …/foo@.service, {foo@.service, foo-alias@.service}, | |
738 | * foo-alias@.service → …/foo@.service, {foo@.service, foo-alias@.service}, | |
739 | * foo@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service}, | |
740 | * foo-alias@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service}, | |
741 | * foo-alias@inst.service → …/foo@inst.service, {foo@inst.service, foo-alias@inst.service}. | |
742 | */ | |
743 | ||
fd228387 | 744 | UnitNameFlags name_type = unit_name_to_instance(unit_name, &instance); |
e8630e69 ZJS |
745 | if (name_type < 0) |
746 | return name_type; | |
747 | ||
11e9347b DDM |
748 | if (ret_names) { |
749 | r = add_names(unit_ids_map, unit_name_map, unit_name, NULL, name_type, instance, &names, unit_name); | |
750 | if (r < 0) | |
751 | return r; | |
752 | } | |
e8630e69 ZJS |
753 | |
754 | /* First try to load fragment under the original name */ | |
755 | r = unit_ids_map_get(unit_ids_map, unit_name, &fragment); | |
756 | if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO)) | |
757 | return log_debug_errno(r, "Cannot load unit %s: %m", unit_name); | |
758 | ||
e8630e69 ZJS |
759 | if (!fragment && name_type == UNIT_NAME_INSTANCE) { |
760 | /* Look for a fragment under the template name */ | |
761 | ||
762 | r = unit_name_template(unit_name, &template); | |
763 | if (r < 0) | |
98fac96c | 764 | return log_debug_errno(r, "Failed to determine template name: %m"); |
e8630e69 ZJS |
765 | |
766 | r = unit_ids_map_get(unit_ids_map, template, &fragment); | |
767 | if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO)) | |
771f8aef | 768 | return log_debug_errno(r, "Cannot load template %s: %m", template); |
fd228387 | 769 | } |
e8630e69 | 770 | |
11e9347b | 771 | if (fragment && ret_names) { |
de282060 OJ |
772 | _cleanup_free_ char *fragment_basename = NULL; |
773 | r = path_extract_filename(fragment, &fragment_basename); | |
774 | if (r < 0) | |
775 | return r; | |
e8630e69 | 776 | |
fd228387 ZJS |
777 | if (!streq(fragment_basename, unit_name)) { |
778 | /* Add names based on the fragment name to the set of names */ | |
779 | r = add_names(unit_ids_map, unit_name_map, unit_name, fragment_basename, name_type, instance, &names, fragment_basename); | |
780 | if (r < 0) | |
781 | return r; | |
e8630e69 ZJS |
782 | } |
783 | } | |
784 | ||
785 | *ret_fragment_path = fragment; | |
11e9347b DDM |
786 | if (ret_names) |
787 | *ret_names = TAKE_PTR(names); | |
e8630e69 | 788 | |
e8630e69 ZJS |
789 | return 0; |
790 | } | |
da33cba0 ZJS |
791 | |
792 | static const char * const rlmap[] = { | |
793 | "emergency", SPECIAL_EMERGENCY_TARGET, | |
794 | "-b", SPECIAL_EMERGENCY_TARGET, | |
795 | "rescue", SPECIAL_RESCUE_TARGET, | |
796 | "single", SPECIAL_RESCUE_TARGET, | |
797 | "-s", SPECIAL_RESCUE_TARGET, | |
798 | "s", SPECIAL_RESCUE_TARGET, | |
799 | "S", SPECIAL_RESCUE_TARGET, | |
800 | "1", SPECIAL_RESCUE_TARGET, | |
801 | "2", SPECIAL_MULTI_USER_TARGET, | |
802 | "3", SPECIAL_MULTI_USER_TARGET, | |
803 | "4", SPECIAL_MULTI_USER_TARGET, | |
804 | "5", SPECIAL_GRAPHICAL_TARGET, | |
805 | NULL | |
806 | }; | |
807 | ||
808 | static const char * const rlmap_initrd[] = { | |
809 | "emergency", SPECIAL_EMERGENCY_TARGET, | |
810 | "rescue", SPECIAL_RESCUE_TARGET, | |
811 | NULL | |
812 | }; | |
813 | ||
814 | const char* runlevel_to_target(const char *word) { | |
815 | const char * const *rlmap_ptr; | |
da33cba0 ZJS |
816 | |
817 | if (!word) | |
818 | return NULL; | |
819 | ||
820 | if (in_initrd()) { | |
821 | word = startswith(word, "rd."); | |
822 | if (!word) | |
823 | return NULL; | |
824 | } | |
825 | ||
826 | rlmap_ptr = in_initrd() ? rlmap_initrd : rlmap; | |
827 | ||
fe96c0f8 | 828 | for (size_t i = 0; rlmap_ptr[i]; i += 2) |
da33cba0 ZJS |
829 | if (streq(word, rlmap_ptr[i])) |
830 | return rlmap_ptr[i+1]; | |
831 | ||
832 | return NULL; | |
833 | } |