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