machine-image: add partial discovery of block devices as images

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
This commit is contained in:
Lennart Poettering 2017-10-04 17:36:58 +02:00
parent 8c4a8ea2ac
commit eb38edce88
4 changed files with 114 additions and 8 deletions

View 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;

View 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);

View 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);

View file

@ -33,6 +33,7 @@ typedef enum ImageType {
IMAGE_DIRECTORY,
IMAGE_SUBVOLUME,
IMAGE_RAW,
IMAGE_BLOCK,
_IMAGE_TYPE_MAX,
_IMAGE_TYPE_INVALID = -1
} ImageType;