Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include "dirent-util.h"
4 : : #include "fd-util.h"
5 : : #include "fs-util.h"
6 : : #include "macro.h"
7 : : #include "path-lookup.h"
8 : : #include "set.h"
9 : : #include "stat-util.h"
10 : : #include "string-util.h"
11 : : #include "strv.h"
12 : : #include "unit-file.h"
13 : :
14 : 9392 : bool unit_type_may_alias(UnitType type) {
15 [ + + ]: 9392 : return IN_SET(type,
16 : : UNIT_SERVICE,
17 : : UNIT_SOCKET,
18 : : UNIT_TARGET,
19 : : UNIT_DEVICE,
20 : : UNIT_TIMER,
21 : : UNIT_PATH);
22 : : }
23 : :
24 : 11004 : bool unit_type_may_template(UnitType type) {
25 [ + - ]: 11004 : return IN_SET(type,
26 : : UNIT_SERVICE,
27 : : UNIT_SOCKET,
28 : : UNIT_TARGET,
29 : : UNIT_TIMER,
30 : : UNIT_PATH);
31 : : }
32 : :
33 : 384 : int unit_validate_alias_symlink_and_warn(const char *filename, const char *target) {
34 : : const char *src, *dst;
35 : 384 : _cleanup_free_ char *src_instance = NULL, *dst_instance = NULL;
36 : : UnitType src_unit_type, dst_unit_type;
37 : : int src_name_type, dst_name_type;
38 : :
39 : : /* Check if the *alias* symlink is valid. This applies to symlinks like
40 : : * /etc/systemd/system/dbus.service → dbus-broker.service, but not to .wants or .requires symlinks
41 : : * and such. Neither does this apply to symlinks which *link* units, i.e. symlinks to outside of the
42 : : * unit lookup path.
43 : : *
44 : : * -EINVAL is returned if the something is wrong with the source filename or the source unit type is
45 : : * not allowed to symlink,
46 : : * -EXDEV if the target filename is not a valid unit name or doesn't match the source.
47 : : */
48 : :
49 : 384 : src = basename(filename);
50 : 384 : dst = basename(target);
51 : :
52 : : /* src checks */
53 : :
54 : 384 : src_name_type = unit_name_to_instance(src, &src_instance);
55 [ - + ]: 384 : if (src_name_type < 0)
56 [ # # ]: 0 : return log_notice_errno(src_name_type,
57 : : "%s: not a valid unit name \"%s\": %m", filename, src);
58 : :
59 : 384 : src_unit_type = unit_name_to_type(src);
60 [ - + ]: 384 : assert(src_unit_type >= 0); /* unit_name_to_instance() checked the suffix already */
61 : :
62 [ + + ]: 384 : if (!unit_type_may_alias(src_unit_type))
63 [ + - ]: 8 : return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
64 : : "%s: symlinks are not allowed for units of this type, rejecting.",
65 : : filename);
66 : :
67 [ + + ]: 376 : if (src_name_type != UNIT_NAME_PLAIN &&
68 [ - + ]: 36 : !unit_type_may_template(src_unit_type))
69 [ # # ]: 0 : return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
70 : : "%s: templates not allowed for %s units, rejecting.",
71 : : filename, unit_type_to_string(src_unit_type));
72 : :
73 : : /* dst checks */
74 : :
75 : 376 : dst_name_type = unit_name_to_instance(dst, &dst_instance);
76 [ + + ]: 376 : if (dst_name_type < 0)
77 [ - + + - ]: 4 : return log_notice_errno(dst_name_type == -EINVAL ? SYNTHETIC_ERRNO(EXDEV) : dst_name_type,
78 : : "%s points to \"%s\" which is not a valid unit name: %m",
79 : : filename, dst);
80 : :
81 [ + + + + ]: 372 : if (!(dst_name_type == src_name_type ||
82 [ - + ]: 4 : (src_name_type == UNIT_NAME_INSTANCE && dst_name_type == UNIT_NAME_TEMPLATE)))
83 [ + - ]: 12 : return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
84 : : "%s: symlink target name type \"%s\" does not match source, rejecting.",
85 : : filename, dst);
86 : :
87 [ + + ]: 360 : if (dst_name_type == UNIT_NAME_INSTANCE) {
88 [ - + ]: 12 : assert(src_instance);
89 [ - + ]: 12 : assert(dst_instance);
90 [ + + ]: 12 : if (!streq(src_instance, dst_instance))
91 [ + - ]: 8 : return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
92 : : "%s: unit symlink target \"%s\" instance name doesn't match, rejecting.",
93 : : filename, dst);
94 : : }
95 : :
96 : 352 : dst_unit_type = unit_name_to_type(dst);
97 [ + + ]: 352 : if (dst_unit_type != src_unit_type)
98 [ + - ]: 8 : return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
99 : : "%s: symlink target \"%s\" has incompatible suffix, rejecting.",
100 : : filename, dst);
101 : :
102 : 344 : return 0;
103 : : }
104 : :
105 : : #define FOLLOW_MAX 8
106 : :
107 : 11964 : static int unit_ids_map_get(
108 : : Hashmap *unit_ids_map,
109 : : const char *unit_name,
110 : : const char **ret_fragment_path) {
111 : :
112 : : /* Resolve recursively until we hit an absolute path, i.e. a non-aliased unit.
113 : : *
114 : : * We distinguish the case where unit_name was not found in the hashmap at all, and the case where
115 : : * some symlink was broken.
116 : : *
117 : : * If a symlink target points to an instance name, then we also check for the template. */
118 : :
119 : 11964 : const char *id = NULL;
120 : : int r;
121 : :
122 [ + - ]: 12316 : for (unsigned n = 0; n < FOLLOW_MAX; n++) {
123 [ + + ]: 12316 : const char *t = hashmap_get(unit_ids_map, id ?: unit_name);
124 [ + + ]: 12316 : if (!t) {
125 [ - + ]: 8312 : _cleanup_free_ char *template = NULL;
126 : :
127 [ + - ]: 8312 : if (!id)
128 : 8312 : return -ENOENT;
129 : :
130 : 0 : r = unit_name_template(id, &template);
131 [ # # ]: 0 : if (r == -EINVAL)
132 : 0 : return -ENXIO; /* we failed to find the symlink target */
133 [ # # ]: 0 : if (r < 0)
134 [ # # ]: 0 : return log_error_errno(r, "Failed to determine template name for %s: %m", id);
135 : :
136 : 0 : t = hashmap_get(unit_ids_map, template);
137 [ # # ]: 0 : if (!t)
138 : 0 : return -ENXIO;
139 : :
140 : : /* We successfully switched from instanced name to a template, let's continue */
141 : : }
142 : :
143 [ + + ]: 4004 : if (path_is_absolute(t)) {
144 [ + - ]: 3652 : if (ret_fragment_path)
145 : 3652 : *ret_fragment_path = t;
146 : 3652 : return 0;
147 : : }
148 : :
149 : 352 : id = t;
150 : : }
151 : :
152 : 0 : return -ELOOP;
153 : : }
154 : :
155 : 8736 : static bool lookup_paths_mtime_exclude(const LookupPaths *lp, const char *path) {
156 : : /* Paths that are under our exclusive control. Users shall not alter those directly. */
157 : :
158 [ + + ]: 17464 : return streq_ptr(path, lp->generator) ||
159 [ + + ]: 17452 : streq_ptr(path, lp->generator_early) ||
160 [ + + ]: 17440 : streq_ptr(path, lp->generator_late) ||
161 [ + + ]: 17424 : streq_ptr(path, lp->transient) ||
162 [ + + + + ]: 26172 : streq_ptr(path, lp->persistent_control) ||
163 : 8704 : streq_ptr(path, lp->runtime_control);
164 : : }
165 : :
166 : 8624 : static bool lookup_paths_mtime_good(const LookupPaths *lp, usec_t mtime) {
167 : : char **dir;
168 : :
169 [ + - + + ]: 17292 : STRV_FOREACH(dir, (char**) lp->search_path) {
170 : : struct stat st;
171 : :
172 [ + + ]: 8668 : if (lookup_paths_mtime_exclude(lp, *dir))
173 : 36 : continue;
174 : :
175 : : /* Determine the latest lookup path modification time */
176 [ + + ]: 8644 : if (stat(*dir, &st) < 0) {
177 [ + - ]: 12 : if (errno == ENOENT)
178 : 12 : continue;
179 : :
180 [ # # ]: 0 : log_debug_errno(errno, "Failed to stat %s, ignoring: %m", *dir);
181 : 0 : continue;
182 : : }
183 : :
184 [ - + ]: 8632 : if (timespec_load(&st.st_mtim) > mtime) {
185 [ # # ]: 0 : log_debug_errno(errno, "Unit dir %s has changed, need to update cache.", *dir);
186 : 0 : return false;
187 : : }
188 : : }
189 : :
190 : 8624 : return true;
191 : : }
192 : :
193 : 8672 : int unit_file_build_name_map(
194 : : const LookupPaths *lp,
195 : : usec_t *cache_mtime,
196 : : Hashmap **ret_unit_ids_map,
197 : : Hashmap **ret_unit_names_map,
198 : : Set **ret_path_cache) {
199 : :
200 : : /* Build two mappings: any name → main unit (i.e. the end result of symlink resolution), unit name →
201 : : * all aliases (i.e. the entry for a given key is a a list of all names which point to this key). The
202 : : * key is included in the value iff we saw a file or symlink with that name. In other words, if we
203 : : * have a key, but it is not present in the value for itself, there was an alias pointing to it, but
204 : : * the unit itself is not loadable.
205 : : *
206 : : * At the same, build a cache of paths where to find units.
207 : : */
208 : :
209 : 8672 : _cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL;
210 : 8672 : _cleanup_set_free_free_ Set *paths = NULL;
211 : : char **dir;
212 : : int r;
213 : 8672 : usec_t mtime = 0;
214 : :
215 : : /* Before doing anything, check if the mtime that was passed is still valid. If
216 : : * yes, do nothing. If *cache_time == 0, always build the cache. */
217 [ + - + + : 8672 : if (cache_mtime && *cache_mtime > 0 && lookup_paths_mtime_good(lp, *cache_mtime))
+ - ]
218 : 8624 : return 0;
219 : :
220 [ + + ]: 48 : if (ret_path_cache) {
221 : 44 : paths = set_new(&path_hash_ops);
222 [ - + ]: 44 : if (!paths)
223 : 0 : return log_oom();
224 : : }
225 : :
226 [ + - + + ]: 140 : STRV_FOREACH(dir, (char**) lp->search_path) {
227 : : struct dirent *de;
228 [ + + - ]: 92 : _cleanup_closedir_ DIR *d = NULL;
229 : : struct stat st;
230 : :
231 : 92 : d = opendir(*dir);
232 [ + + ]: 92 : if (!d) {
233 [ - + ]: 24 : if (errno != ENOENT)
234 [ # # ]: 0 : log_warning_errno(errno, "Failed to open \"%s\", ignoring: %m", *dir);
235 : 24 : continue;
236 : : }
237 : :
238 : : /* Determine the latest lookup path modification time */
239 [ - + ]: 68 : if (fstat(dirfd(d), &st) < 0)
240 [ # # ]: 0 : return log_error_errno(errno, "Failed to fstat %s: %m", *dir);
241 : :
242 [ + + ]: 68 : if (!lookup_paths_mtime_exclude(lp, *dir))
243 : 56 : mtime = MAX(mtime, timespec_load(&st.st_mtim));
244 : :
245 [ + + - + : 4880 : FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) {
# # + + ]
246 : : char *filename;
247 [ + - + + : 6020 : _cleanup_free_ char *_filename_free = NULL, *simplified = NULL;
- + ]
248 : 4660 : const char *suffix, *dst = NULL;
249 : :
250 : 4660 : filename = path_join(*dir, de->d_name);
251 [ - + ]: 4660 : if (!filename)
252 : 0 : return log_oom();
253 : :
254 [ + + ]: 4660 : if (ret_path_cache) {
255 : 2360 : r = set_consume(paths, filename);
256 [ - + ]: 2360 : if (r < 0)
257 : 0 : return log_oom();
258 : : /* We will still use filename below. This is safe because we know the set
259 : : * holds a reference. */
260 : : } else
261 : 2300 : _filename_free = filename; /* Make sure we free the filename. */
262 : :
263 [ + + ]: 4660 : if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
264 : 1340 : continue;
265 [ - + ]: 3320 : assert_se(suffix = strrchr(de->d_name, '.'));
266 : :
267 : : /* search_path is ordered by priority (highest first). If the name is already mapped
268 : : * to something (incl. itself), it means that we have already seen it, and we should
269 : : * ignore it here. */
270 [ + + ]: 3320 : if (hashmap_contains(ids, de->d_name))
271 : 20 : continue;
272 : :
273 [ + + ]: 3300 : if (de->d_type == DT_LNK) {
274 : : /* We don't explicitly check for alias loops here. unit_ids_map_get() which
275 : : * limits the number of hops should be used to access the map. */
276 : :
277 [ + - - + : 528 : _cleanup_free_ char *target = NULL, *target_abs = NULL;
- - ]
278 : :
279 : 528 : r = readlinkat_malloc(dirfd(d), de->d_name, &target);
280 [ - + ]: 528 : if (r < 0) {
281 [ # # ]: 0 : log_warning_errno(r, "Failed to read symlink %s/%s, ignoring: %m",
282 : : *dir, de->d_name);
283 : 0 : continue;
284 : : }
285 : :
286 [ + + ]: 528 : if (!path_is_absolute(target)) {
287 : 480 : target_abs = path_join(*dir, target);
288 [ - + ]: 480 : if (!target_abs)
289 : 0 : return log_oom();
290 : :
291 : 480 : free_and_replace(target, target_abs);
292 : : }
293 : :
294 : : /* Get rid of "." and ".." components in target path */
295 : 528 : r = chase_symlinks(target, lp->root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified);
296 [ - + ]: 528 : if (r < 0) {
297 [ # # ]: 0 : log_warning_errno(r, "Failed to resolve symlink %s pointing to %s, ignoring: %m",
298 : : filename, target);
299 : 0 : continue;
300 : : }
301 : :
302 : : /* Check if the symlink goes outside of our search path.
303 : : * If yes, it's a linked unit file or mask, and we don't care about the target name.
304 : : * Let's just store the link destination directly.
305 : : * If not, let's verify that it's a good symlink. */
306 : 528 : char *tail = path_startswith_strv(simplified, lp->search_path);
307 [ + + ]: 528 : if (tail) {
308 : : bool self_alias;
309 : :
310 : 328 : dst = basename(simplified);
311 : 328 : self_alias = streq(dst, de->d_name);
312 : :
313 [ - + ]: 328 : if (is_path(tail))
314 [ # # # # ]: 0 : log_full(self_alias ? LOG_DEBUG : LOG_WARNING,
315 : : "Suspicious symlink %s→%s, treating as alias.",
316 : : filename, simplified);
317 : :
318 : 328 : r = unit_validate_alias_symlink_and_warn(filename, simplified);
319 [ - + ]: 328 : if (r < 0)
320 : 0 : continue;
321 : :
322 [ - + ]: 328 : if (self_alias) {
323 : : /* A self-alias that has no effect */
324 [ # # ]: 0 : log_debug("%s: self-alias: %s/%s → %s, ignoring.",
325 : : __func__, *dir, de->d_name, dst);
326 : 0 : continue;
327 : : }
328 : :
329 [ + + ]: 328 : log_debug("%s: alias: %s/%s → %s", __func__, *dir, de->d_name, dst);
330 : : } else {
331 : 200 : dst = simplified;
332 : :
333 [ + + ]: 200 : log_debug("%s: linked unit file: %s/%s → %s", __func__, *dir, de->d_name, dst);
334 : : }
335 : :
336 : : } else {
337 : 2772 : dst = filename;
338 [ + + ]: 2772 : log_debug("%s: normal unit file: %s", __func__, dst);
339 : : }
340 : :
341 : 3300 : r = hashmap_put_strdup(&ids, de->d_name, dst);
342 [ - + ]: 3300 : if (r < 0)
343 [ # # ]: 0 : return log_warning_errno(r, "Failed to add entry to hashmap (%s→%s): %m",
344 : : de->d_name, dst);
345 : : }
346 : : }
347 : :
348 : : /* Let's also put the names in the reverse db. */
349 : : Iterator it;
350 : : const char *dummy, *src;
351 [ + + ]: 3348 : HASHMAP_FOREACH_KEY(dummy, src, ids, it) {
352 : : const char *dst;
353 : :
354 : 3300 : r = unit_ids_map_get(ids, src, &dst);
355 [ - + ]: 3300 : if (r < 0)
356 : 4 : continue;
357 : :
358 [ + + ]: 3300 : if (null_or_empty_path(dst) != 0)
359 : 4 : continue;
360 : :
361 : : /* Do not treat instance symlinks that point to the template as aliases */
362 [ + + ]: 3296 : if (unit_name_is_valid(basename(dst), UNIT_NAME_TEMPLATE) &&
363 [ - + ]: 156 : unit_name_is_valid(src, UNIT_NAME_INSTANCE))
364 : 0 : continue;
365 : :
366 : 3296 : r = string_strv_hashmap_put(&names, basename(dst), src);
367 [ - + ]: 3296 : if (r < 0)
368 [ # # ]: 0 : return log_warning_errno(r, "Failed to add entry to hashmap (%s→%s): %m",
369 : : basename(dst), src);
370 : : }
371 : :
372 [ + - ]: 48 : if (cache_mtime)
373 : 48 : *cache_mtime = mtime;
374 : 48 : *ret_unit_ids_map = TAKE_PTR(ids);
375 : 48 : *ret_unit_names_map = TAKE_PTR(names);
376 [ + + ]: 48 : if (ret_path_cache)
377 : 44 : *ret_path_cache = TAKE_PTR(paths);
378 : :
379 : 48 : return 1;
380 : : }
381 : :
382 : 8664 : int unit_file_find_fragment(
383 : : Hashmap *unit_ids_map,
384 : : Hashmap *unit_name_map,
385 : : const char *unit_name,
386 : : const char **ret_fragment_path,
387 : : Set **ret_names) {
388 : :
389 : 8664 : const char *fragment = NULL;
390 : 8664 : _cleanup_free_ char *template = NULL, *instance = NULL;
391 : 8664 : _cleanup_set_free_free_ Set *names = NULL;
392 : : char **t, **nnn;
393 : : int r, name_type;
394 : :
395 : : /* Finds a fragment path, and returns the set of names:
396 : : * if we have …/foo.service and …/foo-alias.service→foo.service,
397 : : * and …/foo@.service and …/foo-alias@.service→foo@.service,
398 : : * and …/foo@inst.service,
399 : : * this should return:
400 : : * foo.service → …/foo.service, {foo.service, foo-alias.service},
401 : : * foo-alias.service → …/foo.service, {foo.service, foo-alias.service},
402 : : * foo@.service → …/foo@.service, {foo@.service, foo-alias@.service},
403 : : * foo-alias@.service → …/foo@.service, {foo@.service, foo-alias@.service},
404 : : * foo@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service},
405 : : * foo-alias@bar.service → …/foo@.service, {foo@bar.service, foo-alias@bar.service},
406 : : * foo-alias@inst.service → …/foo@inst.service, {foo@inst.service, foo-alias@inst.service}.
407 : : */
408 : :
409 : 8664 : name_type = unit_name_to_instance(unit_name, &instance);
410 [ - + ]: 8664 : if (name_type < 0)
411 : 0 : return name_type;
412 : :
413 : 8664 : names = set_new(&string_hash_ops);
414 [ - + ]: 8664 : if (!names)
415 : 0 : return -ENOMEM;
416 : :
417 : : /* The unit always has its own name if it's not a template. */
418 [ + - + - ]: 8664 : if (IN_SET(name_type, UNIT_NAME_PLAIN, UNIT_NAME_INSTANCE)) {
419 : 8664 : r = set_put_strdup(names, unit_name);
420 [ - + ]: 8664 : if (r < 0)
421 : 0 : return r;
422 : : }
423 : :
424 : : /* First try to load fragment under the original name */
425 : 8664 : r = unit_ids_map_get(unit_ids_map, unit_name, &fragment);
426 [ + + + - : 8664 : if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
- + ]
427 [ # # ]: 0 : return log_debug_errno(r, "Cannot load unit %s: %m", unit_name);
428 : :
429 [ + + ]: 8664 : if (fragment) {
430 : : /* Add any aliases of the original name to the set of names */
431 : 352 : nnn = hashmap_get(unit_name_map, basename(fragment));
432 [ + - + + ]: 848 : STRV_FOREACH(t, nnn) {
433 [ - + # # ]: 496 : if (name_type == UNIT_NAME_INSTANCE && unit_name_is_valid(*t, UNIT_NAME_TEMPLATE)) {
434 : : char *inst;
435 : :
436 : 0 : r = unit_name_replace_instance(*t, instance, &inst);
437 [ # # ]: 0 : if (r < 0)
438 [ # # ]: 0 : return log_debug_errno(r, "Cannot build instance name %s+%s: %m", *t, instance);
439 : :
440 [ # # ]: 0 : if (!streq(unit_name, inst))
441 [ # # ]: 0 : log_debug("%s: %s has alias %s", __func__, unit_name, inst);
442 : :
443 [ # # ]: 0 : log_info("%s: %s+%s → %s", __func__, *t, instance, inst);
444 : 0 : r = set_consume(names, inst);
445 : : } else {
446 [ + + ]: 496 : if (!streq(unit_name, *t))
447 [ - + ]: 144 : log_debug("%s: %s has alias %s", __func__, unit_name, *t);
448 : :
449 : 496 : r = set_put_strdup(names, *t);
450 : : }
451 [ - + ]: 496 : if (r < 0)
452 : 0 : return r;
453 : : }
454 : : }
455 : :
456 [ + + - + ]: 8664 : if (!fragment && name_type == UNIT_NAME_INSTANCE) {
457 : : /* Look for a fragment under the template name */
458 : :
459 : 0 : r = unit_name_template(unit_name, &template);
460 [ # # ]: 0 : if (r < 0)
461 [ # # ]: 0 : return log_error_errno(r, "Failed to determine template name: %m");
462 : :
463 : 0 : r = unit_ids_map_get(unit_ids_map, template, &fragment);
464 [ # # # # : 0 : if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
# # ]
465 [ # # ]: 0 : return log_debug_errno(r, "Cannot load template %s: %m", template);
466 : :
467 [ # # ]: 0 : if (fragment) {
468 : : /* Add any aliases of the original name to the set of names */
469 : 0 : nnn = hashmap_get(unit_name_map, basename(fragment));
470 [ # # # # ]: 0 : STRV_FOREACH(t, nnn) {
471 [ # # # ]: 0 : _cleanup_free_ char *inst = NULL;
472 : 0 : const char *inst_fragment = NULL;
473 : :
474 : 0 : r = unit_name_replace_instance(*t, instance, &inst);
475 [ # # ]: 0 : if (r < 0)
476 [ # # ]: 0 : return log_debug_errno(r, "Cannot build instance name %s+%s: %m", template, instance);
477 : :
478 : : /* Exclude any aliases that point in some other direction. */
479 : 0 : r = unit_ids_map_get(unit_ids_map, inst, &inst_fragment);
480 [ # # # # : 0 : if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
# # ]
481 [ # # ]: 0 : return log_debug_errno(r, "Cannot find instance fragment %s: %m", inst);
482 : :
483 [ # # ]: 0 : if (inst_fragment &&
484 [ # # ]: 0 : !streq(basename(inst_fragment), basename(fragment))) {
485 [ # # ]: 0 : log_debug("Instance %s has fragment %s and is not an alias of %s.",
486 : : inst, inst_fragment, unit_name);
487 : 0 : continue;
488 : : }
489 : :
490 [ # # ]: 0 : if (!streq(unit_name, inst))
491 [ # # ]: 0 : log_debug("%s: %s has alias %s", __func__, unit_name, inst);
492 : 0 : r = set_consume(names, TAKE_PTR(inst));
493 [ # # ]: 0 : if (r < 0)
494 : 0 : return r;
495 : : }
496 : : }
497 : : }
498 : :
499 : 8664 : *ret_fragment_path = fragment;
500 : 8664 : *ret_names = TAKE_PTR(names);
501 : :
502 : : // FIXME: if instance, consider any unit names with different template name
503 : 8664 : return 0;
504 : : }
|