]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/machine-image.c
tree-wide: drop 'This file is part of systemd' blurb
[thirdparty/systemd.git] / src / shared / machine-image.c
index a9e5d608a52268dd703e0801cb0cd49e011ec743..a259b1830100296a96ef07ebfeb9e018cffb64d8 100644 (file)
@@ -1,21 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
   Copyright 2013 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 <dirent.h>
 #include "chattr-util.h"
 #include "copy.h"
 #include "dirent-util.h"
+#include "dissect-image.h"
 #include "env-util.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "fs-util.h"
 #include "hashmap.h"
+#include "hostname-util.h"
+#include "id128-util.h"
 #include "lockfile-util.h"
 #include "log.h"
+#include "loop-util.h"
 #include "machine-image.h"
 #include "macro.h"
 #include "mkdir.h"
+#include "os-util.h"
 #include "path-util.h"
 #include "rm-rf.h"
 #include "string-table.h"
 #include "util.h"
 #include "xattr-util.h"
 
-static const char image_search_path[] =
-        "/var/lib/machines\0"
-        "/var/lib/container\0" /* legacy */
-        "/usr/local/lib/machines\0"
-        "/usr/lib/machines\0";
+static const char* const image_search_path[_IMAGE_CLASS_MAX] = {
+        [IMAGE_MACHINE] =  "/etc/machines\0"              /* only place symlinks here */
+                           "/run/machines\0"              /* and here too */
+                           "/var/lib/machines\0"          /* the main place for images */
+                           "/var/lib/container\0"         /* legacy */
+                           "/usr/local/lib/machines\0"
+                           "/usr/lib/machines\0",
+
+        [IMAGE_PORTABLE] = "/etc/portables\0"             /* only place symlinks here */
+                           "/run/portables\0"             /* and here too */
+                           "/var/lib/portables\0"         /* the main place for images */
+                           "/usr/local/lib/portables\0"
+                           "/usr/lib/portables\0",
+};
 
 Image *image_unref(Image *i) {
         if (!i)
                 return NULL;
 
+        assert(i->n_ref > 0);
+        i->n_ref--;
+
+        if (i->n_ref > 0)
+                return NULL;
+
         free(i->name);
         free(i->path);
+
+        free(i->hostname);
+        strv_free(i->machine_info);
+        strv_free(i->os_release);
+
         return mfree(i);
 }
 
