dissect: add support for encrypted images

This adds support to the image dissector to deal with encrypted images (only
LUKS). Given that we now have a neatly isolated image dissector codebase, let's
add a new feature to it: support for automatically dealing with encrypted
images. This is then exposed in systemd-dissect and nspawn.

It's pretty basic: only support for passphrase-based encryption.

In order to ensure that "systemd-dissect --mount" results in mount points whose
backing LUKS DM devices are cleaned up automatically we use the DM_DEV_REMOVE
ioctl() directly on the device (in DM_DEFERRED_REMOVE mode). libgcryptsetup at
the moment doesn't provide a proper API for this. Thankfully, the ioctl() API
is pretty easy to use.
This commit is contained in:
Lennart Poettering 2016-12-05 16:26:48 +01:00
parent cf139e6025
commit 18b5886e56
6 changed files with 467 additions and 79 deletions

View File

@ -1086,7 +1086,8 @@ libshared_la_CFLAGS = \
$(ACL_CFLAGS) \
$(LIBIDN_CFLAGS) \
$(SECCOMP_CFLAGS) \
$(BLKID_CFLAGS)
$(BLKID_CFLAGS) \
$(LIBCRYPTSETUP_CFLAGS)
libshared_la_LIBADD = \
libsystemd-internal.la \
@ -1096,7 +1097,8 @@ libshared_la_LIBADD = \
$(ACL_LIBS) \
$(LIBIDN_LIBS) \
$(SECCOMP_LIBS) \
$(BLKID_LIBS)
$(BLKID_LIBS) \
$(LIBCRYPTSETUP_LIBS)
rootlibexec_LTLIBRARIES += \
libsystemd-shared.la
@ -1119,6 +1121,7 @@ libsystemd_shared_la_CFLAGS = \
$(LIBIDN_CFLAGS) \
$(SECCOMP_CFLAGS) \
$(BLKID_CFLAGS) \
$(LIBCRYPTSETUP_CFLAGS) \
-fvisibility=default
# We can't use libshared_la_LIBADD here because it would
@ -1131,7 +1134,8 @@ libsystemd_shared_la_LIBADD = \
$(ACL_LIBS) \
$(LIBIDN_LIBS) \
$(SECCOMP_LIBS) \
$(BLKID_LIBS)
$(BLKID_LIBS) \
$(LIBCRYPTSETUP_LIBS)
libsystemd_shared_la_LDFLAGS = \
$(AM_LDFLAGS) \

View File

