1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-22 11:17:19 +00:00
freebsd/stand/common/vdisk.c
Toomas Soome 4914ee11ae loader: implement map-vdisk and unmap-vdisk commands
illumos update: https://www.illumos.org/issues/10598

Add map-vdisk and unmap-vdisk commands to create virtual disk interface on top of file. This will allow to use disk image from file system to load and start the kernel.

By mapping file, we create vdiskX device, the device will be listed by lsdev [-v] and can be accessed directly as ls vdisk0p1:/path or can be used as value for currdev variable.

vdisk strategy function does not use bcache as we have bcache used with backing file. vdisk can be unmapped when all consumers have closed the open files.

In first iteration we do not support the zfs images because zfs pools do keep the device open (there is no "zpool export" mechanism). Adding zfs support is relatively simple, we just need to run zfs disk probe after mapping is done.

Differential Revision:	https://reviews.freebsd.org/D19733
2019-04-26 17:58:44 +00:00

418 lines
9.5 KiB
C

/*-
* Copyright 2019 Toomas Soome <tsoome@me.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <stand.h>
#include <stdarg.h>
#include <machine/_inttypes.h>
#include <bootstrap.h>
#include <sys/disk.h>
#include <sys/errno.h>
#include <sys/queue.h>
#include <sys/param.h>
#include <disk.h>
static int vdisk_init(void);
static int vdisk_strategy(void *, int, daddr_t, size_t, char *, size_t *);
static int vdisk_open(struct open_file *, ...);
static int vdisk_close(struct open_file *);
static int vdisk_ioctl(struct open_file *, u_long, void *);
static int vdisk_print(int);
struct devsw vdisk_dev = {
.dv_name = "vdisk",
.dv_type = DEVT_DISK,
.dv_init = vdisk_init,
.dv_strategy = vdisk_strategy,
.dv_open = vdisk_open,
.dv_close = vdisk_close,
.dv_ioctl = vdisk_ioctl,
.dv_print = vdisk_print,
.dv_cleanup = NULL
};
typedef STAILQ_HEAD(vdisk_info_list, vdisk_info) vdisk_info_list_t;
typedef struct vdisk_info
{
STAILQ_ENTRY(vdisk_info) vdisk_link; /* link in device list */
char *vdisk_path;
int vdisk_unit;
int vdisk_fd;
uint64_t vdisk_size; /* size in bytes */
uint32_t vdisk_sectorsz;
uint32_t vdisk_open; /* reference counter */
} vdisk_info_t;
static vdisk_info_list_t vdisk_list; /* list of mapped vdisks. */
static vdisk_info_t *
vdisk_get_info(struct devdesc *dev)
{
vdisk_info_t *vd;
STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
if (vd->vdisk_unit == dev->d_unit)
return (vd);
}
return (vd);
}
COMMAND_SET(map_vdisk, "map-vdisk", "map file as virtual disk", command_mapvd);
static int
command_mapvd(int argc, char *argv[])
{
vdisk_info_t *vd, *p;
struct stat sb;
if (argc != 2) {
printf("usage: %s filename\n", argv[0]);
return (CMD_ERROR);
}
STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
if (strcmp(vd->vdisk_path, argv[1]) == 0) {
printf("%s: file %s is already mapped as %s%d\n",
argv[0], argv[1], vdisk_dev.dv_name,
vd->vdisk_unit);
return (CMD_ERROR);
}
}
if (stat(argv[1], &sb) < 0) {
/*
* ENOSYS is really ENOENT because we did try to walk
* through devsw list to try to open this file.
*/
if (errno == ENOSYS)
errno = ENOENT;
printf("%s: stat failed: %s\n", argv[0], strerror(errno));
return (CMD_ERROR);
}
/*
* Avoid mapping small files.
*/
if (sb.st_size < 1024 * 1024) {
printf("%s: file %s is too small.\n", argv[0], argv[1]);
return (CMD_ERROR);
}
vd = calloc(1, sizeof (*vd));
if (vd == NULL) {
printf("%s: out of memory\n", argv[0]);
return (CMD_ERROR);
}
vd->vdisk_path = strdup(argv[1]);
if (vd->vdisk_path == NULL) {
free (vd);
printf("%s: out of memory\n", argv[0]);
return (CMD_ERROR);
}
vd->vdisk_fd = open(vd->vdisk_path, O_RDONLY);
if (vd->vdisk_fd < 0) {
printf("%s: open failed: %s\n", argv[0], strerror(errno));
free(vd->vdisk_path);
free(vd);
return (CMD_ERROR);
}
vd->vdisk_size = sb.st_size;
vd->vdisk_sectorsz = DEV_BSIZE;
STAILQ_FOREACH(p, &vdisk_list, vdisk_link) {
vdisk_info_t *n;
if (p->vdisk_unit == vd->vdisk_unit) {
vd->vdisk_unit++;
continue;
}
n = STAILQ_NEXT(p, vdisk_link);
if (p->vdisk_unit < vd->vdisk_unit) {
if (n == NULL) {
/* p is last elem */
STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link);
break;
}
if (n->vdisk_unit > vd->vdisk_unit) {
/* p < vd < n */
STAILQ_INSERT_AFTER(&vdisk_list, p, vd,
vdisk_link);
break;
}
/* else n < vd or n == vd */
vd->vdisk_unit++;
continue;
}
/* p > vd only if p is the first element */
STAILQ_INSERT_HEAD(&vdisk_list, vd, vdisk_link);
break;
}
/* if the list was empty or contiguous */
if (p == NULL)
STAILQ_INSERT_TAIL(&vdisk_list, vd, vdisk_link);
printf("%s: file %s is mapped as %s%d\n", argv[0], vd->vdisk_path,
vdisk_dev.dv_name, vd->vdisk_unit);
return (CMD_OK);
}
COMMAND_SET(unmap_vdisk, "unmap-vdisk", "unmap virtual disk", command_unmapvd);
/*
* unmap-vdisk vdiskX
*/
static int
command_unmapvd(int argc, char *argv[])
{
size_t len;
vdisk_info_t *vd;
long unit;
char *end;
if (argc != 2) {
printf("usage: %s %sN\n", argv[0], vdisk_dev.dv_name);
return (CMD_ERROR);
}
len = strlen(vdisk_dev.dv_name);
if (strncmp(vdisk_dev.dv_name, argv[1], len) != 0) {
printf("%s: unknown device %s\n", argv[0], argv[1]);
return (CMD_ERROR);
}
errno = 0;
unit = strtol(argv[1] + len, &end, 10);
if (errno != 0 || (*end != '\0' && strcmp(end, ":") != 0)) {
printf("%s: unknown device %s\n", argv[0], argv[1]);
return (CMD_ERROR);
}
STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
if (vd->vdisk_unit == unit)
break;
}
if (vd == NULL) {
printf("%s: unknown device %s\n", argv[0], argv[1]);
return (CMD_ERROR);
}
if (vd->vdisk_open != 0) {
printf("%s: %s is in use, unable to unmap.\n",
argv[0], argv[1]);
return (CMD_ERROR);
}
STAILQ_REMOVE(&vdisk_list, vd, vdisk_info, vdisk_link);
close(vd->vdisk_fd);
free(vd->vdisk_path);
free(vd);
printf("%s (%s) unmapped\n", argv[1], vd->vdisk_path);
return (CMD_OK);
}
static int
vdisk_init(void)
{
STAILQ_INIT(&vdisk_list);
return (0);
}
static int
vdisk_strategy(void *devdata, int rw, daddr_t blk, size_t size,
char *buf, size_t *rsize)
{
struct disk_devdesc *dev;
vdisk_info_t *vd;
ssize_t rv;
dev = devdata;
if (dev == NULL)
return (EINVAL);
vd = vdisk_get_info((struct devdesc *)dev);
if (vd == NULL)
return (EINVAL);
if (size == 0 || (size % 512) != 0)
return (EIO);
if (dev->dd.d_dev->dv_type == DEVT_DISK) {
daddr_t offset;
offset = dev->d_offset * vd->vdisk_sectorsz;
offset /= 512;
blk += offset;
}
if (lseek(vd->vdisk_fd, blk << 9, SEEK_SET) == -1)
return (EIO);
errno = 0;
switch (rw & F_MASK) {
case F_READ:
rv = read(vd->vdisk_fd, buf, size);
break;
case F_WRITE:
rv = write(vd->vdisk_fd, buf, size);
break;
default:
return (ENOSYS);
}
if (errno == 0 && rsize != NULL) {
*rsize = rv;
}
return (errno);
}
static int
vdisk_open(struct open_file *f, ...)
{
va_list args;
struct disk_devdesc *dev;
vdisk_info_t *vd;
int rc = 0;
va_start(args, f);
dev = va_arg(args, struct disk_devdesc *);
va_end(args);
if (dev == NULL)
return (EINVAL);
vd = vdisk_get_info((struct devdesc *)dev);
if (vd == NULL)
return (EINVAL);
if (dev->dd.d_dev->dv_type == DEVT_DISK) {
rc = disk_open(dev, vd->vdisk_size, vd->vdisk_sectorsz);
}
if (rc == 0)
vd->vdisk_open++;
return (rc);
}
static int
vdisk_close(struct open_file *f)
{
struct disk_devdesc *dev;
vdisk_info_t *vd;
dev = (struct disk_devdesc *)(f->f_devdata);
if (dev == NULL)
return (EINVAL);
vd = vdisk_get_info((struct devdesc *)dev);
if (vd == NULL)
return (EINVAL);
vd->vdisk_open--;
if (dev->dd.d_dev->dv_type == DEVT_DISK)
return (disk_close(dev));
return (0);
}
static int
vdisk_ioctl(struct open_file *f, u_long cmd, void *data)
{
struct disk_devdesc *dev;
vdisk_info_t *vd;
int rc;
dev = (struct disk_devdesc *)(f->f_devdata);
if (dev == NULL)
return (EINVAL);
vd = vdisk_get_info((struct devdesc *)dev);
if (vd == NULL)
return (EINVAL);
if (dev->dd.d_dev->dv_type == DEVT_DISK) {
rc = disk_ioctl(dev, cmd, data);
if (rc != ENOTTY)
return (rc);
}
switch (cmd) {
case DIOCGSECTORSIZE:
*(u_int *)data = vd->vdisk_sectorsz;
break;
case DIOCGMEDIASIZE:
*(uint64_t *)data = vd->vdisk_size;
break;
default:
return (ENOTTY);
}
return (0);
}
static int
vdisk_print(int verbose)
{
int ret = 0;
vdisk_info_t *vd;
char line[80];
if (STAILQ_EMPTY(&vdisk_list))
return (ret);
printf("%s devices:", vdisk_dev.dv_name);
if ((ret = pager_output("\n")) != 0)
return (ret);
STAILQ_FOREACH(vd, &vdisk_list, vdisk_link) {
struct disk_devdesc vd_dev;
if (verbose) {
printf(" %s", vd->vdisk_path);
if ((ret = pager_output("\n")) != 0)
break;
}
snprintf(line, sizeof(line),
" %s%d", vdisk_dev.dv_name, vd->vdisk_unit);
printf("%s: %" PRIu64 " X %u blocks", line,
vd->vdisk_size / vd->vdisk_sectorsz,
vd->vdisk_sectorsz);
if ((ret = pager_output("\n")) != 0)
break;
vd_dev.dd.d_dev = &vdisk_dev;
vd_dev.dd.d_unit = vd->vdisk_unit;
vd_dev.d_slice = -1;
vd_dev.d_partition = -1;
ret = disk_open(&vd_dev, vd->vdisk_size, vd->vdisk_sectorsz);
if (ret == 0) {
ret = disk_print(&vd_dev, line, verbose);
disk_close(&vd_dev);
if (ret != 0)
break;
} else {
ret = 0;
}
}
return (ret);
}