machine-image: add partial discovery of block devices as images
authorLennart Poettering <lennart@poettering.net>
Wed, 4 Oct 2017 15:36:58 +0000 (17:36 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 26 Oct 2017 15:54:56 +0000 (17:54 +0200)
This adds some basic discovery of block device images for nspawn and
friends. Note that this doesn't add searching for block devices using
udev, but instead expects users to symlink relevant block devices into
/var/lib/machines. Discovery is hence done exactly like for
dir/subvol/raw file images, except that what is found may be a (symlink
to) a block device.

For now, we do not support cloning these images, but removal, renaming
and read-only flags are supported to the point where that makes sense.

Fixe: #6990

src/machine/image-dbus.c
src/nspawn/nspawn.c
src/shared/machine-image.c
src/shared/machine-image.h

index 18e0e34..e534470 100644 (file)
@@ -431,6 +431,7 @@ int bus_image_method_get_os_release(
                 break;
 
         case IMAGE_RAW:
+        case IMAGE_BLOCK:
                 r = raw_image_get_os_release(image, &v, error);
                 break;
 
index 4e3803b..d1e3855 100644 (file)
@@ -2089,7 +2089,7 @@ static int determine_names(void) {
                                 return -ENOENT;
                         }
 
-                        if (i->type == IMAGE_RAW)
+                        if (IN_SET(i->type, IMAGE_RAW, IMAGE_BLOCK))
                                 r = free_and_strdup(&arg_image, i->path);
                         else
                                 r = free_and_strdup(&arg_directory, i->path);
index 859e5ff..a8af1b7 100644 (file)
@@ -171,9 +171,8 @@ static int image_make(
 
         assert(filename);
 
-        /* We explicitly *do* follow symlinks here, since we want to
-         * allow symlinking trees into /var/lib/machines/, and treat
-         * them normally. */
+        /* 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. */
 
         if (fstatat(dfd, filename, &st, 0) < 0)
                 return -errno;
@@ -286,6 +285,58 @@ static int image_make(
                 (*ret)->limit = (*ret)->limit_exclusive = st.st_size;
 
                 return 1;
+
+        } 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;
+
+                if (!pretty)
+                        pretty = filename;
+
+                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)
+                                return -errno;
+                        if (!S_ISBLK(st.st_mode)) /* Verify that what we opened is actually what we think it is */
+                                return -ENOTTY;
+
+                        if (!read_only) {
+                                int state = 0;
+
+                                if (ioctl(block_fd, BLKROGET, &state) < 0)
+                                        log_debug_errno(errno, "Failed to issue BLKROGET on device %s/%s, ignoring: %m", path, filename);
+                                else if (state)
+                                        read_only = true;
+                        }
+
+                        if (ioctl(block_fd, BLKGETSIZE64, &size) < 0)
+                                log_debug_errno(errno, "Failed to issue BLKFLSBUF on device %s/%s, ignoring: %m", path, filename);
+
+                        block_fd = safe_close(block_fd);
+                }
+
+                r = image_new(IMAGE_BLOCK,
+                              pretty,
+                              path,
+                              filename,
+                              !(st.st_mode & 0222) || read_only,
+                              0,
+                              0,
+                              ret);
+                if (r < 0)
+                        return r;
+
+                if (size != 0 && size != UINT64_MAX)
+                        (*ret)->usage = (*ret)->usage_exclusive = (*ret)->limit = (*ret)->limit_exclusive = size;
+
+                return 1;
         }
 
         return 0;
@@ -446,6 +497,17 @@ int image_remove(Image *i) {
 
                 break;
 
+        case IMAGE_BLOCK:
+
+                /* If this is inside of /dev, then it's a real block device, hence let's not touch the device node
+                 * itself (but let's remove the stuff stored alongside it). If it's anywhere else, let's try to unlink
+                 * the thing (it's most likely a symlink after all). */
+
+                if (path_startswith(i->path, "/dev"))
+                        break;
+
+                /* fallthrough */
+
         case IMAGE_RAW:
                 if (unlink(i->path) < 0)
                         return -errno;
@@ -536,6 +598,15 @@ int image_rename(Image *i, const char *new_name) {
                 new_path = file_in_same_dir(i->path, new_name);
                 break;
 
+        case IMAGE_BLOCK:
+
+                /* Refuse renaming raw block devices in /dev, the names are picked by udev after all. */
+                if (path_startswith(i->path, "/dev"))
+                        return -EROFS;
+
+                new_path = file_in_same_dir(i->path, new_name);
+                break;
+
         case IMAGE_RAW: {
                 const char *fn;
 
@@ -659,6 +730,7 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
                 r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, FS_NOCOW_FL, COPY_REFLINK);
                 break;
 
+        case IMAGE_BLOCK:
         default:
                 return -EOPNOTSUPP;
         }
@@ -737,6 +809,26 @@ int image_read_only(Image *i, bool b) {
                 break;
         }
 
+        case IMAGE_BLOCK: {
+                _cleanup_close_ int fd = -1;
+                struct stat st;
+                int state = b;
+
+                fd = open(i->path, O_CLOEXEC|O_RDONLY|O_NONBLOCK|O_NOCTTY);
+                if (fd < 0)
+                        return -errno;
+
+                if (fstat(fd, &st) < 0)
+                        return -errno;
+                if (!S_ISBLK(st.st_mode))
+                        return -ENOTTY;
+
+                if (ioctl(fd, BLKROSET, &state) < 0)
+                        return -errno;
+
+                break;
+        }
+
         default:
                 return -EOPNOTSUPP;
         }
@@ -772,13 +864,24 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
                 return -EBUSY;
 
         if (stat(path, &st) >= 0) {
-                if (asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0)
+                if (S_ISBLK(st.st_mode))
+                        r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev));
+                else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))
+                        r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino);
+                else
+                        return -ENOTTY;
+
+                if (r < 0)
                         return -ENOMEM;
         }
 
-        r = make_lock_file_for(path, operation, &t);
-        if (r < 0)
-                return r;
+        /* For block devices we don't need the "local" lock, as the major/minor lock above should be sufficient, since
+         * 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 (p) {
                 mkdir_p("/run/systemd/nspawn/locks", 0700);
@@ -860,6 +963,7 @@ static const char* const image_type_table[_IMAGE_TYPE_MAX] = {
         [IMAGE_DIRECTORY] = "directory",
         [IMAGE_SUBVOLUME] = "subvolume",
         [IMAGE_RAW] = "raw",
+        [IMAGE_BLOCK] = "block",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType);
index 7410168..50d89e4 100644 (file)
@@ -33,6 +33,7 @@ typedef enum ImageType {
         IMAGE_DIRECTORY,
         IMAGE_SUBVOLUME,
         IMAGE_RAW,
+        IMAGE_BLOCK,
         _IMAGE_TYPE_MAX,
         _IMAGE_TYPE_INVALID = -1
 } ImageType;