diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/dissect/dissect.c | 46 | ||||
-rw-r--r-- | src/machine/image-dbus.c | 2 | ||||
-rw-r--r-- | src/nspawn/nspawn.c | 9 | ||||
-rw-r--r-- | src/shared/dissect-image.c | 457 | ||||
-rw-r--r-- | src/shared/dissect-image.h | 22 |
5 files changed, 460 insertions, 76 deletions
diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 93ece05948..5e6848acb4 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -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; diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 400d8ec7b0..65953b368f 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -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); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 6ad20f7457..035456f45b 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 7b65daa0eb..bc4e45be6e 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -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; - - if (!m->partitions[i].found) /* not found? */ - continue; - - if (m->partitions[i].fstype) /* already know the type? */ - continue; + DissectedPartition *p = m->partitions + i; - if (!m->partitions[i].node) /* have no device node for? */ + if (!p->found) 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 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"; - } + /* 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"; - 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", diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 04b19e8553..69484eb32c 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -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_; |