Merge remote-tracking branch 'aneesh/for-upstream' into staging

* aneesh/for-upstream:
  hw/9pfs: Add support to use named socket for proxy FS
  hw/9pfs: man page for proxy helper
  hw/9pfs: Documentation changes related to proxy fs
  hw/9pfs: Proxy getversion
  hw/9pfs: xattr interfaces in proxy filesystem driver
  hw/9pfs: File ownership and others
  hw/9pfs: Add stat/readlink/statfs for proxy FS
  hw/9pfs: Create other filesystem objects
  hw/9pfs: Open and create files
  hw/9pfs: File system helper process for qemu 9p proxy FS
  hw/9pfs: Add new proxy filesystem driver
  hw/9pfs: Add validation to {un}marshal code
  hw/9pfs: Move pdu_marshal/unmarshal code to a seperate file
  hw/9pfs: Move opt validation to FsDriver callback
This commit is contained in:
Anthony Liguori 2012-01-06 08:14:28 -06:00
commit 74b728e4f3
20 changed files with 3415 additions and 515 deletions

View File

@ -38,6 +38,7 @@ LIBS+=-lz $(LIBS_TOOLS)
ifdef BUILD_DOCS ifdef BUILD_DOCS
DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 qemu-nbd.8 QMP/qmp-commands.txt DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 qemu-nbd.8 QMP/qmp-commands.txt
DOCS+=fsdev/virtfs-proxy-helper.1
else else
DOCS= DOCS=
endif endif
@ -155,6 +156,9 @@ qemu-img$(EXESUF): qemu-img.o $(tools-obj-y) $(block-obj-y)
qemu-nbd$(EXESUF): qemu-nbd.o $(tools-obj-y) $(block-obj-y) qemu-nbd$(EXESUF): qemu-nbd.o $(tools-obj-y) $(block-obj-y)
qemu-io$(EXESUF): qemu-io.o cmd.o $(tools-obj-y) $(block-obj-y) qemu-io$(EXESUF): qemu-io.o cmd.o $(tools-obj-y) $(block-obj-y)
fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/virtio-9p-marshal.o oslib-posix.o $(trace-obj-y)
fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap
qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx
$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@") $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@")
@ -286,7 +290,10 @@ ifdef CONFIG_POSIX
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8" $(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8"
$(INSTALL_DATA) qemu-nbd.8 "$(DESTDIR)$(mandir)/man8" $(INSTALL_DATA) qemu-nbd.8 "$(DESTDIR)$(mandir)/man8"
endif endif
ifdef CONFIG_VIRTFS
$(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1"
$(INSTALL_DATA) fsdev/virtfs-proxy-helper.1 "$(DESTDIR)$(mandir)/man1"
endif
install-sysconfig: install-sysconfig:
$(INSTALL_DIR) "$(DESTDIR)$(sysconfdir)/qemu" $(INSTALL_DIR) "$(DESTDIR)$(sysconfdir)/qemu"
$(INSTALL_DATA) $(SRC_PATH)/sysconfigs/target/target-x86_64.conf "$(DESTDIR)$(sysconfdir)/qemu" $(INSTALL_DATA) $(SRC_PATH)/sysconfigs/target/target-x86_64.conf "$(DESTDIR)$(sysconfdir)/qemu"
@ -370,6 +377,12 @@ qemu-img.1: qemu-img.texi qemu-img-cmds.texi
pod2man --section=1 --center=" " --release=" " qemu-img.pod > $@, \ pod2man --section=1 --center=" " --release=" " qemu-img.pod > $@, \
" GEN $@") " GEN $@")
fsdev/virtfs-proxy-helper.1: fsdev/virtfs-proxy-helper.texi
$(call quiet-command, \
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< fsdev/virtfs-proxy-helper.pod && \
pod2man --section=1 --center=" " --release=" " fsdev/virtfs-proxy-helper.pod > $@, \
" GEN $@")
qemu-nbd.8: qemu-nbd.texi qemu-nbd.8: qemu-nbd.texi
$(call quiet-command, \ $(call quiet-command, \
perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-nbd.pod && \ perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-nbd.pod && \

View File

@ -61,7 +61,7 @@ ifeq ($(CONFIG_VIRTIO)$(CONFIG_VIRTFS)$(CONFIG_PCI),yyy)
# Lots of the fsdev/9pcode is pulled in by vl.c via qemu_fsdev_add. # Lots of the fsdev/9pcode is pulled in by vl.c via qemu_fsdev_add.
# only pull in the actual virtio-9p device if we also enabled virtio. # only pull in the actual virtio-9p device if we also enabled virtio.
CONFIG_REALLY_VIRTFS=y CONFIG_REALLY_VIRTFS=y
fsdev-nested-y = qemu-fsdev.o fsdev-nested-y = qemu-fsdev.o virtio-9p-marshal.o
else else
fsdev-nested-y = qemu-fsdev-dummy.o fsdev-nested-y = qemu-fsdev-dummy.o
endif endif
@ -311,6 +311,7 @@ hw-obj-$(CONFIG_SOUND) += $(sound-obj-y)
9pfs-nested-$(CONFIG_VIRTFS) += virtio-9p-coth.o cofs.o codir.o cofile.o 9pfs-nested-$(CONFIG_VIRTFS) += virtio-9p-coth.o cofs.o codir.o cofile.o
9pfs-nested-$(CONFIG_VIRTFS) += coxattr.o virtio-9p-synth.o 9pfs-nested-$(CONFIG_VIRTFS) += coxattr.o virtio-9p-synth.o
9pfs-nested-$(CONFIG_OPEN_BY_HANDLE) += virtio-9p-handle.o 9pfs-nested-$(CONFIG_OPEN_BY_HANDLE) += virtio-9p-handle.o
9pfs-nested-$(CONFIG_VIRTFS) += virtio-9p-proxy.o
hw-obj-$(CONFIG_REALLY_VIRTFS) += $(addprefix 9pfs/, $(9pfs-nested-y)) hw-obj-$(CONFIG_REALLY_VIRTFS) += $(addprefix 9pfs/, $(9pfs-nested-y))
$(addprefix 9pfs/, $(9pfs-nested-y)): QEMU_CFLAGS+=$(GLIB_CFLAGS) $(addprefix 9pfs/, $(9pfs-nested-y)): QEMU_CFLAGS+=$(GLIB_CFLAGS)

19
configure vendored
View File

@ -1966,6 +1966,22 @@ else
exit 1 exit 1
fi fi
##########################################
# libcap probe
if test "$cap" != "no" ; then
cat > $TMPC <<EOF
#include <stdio.h>
#include <sys/capability.h>
int main(void) { cap_t caps; caps = cap_init(); }
EOF
if compile_prog "" "-lcap" ; then
cap=yes
else
cap=no
fi
fi
########################################## ##########################################
# pthread probe # pthread probe
PTHREADLIBS_LIST="-pthread -lpthread -lpthreadGC2" PTHREADLIBS_LIST="-pthread -lpthread -lpthreadGC2"
@ -2767,6 +2783,9 @@ confdir=$sysconfdir$confsuffix
tools= tools=
if test "$softmmu" = yes ; then if test "$softmmu" = yes ; then
tools="qemu-img\$(EXESUF) qemu-io\$(EXESUF) $tools" tools="qemu-img\$(EXESUF) qemu-io\$(EXESUF) $tools"
if [ "$cap" = "yes" -a "$linux" = "yes" ] ; then
tools="$tools fsdev/virtfs-proxy-helper\$(EXESUF)"
fi
if [ "$linux" = "yes" -o "$bsd" = "yes" -o "$solaris" = "yes" ] ; then if [ "$linux" = "yes" -o "$bsd" = "yes" -o "$solaris" = "yes" ] ; then
tools="qemu-nbd\$(EXESUF) $tools" tools="qemu-nbd\$(EXESUF) $tools"
if [ "$guest_agent" = "yes" ]; then if [ "$guest_agent" = "yes" ]; then

View File

@ -57,10 +57,22 @@ typedef struct extended_ops {
*/ */
#define V9FS_SM_NONE 0x00000010 #define V9FS_SM_NONE 0x00000010
#define V9FS_RDONLY 0x00000020 #define V9FS_RDONLY 0x00000020
#define V9FS_PROXY_SOCK_FD 0x00000040
#define V9FS_PROXY_SOCK_NAME 0x00000080
#define V9FS_SEC_MASK 0x0000001C #define V9FS_SEC_MASK 0x0000001C
typedef struct FileOperations FileOperations;
/*
* Structure to store the various fsdev's passed through command line.
*/
typedef struct FsDriverEntry {
char *fsdev_id;
char *path;
int export_flags;
FileOperations *ops;
} FsDriverEntry;
typedef struct FsContext typedef struct FsContext
{ {
@ -82,8 +94,9 @@ typedef union V9fsFidOpenState V9fsFidOpenState;
void cred_init(FsCred *); void cred_init(FsCred *);
typedef struct FileOperations struct FileOperations
{ {
int (*parse_opts)(QemuOpts *, struct FsDriverEntry *);
int (*init)(struct FsContext *); int (*init)(struct FsContext *);
int (*lstat)(FsContext *, V9fsPath *, struct stat *); int (*lstat)(FsContext *, V9fsPath *, struct stat *);
ssize_t (*readlink)(FsContext *, V9fsPath *, char *, size_t); ssize_t (*readlink)(FsContext *, V9fsPath *, char *, size_t);
@ -128,6 +141,6 @@ typedef struct FileOperations
V9fsPath *newdir, const char *new_name); V9fsPath *newdir, const char *new_name);
int (*unlinkat)(FsContext *ctx, V9fsPath *dir, const char *name, int flags); int (*unlinkat)(FsContext *ctx, V9fsPath *dir, const char *name, int flags);
void *opaque; void *opaque;
} FileOperations; };
#endif #endif

View File

@ -27,16 +27,15 @@ static FsDriverTable FsDrivers[] = {
{ .name = "handle", .ops = &handle_ops}, { .name = "handle", .ops = &handle_ops},
#endif #endif
{ .name = "synth", .ops = &synth_ops}, { .name = "synth", .ops = &synth_ops},
{ .name = "proxy", .ops = &proxy_ops},
}; };
int qemu_fsdev_add(QemuOpts *opts) int qemu_fsdev_add(QemuOpts *opts)
{ {
struct FsDriverListEntry *fsle;
int i; int i;
struct FsDriverListEntry *fsle;
const char *fsdev_id = qemu_opts_id(opts); const char *fsdev_id = qemu_opts_id(opts);
const char *fsdriver = qemu_opt_get(opts, "fsdriver"); const char *fsdriver = qemu_opt_get(opts, "fsdriver");
const char *path = qemu_opt_get(opts, "path");
const char *sec_model = qemu_opt_get(opts, "security_model");
const char *writeout = qemu_opt_get(opts, "writeout"); const char *writeout = qemu_opt_get(opts, "writeout");
bool ro = qemu_opt_get_bool(opts, "readonly", 0); bool ro = qemu_opt_get_bool(opts, "readonly", 0);
@ -61,29 +60,9 @@ int qemu_fsdev_add(QemuOpts *opts)
return -1; return -1;
} }
if (!strcmp(fsdriver, "local") && !sec_model) { fsle = g_malloc0(sizeof(*fsle));
fprintf(stderr, "security model not specified, "
"local fs needs security model\nvalid options are:"
"\tsecurity_model=[passthrough|mapped|none]\n");
return -1;
}
if (strcmp(fsdriver, "local") && sec_model) {
fprintf(stderr, "only local fs driver needs security model\n");
return -1;
}
if (!path) {
fprintf(stderr, "fsdev: No path specified.\n");
return -1;
}
fsle = g_malloc(sizeof(*fsle));
fsle->fse.fsdev_id = g_strdup(fsdev_id); fsle->fse.fsdev_id = g_strdup(fsdev_id);
fsle->fse.path = g_strdup(path);
fsle->fse.ops = FsDrivers[i].ops; fsle->fse.ops = FsDrivers[i].ops;
fsle->fse.export_flags = 0;
if (writeout) { if (writeout) {
if (!strcmp(writeout, "immediate")) { if (!strcmp(writeout, "immediate")) {
fsle->fse.export_flags |= V9FS_IMMEDIATE_WRITEOUT; fsle->fse.export_flags |= V9FS_IMMEDIATE_WRITEOUT;
@ -95,22 +74,12 @@ int qemu_fsdev_add(QemuOpts *opts)
fsle->fse.export_flags &= ~V9FS_RDONLY; fsle->fse.export_flags &= ~V9FS_RDONLY;
} }
if (strcmp(fsdriver, "local")) { if (fsle->fse.ops->parse_opts) {
goto done; if (fsle->fse.ops->parse_opts(opts, &fsle->fse)) {
return -1;
}
} }
if (!strcmp(sec_model, "passthrough")) {
fsle->fse.export_flags |= V9FS_SM_PASSTHROUGH;
} else if (!strcmp(sec_model, "mapped")) {
fsle->fse.export_flags |= V9FS_SM_MAPPED;
} else if (!strcmp(sec_model, "none")) {
fsle->fse.export_flags |= V9FS_SM_NONE;
} else {
fprintf(stderr, "Invalid security model %s specified, valid options are"
"\n\t [passthrough|mapped|none]\n", sec_model);
return -1;
}
done:
QTAILQ_INSERT_TAIL(&fsdriver_entries, fsle, next); QTAILQ_INSERT_TAIL(&fsdriver_entries, fsle, next);
return 0; return 0;
} }

View File

@ -34,16 +34,6 @@ typedef struct FsDriverTable {
FileOperations *ops; FileOperations *ops;
} FsDriverTable; } FsDriverTable;
/*
* Structure to store the various fsdev's passed through command line.
*/
typedef struct FsDriverEntry {
char *fsdev_id;
char *path;
int export_flags;
FileOperations *ops;
} FsDriverEntry;
typedef struct FsDriverListEntry { typedef struct FsDriverListEntry {
FsDriverEntry fse; FsDriverEntry fse;
QTAILQ_ENTRY(FsDriverListEntry) next; QTAILQ_ENTRY(FsDriverListEntry) next;
@ -54,4 +44,5 @@ FsDriverEntry *get_fsdev_fsentry(char *id);
extern FileOperations local_ops; extern FileOperations local_ops;
extern FileOperations handle_ops; extern FileOperations handle_ops;
extern FileOperations synth_ops; extern FileOperations synth_ops;
extern FileOperations proxy_ops;
#endif #endif

1120
fsdev/virtfs-proxy-helper.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
@example
@c man begin SYNOPSIS
usage: virtfs-proxy-helper options
@c man end
@end example
@c man begin DESCRIPTION
@table @description
Pass-through security model in QEMU 9p server needs root privilege to do
few file operations (like chown, chmod to any mode/uid:gid). There are two
issues in pass-through security model
1) TOCTTOU vulnerability: Following symbolic links in the server could
provide access to files beyond 9p export path.
2) Running QEMU with root privilege could be a security issue.
To overcome above issues, following approach is used: A new filesytem
type 'proxy' is introduced. Proxy FS uses chroot + socket combination
for securing the vulnerability known with following symbolic links.
Intention of adding a new filesystem type is to allow qemu to run
in non-root mode, but doing privileged operations using socket IO.
Proxy helper(a stand alone binary part of qemu) is invoked with
root privileges. Proxy helper chroots into 9p export path and creates
a socket pair or a named socket based on the command line parameter.
Qemu and proxy helper communicate using this socket. QEMU proxy fs
driver sends filesystem request to proxy helper and receives the
response from it.
Proxy helper is designed so that it can drop the root privilege with
retaining capbilities needed for doing filesystem operations only.
@end table
@c man end
@c man begin OPTIONS
The following options are supported:
@table @option
@item -h
@findex -h
Display help and exit
@item -p|--path path
Path to export for proxy filesystem driver
@item -f|--fd socket-id
Use given file descriptor as socket descriptor for communicating with
qemu proxy fs drier. Usually a helper like libvirt will create
socketpair and pass one of the fds as parameter to -f|--fd
@item -s|--socket socket-file
Creates named socket file for communicating with qemu proxy fs driver
@item -u|--uid uid -g|--gid gid
uid:gid combination to give access to named socket file
@item -n|--nodaemon
Run as a normal program. By default program will run in daemon mode
@end table
@c man end
@setfilename virtfs-proxy-helper
@settitle QEMU 9p virtfs proxy filesystem helper
@c man begin AUTHOR
M. Mohan Kumar
@c man end

323
fsdev/virtio-9p-marshal.c Normal file
View File

@ -0,0 +1,323 @@
/*
* Virtio 9p backend
*
* Copyright IBM, Corp. 2010
*
* Authors:
* Anthony Liguori <aliguori@us.ibm.com>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*
*/
#include <glib.h>
#include <glib/gprintf.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/time.h>
#include <utime.h>
#include <sys/uio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include "compiler.h"
#include "virtio-9p-marshal.h"
#include "bswap.h"
void v9fs_string_free(V9fsString *str)
{
g_free(str->data);
str->data = NULL;
str->size = 0;
}
void v9fs_string_null(V9fsString *str)
{
v9fs_string_free(str);
}
void GCC_FMT_ATTR(2, 3)
v9fs_string_sprintf(V9fsString *str, const char *fmt, ...)
{
va_list ap;
v9fs_string_free(str);
va_start(ap, fmt);
str->size = g_vasprintf(&str->data, fmt, ap);
va_end(ap);
}
void v9fs_string_copy(V9fsString *lhs, V9fsString *rhs)
{
v9fs_string_free(lhs);
v9fs_string_sprintf(lhs, "%s", rhs->data);
}
static ssize_t v9fs_packunpack(void *addr, struct iovec *sg, int sg_count,
size_t offset, size_t size, int pack)
{
int i = 0;
size_t copied = 0;
size_t req_size = size;
for (i = 0; size && i < sg_count; i++) {
size_t len;
if (offset >= sg[i].iov_len) {
/* skip this sg */
offset -= sg[i].iov_len;
continue;
} else {
len = MIN(sg[i].iov_len - offset, size);
if (pack) {
memcpy(sg[i].iov_base + offset, addr, len);
} else {
memcpy(addr, sg[i].iov_base + offset, len);
}
size -= len;
copied += len;
addr += len;
if (size) {
offset = 0;
continue;
}
}
}
if (copied < req_size) {
/*
* We copied less that requested size. error out
*/
return -ENOBUFS;
}
return copied;
}
static ssize_t v9fs_unpack(void *dst, struct iovec *out_sg, int out_num,
size_t offset, size_t size)
{
return v9fs_packunpack(dst, out_sg, out_num, offset, size, 0);
}
ssize_t v9fs_pack(struct iovec *in_sg, int in_num, size_t offset,
const void *src, size_t size)
{
return v9fs_packunpack((void *)src, in_sg, in_num, offset, size, 1);
}
ssize_t v9fs_unmarshal(struct iovec *out_sg, int out_num, size_t offset,
int bswap, const char *fmt, ...)
{
int i;
va_list ap;
ssize_t copied = 0;
size_t old_offset = offset;
va_start(ap, fmt);
for (i = 0; fmt[i]; i++) {
switch (fmt[i]) {
case 'b': {
uint8_t *valp = va_arg(ap, uint8_t *);
copied = v9fs_unpack(valp, out_sg, out_num, offset, sizeof(*valp));
break;
}
case 'w': {
uint16_t val, *valp;
valp = va_arg(ap, uint16_t *);
copied = v9fs_unpack(&val, out_sg, out_num, offset, sizeof(val));
if (bswap) {
*valp = le16_to_cpu(val);
} else {
*valp = val;
}
break;
}
case 'd': {
uint32_t val, *valp;
valp = va_arg(ap, uint32_t *);
copied = v9fs_unpack(&val, out_sg, out_num, offset, sizeof(val));
if (bswap) {
*valp = le32_to_cpu(val);
} else {
*valp = val;
}
break;
}
case 'q': {
uint64_t val, *valp;
valp = va_arg(ap, uint64_t *);
copied = v9fs_unpack(&val, out_sg, out_num, offset, sizeof(val));
if (bswap) {
*valp = le64_to_cpu(val);
} else {
*valp = val;
}
break;
}
case 's': {
V9fsString *str = va_arg(ap, V9fsString *);
copied = v9fs_unmarshal(out_sg, out_num, offset, bswap,
"w", &str->size);
if (copied > 0) {
offset += copied;
str->data = g_malloc(str->size + 1);
copied = v9fs_unpack(str->data, out_sg, out_num, offset,
str->size);
if (copied > 0) {
str->data[str->size] = 0;
} else {
v9fs_string_free(str);
}
}
break;
}
case 'Q': {
V9fsQID *qidp = va_arg(ap, V9fsQID *);
copied = v9fs_unmarshal(out_sg, out_num, offset, bswap, "bdq",
&qidp->type, &qidp->version, &qidp->path);
break;
}
case 'S': {
V9fsStat *statp = va_arg(ap, V9fsStat *);
copied = v9fs_unmarshal(out_sg, out_num, offset, bswap,
"wwdQdddqsssssddd",
&statp->size, &statp->type, &statp->dev,
&statp->qid, &statp->mode, &statp->atime,
&statp->mtime, &statp->length,
&statp->name, &statp->uid, &statp->gid,
&statp->muid, &statp->extension,
&statp->n_uid, &statp->n_gid,
&statp->n_muid);
break;
}
case 'I': {
V9fsIattr *iattr = va_arg(ap, V9fsIattr *);
copied = v9fs_unmarshal(out_sg, out_num, offset, bswap,
"ddddqqqqq",
&iattr->valid, &iattr->mode,
&iattr->uid, &iattr->gid, &iattr->size,
&iattr->atime_sec, &iattr->atime_nsec,
&iattr->mtime_sec, &iattr->mtime_nsec);
break;
}
default:
break;
}
if (copied < 0) {
va_end(ap);
return copied;
}
offset += copied;
}
va_end(ap);
return offset - old_offset;
}
ssize_t v9fs_marshal(struct iovec *in_sg, int in_num, size_t offset,
int bswap, const char *fmt, ...)
{
int i;
va_list ap;
ssize_t copied = 0;
size_t old_offset = offset;
va_start(ap, fmt);
for (i = 0; fmt[i]; i++) {
switch (fmt[i]) {
case 'b': {
uint8_t val = va_arg(ap, int);
copied = v9fs_pack(in_sg, in_num, offset, &val, sizeof(val));
break;
}
case 'w': {
uint16_t val;
if (bswap) {
cpu_to_le16w(&val, va_arg(ap, int));
} else {
val = va_arg(ap, int);
}
copied = v9fs_pack(in_sg, in_num, offset, &val, sizeof(val));
break;
}
case 'd': {
uint32_t val;
if (bswap) {
cpu_to_le32w(&val, va_arg(ap, uint32_t));
} else {
val = va_arg(ap, uint32_t);
}
copied = v9fs_pack(in_sg, in_num, offset, &val, sizeof(val));
break;
}
case 'q': {
uint64_t val;
if (bswap) {
cpu_to_le64w(&val, va_arg(ap, uint64_t));
} else {
val = va_arg(ap, uint64_t);
}
copied = v9fs_pack(in_sg, in_num, offset, &val, sizeof(val));
break;
}
case 's': {
V9fsString *str = va_arg(ap, V9fsString *);
copied = v9fs_marshal(in_sg, in_num, offset, bswap,
"w", str->size);
if (copied > 0) {
offset += copied;
copied = v9fs_pack(in_sg, in_num, offset, str->data, str->size);
}
break;
}
case 'Q': {
V9fsQID *qidp = va_arg(ap, V9fsQID *);
copied = v9fs_marshal(in_sg, in_num, offset, bswap, "bdq",
qidp->type, qidp->version, qidp->path);
break;
}
case 'S': {
V9fsStat *statp = va_arg(ap, V9fsStat *);
copied = v9fs_marshal(in_sg, in_num, offset, bswap,
"wwdQdddqsssssddd",
statp->size, statp->type, statp->dev,
&statp->qid, statp->mode, statp->atime,
statp->mtime, statp->length, &statp->name,
&statp->uid, &statp->gid, &statp->muid,
&statp->extension, statp->n_uid,
statp->n_gid, statp->n_muid);
break;
}
case 'A': {
V9fsStatDotl *statp = va_arg(ap, V9fsStatDotl *);
copied = v9fs_marshal(in_sg, in_num, offset, bswap,
"qQdddqqqqqqqqqqqqqqq",
statp->st_result_mask,
&statp->qid, statp->st_mode,
statp->st_uid, statp->st_gid,
statp->st_nlink, statp->st_rdev,
statp->st_size, statp->st_blksize,
statp->st_blocks, statp->st_atime_sec,
statp->st_atime_nsec, statp->st_mtime_sec,
statp->st_mtime_nsec, statp->st_ctime_sec,
statp->st_ctime_nsec, statp->st_btime_sec,
statp->st_btime_nsec, statp->st_gen,
statp->st_data_version);
break;
}
default:
break;
}
if (copied < 0) {
va_end(ap);
return copied;
}
offset += copied;
}
va_end(ap);
return offset - old_offset;
}

90
fsdev/virtio-9p-marshal.h Normal file
View File

@ -0,0 +1,90 @@
#ifndef _QEMU_VIRTIO_9P_MARSHAL_H
#define _QEMU_VIRTIO_9P_MARSHAL_H
typedef struct V9fsString
{
uint16_t size;
char *data;
} V9fsString;
typedef struct V9fsQID
{
int8_t type;
int32_t version;
int64_t path;
} V9fsQID;
typedef struct V9fsStat
{
int16_t size;
int16_t type;
int32_t dev;
V9fsQID qid;
int32_t mode;
int32_t atime;
int32_t mtime;
int64_t length;
V9fsString name;
V9fsString uid;
V9fsString gid;
V9fsString muid;
/* 9p2000.u */
V9fsString extension;
int32_t n_uid;
int32_t n_gid;
int32_t n_muid;
} V9fsStat;
typedef struct V9fsIattr
{
int32_t valid;
int32_t mode;
int32_t uid;
int32_t gid;
int64_t size;
int64_t atime_sec;
int64_t atime_nsec;
int64_t mtime_sec;
int64_t mtime_nsec;
} V9fsIattr;
typedef struct V9fsStatDotl {
uint64_t st_result_mask;
V9fsQID qid;
uint32_t st_mode;
uint32_t st_uid;
uint32_t st_gid;
uint64_t st_nlink;
uint64_t st_rdev;
uint64_t st_size;
uint64_t st_blksize;
uint64_t st_blocks;
uint64_t st_atime_sec;
uint64_t st_atime_nsec;
uint64_t st_mtime_sec;
uint64_t st_mtime_nsec;
uint64_t st_ctime_sec;
uint64_t st_ctime_nsec;
uint64_t st_btime_sec;
uint64_t st_btime_nsec;
uint64_t st_gen;
uint64_t st_data_version;
} V9fsStatDotl;
static inline void v9fs_string_init(V9fsString *str)
{
str->data = NULL;
str->size = 0;
}
extern void v9fs_string_free(V9fsString *str);
extern void v9fs_string_null(V9fsString *str);
extern void v9fs_string_sprintf(V9fsString *str, const char *fmt, ...);
extern void v9fs_string_copy(V9fsString *lhs, V9fsString *rhs);
ssize_t v9fs_pack(struct iovec *in_sg, int in_num, size_t offset,
const void *src, size_t size);
ssize_t v9fs_unmarshal(struct iovec *out_sg, int out_num, size_t offset,
int bswap, const char *fmt, ...);
ssize_t v9fs_marshal(struct iovec *in_sg, int in_num, size_t offset,
int bswap, const char *fmt, ...);
#endif

View File

@ -77,16 +77,19 @@ VirtIODevice *virtio_9p_init(DeviceState *dev, V9fsConf *conf)
exit(1); exit(1);
} }
if (!fse->path || !conf->tag) { if (!conf->tag) {
/* we haven't specified a mount_tag or the path */ /* we haven't specified a mount_tag */
fprintf(stderr, "fsdev with id %s needs path " fprintf(stderr, "fsdev with id %s needs mount_tag arguments\n",
"and Virtio-9p device needs mount_tag arguments\n",
conf->fsdev_id); conf->fsdev_id);
exit(1); exit(1);
} }
s->ctx.export_flags = fse->export_flags; s->ctx.export_flags = fse->export_flags;
s->ctx.fs_root = g_strdup(fse->path); if (fse->path) {
s->ctx.fs_root = g_strdup(fse->path);
} else {
s->ctx.fs_root = NULL;
}
s->ctx.exops.get_st_gen = NULL; s->ctx.exops.get_st_gen = NULL;
if (fse->export_flags & V9FS_SM_PASSTHROUGH) { if (fse->export_flags & V9FS_SM_PASSTHROUGH) {

View File

@ -641,7 +641,27 @@ out:
return ret; return ret;
} }
static int handle_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
{
const char *sec_model = qemu_opt_get(opts, "security_model");
const char *path = qemu_opt_get(opts, "path");
if (sec_model) {
fprintf(stderr, "Invalid argument security_model specified with handle fsdriver\n");
return -1;
}
if (!path) {
fprintf(stderr, "fsdev: No path specified.\n");
return -1;
}
fse->path = g_strdup(path);
return 0;
}
FileOperations handle_ops = { FileOperations handle_ops = {
.parse_opts = handle_parse_opts,
.init = handle_init, .init = handle_init,
.lstat = handle_lstat, .lstat = handle_lstat,
.readlink = handle_readlink, .readlink = handle_readlink,

View File

@ -756,7 +756,41 @@ static int local_init(FsContext *ctx)
return err; return err;
} }
static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
{
const char *sec_model = qemu_opt_get(opts, "security_model");
const char *path = qemu_opt_get(opts, "path");
if (!sec_model) {
fprintf(stderr, "security model not specified, "
"local fs needs security model\nvalid options are:"
"\tsecurity_model=[passthrough|mapped|none]\n");
return -1;
}
if (!strcmp(sec_model, "passthrough")) {
fse->export_flags |= V9FS_SM_PASSTHROUGH;
} else if (!strcmp(sec_model, "mapped")) {
fse->export_flags |= V9FS_SM_MAPPED;
} else if (!strcmp(sec_model, "none")) {
fse->export_flags |= V9FS_SM_NONE;
} else {
fprintf(stderr, "Invalid security model %s specified, valid options are"
"\n\t [passthrough|mapped|none]\n", sec_model);
return -1;
}
if (!path) {
fprintf(stderr, "fsdev: No path specified.\n");
return -1;
}
fse->path = g_strdup(path);
return 0;
}
FileOperations local_ops = { FileOperations local_ops = {
.parse_opts = local_parse_opts,
.init = local_init, .init = local_init,
.lstat = local_lstat, .lstat = local_lstat,
.readlink = local_readlink, .readlink = local_readlink,

1210
hw/9pfs/virtio-9p-proxy.c Normal file

File diff suppressed because it is too large Load Diff

95
hw/9pfs/virtio-9p-proxy.h Normal file
View File

@ -0,0 +1,95 @@
/*
* Virtio 9p Proxy callback
*
* Copyright IBM, Corp. 2011
*
* Authors:
* M. Mohan Kumar <mohan@in.ibm.com>
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
#ifndef _QEMU_VIRTIO_9P_PROXY_H
#define _QEMU_VIRTIO_9P_PROXY_H
#define PROXY_MAX_IO_SZ (64 * 1024)
#define V9FS_FD_VALID INT_MAX
/*
* proxy iovec only support one element and
* marsha/unmarshal doesn't do little endian conversion.
*/
#define proxy_unmarshal(in_sg, offset, fmt, args...) \
v9fs_unmarshal(in_sg, 1, offset, 0, fmt, ##args)
#define proxy_marshal(out_sg, offset, fmt, args...) \
v9fs_marshal(out_sg, 1, offset, 0, fmt, ##args)
union MsgControl {
struct cmsghdr cmsg;
char control[CMSG_SPACE(sizeof(int))];
};
typedef struct {
uint32_t type;
uint32_t size;
} ProxyHeader;
#define PROXY_HDR_SZ (sizeof(ProxyHeader))
enum {
T_SUCCESS = 0,
T_ERROR,
T_OPEN,
T_CREATE,
T_MKNOD,
T_MKDIR,
T_SYMLINK,
T_LINK,
T_LSTAT,
T_READLINK,
T_STATFS,
T_CHMOD,
T_CHOWN,
T_TRUNCATE,
T_UTIME,
T_RENAME,
T_REMOVE,
T_LGETXATTR,
T_LLISTXATTR,
T_LSETXATTR,
T_LREMOVEXATTR,
T_GETVERSION,
};
typedef struct {
uint64_t st_dev;
uint64_t st_ino;
uint64_t st_nlink;
uint32_t st_mode;
uint32_t st_uid;
uint32_t st_gid;
uint64_t st_rdev;
uint64_t st_size;
uint64_t st_blksize;
uint64_t st_blocks;
uint64_t st_atim_sec;
uint64_t st_atim_nsec;
uint64_t st_mtim_sec;
uint64_t st_mtim_nsec;
uint64_t st_ctim_sec;
uint64_t st_ctim_nsec;
} ProxyStat;
typedef struct {
uint64_t f_type;
uint64_t f_bsize;
uint64_t f_blocks;
uint64_t f_bfree;
uint64_t f_bavail;
uint64_t f_files;
uint64_t f_ffree;
uint64_t f_fsid[2];
uint64_t f_namelen;
uint64_t f_frsize;
} ProxyStatFS;
#endif

File diff suppressed because it is too large Load Diff

View File

@ -8,9 +8,11 @@
#include <sys/resource.h> #include <sys/resource.h>
#include "hw/virtio.h" #include "hw/virtio.h"
#include "fsdev/file-op-9p.h" #include "fsdev/file-op-9p.h"
#include "fsdev/virtio-9p-marshal.h"
#include "qemu-thread.h" #include "qemu-thread.h"
#include "qemu-coroutine.h" #include "qemu-coroutine.h"
/* The feature bitmap for virtio 9P */ /* The feature bitmap for virtio 9P */
/* The mount point is specified in a config variable */ /* The mount point is specified in a config variable */
#define VIRTIO_9P_MOUNT_TAG 0 #define VIRTIO_9P_MOUNT_TAG 0
@ -154,40 +156,6 @@ struct V9fsPDU
typedef struct V9fsFidState V9fsFidState; typedef struct V9fsFidState V9fsFidState;
typedef struct V9fsString
{
uint16_t size;
char *data;
} V9fsString;
typedef struct V9fsQID
{
int8_t type;
int32_t version;
int64_t path;
} V9fsQID;
typedef struct V9fsStat
{
int16_t size;
int16_t type;
int32_t dev;
V9fsQID qid;
int32_t mode;
int32_t atime;
int32_t mtime;
int64_t length;
V9fsString name;
V9fsString uid;
V9fsString gid;
V9fsString muid;
/* 9p2000.u */
V9fsString extension;
int32_t n_uid;
int32_t n_gid;
int32_t n_muid;
} V9fsStat;
enum { enum {
P9_FID_NONE = 0, P9_FID_NONE = 0,
P9_FID_FILE, P9_FID_FILE,
@ -267,29 +235,6 @@ typedef struct V9fsStatState {
struct stat stbuf; struct stat stbuf;
} V9fsStatState; } V9fsStatState;
typedef struct V9fsStatDotl {
uint64_t st_result_mask;
V9fsQID qid;
uint32_t st_mode;
uint32_t st_uid;
uint32_t st_gid;
uint64_t st_nlink;
uint64_t st_rdev;
uint64_t st_size;
uint64_t st_blksize;
uint64_t st_blocks;
uint64_t st_atime_sec;
uint64_t st_atime_nsec;
uint64_t st_mtime_sec;
uint64_t st_mtime_nsec;
uint64_t st_ctime_sec;
uint64_t st_ctime_nsec;
uint64_t st_btime_sec;
uint64_t st_btime_nsec;
uint64_t st_gen;
uint64_t st_data_version;
} V9fsStatDotl;
typedef struct V9fsOpenState { typedef struct V9fsOpenState {
V9fsPDU *pdu; V9fsPDU *pdu;
size_t offset; size_t offset;
@ -332,19 +277,6 @@ typedef struct V9fsWriteState {
int cnt; int cnt;
} V9fsWriteState; } V9fsWriteState;
typedef struct V9fsIattr
{
int32_t valid;
int32_t mode;
int32_t uid;
int32_t gid;
int64_t size;
int64_t atime_sec;
int64_t atime_nsec;
int64_t mtime_sec;
int64_t mtime_nsec;
} V9fsIattr;
struct virtio_9p_config struct virtio_9p_config
{ {
/* number of characters in tag */ /* number of characters in tag */
@ -459,14 +391,15 @@ static inline uint8_t v9fs_request_cancelled(V9fsPDU *pdu)
extern void handle_9p_output(VirtIODevice *vdev, VirtQueue *vq); extern void handle_9p_output(VirtIODevice *vdev, VirtQueue *vq);
extern void virtio_9p_set_fd_limit(void); extern void virtio_9p_set_fd_limit(void);
extern void v9fs_reclaim_fd(V9fsPDU *pdu); extern void v9fs_reclaim_fd(V9fsPDU *pdu);
extern void v9fs_string_init(V9fsString *str);
extern void v9fs_string_free(V9fsString *str);
extern void v9fs_string_null(V9fsString *str);
extern void v9fs_string_sprintf(V9fsString *str, const char *fmt, ...);
extern void v9fs_string_copy(V9fsString *lhs, V9fsString *rhs);
extern void v9fs_path_init(V9fsPath *path); extern void v9fs_path_init(V9fsPath *path);
extern void v9fs_path_free(V9fsPath *path); extern void v9fs_path_free(V9fsPath *path);
extern void v9fs_path_copy(V9fsPath *lhs, V9fsPath *rhs); extern void v9fs_path_copy(V9fsPath *lhs, V9fsPath *rhs);
extern int v9fs_name_to_path(V9fsState *s, V9fsPath *dirpath, extern int v9fs_name_to_path(V9fsState *s, V9fsPath *dirpath,
const char *name, V9fsPath *path); const char *name, V9fsPath *path);
#define pdu_marshal(pdu, offset, fmt, args...) \
v9fs_marshal(pdu->elem.in_sg, pdu->elem.in_num, offset, 1, fmt, ##args)
#define pdu_unmarshal(pdu, offset, fmt, args...) \
v9fs_unmarshal(pdu->elem.out_sg, pdu->elem.out_num, offset, 1, fmt, ##args)
#endif #endif

View File

@ -211,6 +211,13 @@ QemuOptsList qemu_fsdev_opts = {
}, { }, {
.name = "readonly", .name = "readonly",
.type = QEMU_OPT_BOOL, .type = QEMU_OPT_BOOL,
}, {
.name = "socket",
.type = QEMU_OPT_STRING,
}, {
.name = "sock_fd",
.type = QEMU_OPT_NUMBER,
}, },
{ /*End of list */ } { /*End of list */ }
@ -240,6 +247,12 @@ QemuOptsList qemu_virtfs_opts = {
}, { }, {
.name = "readonly", .name = "readonly",
.type = QEMU_OPT_BOOL, .type = QEMU_OPT_BOOL,
}, {
.name = "socket",
.type = QEMU_OPT_STRING,
}, {
.name = "sock_fd",
.type = QEMU_OPT_NUMBER,
}, },
{ /*End of list */ } { /*End of list */ }

View File

@ -551,19 +551,19 @@ DEFHEADING()
DEFHEADING(File system options:) DEFHEADING(File system options:)
DEF("fsdev", HAS_ARG, QEMU_OPTION_fsdev, DEF("fsdev", HAS_ARG, QEMU_OPTION_fsdev,
"-fsdev fsdriver,id=id,path=path,[security_model={mapped|passthrough|none}]\n" "-fsdev fsdriver,id=id[,path=path,][security_model={mapped|passthrough|none}]\n"
" [,writeout=immediate][,readonly]\n", " [,writeout=immediate][,readonly][,socket=socket|sock_fd=sock_fd]\n",
QEMU_ARCH_ALL) QEMU_ARCH_ALL)
STEXI STEXI
@item -fsdev @var{fsdriver},id=@var{id},path=@var{path},[security_model=@var{security_model}][,writeout=@var{writeout}][,readonly] @item -fsdev @var{fsdriver},id=@var{id},path=@var{path},[security_model=@var{security_model}][,writeout=@var{writeout}][,readonly][,socket=@var{socket}|sock_fd=@var{sock_fd}]
@findex -fsdev @findex -fsdev
Define a new file system device. Valid options are: Define a new file system device. Valid options are:
@table @option @table @option
@item @var{fsdriver} @item @var{fsdriver}
This option specifies the fs driver backend to use. This option specifies the fs driver backend to use.
Currently "local" and "handle" file system drivers are supported. Currently "local", "handle" and "proxy" file system drivers are supported.
@item id=@var{id} @item id=@var{id}
Specifies identifier for this device Specifies identifier for this device
@item path=@var{path} @item path=@var{path}
@ -580,7 +580,7 @@ file attributes. Directories exported by this security model cannot
interact with other unix tools. "none" security model is same as interact with other unix tools. "none" security model is same as
passthrough except the sever won't report failures if it fails to passthrough except the sever won't report failures if it fails to
set file attributes like ownership. Security model is mandatory set file attributes like ownership. Security model is mandatory
only for local fsdriver. Other fsdrivers (like handle) don't take only for local fsdriver. Other fsdrivers (like handle, proxy) don't take
security model as a parameter. security model as a parameter.
@item writeout=@var{writeout} @item writeout=@var{writeout}
This is an optional argument. The only supported value is "immediate". This is an optional argument. The only supported value is "immediate".
@ -590,6 +590,13 @@ reported as written by the storage subsystem.
@item readonly @item readonly
Enables exporting 9p share as a readonly mount for guests. By default Enables exporting 9p share as a readonly mount for guests. By default
read-write access is given. read-write access is given.
@item socket=@var{socket}
Enables proxy filesystem driver to use passed socket file for communicating
with virtfs-proxy-helper
@item sock_fd=@var{sock_fd}
Enables proxy filesystem driver to use passed socket descriptor for
communicating with virtfs-proxy-helper. Usually a helper like libvirt
will create socketpair and pass one of the fds as sock_fd
@end table @end table
-fsdev option is used along with -device driver "virtio-9p-pci". -fsdev option is used along with -device driver "virtio-9p-pci".
@ -610,19 +617,19 @@ DEFHEADING(Virtual File system pass-through options:)
DEF("virtfs", HAS_ARG, QEMU_OPTION_virtfs, DEF("virtfs", HAS_ARG, QEMU_OPTION_virtfs,
"-virtfs local,path=path,mount_tag=tag,security_model=[mapped|passthrough|none]\n" "-virtfs local,path=path,mount_tag=tag,security_model=[mapped|passthrough|none]\n"
" [,writeout=immediate][,readonly]\n", " [,writeout=immediate][,readonly][,socket=socket|sock_fd=sock_fd]\n",
QEMU_ARCH_ALL) QEMU_ARCH_ALL)
STEXI STEXI
@item -virtfs @var{fsdriver},path=@var{path},mount_tag=@var{mount_tag},security_model=@var{security_model}[,writeout=@var{writeout}][,readonly] @item -virtfs @var{fsdriver}[,path=@var{path}],mount_tag=@var{mount_tag}[,security_model=@var{security_model}][,writeout=@var{writeout}][,readonly][,socket=@var{socket}|sock_fd=@var{sock_fd}]
@findex -virtfs @findex -virtfs
The general form of a Virtual File system pass-through options are: The general form of a Virtual File system pass-through options are:
@table @option @table @option
@item @var{fsdriver} @item @var{fsdriver}
This option specifies the fs driver backend to use. This option specifies the fs driver backend to use.
Currently "local" and "handle" file system drivers are supported. Currently "local", "handle" and "proxy" file system drivers are supported.
@item id=@var{id} @item id=@var{id}
Specifies identifier for this device Specifies identifier for this device
@item path=@var{path} @item path=@var{path}
@ -639,7 +646,7 @@ file attributes. Directories exported by this security model cannot
interact with other unix tools. "none" security model is same as interact with other unix tools. "none" security model is same as
passthrough except the sever won't report failures if it fails to passthrough except the sever won't report failures if it fails to
set file attributes like ownership. Security model is mandatory only set file attributes like ownership. Security model is mandatory only
for local fsdriver. Other fsdrivers (like handle) don't take security for local fsdriver. Other fsdrivers (like handle, proxy) don't take security
model as a parameter. model as a parameter.
@item writeout=@var{writeout} @item writeout=@var{writeout}
This is an optional argument. The only supported value is "immediate". This is an optional argument. The only supported value is "immediate".
@ -649,6 +656,13 @@ reported as written by the storage subsystem.
@item readonly @item readonly
Enables exporting 9p share as a readonly mount for guests. By default Enables exporting 9p share as a readonly mount for guests. By default
read-write access is given. read-write access is given.
@item socket=@var{socket}
Enables proxy filesystem driver to use passed socket file for
communicating with virtfs-proxy-helper. Usually a helper like libvirt
will create socketpair and pass one of the fds as sock_fd
@item sock_fd
Enables proxy filesystem driver to use passed 'sock_fd' as the socket
descriptor for interfacing with virtfs-proxy-helper
@end table @end table
ETEXI ETEXI

18
vl.c
View File

@ -2661,7 +2661,7 @@ int main(int argc, char **argv, char **envp)
case QEMU_OPTION_virtfs: { case QEMU_OPTION_virtfs: {
QemuOpts *fsdev; QemuOpts *fsdev;
QemuOpts *device; QemuOpts *device;
const char *writeout; const char *writeout, *sock_fd, *socket;
olist = qemu_find_opts("virtfs"); olist = qemu_find_opts("virtfs");
if (!olist) { if (!olist) {
@ -2675,11 +2675,8 @@ int main(int argc, char **argv, char **envp)
} }
if (qemu_opt_get(opts, "fsdriver") == NULL || if (qemu_opt_get(opts, "fsdriver") == NULL ||
qemu_opt_get(opts, "mount_tag") == NULL || qemu_opt_get(opts, "mount_tag") == NULL) {
qemu_opt_get(opts, "path") == NULL) { fprintf(stderr, "Usage: -virtfs fsdriver,mount_tag=tag.\n");
fprintf(stderr, "Usage: -virtfs fsdriver,path=/share_path/,"
"[security_model={mapped|passthrough|none}],"
"mount_tag=tag.\n");
exit(1); exit(1);
} }
fsdev = qemu_opts_create(qemu_find_opts("fsdev"), fsdev = qemu_opts_create(qemu_find_opts("fsdev"),
@ -2704,6 +2701,14 @@ int main(int argc, char **argv, char **envp)
qemu_opt_set(fsdev, "path", qemu_opt_get(opts, "path")); qemu_opt_set(fsdev, "path", qemu_opt_get(opts, "path"));
qemu_opt_set(fsdev, "security_model", qemu_opt_set(fsdev, "security_model",
qemu_opt_get(opts, "security_model")); qemu_opt_get(opts, "security_model"));
socket = qemu_opt_get(opts, "socket");
if (socket) {
qemu_opt_set(fsdev, "socket", socket);
}
sock_fd = qemu_opt_get(opts, "sock_fd");
if (sock_fd) {
qemu_opt_set(fsdev, "sock_fd", sock_fd);
}
qemu_opt_set_bool(fsdev, "readonly", qemu_opt_set_bool(fsdev, "readonly",
qemu_opt_get_bool(opts, "readonly", 0)); qemu_opt_get_bool(opts, "readonly", 0));
@ -2725,7 +2730,6 @@ int main(int argc, char **argv, char **envp)
exit(1); exit(1);
} }
qemu_opt_set(fsdev, "fsdriver", "synth"); qemu_opt_set(fsdev, "fsdriver", "synth");
qemu_opt_set(fsdev, "path", "/"); /* ignored */
device = qemu_opts_create(qemu_find_opts("device"), NULL, 0); device = qemu_opts_create(qemu_find_opts("device"), NULL, 0);
qemu_opt_set(device, "driver", "virtio-9p-pci"); qemu_opt_set(device, "driver", "virtio-9p-pci");