Files
libguestfs/lib/drives.c
2025-11-26 15:17:50 +00:00

996 lines
27 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* libguestfs
* Copyright (C) 2013 Red Hat Inc.
*
* This library 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 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* Drives added are stored in an array in the handle. Code here
* manages that array and the individual C<struct drive> data.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <libintl.h>
#include "c-ctype.h"
#include "ignore-value.h"
#include "guestfs.h"
#include "guestfs-internal.h"
#include "guestfs-internal-actions.h"
/* Helper struct to hold all the data needed when creating a new
* drive.
*/
struct drive_create_data {
enum drive_protocol protocol;
struct drive_server *servers;
size_t nr_servers;
const char *exportname; /* File name or path to the resource. */
const char *username;
const char *secret;
bool readonly;
const char *format;
const char *name;
const char *disk_label;
const char *cachemode;
enum discard discard;
bool copyonread;
int blocksize;
};
COMPILE_REGEXP (re_hostname_port, "(.*):(\\d+)$", 0)
static void free_drive_struct (struct drive *drv);
static void free_drive_source (struct drive_source *src);
/**
* For readonly drives, create an overlay to protect the original
* drive content. Note we never need to clean up these overlays since
* they are created in the temporary directory and deleted when the
* handle is closed.
*/
static int
create_overlay (guestfs_h *g, struct drive *drv)
{
char *overlay;
assert (g->backend_ops != NULL);
if (g->backend_ops->create_cow_overlay == NULL) {
error (g, _("this backend does not support adding read-only drives"));
return -1;
}
debug (g, "creating COW overlay to protect original drive content");
overlay = g->backend_ops->create_cow_overlay (g, g->backend_data, drv);
if (overlay == NULL)
return -1;
free (drv->overlay);
drv->overlay = overlay;
return 0;
}
/**
* Create and free the C<struct drive>.
*/
static struct drive *
create_drive_file (guestfs_h *g, const struct drive_create_data *data)
{
struct drive *drv = safe_calloc (g, 1, sizeof *drv);
*drv = (struct drive){
.src.protocol = drive_protocol_file,
.src.u.path = safe_strdup (g, data->exportname),
.src.format = data->format ? safe_strdup (g, data->format) : NULL,
.name = data->name ? safe_strdup (g, data->name) : NULL,
.disk_label = data->disk_label ? safe_strdup (g, data->disk_label) : NULL,
.cachemode = data->cachemode ? safe_strdup (g, data->cachemode) : NULL,
.readonly = data->readonly,
.discard = data->discard,
.copyonread = data->copyonread,
.blocksize = data->blocksize,
};
if (data->readonly) {
if (create_overlay (g, drv) == -1) {
/* Don't double-free the servers in free_drive_struct, since
* they are owned by the caller along this error path.
*/
drv->src.servers = NULL; drv->src.nr_servers = 0;
free_drive_struct (drv);
return NULL;
}
}
return drv;
}
static struct drive *
create_drive_non_file (guestfs_h *g,
const struct drive_create_data *data)
{
struct drive *drv = safe_calloc (g, 1, sizeof *drv);
drv->src.protocol = data->protocol;
drv->src.servers = data->servers;
drv->src.nr_servers = data->nr_servers;
drv->src.u.exportname = safe_strdup (g, data->exportname);
drv->src.username = data->username ? safe_strdup (g, data->username) : NULL;
drv->src.secret = data->secret ? safe_strdup (g, data->secret) : NULL;
drv->src.format = data->format ? safe_strdup (g, data->format) : NULL;
drv->readonly = data->readonly;
drv->name = data->name ? safe_strdup (g, data->name) : NULL;
drv->disk_label = data->disk_label ? safe_strdup (g, data->disk_label) : NULL;
drv->cachemode = data->cachemode ? safe_strdup (g, data->cachemode) : NULL;
drv->discard = data->discard;
drv->copyonread = data->copyonread;
drv->blocksize = data->blocksize;
if (data->readonly) {
if (create_overlay (g, drv) == -1) {
/* Don't double-free the servers in free_drive_struct, since
* they are owned by the caller along this error path.
*/
drv->src.servers = NULL; drv->src.nr_servers = 0;
free_drive_struct (drv);
return NULL;
}
}
return drv;
}
static struct drive *
create_drive_curl (guestfs_h *g,
const struct drive_create_data *data)
{
if (data->nr_servers != 1) {
error (g, _("curl: you must specify exactly one server"));
return NULL;
}
if (data->servers[0].transport != drive_transport_none &&
data->servers[0].transport != drive_transport_tcp) {
error (g, _("curl: only tcp transport is supported"));
return NULL;
}
if (STREQ (data->exportname, "")) {
error (g, _("curl: pathname should not be an empty string"));
return NULL;
}
if (data->exportname[0] != '/') {
error (g, _("curl: pathname must begin with a '/'"));
return NULL;
}
return create_drive_non_file (g, data);
}
static int
nbd_port (void)
{
struct servent *servent;
servent = getservbyname ("nbd", "tcp");
if (servent)
return ntohs (servent->s_port);
else
return 10809;
}
static struct drive *
create_drive_nbd (guestfs_h *g,
const struct drive_create_data *data)
{
if (data->username != NULL) {
error (g, _("nbd: you cannot specify a username with this protocol"));
return NULL;
}
if (data->secret != NULL) {
error (g, _("nbd: you cannot specify a secret with this protocol"));
return NULL;
}
if (data->nr_servers != 1) {
error (g, _("nbd: you must specify exactly one server"));
return NULL;
}
if (data->servers[0].port == 0)
data->servers[0].port = nbd_port ();
return create_drive_non_file (g, data);
}
static struct drive *
create_drive_rbd (guestfs_h *g,
const struct drive_create_data *data)
{
size_t i;
for (i = 0; i < data->nr_servers; ++i) {
if (data->servers[i].transport != drive_transport_none &&
data->servers[i].transport != drive_transport_tcp) {
error (g, _("rbd: only tcp transport is supported"));
return NULL;
}
if (data->servers[i].port == 0) {
error (g, _("rbd: port number must be specified"));
return NULL;
}
}
if (STREQ (data->exportname, "")) {
error (g, _("rbd: image name parameter should not be an empty string"));
return NULL;
}
if (data->exportname[0] == '/') {
error (g, _("rbd: image name must not begin with a '/'"));
return NULL;
}
return create_drive_non_file (g, data);
}
static struct drive *
create_drive_ssh (guestfs_h *g,
const struct drive_create_data *data)
{
if (data->nr_servers != 1) {
error (g, _("ssh: you must specify exactly one server"));
return NULL;
}
if (data->servers[0].transport != drive_transport_none &&
data->servers[0].transport != drive_transport_tcp) {
error (g, _("ssh: only tcp transport is supported"));
return NULL;
}
if (STREQ (data->exportname, "")) {
error (g, _("ssh: pathname should not be an empty string"));
return NULL;
}
if (data->exportname[0] != '/') {
error (g, _("ssh: pathname must begin with a '/'"));
return NULL;
}
if (data->username && STREQ (data->username, "")) {
error (g, _("ssh: username should not be an empty string"));
return NULL;
}
return create_drive_non_file (g, data);
}
static struct drive *
create_drive_iscsi (guestfs_h *g,
const struct drive_create_data *data)
{
if (data->nr_servers != 1) {
error (g, _("iscsi: you must specify exactly one server"));
return NULL;
}
if (data->servers[0].transport != drive_transport_none &&
data->servers[0].transport != drive_transport_tcp) {
error (g, _("iscsi: only tcp transport is supported"));
return NULL;
}
if (STREQ (data->exportname, "")) {
error (g, _("iscsi: target name should not be an empty string"));
return NULL;
}
if (data->exportname[0] == '/') {
error (g, _("iscsi: target string must not begin with a '/'"));
return NULL;
}
return create_drive_non_file (g, data);
}
/**
* Create the special F</dev/null> drive.
*
* Traditionally you have been able to use F</dev/null> as a filename,
* as many times as you like. Ancient KVM (RHEL 5) cannot handle
* adding F</dev/null> readonly. qemu 1.2 + virtio-scsi segfaults
* when you use any zero-sized file including F</dev/null>.
*
* Because of these problems, we replace F</dev/null> with a non-zero
* sized temporary file. This shouldn't make any difference since
* users are not supposed to try and access a null drive.
*/
static struct drive *
create_drive_dev_null (guestfs_h *g,
struct drive_create_data *data)
{
CLEANUP_FREE char *tmpfile = NULL;
if (data->format) {
if (STRNEQ (data->format, "raw")) {
error (g, _("for device /dev/null, format must be raw"));
return NULL;
}
} else {
/* Manual set format=raw for /dev/null drives, if that was not
* already manually specified. */
data->format = "raw";
}
tmpfile = guestfs_int_make_temp_path (g, "devnull", "img");
if (tmpfile == NULL)
return NULL;
/* Because we create a special file, there is no point forcing qemu
* to create an overlay as well. Save time by setting readonly = false.
*/
data->readonly = false;
if (guestfs_disk_create (g, tmpfile, "raw", 4096, -1) == -1)
return NULL;
data->exportname = tmpfile;
data->discard = discard_disable;
data->copyonread = false;
return create_drive_file (g, data);
}
static struct drive *
create_drive_dummy (guestfs_h *g)
{
/* A special drive struct that is used as a dummy slot for the appliance. */
struct drive_create_data data = { 0, };
data.exportname = "";
return create_drive_file (g, &data);
}
static void
free_drive_servers (struct drive_server *servers, size_t nr_servers)
{
if (servers) {
size_t i;
for (i = 0; i < nr_servers; ++i)
free (servers[i].u.hostname);
free (servers);
}
}
static void
free_drive_struct (struct drive *drv)
{
free_drive_source (&drv->src);
free (drv->overlay);
free (drv->name);
free (drv->disk_label);
free (drv->cachemode);
free (drv);
}
const char *
guestfs_int_drive_protocol_to_string (enum drive_protocol protocol)
{
static const char *const names[] = {
[drive_protocol_file] = "file",
[drive_protocol_ftp] = "ftp",
[drive_protocol_ftps] = "ftps",
[drive_protocol_http] = "http",
[drive_protocol_https] = "https",
[drive_protocol_iscsi] = "iscsi",
[drive_protocol_nbd] = "nbd",
[drive_protocol_rbd] = "rbd",
[drive_protocol_ssh] = "ssh",
};
if ((unsigned int)protocol < sizeof(names)/sizeof(names[0]))
return names[protocol];
abort();
}
/**
* Convert a C<struct drive> to a string for debugging. The caller
* must free this string.
*/
static char *
drive_to_string (guestfs_h *g, const struct drive *drv)
{
CLEANUP_FREE char *s_blocksize = NULL;
if (drv->blocksize)
s_blocksize = safe_asprintf (g, "%d", drv->blocksize);
return safe_asprintf
(g, "%s%s%s%s protocol=%s%s%s%s%s%s%s%s%s%s%s",
drv->src.u.path,
drv->readonly ? " readonly" : "",
drv->src.format ? " format=" : "",
drv->src.format ? : "",
guestfs_int_drive_protocol_to_string (drv->src.protocol),
drv->name ? " name=" : "",
drv->name ? : "",
drv->disk_label ? " label=" : "",
drv->disk_label ? : "",
drv->cachemode ? " cache=" : "",
drv->cachemode ? : "",
drv->discard == discard_disable ? "" :
drv->discard == discard_enable ? " discard=enable" : " discard=besteffort",
drv->copyonread ? " copyonread" : "",
drv->blocksize ? " blocksize=" : "",
drv->blocksize ? s_blocksize : "");
}
/**
* Add C<struct drive> to the C<g-E<gt>drives> vector at the given
* index C<drv_index>. If the array isn't large enough it is
* reallocated. The index must not contain a drive already.
*/
static void
add_drive_to_handle_at (guestfs_h *g, struct drive *d, size_t drv_index)
{
if (drv_index >= g->nr_drives) {
g->drives = safe_realloc (g, g->drives,
sizeof (struct drive *) * (drv_index + 1));
while (g->nr_drives < drv_index+1) {
g->drives[g->nr_drives] = NULL;
g->nr_drives++;
}
}
assert (g->drives[drv_index] == NULL);
g->drives[drv_index] = d;
}
/**
* Add struct drive to the end of the C<g-E<gt>drives> vector in the
* handle.
*/
static void
add_drive_to_handle (guestfs_h *g, struct drive *d)
{
add_drive_to_handle_at (g, d, g->nr_drives);
}
/**
* Called during launch to add a dummy slot to C<g-E<gt>drives>.
*/
void
guestfs_int_add_dummy_appliance_drive (guestfs_h *g)
{
struct drive *drv;
drv = create_drive_dummy (g);
add_drive_to_handle (g, drv);
}
/**
* Free up all the drives in the handle.
*/
void
guestfs_int_free_drives (guestfs_h *g)
{
struct drive *drv;
size_t i;
ITER_DRIVES (g, i, drv) {
free_drive_struct (drv);
}
free (g->drives);
g->drives = NULL;
g->nr_drives = 0;
}
/**
* Check string parameter matches regular expression
* C<^[-_[:alnum:]]+$> (in C locale).
*/
#define VALID_FORMAT(str) \
guestfs_int_string_is_valid ((str), 1, 0, \
VALID_FLAG_ALPHA|VALID_FLAG_DIGIT, "-_")
/**
* Check the disk label is reasonable. It can't contain certain
* characters, eg. C<'/'>, C<','>. However be stricter here and
* ensure it's just alphabetic and E<le> 20 characters in length.
*/
#define VALID_DISK_LABEL(str) \
guestfs_int_string_is_valid ((str), 1, 20, VALID_FLAG_ALPHA, NULL)
/**
* Check the server hostname is reasonable.
*/
#define VALID_HOSTNAME(str) \
guestfs_int_string_is_valid ((str), 1, 255, \
VALID_FLAG_ALPHA|VALID_FLAG_DIGIT, "-.:[]")
/**
* Check the port number is reasonable.
*/
static int
valid_port (int port)
{
if (port <= 0 || port > 65535)
return 0;
return 1;
}
/**
* Check the block size is reasonable. It can't be other then 512 or 4096.
*/
static int
valid_blocksize (int blocksize)
{
if (blocksize == 512 || blocksize == 4096)
return 1;
return 0;
}
static int
parse_one_server (guestfs_h *g, const char *server, struct drive_server *ret)
{
char *hostname;
char *port_str;
int port;
/* Note! Do not set any string field in *ret until you know the
* function will return successfully. Otherwise there can be a
* double-free in parse_servers -> free_drive_servers below.
*/
ret->transport = drive_transport_none;
if (STRPREFIX (server, "tcp:")) {
/* Explicit tcp: prefix means to skip the unix test. */
server += 4;
ret->transport = drive_transport_tcp;
goto skip_unix;
}
if (STRPREFIX (server, "unix:")) {
if (strlen (server) == 5) {
error (g, _("missing Unix domain socket path"));
return -1;
}
ret->transport = drive_transport_unix;
ret->u.socket = safe_strdup (g, server+5);
ret->port = 0;
return 0;
}
skip_unix:
if (match2 (g, server, re_hostname_port, &hostname, &port_str)) {
if (sscanf (port_str, "%d", &port) != 1 || !valid_port (port)) {
error (g, _("invalid port number %s"), port_str);
free (hostname);
free (port_str);
return -1;
}
free (port_str);
if (!VALID_HOSTNAME (hostname)) {
error (g, _("invalid hostname %s"), hostname);
free (hostname);
return -1;
}
ret->u.hostname = hostname;
ret->port = port;
return 0;
}
/* Doesn't match anything above, so assume it's a bare hostname. */
if (!VALID_HOSTNAME (server)) {
error (g, _("invalid hostname or server string %s"), server);
return -1;
}
ret->u.hostname = safe_strdup (g, server);
ret->port = 0;
return 0;
}
static ssize_t
parse_servers (guestfs_h *g, char *const *strs,
struct drive_server **servers_rtn)
{
size_t i;
const size_t n = guestfs_int_count_strings (strs);
struct drive_server *servers;
if (n == 0) {
*servers_rtn = NULL;
return 0;
}
/* Must use calloc here to avoid freeing garbage along the error
* path below.
*/
servers = safe_calloc (g, n, sizeof (struct drive_server));
for (i = 0; i < n; ++i) {
if (parse_one_server (g, strs[i], &servers[i]) == -1) {
free_drive_servers (servers, i);
return -1;
}
}
*servers_rtn = servers;
return n;
}
int
guestfs_impl_add_drive_opts (guestfs_h *g,
const char *filename,
const struct guestfs_add_drive_opts_argv *optargs)
{
struct drive_create_data data = { .nr_servers = 0, .exportname = filename };
const char *protocol;
struct drive *drv;
data.readonly = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK
? optargs->readonly : false;
data.format = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK
? optargs->format : NULL;
data.name = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_NAME_BITMASK
? optargs->name : NULL;
data.disk_label = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_LABEL_BITMASK
? optargs->label : NULL;
protocol = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_PROTOCOL_BITMASK
? optargs->protocol : "file";
if (optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_SERVER_BITMASK) {
ssize_t r = parse_servers (g, optargs->server, &data.servers);
if (r == -1)
return -1;
data.nr_servers = r;
}
data.username = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_USERNAME_BITMASK
? optargs->username : NULL;
data.secret = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_SECRET_BITMASK
? optargs->secret : NULL;
data.cachemode = optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_CACHEMODE_BITMASK
? optargs->cachemode : NULL;
if (optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_DISCARD_BITMASK) {
if (STREQ (optargs->discard, "disable"))
data.discard = discard_disable;
else if (STREQ (optargs->discard, "enable"))
data.discard = discard_enable;
else if (STREQ (optargs->discard, "besteffort"))
data.discard = discard_besteffort;
else {
error (g, _("discard parameter must be disable, enable or besteffort"));
free_drive_servers (data.servers, data.nr_servers);
return -1;
}
}
else
data.discard = discard_disable;
data.copyonread =
optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_COPYONREAD_BITMASK
? optargs->copyonread : false;
data.blocksize =
optargs->bitmask & GUESTFS_ADD_DRIVE_OPTS_BLOCKSIZE_BITMASK
? optargs->blocksize : 0;
if (data.readonly && data.discard == discard_enable) {
error (g, _("discard support cannot be enabled on read-only drives"));
free_drive_servers (data.servers, data.nr_servers);
return -1;
}
if (data.format && !VALID_FORMAT (data.format)) {
error (g, _("%s parameter is empty or contains disallowed characters"),
"format");
free_drive_servers (data.servers, data.nr_servers);
return -1;
}
if (data.disk_label && !VALID_DISK_LABEL (data.disk_label)) {
error (g, _("label parameter is empty, too long, or contains disallowed characters"));
free_drive_servers (data.servers, data.nr_servers);
return -1;
}
if (data.cachemode &&
!(STREQ (data.cachemode, "writeback") || STREQ (data.cachemode, "unsafe"))) {
error (g, _("cachemode parameter must be writeback (default) or unsafe"));
free_drive_servers (data.servers, data.nr_servers);
return -1;
}
if (data.blocksize && !valid_blocksize (data.blocksize)) {
error (g, _("%s parameter is invalid"), "blocksize");
free_drive_servers (data.servers, data.nr_servers);
return -1;
}
if (STREQ (protocol, "file")) {
if (data.servers != NULL) {
error (g, _("you cannot specify a server with file-backed disks"));
free_drive_servers (data.servers, data.nr_servers);
return -1;
}
if (data.username != NULL) {
error (g, _("you cannot specify a username with file-backed disks"));
return -1;
}
if (data.secret != NULL) {
error (g, _("you cannot specify a secret with file-backed disks"));
return -1;
}
if (STREQ (filename, "/dev/null"))
drv = create_drive_dev_null (g, &data);
else {
/* We have to check for the existence of the file since that's
* required by the API.
*/
if (access (filename, R_OK) == -1) {
perrorf (g, "%s", filename);
return -1;
}
drv = create_drive_file (g, &data);
}
}
else if (STREQ (protocol, "ftp")) {
data.protocol = drive_protocol_ftp;
drv = create_drive_curl (g, &data);
}
else if (STREQ (protocol, "ftps")) {
data.protocol = drive_protocol_ftps;
drv = create_drive_curl (g, &data);
}
else if (STREQ (protocol, "http")) {
data.protocol = drive_protocol_http;
drv = create_drive_curl (g, &data);
}
else if (STREQ (protocol, "https")) {
data.protocol = drive_protocol_https;
drv = create_drive_curl (g, &data);
}
else if (STREQ (protocol, "iscsi")) {
data.protocol = drive_protocol_iscsi;
drv = create_drive_iscsi (g, &data);
}
else if (STREQ (protocol, "nbd")) {
data.protocol = drive_protocol_nbd;
drv = create_drive_nbd (g, &data);
}
else if (STREQ (protocol, "rbd")) {
data.protocol = drive_protocol_rbd;
drv = create_drive_rbd (g, &data);
}
else if (STREQ (protocol, "ssh")) {
data.protocol = drive_protocol_ssh;
drv = create_drive_ssh (g, &data);
}
else {
error (g, _("unknown protocol %s"), protocol);
drv = NULL; /*FALLTHROUGH*/
}
if (drv == NULL) {
free_drive_servers (data.servers, data.nr_servers);
return -1;
}
/* Add the drive. */
if (g->state == CONFIG) {
/* Not hotplugging, so just add it to the handle. */
add_drive_to_handle (g, drv); /* drv is now owned by the handle */
return 0;
}
/* ... else the old hotplugging case */
error (g, _("hotplugging support was removed in libguestfs 1.48"));
return -1;
}
int
guestfs_impl_add_drive_ro (guestfs_h *g, const char *filename)
{
const struct guestfs_add_drive_opts_argv optargs = {
.bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK,
.readonly = true,
};
return guestfs_add_drive_opts_argv (g, filename, &optargs);
}
int
guestfs_impl_add_drive_with_if (guestfs_h *g, const char *filename,
const char *iface ATTRIBUTE_UNUSED)
{
return guestfs_add_drive_opts_argv (g, filename, NULL);
}
int
guestfs_impl_add_drive_ro_with_if (guestfs_h *g, const char *filename,
const char *iface ATTRIBUTE_UNUSED)
{
const struct guestfs_add_drive_opts_argv optargs = {
.bitmask = GUESTFS_ADD_DRIVE_OPTS_READONLY_BITMASK,
.readonly = true,
};
return guestfs_add_drive_opts_argv (g, filename, &optargs);
}
int
guestfs_impl_add_drive_scratch (guestfs_h *g, int64_t size,
const struct guestfs_add_drive_scratch_argv *optargs)
{
struct guestfs_add_drive_opts_argv add_drive_optargs = { .bitmask = 0 };
CLEANUP_FREE char *filename = NULL;
/* Some parameters we always set. */
add_drive_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_FORMAT_BITMASK;
add_drive_optargs.format = "raw";
add_drive_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_CACHEMODE_BITMASK;
add_drive_optargs.cachemode = "unsafe";
/* Copy the optional arguments through to guestfs_add_drive_opts. */
if (optargs->bitmask & GUESTFS_ADD_DRIVE_SCRATCH_NAME_BITMASK) {
add_drive_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_NAME_BITMASK;
add_drive_optargs.name = optargs->name;
}
if (optargs->bitmask & GUESTFS_ADD_DRIVE_SCRATCH_LABEL_BITMASK) {
add_drive_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_LABEL_BITMASK;
add_drive_optargs.label = optargs->label;
}
if (optargs->bitmask & GUESTFS_ADD_DRIVE_SCRATCH_BLOCKSIZE_BITMASK) {
add_drive_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_BLOCKSIZE_BITMASK;
add_drive_optargs.blocksize = optargs->blocksize;
}
/* Create the temporary file. We don't have to worry about cleanup
* because everything in g->tmpdir is 'rm -rf'd when the handle is
* closed.
*/
filename = guestfs_int_make_temp_path (g, "scratch", "img");
if (!filename)
return -1;
/* Create a raw format temporary disk. */
if (guestfs_disk_create (g, filename, "raw", size, -1) == -1)
return -1;
/* Call guestfs_add_drive_opts to add the drive. */
return guestfs_add_drive_opts_argv (g, filename, &add_drive_optargs);
}
int
guestfs_impl_add_cdrom (guestfs_h *g, const char *filename)
{
return guestfs_impl_add_drive_ro (g, filename);
}
int
guestfs_impl_remove_drive (guestfs_h *g, const char *label)
{
error (g, _("hotplugging support was removed in libguestfs 1.48"));
return -1;
}
/**
* Checkpoint and roll back drives, so that groups of drives can be
* added atomically. Only used by L<guestfs(3)/guestfs_add_domain>.
*/
size_t
guestfs_int_checkpoint_drives (guestfs_h *g)
{
return g->nr_drives;
}
void
guestfs_int_rollback_drives (guestfs_h *g, size_t old_i)
{
size_t i;
for (i = old_i; i < g->nr_drives; ++i) {
if (g->drives[i])
free_drive_struct (g->drives[i]);
}
g->nr_drives = old_i;
}
/**
* Internal function to return the list of drives.
*/
char **
guestfs_impl_debug_drives (guestfs_h *g)
{
size_t i;
DECLARE_STRINGSBUF (ret);
struct drive *drv;
ITER_DRIVES (g, i, drv) {
guestfs_int_add_string_nodup (g, &ret, drive_to_string (g, drv));
}
guestfs_int_end_stringsbuf (g, &ret);
return ret.argv; /* caller frees */
}
static void
free_drive_source (struct drive_source *src)
{
if (src) {
free (src->format);
free (src->u.path);
free (src->username);
free (src->secret);
free_drive_servers (src->servers, src->nr_servers);
}
}
int
guestfs_impl_device_index (guestfs_h *g, const char *device)
{
size_t len;
ssize_t r = -1;
/* /dev/hd etc. */
if (STRPREFIX (device, "/dev/") &&
strchr (device+5, '/') == NULL && /* not an LV name */
device[5] != 'm' && /* not /dev/md - RHBZ#1414682 */
((len = strcspn (device+5, "d")) > 0 && len <= 2))
r = guestfs_int_drive_index (device+5+len+1);
if (r == -1)
error (g, _("%s: device not found"), device);
return r;
}
char *
guestfs_impl_device_name (guestfs_h *g, int index)
{
char drive_name[64];
if (index < 0 || index >= g->nr_drives) {
guestfs_int_error_errno (g, EINVAL, _("drive index out of range"));
return NULL;
}
guestfs_int_drive_name (index, drive_name);
return safe_asprintf (g, "/dev/sd%s", drive_name);
}