1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <sys/mount.h>
24 #include "alloc-util.h"
25 #include "bus-label.h"
28 #include "dissect-image.h"
32 #include "image-dbus.h"
34 #include "loop-util.h"
35 #include "machine-image.h"
36 #include "mount-util.h"
37 #include "process-util.h"
38 #include "raw-clone.h"
40 #include "user-util.h"
42 static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type
, image_type
, ImageType
);
44 int bus_image_method_remove(
45 sd_bus_message
*message
,
47 sd_bus_error
*error
) {
49 _cleanup_close_pair_
int errno_pipe_fd
[2] = { -1, -1 };
50 Image
*image
= userdata
;
51 Manager
*m
= image
->userdata
;
58 if (m
->n_operations
>= OPERATIONS_MAX
)
59 return sd_bus_error_setf(error
, SD_BUS_ERROR_LIMITS_EXCEEDED
, "Too many ongoing operations.");
61 r
= bus_verify_polkit_async(
64 "org.freedesktop.machine1.manage-images",
73 return 1; /* Will call us back */
75 if (pipe2(errno_pipe_fd
, O_CLOEXEC
|O_NONBLOCK
) < 0)
76 return sd_bus_error_set_errnof(error
, errno
, "Failed to create pipe: %m");
80 return sd_bus_error_set_errnof(error
, errno
, "Failed to fork(): %m");
82 errno_pipe_fd
[0] = safe_close(errno_pipe_fd
[0]);
84 r
= image_remove(image
);
86 (void) write(errno_pipe_fd
[1], &r
, sizeof(r
));
93 errno_pipe_fd
[1] = safe_close(errno_pipe_fd
[1]);
95 r
= operation_new(m
, NULL
, child
, message
, errno_pipe_fd
[0], NULL
);
97 (void) sigkill_wait(child
);
101 errno_pipe_fd
[0] = -1;
106 int bus_image_method_rename(
107 sd_bus_message
*message
,
109 sd_bus_error
*error
) {
111 Image
*image
= userdata
;
112 Manager
*m
= image
->userdata
;
113 const char *new_name
;
119 r
= sd_bus_message_read(message
, "s", &new_name
);
123 if (!image_name_is_valid(new_name
))
124 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Image name '%s' is invalid.", new_name
);
126 r
= bus_verify_polkit_async(
129 "org.freedesktop.machine1.manage-images",
138 return 1; /* Will call us back */
140 r
= image_rename(image
, new_name
);
144 return sd_bus_reply_method_return(message
, NULL
);
147 int bus_image_method_clone(
148 sd_bus_message
*message
,
150 sd_bus_error
*error
) {
152 _cleanup_close_pair_
int errno_pipe_fd
[2] = { -1, -1 };
153 Image
*image
= userdata
;
154 Manager
*m
= image
->userdata
;
155 const char *new_name
;
163 if (m
->n_operations
>= OPERATIONS_MAX
)
164 return sd_bus_error_setf(error
, SD_BUS_ERROR_LIMITS_EXCEEDED
, "Too many ongoing operations.");
166 r
= sd_bus_message_read(message
, "sb", &new_name
, &read_only
);
170 if (!image_name_is_valid(new_name
))
171 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "Image name '%s' is invalid.", new_name
);
173 r
= bus_verify_polkit_async(
176 "org.freedesktop.machine1.manage-images",
185 return 1; /* Will call us back */
187 if (pipe2(errno_pipe_fd
, O_CLOEXEC
|O_NONBLOCK
) < 0)
188 return sd_bus_error_set_errnof(error
, errno
, "Failed to create pipe: %m");
192 return sd_bus_error_set_errnof(error
, errno
, "Failed to fork(): %m");
194 errno_pipe_fd
[0] = safe_close(errno_pipe_fd
[0]);
196 r
= image_clone(image
, new_name
, read_only
);
198 (void) write(errno_pipe_fd
[1], &r
, sizeof(r
));
205 errno_pipe_fd
[1] = safe_close(errno_pipe_fd
[1]);
207 r
= operation_new(m
, NULL
, child
, message
, errno_pipe_fd
[0], NULL
);
209 (void) sigkill_wait(child
);
213 errno_pipe_fd
[0] = -1;
218 int bus_image_method_mark_read_only(
219 sd_bus_message
*message
,
221 sd_bus_error
*error
) {
223 Image
*image
= userdata
;
224 Manager
*m
= image
->userdata
;
229 r
= sd_bus_message_read(message
, "b", &read_only
);
233 r
= bus_verify_polkit_async(
236 "org.freedesktop.machine1.manage-images",
245 return 1; /* Will call us back */
247 r
= image_read_only(image
, read_only
);
251 return sd_bus_reply_method_return(message
, NULL
);
254 int bus_image_method_set_limit(
255 sd_bus_message
*message
,
257 sd_bus_error
*error
) {
259 Image
*image
= userdata
;
260 Manager
*m
= image
->userdata
;
266 r
= sd_bus_message_read(message
, "t", &limit
);
269 if (!FILE_SIZE_VALID_OR_INFINITY(limit
))
270 return sd_bus_error_setf(error
, SD_BUS_ERROR_INVALID_ARGS
, "New limit out of range");
272 r
= bus_verify_polkit_async(
275 "org.freedesktop.machine1.manage-images",
284 return 1; /* Will call us back */
286 r
= image_set_limit(image
, limit
);
290 return sd_bus_reply_method_return(message
, NULL
);
293 #define EXIT_NOT_FOUND 2
295 static int directory_image_get_os_release(Image
*image
, char ***ret
, sd_bus_error
*error
) {
297 _cleanup_free_
char *path
= NULL
;
303 r
= chase_symlinks("/etc/os-release", image
->path
, CHASE_PREFIX_ROOT
, &path
);
305 r
= chase_symlinks("/usr/lib/os-release", image
->path
, CHASE_PREFIX_ROOT
, &path
);
307 return sd_bus_error_setf(error
, SD_BUS_ERROR_FAILED
, "Image does not contain OS release information");
309 return sd_bus_error_set_errnof(error
, r
, "Failed to resolve %s: %m", image
->path
);
311 r
= load_env_file_pairs(NULL
, path
, NULL
, ret
);
313 return sd_bus_error_set_errnof(error
, r
, "Failed to open %s: %m", path
);
318 static int raw_image_get_os_release(Image
*image
, char ***ret
, sd_bus_error
*error
) {
319 _cleanup_(rmdir_and_freep
) char *t
= NULL
;
320 _cleanup_(loop_device_unrefp
) LoopDevice
*d
= NULL
;
321 _cleanup_(dissected_image_unrefp
) DissectedImage
*m
= NULL
;
322 _cleanup_(sigkill_waitp
) pid_t child
= 0;
323 _cleanup_close_pair_
int pair
[2] = { -1, -1 };
324 _cleanup_fclose_
FILE *f
= NULL
;
325 _cleanup_strv_free_
char **v
= NULL
;
332 r
= mkdtemp_malloc("/tmp/machined-root-XXXXXX", &t
);
334 return sd_bus_error_set_errnof(error
, r
, "Failed to create temporary directory: %m");
336 r
= loop_device_make_by_path(image
->path
, O_RDONLY
, &d
);
338 return sd_bus_error_set_errnof(error
, r
, "Failed to set up loop block device for %s: %m", image
->path
);
340 r
= dissect_image(d
->fd
, NULL
, 0, DISSECT_IMAGE_REQUIRE_ROOT
, &m
);
342 return sd_bus_error_set_errnof(error
, r
, "Disk image %s not understood: %m", image
->path
);
344 return sd_bus_error_set_errnof(error
, r
, "Failed to dissect image %s: %m", image
->path
);
346 if (pipe2(pair
, O_CLOEXEC
) < 0)
347 return sd_bus_error_set_errnof(error
, errno
, "Failed to create communication pipe: %m");
349 child
= raw_clone(SIGCHLD
|CLONE_NEWNS
);
351 return sd_bus_error_set_errnof(error
, errno
, "Failed to fork(): %m");
356 pair
[0] = safe_close(pair
[0]);
358 /* Make sure we never propagate to the host */
359 if (mount(NULL
, "/", NULL
, MS_SLAVE
| MS_REC
, NULL
) < 0)
362 r
= dissected_image_mount(m
, t
, DISSECT_IMAGE_READ_ONLY
);
366 r
= mount_move_root(t
);
370 fd
= open("/etc/os-release", O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
371 if (fd
< 0 && errno
== ENOENT
) {
372 fd
= open("/usr/lib/os-release", O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
373 if (fd
< 0 && errno
== ENOENT
)
374 _exit(EXIT_NOT_FOUND
);
379 r
= copy_bytes(fd
, pair
[1], (uint64_t) -1, 0);
386 pair
[1] = safe_close(pair
[1]);
388 f
= fdopen(pair
[0], "re");
394 r
= load_env_file_pairs(f
, "os-release", NULL
, &v
);
398 r
= wait_for_terminate(child
, &si
);
400 return sd_bus_error_set_errnof(error
, r
, "Failed to wait for child: %m");
402 if (si
.si_code
== CLD_EXITED
&& si
.si_status
== EXIT_NOT_FOUND
)
403 return sd_bus_error_setf(error
, SD_BUS_ERROR_FAILED
, "Image does not contain OS release information");
404 if (si
.si_code
!= CLD_EXITED
|| si
.si_status
!= EXIT_SUCCESS
)
405 return sd_bus_error_setf(error
, SD_BUS_ERROR_FAILED
, "Child died abnormally.");
413 int bus_image_method_get_os_release(
414 sd_bus_message
*message
,
416 sd_bus_error
*error
) {
418 _cleanup_release_lock_file_ LockFile tree_global_lock
= LOCK_FILE_INIT
, tree_local_lock
= LOCK_FILE_INIT
;
419 _cleanup_strv_free_
char **v
= NULL
;
420 Image
*image
= userdata
;
423 r
= image_path_lock(image
->path
, LOCK_SH
|LOCK_NB
, &tree_global_lock
, &tree_local_lock
);
425 return sd_bus_error_set_errnof(error
, r
, "Failed to lock image: %m");
427 switch (image
->type
) {
429 case IMAGE_DIRECTORY
:
430 case IMAGE_SUBVOLUME
:
431 r
= directory_image_get_os_release(image
, &v
, error
);
436 r
= raw_image_get_os_release(image
, &v
, error
);
440 assert_not_reached("Unknown image type");
445 return bus_reply_pair_array(message
, v
);
448 const sd_bus_vtable image_vtable
[] = {
449 SD_BUS_VTABLE_START(0),
450 SD_BUS_PROPERTY("Name", "s", NULL
, offsetof(Image
, name
), 0),
451 SD_BUS_PROPERTY("Path", "s", NULL
, offsetof(Image
, path
), 0),
452 SD_BUS_PROPERTY("Type", "s", property_get_type
, offsetof(Image
, type
), 0),
453 SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool
, offsetof(Image
, read_only
), 0),
454 SD_BUS_PROPERTY("CreationTimestamp", "t", NULL
, offsetof(Image
, crtime
), 0),
455 SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL
, offsetof(Image
, mtime
), 0),
456 SD_BUS_PROPERTY("Usage", "t", NULL
, offsetof(Image
, usage
), 0),
457 SD_BUS_PROPERTY("Limit", "t", NULL
, offsetof(Image
, limit
), 0),
458 SD_BUS_PROPERTY("UsageExclusive", "t", NULL
, offsetof(Image
, usage_exclusive
), 0),
459 SD_BUS_PROPERTY("LimitExclusive", "t", NULL
, offsetof(Image
, limit_exclusive
), 0),
460 SD_BUS_METHOD("Remove", NULL
, NULL
, bus_image_method_remove
, SD_BUS_VTABLE_UNPRIVILEGED
),
461 SD_BUS_METHOD("Rename", "s", NULL
, bus_image_method_rename
, SD_BUS_VTABLE_UNPRIVILEGED
),
462 SD_BUS_METHOD("Clone", "sb", NULL
, bus_image_method_clone
, SD_BUS_VTABLE_UNPRIVILEGED
),
463 SD_BUS_METHOD("MarkReadOnly", "b", NULL
, bus_image_method_mark_read_only
, SD_BUS_VTABLE_UNPRIVILEGED
),
464 SD_BUS_METHOD("SetLimit", "t", NULL
, bus_image_method_set_limit
, SD_BUS_VTABLE_UNPRIVILEGED
),
465 SD_BUS_METHOD("GetOSRelease", NULL
, "a{ss}", bus_image_method_get_os_release
, SD_BUS_VTABLE_UNPRIVILEGED
),
469 static int image_flush_cache(sd_event_source
*s
, void *userdata
) {
470 Manager
*m
= userdata
;
476 while ((i
= hashmap_steal_first(m
->image_cache
)))
482 int image_object_find(sd_bus
*bus
, const char *path
, const char *interface
, void *userdata
, void **found
, sd_bus_error
*error
) {
483 _cleanup_free_
char *e
= NULL
;
484 Manager
*m
= userdata
;
494 p
= startswith(path
, "/org/freedesktop/machine1/image/");
498 e
= bus_label_unescape(p
);
502 image
= hashmap_get(m
->image_cache
, e
);
508 r
= hashmap_ensure_allocated(&m
->image_cache
, &string_hash_ops
);
512 if (!m
->image_cache_defer_event
) {
513 r
= sd_event_add_defer(m
->event
, &m
->image_cache_defer_event
, image_flush_cache
, m
);
517 r
= sd_event_source_set_priority(m
->image_cache_defer_event
, SD_EVENT_PRIORITY_IDLE
);
522 r
= sd_event_source_set_enabled(m
->image_cache_defer_event
, SD_EVENT_ONESHOT
);
526 r
= image_find(e
, &image
);
532 r
= hashmap_put(m
->image_cache
, image
->name
, image
);
542 char *image_bus_path(const char *name
) {
543 _cleanup_free_
char *e
= NULL
;
547 e
= bus_label_escape(name
);
551 return strappend("/org/freedesktop/machine1/image/", e
);
554 int image_node_enumerator(sd_bus
*bus
, const char *path
, void *userdata
, char ***nodes
, sd_bus_error
*error
) {
555 _cleanup_(image_hashmap_freep
) Hashmap
*images
= NULL
;
556 _cleanup_strv_free_
char **l
= NULL
;
565 images
= hashmap_new(&string_hash_ops
);
569 r
= image_discover(images
);
573 HASHMAP_FOREACH(image
, images
, i
) {
576 p
= image_bus_path(image
->name
);
580 r
= strv_consume(&l
, p
);