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