]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/unit-file.c
shared/unit-file: fix systemctl cat user@.service
[thirdparty/systemd.git] / src / shared / unit-file.c
CommitLineData
5cfa33e0
ZJS
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
e8630e69
ZJS
3#include "dirent-util.h"
4#include "fd-util.h"
5#include "fs-util.h"
5cfa33e0 6#include "macro.h"
e8630e69
ZJS
7#include "path-lookup.h"
8#include "set.h"
9#include "stat-util.h"
7d1e91d1 10#include "string-util.h"
e8630e69 11#include "strv.h"
5cfa33e0
ZJS
12#include "unit-file.h"
13
14bool unit_type_may_alias(UnitType type) {
15 return IN_SET(type,
16 UNIT_SERVICE,
17 UNIT_SOCKET,
18 UNIT_TARGET,
19 UNIT_DEVICE,
20 UNIT_TIMER,
21 UNIT_PATH);
22}
23
24bool unit_type_may_template(UnitType type) {
25 return IN_SET(type,
26 UNIT_SERVICE,
27 UNIT_SOCKET,
28 UNIT_TARGET,
29 UNIT_TIMER,
30 UNIT_PATH);
31}
7d1e91d1
ZJS
32
33int unit_validate_alias_symlink_and_warn(const char *filename, const char *target) {
34 const char *src, *dst;
35 _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 src = basename(filename);
50 dst = basename(target);
51
52 /* src checks */
53
54 src_name_type = unit_name_to_instance(src, &src_instance);
55 if (src_name_type < 0)
56 return log_notice_errno(src_name_type,
57 "%s: not a valid unit name \"%s\": %m", filename, src);
58
59 src_unit_type = unit_name_to_type(src);
60 assert(src_unit_type >= 0); /* unit_name_to_instance() checked the suffix already */
61
62 if (!unit_type_may_alias(src_unit_type))
63 return log_notice_errno(SYNTHETIC_ERRNO(EINVAL),
64 "%s: symlinks are not allowed for units of this type, rejecting.",
65 filename);
66
67 if (src_name_type != UNIT_NAME_PLAIN &&
68 !unit_type_may_template(src_unit_type))
69 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 dst_name_type = unit_name_to_instance(dst, &dst_instance);
76 if (dst_name_type < 0)
77 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 if (!(dst_name_type == src_name_type ||
82 (src_name_type == UNIT_NAME_INSTANCE && dst_name_type == UNIT_NAME_TEMPLATE)))
83 return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
84 "%s: symlink target name type \"%s\" does not match source, rejecting.",
85 filename, dst);
86
87 if (dst_name_type == UNIT_NAME_INSTANCE) {
88 assert(src_instance);
89 assert(dst_instance);
90 if (!streq(src_instance, dst_instance))
91 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 dst_unit_type = unit_name_to_type(dst);
97 if (dst_unit_type != src_unit_type)
98 return log_notice_errno(SYNTHETIC_ERRNO(EXDEV),
99 "%s: symlink target \"%s\" has incompatible suffix, rejecting.",
100 filename, dst);
101
102 return 0;
103}
e8630e69
ZJS
104
105#define FOLLOW_MAX 8
106
107static 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 const char *id = NULL;
120 int r;
121
122 for (unsigned n = 0; n < FOLLOW_MAX; n++) {
123 const char *t = hashmap_get(unit_ids_map, id ?: unit_name);
124 if (!t) {
125 _cleanup_free_ char *template = NULL;
126
127 if (!id)
128 return -ENOENT;
129
130 r = unit_name_template(id, &template);
131 if (r == -EINVAL)
132 return -ENXIO; /* we failed to find the symlink target */
133 if (r < 0)
134 return log_error_errno(r, "Failed to determine template name for %s: %m", id);
135
136 t = hashmap_get(unit_ids_map, template);
137 if (!t)
138 return -ENXIO;
139
140 /* We successfully switched from instanced name to a template, let's continue */
141 }
142
143 if (path_is_absolute(t)) {
144 if (ret_fragment_path)
145 *ret_fragment_path = t;
146 return 0;
147 }
148
149 id = t;
150 }
151
152 return -ELOOP;
153}
154
91e0ee5f
ZJS
155static 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 return streq_ptr(path, lp->generator) ||
159 streq_ptr(path, lp->generator_early) ||
160 streq_ptr(path, lp->generator_late) ||
161 streq_ptr(path, lp->transient) ||
162 streq_ptr(path, lp->persistent_control) ||
163 streq_ptr(path, lp->runtime_control);
164}
165
166static bool lookup_paths_mtime_good(const LookupPaths *lp, usec_t mtime) {
167 char **dir;
168
169 STRV_FOREACH(dir, (char**) lp->search_path) {
170 struct stat st;
171
172 if (lookup_paths_mtime_exclude(lp, *dir))
173 continue;
174
175 /* Determine the latest lookup path modification time */
176 if (stat(*dir, &st) < 0) {
177 if (errno == ENOENT)
178 continue;
179
180 log_debug_errno(errno, "Failed to stat %s, ignoring: %m", *dir);
181 continue;
182 }
183
184 if (timespec_load(&st.st_mtim) > mtime) {
185 log_debug_errno(errno, "Unit dir %s has changed, need to update cache.", *dir);
186 return false;
187 }
188 }
189
190 return true;
191}
192
e8630e69
ZJS
193int unit_file_build_name_map(
194 const LookupPaths *lp,
91e0ee5f 195 usec_t *cache_mtime,
e8630e69
ZJS
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 _cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL;
210 _cleanup_set_free_free_ Set *paths = NULL;
211 char **dir;
212 int r;
91e0ee5f
ZJS
213 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 if (cache_mtime && *cache_mtime > 0 && lookup_paths_mtime_good(lp, *cache_mtime))
218 return 0;
e8630e69
ZJS
219
220 if (ret_path_cache) {
221 paths = set_new(&path_hash_ops);
222 if (!paths)
223 return log_oom();
224 }
225
226 STRV_FOREACH(dir, (char**) lp->search_path) {
227 struct dirent *de;
228 _cleanup_closedir_ DIR *d = NULL;
91e0ee5f 229 struct stat st;
e8630e69
ZJS
230
231 d = opendir(*dir);
232 if (!d) {
233 if (errno != ENOENT)
234 log_warning_errno(errno, "Failed to open \"%s\", ignoring: %m", *dir);
235 continue;
236 }
237
91e0ee5f
ZJS
238 /* Determine the latest lookup path modification time */
239 if (fstat(dirfd(d), &st) < 0)
240 return log_error_errno(errno, "Failed to fstat %s: %m", *dir);
241
242 if (!lookup_paths_mtime_exclude(lp, *dir))
243 mtime = MAX(mtime, timespec_load(&st.st_mtim));
244
e8630e69
ZJS
245 FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) {
246 char *filename;
247 _cleanup_free_ char *_filename_free = NULL, *simplified = NULL;
248 const char *suffix, *dst = NULL;
249
250 filename = path_join(*dir, de->d_name);
251 if (!filename)
252 return log_oom();
253
254 if (ret_path_cache) {
255 r = set_consume(paths, filename);
256 if (r < 0)
257 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 _filename_free = filename; /* Make sure we free the filename. */
262
263 if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
264 continue;
265 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 if (hashmap_contains(ids, de->d_name))
271 continue;
272
273 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 _cleanup_free_ char *target = NULL, *target_abs = NULL;
278
279 r = readlinkat_malloc(dirfd(d), de->d_name, &target);
280 if (r < 0) {
281 log_warning_errno(r, "Failed to read symlink %s/%s, ignoring: %m",
282 *dir, de->d_name);
283 continue;
284 }
285
286 if (!path_is_absolute(target)) {
287 target_abs = path_join(*dir, target);
288 if (!target_abs)
289 return log_oom();
290
291 free_and_replace(target, target_abs);
292 }
293
294 /* Get rid of "." and ".." components in target path */
295 r = chase_symlinks(target, lp->root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified);
296 if (r < 0) {
297 log_warning_errno(r, "Failed to resolve symlink %s pointing to %s, ignoring: %m",
298 filename, target);
299 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 char *tail = path_startswith_strv(simplified, lp->search_path);
307 if (tail) {
308 bool self_alias;
309
310 dst = basename(simplified);
311 self_alias = streq(dst, de->d_name);
312
313 if (is_path(tail))
314 log_full(self_alias ? LOG_DEBUG : LOG_WARNING,
315 "Suspicious symlink %s→%s, treating as alias.",
316 filename, simplified);
317
318 r = unit_validate_alias_symlink_and_warn(filename, simplified);
319 if (r < 0)
320 continue;
321
322 if (self_alias) {
323 /* A self-alias that has no effect */
324 log_debug("%s: self-alias: %s/%s → %s, ignoring.",
325 __func__, *dir, de->d_name, dst);
326 continue;
327 }
328
329 log_debug("%s: alias: %s/%s → %s", __func__, *dir, de->d_name, dst);
330 } else {
331 dst = simplified;
332
333 log_debug("%s: linked unit file: %s/%s → %s", __func__, *dir, de->d_name, dst);
334 }
335
336 } else {
337 dst = filename;
338 log_debug("%s: normal unit file: %s", __func__, dst);
339 }
340
341 r = hashmap_put_strdup(&ids, de->d_name, dst);
342 if (r < 0)
343 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 HASHMAP_FOREACH_KEY(dummy, src, ids, it) {
352 const char *dst;
353
354 r = unit_ids_map_get(ids, src, &dst);
355 if (r < 0)
356 continue;
357
358 if (null_or_empty_path(dst) != 0)
359 continue;
360
361 /* Do not treat instance symlinks that point to the template as aliases */
362 if (unit_name_is_valid(basename(dst), UNIT_NAME_TEMPLATE) &&
363 unit_name_is_valid(src, UNIT_NAME_INSTANCE))
364 continue;
365
366 r = string_strv_hashmap_put(&names, basename(dst), src);
367 if (r < 0)
368 return log_warning_errno(r, "Failed to add entry to hashmap (%s→%s): %m",
369 basename(dst), src);
370 }
371
91e0ee5f
ZJS
372 if (cache_mtime)
373 *cache_mtime = mtime;
e8630e69
ZJS
374 *ret_unit_ids_map = TAKE_PTR(ids);
375 *ret_unit_names_map = TAKE_PTR(names);
376 if (ret_path_cache)
377 *ret_path_cache = TAKE_PTR(paths);
378
91e0ee5f 379 return 1;
e8630e69
ZJS
380}
381
382int 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 const char *fragment = NULL;
390 _cleanup_free_ char *template = NULL, *instance = NULL;
391 _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 name_type = unit_name_to_instance(unit_name, &instance);
410 if (name_type < 0)
411 return name_type;
412
413 names = set_new(&string_hash_ops);
414 if (!names)
415 return -ENOMEM;
416
417 /* The unit always has its own name if it's not a template. */
b208cbe5 418 if (IN_SET(name_type, UNIT_NAME_PLAIN, UNIT_NAME_INSTANCE)) {
e8630e69
ZJS
419 r = set_put_strdup(names, unit_name);
420 if (r < 0)
421 return r;
422 }
423
424 /* First try to load fragment under the original name */
425 r = unit_ids_map_get(unit_ids_map, unit_name, &fragment);
426 if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
427 return log_debug_errno(r, "Cannot load unit %s: %m", unit_name);
428
429 if (fragment) {
430 /* Add any aliases of the original name to the set of names */
431 nnn = hashmap_get(unit_name_map, basename(fragment));
432 STRV_FOREACH(t, nnn) {
b208cbe5 433 if (name_type == UNIT_NAME_INSTANCE && unit_name_is_valid(*t, UNIT_NAME_TEMPLATE)) {
e8630e69
ZJS
434 char *inst;
435
436 r = unit_name_replace_instance(*t, instance, &inst);
437 if (r < 0)
b208cbe5 438 return log_debug_errno(r, "Cannot build instance name %s+%s: %m", *t, instance);
e8630e69
ZJS
439
440 if (!streq(unit_name, inst))
441 log_debug("%s: %s has alias %s", __func__, unit_name, inst);
442
443 log_info("%s: %s+%s → %s", __func__, *t, instance, inst);
444 r = set_consume(names, inst);
445 } else {
446 if (!streq(unit_name, *t))
447 log_debug("%s: %s has alias %s", __func__, unit_name, *t);
448
449 r = set_put_strdup(names, *t);
450 }
451 if (r < 0)
452 return r;
453 }
454 }
455
456 if (!fragment && name_type == UNIT_NAME_INSTANCE) {
457 /* Look for a fragment under the template name */
458
459 r = unit_name_template(unit_name, &template);
460 if (r < 0)
461 return log_error_errno(r, "Failed to determine template name: %m");
462
463 r = unit_ids_map_get(unit_ids_map, template, &fragment);
464 if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
771f8aef 465 return log_debug_errno(r, "Cannot load template %s: %m", template);
e8630e69
ZJS
466
467 if (fragment) {
468 /* Add any aliases of the original name to the set of names */
469 nnn = hashmap_get(unit_name_map, basename(fragment));
470 STRV_FOREACH(t, nnn) {
471 _cleanup_free_ char *inst = NULL;
472 const char *inst_fragment = NULL;
473
474 r = unit_name_replace_instance(*t, instance, &inst);
475 if (r < 0)
476 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 r = unit_ids_map_get(unit_ids_map, inst, &inst_fragment);
480 if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
481 return log_debug_errno(r, "Cannot find instance fragment %s: %m", inst);
482
483 if (inst_fragment &&
484 !streq(basename(inst_fragment), basename(fragment))) {
485 log_debug("Instance %s has fragment %s and is not an alias of %s.",
486 inst, inst_fragment, unit_name);
487 continue;
488 }
489
490 if (!streq(unit_name, inst))
491 log_info("%s: %s has alias %s", __func__, unit_name, inst);
492 r = set_consume(names, TAKE_PTR(inst));
493 if (r < 0)
494 return r;
495 }
496 }
497 }
498
499 *ret_fragment_path = fragment;
500 *ret_names = TAKE_PTR(names);
501
502 // FIXME: if instance, consider any unit names with different template name
503 return 0;
504}