]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/portable/portable.c
docs/RANDOM_SEEDS: update NetBSD link
[thirdparty/systemd.git] / src / portable / portable.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <linux/loop.h>
4
5 #include "sd-messages.h"
6
7 #include "bus-common-errors.h"
8 #include "bus-error.h"
9 #include "bus-locator.h"
10 #include "chase.h"
11 #include "conf-files.h"
12 #include "copy.h"
13 #include "data-fd-util.h"
14 #include "constants.h"
15 #include "dirent-util.h"
16 #include "discover-image.h"
17 #include "dissect-image.h"
18 #include "env-file.h"
19 #include "env-util.h"
20 #include "errno-list.h"
21 #include "escape.h"
22 #include "extension-util.h"
23 #include "fd-util.h"
24 #include "fileio.h"
25 #include "fs-util.h"
26 #include "install.h"
27 #include "iovec-util.h"
28 #include "locale-util.h"
29 #include "loop-util.h"
30 #include "mkdir.h"
31 #include "nulstr-util.h"
32 #include "os-util.h"
33 #include "path-lookup.h"
34 #include "portable.h"
35 #include "process-util.h"
36 #include "rm-rf.h"
37 #include "selinux-util.h"
38 #include "set.h"
39 #include "signal-util.h"
40 #include "socket-util.h"
41 #include "sort-util.h"
42 #include "string-table.h"
43 #include "strv.h"
44 #include "tmpfile-util.h"
45 #include "user-util.h"
46
47 /* Markers used in the first line of our 20-portable.conf unit file drop-in to determine, that a) the unit file was
48 * dropped there by the portable service logic and b) for which image it was dropped there. */
49 #define PORTABLE_DROPIN_MARKER_BEGIN "# Drop-in created for image '"
50 #define PORTABLE_DROPIN_MARKER_END "', do not edit."
51
52 static bool prefix_match(const char *unit, const char *prefix) {
53 const char *p;
54
55 p = startswith(unit, prefix);
56 if (!p)
57 return false;
58
59 /* Only respect prefixes followed by dash or dot or when there's a complete match */
60 return IN_SET(*p, '-', '.', '@', 0);
61 }
62
63 static bool unit_match(const char *unit, char **matches) {
64 const char *dot;
65
66 dot = strrchr(unit, '.');
67 if (!dot)
68 return false;
69
70 if (!STR_IN_SET(dot, ".service", ".socket", ".target", ".timer", ".path"))
71 return false;
72
73 /* Empty match expression means: everything */
74 if (strv_isempty(matches))
75 return true;
76
77 /* Otherwise, at least one needs to match */
78 STRV_FOREACH(i, matches)
79 if (prefix_match(unit, *i))
80 return true;
81
82 return false;
83 }
84
85 static PortableMetadata *portable_metadata_new(const char *name, const char *path, const char *selinux_label, int fd) {
86 PortableMetadata *m;
87
88 m = malloc0(offsetof(PortableMetadata, name) + strlen(name) + 1);
89 if (!m)
90 return NULL;
91
92 /* In case of a layered attach, we want to remember which image the unit came from */
93 if (path) {
94 m->image_path = strdup(path);
95 if (!m->image_path)
96 return mfree(m);
97 }
98
99 /* The metadata file might have SELinux labels, we need to carry them and reapply them */
100 if (!isempty(selinux_label)) {
101 m->selinux_label = strdup(selinux_label);
102 if (!m->selinux_label) {
103 free(m->image_path);
104 return mfree(m);
105 }
106 }
107
108 strcpy(m->name, name);
109 m->fd = fd;
110
111 return TAKE_PTR(m);
112 }
113
114 PortableMetadata *portable_metadata_unref(PortableMetadata *i) {
115 if (!i)
116 return NULL;
117
118 safe_close(i->fd);
119 free(i->source);
120 free(i->image_path);
121 free(i->selinux_label);
122
123 return mfree(i);
124 }
125
126 static int compare_metadata(PortableMetadata *const *x, PortableMetadata *const *y) {
127 return strcmp((*x)->name, (*y)->name);
128 }
129
130 int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret) {
131
132 _cleanup_free_ PortableMetadata **sorted = NULL;
133 PortableMetadata *item;
134 size_t k = 0;
135
136 sorted = new(PortableMetadata*, hashmap_size(unit_files));
137 if (!sorted)
138 return -ENOMEM;
139
140 HASHMAP_FOREACH(item, unit_files)
141 sorted[k++] = item;
142
143 assert(k == hashmap_size(unit_files));
144
145 typesafe_qsort(sorted, k, compare_metadata);
146
147 *ret = TAKE_PTR(sorted);
148 return 0;
149 }
150
151 static int send_one_fd_iov_with_data_fd(
152 int socket_fd,
153 const struct iovec *iov,
154 size_t iovlen,
155 int fd) {
156
157 _cleanup_close_ int data_fd = -EBADF;
158
159 assert(iov || iovlen == 0);
160 assert(socket_fd >= 0);
161 assert(fd >= 0);
162
163 data_fd = copy_data_fd(fd);
164 if (data_fd < 0)
165 return data_fd;
166
167 return send_one_fd_iov(socket_fd, data_fd, iov, iovlen, 0);
168 }
169
170 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(portable_metadata_hash_ops, char, string_hash_func, string_compare_func,
171 PortableMetadata, portable_metadata_unref);
172
173 static int extract_now(
174 const char *where,
175 char **matches,
176 const char *image_name,
177 bool path_is_extension,
178 bool relax_extension_release_check,
179 int socket_fd,
180 PortableMetadata **ret_os_release,
181 Hashmap **ret_unit_files) {
182
183 _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
184 _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
185 _cleanup_(lookup_paths_done) LookupPaths paths = {};
186 _cleanup_close_ int os_release_fd = -EBADF;
187 _cleanup_free_ char *os_release_path = NULL;
188 const char *os_release_id;
189 int r;
190
191 /* Extracts the metadata from a directory tree 'where'. Extracts two kinds of information: the /etc/os-release
192 * data, and all unit files matching the specified expression. Note that this function is called in two very
193 * different but also similar contexts. When the tool gets invoked on a directory tree, we'll process it
194 * directly, and in-process, and thus can return the requested data directly, via 'ret_os_release' and
195 * 'ret_unit_files'. However, if the tool is invoked on a raw disk image — which needs to be mounted first — we
196 * are invoked in a child process with private mounts and then need to send the collected data to our
197 * parent. To handle both cases in one call this function also gets a 'socket_fd' parameter, which when >= 0 is
198 * used to send the data to the parent. */
199
200 assert(where);
201
202 /* First, find os-release/extension-release and send it upstream (or just save it). */
203 if (path_is_extension) {
204 ImageClass class = IMAGE_SYSEXT;
205
206 r = open_extension_release(where, IMAGE_SYSEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
207 if (r == -ENOENT) {
208 r = open_extension_release(where, IMAGE_CONFEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd);
209 if (r >= 0)
210 class = IMAGE_CONFEXT;
211 }
212 if (r < 0)
213 return log_error_errno(r, "Failed to open extension release from '%s': %m", image_name);
214
215 os_release_id = strjoina((class == IMAGE_SYSEXT) ? "/usr/lib" : "/etc", "/extension-release.d/extension-release.", image_name);
216 } else {
217 os_release_id = "/etc/os-release";
218 r = open_os_release(where, &os_release_path, &os_release_fd);
219 }
220 if (r < 0)
221 log_debug_errno(r,
222 "Couldn't acquire %s file, ignoring: %m",
223 path_is_extension ? "extension-release " : "os-release");
224 else {
225 if (socket_fd >= 0) {
226 struct iovec iov[] = {
227 IOVEC_MAKE_STRING(os_release_id),
228 IOVEC_MAKE((char *)"\0", sizeof(char)),
229 };
230
231 r = send_one_fd_iov_with_data_fd(socket_fd, iov, ELEMENTSOF(iov), os_release_fd);
232 if (r < 0)
233 return log_debug_errno(r, "Failed to send os-release file: %m");
234 }
235
236 if (ret_os_release) {
237 os_release = portable_metadata_new(os_release_id, NULL, NULL, os_release_fd);
238 if (!os_release)
239 return -ENOMEM;
240
241 os_release_fd = -EBADF;
242 os_release->source = TAKE_PTR(os_release_path);
243 }
244 }
245
246 /* Then, send unit file data to the parent (or/and add it to the hashmap). For that we use our usual unit
247 * discovery logic. Note that we force looking inside of /lib/systemd/system/ for units too, as the
248 * image might have a legacy split-usr layout. */
249 r = lookup_paths_init(&paths, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, where);
250 if (r < 0)
251 return log_debug_errno(r, "Failed to acquire lookup paths: %m");
252
253 unit_files = hashmap_new(&portable_metadata_hash_ops);
254 if (!unit_files)
255 return -ENOMEM;
256
257 STRV_FOREACH(i, paths.search_path) {
258 _cleanup_free_ char *resolved = NULL;
259 _cleanup_closedir_ DIR *d = NULL;
260
261 r = chase_and_opendir(*i, where, 0, &resolved, &d);
262 if (r < 0) {
263 log_debug_errno(r, "Failed to open unit path '%s', ignoring: %m", *i);
264 continue;
265 }
266
267 FOREACH_DIRENT(de, d, return log_debug_errno(errno, "Failed to read directory: %m")) {
268 _cleanup_(portable_metadata_unrefp) PortableMetadata *m = NULL;
269 _cleanup_(mac_selinux_freep) char *con = NULL;
270 _cleanup_close_ int fd = -EBADF;
271 struct stat st;
272
273 if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
274 continue;
275
276 if (!unit_match(de->d_name, matches))
277 continue;
278
279 /* Filter out duplicates */
280 if (hashmap_get(unit_files, de->d_name))
281 continue;
282
283 if (!IN_SET(de->d_type, DT_LNK, DT_REG))
284 continue;
285
286 fd = openat(dirfd(d), de->d_name, O_CLOEXEC|O_RDONLY);
287 if (fd < 0) {
288 log_debug_errno(errno, "Failed to open unit file '%s', ignoring: %m", de->d_name);
289 continue;
290 }
291
292 /* Reject empty files, just in case */
293 if (fstat(fd, &st) < 0) {
294 log_debug_errno(errno, "Failed to stat unit file '%s', ignoring: %m", de->d_name);
295 continue;
296 }
297
298 if (st.st_size <= 0) {
299 log_debug("Unit file '%s' is empty, ignoring.", de->d_name);
300 continue;
301 }
302
303 #if HAVE_SELINUX
304 /* The units will be copied on the host's filesystem, so if they had a SELinux label
305 * we have to preserve it. Copy it out so that it can be applied later. */
306
307 r = fgetfilecon_raw(fd, &con);
308 if (r < 0 && !ERRNO_IS_XATTR_ABSENT(errno))
309 log_debug_errno(errno, "Failed to get SELinux file context from '%s', ignoring: %m", de->d_name);
310 #endif
311
312 if (socket_fd >= 0) {
313 struct iovec iov[] = {
314 IOVEC_MAKE_STRING(de->d_name),
315 IOVEC_MAKE((char *)"\0", sizeof(char)),
316 IOVEC_MAKE_STRING(strempty(con)),
317 };
318
319 r = send_one_fd_iov_with_data_fd(socket_fd, iov, ELEMENTSOF(iov), fd);
320 if (r < 0)
321 return log_debug_errno(r, "Failed to send unit metadata to parent: %m");
322 }
323
324 m = portable_metadata_new(de->d_name, where, con, fd);
325 if (!m)
326 return -ENOMEM;
327 fd = -EBADF;
328
329 m->source = path_join(resolved, de->d_name);
330 if (!m->source)
331 return -ENOMEM;
332
333 r = hashmap_put(unit_files, m->name, m);
334 if (r < 0)
335 return log_debug_errno(r, "Failed to add unit to hashmap: %m");
336 m = NULL;
337 }
338 }
339
340 if (ret_os_release)
341 *ret_os_release = TAKE_PTR(os_release);
342 if (ret_unit_files)
343 *ret_unit_files = TAKE_PTR(unit_files);
344
345 return 0;
346 }
347
348 static int portable_extract_by_path(
349 const char *path,
350 bool path_is_extension,
351 bool relax_extension_release_check,
352 char **matches,
353 const ImagePolicy *image_policy,
354 PortableMetadata **ret_os_release,
355 Hashmap **ret_unit_files,
356 sd_bus_error *error) {
357
358 _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
359 _cleanup_(portable_metadata_unrefp) PortableMetadata* os_release = NULL;
360 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
361 int r;
362
363 assert(path);
364
365 r = loop_device_make_by_path(
366 path,
367 O_RDONLY,
368 /* sector_size= */ UINT32_MAX,
369 LO_FLAGS_PARTSCAN,
370 LOCK_SH,
371 &d);
372 if (r == -EISDIR) {
373 _cleanup_free_ char *image_name = NULL;
374
375 /* We can't turn this into a loop-back block device, and this returns EISDIR? Then this is a directory
376 * tree and not a raw device. It's easy then. */
377
378 r = path_extract_filename(path, &image_name);
379 if (r < 0)
380 return log_error_errno(r, "Failed to extract image name from path '%s': %m", path);
381
382 r = extract_now(path, matches, image_name, path_is_extension, /* relax_extension_release_check= */ false, -1, &os_release, &unit_files);
383 if (r < 0)
384 return r;
385
386 } else if (r < 0)
387 return log_debug_errno(r, "Failed to set up loopback device for %s: %m", path);
388 else {
389 _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
390 _cleanup_(rmdir_and_freep) char *tmpdir = NULL;
391 _cleanup_close_pair_ int seq[2] = EBADF_PAIR;
392 _cleanup_(sigkill_waitp) pid_t child = 0;
393 DissectImageFlags flags =
394 DISSECT_IMAGE_READ_ONLY |
395 DISSECT_IMAGE_GENERIC_ROOT |
396 DISSECT_IMAGE_REQUIRE_ROOT |
397 DISSECT_IMAGE_DISCARD_ON_LOOP |
398 DISSECT_IMAGE_RELAX_VAR_CHECK |
399 DISSECT_IMAGE_USR_NO_ROOT |
400 DISSECT_IMAGE_ADD_PARTITION_DEVICES |
401 DISSECT_IMAGE_PIN_PARTITION_DEVICES |
402 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY;
403
404 if (path_is_extension)
405 flags |= DISSECT_IMAGE_VALIDATE_OS_EXT | (relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0);
406 else
407 flags |= DISSECT_IMAGE_VALIDATE_OS;
408
409 /* We now have a loopback block device, let's fork off a child in its own mount namespace, mount it
410 * there, and extract the metadata we need. The metadata is sent from the child back to us. */
411
412 BLOCK_SIGNALS(SIGCHLD);
413
414 r = mkdtemp_malloc("/tmp/inspect-XXXXXX", &tmpdir);
415 if (r < 0)
416 return log_debug_errno(r, "Failed to create temporary directory: %m");
417
418 r = dissect_loop_device(
419 d,
420 /* verity= */ NULL,
421 /* mount_options= */ NULL,
422 image_policy,
423 flags,
424 &m);
425 if (r == -ENOPKG)
426 sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't identify a suitable partition table or file system in '%s'.", path);
427 else if (r == -EADDRNOTAVAIL)
428 sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No root partition for specified root hash found in '%s'.", path);
429 else if (r == -ENOTUNIQ)
430 sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Multiple suitable root partitions found in image '%s'.", path);
431 else if (r == -ENXIO)
432 sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No suitable root partition found in image '%s'.", path);
433 else if (r == -EPROTONOSUPPORT)
434 sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Device '%s' is loopback block device with partition scanning turned off, please turn it on.", path);
435 if (r < 0)
436 return r;
437
438 if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, seq) < 0)
439 return log_debug_errno(errno, "Failed to allocated SOCK_SEQPACKET socket: %m");
440
441 r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE|FORK_LOG, &child);
442 if (r < 0)
443 return r;
444 if (r == 0) {
445 seq[0] = safe_close(seq[0]);
446
447 r = dissected_image_mount(
448 m,
449 tmpdir,
450 /* uid_shift= */ UID_INVALID,
451 /* uid_range= */ UID_INVALID,
452 /* userns_fd= */ -EBADF,
453 flags);
454 if (r < 0) {
455 log_debug_errno(r, "Failed to mount dissected image: %m");
456 goto child_finish;
457 }
458
459 r = extract_now(tmpdir, matches, m->image_name, path_is_extension, relax_extension_release_check, seq[1], NULL, NULL);
460
461 child_finish:
462 _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
463 }
464
465 seq[1] = safe_close(seq[1]);
466
467 unit_files = hashmap_new(&portable_metadata_hash_ops);
468 if (!unit_files)
469 return -ENOMEM;
470
471 for (;;) {
472 _cleanup_(portable_metadata_unrefp) PortableMetadata *add = NULL;
473 _cleanup_close_ int fd = -EBADF;
474 /* We use NAME_MAX space for the SELinux label here. The kernel currently enforces no limit, but
475 * according to suggestions from the SELinux people this will change and it will probably be
476 * identical to NAME_MAX. For now we use that, but this should be updated one day when the final
477 * limit is known. */
478 char iov_buffer[PATH_MAX + NAME_MAX + 2];
479 struct iovec iov = IOVEC_MAKE(iov_buffer, sizeof(iov_buffer));
480
481 ssize_t n = receive_one_fd_iov(seq[0], &iov, 1, 0, &fd);
482 if (n == -EIO)
483 break;
484 if (n < 0)
485 return log_debug_errno(n, "Failed to receive item: %m");
486 iov_buffer[n] = 0;
487
488 /* We can't really distinguish a zero-length datagram without any fds from EOF (both are signalled the
489 * same way by recvmsg()). Hence, accept either as end notification. */
490 if (isempty(iov_buffer) && fd < 0)
491 break;
492
493 if (isempty(iov_buffer) || fd < 0)
494 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
495 "Invalid item sent from child.");
496
497 /* Given recvmsg cannot be used with multiple io vectors if you don't know the size in advance,
498 * use a marker to separate the name and the optional SELinux context. */
499 char *selinux_label = memchr(iov_buffer, 0, n);
500 assert(selinux_label);
501 selinux_label++;
502
503 add = portable_metadata_new(iov_buffer, path, selinux_label, fd);
504 if (!add)
505 return -ENOMEM;
506 fd = -EBADF;
507
508 /* Note that we do not initialize 'add->source' here, as the source path is not usable here as
509 * it refers to a path only valid in the short-living namespaced child process we forked
510 * here. */
511
512 if (PORTABLE_METADATA_IS_UNIT(add)) {
513 r = hashmap_put(unit_files, add->name, add);
514 if (r < 0)
515 return log_debug_errno(r, "Failed to add item to unit file list: %m");
516
517 add = NULL;
518
519 } else if (PORTABLE_METADATA_IS_OS_RELEASE(add) || PORTABLE_METADATA_IS_EXTENSION_RELEASE(add)) {
520
521 assert(!os_release);
522 os_release = TAKE_PTR(add);
523 } else
524 assert_not_reached();
525 }
526
527 r = wait_for_terminate_and_check("(sd-dissect)", child, 0);
528 if (r < 0)
529 return r;
530 child = 0;
531 }
532
533 if (!os_release)
534 return sd_bus_error_setf(error,
535 SD_BUS_ERROR_INVALID_ARGS,
536 "Image '%s' lacks %s data, refusing.",
537 path,
538 path_is_extension ? "extension-release" : "os-release");
539
540 if (ret_unit_files)
541 *ret_unit_files = TAKE_PTR(unit_files);
542
543 if (ret_os_release)
544 *ret_os_release = TAKE_PTR(os_release);
545
546 return 0;
547 }
548
549 static int extract_image_and_extensions(
550 const char *name_or_path,
551 char **matches,
552 char **extension_image_paths,
553 bool validate_extension,
554 bool relax_extension_release_check,
555 const ImagePolicy *image_policy,
556 Image **ret_image,
557 OrderedHashmap **ret_extension_images,
558 OrderedHashmap **ret_extension_releases,
559 PortableMetadata **ret_os_release,
560 Hashmap **ret_unit_files,
561 char ***ret_valid_prefixes,
562 sd_bus_error *error) {
563
564 _cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL, *confext_level = NULL;
565 _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
566 _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
567 _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
568 _cleanup_strv_free_ char **valid_prefixes = NULL;
569 _cleanup_(image_unrefp) Image *image = NULL;
570 Image *ext;
571 int r;
572
573 assert(name_or_path);
574
575 r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image);
576 if (r < 0)
577 return r;
578
579 if (!strv_isempty(extension_image_paths)) {
580 extension_images = ordered_hashmap_new(&image_hash_ops);
581 if (!extension_images)
582 return -ENOMEM;
583
584 if (ret_extension_releases) {
585 extension_releases = ordered_hashmap_new(&portable_metadata_hash_ops);
586 if (!extension_releases)
587 return -ENOMEM;
588 }
589
590 STRV_FOREACH(p, extension_image_paths) {
591 _cleanup_(image_unrefp) Image *new = NULL;
592
593 r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new);
594 if (r < 0)
595 return r;
596
597 r = ordered_hashmap_put(extension_images, new->name, new);
598 if (r < 0)
599 return r;
600 TAKE_PTR(new);
601 }
602 }
603
604 r = portable_extract_by_path(
605 image->path,
606 /* path_is_extension= */ false,
607 /* relax_extension_release_check= */ false,
608 matches,
609 image_policy,
610 &os_release,
611 &unit_files,
612 error);
613 if (r < 0)
614 return r;
615
616 /* If we are layering extension images on top of a runtime image, check that the os-release and
617 * extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when
618 * the units are started. Also, collect valid portable prefixes if caller requested that. */
619 if (validate_extension || ret_valid_prefixes) {
620 _cleanup_free_ char *prefixes = NULL;
621
622 r = parse_env_file_fd(os_release->fd, os_release->name,
623 "ID", &id,
624 "VERSION_ID", &version_id,
625 "SYSEXT_LEVEL", &sysext_level,
626 "CONFEXT_LEVEL", &confext_level,
627 "PORTABLE_PREFIXES", &prefixes);
628 if (r < 0)
629 return r;
630 if (isempty(id))
631 return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s os-release metadata lacks the ID field", name_or_path);
632
633 if (prefixes) {
634 valid_prefixes = strv_split(prefixes, WHITESPACE);
635 if (!valid_prefixes)
636 return -ENOMEM;
637 }
638 }
639
640 ORDERED_HASHMAP_FOREACH(ext, extension_images) {
641 _cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL;
642 _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL;
643 _cleanup_strv_free_ char **extension_release = NULL;
644 const char *e;
645
646 r = portable_extract_by_path(
647 ext->path,
648 /* path_is_extension= */ true,
649 relax_extension_release_check,
650 matches,
651 image_policy,
652 &extension_release_meta,
653 &extra_unit_files,
654 error);
655 if (r < 0)
656 return r;
657
658 r = hashmap_move(unit_files, extra_unit_files);
659 if (r < 0)
660 return r;
661
662 if (!validate_extension && !ret_valid_prefixes && !ret_extension_releases)
663 continue;
664
665 r = load_env_file_pairs_fd(extension_release_meta->fd, extension_release_meta->name, &extension_release);
666 if (r < 0)
667 return r;
668
669 if (validate_extension) {
670 r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release, IMAGE_SYSEXT);
671 if (r < 0)
672 r = extension_release_validate(ext->path, id, version_id, confext_level, "portable", extension_release, IMAGE_CONFEXT);
673
674 if (r == 0)
675 return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path);
676 if (r < 0)
677 return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path);
678 }
679
680 e = strv_env_pairs_get(extension_release, "PORTABLE_PREFIXES");
681 if (e) {
682 _cleanup_strv_free_ char **l = NULL;
683
684 l = strv_split(e, WHITESPACE);
685 if (!l)
686 return -ENOMEM;
687
688 r = strv_extend_strv(&valid_prefixes, l, true);
689 if (r < 0)
690 return r;
691 }
692
693 if (ret_extension_releases) {
694 r = ordered_hashmap_put(extension_releases, ext->name, extension_release_meta);
695 if (r < 0)
696 return r;
697 TAKE_PTR(extension_release_meta);
698 }
699 }
700
701 strv_sort(valid_prefixes);
702
703 if (ret_image)
704 *ret_image = TAKE_PTR(image);
705 if (ret_extension_images)
706 *ret_extension_images = TAKE_PTR(extension_images);
707 if (ret_extension_releases)
708 *ret_extension_releases = TAKE_PTR(extension_releases);
709 if (ret_os_release)
710 *ret_os_release = TAKE_PTR(os_release);
711 if (ret_unit_files)
712 *ret_unit_files = TAKE_PTR(unit_files);
713 if (ret_valid_prefixes)
714 *ret_valid_prefixes = TAKE_PTR(valid_prefixes);
715
716 return 0;
717 }
718
719 int portable_extract(
720 const char *name_or_path,
721 char **matches,
722 char **extension_image_paths,
723 const ImagePolicy *image_policy,
724 PortableFlags flags,
725 PortableMetadata **ret_os_release,
726 OrderedHashmap **ret_extension_releases,
727 Hashmap **ret_unit_files,
728 char ***ret_valid_prefixes,
729 sd_bus_error *error) {
730
731 _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
732 _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
733 _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
734 _cleanup_strv_free_ char **valid_prefixes = NULL;
735 _cleanup_(image_unrefp) Image *image = NULL;
736 int r;
737
738 assert(name_or_path);
739
740 r = extract_image_and_extensions(
741 name_or_path,
742 matches,
743 extension_image_paths,
744 /* validate_extension= */ false,
745 /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION),
746 image_policy,
747 &image,
748 &extension_images,
749 &extension_releases,
750 &os_release,
751 &unit_files,
752 ret_valid_prefixes ? &valid_prefixes : NULL,
753 error);
754 if (r < 0)
755 return r;
756
757 if (hashmap_isempty(unit_files)) {
758 _cleanup_free_ char *extensions = strv_join(extension_image_paths, ", ");
759 if (!extensions)
760 return -ENOMEM;
761
762 return sd_bus_error_setf(error,
763 SD_BUS_ERROR_INVALID_ARGS,
764 "Couldn't find any matching unit files in image '%s%s%s', refusing.",
765 image->path,
766 isempty(extensions) ? "" : "' or any of its extensions '",
767 isempty(extensions) ? "" : extensions);
768 }
769
770 if (ret_os_release)
771 *ret_os_release = TAKE_PTR(os_release);
772 if (ret_extension_releases)
773 *ret_extension_releases = TAKE_PTR(extension_releases);
774 if (ret_unit_files)
775 *ret_unit_files = TAKE_PTR(unit_files);
776 if (ret_valid_prefixes)
777 *ret_valid_prefixes = TAKE_PTR(valid_prefixes);
778
779 return 0;
780 }
781
782 static int unit_file_is_active(
783 sd_bus *bus,
784 const char *name,
785 sd_bus_error *error) {
786
787 static const char *const active_states[] = {
788 "activating",
789 "active",
790 "reloading",
791 "deactivating",
792 NULL,
793 };
794 int r;
795
796 if (!bus)
797 return false;
798
799 /* If we are looking at a plain or instance things are easy, we can just query the state */
800 if (unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) {
801 _cleanup_free_ char *path = NULL, *buf = NULL;
802
803 path = unit_dbus_path_from_name(name);
804 if (!path)
805 return -ENOMEM;
806
807 r = sd_bus_get_property_string(
808 bus,
809 "org.freedesktop.systemd1",
810 path,
811 "org.freedesktop.systemd1.Unit",
812 "ActiveState",
813 error,
814 &buf);
815 if (r < 0)
816 return log_debug_errno(r, "Failed to retrieve unit state: %s", bus_error_message(error, r));
817
818 return strv_contains((char**) active_states, buf);
819 }
820
821 /* Otherwise we need to enumerate. But let's build the most restricted query we can */
822 if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) {
823 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
824 const char *at, *prefix, *joined;
825
826 r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "ListUnitsByPatterns");
827 if (r < 0)
828 return r;
829
830 r = sd_bus_message_append_strv(m, (char**) active_states);
831 if (r < 0)
832 return r;
833
834 at = strchr(name, '@');
835 assert(at);
836
837 prefix = strndupa_safe(name, at + 1 - name);
838 joined = strjoina(prefix, "*", at + 1);
839
840 r = sd_bus_message_append_strv(m, STRV_MAKE(joined));
841 if (r < 0)
842 return r;
843
844 r = sd_bus_call(bus, m, 0, error, &reply);
845 if (r < 0)
846 return log_debug_errno(r, "Failed to list units: %s", bus_error_message(error, r));
847
848 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
849 if (r < 0)
850 return r;
851
852 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_STRUCT, "ssssssouso");
853 if (r < 0)
854 return r;
855
856 return r > 0;
857 }
858
859 return -EINVAL;
860 }
861
862 static int portable_changes_add(
863 PortableChange **changes,
864 size_t *n_changes,
865 int type_or_errno, /* PORTABLE_COPY, PORTABLE_SYMLINK, … if positive, or errno if negative */
866 const char *path,
867 const char *source) {
868
869 _cleanup_free_ char *p = NULL, *s = NULL;
870 PortableChange *c;
871 int r;
872
873 assert(path);
874 assert(!changes == !n_changes);
875
876 if (type_or_errno >= 0)
877 assert(type_or_errno < _PORTABLE_CHANGE_TYPE_MAX);
878 else
879 assert(type_or_errno >= -ERRNO_MAX);
880
881 if (!changes)
882 return 0;
883
884 c = reallocarray(*changes, *n_changes + 1, sizeof(PortableChange));
885 if (!c)
886 return -ENOMEM;
887 *changes = c;
888
889 r = path_simplify_alloc(path, &p);
890 if (r < 0)
891 return r;
892
893 r = path_simplify_alloc(source, &s);
894 if (r < 0)
895 return r;
896
897 c[(*n_changes)++] = (PortableChange) {
898 .type_or_errno = type_or_errno,
899 .path = TAKE_PTR(p),
900 .source = TAKE_PTR(s),
901 };
902
903 return 0;
904 }
905
906 static int portable_changes_add_with_prefix(
907 PortableChange **changes,
908 size_t *n_changes,
909 int type_or_errno,
910 const char *prefix,
911 const char *path,
912 const char *source) {
913
914 _cleanup_free_ char *path_buf = NULL, *source_buf = NULL;
915
916 assert(path);
917 assert(!changes == !n_changes);
918
919 if (!changes)
920 return 0;
921
922 if (prefix) {
923 path_buf = path_join(prefix, path);
924 if (!path_buf)
925 return -ENOMEM;
926
927 path = path_buf;
928
929 if (source) {
930 source_buf = path_join(prefix, source);
931 if (!source_buf)
932 return -ENOMEM;
933
934 source = source_buf;
935 }
936 }
937
938 return portable_changes_add(changes, n_changes, type_or_errno, path, source);
939 }
940
941 void portable_changes_free(PortableChange *changes, size_t n_changes) {
942 size_t i;
943
944 assert(changes || n_changes == 0);
945
946 for (i = 0; i < n_changes; i++) {
947 free(changes[i].path);
948 free(changes[i].source);
949 }
950
951 free(changes);
952 }
953
954 static const char *root_setting_from_image(ImageType type) {
955 return IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage=";
956 }
957
958 static const char *extension_setting_from_image(ImageType type) {
959 return IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "ExtensionDirectories=" : "ExtensionImages=";
960 }
961
962 static int make_marker_text(const char *image_path, OrderedHashmap *extension_images, char **ret_text) {
963 _cleanup_free_ char *text = NULL, *escaped_image_path = NULL;
964 Image *ext;
965
966 assert(image_path);
967 assert(ret_text);
968
969 escaped_image_path = xescape(image_path, ":");
970 if (!escaped_image_path)
971 return -ENOMEM;
972
973 /* If the image is layered, include all layers in the marker as a colon-separated
974 * list of paths, so that we can do exact matches on removal. */
975 text = strjoin(PORTABLE_DROPIN_MARKER_BEGIN, escaped_image_path);
976 if (!text)
977 return -ENOMEM;
978
979 ORDERED_HASHMAP_FOREACH(ext, extension_images) {
980 _cleanup_free_ char *escaped = NULL;
981
982 escaped = xescape(ext->path, ":");
983 if (!escaped)
984 return -ENOMEM;
985
986 if (!strextend(&text, ":", escaped))
987 return -ENOMEM;
988 }
989
990 if (!strextend(&text, PORTABLE_DROPIN_MARKER_END "\n"))
991 return -ENOMEM;
992
993 *ret_text = TAKE_PTR(text);
994 return 0;
995 }
996
997 static int append_release_log_fields(
998 char **text,
999 const PortableMetadata *release,
1000 ImageClass type,
1001 const char *field_name) {
1002
1003 static const char *const field_versions[_IMAGE_CLASS_MAX][4]= {
1004 [IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "BUILD_ID", NULL },
1005 [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL },
1006 [IMAGE_CONFEXT] = { "CONFEXT_IMAGE_VERSION", "CONFEXT_VERSION_ID", "CONFEXT_BUILD_ID", NULL },
1007 };
1008 static const char *const field_ids[_IMAGE_CLASS_MAX][3]= {
1009 [IMAGE_PORTABLE] = { "IMAGE_ID", "ID", NULL },
1010 [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL },
1011 [IMAGE_CONFEXT] = { "CONFEXT_IMAGE_ID", "CONFEXT_ID", NULL },
1012 };
1013 _cleanup_strv_free_ char **fields = NULL;
1014 const char *id = NULL, *version = NULL;
1015 int r;
1016
1017 assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_SYSEXT, IMAGE_CONFEXT));
1018 assert(!strv_isempty((char *const *)field_ids[type]));
1019 assert(!strv_isempty((char *const *)field_versions[type]));
1020 assert(field_name);
1021 assert(text);
1022
1023 if (!release)
1024 return 0; /* Nothing to do. */
1025
1026 r = load_env_file_pairs_fd(release->fd, release->name, &fields);
1027 if (r < 0)
1028 return log_debug_errno(r, "Failed to parse '%s': %m", release->name);
1029
1030 /* Find an ID first, in order of preference from more specific to less specific: IMAGE_ID -> ID */
1031 id = strv_find_first_field((char *const *)field_ids[type], fields);
1032
1033 /* Then the version, same logic, prefer the more specific one */
1034 version = strv_find_first_field((char *const *)field_versions[type], fields);
1035
1036 /* If there's no valid version to be found, simply omit it. */
1037 if (!id && !version)
1038 return 0;
1039
1040 if (!strextend(text,
1041 "LogExtraFields=",
1042 field_name,
1043 "=",
1044 strempty(id),
1045 id && version ? "_" : "",
1046 strempty(version),
1047 "\n"))
1048 return -ENOMEM;
1049
1050 return 0;
1051 }
1052
1053 static int install_chroot_dropin(
1054 const char *image_path,
1055 ImageType type,
1056 OrderedHashmap *extension_images,
1057 OrderedHashmap *extension_releases,
1058 const PortableMetadata *m,
1059 const PortableMetadata *os_release,
1060 const char *dropin_dir,
1061 PortableFlags flags,
1062 char **ret_dropin,
1063 PortableChange **changes,
1064 size_t *n_changes) {
1065
1066 _cleanup_free_ char *text = NULL, *dropin = NULL;
1067 int r;
1068
1069 assert(image_path);
1070 assert(m);
1071 assert(dropin_dir);
1072
1073 dropin = path_join(dropin_dir, "20-portable.conf");
1074 if (!dropin)
1075 return -ENOMEM;
1076
1077 r = make_marker_text(image_path, extension_images, &text);
1078 if (r < 0)
1079 return log_debug_errno(r, "Failed to generate marker string for portable drop-in: %m");
1080
1081 if (endswith(m->name, ".service")) {
1082 const char *root_type;
1083 _cleanup_free_ char *base_name = NULL;
1084 Image *ext;
1085
1086 root_type = root_setting_from_image(type);
1087
1088 r = path_extract_filename(m->image_path ?: image_path, &base_name);
1089 if (r < 0)
1090 return log_debug_errno(r, "Failed to extract basename from '%s': %m", m->image_path ?: image_path);
1091
1092 if (!strextend(&text,
1093 "\n"
1094 "[Service]\n",
1095 root_type, image_path, "\n"
1096 "Environment=PORTABLE=", base_name, "\n"
1097 "LogExtraFields=PORTABLE=", base_name, "\n"))
1098 return -ENOMEM;
1099
1100 /* If we have a single image then PORTABLE= will point to it, so we add
1101 * PORTABLE_NAME_AND_VERSION= with the os-release fields and we are done. But if we have
1102 * extensions, PORTABLE= will point to the image where the current unit was found in. So we
1103 * also list PORTABLE_ROOT= and PORTABLE_ROOT_NAME_AND_VERSION= for the base image, and
1104 * PORTABLE_EXTENSION= and PORTABLE_EXTENSION_NAME_AND_VERSION= for each extension, so that
1105 * all needed metadata is available. */
1106 if (ordered_hashmap_isempty(extension_images))
1107 r = append_release_log_fields(&text, os_release, IMAGE_PORTABLE, "PORTABLE_NAME_AND_VERSION");
1108 else {
1109 _cleanup_free_ char *root_base_name = NULL;
1110
1111 r = path_extract_filename(image_path, &root_base_name);
1112 if (r < 0)
1113 return log_debug_errno(r, "Failed to extract basename from '%s': %m", image_path);
1114
1115 if (!strextend(&text,
1116 "Environment=PORTABLE_ROOT=", root_base_name, "\n",
1117 "LogExtraFields=PORTABLE_ROOT=", root_base_name, "\n"))
1118 return -ENOMEM;
1119
1120 r = append_release_log_fields(&text, os_release, IMAGE_PORTABLE, "PORTABLE_ROOT_NAME_AND_VERSION");
1121 }
1122 if (r < 0)
1123 return r;
1124
1125 if (m->image_path && !path_equal(m->image_path, image_path))
1126 ORDERED_HASHMAP_FOREACH(ext, extension_images) {
1127 _cleanup_free_ char *extension_base_name = NULL;
1128
1129 r = path_extract_filename(ext->path, &extension_base_name);
1130 if (r < 0)
1131 return log_debug_errno(r, "Failed to extract basename from '%s': %m", ext->path);
1132
1133 if (!strextend(&text,
1134 "\n",
1135 extension_setting_from_image(ext->type),
1136 ext->path,
1137 /* With --force tell PID1 to avoid enforcing that the image <name> and
1138 * extension-release.<name> have to match. */
1139 !IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) &&
1140 FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION) ?
1141 ":x-systemd.relax-extension-release-check\n" :
1142 "\n",
1143 /* In PORTABLE= we list the 'main' image name for this unit
1144 * (the image where the unit was extracted from), but we are
1145 * stacking multiple images, so list those too. */
1146 "LogExtraFields=PORTABLE_EXTENSION=", extension_base_name, "\n"))
1147 return -ENOMEM;
1148
1149 /* Look for image/version identifiers in the extension release files. We
1150 * look for all possible IDs, but typically only 1 or 2 will be set, so
1151 * the number of fields added shouldn't be too large. We prefix the DDI
1152 * name to the value, so that we can add the same field multiple times and
1153 * still be able to identify what applies to what. */
1154 r = append_release_log_fields(&text,
1155 ordered_hashmap_get(extension_releases, ext->name),
1156 IMAGE_SYSEXT,
1157 "PORTABLE_EXTENSION_NAME_AND_VERSION");
1158 if (r < 0)
1159 return r;
1160
1161 r = append_release_log_fields(&text,
1162 ordered_hashmap_get(extension_releases, ext->name),
1163 IMAGE_CONFEXT,
1164 "PORTABLE_EXTENSION_NAME_AND_VERSION");
1165 if (r < 0)
1166 return r;
1167 }
1168 }
1169
1170 r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC);
1171 if (r < 0)
1172 return log_debug_errno(r, "Failed to write '%s': %m", dropin);
1173
1174 (void) portable_changes_add(changes, n_changes, PORTABLE_WRITE, dropin, NULL);
1175
1176 if (ret_dropin)
1177 *ret_dropin = TAKE_PTR(dropin);
1178
1179 return 0;
1180 }
1181
1182 static int install_profile_dropin(
1183 const char *image_path,
1184 const PortableMetadata *m,
1185 const char *dropin_dir,
1186 const char *profile,
1187 PortableFlags flags,
1188 char **ret_dropin,
1189 PortableChange **changes,
1190 size_t *n_changes) {
1191
1192 _cleanup_free_ char *dropin = NULL, *from = NULL;
1193 int r;
1194
1195 assert(image_path);
1196 assert(m);
1197 assert(dropin_dir);
1198
1199 if (!profile)
1200 return 0;
1201
1202 r = find_portable_profile(profile, m->name, &from);
1203 if (r < 0) {
1204 if (r != -ENOENT)
1205 return log_debug_errno(errno, "Profile '%s' is not accessible: %m", profile);
1206
1207 log_debug_errno(errno, "Skipping link to profile '%s', as it does not exist: %m", profile);
1208 return 0;
1209 }
1210
1211 dropin = path_join(dropin_dir, "10-profile.conf");
1212 if (!dropin)
1213 return -ENOMEM;
1214
1215 if (flags & PORTABLE_PREFER_COPY) {
1216
1217 r = copy_file_atomic(from, dropin, 0644, COPY_REFLINK|COPY_FSYNC);
1218 if (r < 0)
1219 return log_debug_errno(r, "Failed to copy %s %s %s: %m", from, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), dropin);
1220
1221 (void) portable_changes_add(changes, n_changes, PORTABLE_COPY, dropin, from);
1222
1223 } else {
1224
1225 if (symlink(from, dropin) < 0)
1226 return log_debug_errno(errno, "Failed to link %s %s %s: %m", from, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), dropin);
1227
1228 (void) portable_changes_add(changes, n_changes, PORTABLE_SYMLINK, dropin, from);
1229 }
1230
1231 if (ret_dropin)
1232 *ret_dropin = TAKE_PTR(dropin);
1233
1234 return 0;
1235 }
1236
1237 static const char *attached_path(const LookupPaths *paths, PortableFlags flags) {
1238 const char *where;
1239
1240 assert(paths);
1241
1242 if (flags & PORTABLE_RUNTIME)
1243 where = paths->runtime_attached;
1244 else
1245 where = paths->persistent_attached;
1246
1247 assert(where);
1248 return where;
1249 }
1250
1251 static int attach_unit_file(
1252 const LookupPaths *paths,
1253 const char *image_path,
1254 ImageType type,
1255 OrderedHashmap *extension_images,
1256 OrderedHashmap *extension_releases,
1257 const PortableMetadata *m,
1258 const PortableMetadata *os_release,
1259 const char *profile,
1260 PortableFlags flags,
1261 PortableChange **changes,
1262 size_t *n_changes) {
1263
1264 _cleanup_(unlink_and_freep) char *chroot_dropin = NULL, *profile_dropin = NULL;
1265 _cleanup_(rmdir_and_freep) char *dropin_dir = NULL;
1266 _cleanup_free_ char *path = NULL;
1267 const char *where;
1268 int r;
1269
1270 assert(paths);
1271 assert(image_path);
1272 assert(m);
1273 assert(PORTABLE_METADATA_IS_UNIT(m));
1274
1275 where = attached_path(paths, flags);
1276
1277 (void) mkdir_parents(where, 0755);
1278 if (mkdir(where, 0755) < 0) {
1279 if (errno != EEXIST)
1280 return log_debug_errno(errno, "Failed to create attach directory %s: %m", where);
1281 } else
1282 (void) portable_changes_add(changes, n_changes, PORTABLE_MKDIR, where, NULL);
1283
1284 path = path_join(where, m->name);
1285 if (!path)
1286 return -ENOMEM;
1287
1288 dropin_dir = strjoin(path, ".d");
1289 if (!dropin_dir)
1290 return -ENOMEM;
1291
1292 if (mkdir(dropin_dir, 0755) < 0) {
1293 if (errno != EEXIST)
1294 return log_debug_errno(errno, "Failed to create drop-in directory %s: %m", dropin_dir);
1295 } else
1296 (void) portable_changes_add(changes, n_changes, PORTABLE_MKDIR, dropin_dir, NULL);
1297
1298 /* We install the drop-ins first, and the actual unit file last to achieve somewhat atomic behaviour if PID 1
1299 * is reloaded while we are creating things here: as long as only the drop-ins exist the unit doesn't exist at
1300 * all for PID 1. */
1301
1302 r = install_chroot_dropin(image_path, type, extension_images, extension_releases, m, os_release, dropin_dir, flags, &chroot_dropin, changes, n_changes);
1303 if (r < 0)
1304 return r;
1305
1306 r = install_profile_dropin(image_path, m, dropin_dir, profile, flags, &profile_dropin, changes, n_changes);
1307 if (r < 0)
1308 return r;
1309
1310 if ((flags & PORTABLE_PREFER_SYMLINK) && m->source) {
1311
1312 if (symlink(m->source, path) < 0)
1313 return log_debug_errno(errno, "Failed to symlink unit file '%s': %m", path);
1314
1315 (void) portable_changes_add(changes, n_changes, PORTABLE_SYMLINK, path, m->source);
1316
1317 } else {
1318 _cleanup_(unlink_and_freep) char *tmp = NULL;
1319 _cleanup_close_ int fd = -EBADF;
1320
1321 (void) mac_selinux_create_file_prepare_label(path, m->selinux_label);
1322
1323 fd = open_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &tmp);
1324 mac_selinux_create_file_clear(); /* Clear immediately in case of errors */
1325 if (fd < 0)
1326 return log_debug_errno(fd, "Failed to create unit file '%s': %m", path);
1327
1328 r = copy_bytes(m->fd, fd, UINT64_MAX, COPY_REFLINK);
1329 if (r < 0)
1330 return log_debug_errno(r, "Failed to copy unit file '%s': %m", path);
1331
1332 if (fchmod(fd, 0644) < 0)
1333 return log_debug_errno(errno, "Failed to change unit file access mode for '%s': %m", path);
1334
1335 r = link_tmpfile(fd, tmp, path, LINK_TMPFILE_SYNC);
1336 if (r < 0)
1337 return log_debug_errno(r, "Failed to install unit file '%s': %m", path);
1338
1339 tmp = mfree(tmp);
1340
1341 (void) portable_changes_add(changes, n_changes, PORTABLE_COPY, path, m->source);
1342 }
1343
1344 /* All is established now, now let's disable any rollbacks */
1345 chroot_dropin = mfree(chroot_dropin);
1346 profile_dropin = mfree(profile_dropin);
1347 dropin_dir = mfree(dropin_dir);
1348
1349 return 0;
1350 }
1351
1352 static int image_target_path(
1353 const char *image_path,
1354 PortableFlags flags,
1355 char **ret) {
1356
1357 const char *fn, *where;
1358 char *joined = NULL;
1359
1360 assert(image_path);
1361 assert(ret);
1362
1363 fn = last_path_component(image_path);
1364
1365 if (flags & PORTABLE_RUNTIME)
1366 where = "/run/portables/";
1367 else
1368 where = "/etc/portables/";
1369
1370 joined = strjoin(where, fn);
1371 if (!joined)
1372 return -ENOMEM;
1373
1374 *ret = joined;
1375 return 0;
1376 }
1377
1378 static int install_image(
1379 const char *image_path,
1380 PortableFlags flags,
1381 PortableChange **changes,
1382 size_t *n_changes) {
1383
1384 _cleanup_free_ char *target = NULL;
1385 int r;
1386
1387 assert(image_path);
1388
1389 /* If the image is outside of the image search also link it into it, so that it can be found with
1390 * short image names and is listed among the images. If we are operating in mixed mode, the image is
1391 * copied instead. */
1392
1393 if (image_in_search_path(IMAGE_PORTABLE, NULL, image_path))
1394 return 0;
1395
1396 r = image_target_path(image_path, flags, &target);
1397 if (r < 0)
1398 return log_debug_errno(r, "Failed to generate image symlink path: %m");
1399
1400 (void) mkdir_parents(target, 0755);
1401
1402 if (flags & PORTABLE_MIXED_COPY_LINK) {
1403 r = copy_tree(image_path,
1404 target,
1405 UID_INVALID,
1406 GID_INVALID,
1407 COPY_REFLINK | COPY_FSYNC | COPY_FSYNC_FULL | COPY_SYNCFS,
1408 /* denylist= */ NULL,
1409 /* subvolumes= */ NULL);
1410 if (r < 0)
1411 return log_debug_errno(
1412 r,
1413 "Failed to copy %s %s %s: %m",
1414 image_path,
1415 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
1416 target);
1417
1418 } else {
1419 if (symlink(image_path, target) < 0)
1420 return log_debug_errno(
1421 errno,
1422 "Failed to link %s %s %s: %m",
1423 image_path,
1424 special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
1425 target);
1426 }
1427
1428 (void) portable_changes_add(
1429 changes,
1430 n_changes,
1431 (flags & PORTABLE_MIXED_COPY_LINK) ? PORTABLE_COPY : PORTABLE_SYMLINK,
1432 target,
1433 image_path);
1434 return 0;
1435 }
1436
1437 static int install_image_and_extensions(
1438 const Image *image,
1439 OrderedHashmap *extension_images,
1440 PortableFlags flags,
1441 PortableChange **changes,
1442 size_t *n_changes) {
1443
1444 Image *ext;
1445 int r;
1446
1447 assert(image);
1448
1449 ORDERED_HASHMAP_FOREACH(ext, extension_images) {
1450 r = install_image(ext->path, flags, changes, n_changes);
1451 if (r < 0)
1452 return r;
1453 }
1454
1455 r = install_image(image->path, flags, changes, n_changes);
1456 if (r < 0)
1457 return r;
1458
1459 return 0;
1460 }
1461
1462 static bool prefix_matches_compatible(char **matches, char **valid_prefixes) {
1463 /* Checks if all 'matches' are included in the list of 'valid_prefixes' */
1464
1465 STRV_FOREACH(m, matches)
1466 if (!strv_contains(valid_prefixes, *m))
1467 return false;
1468
1469 return true;
1470 }
1471
1472 static void log_portable_verb(
1473 const char *verb,
1474 const char *message_id,
1475 const char *image_path,
1476 const char *profile,
1477 OrderedHashmap *extension_images,
1478 char **extension_image_paths,
1479 PortableFlags flags) {
1480
1481 _cleanup_free_ char *root_base_name = NULL, *extensions_joined = NULL;
1482 _cleanup_strv_free_ char **extension_base_names = NULL;
1483 Image *ext;
1484 int r;
1485
1486 assert(verb);
1487 assert(message_id);
1488 assert(image_path);
1489 assert(!extension_images || !extension_image_paths);
1490
1491 /* Use the same structured metadata as it is attached to units via LogExtraFields=. The main image
1492 * is logged as PORTABLE_ROOT= and extensions, if any, as individual PORTABLE_EXTENSION= fields. */
1493
1494 r = path_extract_filename(image_path, &root_base_name);
1495 if (r < 0)
1496 log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", image_path);
1497
1498 ORDERED_HASHMAP_FOREACH(ext, extension_images) {
1499 _cleanup_free_ char *extension_base_name = NULL;
1500
1501 r = path_extract_filename(ext->path, &extension_base_name);
1502 if (r < 0) {
1503 log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", ext->path);
1504 continue;
1505 }
1506
1507 r = strv_extendf(&extension_base_names, "PORTABLE_EXTENSION=%s", extension_base_name);
1508 if (r < 0)
1509 log_oom_debug();
1510
1511 if (!strextend_with_separator(&extensions_joined, ", ", ext->path))
1512 log_oom_debug();
1513 }
1514
1515 STRV_FOREACH(e, extension_image_paths) {
1516 _cleanup_free_ char *extension_base_name = NULL;
1517
1518 r = path_extract_filename(*e, &extension_base_name);
1519 if (r < 0) {
1520 log_debug_errno(r, "Failed to extract basename from '%s', ignoring: %m", *e);
1521 continue;
1522 }
1523
1524 r = strv_extendf(&extension_base_names, "PORTABLE_EXTENSION=%s", extension_base_name);
1525 if (r < 0)
1526 log_oom_debug();
1527
1528 if (!strextend_with_separator(&extensions_joined, ", ", *e))
1529 log_oom_debug();
1530 }
1531
1532 LOG_CONTEXT_PUSH_STRV(extension_base_names);
1533
1534 log_struct(LOG_INFO,
1535 LOG_MESSAGE("Successfully %s%s '%s%s%s%s%s'",
1536 verb,
1537 FLAGS_SET(flags, PORTABLE_RUNTIME) ? " ephemeral" : "",
1538 image_path,
1539 isempty(extensions_joined) ? "" : "' and its extension(s) '",
1540 strempty(extensions_joined),
1541 isempty(profile) ? "" : "' using profile '",
1542 strempty(profile)),
1543 message_id,
1544 "PORTABLE_ROOT=%s", strna(root_base_name));
1545 }
1546
1547 int portable_attach(
1548 sd_bus *bus,
1549 const char *name_or_path,
1550 char **matches,
1551 const char *profile,
1552 char **extension_image_paths,
1553 const ImagePolicy *image_policy,
1554 PortableFlags flags,
1555 PortableChange **changes,
1556 size_t *n_changes,
1557 sd_bus_error *error) {
1558
1559 _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL;
1560 _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL;
1561 _cleanup_hashmap_free_ Hashmap *unit_files = NULL;
1562 _cleanup_(lookup_paths_done) LookupPaths paths = {};
1563 _cleanup_strv_free_ char **valid_prefixes = NULL;
1564 _cleanup_(image_unrefp) Image *image = NULL;
1565 PortableMetadata *item;
1566 int r;
1567
1568 r = extract_image_and_extensions(
1569 name_or_path,
1570 matches,
1571 extension_image_paths,
1572 /* validate_extension= */ true,
1573 /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION),
1574 image_policy,
1575 &image,
1576 &extension_images,
1577 &extension_releases,
1578 &os_release,
1579 &unit_files,
1580 &valid_prefixes,
1581 error);
1582 if (r < 0)
1583 return r;
1584
1585 if (valid_prefixes && !prefix_matches_compatible(matches, valid_prefixes)) {
1586 _cleanup_free_ char *matches_joined = NULL, *extensions_joined = NULL, *valid_prefixes_joined = NULL;
1587
1588 matches_joined = strv_join(matches, "', '");
1589 if (!matches_joined)
1590 return -ENOMEM;
1591
1592 extensions_joined = strv_join(extension_image_paths, ", ");
1593 if (!extensions_joined)
1594 return -ENOMEM;
1595
1596 valid_prefixes_joined = strv_join(valid_prefixes, ", ");
1597 if (!valid_prefixes_joined)
1598 return -ENOMEM;
1599
1600 return sd_bus_error_setf(
1601 error,
1602 SD_BUS_ERROR_INVALID_ARGS,
1603 "Selected matches '%s' are not compatible with portable service image '%s%s%s', refusing. (Acceptable prefix matches are: %s)",
1604 matches_joined,
1605 image->path,
1606 isempty(extensions_joined) ? "" : "' or any of its extensions '",
1607 strempty(extensions_joined),
1608 valid_prefixes_joined);
1609 }
1610
1611 if (hashmap_isempty(unit_files)) {
1612 _cleanup_free_ char *extensions_joined = strv_join(extension_image_paths, ", ");
1613 if (!extensions_joined)
1614 return -ENOMEM;
1615
1616 return sd_bus_error_setf(
1617 error,
1618 SD_BUS_ERROR_INVALID_ARGS,
1619 "Couldn't find any matching unit files in image '%s%s%s', refusing.",
1620 image->path,
1621 isempty(extensions_joined) ? "" : "' or any of its extensions '",
1622 strempty(extensions_joined));
1623 }
1624
1625 r = lookup_paths_init(&paths, RUNTIME_SCOPE_SYSTEM, /* flags= */ 0, NULL);
1626 if (r < 0)
1627 return r;
1628
1629 if (!FLAGS_SET(flags, PORTABLE_REATTACH) && !FLAGS_SET(flags, PORTABLE_FORCE_ATTACH))
1630 HASHMAP_FOREACH(item, unit_files) {
1631 r = unit_file_exists(RUNTIME_SCOPE_SYSTEM, &paths, item->name);
1632 if (r < 0)
1633 return sd_bus_error_set_errnof(error, r, "Failed to determine whether unit '%s' exists on the host: %m", item->name);
1634 if (r > 0)
1635 return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' exists on the host already, refusing.", item->name);
1636
1637 r = unit_file_is_active(bus, item->name, error);
1638 if (r < 0)
1639 return r;
1640 if (r > 0)
1641 return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' is active already, refusing.", item->name);
1642 }
1643
1644 HASHMAP_FOREACH(item, unit_files) {
1645 r = attach_unit_file(&paths, image->path, image->type, extension_images, extension_releases,
1646 item, os_release, profile, flags, changes, n_changes);
1647 if (r < 0)
1648 return sd_bus_error_set_errnof(error, r, "Failed to attach unit '%s': %m", item->name);
1649 }
1650
1651 /* We don't care too much for the image symlink/copy, it's just a convenience thing, it's not necessary for
1652 * proper operation otherwise. */
1653 (void) install_image_and_extensions(image, extension_images, flags, changes, n_changes);
1654
1655 log_portable_verb(
1656 "attached",
1657 "MESSAGE_ID=" SD_MESSAGE_PORTABLE_ATTACHED_STR,
1658 image->path,
1659 profile,
1660 extension_images,
1661 /* extension_image_paths= */ NULL,
1662 flags);
1663
1664 return 0;
1665 }
1666
1667 static bool marker_matches_images(const char *marker, const char *name_or_path, char **extension_image_paths) {
1668 _cleanup_strv_free_ char **root_and_extensions = NULL;
1669 const char *a;
1670 int r;
1671
1672 assert(marker);
1673 assert(name_or_path);
1674
1675 /* If extensions were used when attaching, the marker will be a colon-separated
1676 * list of images/paths. We enforce strict 1:1 matching, so that we are sure
1677 * we are detaching exactly what was attached.
1678 * For each image, starting with the root, we look for a token in the marker,
1679 * and return a negative answer on any non-matching combination. */
1680
1681 root_and_extensions = strv_new(name_or_path);
1682 if (!root_and_extensions)
1683 return -ENOMEM;
1684
1685 r = strv_extend_strv(&root_and_extensions, extension_image_paths, false);
1686 if (r < 0)
1687 return r;
1688
1689 STRV_FOREACH(image_name_or_path, root_and_extensions) {
1690 _cleanup_free_ char *image = NULL;
1691
1692 r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
1693 if (r < 0)
1694 return log_debug_errno(r, "Failed to parse marker: %s", marker);
1695 if (r == 0)
1696 return false;
1697
1698 a = last_path_component(image);
1699
1700 if (image_name_is_valid(*image_name_or_path)) {
1701 const char *e, *underscore;
1702
1703 /* We shall match against an image name. In that case let's compare the last component, and optionally
1704 * allow either a suffix of ".raw" or a series of "/".
1705 * But allow matching on a different version of the same image, when a "_" is used as a separator. */
1706 underscore = strchr(*image_name_or_path, '_');
1707 if (underscore) {
1708 if (strneq(a, *image_name_or_path, underscore - *image_name_or_path))
1709 continue;
1710 return false;
1711 }
1712
1713 e = startswith(a, *image_name_or_path);
1714 if (!e)
1715 return false;
1716
1717 if(!(e[strspn(e, "/")] == 0 || streq(e, ".raw")))
1718 return false;
1719 } else {
1720 const char *b, *underscore;
1721 size_t l;
1722
1723 /* We shall match against a path. Let's ignore any prefix here though, as often there are many ways to
1724 * reach the same file. However, in this mode, let's validate any file suffix.
1725 * But also ensure that we don't fail if both components don't have a '/' at all
1726 * (strcspn returns the full length of the string in that case, which might not
1727 * match as the versions might differ). */
1728
1729 l = strcspn(a, "/");
1730 b = last_path_component(*image_name_or_path);
1731
1732 if ((a[l] != '/') != !strchr(b, '/')) /* One is a directory, the other is not */
1733 return false;
1734
1735 if (a[l] != 0 && strcspn(b, "/") != l)
1736 return false;
1737
1738 underscore = strchr(b, '_');
1739 if (underscore)
1740 l = underscore - b;
1741 else { /* Either component could be versioned */
1742 underscore = strchr(a, '_');
1743 if (underscore)
1744 l = underscore - a;
1745 }
1746
1747 if (!strneq(a, b, l))
1748 return false;
1749 }
1750 }
1751
1752 return true;
1753 }
1754
1755 static int test_chroot_dropin(
1756 DIR *d,
1757 const char *where,
1758 const char *fname,
1759 const char *name_or_path,
1760 char **extension_image_paths,
1761 char **ret_marker) {
1762
1763 _cleanup_free_ char *line = NULL, *marker = NULL;
1764 _cleanup_fclose_ FILE *f = NULL;
1765 _cleanup_close_ int fd = -EBADF;
1766 const char *p, *e, *k;
1767 int r;
1768
1769 assert(d);
1770 assert(where);
1771 assert(fname);
1772
1773 /* We recognize unis created from portable images via the drop-in we created for them */
1774
1775 p = strjoina(fname, ".d/20-portable.conf");
1776 fd = openat(dirfd(d), p, O_RDONLY|O_CLOEXEC);
1777 if (fd < 0) {
1778 if (errno == ENOENT)
1779 return 0;
1780
1781 return log_debug_errno(errno, "Failed to open %s/%s: %m", where, p);
1782 }
1783
1784 r = take_fdopen_unlocked(&fd, "r", &f);
1785 if (r < 0)
1786 return log_debug_errno(r, "Failed to convert file handle: %m");
1787
1788 r = read_line(f, LONG_LINE_MAX, &line);
1789 if (r < 0)
1790 return log_debug_errno(r, "Failed to read from %s/%s: %m", where, p);
1791
1792 e = startswith(line, PORTABLE_DROPIN_MARKER_BEGIN);
1793 if (!e)
1794 return 0;
1795
1796 k = endswith(e, PORTABLE_DROPIN_MARKER_END);
1797 if (!k)
1798 return 0;
1799
1800 marker = strndup(e, k - e);
1801 if (!marker)
1802 return -ENOMEM;
1803
1804 if (!name_or_path)
1805 r = true;
1806 else
1807 r = marker_matches_images(marker, name_or_path, extension_image_paths);
1808
1809 if (ret_marker)
1810 *ret_marker = TAKE_PTR(marker);
1811
1812 return r;
1813 }
1814
1815 int portable_detach(
1816 sd_bus *bus,
1817 const char *name_or_path,
1818 char **extension_image_paths,
1819 PortableFlags flags,
1820 PortableChange **changes,
1821 size_t *n_changes,
1822 sd_bus_error *error) {
1823
1824 _cleanup_(lookup_paths_done) LookupPaths paths = {};
1825 _cleanup_set_free_ Set *unit_files = NULL, *markers = NULL;
1826 _cleanup_free_ char *extensions = NULL;
1827 _cleanup_closedir_ DIR *d = NULL;
1828 const char *where, *item;
1829 int ret = 0;
1830 int r;
1831
1832 assert(name_or_path);
1833
1834 r = lookup_paths_init(&paths, RUNTIME_SCOPE_SYSTEM, /* flags= */ 0, NULL);
1835 if (r < 0)
1836 return r;
1837
1838 where = attached_path(&paths, flags);
1839
1840 d = opendir(where);
1841 if (!d) {
1842 if (errno == ENOENT)
1843 goto not_found;
1844
1845 return log_debug_errno(errno, "Failed to open '%s' directory: %m", where);
1846 }
1847
1848 FOREACH_DIRENT(de, d, return log_debug_errno(errno, "Failed to enumerate '%s' directory: %m", where)) {
1849 _cleanup_free_ char *marker = NULL, *unit_name = NULL;
1850 const char *dot;
1851
1852 /* When a portable service is enabled with "portablectl --copy=symlink --enable --now attach",
1853 * and is disabled with "portablectl --enable --now detach", which calls DisableUnitFilesWithFlags
1854 * DBus method, the main unit file is removed, but its drop-ins are not. Hence, here we need
1855 * to list both main unit files and drop-in directories (without the main unit files). */
1856
1857 dot = endswith(de->d_name, ".d");
1858 if (dot)
1859 unit_name = strndup(de->d_name, dot - de->d_name);
1860 else
1861 unit_name = strdup(de->d_name);
1862 if (!unit_name)
1863 return -ENOMEM;
1864
1865 if (!unit_name_is_valid(unit_name, UNIT_NAME_ANY))
1866 continue;
1867
1868 /* Filter out duplicates */
1869 if (set_contains(unit_files, unit_name))
1870 continue;
1871
1872 if (dot ? !IN_SET(de->d_type, DT_LNK, DT_DIR) : !IN_SET(de->d_type, DT_LNK, DT_REG))
1873 continue;
1874
1875 r = test_chroot_dropin(d, where, unit_name, name_or_path, extension_image_paths, &marker);
1876 if (r < 0)
1877 return r;
1878 if (r == 0)
1879 continue;
1880
1881 if (!FLAGS_SET(flags, PORTABLE_REATTACH) && !FLAGS_SET(flags, PORTABLE_FORCE_ATTACH)) {
1882 r = unit_file_is_active(bus, unit_name, error);
1883 if (r < 0)
1884 return r;
1885 if (r > 0)
1886 return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit file '%s' is active, can't detach.", unit_name);
1887 }
1888
1889 r = set_ensure_consume(&unit_files, &string_hash_ops_free, TAKE_PTR(unit_name));
1890 if (r < 0)
1891 return log_oom_debug();
1892
1893 for (const char *p = marker;;) {
1894 _cleanup_free_ char *image = NULL;
1895
1896 r = extract_first_word(&p, &image, ":", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
1897 if (r < 0)
1898 return log_debug_errno(r, "Failed to parse marker: %s", p);
1899 if (r == 0)
1900 break;
1901
1902 if (path_is_absolute(image) && !image_in_search_path(IMAGE_PORTABLE, NULL, image)) {
1903 r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(image));
1904 if (r < 0)
1905 return r;
1906 }
1907 }
1908 }
1909
1910 if (set_isempty(unit_files))
1911 goto not_found;
1912
1913 SET_FOREACH(item, unit_files) {
1914 _cleanup_free_ char *md = NULL;
1915
1916 if (unlinkat(dirfd(d), item, 0) < 0) {
1917 log_debug_errno(errno, "Can't remove unit file %s/%s: %m", where, item);
1918
1919 if (errno != ENOENT && ret >= 0)
1920 ret = -errno;
1921 } else
1922 portable_changes_add_with_prefix(changes, n_changes, PORTABLE_UNLINK, where, item, NULL);
1923
1924 FOREACH_STRING(suffix, ".d/10-profile.conf", ".d/20-portable.conf") {
1925 _cleanup_free_ char *dropin = NULL;
1926
1927 dropin = strjoin(item, suffix);
1928 if (!dropin)
1929 return -ENOMEM;
1930
1931 if (unlinkat(dirfd(d), dropin, 0) < 0) {
1932 log_debug_errno(errno, "Can't remove drop-in %s/%s: %m", where, dropin);
1933
1934 if (errno != ENOENT && ret >= 0)
1935 ret = -errno;
1936 } else
1937 portable_changes_add_with_prefix(changes, n_changes, PORTABLE_UNLINK, where, dropin, NULL);
1938 }
1939
1940 md = strjoin(item, ".d");
1941 if (!md)
1942 return -ENOMEM;
1943
1944 if (unlinkat(dirfd(d), md, AT_REMOVEDIR) < 0) {
1945 log_debug_errno(errno, "Can't remove drop-in directory %s/%s: %m", where, md);
1946
1947 if (errno != ENOENT && ret >= 0)
1948 ret = -errno;
1949 } else
1950 portable_changes_add_with_prefix(changes, n_changes, PORTABLE_UNLINK, where, md, NULL);
1951 }
1952
1953 /* Now, also drop any image symlink or copy, for images outside of the sarch path */
1954 SET_FOREACH(item, markers) {
1955 _cleanup_free_ char *target = NULL;
1956
1957 r = image_target_path(item, flags, &target);
1958 if (r < 0) {
1959 log_debug_errno(r, "Failed to determine image path for '%s', ignoring: %m", item);
1960 continue;
1961 }
1962
1963 r = rm_rf(target, REMOVE_ROOT | REMOVE_PHYSICAL | REMOVE_MISSING_OK | REMOVE_SYNCFS);
1964 if (r < 0) {
1965 log_debug_errno(r, "Can't remove image '%s': %m", target);
1966
1967 if (r != -ENOENT)
1968 RET_GATHER(ret, r);
1969 } else
1970 portable_changes_add(changes, n_changes, PORTABLE_UNLINK, target, NULL);
1971 }
1972
1973 /* Try to remove the unit file directory, if we can */
1974 if (rmdir(where) >= 0)
1975 portable_changes_add(changes, n_changes, PORTABLE_UNLINK, where, NULL);
1976
1977 log_portable_verb(
1978 "detached",
1979 "MESSAGE_ID=" SD_MESSAGE_PORTABLE_DETACHED_STR,
1980 name_or_path,
1981 /* profile= */ NULL,
1982 /* extension_images= */ NULL,
1983 extension_image_paths,
1984 flags);
1985
1986 return ret;
1987
1988 not_found:
1989 extensions = strv_join(extension_image_paths, ", ");
1990 if (!extensions)
1991 return -ENOMEM;
1992
1993 r = sd_bus_error_setf(error,
1994 BUS_ERROR_NO_SUCH_UNIT,
1995 "No unit files associated with '%s%s%s' found attached to the system. Image not attached?",
1996 name_or_path,
1997 isempty(extensions) ? "" : "' or any of its extensions '",
1998 isempty(extensions) ? "" : extensions);
1999 return log_debug_errno(r, "%s", error->message);
2000 }
2001
2002 static int portable_get_state_internal(
2003 sd_bus *bus,
2004 const char *name_or_path,
2005 char **extension_image_paths,
2006 PortableFlags flags,
2007 PortableState *ret,
2008 sd_bus_error *error) {
2009
2010 _cleanup_(lookup_paths_done) LookupPaths paths = {};
2011 bool found_enabled = false, found_running = false;
2012 _cleanup_set_free_ Set *unit_files = NULL;
2013 _cleanup_closedir_ DIR *d = NULL;
2014 const char *where;
2015 int r;
2016
2017 assert(name_or_path);
2018 assert(ret);
2019
2020 r = lookup_paths_init(&paths, RUNTIME_SCOPE_SYSTEM, /* flags= */ 0, NULL);
2021 if (r < 0)
2022 return r;
2023
2024 where = attached_path(&paths, flags);
2025
2026 d = opendir(where);
2027 if (!d) {
2028 if (errno == ENOENT) {
2029 /* If the 'attached' directory doesn't exist at all, then we know for sure this image isn't attached. */
2030 *ret = PORTABLE_DETACHED;
2031 return 0;
2032 }
2033
2034 return log_debug_errno(errno, "Failed to open '%s' directory: %m", where);
2035 }
2036
2037 FOREACH_DIRENT(de, d, return log_debug_errno(errno, "Failed to enumerate '%s' directory: %m", where)) {
2038 UnitFileState state;
2039
2040 if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
2041 continue;
2042
2043 /* Filter out duplicates */
2044 if (set_contains(unit_files, de->d_name))
2045 continue;
2046
2047 if (!IN_SET(de->d_type, DT_LNK, DT_REG))
2048 continue;
2049
2050 r = test_chroot_dropin(d, where, de->d_name, name_or_path, extension_image_paths, NULL);
2051 if (r < 0)
2052 return r;
2053 if (r == 0)
2054 continue;
2055
2056 r = unit_file_lookup_state(RUNTIME_SCOPE_SYSTEM, &paths, de->d_name, &state);
2057 if (r < 0)
2058 return log_debug_errno(r, "Failed to determine unit file state of '%s': %m", de->d_name);
2059 if (!IN_SET(state, UNIT_FILE_STATIC, UNIT_FILE_DISABLED, UNIT_FILE_LINKED, UNIT_FILE_LINKED_RUNTIME))
2060 found_enabled = true;
2061
2062 r = unit_file_is_active(bus, de->d_name, error);
2063 if (r < 0)
2064 return r;
2065 if (r > 0)
2066 found_running = true;
2067
2068 r = set_put_strdup(&unit_files, de->d_name);
2069 if (r < 0)
2070 return log_debug_errno(r, "Failed to add unit name '%s' to set: %m", de->d_name);
2071 }
2072
2073 *ret = found_running ? (!set_isempty(unit_files) && (flags & PORTABLE_RUNTIME) ? PORTABLE_RUNNING_RUNTIME : PORTABLE_RUNNING) :
2074 found_enabled ? (flags & PORTABLE_RUNTIME ? PORTABLE_ENABLED_RUNTIME : PORTABLE_ENABLED) :
2075 !set_isempty(unit_files) ? (flags & PORTABLE_RUNTIME ? PORTABLE_ATTACHED_RUNTIME : PORTABLE_ATTACHED) : PORTABLE_DETACHED;
2076
2077 return 0;
2078 }
2079
2080 int portable_get_state(
2081 sd_bus *bus,
2082 const char *name_or_path,
2083 char **extension_image_paths,
2084 PortableFlags flags,
2085 PortableState *ret,
2086 sd_bus_error *error) {
2087
2088 PortableState state;
2089 int r;
2090
2091 assert(name_or_path);
2092 assert(ret);
2093
2094 /* We look for matching units twice: once in the regular directories, and once in the runtime directories — but
2095 * the latter only if we didn't find anything in the former. */
2096
2097 r = portable_get_state_internal(bus, name_or_path, extension_image_paths, flags & ~PORTABLE_RUNTIME, &state, error);
2098 if (r < 0)
2099 return r;
2100
2101 if (state == PORTABLE_DETACHED) {
2102 r = portable_get_state_internal(bus, name_or_path, extension_image_paths, flags | PORTABLE_RUNTIME, &state, error);
2103 if (r < 0)
2104 return r;
2105 }
2106
2107 *ret = state;
2108 return 0;
2109 }
2110
2111 int portable_get_profiles(char ***ret) {
2112 assert(ret);
2113
2114 return conf_files_list_nulstr(ret, NULL, NULL, CONF_FILES_DIRECTORY|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED, PORTABLE_PROFILE_DIRS);
2115 }
2116
2117 static const char* const portable_change_type_table[_PORTABLE_CHANGE_TYPE_MAX] = {
2118 [PORTABLE_COPY] = "copy",
2119 [PORTABLE_MKDIR] = "mkdir",
2120 [PORTABLE_SYMLINK] = "symlink",
2121 [PORTABLE_UNLINK] = "unlink",
2122 [PORTABLE_WRITE] = "write",
2123 };
2124
2125 DEFINE_STRING_TABLE_LOOKUP(portable_change_type, int);
2126
2127 static const char* const portable_state_table[_PORTABLE_STATE_MAX] = {
2128 [PORTABLE_DETACHED] = "detached",
2129 [PORTABLE_ATTACHED] = "attached",
2130 [PORTABLE_ATTACHED_RUNTIME] = "attached-runtime",
2131 [PORTABLE_ENABLED] = "enabled",
2132 [PORTABLE_ENABLED_RUNTIME] = "enabled-runtime",
2133 [PORTABLE_RUNNING] = "running",
2134 [PORTABLE_RUNNING_RUNTIME] = "running-runtime",
2135 };
2136
2137 DEFINE_STRING_TABLE_LOOKUP(portable_state, PortableState);