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 2326 : bool unit_type_may_alias(UnitType type) {
15 2326 : 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 2751 : bool unit_type_may_template(UnitType type) {
25 2751 : return IN_SET(type,
26 : UNIT_SERVICE,
27 : UNIT_SOCKET,
28 : UNIT_TARGET,
29 : UNIT_TIMER,
30 : UNIT_PATH);
31 : }
32 :
33 96 : int unit_validate_alias_symlink_and_warn(const char *filename, const char *target) {
34 : const char *src, *dst;
35 96 : _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 96 : src = basename(filename);
50 96 : dst = basename(target);
51 :
52 : /* src checks */
53 :
54 96 : src_name_type = unit_name_to_instance(src, &src_instance);
55 96 : 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 96 : src_unit_type = unit_name_to_type(src);
60 96 : assert(src_unit_type >= 0); /* unit_name_to_instance() checked the suffix already */
61 :
62 96 : if (!unit_type_may_alias(src_unit_type))
63 2 : return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
64 : "%s: symlinks are not allowed for units of this type, rejecting.",
65 : filename);
66 :
67 94 : if (src_name_type != UNIT_NAME_PLAIN &&
68 9 : !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 94 : dst_name_type = unit_name_to_instance(dst, &dst_instance);
76 94 : if (dst_name_type < 0)
77 1 : 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 93 : if (!(dst_name_type == src_name_type ||
82 1 : (src_name_type == UNIT_NAME_INSTANCE && dst_name_type == UNIT_NAME_TEMPLATE)))
83 3 : return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
84 : "%s: symlink target name type \"%s\" does not match source, rejecting.",
85 : filename, dst);
86 :
87 90 : if (dst_name_type == UNIT_NAME_INSTANCE) {
88 3 : assert(src_instance);
89 3 : assert(dst_instance);
90 3 : if (!streq(src_instance, dst_instance))
91 2 : 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 88 : dst_unit_type = unit_name_to_type(dst);
97 88 : if (dst_unit_type != src_unit_type)
98 2 : return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
99 : "%s: symlink target \"%s\" has incompatible suffix, rejecting.",
100 : filename, dst);
101 :
102 86 : return 0;
103 : }
104 :
105 : #define FOLLOW_MAX 8
106 :
107 2969 : 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 2969 : const char *id = NULL;
120 : int r;
121 :
122 3057 : for (unsigned n = 0; n < FOLLOW_MAX; n++) {
123 3057 : const char *t = hashmap_get(unit_ids_map, id ?: unit_name);
124 3057 : if (!t) {
125 2056 : _cleanup_free_ char *template = NULL;
126 :
127 2056 : if (!id)
128 2056 : 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 1001 : if (path_is_absolute(t)) {
144 913 : if (ret_fragment_path)
145 913 : *ret_fragment_path = t;
146 913 : return 0;
147 : }
148 :
149 88 : id = t;
150 : }
151 :
152 0 : return -ELOOP;
153 : }
154 :
155 2162 : 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 4322 : return streq_ptr(path, lp->generator) ||
159 4319 : streq_ptr(path, lp->generator_early) ||
160 4316 : streq_ptr(path, lp->generator_late) ||
161 4312 : streq_ptr(path, lp->transient) ||
162 6477 : streq_ptr(path, lp->persistent_control) ||
163 2154 : streq_ptr(path, lp->runtime_control);
164 : }
165 :
166 2134 : static bool lookup_paths_mtime_good(const LookupPaths *lp, usec_t mtime) {
167 : char **dir;
168 :
169 4279 : STRV_FOREACH(dir, (char**) lp->search_path) {
170 : struct stat st;
171 :
172 2145 : if (lookup_paths_mtime_exclude(lp, *dir))
173 9 : continue;
174 :
175 : /* Determine the latest lookup path modification time */
176 2139 : if (stat(*dir, &st) < 0) {
177 3 : if (errno == ENOENT)
178 3 : continue;
179 :
180 0 : log_debug_errno(errno, "Failed to stat %s, ignoring: %m", *dir);
181 0 : continue;
182 : }
183 :
184 2136 : 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 2134 : return true;
191 : }
192 :
193 2146 : 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 2146 : _cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL;
210 2146 : _cleanup_set_free_free_ Set *paths = NULL;
211 : char **dir;
212 : int r;
213 2146 : 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 2146 : if (cache_mtime && *cache_mtime > 0 && lookup_paths_mtime_good(lp, *cache_mtime))
218 2134 : return 0;
219 :
220 12 : if (ret_path_cache) {
221 11 : paths = set_new(&path_hash_ops);
222 11 : if (!paths)
223 0 : return log_oom();
224 : }
225 :
226 35 : STRV_FOREACH(dir, (char**) lp->search_path) {
227 : struct dirent *de;
228 23 : _cleanup_closedir_ DIR *d = NULL;
229 : struct stat st;
230 :
231 23 : d = opendir(*dir);
232 23 : if (!d) {
233 6 : if (errno != ENOENT)
234 0 : log_warning_errno(errno, "Failed to open \"%s\", ignoring: %m", *dir);
235 6 : continue;
236 : }
237 :
238 : /* Determine the latest lookup path modification time */
239 17 : if (fstat(dirfd(d), &st) < 0)
240 0 : return log_error_errno(errno, "Failed to fstat %s: %m", *dir);
241 :
242 17 : if (!lookup_paths_mtime_exclude(lp, *dir))
243 14 : mtime = MAX(mtime, timespec_load(&st.st_mtim));
244 :
245 1220 : FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) {
246 : char *filename;
247 1505 : _cleanup_free_ char *_filename_free = NULL, *simplified = NULL;
248 1165 : const char *suffix, *dst = NULL;
249 :
250 1165 : filename = path_join(*dir, de->d_name);
251 1165 : if (!filename)
252 0 : return log_oom();
253 :
254 1165 : if (ret_path_cache) {
255 590 : r = set_consume(paths, filename);
256 590 : 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 575 : _filename_free = filename; /* Make sure we free the filename. */
262 :
263 1165 : if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
264 335 : continue;
265 830 : 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 830 : if (hashmap_contains(ids, de->d_name))
271 5 : continue;
272 :
273 825 : 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 132 : _cleanup_free_ char *target = NULL, *target_abs = NULL;
278 :
279 132 : r = readlinkat_malloc(dirfd(d), de->d_name, &target);
280 132 : 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 132 : if (!path_is_absolute(target)) {
287 120 : target_abs = path_join(*dir, target);
288 120 : if (!target_abs)
289 0 : return log_oom();
290 :
291 120 : free_and_replace(target, target_abs);
292 : }
293 :
294 : /* Get rid of "." and ".." components in target path */
295 132 : r = chase_symlinks(target, lp->root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified);
296 132 : 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 132 : char *tail = path_startswith_strv(simplified, lp->search_path);
307 132 : if (tail) {
308 : bool self_alias;
309 :
310 82 : dst = basename(simplified);
311 82 : self_alias = streq(dst, de->d_name);
312 :
313 82 : 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 82 : r = unit_validate_alias_symlink_and_warn(filename, simplified);
319 82 : if (r < 0)
320 0 : continue;
321 :
322 82 : 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 82 : log_debug("%s: alias: %s/%s → %s", __func__, *dir, de->d_name, dst);
330 : } else {
331 50 : dst = simplified;
332 :
333 50 : log_debug("%s: linked unit file: %s/%s → %s", __func__, *dir, de->d_name, dst);
334 : }
335 :
336 : } else {
337 693 : dst = filename;
338 693 : log_debug("%s: normal unit file: %s", __func__, dst);
339 : }
340 :
341 825 : r = hashmap_put_strdup(&ids, de->d_name, dst);
342 825 : 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 837 : HASHMAP_FOREACH_KEY(dummy, src, ids, it) {
352 : const char *dst;
353 :
354 825 : r = unit_ids_map_get(ids, src, &dst);
355 825 : if (r < 0)
356 1 : continue;
357 :
358 825 : if (null_or_empty_path(dst) != 0)
359 1 : continue;
360 :
361 : /* Do not treat instance symlinks that point to the template as aliases */
362 824 : if (unit_name_is_valid(basename(dst), UNIT_NAME_TEMPLATE) &&
363 39 : unit_name_is_valid(src, UNIT_NAME_INSTANCE))
364 0 : continue;
365 :
366 824 : r = string_strv_hashmap_put(&names, basename(dst), src);
367 824 : 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 12 : if (cache_mtime)
373 12 : *cache_mtime = mtime;
374 12 : *ret_unit_ids_map = TAKE_PTR(ids);
375 12 : *ret_unit_names_map = TAKE_PTR(names);
376 12 : if (ret_path_cache)
377 11 : *ret_path_cache = TAKE_PTR(paths);
378 :
379 12 : return 1;
380 : }
381 :
382 2144 : 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 2144 : const char *fragment = NULL;
390 2144 : _cleanup_free_ char *template = NULL, *instance = NULL;
391 2144 : _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 2144 : name_type = unit_name_to_instance(unit_name, &instance);
410 2144 : if (name_type < 0)
411 0 : return name_type;
412 :
413 2144 : names = set_new(&string_hash_ops);
414 2144 : if (!names)
415 0 : return -ENOMEM;
416 :
417 : /* The unit always has its own name if it's not a template. */
418 2144 : if (IN_SET(name_type, UNIT_NAME_PLAIN, UNIT_NAME_INSTANCE)) {
419 2144 : r = set_put_strdup(names, unit_name);
420 2144 : if (r < 0)
421 0 : return r;
422 : }
423 :
424 : /* First try to load fragment under the original name */
425 2144 : r = unit_ids_map_get(unit_ids_map, unit_name, &fragment);
426 2144 : if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
427 0 : return log_debug_errno(r, "Cannot load unit %s: %m", unit_name);
428 :
429 2144 : if (fragment) {
430 : /* Add any aliases of the original name to the set of names */
431 88 : nnn = hashmap_get(unit_name_map, basename(fragment));
432 212 : STRV_FOREACH(t, nnn) {
433 124 : 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 124 : if (!streq(unit_name, *t))
447 36 : log_debug("%s: %s has alias %s", __func__, unit_name, *t);
448 :
449 124 : r = set_put_strdup(names, *t);
450 : }
451 124 : if (r < 0)
452 0 : return r;
453 : }
454 : }
455 :
456 2144 : 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 2144 : *ret_fragment_path = fragment;
500 2144 : *ret_names = TAKE_PTR(names);
501 :
502 : // FIXME: if instance, consider any unit names with different template name
503 2144 : return 0;
504 : }
|