@ -34,7 +34,7 @@ static enum {
} arg_action = ACTION_DISSECT;
static const char *arg_image = NULL;
static const char *arg_path = NULL;
static bool arg_read_only = false;
static DissectImageFlags arg_flags = DISSECT_IMAGE_DISCARD_ON_LOOP;
static void help(void) {
printf("%s [OPTIONS...] IMAGE\n"
@ -43,7 +43,8 @@ static void help(void) {
" -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",
" -r --read-only Mount read-only\n"
" --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n",
program_invocation_short_name,
program_invocation_short_name);
}
@ -52,6 +53,7 @@ static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_DISCARD,
};
static const struct option options[] = {
@ -59,6 +61,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "version", no_argument, NULL, ARG_VERSION },
{ "mount", no_argument, NULL, 'm' },
{ "read-only", no_argument, NULL, 'r' },
{ "discard", required_argument, NULL, ARG_DISCARD },
{}
};
@ -83,7 +86,23 @@ static int parse_argv(int argc, char *argv[]) {
break;
case 'r':
arg_read_only = true;
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 '?':
@ -104,7 +123,7 @@ static int parse_argv(int argc, char *argv[]) {
}
arg_image = argv[optind];
arg_read_only = true;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
case ACTION_MOUNT:
@ -126,6 +145,7 @@ static int parse_argv(int argc, char *argv[]) {
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;
@ -136,7 +156,7 @@ int main(int argc, char *argv[]) {
if (r <= 0)
goto finish;
r = loop_device_make_by_path(arg_image, arg_read_only ? O_RDONLY : O_RDWR, &d);
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;
@ -186,14 +206,24 @@ int main(int argc, char *argv[]) {
}
case ACTION_MOUNT:
r = dissected_image_mount(m, arg_path,
(arg_read_only ? DISSECTED_IMAGE_READ_ONLY : 0) |
DISSECTED_IMAGE_DISCARD_ON_LOOP);
r = dissected_image_decrypt_interactively(m, NULL, 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;

View File

@ -358,7 +358,7 @@ static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *err
if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0)
_exit(EXIT_FAILURE);
r = dissected_image_mount(m, t, DISSECTED_IMAGE_READ_ONLY);
r = dissected_image_mount(m, t, DISSECT_IMAGE_READ_ONLY);
if (r < 0)
_exit(EXIT_FAILURE);

View File

@ -2365,7 +2365,7 @@ static int outer_child(
return r;
if (dissected_image) {
r = dissected_image_mount(dissected_image, directory, DISSECTED_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECTED_IMAGE_READ_ONLY : 0));
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;
}
@ -3410,8 +3410,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_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_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();
@ -3652,6 +3653,10 @@ int main(int argc, char *argv[]) {
goto finish;
}
r = dissected_image_decrypt_interactively(dissected_image, NULL, 0, &decrypted_image);
if (r < 0)
goto finish;
/* Now that we mounted the image, let's try to remove it again, if it is ephemeral */
if (remove_image && unlink(arg_image) >= 0)
remove_image = false;

View File

@ -17,19 +17,73 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#ifdef HAVE_LIBCRYPTSETUP
#include <libcryptsetup.h>
#endif
#include <linux/dm-ioctl.h>
#include <sys/mount.h>
#include "architecture.h"
#include "ask-password-api.h"
#include "blkid-util.h"
#include "dissect-image.h"
#include "fd-util.h"
#include "gpt.h"
#include "mount-util.h"
#include "path-util.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-table.h"
#include "string-util.h"
#include "udev-util.h"
static int probe_filesystem(const char *node, char **ret_fstype) {
#ifdef HAVE_BLKID
_cleanup_blkid_free_probe_ blkid_probe b = NULL;
const char *fstype;
int r;
b = blkid_new_probe_from_filename(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 %s", node);
goto not_found;
}
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;
*ret_fstype = t;
return 1;
}
not_found:
*ret_fstype = NULL;
return 0;
#else
return -EOPNOTSUPP;
#endif
}
int dissect_image(int fd, DissectedImage **ret) {
#ifdef HAVE_BLKID
@ -96,7 +150,7 @@ int dissect_image(int fd, DissectedImage **ret) {
return -ENOMEM;
(void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL);
if (streq_ptr(usage, "filesystem")) {
if (STRPTR_IN_SET(usage, "filesystem", "crypto")) {
_cleanup_free_ char *t = NULL, *n = NULL;
const char *fstype = NULL;
@ -123,6 +177,8 @@ int dissect_image(int fd, DissectedImage **ret) {
t = n = NULL;
m->encrypted = streq(fstype, "crypto_LUKS");
*ret = m;
m = NULL;
@ -385,52 +441,24 @@ int dissect_image(int fd, DissectedImage **ret) {
return -ENXIO;
}
blkid_free_probe(b);
b = NULL;
/* Fill in file system types if we don't know them yet. */
for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
const char *fstype;
DissectedPartition *p = m->partitions + i;
if (!m->partitions[i].found) /* not found? */
if (!p->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;
if (!p->fstype && p->node) {
r = probe_filesystem(p->node, &p->fstype);
if (r < 0)
return r;
}
(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;
}
if (streq_ptr(p->fstype, "crypto_LUKS"))
m->encrypted = true;
}
*ret = m;
@ -451,48 +479,79 @@ DissectedImage* dissected_image_unref(DissectedImage *m) {
for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
free(m->partitions[i].fstype);
free(m->partitions[i].node);
free(m->partitions[i].decrypted_fstype);
free(m->partitions[i].decrypted_node);
}
free(m);
return NULL;
}
static int mount_partition(DissectedPartition *m, const char *where, const char *directory, DissectedImageMountFlags flags) {
const char *p, *options = NULL;
static int is_loop_device(const char *path) {
char s[strlen("/sys/dev/block/") + DECIMAL_STR_MAX(dev_t) + 1 + DECIMAL_STR_MAX(dev_t) + strlen("/../loop/")];
struct stat st;
assert(path);
if (stat(path, &st) < 0)
return -errno;
if (!S_ISBLK(st.st_mode))
return -ENOTBLK;
xsprintf(s, "/sys/dev/block/%u:%u/loop/", major(st.st_rdev), minor(st.st_rdev));
if (access(s, F_OK) < 0) {
if (errno != ENOENT)
return -errno;
/* The device itself isn't a loop device, but maybe it's a partition and its parent is? */
xsprintf(s, "/sys/dev/block/%u:%u/../loop/", major(st.st_rdev), minor(st.st_rdev));
if (access(s, F_OK) < 0)
return errno == ENOENT ? false : -errno;
}
return true;
}
static int mount_partition(
DissectedPartition *m,
const char *where,
const char *directory,
DissectImageFlags flags) {
const char *p, *options = NULL, *node, *fstype;
bool rw;
assert(m);
assert(where);
if (!m->found || !m->node || !m->fstype)
node = m->decrypted_node ?: m->node;
fstype = m->decrypted_fstype ?: m->fstype;
if (!m->found || !node || !fstype)
return 0;
rw = m->rw && !(flags & DISSECTED_IMAGE_READ_ONLY);
/* Stacked encryption? Yuck */
if (streq_ptr(fstype, "crypto_LUKS"))
return -ELOOP;
rw = m->rw && !(flags & DISSECT_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 requested, turn on discard support. */
if (STR_IN_SET(fstype, "btrfs", "ext4", "vfat", "xfs") &&
((flags & DISSECT_IMAGE_DISCARD) ||
((flags & DISSECT_IMAGE_DISCARD_ON_LOOP) && is_loop_device(m->node))))
options = "discard";
/* 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);
return mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options);
}
int dissected_image_mount(DissectedImage *m, const char *where, DissectedImageMountFlags flags) {
int dissected_image_mount(DissectedImage *m, const char *where, DissectImageFlags flags) {
int r;
assert(m);
@ -536,6 +595,284 @@ int dissected_image_mount(DissectedImage *m, const char *where, DissectedImageMo
return 0;
}
#ifdef HAVE_LIBCRYPTSETUP
typedef struct DecryptedPartition {
struct crypt_device *device;
char *name;
bool relinquished;
} DecryptedPartition;
struct DecryptedImage {
DecryptedPartition *decrypted;
size_t n_decrypted;
size_t n_allocated;
};
#endif
DecryptedImage* decrypted_image_unref(DecryptedImage* d) {
#ifdef HAVE_LIBCRYPTSETUP
size_t i;
int r;
if (!d)
return NULL;
for (i = 0; i < d->n_decrypted; i++) {
DecryptedPartition *p = d->decrypted + i;
if (p->device && p->name && !p->relinquished) {
r = crypt_deactivate(p->device, p->name);
if (r < 0)
log_debug_errno(r, "Failed to deactivate encrypted partition %s", p->name);
}
if (p->device)
crypt_free(p->device);
free(p->name);
}
free(d);
#endif
return NULL;
}
#ifdef HAVE_LIBCRYPTSETUP
static int decrypt_partition(
DissectedPartition *m,
const char *passphrase,
DissectImageFlags flags,
DecryptedImage *d) {
_cleanup_free_ char *node = NULL, *name = NULL;
struct crypt_device *cd;
const char *suffix;
int r;
assert(m);
assert(d);
if (!m->found || !m->node || !m->fstype)
return 0;
if (!streq(m->fstype, "crypto_LUKS"))
return 0;
suffix = strrchr(m->node, '/');
if (!suffix)
return -EINVAL;
suffix++;
if (isempty(suffix))
return -EINVAL;
name = strjoin(suffix, "-decrypted");
if (!name)
return -ENOMEM;
if (!filename_is_valid(name))
return -EINVAL;
node = strjoin(crypt_get_dir(), "/", name);
if (!node)
return -ENOMEM;
if (!GREEDY_REALLOC0(d->decrypted, d->n_allocated, d->n_decrypted + 1))
return -ENOMEM;
r = crypt_init(&cd, m->node);
if (r < 0)
return r;
r = crypt_load(cd, CRYPT_LUKS1, NULL);
if (r < 0)
goto fail;
r = crypt_activate_by_passphrase(cd, name, CRYPT_ANY_SLOT, passphrase, strlen(passphrase),
((flags & DISSECT_IMAGE_READ_ONLY) ? CRYPT_ACTIVATE_READONLY : 0) |
((flags & DISSECT_IMAGE_DISCARD_ON_CRYPTO) ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0));
if (r == -EPERM) {
r = -EKEYREJECTED;
goto fail;
}
if (r < 0)
goto fail;
d->decrypted[d->n_decrypted].name = name;
name = NULL;
d->decrypted[d->n_decrypted].device = cd;
d->n_decrypted++;
m->decrypted_node = node;
node = NULL;
return 0;
fail:
crypt_free(cd);
return r;
}
#endif
int dissected_image_decrypt(
DissectedImage *m,
const char *passphrase,
DissectImageFlags flags,
DecryptedImage **ret) {
_cleanup_(decrypted_image_unrefp) DecryptedImage *d = NULL;
#ifdef HAVE_LIBCRYPTSETUP
unsigned i;
int r;
#endif
assert(m);
/* Returns:
*
* = 0 There was nothing to decrypt
* > 0 Decrypted successfully
* -ENOKEY There's some to decrypt but no key was supplied
* -EKEYREJECTED Passed key was not correct
*/
if (!m->encrypted) {
*ret = NULL;
return 0;
}
#ifdef HAVE_LIBCRYPTSETUP
if (!passphrase)
return -ENOKEY;
d = new0(DecryptedImage, 1);
if (!d)
return -ENOMEM;
for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
DissectedPartition *p = m->partitions + i;
if (!p->found)
continue;
r = decrypt_partition(p, passphrase, flags, d);
if (r < 0)
return r;
if (!p->decrypted_fstype && p->decrypted_node) {
r = probe_filesystem(p->decrypted_node, &p->decrypted_fstype);
if (r < 0)
return r;
}
}
*ret = d;
d = NULL;
return 1;
#else
return -EOPNOTSUPP;
#endif
}
int dissected_image_decrypt_interactively(
DissectedImage *m,
const char *passphrase,
DissectImageFlags flags,
DecryptedImage **ret) {
_cleanup_strv_free_erase_ char **z = NULL;
int n = 3, r;
if (passphrase)
n--;
for (;;) {
r = dissected_image_decrypt(m, passphrase, flags, ret);
if (r >= 0)
return r;
if (r == -EKEYREJECTED)
log_error_errno(r, "Incorrect passphrase, try again!");
else if (r != -ENOKEY) {
log_error_errno(r, "Failed to decrypt image: %m");
return r;
}
if (--n < 0) {
log_error("Too many retries.");
return -EKEYREJECTED;
}
z = strv_free(z);
r = ask_password_auto("Please enter image passphrase!", NULL, "dissect", "dissect", USEC_INFINITY, 0, &z);
if (r < 0)
return log_error_errno(r, "Failed to query for passphrase: %m");
passphrase = z[0];
}
}
#ifdef HAVE_LIBCRYPTSETUP
static int deferred_remove(DecryptedPartition *p) {
struct dm_ioctl dm = {
.version = {
DM_VERSION_MAJOR,
DM_VERSION_MINOR,
DM_VERSION_PATCHLEVEL
},
.data_size = sizeof(dm),
.flags = DM_DEFERRED_REMOVE,
};
_cleanup_close_ int fd = -1;
assert(p);
/* Unfortunately, libcryptsetup doesn't provide a proper API for this, hence call the ioctl() directly. */
fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC);
if (fd < 0)
return -errno;
strncpy(dm.name, p->name, sizeof(dm.name));
if (ioctl(fd, DM_DEV_REMOVE, &dm))
return -errno;
return 0;
}
#endif
int decrypted_image_relinquish(DecryptedImage *d) {
#ifdef HAVE_LIBCRYPTSETUP
size_t i;
int r;
#endif
assert(d);
/* Turns on automatic removal after the last use ended for all DM devices of this image, and sets a boolean so
* that we don't clean it up ourselves either anymore */
#ifdef HAVE_LIBCRYPTSETUP
for (i = 0; i < d->n_decrypted; i++) {
DecryptedPartition *p = d->decrypted + i;
if (p->relinquished)
continue;
r = deferred_remove(p);
if (r < 0)
return log_debug_errno(r, "Failed to mark %s for auto-removal: %m", p->name);
p->relinquished = true;
}
#endif
return 0;
}
static const char *const partition_designator_table[] = {
[PARTITION_ROOT] = "root",
[PARTITION_ROOT_SECONDARY] = "root-secondary",

View File

@ -25,6 +25,7 @@
typedef struct DissectedImage DissectedImage;
typedef struct DissectedPartition DissectedPartition;
typedef struct DecryptedImage DecryptedImage;
struct DissectedPartition {
bool found:1;
@ -33,6 +34,8 @@ struct DissectedPartition {
int architecture; /* Intended architecture: either native, secondary or unset (-1). */
char *fstype;
char *node;
char *decrypted_node;
char *decrypted_fstype;
};
enum {
@ -46,12 +49,15 @@ enum {
_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;
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;
DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX];
};
@ -60,7 +66,13 @@ 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);
int dissected_image_decrypt(DissectedImage *m, const char *passphrase, DissectImageFlags flags, DecryptedImage **ret);
int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, 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_;