+Image *image_ref(Image *i) {
+        if (!i)
+                return NULL;
+
+        assert(i->n_ref > 0);
+        i->n_ref++;
+
+        return i;
+}
+
 static char **image_settings_path(Image *image) {
         _cleanup_strv_free_ char **l = NULL;
-        char **ret;
         const char *fn, *s;
         unsigned i = 0;
 
@@ -94,10 +114,7 @@ static char **image_settings_path(Image *image) {
         if (!l[i])
                 return NULL;
 
-        ret = l;
-        l = NULL;
-
-        return ret;
+        return TAKE_PTR(l);
 }
 
 static char *image_roothash_path(Image *image) {
@@ -132,6 +149,7 @@ static int image_new(
         if (!i)
                 return -ENOMEM;
 
+        i->n_ref = 1;
         i->type = t;
         i->read_only = read_only;
         i->crtime = crtime;
@@ -147,57 +165,102 @@ static int image_new(
                 i->path = strjoin(path, "/", filename);
         else
                 i->path = strdup(filename);
-
         if (!i->path)
                 return -ENOMEM;
 
-        path_kill_slashes(i->path);
+        path_simplify(i->path, false);
 
-        *ret = i;
-        i = NULL;
+        *ret = TAKE_PTR(i);
 
         return 0;
 }
 
+static int extract_pretty(const char *path, const char *suffix, char **ret) {
+        _cleanup_free_ char *name = NULL;
+        const char *p;
+        size_t n;
+
+        assert(path);
+        assert(ret);
+
+        p = last_path_component(path);
+        n = strcspn(p, "/");
+
+        name = strndup(p, n);
+        if (!name)
+                return -ENOMEM;
+
+        if (suffix) {
+                char *e;
+
+                e = endswith(name, suffix);
+                if (!e)
+                        return -EINVAL;
+
+                *e = 0;
+        }
+
+        if (!image_name_is_valid(name))
+                return -EINVAL;
+
+        *ret = TAKE_PTR(name);
+        return 0;
+}
+
 static int image_make(
                 const char *pretty,
                 int dfd,
                 const char *path,
                 const char *filename,
+                const struct stat *st,
                 Image **ret) {
 
-        struct stat st;
+        _cleanup_free_ char *pretty_buffer = NULL;
+        struct stat stbuf;
         bool read_only;
         int r;
 
+        assert(dfd >= 0 || dfd == AT_FDCWD);
         assert(filename);
 
         /* We explicitly *do* follow symlinks here, since we want to allow symlinking trees, raw files and block
-         * devices into /var/lib/machines/, and treat them normally. */
+         * devices into /var/lib/machines/, and treat them normally.
+         *
+         * This function returns -ENOENT if we can't find the image after all, and -EMEDIUMTYPE if it's not a file we
+         * recognize. */
 
-        if (fstatat(dfd, filename, &st, 0) < 0)
-                return -errno;
+        if (!st) {
+                if (fstatat(dfd, filename, &stbuf, 0) < 0)
+                        return -errno;
+
+                st = &stbuf;
+        }
 
         read_only =
                 (path && path_startswith(path, "/usr")) ||
                 (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
 
-        if (S_ISDIR(st.st_mode)) {
+        if (S_ISDIR(st->st_mode)) {
                 _cleanup_close_ int fd = -1;
                 unsigned file_attr = 0;
 
                 if (!ret)
-                        return 1;
+                        return 0;
+
+                if (!pretty) {
+                        r = extract_pretty(filename, NULL, &pretty_buffer);
+                        if (r < 0)
+                                return r;
 
-                if (!pretty)
-                        pretty = filename;
+                        pretty = pretty_buffer;
+                }
 
                 fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
                 if (fd < 0)
                         return -errno;
 
                 /* btrfs subvolumes have inode 256 */
-                if (st.st_ino == 256) {
+                if (st->st_ino == 256) {
 
                         r = btrfs_is_filesystem(fd);
                         if (r < 0)
@@ -235,7 +298,7 @@ static int image_make(
                                         }
                                 }
 
-                                return 1;
+                                return 0;
                         }
                 }
 
@@ -256,56 +319,69 @@ static int image_make(
                 if (r < 0)
                         return r;
 
-                return 1;
+                return 0;
 
-        } else if (S_ISREG(st.st_mode) && endswith(filename, ".raw")) {
+        } else if (S_ISREG(st->st_mode) && endswith(filename, ".raw")) {
                 usec_t crtime = 0;
 
                 /* It's a RAW disk image */
 
                 if (!ret)
-                        return 1;
+                        return 0;
 
-                fd_getcrtime_at(dfd, filename, &crtime, 0);
+                (void) fd_getcrtime_at(dfd, filename, &crtime, 0);
+
+                if (!pretty) {
+                        r = extract_pretty(filename, ".raw", &pretty_buffer);
+                        if (r < 0)
+                                return r;
 
-                if (!pretty)
-                        pretty = strndupa(filename, strlen(filename) - 4);
+                        pretty = pretty_buffer;
+                }
 
                 r = image_new(IMAGE_RAW,
                               pretty,
                               path,
                               filename,
-                              !(st.st_mode & 0222) || read_only,
+                              !(st->st_mode & 0222) || read_only,
                               crtime,
-                              timespec_load(&st.st_mtim),
+                              timespec_load(&st->st_mtim),
                               ret);
                 if (r < 0)
                         return r;
 
-                (*ret)->usage = (*ret)->usage_exclusive = st.st_blocks * 512;
-                (*ret)->limit = (*ret)->limit_exclusive = st.st_size;
+                (*ret)->usage = (*ret)->usage_exclusive = st->st_blocks * 512;
+                (*ret)->limit = (*ret)->limit_exclusive = st->st_size;
 
-                return 1;
+                return 0;
 
-        } else if (S_ISBLK(st.st_mode)) {
+        } else if (S_ISBLK(st->st_mode)) {
                 _cleanup_close_ int block_fd = -1;
                 uint64_t size = UINT64_MAX;
 
                 /* A block device */
 
                 if (!ret)
-                        return 1;
+                        return 0;
 
-                if (!pretty)
-                        pretty = filename;
+                if (!pretty) {
+                        r = extract_pretty(filename, NULL, &pretty_buffer);
+                        if (r < 0)
+                                return r;
+
+                        pretty = pretty_buffer;
+                }
 
                 block_fd = openat(dfd, filename, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY);
                 if (block_fd < 0)
                         log_debug_errno(errno, "Failed to open block device %s/%s, ignoring: %m", path, filename);
                 else {
-                        if (fstat(block_fd, &st) < 0)
+                        /* Refresh stat data after opening the node */
+                        if (fstat(block_fd, &stbuf) < 0)
                                 return -errno;
-                        if (!S_ISBLK(st.st_mode)) /* Verify that what we opened is actually what we think it is */
+                        st = &stbuf;
+
+                        if (!S_ISBLK(st->st_mode)) /* Verify that what we opened is actually what we think it is */
                                 return -ENOTTY;
 
                         if (!read_only) {
@@ -318,7 +394,7 @@ static int image_make(
                         }
 
                         if (ioctl(block_fd, BLKGETSIZE64, &size) < 0)
-                                log_debug_errno(errno, "Failed to issue BLKFLSBUF on device %s/%s, ignoring: %m", path, filename);
+                                log_debug_errno(errno, "Failed to issue BLKGETSIZE64 on device %s/%s, ignoring: %m", path, filename);
 
                         block_fd = safe_close(block_fd);
                 }
@@ -327,7 +403,7 @@ static int image_make(
                               pretty,
                               path,
                               filename,
-                              !(st.st_mode & 0222) || read_only,
+                              !(st->st_mode & 0222) || read_only,
                               0,
                               0,
                               ret);
@@ -337,24 +413,27 @@ static int image_make(
                 if (size != 0 && size != UINT64_MAX)
                         (*ret)->usage = (*ret)->usage_exclusive = (*ret)->limit = (*ret)->limit_exclusive = size;
 
-                return 1;
+                return 0;
         }
 
-        return 0;
+        return -EMEDIUMTYPE;
 }
 
-int image_find(const char *name, Image **ret) {
+int image_find(ImageClass class, const char *name, Image **ret) {
         const char *path;
         int r;
 
+        assert(class >= 0);
+        assert(class < _IMAGE_CLASS_MAX);
         assert(name);
 
         /* There are no images with invalid names */
         if (!image_name_is_valid(name))
-                return 0;
+                return -ENOENT;
 
-        NULSTR_FOREACH(path, image_search_path) {
+        NULSTR_FOREACH(path, image_search_path[class]) {
                 _cleanup_closedir_ DIR *d = NULL;
+                struct stat st;
 
                 d = opendir(path);
                 if (!d) {
@@ -364,37 +443,90 @@ int image_find(const char *name, Image **ret) {
                         return -errno;
                 }
 
-                r = image_make(NULL, dirfd(d), path, name, ret);
-                if (IN_SET(r, 0, -ENOENT)) {
+                /* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people to
+                 * symlink block devices into the search path */
+                if (fstatat(dirfd(d), name, &st, 0) < 0) {
                         _cleanup_free_ char *raw = NULL;
 
+                        if (errno != ENOENT)
+                                return -errno;
+
                         raw = strappend(name, ".raw");
                         if (!raw)
                                 return -ENOMEM;
 
-                        r = image_make(NULL, dirfd(d), path, raw, ret);
-                        if (IN_SET(r, 0, -ENOENT))
+                        if (fstatat(dirfd(d), raw, &st, 0) < 0) {
+
+                                if (errno == ENOENT)
+                                        continue;
+
+                                return -errno;
+                        }
+
+                        if (!S_ISREG(st.st_mode))
+                                continue;
+
+                        r = image_make(name, dirfd(d), path, raw, &st, ret);
+
+                } else {
+                        if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode))
                                 continue;
+
+                        r = image_make(name, dirfd(d), path, name, &st, ret);
                 }
+                if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
+                        continue;
                 if (r < 0)
                         return r;
 
+                if (ret)
+                        (*ret)->discoverable = true;
+
                 return 1;
         }
 
-        if (streq(name, ".host"))
-                return image_make(".host", AT_FDCWD, NULL, "/", ret);
+        if (class == IMAGE_MACHINE && streq(name, ".host")) {
+                r = image_make(".host", AT_FDCWD, NULL, "/", NULL, ret);
+                if (r < 0)
+                        return r;
 
-        return 0;
+                if (ret)
+                        (*ret)->discoverable = true;
+
+                return r;
+        }
+
+        return -ENOENT;
 };
 
-int image_discover(Hashmap *h) {
+int image_from_path(const char *path, Image **ret) {
+
+        /* Note that we don't set the 'discoverable' field of the returned object, because we don't check here whether
+         * the image is in the image search path. And if it is we don't know if the path we used is actually not
+         * overriden by another, different image earlier in the search path */
+
+        if (path_equal(path, "/"))
+                return image_make(".host", AT_FDCWD, NULL, "/", NULL, ret);
+
+        return image_make(NULL, AT_FDCWD, NULL, path, NULL, ret);
+}
+
+int image_find_harder(ImageClass class, const char *name_or_path, Image **ret) {
+        if (image_name_is_valid(name_or_path))
+                return image_find(class, name_or_path, ret);
+
+        return image_from_path(name_or_path, ret);
+}
+
+int image_discover(ImageClass class, Hashmap *h) {
         const char *path;
         int r;
 
+        assert(class >= 0);
+        assert(class < _IMAGE_CLASS_MAX);
         assert(h);
 
-        NULSTR_FOREACH(path, image_search_path) {
+        NULSTR_FOREACH(path, image_search_path[class]) {
                 _cleanup_closedir_ DIR *d = NULL;
                 struct dirent *de;
 
@@ -408,19 +540,53 @@ int image_discover(Hashmap *h) {
 
                 FOREACH_DIRENT_ALL(de, d, return -errno) {
                         _cleanup_(image_unrefp) Image *image = NULL;
+                        _cleanup_free_ char *truncated = NULL;
+                        const char *pretty;
+                        struct stat st;
 
-                        if (!image_name_is_valid(de->d_name))
+                        if (dot_or_dot_dot(de->d_name))
                                 continue;
 
-                        if (hashmap_contains(h, de->d_name))
+                        /* As mentioned above, we follow symlinks on this fstatat(), because we want to permit people
+                         * to symlink block devices into the search path */
+                        if (fstatat(dirfd(d), de->d_name, &st, 0) < 0) {
+                                if (errno == ENOENT)
+                                        continue;
+
+                                return -errno;
+                        }
+
+                        if (S_ISREG(st.st_mode)) {
+                                const char *e;
+
+                                e = endswith(de->d_name, ".raw");
+                                if (!e)
+                                        continue;
+
+                                truncated = strndup(de->d_name, e - de->d_name);
+                                if (!truncated)
+                                        return -ENOMEM;
+
+                                pretty = truncated;
+                        } else if (S_ISDIR(st.st_mode) || S_ISBLK(st.st_mode))
+                                pretty = de->d_name;
+                        else
                                 continue;
 
-                        r = image_make(NULL, dirfd(d), path, de->d_name, &image);
-                        if (IN_SET(r, 0, -ENOENT))
+                        if (!image_name_is_valid(pretty))
+                                continue;
+
+                        if (hashmap_contains(h, pretty))
+                                continue;
+
+                        r = image_make(pretty, dirfd(d), path, de->d_name, &st, &image);
+                        if (IN_SET(r, -ENOENT, -EMEDIUMTYPE))
                                 continue;
                         if (r < 0)
                                 return r;
 
+                        image->discoverable = true;
+
                         r = hashmap_put(h, image->name, image);
                         if (r < 0)
                                 return r;
@@ -429,35 +595,27 @@ int image_discover(Hashmap *h) {
                 }
         }
 
-        if (!hashmap_contains(h, ".host")) {
+        if (class == IMAGE_MACHINE && !hashmap_contains(h, ".host")) {
                 _cleanup_(image_unrefp) Image *image = NULL;
 
-                r = image_make(".host", AT_FDCWD, NULL, "/", &image);
+                r = image_make(".host", AT_FDCWD, NULL, "/", NULL, &image);
                 if (r < 0)
                         return r;
 
+                image->discoverable = true;
+
                 r = hashmap_put(h, image->name, image);
                 if (r < 0)
                         return r;
 
                 image = NULL;
-
         }
 
         return 0;
 }
 
-void image_hashmap_free(Hashmap *map) {
-        Image *i;
-
-        while ((i = hashmap_steal_first(map)))
-                image_unref(i);
-
-        hashmap_free(map);
-}
-
 int image_remove(Image *i) {
-        _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
+        _cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
         _cleanup_strv_free_ char **settings = NULL;
         _cleanup_free_ char *roothash = NULL;
         char **j;
@@ -513,8 +671,7 @@ int image_remove(Image *i) {
                 if (path_startswith(i->path, "/dev"))
                         break;
 
-                /* fallthrough */
-
+                _fallthrough_;
         case IMAGE_RAW:
                 if (unlink(i->path) < 0)
                         return -errno;
@@ -549,7 +706,7 @@ static int rename_auxiliary_file(const char *path, const char *new_name, const c
 }
 
 int image_rename(Image *i, const char *new_name) {
-        _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT;
+        _cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT;
         _cleanup_free_ char *new_path = NULL, *nn = NULL, *roothash = NULL;
         _cleanup_strv_free_ char **settings = NULL;
         unsigned file_attr = 0;
@@ -584,11 +741,11 @@ int image_rename(Image *i, const char *new_name) {
         if (r < 0)
                 return r;
 
-        r = image_find(new_name, NULL);
-        if (r < 0)
-                return r;
-        if (r > 0)
+        r = image_find(IMAGE_MACHINE, new_name, NULL);
+        if (r >= 0)
                 return -EEXIST;
+        if (r != -ENOENT)
+                return r;
 
         switch (i->type) {
 
@@ -599,8 +756,7 @@ int image_rename(Image *i, const char *new_name) {
                 if (file_attr & FS_IMMUTABLE_FL)
                         (void) chattr_path(i->path, 0, FS_IMMUTABLE_FL);
 
-                /* fall through */
-
+                _fallthrough_;
         case IMAGE_SUBVOLUME:
                 new_path = file_in_same_dir(i->path, new_name);
                 break;
@@ -641,13 +797,8 @@ int image_rename(Image *i, const char *new_name) {
         if (file_attr & FS_IMMUTABLE_FL)
                 (void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL);
 
-        free(i->path);
-        i->path = new_path;
-        new_path = NULL;
-
-        free(i->name);
-        i->name = nn;
-        nn = NULL;
+        free_and_replace(i->path, new_path);
+        free_and_replace(i->name, nn);
 
         STRV_FOREACH(j, settings) {
                 r = rename_auxiliary_file(*j, new_name, ".nspawn");
@@ -676,7 +827,7 @@ static int clone_auxiliary_file(const char *path, const char *new_name, const ch
 }
 
 int image_clone(Image *i, const char *new_name, bool read_only) {
-        _cleanup_release_lock_file_ LockFile name_lock = LOCK_FILE_INIT;
+        _cleanup_(release_lock_file) LockFile name_lock = LOCK_FILE_INIT;
         _cleanup_strv_free_ char **settings = NULL;
         _cleanup_free_ char *roothash = NULL;
         const char *new_path;
@@ -703,11 +854,11 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
         if (r < 0)
                 return r;
 
-        r = image_find(new_name, NULL);
-        if (r < 0)
-                return r;
-        if (r > 0)
+        r = image_find(IMAGE_MACHINE, new_name, NULL);
+        if (r >= 0)
                 return -EEXIST;
+        if (r != -ENOENT)
+                return r;
 
         switch (i->type) {
 
@@ -759,8 +910,9 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
 }
 
 int image_read_only(Image *i, bool b) {
-        _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
+        _cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
         int r;
+
         assert(i);
 
         if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
@@ -886,8 +1038,12 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
          * block devices are device local anyway. */
         if (!path_startswith(path, "/dev")) {
                 r = make_lock_file_for(path, operation, &t);
-                if (r < 0)
-                        return r;
+                if (r < 0) {
+                        if ((operation & LOCK_SH) && r == -EROFS)
+                                log_debug_errno(r, "Failed to create shared lock for '%s', ignoring: %m", path);
+                        else
+                                return r;
+                }
         }
 
         if (p) {
@@ -924,6 +1080,109 @@ int image_set_limit(Image *i, uint64_t referenced_max) {
         return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max);
 }
 
+int image_read_metadata(Image *i) {
+        _cleanup_(release_lock_file) LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT;
+        int r;
+
+        assert(i);
+
+        r = image_path_lock(i->path, LOCK_SH|LOCK_NB, &global_lock, &local_lock);
+        if (r < 0)
+                return r;
+
+        switch (i->type) {
+
+        case IMAGE_SUBVOLUME:
+        case IMAGE_DIRECTORY: {
+                _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL;
+                sd_id128_t machine_id = SD_ID128_NULL;
+                _cleanup_free_ char *hostname = NULL;
+                _cleanup_free_ char *path = NULL;
+
+                r = chase_symlinks("/etc/hostname", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path);
+                if (r < 0 && r != -ENOENT)
+                        log_debug_errno(r, "Failed to chase /etc/hostname in image %s: %m", i->name);
+                else if (r >= 0) {
+                        r = read_etc_hostname(path, &hostname);
+                        if (r < 0)
+                                log_debug_errno(errno, "Failed to read /etc/hostname of image %s: %m", i->name);
+                }
+
+                path = mfree(path);
+
+                r = chase_symlinks("/etc/machine-id", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path);
+                if (r < 0 && r != -ENOENT)
+                        log_debug_errno(r, "Failed to chase /etc/machine-id in image %s: %m", i->name);
+                else if (r >= 0) {
+                        _cleanup_close_ int fd = -1;
+
+                        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+                        if (fd < 0)
+                                log_debug_errno(errno, "Failed to open %s: %m", path);
+                        else {
+                                r = id128_read_fd(fd, ID128_PLAIN, &machine_id);
+                                if (r < 0)
+                                        log_debug_errno(r, "Image %s contains invalid machine ID.", i->name);
+                        }
+                }
+
+                path = mfree(path);
+
+                r = chase_symlinks("/etc/machine-info", i->path, CHASE_PREFIX_ROOT|CHASE_TRAIL_SLASH, &path);
+                if (r < 0 && r != -ENOENT)
+                        log_debug_errno(r, "Failed to chase /etc/machine-info in image %s: %m", i->name);
+                else if (r >= 0) {
+                        r = load_env_file_pairs(NULL, path, NULL, &machine_info);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to parse machine-info data of %s: %m", i->name);
+                }
+
+                r = load_os_release_pairs(i->path, &os_release);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to read os-release in image, ignoring: %m");
+
+                free_and_replace(i->hostname, hostname);
+                i->machine_id = machine_id;
+                strv_free_and_replace(i->machine_info, machine_info);
+                strv_free_and_replace(i->os_release, os_release);
+
+                break;
+        }
+
+        case IMAGE_RAW:
+        case IMAGE_BLOCK: {
+                _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
+                _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL;
+
+                r = loop_device_make_by_path(i->path, O_RDONLY, &d);
+                if (r < 0)
+                        return r;
+
+                r = dissect_image(d->fd, NULL, 0, DISSECT_IMAGE_REQUIRE_ROOT, &m);
+                if (r < 0)
+                        return r;
+
+                r = dissected_image_acquire_metadata(m);
+                if (r < 0)
+                        return r;
+
+                free_and_replace(i->hostname, m->hostname);
+                i->machine_id = m->machine_id;
+                strv_free_and_replace(i->machine_info, m->machine_info);
+                strv_free_and_replace(i->os_release, m->os_release);
+
+                break;
+        }
+
+        default:
+                return -EOPNOTSUPP;
+        }
+
+        i->metadata_valid = true;
+
+        return 0;
+}
+
 int image_name_lock(const char *name, int operation, LockFile *ret) {
         const char *p;
 
@@ -966,6 +1225,35 @@ bool image_name_is_valid(const char *s) {
         return true;
 }
 
+bool image_in_search_path(ImageClass class, const char *image) {
+        const char *path;
+
+        assert(image);
+
+        NULSTR_FOREACH(path, image_search_path[class]) {
+                const char *p;
+                size_t k;
+
+                p = path_startswith(image, path);
+                if (!p)
+                        continue;
+
+                /* Make sure there's a filename following */
+                k = strcspn(p, "/");
+                if (k == 0)
+                        continue;
+
+                p += k;
+
+                /* Accept trailing slashes */
+                if (p[strspn(p, "/")] == 0)
+                        return true;
+
+        }
+
+        return false;
+}
+
 static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
         [IMAGE_DIRECTORY] = "directory",
         [IMAGE_SUBVOLUME] = "subvolume",