diff --git a/.gitignore b/.gitignore index 21fcf9841c..405b07ef4d 100644 --- a/.gitignore +++ b/.gitignore @@ -180,6 +180,7 @@ /test-dhcp-option /test-dhcp-server /test-dhcp6-client +/test-dissect-image /test-dns-domain /test-dns-packet /test-dnssec diff --git a/Makefile.am b/Makefile.am index 124e1867cd..c47a07a1f0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1042,6 +1042,8 @@ libshared_la_SOURCES = \ src/shared/machine-image.h \ src/shared/machine-pool.c \ src/shared/machine-pool.h \ + src/shared/loop-util.c \ + src/shared/loop-util.h \ src/shared/resolve-util.c \ src/shared/resolve-util.h \ src/shared/bus-unit-util.c \ @@ -1053,7 +1055,9 @@ libshared_la_SOURCES = \ src/shared/fdset.c \ src/shared/fdset.h \ src/shared/nsflags.h \ - src/shared/nsflags.c + src/shared/nsflags.c \ + src/shared/dissect-image.c \ + src/shared/dissect-image.h if HAVE_UTMP libshared_la_SOURCES += \ @@ -1076,7 +1080,8 @@ libshared_la_CFLAGS = \ $(AM_CFLAGS) \ $(ACL_CFLAGS) \ $(LIBIDN_CFLAGS) \ - $(SECCOMP_CFLAGS) + $(SECCOMP_CFLAGS) \ + $(BLKID_CFLAGS) libshared_la_LIBADD = \ libsystemd-internal.la \ @@ -1085,7 +1090,8 @@ libshared_la_LIBADD = \ libudev-internal.la \ $(ACL_LIBS) \ $(LIBIDN_LIBS) \ - $(SECCOMP_LIBS) + $(SECCOMP_LIBS) \ + $(BLKID_LIBS) rootlibexec_LTLIBRARIES += \ libsystemd-shared.la @@ -1107,6 +1113,7 @@ libsystemd_shared_la_CFLAGS = \ $(ACL_CFLAGS) \ $(LIBIDN_CFLAGS) \ $(SECCOMP_CFLAGS) \ + $(BLKID_CFLAGS) \ -fvisibility=default # We can't use libshared_la_LIBADD here because it would @@ -1118,7 +1125,8 @@ libsystemd_shared_la_LIBADD = \ $(libudev_internal_la_LIBADD) \ $(ACL_LIBS) \ $(LIBIDN_LIBS) \ - $(SECCOMP_LIBS) + $(SECCOMP_LIBS) \ + $(BLKID_LIBS) libsystemd_shared_la_LDFLAGS = \ $(AM_LDFLAGS) \ @@ -1456,7 +1464,8 @@ manual_tests += \ test-btrfs \ test-acd \ test-ipv4ll-manual \ - test-ask-password-api + test-ask-password-api \ + test-dissect-image unsafe_tests = \ test-hostname \ @@ -2067,6 +2076,17 @@ test_ask_password_api_SOURCES = \ test_ask_password_api_LDADD = \ libsystemd-shared.la +test_dissect_image_SOURCES = \ + src/test/test-dissect-image.c + +test_dissect_image_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) + +test_dissect_image_LDADD = \ + libsystemd-shared.la \ + $(BLKID_LIBS) + test_signal_util_SOURCES = \ src/test/test-signal-util.c diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c new file mode 100644 index 0000000000..7b65daa0eb --- /dev/null +++ b/src/shared/dissect-image.c @@ -0,0 +1,548 @@ +/*** + 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 . +***/ + +#include + +#include "architecture.h" +#include "blkid-util.h" +#include "dissect-image.h" +#include "gpt.h" +#include "mount-util.h" +#include "path-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "udev-util.h" + +int dissect_image(int fd, DissectedImage **ret) { + +#ifdef HAVE_BLKID + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + bool is_gpt, is_mbr, generic_rw, multiple_generic = false; + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_free_ char *generic_node = NULL; + const char *pttype = NULL, *usage = NULL; + struct udev_list_entry *first, *item; + blkid_partlist pl; + int r, generic_nr; + struct stat st; + unsigned i; + + assert(fd >= 0); + assert(ret); + + /* Probes a disk image, and returns information about what it found in *ret. + * + * Returns -ENOPKG if no suitable partition table or file system could be found. */ + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISBLK(st.st_mode)) + return -ENOTBLK; + + b = blkid_new_probe(); + if (!b) + return -ENOMEM; + + errno = 0; + r = blkid_probe_set_device(b, fd, 0, 0); + if (r != 0) { + if (errno == 0) + return -ENOMEM; + + return -errno; + } + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_USAGE); + 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_debug("Failed to identify any partition table."); + return -ENOPKG; + } + if (r != 0) { + if (errno == 0) + return -EIO; + + return -errno; + } + + m = new0(DissectedImage, 1); + if (!m) + return -ENOMEM; + + (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL); + if (streq_ptr(usage, "filesystem")) { + _cleanup_free_ char *t = NULL, *n = NULL; + const char *fstype = NULL; + + /* OK, we have found a file system, that's our root partition then. */ + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + + if (fstype) { + t = strdup(fstype); + if (!t) + return -ENOMEM; + } + + if (asprintf(&n, "/dev/block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) + return -ENOMEM; + + m->partitions[PARTITION_ROOT] = (DissectedPartition) { + .found = true, + .rw = true, + .partno = -1, + .architecture = _ARCHITECTURE_INVALID, + .fstype = t, + .node = n, + }; + + t = n = NULL; + + *ret = m; + m = NULL; + + return 0; + } + + (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); + if (!pttype) + return -ENOPKG; + + is_gpt = streq_ptr(pttype, "gpt"); + is_mbr = streq_ptr(pttype, "dos"); + + if (!is_gpt && !is_mbr) + return -ENOPKG; + + errno = 0; + pl = blkid_probe_get_partitions(b); + if (!pl) { + if (errno == 0) + return -ENOMEM; + + return -errno; + } + + udev = udev_new(); + if (!udev) + return -errno; + + d = udev_device_new_from_devnum(udev, 'b', st.st_rdev); + if (!d) + return -ENOMEM; + + for (i = 0;; i++) { + int n, z; + + if (i >= 10) { + log_debug("Kernel partitions never appeared."); + return -ENXIO; + } + + e = udev_enumerate_new(udev); + if (!e) + return -errno; + + r = udev_enumerate_add_match_parent(e, d); + if (r < 0) + return r; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return r; + + /* 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 */ + z = blkid_partlist_numof_partitions(pl); + if (n == z + 1) + break; + if (n > z + 1) { + log_debug("blkid and kernel partition list do not match."); + return -EIO; + } + if (n < z + 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 r; + } + + 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; + unsigned long long flags; + blkid_partition pp; + const char *node; + dev_t qn; + int nr; + + q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!q) + return -errno; + + 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) { + int designator = _PARTITION_DESIGNATOR_INVALID, architecture = _ARCHITECTURE_INVALID; + const char *stype, *fstype = NULL; + sd_id128_t type_id; + bool rw = true; + + 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)) { + designator = PARTITION_HOME; + rw = !(flags & GPT_FLAG_READ_ONLY); + } else if (sd_id128_equal(type_id, GPT_SRV)) { + designator = PARTITION_SRV; + rw = !(flags & GPT_FLAG_READ_ONLY); + } else if (sd_id128_equal(type_id, GPT_ESP)) { + designator = PARTITION_ESP; + fstype = "vfat"; + } +#ifdef GPT_ROOT_NATIVE + else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { + designator = PARTITION_ROOT; + architecture = native_architecture(); + rw = !(flags & GPT_FLAG_READ_ONLY); + } +#endif +#ifdef GPT_ROOT_SECONDARY + else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) { + designator = PARTITION_ROOT_SECONDARY; + architecture = SECONDARY_ARCHITECTURE; + rw = !(flags & GPT_FLAG_READ_ONLY); + } +#endif + else if (sd_id128_equal(type_id, GPT_SWAP)) { + designator = PARTITION_SWAP; + fstype = "swap"; + } else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) { + + if (generic_node) + multiple_generic = true; + else { + generic_nr = nr; + generic_rw = !(flags & GPT_FLAG_READ_ONLY); + generic_node = strdup(node); + if (!generic_node) + return -ENOMEM; + } + } + + if (designator != _PARTITION_DESIGNATOR_INVALID) { + _cleanup_free_ char *t = NULL, *n = NULL; + + /* First one wins */ + if (m->partitions[designator].found) + continue; + + if (fstype) { + t = strdup(fstype); + if (!t) + return -ENOMEM; + } + + n = strdup(node); + if (!n) + return -ENOMEM; + + m->partitions[designator] = (DissectedPartition) { + .found = true, + .partno = nr, + .rw = rw, + .architecture = architecture, + .node = n, + .fstype = t, + }; + + n = t = NULL; + } + + } else if (is_mbr) { + + if (flags != 0x80) /* Bootable flag */ + continue; + + if (blkid_partition_get_type(pp) != 0x83) /* Linux partition */ + continue; + + if (generic_node) + multiple_generic = true; + else { + generic_nr = nr; + generic_rw = true; + generic_node = strdup(node); + if (!generic_node) + return -ENOMEM; + } + } + } + + if (!m->partitions[PARTITION_ROOT].found) { + /* No root partition found? Then let's see if ther's one for the secondary architecture. And if not + * either, then check if there's a single generic one, and use that. */ + + if (m->partitions[PARTITION_ROOT_SECONDARY].found) { + m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY]; + zero(m->partitions[PARTITION_ROOT_SECONDARY]); + } else if (generic_node) { + + if (multiple_generic) + return -ENOTUNIQ; + + m->partitions[PARTITION_ROOT] = (DissectedPartition) { + .found = true, + .rw = generic_rw, + .partno = generic_nr, + .architecture = _ARCHITECTURE_INVALID, + .node = generic_node, + }; + + generic_node = NULL; + } else + return -ENXIO; + } + + /* Fill in file system types if we don't know them yet. */ + for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { + const char *fstype; + + if (!m->partitions[i].found) /* not found? */ + continue; + + if (m->partitions[i].fstype) /* already know the type? */ + continue; + + if (!m->partitions[i].node) /* have no device node for? */ + continue; + + if (b) + blkid_free_probe(b); + + b = blkid_new_probe_from_filename(m->partitions[i].node); + if (!b) + return -ENOMEM; + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2 || r == 1) { + log_debug("Failed to identify any partition type on partition %i", m->partitions[i].partno); + continue; + } + if (r != 0) { + if (errno == 0) + return -EIO; + + return -errno; + } + + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + if (fstype) { + char *t; + + t = strdup(fstype); + if (!t) + return -ENOMEM; + + m->partitions[i].fstype = t; + } + } + + *ret = m; + m = NULL; + + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +DissectedImage* dissected_image_unref(DissectedImage *m) { + unsigned i; + + if (!m) + return NULL; + + for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { + free(m->partitions[i].fstype); + free(m->partitions[i].node); + } + + free(m); + return NULL; +} + +static int mount_partition(DissectedPartition *m, const char *where, const char *directory, DissectedImageMountFlags flags) { + const char *p, *options = NULL; + bool rw; + + assert(m); + assert(where); + + if (!m->found || !m->node || !m->fstype) + return 0; + + rw = m->rw && !(flags & DISSECTED_IMAGE_READ_ONLY); + + if (directory) + p = strjoina(where, directory); + else + p = where; + + /* Not supported for now. */ + if (streq(m->fstype, "crypto_LUKS")) + 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 ((flags & DISSECTED_IMAGE_DISCARD_ON_LOOP) && + STR_IN_SET(m->fstype, "btrfs", "ext4", "vfat", "xfs")) { + const char *l; + + l = path_startswith(m->node, "/dev"); + if (l && startswith(l, "loop")) + options = "discard"; + } + + return mount_verbose(LOG_DEBUG, m->node, p, m->fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); +} + +int dissected_image_mount(DissectedImage *m, const char *where, DissectedImageMountFlags flags) { + int r; + + assert(m); + assert(where); + + if (!m->partitions[PARTITION_ROOT].found) + return -ENXIO; + + r = mount_partition(m->partitions + PARTITION_ROOT, where, NULL, flags); + if (r < 0) + return r; + + r = mount_partition(m->partitions + PARTITION_HOME, where, "/home", flags); + if (r < 0) + return r; + + r = mount_partition(m->partitions + PARTITION_SRV, where, "/srv", flags); + if (r < 0) + return r; + + if (m->partitions[PARTITION_ESP].found) { + 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(where, mp); + r = dir_is_empty(x); + if (r == -ENOENT) { + mp = "/boot"; + x = strjoina(where, mp); + r = dir_is_empty(x); + } + if (r > 0) { + r = mount_partition(m->partitions + PARTITION_ESP, where, mp, flags); + if (r < 0) + return r; + } + } + + return 0; +} + +static const char *const partition_designator_table[] = { + [PARTITION_ROOT] = "root", + [PARTITION_ROOT_SECONDARY] = "root-secondary", + [PARTITION_HOME] = "home", + [PARTITION_SRV] = "srv", + [PARTITION_ESP] = "esp", + [PARTITION_SWAP] = "swap", +}; + +DEFINE_STRING_TABLE_LOOKUP(partition_designator, int); diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h new file mode 100644 index 0000000000..04b19e8553 --- /dev/null +++ b/src/shared/dissect-image.h @@ -0,0 +1,66 @@ +#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 . +***/ + +#include + +#include "macro.h" + +typedef struct DissectedImage DissectedImage; +typedef struct DissectedPartition DissectedPartition; + +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; +}; + +enum { + PARTITION_ROOT, + PARTITION_ROOT_SECONDARY, /* Secondary architecture */ + PARTITION_HOME, + PARTITION_SRV, + PARTITION_ESP, + PARTITION_SWAP, + _PARTITION_DESIGNATOR_MAX, + _PARTITION_DESIGNATOR_INVALID = -1 +}; + +typedef enum DissectedImageMountFlags { + DISSECTED_IMAGE_READ_ONLY = 1, + DISSECTED_IMAGE_DISCARD_ON_LOOP = 2, /* Turn on "discard" if on loop device and file system supports it */ +} DissectedImageMountFlags; + +struct DissectedImage { + DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; +}; + +int dissect_image(int fd, DissectedImage **ret); + +DissectedImage* dissected_image_unref(DissectedImage *m); +DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); + +int dissected_image_mount(DissectedImage *m, const char *dest, DissectedImageMountFlags flags); + +const char* partition_designator_to_string(int i) _const_; +int partition_designator_from_string(const char *name) _pure_; diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c new file mode 100644 index 0000000000..8be4dbf938 --- /dev/null +++ b/src/shared/loop-util.c @@ -0,0 +1,157 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include + +#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) { + 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) { + _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; +} diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h new file mode 100644 index 0000000000..5c847c4ac3 --- /dev/null +++ b/src/shared/loop-util.h @@ -0,0 +1,39 @@ +#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 . +***/ + +#include "macro.h" + +typedef struct LoopDevice LoopDevice; + +/* Some helpers for setting up loopback block devices */ + +struct LoopDevice { + int fd; + int nr; + char *node; +}; + +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); diff --git a/src/test/test-dissect-image.c b/src/test/test-dissect-image.c new file mode 100644 index 0000000000..0363ef8eb6 --- /dev/null +++ b/src/test/test-dissect-image.c @@ -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 . +***/ + +#include +#include + +#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, &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; +}