Merge pull request #4795 from poettering/dissect

Generalize image dissection logic of nspawn, and make it useful for other tools.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2016-12-10 01:08:13 -05:00
committed by GitHub
26 changed files with 2234 additions and 714 deletions
+19
View File
@@ -1409,3 +1409,22 @@ int read_nul_string(FILE *f, char **ret) {
return 0;
}
int mkdtemp_malloc(const char *template, char **ret) {
char *p;
assert(template);
assert(ret);
p = strdup(template);
if (!p)
return -ENOMEM;
if (!mkdtemp(p)) {
free(p);
return -errno;
}
*ret = p;
return 0;
}
+2
View File
@@ -88,3 +88,5 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
int link_tmpfile(int fd, const char *path, const char *target);
int read_nul_string(FILE *f, char **ret);
int mkdtemp_malloc(const char *template, char **ret);
+7
View File
@@ -84,3 +84,10 @@ enum {
};
int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret);
/* Useful for usage with _cleanup_(), removes a directory and frees the pointer */
static inline void rmdir_and_free(char *p) {
(void) rmdir(p);
free(p);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, rmdir_and_free);
-2
View File
@@ -33,8 +33,6 @@ int rm_rf(const char *path, RemoveFlags flags);
/* Useful for usage with _cleanup_(), destroys a directory and frees the pointer */
static inline void rm_rf_physical_and_free(char *p) {
if (!p)
return;
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
free(p);
}
+12 -7
View File
@@ -344,24 +344,29 @@ static int delete_loopback(const char *device) {
}
static int delete_dm(dev_t devnum) {
_cleanup_close_ int fd = -1;
int r;
struct dm_ioctl dm = {
.version = {DM_VERSION_MAJOR,
DM_VERSION_MINOR,
DM_VERSION_PATCHLEVEL},
.version = {
DM_VERSION_MAJOR,
DM_VERSION_MINOR,
DM_VERSION_PATCHLEVEL
},
.data_size = sizeof(dm),
.dev = devnum,
};
_cleanup_close_ int fd = -1;
assert(major(devnum) != 0);
fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC);
if (fd < 0)
return -errno;
r = ioctl(fd, DM_DEV_REMOVE, &dm);
return r >= 0 ? 0 : -errno;
if (ioctl(fd, DM_DEV_REMOVE, &dm) < 0)
return -errno;
return 0;
}
static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) {
+1 -1
View File
@@ -651,7 +651,7 @@ int main(int argc, char *argv[]) {
k = crypt_init(&cd, arg_header);
} else
k = crypt_init(&cd, argv[3]);
if (k) {
if (k != 0) {
log_error_errno(k, "crypt_init() failed: %m");
goto finish;
}
+1
View File
@@ -0,0 +1 @@
../Makefile
+271
View File
@@ -0,0 +1,271 @@
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <fcntl.h>
#include <stdio.h>
#include <getopt.h>
#include "architecture.h"
#include "dissect-image.h"
#include "hexdecoct.h"
#include "log.h"
#include "loop-util.h"
#include "string-util.h"
#include "util.h"
static enum {
ACTION_DISSECT,
ACTION_MOUNT,
} arg_action = ACTION_DISSECT;
static const char *arg_image = NULL;
static const char *arg_path = NULL;
static DissectImageFlags arg_flags = DISSECT_IMAGE_DISCARD_ON_LOOP;
static void *arg_root_hash = NULL;
static size_t arg_root_hash_size = 0;
static void help(void) {
printf("%s [OPTIONS...] IMAGE\n"
"%s [OPTIONS...] --mount IMAGE PATH\n"
"Dissect a file system OS image.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -m --mount Mount the image to the specified directory\n"
" -r --read-only Mount read-only\n"
" --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
" --root-hash=HASH Specify root hash for verity\n",
program_invocation_short_name,
program_invocation_short_name);
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_DISCARD,
ARG_ROOT_HASH,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "mount", no_argument, NULL, 'm' },
{ "read-only", no_argument, NULL, 'r' },
{ "discard", required_argument, NULL, ARG_DISCARD },
{ "root-hash", required_argument, NULL, ARG_ROOT_HASH },
{}
};
int c, r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hmr", options, NULL)) >= 0) {
switch (c) {
case 'h':
help();
return 0;
case ARG_VERSION:
return version();
case 'm':
arg_action = ACTION_MOUNT;
break;
case 'r':
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
case ARG_DISCARD:
if (streq(optarg, "disabled"))
arg_flags &= ~(DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_DISCARD|DISSECT_IMAGE_DISCARD_ON_CRYPTO);
else if (streq(optarg, "loop"))
arg_flags = (arg_flags & ~(DISSECT_IMAGE_DISCARD|DISSECT_IMAGE_DISCARD_ON_CRYPTO)) | DISSECT_IMAGE_DISCARD_ON_LOOP;
else if (streq(optarg, "all"))
arg_flags = (arg_flags & ~(DISSECT_IMAGE_DISCARD_ON_CRYPTO)) | DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD;
else if (streq(optarg, "crypt"))
arg_flags |= DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD | DISSECT_IMAGE_DISCARD_ON_CRYPTO;
else {
log_error("Unknown --discard= parameter: %s", optarg);
return -EINVAL;
}
break;
case ARG_ROOT_HASH: {
void *p;
size_t l;
r = unhexmem(optarg, strlen(optarg), &p, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash: %s", optarg);
if (l < sizeof(sd_id128_t)) {
log_error("Root hash must be at least 128bit long: %s", optarg);
free(p);
return -EINVAL;
}
free(arg_root_hash);
arg_root_hash = p;
arg_root_hash_size = l;
break;
}
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
}
switch (arg_action) {
case ACTION_DISSECT:
if (optind + 1 != argc) {
log_error("Expected a file path as only argument.");
return -EINVAL;
}
arg_image = argv[optind];
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
case ACTION_MOUNT:
if (optind + 2 != argc) {
log_error("Expected a file path and mount point path as only arguments.");
return -EINVAL;
}
arg_image = argv[optind];
arg_path = argv[optind + 1];
break;
default:
assert_not_reached("Unknown action.");
}
return 1;
}
int main(int argc, char *argv[]) {
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
int r;
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
goto finish;
r = loop_device_make_by_path(arg_image, (arg_flags & DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, &d);
if (r < 0) {
log_error_errno(r, "Failed to set up loopback device: %m");
goto finish;
}
r = dissect_image(d->fd, arg_root_hash, arg_root_hash_size, &m);
if (r == -ENOPKG) {
log_error_errno(r, "Couldn't identify a suitable partition table or file system in %s.", arg_image);
goto finish;
}
if (r == -EADDRNOTAVAIL) {
log_error_errno(r, "No root partition for specified root hash found in %s.", arg_image);
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to dissect image: %m");
goto finish;
}
switch (arg_action) {
case ACTION_DISSECT: {
unsigned i;
for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
DissectedPartition *p = m->partitions + i;
int k;
if (!p->found)
continue;
printf("Found %s '%s' partition",
p->rw ? "writable" : "read-only",
partition_designator_to_string(i));
if (p->fstype)
printf(" of type %s", p->fstype);
if (p->architecture != _ARCHITECTURE_INVALID)
printf(" for %s", architecture_to_string(p->architecture));
k = PARTITION_VERITY_OF(i);
if (k >= 0)
printf(" %s verity", m->partitions[k].found ? "with" : "without");
if (p->partno >= 0)
printf(" on partition #%i", p->partno);
if (p->node)
printf(" (%s)", p->node);
putchar('\n');
}
break;
}
case ACTION_MOUNT:
r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_flags, &di);
if (r < 0)
goto finish;
r = dissected_image_mount(m, arg_path, arg_flags);
if (r < 0) {
log_error_errno(r, "Failed to mount image: %m");
goto finish;
}
if (di) {
r = decrypted_image_relinquish(di);
if (r < 0) {
log_error_errno(r, "Failed to relinquish DM devices: %m");
goto finish;
}
}
loop_device_relinquish(d);
break;
default:
assert_not_reached("Unknown action.");
}
finish:
free(arg_root_hash);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
+3 -1
View File
@@ -97,8 +97,10 @@ _public_ struct udev *udev_new(void) {
_cleanup_fclose_ FILE *f = NULL;
udev = new0(struct udev, 1);
if (udev == NULL)
if (!udev) {
errno = -ENOMEM;
return NULL;
}
udev->refcount = 1;
f = fopen("/etc/udev/udev.conf", "re");
+165
View File
@@ -17,14 +17,23 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sys/mount.h>
#include "alloc-util.h"
#include "bus-label.h"
#include "bus-util.h"
#include "copy.h"
#include "dissect-image.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "image-dbus.h"
#include "io-util.h"
#include "loop-util.h"
#include "machine-image.h"
#include "mount-util.h"
#include "process-util.h"
#include "raw-clone.h"
#include "strv.h"
#include "user-util.h"
@@ -279,6 +288,161 @@ int bus_image_method_set_limit(
return sd_bus_reply_method_return(message, NULL);
}
#define EXIT_NOT_FOUND 2
static int directory_image_get_os_release(Image *image, char ***ret, sd_bus_error *error) {
_cleanup_free_ char *path = NULL;
_cleanup_close_ int fd = -1;
int r;
assert(image);
assert(ret);
r = chase_symlinks("/etc/os-release", image->path, CHASE_PREFIX_ROOT, &path);
if (r == -ENOENT)
r = chase_symlinks("/usr/lib/os-release", image->path, CHASE_PREFIX_ROOT, &path);
if (r == -ENOENT)
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Image does not contain OS release information");
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to resolve %s: %m", image->path);
r = load_env_file_pairs(NULL, path, NULL, ret);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to open %s: %m", path);
return 0;
}
static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *error) {
_cleanup_(rmdir_and_freep) char *t = NULL;
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
_cleanup_(sigkill_waitp) pid_t child = 0;
_cleanup_close_pair_ int pair[2] = { -1, -1 };
_cleanup_fclose_ FILE *f = NULL;
_cleanup_strv_free_ char **v = NULL;
siginfo_t si;
int r;
assert(image);
assert(ret);
r = mkdtemp_malloc("/tmp/machined-root-XXXXXX", &t);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to create temporary directory: %m");
r = loop_device_make_by_path(image->path, O_RDONLY, &d);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to set up loop block device for %s: %m", image->path);
r = dissect_image(d->fd, NULL, 0, &m);
if (r == -ENOPKG)
return sd_bus_error_set_errnof(error, r, "Disk image %s not understood: %m", image->path);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to dissect image %s: %m", image->path);
if (pipe2(pair, O_CLOEXEC) < 0)
return sd_bus_error_set_errnof(error, errno, "Failed to create communication pipe: %m");
child = raw_clone(SIGCHLD|CLONE_NEWNS);
if (child < 0)
return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
if (child == 0) {
int fd;
pair[0] = safe_close(pair[0]);
/* Make sure we never propagate to the host */
if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0)
_exit(EXIT_FAILURE);
r = dissected_image_mount(m, t, DISSECT_IMAGE_READ_ONLY);
if (r < 0)
_exit(EXIT_FAILURE);
r = mount_move_root(t);
if (r < 0)
_exit(EXIT_FAILURE);
fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0 && errno == ENOENT) {
fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0 && errno == ENOENT)
_exit(EXIT_NOT_FOUND);
}
if (fd < 0)
_exit(EXIT_FAILURE);
r = copy_bytes(fd, pair[1], (uint64_t) -1, false);
if (r < 0)
_exit(EXIT_FAILURE);
_exit(EXIT_SUCCESS);
}
pair[1] = safe_close(pair[1]);
f = fdopen(pair[0], "re");
if (!f)
return -errno;
pair[0] = -1;
r = load_env_file_pairs(f, "os-release", NULL, &v);
if (r < 0)
return r;
r = wait_for_terminate(child, &si);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
child = 0;
if (si.si_code == CLD_EXITED && si.si_status == EXIT_NOT_FOUND)
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Image does not contain OS release information");
if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
*ret = v;
v = NULL;
return 0;
}
int bus_image_method_get_os_release(
sd_bus_message *message,
void *userdata,
sd_bus_error *error) {
_cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
_cleanup_strv_free_ char **v = NULL;
Image *image = userdata;
int r;
r = image_path_lock(image->path, LOCK_SH|LOCK_NB, &tree_global_lock, &tree_local_lock);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to lock image: %m");
switch (image->type) {
case IMAGE_DIRECTORY:
case IMAGE_SUBVOLUME:
r = directory_image_get_os_release(image, &v, error);
break;
case IMAGE_RAW:
r = raw_image_get_os_release(image, &v, error);
break;
default:
assert_not_reached("Unknown image type");
}
if (r < 0)
return r;
return bus_reply_pair_array(message, v);
}
const sd_bus_vtable image_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0),
@@ -296,6 +460,7 @@ const sd_bus_vtable image_vtable[] = {
SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_image_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
+1
View File
@@ -33,3 +33,4 @@ int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_erro
int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_image_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error);
+13 -27
View File
@@ -356,11 +356,11 @@ int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd
return sd_bus_send(NULL, reply, NULL);
}
#define EXIT_NOT_FOUND 2
int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_strv_free_ char **l = NULL;
Machine *m = userdata;
char **k, **v;
int r;
assert(message);
@@ -394,7 +394,7 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
if (child == 0) {
_cleanup_close_ int fd = -1;
int fd = -1;
pair[0] = safe_close(pair[0]);
@@ -402,12 +402,14 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
if (r < 0)
_exit(EXIT_FAILURE);
fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC);
if (fd < 0) {
fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC);
if (fd < 0)
_exit(EXIT_FAILURE);
fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0 && errno == ENOENT) {
fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC|O_NOCTTY);
if (fd < 0 && errno == ENOENT)
_exit(EXIT_NOT_FOUND);
}
if (fd < 0)
_exit(EXIT_FAILURE);
r = copy_bytes(fd, pair[1], (uint64_t) -1, false);
if (r < 0)
@@ -431,6 +433,8 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
r = wait_for_terminate(child, &si);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
if (si.si_code == CLD_EXITED && si.si_status == EXIT_NOT_FOUND)
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Machine does not contain OS release information");
if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
@@ -441,25 +445,7 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines.");
}
r = sd_bus_message_new_method_return(message, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{ss}");
if (r < 0)
return r;
STRV_FOREACH_PAIR(k, v, l) {
r = sd_bus_message_append(reply, "{ss}", *k, *v);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
return bus_reply_pair_array(message, l);
}
int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+2
View File
@@ -42,3 +42,5 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda
int machine_send_signal(Machine *m, bool new_machine);
int machine_send_create_reply(Machine *m, sd_bus_error *error);
int bus_reply_pair_array(sd_bus_message *m, char **l);
+20 -13
View File
@@ -138,7 +138,7 @@ static void clean_machine_info(MachineInfo *machines, size_t n_machines) {
free(machines);
}
static int get_os_release_property(sd_bus *bus, const char *name, const char *query, ...) {
static int call_get_os_release(sd_bus *bus, const char *method, const char *name, const char *query, ...) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *k, *v, *iter, **query_res = NULL;
size_t count = 0, awaited_args = 0;
@@ -153,12 +153,13 @@ static int get_os_release_property(sd_bus *bus, const char *name, const char *qu
awaited_args++;
query_res = newa0(const char *, awaited_args);
r = sd_bus_call_method(bus,
"org.freedesktop.machine1",
"/org/freedesktop/machine1",
"org.freedesktop.machine1.Manager",
"GetMachineOSRelease",
NULL, &reply, "s", name);
r = sd_bus_call_method(
bus,
"org.freedesktop.machine1",
"/org/freedesktop/machine1",
"org.freedesktop.machine1.Manager",
method,
NULL, &reply, "s", name);
if (r < 0)
return r;
@@ -193,7 +194,7 @@ static int get_os_release_property(sd_bus *bus, const char *name, const char *qu
val = strdup(query_res[count]);
if (!val) {
va_end(ap);
return log_oom();
return -ENOMEM;
}
*out = val;
}
@@ -249,8 +250,12 @@ static int list_machines(int argc, char *argv[], void *userdata) {
machines[n_machines].os = NULL;
machines[n_machines].version_id = NULL;
r = get_os_release_property(bus, name,
"ID\0" "VERSION_ID\0",
r = call_get_os_release(
bus,
"GetMachineOSRelease",
name,
"ID\0"
"VERSION_ID\0",
&machines[n_machines].os,
&machines[n_machines].version_id);
if (r < 0)
@@ -610,7 +615,7 @@ static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *p
return 0;
}
static int print_os_release(sd_bus *bus, const char *name, const char *prefix) {
static int print_os_release(sd_bus *bus, const char *method, const char *name, const char *prefix) {
_cleanup_free_ char *pretty = NULL;
int r;
@@ -618,7 +623,7 @@ static int print_os_release(sd_bus *bus, const char *name, const char *prefix) {
assert(name);
assert(prefix);
r = get_os_release_property(bus, name, "PRETTY_NAME\0", &pretty, NULL);
r = call_get_os_release(bus, method, name, "PRETTY_NAME\0", &pretty, NULL);
if (r < 0)
return r;
@@ -729,7 +734,7 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
"\n\t ",
ALL_IP_ADDRESSES);
print_os_release(bus, i->name, "\t OS: ");
print_os_release(bus, "GetMachineOSRelease", i->name, "\t OS: ");
if (i->unit) {
printf("\t Unit: %s\n", i->unit);
@@ -927,6 +932,8 @@ static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
if (i->path)
printf("\t Path: %s\n", i->path);
print_os_release(bus, "GetImageOSRelease", i->name, "\t OS: ");
printf("\t RO: %s%s%s\n",
i->read_only ? ansi_highlight_red() : "",
i->read_only ? "read-only" : "writable",
+52
View File
@@ -825,6 +825,30 @@ static int method_mark_image_read_only(sd_bus_message *message, void *userdata,
return bus_image_method_mark_read_only(message, i, error);
}
static int method_get_image_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_(image_unrefp) Image *i = NULL;
const char *name;
int r;
assert(message);
r = sd_bus_message_read(message, "s", &name);
if (r < 0)
return r;
if (!image_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name);
r = image_find(name, &i);
if (r < 0)
return r;
if (r == 0)
return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name);
i->userdata = userdata;
return bus_image_method_get_os_release(message, i, error);
}
static int clean_pool_done(Operation *operation, int ret, sd_bus_error *error) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_fclose_ FILE *f = NULL;
@@ -1396,6 +1420,7 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("GetImageOSRelease", "s", "a{ss}", method_get_image_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CleanPool", "s", "a(st)", method_clean_pool, SD_BUS_VTABLE_UNPRIVILEGED),
@@ -1804,3 +1829,30 @@ int manager_add_machine(Manager *m, const char *name, Machine **_machine) {
return 0;
}
int bus_reply_pair_array(sd_bus_message *m, char **l) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
char **k, **v;
int r;
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "{ss}");
if (r < 0)
return r;
STRV_FOREACH_PAIR(k, v, l) {
r = sd_bus_message_append(reply, "{ss}", *k, *v);
if (r < 0)
return r;
}
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
@@ -116,6 +116,10 @@
send_interface="org.freedesktop.machine1.Manager"
send_member="SetImageLimit"/>
<allow send_destination="org.freedesktop.machine1"
send_interface="org.freedesktop.machine1.Manager"
send_member="GetImageOSRelease"/>
<allow send_destination="org.freedesktop.machine1"
send_interface="org.freedesktop.machine1.Manager"
send_member="CleanPool"/>
@@ -192,6 +196,10 @@
send_interface="org.freedesktop.machine1.Image"
send_member="MarkReadOnly"/>
<allow send_destination="org.freedesktop.machine1"
send_interface="org.freedesktop.machine1.Image"
send_member="GetOSRelease"/>
<allow receive_sender="org.freedesktop.machine1"/>
</policy>
+140 -657
View File
@@ -53,6 +53,7 @@
#include "cgroup-util.h"
#include "copy.h"
#include "dev-setup.h"
#include "dissect-image.h"
#include "env-util.h"
#include "fd-util.h"
#include "fdset.h"
@@ -60,9 +61,11 @@
#include "format-util.h"
#include "fs-util.h"
#include "gpt.h"
#include "hexdecoct.h"
#include "hostname-util.h"
#include "id128-util.h"
#include "log.h"
#include "loop-util.h"
#include "loopback-setup.h"
#include "machine-image.h"
#include "macro.h"
@@ -198,6 +201,8 @@ static bool arg_notify_ready = false;
static bool arg_use_cgns = true;
static unsigned long arg_clone_ns_flags = CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS;
static MountSettingsMask arg_mount_settings = MOUNT_APPLY_APIVFS_RO;
static void *arg_root_hash = NULL;
static size_t arg_root_hash_size = 0;
static void help(void) {
printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n"
@@ -211,6 +216,7 @@ static void help(void) {
" -x --ephemeral Run container with snapshot of root directory, and\n"
" remove it after exit\n"
" -i --image=PATH File system device or disk image for the container\n"
" --root-hash=HASH Specify verity root hash\n"
" -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n"
" -b --boot Boot up full system (i.e. invoke init)\n"
" --chdir=PATH Set working directory in the container\n"
@@ -422,6 +428,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CHDIR,
ARG_PRIVATE_USERS_CHOWN,
ARG_NOTIFY_READY,
ARG_ROOT_HASH,
};
static const struct option options[] = {
@@ -471,6 +478,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "settings", required_argument, NULL, ARG_SETTINGS },
{ "chdir", required_argument, NULL, ARG_CHDIR },
{ "notify-ready", required_argument, NULL, ARG_NOTIFY_READY },
{ "root-hash", required_argument, NULL, ARG_ROOT_HASH },
{}
};
@@ -1014,6 +1022,25 @@ static int parse_argv(int argc, char *argv[]) {
arg_settings_mask |= SETTING_NOTIFY_READY;
break;
case ARG_ROOT_HASH: {
void *k;
size_t l;
r = unhexmem(optarg, strlen(optarg), &k, &l);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash: %s", optarg);
if (l < sizeof(sd_id128_t)) {
log_error("Root hash must be at least 128bit long: %s", optarg);
free(k);
return -EINVAL;
}
free(arg_root_hash);
arg_root_hash = k;
arg_root_hash_size = l;
break;
}
case '?':
return -EINVAL;
@@ -1771,546 +1798,6 @@ static int setup_propagate(const char *root) {
return mount_verbose(LOG_ERR, NULL, q, NULL, MS_SLAVE, NULL);
}
static int setup_image(char **device_path, int *loop_nr) {
struct loop_info64 info = {
.lo_flags = LO_FLAGS_AUTOCLEAR|LO_FLAGS_PARTSCAN
};
_cleanup_close_ int fd = -1, control = -1, loop = -1;
_cleanup_free_ char* loopdev = NULL;
struct stat st;
int r, nr;
assert(device_path);
assert(loop_nr);
assert(arg_image);
fd = open(arg_image, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY);
if (fd < 0)
return log_error_errno(errno, "Failed to open %s: %m", arg_image);
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", arg_image);
if (S_ISBLK(st.st_mode)) {
char *p;
p = strdup(arg_image);
if (!p)
return log_oom();
*device_path = p;
*loop_nr = -1;
r = fd;
fd = -1;
return r;
}
if (!S_ISREG(st.st_mode)) {
log_error("%s is not a regular file or block device.", arg_image);
return -EINVAL;
}
control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (control < 0)
return log_error_errno(errno, "Failed to open /dev/loop-control: %m");
nr = ioctl(control, LOOP_CTL_GET_FREE);
if (nr < 0)
return log_error_errno(errno, "Failed to allocate loop device: %m");
if (asprintf(&loopdev, "/dev/loop%i", nr) < 0)
return log_oom();
loop = open(loopdev, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY);
if (loop < 0)
return log_error_errno(errno, "Failed to open loop device %s: %m", loopdev);
if (ioctl(loop, LOOP_SET_FD, fd) < 0)
return log_error_errno(errno, "Failed to set loopback file descriptor on %s: %m", loopdev);
if (arg_read_only)
info.lo_flags |= LO_FLAGS_READ_ONLY;
if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0)
return log_error_errno(errno, "Failed to set loopback settings on %s: %m", loopdev);
*device_path = loopdev;
loopdev = NULL;
*loop_nr = nr;
r = loop;
loop = -1;
return r;
}
#define PARTITION_TABLE_BLURB \
"Note that the disk image needs to either contain only a single MBR partition of\n" \
"type 0x83 that is marked bootable, or a single GPT partition of type " \
"0FC63DAF-8483-4772-8E79-3D69D8477DE4 or follow\n" \
" http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/\n" \
"to be bootable with systemd-nspawn."
static int dissect_image(
int fd,
char **root_device, bool *root_device_rw,
char **home_device, bool *home_device_rw,
char **srv_device, bool *srv_device_rw,
char **esp_device,
bool *secondary) {
#ifdef HAVE_BLKID
int home_nr = -1, srv_nr = -1, esp_nr = -1;
#ifdef GPT_ROOT_NATIVE
int root_nr = -1;
#endif
#ifdef GPT_ROOT_SECONDARY
int secondary_root_nr = -1;
#endif
_cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *esp = NULL, *generic = NULL;
_cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
_cleanup_udev_device_unref_ struct udev_device *d = NULL;
_cleanup_blkid_free_probe_ blkid_probe b = NULL;
_cleanup_udev_unref_ struct udev *udev = NULL;
struct udev_list_entry *first, *item;
bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true, generic_rw = true;
bool is_gpt, is_mbr, multiple_generic = false;
const char *pttype = NULL;
blkid_partlist pl;
struct stat st;
unsigned i;
int r;
assert(fd >= 0);
assert(root_device);
assert(home_device);
assert(srv_device);
assert(esp_device);
assert(secondary);
assert(arg_image);
b = blkid_new_probe();
if (!b)
return log_oom();
errno = 0;
r = blkid_probe_set_device(b, fd, 0, 0);
if (r != 0) {
if (errno == 0)
return log_oom();
return log_error_errno(errno, "Failed to set device on blkid probe: %m");
}
blkid_probe_enable_partitions(b, 1);
blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
errno = 0;
r = blkid_do_safeprobe(b);
if (r == -2 || r == 1) {
log_error("Failed to identify any partition table on\n"
" %s\n"
PARTITION_TABLE_BLURB, arg_image);
return -EINVAL;
} else if (r != 0) {
if (errno == 0)
errno = EIO;
return log_error_errno(errno, "Failed to probe: %m");
}
(void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL);
is_gpt = streq_ptr(pttype, "gpt");
is_mbr = streq_ptr(pttype, "dos");
if (!is_gpt && !is_mbr) {
log_error("No GPT or MBR partition table discovered on\n"
" %s\n"
PARTITION_TABLE_BLURB, arg_image);
return -EINVAL;
}
errno = 0;
pl = blkid_probe_get_partitions(b);
if (!pl) {
if (errno == 0)
return log_oom();
log_error("Failed to list partitions of %s", arg_image);
return -errno;
}
udev = udev_new();
if (!udev)
return log_oom();
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to stat block device: %m");
d = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
if (!d)
return log_oom();
for (i = 0;; i++) {
int n, m;
if (i >= 10) {
log_error("Kernel partitions never appeared.");
return -ENXIO;
}
e = udev_enumerate_new(udev);
if (!e)
return log_oom();
r = udev_enumerate_add_match_parent(e, d);
if (r < 0)
return log_oom();
r = udev_enumerate_scan_devices(e);
if (r < 0)
return log_error_errno(r, "Failed to scan for partition devices of %s: %m", arg_image);
/* Count the partitions enumerated by the kernel */
n = 0;
first = udev_enumerate_get_list_entry(e);
udev_list_entry_foreach(item, first)
n++;
/* Count the partitions enumerated by blkid */
m = blkid_partlist_numof_partitions(pl);
if (n == m + 1)
break;
if (n > m + 1) {
log_error("blkid and kernel partition list do not match.");
return -EIO;
}
if (n < m + 1) {
unsigned j;
/* The kernel has probed fewer partitions than
* blkid? Maybe the kernel prober is still
* running or it got EBUSY because udev
* already opened the device. Let's reprobe
* the device, which is a synchronous call
* that waits until probing is complete. */
for (j = 0; j < 20; j++) {
r = ioctl(fd, BLKRRPART, 0);
if (r < 0)
r = -errno;
if (r >= 0 || r != -EBUSY)
break;
/* If something else has the device
* open, such as an udev rule, the
* ioctl will return EBUSY. Since
* there's no way to wait until it
* isn't busy anymore, let's just wait
* a bit, and try again.
*
* This is really something they
* should fix in the kernel! */
usleep(50 * USEC_PER_MSEC);
}
if (r < 0)
return log_error_errno(r, "Failed to reread partition table: %m");
}
e = udev_enumerate_unref(e);
}
first = udev_enumerate_get_list_entry(e);
udev_list_entry_foreach(item, first) {
_cleanup_udev_device_unref_ struct udev_device *q;
const char *node;
unsigned long long flags;
blkid_partition pp;
dev_t qn;
int nr;
errno = 0;
q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
if (!q) {
if (!errno)
errno = ENOMEM;
return log_error_errno(errno, "Failed to get partition device of %s: %m", arg_image);
}
qn = udev_device_get_devnum(q);
if (major(qn) == 0)
continue;
if (st.st_rdev == qn)
continue;
node = udev_device_get_devnode(q);
if (!node)
continue;
pp = blkid_partlist_devno_to_partition(pl, qn);
if (!pp)
continue;
flags = blkid_partition_get_flags(pp);
nr = blkid_partition_get_partno(pp);
if (nr < 0)
continue;
if (is_gpt) {
sd_id128_t type_id;
const char *stype;
if (flags & GPT_FLAG_NO_AUTO)
continue;
stype = blkid_partition_get_type_string(pp);
if (!stype)
continue;
if (sd_id128_from_string(stype, &type_id) < 0)
continue;
if (sd_id128_equal(type_id, GPT_HOME)) {
if (home && nr >= home_nr)
continue;
home_nr = nr;
home_rw = !(flags & GPT_FLAG_READ_ONLY);
r = free_and_strdup(&home, node);
if (r < 0)
return log_oom();
} else if (sd_id128_equal(type_id, GPT_SRV)) {
if (srv && nr >= srv_nr)
continue;
srv_nr = nr;
srv_rw = !(flags & GPT_FLAG_READ_ONLY);
r = free_and_strdup(&srv, node);
if (r < 0)
return log_oom();
} else if (sd_id128_equal(type_id, GPT_ESP)) {
if (esp && nr >= esp_nr)
continue;
esp_nr = nr;
r = free_and_strdup(&esp, node);
if (r < 0)
return log_oom();
}
#ifdef GPT_ROOT_NATIVE
else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
if (root && nr >= root_nr)
continue;
root_nr = nr;
root_rw = !(flags & GPT_FLAG_READ_ONLY);
r = free_and_strdup(&root, node);
if (r < 0)
return log_oom();
}
#endif
#ifdef GPT_ROOT_SECONDARY
else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) {
if (secondary_root && nr >= secondary_root_nr)
continue;
secondary_root_nr = nr;
secondary_root_rw = !(flags & GPT_FLAG_READ_ONLY);
r = free_and_strdup(&secondary_root, node);
if (r < 0)
return log_oom();
}
#endif
else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) {
if (generic)
multiple_generic = true;
else {
generic_rw = !(flags & GPT_FLAG_READ_ONLY);
r = free_and_strdup(&generic, node);
if (r < 0)
return log_oom();
}
}
} else if (is_mbr) {
int type;
if (flags != 0x80) /* Bootable flag */
continue;
type = blkid_partition_get_type(pp);
if (type != 0x83) /* Linux partition */
continue;
if (generic)
multiple_generic = true;
else {
generic_rw = true;
r = free_and_strdup(&root, node);
if (r < 0)
return log_oom();
}
}
}
if (root) {
*root_device = root;
root = NULL;
*root_device_rw = root_rw;
*secondary = false;
} else if (secondary_root) {
*root_device = secondary_root;
secondary_root = NULL;
*root_device_rw = secondary_root_rw;
*secondary = true;
} else if (generic) {
/* There were no partitions with precise meanings
* around, but we found generic partitions. In this
* case, if there's only one, we can go ahead and boot
* it, otherwise we bail out, because we really cannot
* make any sense of it. */
if (multiple_generic) {
log_error("Identified multiple bootable Linux partitions on\n"
" %s\n"
PARTITION_TABLE_BLURB, arg_image);
return -EINVAL;
}
*root_device = generic;
generic = NULL;
*root_device_rw = generic_rw;
*secondary = false;
} else {
log_error("Failed to identify root partition in disk image\n"
" %s\n"
PARTITION_TABLE_BLURB, arg_image);
return -EINVAL;
}
if (home) {
*home_device = home;
home = NULL;
*home_device_rw = home_rw;
}
if (srv) {
*srv_device = srv;
srv = NULL;
*srv_device_rw = srv_rw;
}
if (esp) {
*esp_device = esp;
esp = NULL;
}
return 0;
#else
log_error("--image= is not supported, compiled without blkid support.");
return -EOPNOTSUPP;
#endif
}
static int mount_device(const char *what, const char *where, const char *directory, bool rw) {
#ifdef HAVE_BLKID
_cleanup_blkid_free_probe_ blkid_probe b = NULL;
const char *fstype, *p, *options;
int r;
assert(what);
assert(where);
if (arg_read_only)
rw = false;
if (directory)
p = strjoina(where, directory);
else
p = where;
errno = 0;
b = blkid_new_probe_from_filename(what);
if (!b) {
if (errno == 0)
return log_oom();
return log_error_errno(errno, "Failed to allocate prober for %s: %m", what);
}
blkid_probe_enable_superblocks(b, 1);
blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
errno = 0;
r = blkid_do_safeprobe(b);
if (r == -1 || r == 1) {
log_error("Cannot determine file system type of %s", what);
return -EINVAL;
} else if (r != 0) {
if (errno == 0)
errno = EIO;
return log_error_errno(errno, "Failed to probe %s: %m", what);
}
errno = 0;
if (blkid_probe_lookup_value(b, "TYPE", &fstype, NULL) < 0) {
if (errno == 0)
errno = EINVAL;
log_error("Failed to determine file system type of %s", what);
return -errno;
}
if (streq(fstype, "crypto_LUKS")) {
log_error("nspawn currently does not support LUKS disk images.");
return -EOPNOTSUPP;
}
/* If this is a loopback device then let's mount the image with discard, so that the underlying file remains
* sparse when possible. */
if (STR_IN_SET(fstype, "btrfs", "ext4", "vfat", "xfs")) {
const char *l;
l = path_startswith(what, "/dev");
if (l && startswith(l, "loop"))
options = "discard";
}
return mount_verbose(LOG_ERR, what, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options);
#else
log_error("--image= is not supported, compiled without blkid support.");
return -EOPNOTSUPP;
#endif
}
static int setup_machine_id(const char *directory) {
const char *etc_machine_id;
sd_id128_t id;
@@ -2370,83 +1857,6 @@ static int recursive_chown(const char *directory, uid_t shift, uid_t range) {
return r;
}
static int mount_devices(
const char *where,
const char *root_device, bool root_device_rw,
const char *home_device, bool home_device_rw,
const char *srv_device, bool srv_device_rw,
const char *esp_device) {
int r;
assert(where);
if (root_device) {
r = mount_device(root_device, arg_directory, NULL, root_device_rw);
if (r < 0)
return log_error_errno(r, "Failed to mount root directory: %m");
}
if (home_device) {
r = mount_device(home_device, arg_directory, "/home", home_device_rw);
if (r < 0)
return log_error_errno(r, "Failed to mount home directory: %m");
}
if (srv_device) {
r = mount_device(srv_device, arg_directory, "/srv", srv_device_rw);
if (r < 0)
return log_error_errno(r, "Failed to mount server data directory: %m");
}
if (esp_device) {
const char *mp, *x;
/* Mount the ESP to /efi if it exists and is empty. If it doesn't exist, use /boot instead. */
mp = "/efi";
x = strjoina(arg_directory, mp);
r = dir_is_empty(x);
if (r == -ENOENT) {
mp = "/boot";
x = strjoina(arg_directory, mp);
r = dir_is_empty(x);
}
if (r > 0) {
r = mount_device(esp_device, arg_directory, mp, true);
if (r < 0)
return log_error_errno(r, "Failed to mount ESP: %m");
}
}
return 0;
}
static void loop_remove(int nr, int *image_fd) {
_cleanup_close_ int control = -1;
int r;
if (nr < 0)
return;
if (image_fd && *image_fd >= 0) {
r = ioctl(*image_fd, LOOP_CLR_FD);
if (r < 0)
log_debug_errno(errno, "Failed to close loop image: %m");
*image_fd = safe_close(*image_fd);
}
control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (control < 0) {
log_warning_errno(errno, "Failed to open /dev/loop-control: %m");
return;
}
r = ioctl(control, LOOP_CTL_REMOVE, nr);
if (r < 0)
log_debug_errno(errno, "Failed to remove loop %d: %m", nr);
}
/*
* Return values:
* < 0 : wait_for_terminate() failed to get the state of the
@@ -2568,10 +1978,22 @@ static int determine_names(void) {
}
if (!arg_machine) {
if (arg_directory && path_equal(arg_directory, "/"))
arg_machine = gethostname_malloc();
else
arg_machine = strdup(basename(arg_image ?: arg_directory));
else {
if (arg_image) {
char *e;
arg_machine = strdup(basename(arg_image));
/* Truncate suffix if there is one */
e = endswith(arg_machine, ".raw");
if (e)
*e = 0;
} else
arg_machine = strdup(basename(arg_directory));
}
if (!arg_machine)
return log_oom();
@@ -2921,10 +2343,7 @@ static int outer_child(
Barrier *barrier,
const char *directory,
const char *console,
const char *root_device, bool root_device_rw,
const char *home_device, bool home_device_rw,
const char *srv_device, bool srv_device_rw,
const char *esp_device,
DissectedImage *dissected_image,
bool interactive,
bool secondary,
int pid_socket,
@@ -2984,13 +2403,11 @@ static int outer_child(
if (r < 0)
return r;
r = mount_devices(directory,
root_device, root_device_rw,
home_device, home_device_rw,
srv_device, srv_device_rw,
esp_device);
if (r < 0)
return r;
if (dissected_image) {
r = dissected_image_mount(dissected_image, directory, DISSECT_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECT_IMAGE_READ_ONLY : 0));
if (r < 0)
return r;
}
r = determine_uid_shift(directory);
if (r < 0)
@@ -3607,10 +3024,7 @@ static int load_settings(void) {
static int run(int master,
const char* console,
const char *root_device, bool root_device_rw,
const char *home_device, bool home_device_rw,
const char *srv_device, bool srv_device_rw,
const char *esp_device,
DissectedImage *dissected_image,
bool interactive,
bool secondary,
FDSet *fds,
@@ -3717,10 +3131,7 @@ static int run(int master,
r = outer_child(&barrier,
arg_directory,
console,
root_device, root_device_rw,
home_device, home_device_rw,
srv_device, srv_device_rw,
esp_device,
dissected_image,
interactive,
secondary,
pid_socket_pair[1],
@@ -4025,13 +3436,59 @@ static int run(int master,
return 1; /* loop again */
}
static int load_root_hash(const char *image) {
_cleanup_free_ char *text = NULL;
char *fn, *n, *e;
void *k;
size_t l;
int r;
assert_se(image);
/* Try to load the root hash from a file next to the image file if it exists. */
if (arg_root_hash)
return 0;
fn = new(char, strlen(image) + strlen(".roothash") + 1);
if (!fn)
return log_oom();
n = stpcpy(fn, image);
e = endswith(fn, ".raw");
if (e)
n = e;
strcpy(n, ".roothash");
r = read_one_line_file(fn, &text);
if (r == -ENOENT)
return 0;
if (r < 0) {
log_warning_errno(r, "Failed to read %s, ignoring: %m", fn);
return 0;
}
r = unhexmem(text, strlen(text), &k, &l);
if (r < 0)
return log_error_errno(r, "Invalid root hash: %s", text);
if (l < sizeof(sd_id128_t)) {
free(k);
return log_error_errno(r, "Root hash too short: %s", text);
}
arg_root_hash = k;
arg_root_hash_size = l;
return 0;
}
int main(int argc, char *argv[]) {
_cleanup_free_ char *device_path = NULL, *root_device = NULL, *home_device = NULL, *srv_device = NULL, *esp_device = NULL, *console = NULL;
bool root_device_rw = true, home_device_rw = true, srv_device_rw = true;
_cleanup_close_ int master = -1, image_fd = -1;
_cleanup_free_ char *console = NULL;
_cleanup_close_ int master = -1;
_cleanup_fdset_free_ FDSet *fds = NULL;
int r, n_fd_passed, loop_nr = -1, ret = EXIT_SUCCESS;
int r, n_fd_passed, ret = EXIT_SUCCESS;
char veth_name[IFNAMSIZ] = "";
bool secondary = false, remove_directory = false, remove_image = false;
pid_t pid = 0;
@@ -4039,6 +3496,9 @@ int main(int argc, char *argv[]) {
_cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
bool interactive, veth_created = false, remove_tmprootdir = false;
char tmprootdir[] = "/tmp/nspawn-root-XXXXXX";
_cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
log_parse_environment();
log_open();
@@ -4237,6 +3697,10 @@ int main(int argc, char *argv[]) {
r = log_error_errno(r, "Failed to create image lock: %m");
goto finish;
}
r = load_root_hash(arg_image);
if (r < 0)
goto finish;
}
if (!mkdtemp(tmprootdir)) {
@@ -4252,18 +3716,41 @@ int main(int argc, char *argv[]) {
goto finish;
}
image_fd = setup_image(&device_path, &loop_nr);
if (image_fd < 0) {
r = image_fd;
r = loop_device_make_by_path(arg_image, arg_read_only ? O_RDONLY : O_RDWR, &loop);
if (r < 0) {
log_error_errno(r, "Failed to set up loopback block device: %m");
goto finish;
}
r = dissect_image(image_fd,
&root_device, &root_device_rw,
&home_device, &home_device_rw,
&srv_device, &srv_device_rw,
&esp_device,
&secondary);
r = dissect_image(loop->fd, arg_root_hash, arg_root_hash_size, &dissected_image);
if (r == -ENOPKG) {
log_error_errno(r, "Could not find a suitable file system or partition table in image: %s", arg_image);
log_notice("Note that the disk image needs to\n"
" a) either contain only a single MBR partition of type 0x83 that is marked bootable\n"
" b) or contain a single GPT partition of type 0FC63DAF-8483-4772-8E79-3D69D8477DE4\n"
" c) or follow http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/\n"
" d) or contain a file system without a partition table\n"
"in order to be bootable with systemd-nspawn.");
goto finish;
}
if (r == -EADDRNOTAVAIL) {
log_error_errno(r, "No root partition for specified root hash found.");
goto finish;
}
if (r == -EOPNOTSUPP) {
log_error_errno(r, "--image= is not supported, compiled without blkid support.");
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to dissect image: %m");
goto finish;
}
if (!arg_root_hash && dissected_image->can_verity)
log_notice("Note: image %s contains verity information, but no root hash specified! Proceeding without integrity checking.", arg_image);
r = dissected_image_decrypt_interactively(dissected_image, NULL, arg_root_hash, arg_root_hash_size, 0, &decrypted_image);
if (r < 0)
goto finish;
@@ -4317,10 +3804,7 @@ int main(int argc, char *argv[]) {
for (;;) {
r = run(master,
console,
root_device, root_device_rw,
home_device, home_device_rw,
srv_device, srv_device_rw,
esp_device,
dissected_image,
interactive, secondary,
fds,
veth_name, &veth_created,
@@ -4347,8 +3831,6 @@ finish:
if (pid > 0)
(void) wait_for_terminate(pid, NULL);
loop_remove(loop_nr, &image_fd);
if (remove_directory && arg_directory) {
int k;
@@ -4395,6 +3877,7 @@ finish:
strv_free(arg_parameters);
custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts);
expose_port_free_all(arg_expose_ports);
free(arg_root_hash);
return r < 0 ? EXIT_FAILURE : ret;
}
File diff suppressed because it is too large Load Diff
+90
View File
@@ -0,0 +1,90 @@
#pragma once
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <stdbool.h>
#include "macro.h"
typedef struct DissectedImage DissectedImage;
typedef struct DissectedPartition DissectedPartition;
typedef struct DecryptedImage DecryptedImage;
struct DissectedPartition {
bool found:1;
bool rw:1;
int partno; /* -1 if there was no partition and the images contains a file system directly */
int architecture; /* Intended architecture: either native, secondary or unset (-1). */
char *fstype;
char *node;
char *decrypted_node;
char *decrypted_fstype;
};
enum {
PARTITION_ROOT,
PARTITION_ROOT_SECONDARY, /* Secondary architecture */
PARTITION_HOME,
PARTITION_SRV,
PARTITION_ESP,
PARTITION_SWAP,
PARTITION_ROOT_VERITY, /* verity data for the PARTITION_ROOT partition */
PARTITION_ROOT_SECONDARY_VERITY, /* verity data for the PARTITION_ROOT_SECONDARY partition */
_PARTITION_DESIGNATOR_MAX,
_PARTITION_DESIGNATOR_INVALID = -1
};
static inline int PARTITION_VERITY_OF(int p) {
if (p == PARTITION_ROOT)
return PARTITION_ROOT_VERITY;
if (p == PARTITION_ROOT_SECONDARY)
return PARTITION_ROOT_SECONDARY_VERITY;
return _PARTITION_DESIGNATOR_INVALID;
}
typedef enum DissectImageFlags {
DISSECT_IMAGE_READ_ONLY = 1,
DISSECT_IMAGE_DISCARD_ON_LOOP = 2, /* Turn on "discard" if on loop device and file system supports it */
DISSECT_IMAGE_DISCARD = 4, /* Turn on "discard" if file system supports it, on all block devices */
DISSECT_IMAGE_DISCARD_ON_CRYPTO = 8, /* Turn on "discard" also on crypto devices */
} DissectImageFlags;
struct DissectedImage {
bool encrypted:1;
bool verity:1; /* verity available and usable */
bool can_verity:1; /* verity available, but not necessarily used */
DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX];
};
int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectedImage **ret);
DissectedImage* dissected_image_unref(DissectedImage *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref);
int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_mount(DissectedImage *m, const char *dest, DissectImageFlags flags);
DecryptedImage* decrypted_image_unref(DecryptedImage *p);
DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref);
int decrypted_image_relinquish(DecryptedImage *d);
const char* partition_designator_to_string(int i) _const_;
int partition_designator_from_string(const char *name) _pure_;
+16 -1
View File
@@ -32,28 +32,43 @@
#define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3)
#define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae)
#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97)
#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b)
#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f)
#define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15)
#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
/* Verity partitions for the root partitions above (we only define them for the root partitions, because only they are
* are commonly read-only and hence suitable for verity). */
#define GPT_ROOT_X86_VERITY SD_ID128_MAKE(d1,3c,5d,3b,b5,d1,42,2a,b2,9f,94,54,fd,c8,9d,76)
#define GPT_ROOT_X86_64_VERITY SD_ID128_MAKE(2c,73,57,ed,eb,d2,46,d9,ae,c1,23,d4,37,ec,2b,f5)
#define GPT_ROOT_ARM_VERITY SD_ID128_MAKE(73,86,cd,f2,20,3c,47,a9,a4,98,f2,ec,ce,45,a2,d6)
#define GPT_ROOT_ARM_64_VERITY SD_ID128_MAKE(df,33,00,ce,d6,9f,4c,92,97,8c,9b,fb,0f,38,d8,20)
#define GPT_ROOT_IA64_VERITY SD_ID128_MAKE(86,ed,10,d5,b6,07,45,bb,89,57,d3,50,f2,3d,05,71)
#if defined(__x86_64__)
# define GPT_ROOT_NATIVE GPT_ROOT_X86_64
# define GPT_ROOT_SECONDARY GPT_ROOT_X86
# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_X86_64_VERITY
# define GPT_ROOT_SECONDARY_VERITY GPT_ROOT_X86_VERITY
#elif defined(__i386__)
# define GPT_ROOT_NATIVE GPT_ROOT_X86
# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_X86_VERITY
#endif
#if defined(__ia64__)
# define GPT_ROOT_NATIVE GPT_ROOT_IA64
# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_IA64_VERITY
#endif
#if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN)
# define GPT_ROOT_NATIVE GPT_ROOT_ARM_64
# define GPT_ROOT_SECONDARY GPT_ROOT_ARM
# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_ARM_64_VERITY
# define GPT_ROOT_SECONDARY_VERITY GPT_ROOT_ARM_VERITY
#elif defined(__arm__) && (__BYTE_ORDER != __BIG_ENDIAN)
# define GPT_ROOT_NATIVE GPT_ROOT_ARM
# define GPT_ROOT_NATIVE_VERITY GPT_ROOT_ARM_VERITY
#endif
/* Flags we recognize on the root, swap, home and srv partitions when
+166
View File
@@ -0,0 +1,166 @@
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <fcntl.h>
#include <linux/loop.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "alloc-util.h"
#include "fd-util.h"
#include "loop-util.h"
int loop_device_make(int fd, int open_flags, LoopDevice **ret) {
const struct loop_info64 info = {
.lo_flags = LO_FLAGS_AUTOCLEAR|LO_FLAGS_PARTSCAN|(open_flags == O_RDONLY ? LO_FLAGS_READ_ONLY : 0),
};
_cleanup_close_ int control = -1, loop = -1;
_cleanup_free_ char *loopdev = NULL;
struct stat st;
LoopDevice *d;
int nr;
assert(fd >= 0);
assert(ret);
assert(IN_SET(open_flags, O_RDWR, O_RDONLY));
if (fstat(fd, &st) < 0)
return -errno;
if (S_ISBLK(st.st_mode)) {
int copy;
/* If this is already a block device, store a copy of the fd as it is */
copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
if (copy < 0)
return -errno;
d = new0(LoopDevice, 1);
if (!d)
return -ENOMEM;
*d = (LoopDevice) {
.fd = copy,
.nr = -1,
};
*ret = d;
return 0;
}
if (!S_ISREG(st.st_mode))
return -EINVAL;
control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (control < 0)
return -errno;
nr = ioctl(control, LOOP_CTL_GET_FREE);
if (nr < 0)
return -errno;
if (asprintf(&loopdev, "/dev/loop%i", nr) < 0)
return -ENOMEM;
loop = open(loopdev, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags);
if (loop < 0)
return -errno;
if (ioctl(loop, LOOP_SET_FD, fd) < 0)
return -errno;
if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0)
return -errno;
d = new(LoopDevice, 1);
if (!d)
return -ENOMEM;
*d = (LoopDevice) {
.fd = loop,
.node = loopdev,
.nr = nr,
};
loop = -1;
loopdev = NULL;
*ret = d;
return (*ret)->fd;
}
int loop_device_make_by_path(const char *path, int open_flags, LoopDevice **ret) {
_cleanup_close_ int fd = -1;
assert(path);
assert(ret);
assert(IN_SET(open_flags, O_RDWR, O_RDONLY));
fd = open(path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags);
if (fd < 0)
return -errno;
return loop_device_make(fd, open_flags, ret);
}
LoopDevice* loop_device_unref(LoopDevice *d) {
if (!d)
return NULL;
if (d->fd >= 0) {
if (d->nr >= 0 && !d->relinquished) {
if (ioctl(d->fd, LOOP_CLR_FD) < 0)
log_debug_errno(errno, "Failed to clear loop device: %m");
}
safe_close(d->fd);
}
if (d->nr >= 0 && !d->relinquished) {
_cleanup_close_ int control = -1;
control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
if (control < 0)
log_debug_errno(errno, "Failed to open loop control device: %m");
else {
if (ioctl(control, LOOP_CTL_REMOVE, d->nr) < 0)
log_debug_errno(errno, "Failed to remove loop device: %m");
}
}
free(d->node);
free(d);
return NULL;
}
void loop_device_relinquish(LoopDevice *d) {
assert(d);
/* Don't attempt to clean up the loop device anymore from this point on. Leave the clean-ing up to the kernel
* itself, using the loop device "auto-clear" logic we already turned on when creating the device. */
d->relinquished = true;
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "macro.h"
typedef struct LoopDevice LoopDevice;
/* Some helpers for setting up loopback block devices */
struct LoopDevice {
int fd;
int nr;
char *node;
bool relinquished;
};
int loop_device_make(int fd, int open_flags, LoopDevice **ret);
int loop_device_make_by_path(const char *path, int open_flags, LoopDevice **ret);
LoopDevice* loop_device_unref(LoopDevice *d);
DEFINE_TRIVIAL_CLEANUP_FUNC(LoopDevice*, loop_device_unref);
void loop_device_relinquish(LoopDevice *d);
+66
View File
@@ -0,0 +1,66 @@
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <fcntl.h>
#include <stdio.h>
#include "dissect-image.h"
#include "log.h"
#include "loop-util.h"
#include "string-util.h"
int main(int argc, char *argv[]) {
_cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
_cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
int r, i;
log_set_max_level(LOG_DEBUG);
if (argc < 2) {
log_error("Requires one command line argument.");
return EXIT_FAILURE;
}
r = loop_device_make_by_path(argv[1], O_RDONLY, &d);
if (r < 0) {
log_error_errno(r, "Failed to set up loopback device: %m");
return EXIT_FAILURE;
}
r = dissect_image(d->fd, NULL, 0, &m);
if (r < 0) {
log_error_errno(r, "Failed to dissect image: %m");
return EXIT_FAILURE;
}
for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
if (!m->partitions[i].found)
continue;
printf("Found %s partition, %s of type %s at #%i (%s)\n",
partition_designator_to_string(i),
m->partitions[i].rw ? "writable" : "read-only",
strna(m->partitions[i].fstype),
m->partitions[i].partno,
strna(m->partitions[i].node));
}
return EXIT_SUCCESS;
}