提交 444dbc38 编写于 作者: A Anthony Liguori

Merge remote-tracking branch 'kwolf/for-anthony' into staging

* kwolf/for-anthony:
  block: remove keep_read_only flag from BlockDriverState struct
  block: convert bdrv_commit() to use bdrv_reopen()
  block: vpc image file reopen
  block: vdi image file reopen
  block: vmdk image file reopen
  block: qcow image file reopen
  block: qcow2 image file reopen
  block: qed image file reopen
  block: raw image file reopen
  block: raw-posix image file reopen
  block: purge s->aligned_buf and s->aligned_buf_size from raw-posix.c
  block: use BDRV_O_NOCACHE instead of s->aligned_buf in raw-posix.c
  block: do not parse BDRV_O_CACHE_WB in block drivers
  block: move open flag parsing in raw block drivers to helper functions
  block: move aio initialization into a helper function
  block: Framework for reopening files safely
  block: make bdrv_set_enable_write_cache() modify open_flags
  block: correctly set the keep_read_only flag
  blockdev: preserve readonly and snapshot states across media changes
...@@ -668,7 +668,7 @@ static int bdrv_open_common(BlockDriverState *bs, const char *filename, ...@@ -668,7 +668,7 @@ static int bdrv_open_common(BlockDriverState *bs, const char *filename,
open_flags |= BDRV_O_RDWR; open_flags |= BDRV_O_RDWR;
} }
bs->keep_read_only = bs->read_only = !(open_flags & BDRV_O_RDWR); bs->read_only = !(open_flags & BDRV_O_RDWR);
/* Open the image, either directly or using a protocol */ /* Open the image, either directly or using a protocol */
if (drv->bdrv_file_open) { if (drv->bdrv_file_open) {
...@@ -808,6 +808,10 @@ int bdrv_open(BlockDriverState *bs, const char *filename, int flags, ...@@ -808,6 +808,10 @@ int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
goto unlink_and_fail; goto unlink_and_fail;
} }
if (flags & BDRV_O_RDWR) {
flags |= BDRV_O_ALLOW_RDWR;
}
/* Open the image */ /* Open the image */
ret = bdrv_open_common(bs, filename, flags, drv); ret = bdrv_open_common(bs, filename, flags, drv);
if (ret < 0) { if (ret < 0) {
...@@ -837,12 +841,6 @@ int bdrv_open(BlockDriverState *bs, const char *filename, int flags, ...@@ -837,12 +841,6 @@ int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
bdrv_close(bs); bdrv_close(bs);
return ret; return ret;
} }
if (bs->is_temporary) {
bs->backing_hd->keep_read_only = !(flags & BDRV_O_RDWR);
} else {
/* base image inherits from "parent" */
bs->backing_hd->keep_read_only = bs->keep_read_only;
}
} }
if (!bdrv_key_required(bs)) { if (!bdrv_key_required(bs)) {
...@@ -863,6 +861,238 @@ unlink_and_fail: ...@@ -863,6 +861,238 @@ unlink_and_fail:
return ret; return ret;
} }
typedef struct BlockReopenQueueEntry {
bool prepared;
BDRVReopenState state;
QSIMPLEQ_ENTRY(BlockReopenQueueEntry) entry;
} BlockReopenQueueEntry;
/*
* Adds a BlockDriverState to a simple queue for an atomic, transactional
* reopen of multiple devices.
*
* bs_queue can either be an existing BlockReopenQueue that has had QSIMPLE_INIT
* already performed, or alternatively may be NULL a new BlockReopenQueue will
* be created and initialized. This newly created BlockReopenQueue should be
* passed back in for subsequent calls that are intended to be of the same
* atomic 'set'.
*
* bs is the BlockDriverState to add to the reopen queue.
*
* flags contains the open flags for the associated bs
*
* returns a pointer to bs_queue, which is either the newly allocated
* bs_queue, or the existing bs_queue being used.
*
*/
BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
BlockDriverState *bs, int flags)
{
assert(bs != NULL);
BlockReopenQueueEntry *bs_entry;
if (bs_queue == NULL) {
bs_queue = g_new0(BlockReopenQueue, 1);
QSIMPLEQ_INIT(bs_queue);
}
if (bs->file) {
bdrv_reopen_queue(bs_queue, bs->file, flags);
}
bs_entry = g_new0(BlockReopenQueueEntry, 1);
QSIMPLEQ_INSERT_TAIL(bs_queue, bs_entry, entry);
bs_entry->state.bs = bs;
bs_entry->state.flags = flags;
return bs_queue;
}
/*
* Reopen multiple BlockDriverStates atomically & transactionally.
*
* The queue passed in (bs_queue) must have been built up previous
* via bdrv_reopen_queue().
*
* Reopens all BDS specified in the queue, with the appropriate
* flags. All devices are prepared for reopen, and failure of any
* device will cause all device changes to be abandonded, and intermediate
* data cleaned up.
*
* If all devices prepare successfully, then the changes are committed
* to all devices.
*
*/
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp)
{
int ret = -1;
BlockReopenQueueEntry *bs_entry, *next;
Error *local_err = NULL;
assert(bs_queue != NULL);
bdrv_drain_all();
QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) {
if (bdrv_reopen_prepare(&bs_entry->state, bs_queue, &local_err)) {
error_propagate(errp, local_err);
goto cleanup;
}
bs_entry->prepared = true;
}
/* If we reach this point, we have success and just need to apply the
* changes
*/
QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) {
bdrv_reopen_commit(&bs_entry->state);
}
ret = 0;
cleanup:
QSIMPLEQ_FOREACH_SAFE(bs_entry, bs_queue, entry, next) {
if (ret && bs_entry->prepared) {
bdrv_reopen_abort(&bs_entry->state);
}
g_free(bs_entry);
}
g_free(bs_queue);
return ret;
}
/* Reopen a single BlockDriverState with the specified flags. */
int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp)
{
int ret = -1;
Error *local_err = NULL;
BlockReopenQueue *queue = bdrv_reopen_queue(NULL, bs, bdrv_flags);
ret = bdrv_reopen_multiple(queue, &local_err);
if (local_err != NULL) {
error_propagate(errp, local_err);
}
return ret;
}
/*
* Prepares a BlockDriverState for reopen. All changes are staged in the
* 'opaque' field of the BDRVReopenState, which is used and allocated by
* the block driver layer .bdrv_reopen_prepare()
*
* bs is the BlockDriverState to reopen
* flags are the new open flags
* queue is the reopen queue
*
* Returns 0 on success, non-zero on error. On error errp will be set
* as well.
*
* On failure, bdrv_reopen_abort() will be called to clean up any data.
* It is the responsibility of the caller to then call the abort() or
* commit() for any other BDS that have been left in a prepare() state
*
*/
int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue,
Error **errp)
{
int ret = -1;
Error *local_err = NULL;
BlockDriver *drv;
assert(reopen_state != NULL);
assert(reopen_state->bs->drv != NULL);
drv = reopen_state->bs->drv;
/* if we are to stay read-only, do not allow permission change
* to r/w */
if (!(reopen_state->bs->open_flags & BDRV_O_ALLOW_RDWR) &&
reopen_state->flags & BDRV_O_RDWR) {
error_set(errp, QERR_DEVICE_IS_READ_ONLY,
reopen_state->bs->device_name);
goto error;
}
ret = bdrv_flush(reopen_state->bs);
if (ret) {
error_set(errp, ERROR_CLASS_GENERIC_ERROR, "Error (%s) flushing drive",
strerror(-ret));
goto error;
}
if (drv->bdrv_reopen_prepare) {
ret = drv->bdrv_reopen_prepare(reopen_state, queue, &local_err);
if (ret) {
if (local_err != NULL) {
error_propagate(errp, local_err);
} else {
error_set(errp, QERR_OPEN_FILE_FAILED,
reopen_state->bs->filename);
}
goto error;
}
} else {
/* It is currently mandatory to have a bdrv_reopen_prepare()
* handler for each supported drv. */
error_set(errp, QERR_BLOCK_FORMAT_FEATURE_NOT_SUPPORTED,
drv->format_name, reopen_state->bs->device_name,
"reopening of file");
ret = -1;
goto error;
}
ret = 0;
error:
return ret;
}
/*
* Takes the staged changes for the reopen from bdrv_reopen_prepare(), and
* makes them final by swapping the staging BlockDriverState contents into
* the active BlockDriverState contents.
*/
void bdrv_reopen_commit(BDRVReopenState *reopen_state)
{
BlockDriver *drv;
assert(reopen_state != NULL);
drv = reopen_state->bs->drv;
assert(drv != NULL);
/* If there are any driver level actions to take */
if (drv->bdrv_reopen_commit) {
drv->bdrv_reopen_commit(reopen_state);
}
/* set BDS specific flags now */
reopen_state->bs->open_flags = reopen_state->flags;
reopen_state->bs->enable_write_cache = !!(reopen_state->flags &
BDRV_O_CACHE_WB);
reopen_state->bs->read_only = !(reopen_state->flags & BDRV_O_RDWR);
}
/*
* Abort the reopen, and delete and free the staged changes in
* reopen_state
*/
void bdrv_reopen_abort(BDRVReopenState *reopen_state)
{
BlockDriver *drv;
assert(reopen_state != NULL);
drv = reopen_state->bs->drv;
assert(drv != NULL);
if (drv->bdrv_reopen_abort) {
drv->bdrv_reopen_abort(reopen_state);
}
}
void bdrv_close(BlockDriverState *bs) void bdrv_close(BlockDriverState *bs)
{ {
bdrv_flush(bs); bdrv_flush(bs);
...@@ -1269,13 +1499,11 @@ int bdrv_check(BlockDriverState *bs, BdrvCheckResult *res, BdrvCheckMode fix) ...@@ -1269,13 +1499,11 @@ int bdrv_check(BlockDriverState *bs, BdrvCheckResult *res, BdrvCheckMode fix)
int bdrv_commit(BlockDriverState *bs) int bdrv_commit(BlockDriverState *bs)
{ {
BlockDriver *drv = bs->drv; BlockDriver *drv = bs->drv;
BlockDriver *backing_drv;
int64_t sector, total_sectors; int64_t sector, total_sectors;
int n, ro, open_flags; int n, ro, open_flags;
int ret = 0, rw_ret = 0; int ret = 0;
uint8_t *buf; uint8_t *buf;
char filename[1024]; char filename[1024];
BlockDriverState *bs_rw, *bs_ro;
if (!drv) if (!drv)
return -ENOMEDIUM; return -ENOMEDIUM;
...@@ -1284,42 +1512,18 @@ int bdrv_commit(BlockDriverState *bs) ...@@ -1284,42 +1512,18 @@ int bdrv_commit(BlockDriverState *bs)
return -ENOTSUP; return -ENOTSUP;
} }
if (bs->backing_hd->keep_read_only) {
return -EACCES;
}
if (bdrv_in_use(bs) || bdrv_in_use(bs->backing_hd)) { if (bdrv_in_use(bs) || bdrv_in_use(bs->backing_hd)) {
return -EBUSY; return -EBUSY;
} }
backing_drv = bs->backing_hd->drv;
ro = bs->backing_hd->read_only; ro = bs->backing_hd->read_only;
strncpy(filename, bs->backing_hd->filename, sizeof(filename)); strncpy(filename, bs->backing_hd->filename, sizeof(filename));
open_flags = bs->backing_hd->open_flags; open_flags = bs->backing_hd->open_flags;
if (ro) { if (ro) {
/* re-open as RW */ if (bdrv_reopen(bs->backing_hd, open_flags | BDRV_O_RDWR, NULL)) {
bdrv_delete(bs->backing_hd); return -EACCES;
bs->backing_hd = NULL;
bs_rw = bdrv_new("");
rw_ret = bdrv_open(bs_rw, filename, open_flags | BDRV_O_RDWR,
backing_drv);
if (rw_ret < 0) {
bdrv_delete(bs_rw);
/* try to re-open read-only */
bs_ro = bdrv_new("");
ret = bdrv_open(bs_ro, filename, open_flags & ~BDRV_O_RDWR,
backing_drv);
if (ret < 0) {
bdrv_delete(bs_ro);
/* drive not functional anymore */
bs->drv = NULL;
return ret;
}
bs->backing_hd = bs_ro;
return rw_ret;
} }
bs->backing_hd = bs_rw;
} }
total_sectors = bdrv_getlength(bs) >> BDRV_SECTOR_BITS; total_sectors = bdrv_getlength(bs) >> BDRV_SECTOR_BITS;
...@@ -1356,20 +1560,8 @@ ro_cleanup: ...@@ -1356,20 +1560,8 @@ ro_cleanup:
g_free(buf); g_free(buf);
if (ro) { if (ro) {
/* re-open as RO */ /* ignoring error return here */
bdrv_delete(bs->backing_hd); bdrv_reopen(bs->backing_hd, open_flags & ~BDRV_O_RDWR, NULL);
bs->backing_hd = NULL;
bs_ro = bdrv_new("");
ret = bdrv_open(bs_ro, filename, open_flags & ~BDRV_O_RDWR,
backing_drv);
if (ret < 0) {
bdrv_delete(bs_ro);
/* drive not functional anymore */
bs->drv = NULL;
return ret;
}
bs->backing_hd = bs_ro;
bs->backing_hd->keep_read_only = 0;
} }
return ret; return ret;
...@@ -2168,6 +2360,13 @@ int bdrv_enable_write_cache(BlockDriverState *bs) ...@@ -2168,6 +2360,13 @@ int bdrv_enable_write_cache(BlockDriverState *bs)
void bdrv_set_enable_write_cache(BlockDriverState *bs, bool wce) void bdrv_set_enable_write_cache(BlockDriverState *bs, bool wce)
{ {
bs->enable_write_cache = wce; bs->enable_write_cache = wce;
/* so a reopen() will preserve wce */
if (wce) {
bs->open_flags |= BDRV_O_CACHE_WB;
} else {
bs->open_flags &= ~BDRV_O_CACHE_WB;
}
} }
int bdrv_is_encrypted(BlockDriverState *bs) int bdrv_is_encrypted(BlockDriverState *bs)
......
...@@ -80,6 +80,7 @@ typedef struct BlockDevOps { ...@@ -80,6 +80,7 @@ typedef struct BlockDevOps {
#define BDRV_O_COPY_ON_READ 0x0400 /* copy read backing sectors into image */ #define BDRV_O_COPY_ON_READ 0x0400 /* copy read backing sectors into image */
#define BDRV_O_INCOMING 0x0800 /* consistency hint for incoming migration */ #define BDRV_O_INCOMING 0x0800 /* consistency hint for incoming migration */
#define BDRV_O_CHECK 0x1000 /* open solely for consistency check */ #define BDRV_O_CHECK 0x1000 /* open solely for consistency check */
#define BDRV_O_ALLOW_RDWR 0x2000 /* allow reopen to change from r/o to r/w */
#define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH) #define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH)
...@@ -96,6 +97,15 @@ typedef enum { ...@@ -96,6 +97,15 @@ typedef enum {
BDRV_ACTION_REPORT, BDRV_ACTION_IGNORE, BDRV_ACTION_STOP BDRV_ACTION_REPORT, BDRV_ACTION_IGNORE, BDRV_ACTION_STOP
} BlockQMPEventAction; } BlockQMPEventAction;
typedef QSIMPLEQ_HEAD(BlockReopenQueue, BlockReopenQueueEntry) BlockReopenQueue;
typedef struct BDRVReopenState {
BlockDriverState *bs;
int flags;
void *opaque;
} BDRVReopenState;
void bdrv_iostatus_enable(BlockDriverState *bs); void bdrv_iostatus_enable(BlockDriverState *bs);
void bdrv_iostatus_reset(BlockDriverState *bs); void bdrv_iostatus_reset(BlockDriverState *bs);
void bdrv_iostatus_disable(BlockDriverState *bs); void bdrv_iostatus_disable(BlockDriverState *bs);
...@@ -130,6 +140,14 @@ int bdrv_parse_cache_flags(const char *mode, int *flags); ...@@ -130,6 +140,14 @@ int bdrv_parse_cache_flags(const char *mode, int *flags);
int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags); int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags);
int bdrv_open(BlockDriverState *bs, const char *filename, int flags, int bdrv_open(BlockDriverState *bs, const char *filename, int flags,
BlockDriver *drv); BlockDriver *drv);
BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue *bs_queue,
BlockDriverState *bs, int flags);
int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp);
int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp);
int bdrv_reopen_prepare(BDRVReopenState *reopen_state,
BlockReopenQueue *queue, Error **errp);
void bdrv_reopen_commit(BDRVReopenState *reopen_state);
void bdrv_reopen_abort(BDRVReopenState *reopen_state);
void bdrv_close(BlockDriverState *bs); void bdrv_close(BlockDriverState *bs);
int bdrv_attach_dev(BlockDriverState *bs, void *dev); int bdrv_attach_dev(BlockDriverState *bs, void *dev);
void bdrv_attach_dev_nofail(BlockDriverState *bs, void *dev); void bdrv_attach_dev_nofail(BlockDriverState *bs, void *dev);
......
...@@ -262,10 +262,6 @@ iscsi_aio_writev(BlockDriverState *bs, int64_t sector_num, ...@@ -262,10 +262,6 @@ iscsi_aio_writev(BlockDriverState *bs, int64_t sector_num,
acb->task->xfer_dir = SCSI_XFER_WRITE; acb->task->xfer_dir = SCSI_XFER_WRITE;
acb->task->cdb_size = 16; acb->task->cdb_size = 16;
acb->task->cdb[0] = 0x8a; acb->task->cdb[0] = 0x8a;
if (!(bs->open_flags & BDRV_O_CACHE_WB)) {
/* set FUA on writes when cache mode is write through */
acb->task->cdb[1] |= 0x04;
}
lba = sector_qemu2lun(sector_num, iscsilun); lba = sector_qemu2lun(sector_num, iscsilun);
*(uint32_t *)&acb->task->cdb[2] = htonl(lba >> 32); *(uint32_t *)&acb->task->cdb[2] = htonl(lba >> 32);
*(uint32_t *)&acb->task->cdb[6] = htonl(lba & 0xffffffff); *(uint32_t *)&acb->task->cdb[6] = htonl(lba & 0xffffffff);
......
...@@ -197,6 +197,15 @@ static int qcow_open(BlockDriverState *bs, int flags) ...@@ -197,6 +197,15 @@ static int qcow_open(BlockDriverState *bs, int flags)
return ret; return ret;
} }
/* We have nothing to do for QCOW reopen, stubs just return
* success */
static int qcow_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
return 0;
}
static int qcow_set_key(BlockDriverState *bs, const char *key) static int qcow_set_key(BlockDriverState *bs, const char *key)
{ {
BDRVQcowState *s = bs->opaque; BDRVQcowState *s = bs->opaque;
...@@ -868,6 +877,7 @@ static BlockDriver bdrv_qcow = { ...@@ -868,6 +877,7 @@ static BlockDriver bdrv_qcow = {
.bdrv_probe = qcow_probe, .bdrv_probe = qcow_probe,
.bdrv_open = qcow_open, .bdrv_open = qcow_open,
.bdrv_close = qcow_close, .bdrv_close = qcow_close,
.bdrv_reopen_prepare = qcow_reopen_prepare,
.bdrv_create = qcow_create, .bdrv_create = qcow_create,
.bdrv_co_readv = qcow_co_readv, .bdrv_co_readv = qcow_co_readv,
......
...@@ -52,6 +52,7 @@ typedef struct { ...@@ -52,6 +52,7 @@ typedef struct {
uint32_t magic; uint32_t magic;
uint32_t len; uint32_t len;
} QCowExtension; } QCowExtension;
#define QCOW2_EXT_MAGIC_END 0 #define QCOW2_EXT_MAGIC_END 0
#define QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA #define QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA
#define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857 #define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
...@@ -558,6 +559,14 @@ static int qcow2_set_key(BlockDriverState *bs, const char *key) ...@@ -558,6 +559,14 @@ static int qcow2_set_key(BlockDriverState *bs, const char *key)
return 0; return 0;
} }
/* We have nothing to do for QCOW2 reopen, stubs just return
* success */
static int qcow2_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
return 0;
}
static int coroutine_fn qcow2_co_is_allocated(BlockDriverState *bs, static int coroutine_fn qcow2_co_is_allocated(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, int *pnum) int64_t sector_num, int nb_sectors, int *pnum)
{ {
...@@ -1679,6 +1688,7 @@ static BlockDriver bdrv_qcow2 = { ...@@ -1679,6 +1688,7 @@ static BlockDriver bdrv_qcow2 = {
.bdrv_probe = qcow2_probe, .bdrv_probe = qcow2_probe,
.bdrv_open = qcow2_open, .bdrv_open = qcow2_open,
.bdrv_close = qcow2_close, .bdrv_close = qcow2_close,
.bdrv_reopen_prepare = qcow2_reopen_prepare,
.bdrv_create = qcow2_create, .bdrv_create = qcow2_create,
.bdrv_co_is_allocated = qcow2_co_is_allocated, .bdrv_co_is_allocated = qcow2_co_is_allocated,
.bdrv_set_key = qcow2_set_key, .bdrv_set_key = qcow2_set_key,
......
...@@ -505,6 +505,14 @@ out: ...@@ -505,6 +505,14 @@ out:
return ret; return ret;
} }
/* We have nothing to do for QED reopen, stubs just return
* success */
static int bdrv_qed_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
return 0;
}
static void bdrv_qed_close(BlockDriverState *bs) static void bdrv_qed_close(BlockDriverState *bs)
{ {
BDRVQEDState *s = bs->opaque; BDRVQEDState *s = bs->opaque;
...@@ -1564,6 +1572,7 @@ static BlockDriver bdrv_qed = { ...@@ -1564,6 +1572,7 @@ static BlockDriver bdrv_qed = {
.bdrv_rebind = bdrv_qed_rebind, .bdrv_rebind = bdrv_qed_rebind,
.bdrv_open = bdrv_qed_open, .bdrv_open = bdrv_qed_open,
.bdrv_close = bdrv_qed_close, .bdrv_close = bdrv_qed_close,
.bdrv_reopen_prepare = bdrv_qed_reopen_prepare,
.bdrv_create = bdrv_qed_create, .bdrv_create = bdrv_qed_create,
.bdrv_co_is_allocated = bdrv_qed_co_is_allocated, .bdrv_co_is_allocated = bdrv_qed_co_is_allocated,
.bdrv_make_empty = bdrv_qed_make_empty, .bdrv_make_empty = bdrv_qed_make_empty,
......
...@@ -133,13 +133,19 @@ typedef struct BDRVRawState { ...@@ -133,13 +133,19 @@ typedef struct BDRVRawState {
int use_aio; int use_aio;
void *aio_ctx; void *aio_ctx;
#endif #endif
uint8_t *aligned_buf;
unsigned aligned_buf_size;
#ifdef CONFIG_XFS #ifdef CONFIG_XFS
bool is_xfs : 1; bool is_xfs : 1;
#endif #endif
} BDRVRawState; } BDRVRawState;
typedef struct BDRVRawReopenState {
int fd;
int open_flags;
#ifdef CONFIG_LINUX_AIO
int use_aio;
#endif
} BDRVRawReopenState;
static int fd_open(BlockDriverState *bs); static int fd_open(BlockDriverState *bs);
static int64_t raw_getlength(BlockDriverState *bs); static int64_t raw_getlength(BlockDriverState *bs);
...@@ -185,6 +191,57 @@ static int raw_normalize_devicepath(const char **filename) ...@@ -185,6 +191,57 @@ static int raw_normalize_devicepath(const char **filename)
} }
#endif #endif
static void raw_parse_flags(int bdrv_flags, int *open_flags)
{
assert(open_flags != NULL);
*open_flags |= O_BINARY;
*open_flags &= ~O_ACCMODE;
if (bdrv_flags & BDRV_O_RDWR) {
*open_flags |= O_RDWR;
} else {
*open_flags |= O_RDONLY;
}
/* Use O_DSYNC for write-through caching, no flags for write-back caching,
* and O_DIRECT for no caching. */
if ((bdrv_flags & BDRV_O_NOCACHE)) {
*open_flags |= O_DIRECT;
}
}
#ifdef CONFIG_LINUX_AIO
static int raw_set_aio(void **aio_ctx, int *use_aio, int bdrv_flags)
{
int ret = -1;
assert(aio_ctx != NULL);
assert(use_aio != NULL);
/*
* Currently Linux do AIO only for files opened with O_DIRECT
* specified so check NOCACHE flag too
*/
if ((bdrv_flags & (BDRV_O_NOCACHE|BDRV_O_NATIVE_AIO)) ==
(BDRV_O_NOCACHE|BDRV_O_NATIVE_AIO)) {
/* if non-NULL, laio_init() has already been run */
if (*aio_ctx == NULL) {
*aio_ctx = laio_init();
if (!*aio_ctx) {
goto error;
}
}
*use_aio = 1;
} else {
*use_aio = 0;
}
ret = 0;
error:
return ret;
}
#endif
static int raw_open_common(BlockDriverState *bs, const char *filename, static int raw_open_common(BlockDriverState *bs, const char *filename,
int bdrv_flags, int open_flags) int bdrv_flags, int open_flags)
{ {
...@@ -196,20 +253,8 @@ static int raw_open_common(BlockDriverState *bs, const char *filename, ...@@ -196,20 +253,8 @@ static int raw_open_common(BlockDriverState *bs, const char *filename,
return ret; return ret;
} }
s->open_flags = open_flags | O_BINARY; s->open_flags = open_flags;
s->open_flags &= ~O_ACCMODE; raw_parse_flags(bdrv_flags, &s->open_flags);
if (bdrv_flags & BDRV_O_RDWR) {
s->open_flags |= O_RDWR;
} else {
s->open_flags |= O_RDONLY;
}
/* Use O_DSYNC for write-through caching, no flags for write-back caching,
* and O_DIRECT for no caching. */
if ((bdrv_flags & BDRV_O_NOCACHE))
s->open_flags |= O_DIRECT;
if (!(bdrv_flags & BDRV_O_CACHE_WB))
s->open_flags |= O_DSYNC;
s->fd = -1; s->fd = -1;
fd = qemu_open(filename, s->open_flags, 0644); fd = qemu_open(filename, s->open_flags, 0644);
...@@ -220,45 +265,17 @@ static int raw_open_common(BlockDriverState *bs, const char *filename, ...@@ -220,45 +265,17 @@ static int raw_open_common(BlockDriverState *bs, const char *filename,
return ret; return ret;
} }
s->fd = fd; s->fd = fd;
s->aligned_buf = NULL;
if ((bdrv_flags & BDRV_O_NOCACHE)) {
/*
* Allocate a buffer for read/modify/write cycles. Chose the size
* pessimistically as we don't know the block size yet.
*/
s->aligned_buf_size = 32 * MAX_BLOCKSIZE;
s->aligned_buf = qemu_memalign(MAX_BLOCKSIZE, s->aligned_buf_size);
if (s->aligned_buf == NULL) {
goto out_close;
}
}
/* We're falling back to POSIX AIO in some cases so init always */ /* We're falling back to POSIX AIO in some cases so init always */
if (paio_init() < 0) { if (paio_init() < 0) {
goto out_free_buf; goto out_close;
} }
#ifdef CONFIG_LINUX_AIO #ifdef CONFIG_LINUX_AIO
/* if (raw_set_aio(&s->aio_ctx, &s->use_aio, bdrv_flags)) {
* Currently Linux do AIO only for files opened with O_DIRECT goto out_close;
* specified so check NOCACHE flag too
*/
if ((bdrv_flags & (BDRV_O_NOCACHE|BDRV_O_NATIVE_AIO)) ==
(BDRV_O_NOCACHE|BDRV_O_NATIVE_AIO)) {
s->aio_ctx = laio_init();
if (!s->aio_ctx) {
goto out_free_buf;
}
s->use_aio = 1;
} else
#endif
{
#ifdef CONFIG_LINUX_AIO
s->use_aio = 0;
#endif
} }
#endif
#ifdef CONFIG_XFS #ifdef CONFIG_XFS
if (platform_test_xfs_fd(s->fd)) { if (platform_test_xfs_fd(s->fd)) {
...@@ -268,8 +285,6 @@ static int raw_open_common(BlockDriverState *bs, const char *filename, ...@@ -268,8 +285,6 @@ static int raw_open_common(BlockDriverState *bs, const char *filename,
return 0; return 0;
out_free_buf:
qemu_vfree(s->aligned_buf);
out_close: out_close:
qemu_close(fd); qemu_close(fd);
return -errno; return -errno;
...@@ -283,6 +298,109 @@ static int raw_open(BlockDriverState *bs, const char *filename, int flags) ...@@ -283,6 +298,109 @@ static int raw_open(BlockDriverState *bs, const char *filename, int flags)
return raw_open_common(bs, filename, flags, 0); return raw_open_common(bs, filename, flags, 0);
} }
static int raw_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
BDRVRawState *s;
BDRVRawReopenState *raw_s;
int ret = 0;
assert(state != NULL);
assert(state->bs != NULL);
s = state->bs->opaque;
state->opaque = g_malloc0(sizeof(BDRVRawReopenState));
raw_s = state->opaque;
#ifdef CONFIG_LINUX_AIO
raw_s->use_aio = s->use_aio;
/* we can use s->aio_ctx instead of a copy, because the use_aio flag is
* valid in the 'false' condition even if aio_ctx is set, and raw_set_aio()
* won't override aio_ctx if aio_ctx is non-NULL */
if (raw_set_aio(&s->aio_ctx, &raw_s->use_aio, state->flags)) {
return -1;
}
#endif
raw_parse_flags(state->flags, &raw_s->open_flags);
raw_s->fd = -1;
int fcntl_flags = O_APPEND | O_ASYNC | O_NONBLOCK;
#ifdef O_NOATIME
fcntl_flags |= O_NOATIME;
#endif
if ((raw_s->open_flags & ~fcntl_flags) == (s->open_flags & ~fcntl_flags)) {
/* dup the original fd */
/* TODO: use qemu fcntl wrapper */
#ifdef F_DUPFD_CLOEXEC
raw_s->fd = fcntl(s->fd, F_DUPFD_CLOEXEC, 0);
#else
raw_s->fd = dup(s->fd);
if (raw_s->fd != -1) {
qemu_set_cloexec(raw_s->fd);
}
#endif
if (raw_s->fd >= 0) {
ret = fcntl_setfl(raw_s->fd, raw_s->open_flags);
if (ret) {
qemu_close(raw_s->fd);
raw_s->fd = -1;
}
}
}
/* If we cannot use fcntl, or fcntl failed, fall back to qemu_open() */
if (raw_s->fd == -1) {
assert(!(raw_s->open_flags & O_CREAT));
raw_s->fd = qemu_open(state->bs->filename, raw_s->open_flags);
if (raw_s->fd == -1) {
ret = -1;
}
}
return ret;
}
static void raw_reopen_commit(BDRVReopenState *state)
{
BDRVRawReopenState *raw_s = state->opaque;
BDRVRawState *s = state->bs->opaque;
s->open_flags = raw_s->open_flags;
qemu_close(s->fd);
s->fd = raw_s->fd;
#ifdef CONFIG_LINUX_AIO
s->use_aio = raw_s->use_aio;
#endif
g_free(state->opaque);
state->opaque = NULL;
}
static void raw_reopen_abort(BDRVReopenState *state)
{
BDRVRawReopenState *raw_s = state->opaque;
/* nothing to do if NULL, we didn't get far enough */
if (raw_s == NULL) {
return;
}
if (raw_s->fd >= 0) {
qemu_close(raw_s->fd);
raw_s->fd = -1;
}
g_free(state->opaque);
state->opaque = NULL;
}
/* XXX: use host sector size if necessary with: /* XXX: use host sector size if necessary with:
#ifdef DIOCGSECTORSIZE #ifdef DIOCGSECTORSIZE
{ {
...@@ -330,7 +448,7 @@ static BlockDriverAIOCB *raw_aio_submit(BlockDriverState *bs, ...@@ -330,7 +448,7 @@ static BlockDriverAIOCB *raw_aio_submit(BlockDriverState *bs,
* boundary. Check if this is the case or tell the low-level * boundary. Check if this is the case or tell the low-level
* driver that it needs to copy the buffer. * driver that it needs to copy the buffer.
*/ */
if (s->aligned_buf) { if ((bs->open_flags & BDRV_O_NOCACHE)) {
if (!qiov_is_aligned(bs, qiov)) { if (!qiov_is_aligned(bs, qiov)) {
type |= QEMU_AIO_MISALIGNED; type |= QEMU_AIO_MISALIGNED;
#ifdef CONFIG_LINUX_AIO #ifdef CONFIG_LINUX_AIO
...@@ -378,8 +496,6 @@ static void raw_close(BlockDriverState *bs) ...@@ -378,8 +496,6 @@ static void raw_close(BlockDriverState *bs)
if (s->fd >= 0) { if (s->fd >= 0) {
qemu_close(s->fd); qemu_close(s->fd);
s->fd = -1; s->fd = -1;
if (s->aligned_buf != NULL)
qemu_vfree(s->aligned_buf);
} }
} }
...@@ -735,6 +851,9 @@ static BlockDriver bdrv_file = { ...@@ -735,6 +851,9 @@ static BlockDriver bdrv_file = {
.instance_size = sizeof(BDRVRawState), .instance_size = sizeof(BDRVRawState),
.bdrv_probe = NULL, /* no probe for protocols */ .bdrv_probe = NULL, /* no probe for protocols */
.bdrv_file_open = raw_open, .bdrv_file_open = raw_open,
.bdrv_reopen_prepare = raw_reopen_prepare,
.bdrv_reopen_commit = raw_reopen_commit,
.bdrv_reopen_abort = raw_reopen_abort,
.bdrv_close = raw_close, .bdrv_close = raw_close,
.bdrv_create = raw_create, .bdrv_create = raw_create,
.bdrv_co_discard = raw_co_discard, .bdrv_co_discard = raw_co_discard,
......
...@@ -77,6 +77,23 @@ static int set_sparse(int fd) ...@@ -77,6 +77,23 @@ static int set_sparse(int fd)
NULL, 0, NULL, 0, &returned, NULL); NULL, 0, NULL, 0, &returned, NULL);
} }
static void raw_parse_flags(int flags, int *access_flags, DWORD *overlapped)
{
assert(access_flags != NULL);
assert(overlapped != NULL);
if (flags & BDRV_O_RDWR) {
*access_flags = GENERIC_READ | GENERIC_WRITE;
} else {
*access_flags = GENERIC_READ;
}
*overlapped = FILE_ATTRIBUTE_NORMAL;
if (flags & BDRV_O_NOCACHE) {
*overlapped |= FILE_FLAG_NO_BUFFERING;
}
}
static int raw_open(BlockDriverState *bs, const char *filename, int flags) static int raw_open(BlockDriverState *bs, const char *filename, int flags)
{ {
BDRVRawState *s = bs->opaque; BDRVRawState *s = bs->opaque;
...@@ -85,17 +102,8 @@ static int raw_open(BlockDriverState *bs, const char *filename, int flags) ...@@ -85,17 +102,8 @@ static int raw_open(BlockDriverState *bs, const char *filename, int flags)
s->type = FTYPE_FILE; s->type = FTYPE_FILE;
if (flags & BDRV_O_RDWR) { raw_parse_flags(flags, &access_flags, &overlapped);
access_flags = GENERIC_READ | GENERIC_WRITE;
} else {
access_flags = GENERIC_READ;
}
overlapped = FILE_ATTRIBUTE_NORMAL;
if (flags & BDRV_O_NOCACHE)
overlapped |= FILE_FLAG_NO_BUFFERING;
if (!(flags & BDRV_O_CACHE_WB))
overlapped |= FILE_FLAG_WRITE_THROUGH;
s->hfile = CreateFile(filename, access_flags, s->hfile = CreateFile(filename, access_flags,
FILE_SHARE_READ, NULL, FILE_SHARE_READ, NULL,
OPEN_EXISTING, overlapped, NULL); OPEN_EXISTING, overlapped, NULL);
...@@ -374,18 +382,10 @@ static int hdev_open(BlockDriverState *bs, const char *filename, int flags) ...@@ -374,18 +382,10 @@ static int hdev_open(BlockDriverState *bs, const char *filename, int flags)
} }
s->type = find_device_type(bs, filename); s->type = find_device_type(bs, filename);
if (flags & BDRV_O_RDWR) { raw_parse_flags(flags, &access_flags, &overlapped);
access_flags = GENERIC_READ | GENERIC_WRITE;
} else {
access_flags = GENERIC_READ;
}
create_flags = OPEN_EXISTING; create_flags = OPEN_EXISTING;
overlapped = FILE_ATTRIBUTE_NORMAL;
if (flags & BDRV_O_NOCACHE)
overlapped |= FILE_FLAG_NO_BUFFERING;
if (!(flags & BDRV_O_CACHE_WB))
overlapped |= FILE_FLAG_WRITE_THROUGH;
s->hfile = CreateFile(filename, access_flags, s->hfile = CreateFile(filename, access_flags,
FILE_SHARE_READ, NULL, FILE_SHARE_READ, NULL,
create_flags, overlapped, NULL); create_flags, overlapped, NULL);
......
...@@ -9,6 +9,14 @@ static int raw_open(BlockDriverState *bs, int flags) ...@@ -9,6 +9,14 @@ static int raw_open(BlockDriverState *bs, int flags)
return 0; return 0;
} }
/* We have nothing to do for raw reopen, stubs just return
* success */
static int raw_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
return 0;
}
static int coroutine_fn raw_co_readv(BlockDriverState *bs, int64_t sector_num, static int coroutine_fn raw_co_readv(BlockDriverState *bs, int64_t sector_num,
int nb_sectors, QEMUIOVector *qiov) int nb_sectors, QEMUIOVector *qiov)
{ {
...@@ -115,6 +123,8 @@ static BlockDriver bdrv_raw = { ...@@ -115,6 +123,8 @@ static BlockDriver bdrv_raw = {
.bdrv_open = raw_open, .bdrv_open = raw_open,
.bdrv_close = raw_close, .bdrv_close = raw_close,
.bdrv_reopen_prepare = raw_reopen_prepare,
.bdrv_co_readv = raw_co_readv, .bdrv_co_readv = raw_co_readv,
.bdrv_co_writev = raw_co_writev, .bdrv_co_writev = raw_co_writev,
.bdrv_co_is_allocated = raw_co_is_allocated, .bdrv_co_is_allocated = raw_co_is_allocated,
......
...@@ -487,12 +487,6 @@ static int qemu_rbd_open(BlockDriverState *bs, const char *filename, int flags) ...@@ -487,12 +487,6 @@ static int qemu_rbd_open(BlockDriverState *bs, const char *filename, int flags)
rados_conf_set(s->cluster, "rbd_cache", "false"); rados_conf_set(s->cluster, "rbd_cache", "false");
} else { } else {
rados_conf_set(s->cluster, "rbd_cache", "true"); rados_conf_set(s->cluster, "rbd_cache", "true");
if (!(flags & BDRV_O_CACHE_WB)) {
r = rados_conf_set(s->cluster, "rbd_cache_max_dirty", "0");
if (r < 0) {
rados_conf_set(s->cluster, "rbd_cache", "false");
}
}
} }
if (strstr(conf, "conf=") == NULL) { if (strstr(conf, "conf=") == NULL) {
......
...@@ -1114,14 +1114,12 @@ static int sd_open(BlockDriverState *bs, const char *filename, int flags) ...@@ -1114,14 +1114,12 @@ static int sd_open(BlockDriverState *bs, const char *filename, int flags)
goto out; goto out;
} }
if (flags & BDRV_O_CACHE_WB) { s->cache_enabled = 1;
s->cache_enabled = 1; s->flush_fd = connect_to_sdog(s->addr, s->port);
s->flush_fd = connect_to_sdog(s->addr, s->port); if (s->flush_fd < 0) {
if (s->flush_fd < 0) { error_report("failed to connect");
error_report("failed to connect"); ret = s->flush_fd;
ret = s->flush_fd; goto out;
goto out;
}
} }
if (snapid || tag[0] != '\0') { if (snapid || tag[0] != '\0') {
......
...@@ -454,6 +454,12 @@ static int vdi_open(BlockDriverState *bs, int flags) ...@@ -454,6 +454,12 @@ static int vdi_open(BlockDriverState *bs, int flags)
return -1; return -1;
} }
static int vdi_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
return 0;
}
static int coroutine_fn vdi_co_is_allocated(BlockDriverState *bs, static int coroutine_fn vdi_co_is_allocated(BlockDriverState *bs,
int64_t sector_num, int nb_sectors, int *pnum) int64_t sector_num, int nb_sectors, int *pnum)
{ {
...@@ -761,6 +767,7 @@ static BlockDriver bdrv_vdi = { ...@@ -761,6 +767,7 @@ static BlockDriver bdrv_vdi = {
.bdrv_probe = vdi_probe, .bdrv_probe = vdi_probe,
.bdrv_open = vdi_open, .bdrv_open = vdi_open,
.bdrv_close = vdi_close, .bdrv_close = vdi_close,
.bdrv_reopen_prepare = vdi_reopen_prepare,
.bdrv_create = vdi_create, .bdrv_create = vdi_create,
.bdrv_co_is_allocated = vdi_co_is_allocated, .bdrv_co_is_allocated = vdi_co_is_allocated,
.bdrv_make_empty = vdi_make_empty, .bdrv_make_empty = vdi_make_empty,
......
...@@ -300,6 +300,40 @@ static int vmdk_is_cid_valid(BlockDriverState *bs) ...@@ -300,6 +300,40 @@ static int vmdk_is_cid_valid(BlockDriverState *bs)
return 1; return 1;
} }
/* Queue extents, if any, for reopen() */
static int vmdk_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
BDRVVmdkState *s;
int ret = -1;
int i;
VmdkExtent *e;
assert(state != NULL);
assert(state->bs != NULL);
if (queue == NULL) {
error_set(errp, ERROR_CLASS_GENERIC_ERROR,
"No reopen queue for VMDK extents");
goto exit;
}
s = state->bs->opaque;
assert(s != NULL);
for (i = 0; i < s->num_extents; i++) {
e = &s->extents[i];
if (e->file != state->bs->file) {
bdrv_reopen_queue(queue, e->file, state->flags);
}
}
ret = 0;
exit:
return ret;
}
static int vmdk_parent_open(BlockDriverState *bs) static int vmdk_parent_open(BlockDriverState *bs)
{ {
char *p_name; char *p_name;
...@@ -1646,6 +1680,7 @@ static BlockDriver bdrv_vmdk = { ...@@ -1646,6 +1680,7 @@ static BlockDriver bdrv_vmdk = {
.instance_size = sizeof(BDRVVmdkState), .instance_size = sizeof(BDRVVmdkState),
.bdrv_probe = vmdk_probe, .bdrv_probe = vmdk_probe,
.bdrv_open = vmdk_open, .bdrv_open = vmdk_open,
.bdrv_reopen_prepare = vmdk_reopen_prepare,
.bdrv_read = vmdk_co_read, .bdrv_read = vmdk_co_read,
.bdrv_write = vmdk_co_write, .bdrv_write = vmdk_co_write,
.bdrv_close = vmdk_close, .bdrv_close = vmdk_close,
......
...@@ -265,6 +265,12 @@ static int vpc_open(BlockDriverState *bs, int flags) ...@@ -265,6 +265,12 @@ static int vpc_open(BlockDriverState *bs, int flags)
return err; return err;
} }
static int vpc_reopen_prepare(BDRVReopenState *state,
BlockReopenQueue *queue, Error **errp)
{
return 0;
}
/* /*
* Returns the absolute byte offset of the given sector in the image file. * Returns the absolute byte offset of the given sector in the image file.
* If the sector is not allocated, -1 is returned instead. * If the sector is not allocated, -1 is returned instead.
...@@ -783,6 +789,7 @@ static BlockDriver bdrv_vpc = { ...@@ -783,6 +789,7 @@ static BlockDriver bdrv_vpc = {
.bdrv_probe = vpc_probe, .bdrv_probe = vpc_probe,
.bdrv_open = vpc_open, .bdrv_open = vpc_open,
.bdrv_close = vpc_close, .bdrv_close = vpc_close,
.bdrv_reopen_prepare = vpc_reopen_prepare,
.bdrv_create = vpc_create, .bdrv_create = vpc_create,
.bdrv_read = vpc_co_read, .bdrv_read = vpc_co_read,
......
...@@ -139,6 +139,13 @@ struct BlockDriver { ...@@ -139,6 +139,13 @@ struct BlockDriver {
int instance_size; int instance_size;
int (*bdrv_probe)(const uint8_t *buf, int buf_size, const char *filename); int (*bdrv_probe)(const uint8_t *buf, int buf_size, const char *filename);
int (*bdrv_probe_device)(const char *filename); int (*bdrv_probe_device)(const char *filename);
/* For handling image reopen for split or non-split files */
int (*bdrv_reopen_prepare)(BDRVReopenState *reopen_state,
BlockReopenQueue *queue, Error **errp);
void (*bdrv_reopen_commit)(BDRVReopenState *reopen_state);
void (*bdrv_reopen_abort)(BDRVReopenState *reopen_state);
int (*bdrv_open)(BlockDriverState *bs, int flags); int (*bdrv_open)(BlockDriverState *bs, int flags);
int (*bdrv_file_open)(BlockDriverState *bs, const char *filename, int flags); int (*bdrv_file_open)(BlockDriverState *bs, const char *filename, int flags);
int (*bdrv_read)(BlockDriverState *bs, int64_t sector_num, int (*bdrv_read)(BlockDriverState *bs, int64_t sector_num,
...@@ -268,7 +275,6 @@ struct BlockDriverState { ...@@ -268,7 +275,6 @@ struct BlockDriverState {
int64_t total_sectors; /* if we are reading a disk image, give its int64_t total_sectors; /* if we are reading a disk image, give its
size in sectors */ size in sectors */
int read_only; /* if true, the media is read only */ int read_only; /* if true, the media is read only */
int keep_read_only; /* if true, the media was requested to stay read only */
int open_flags; /* flags used to open the file, re-used for re-open */ int open_flags; /* flags used to open the file, re-used for re-open */
int encrypted; /* if true, the media is encrypted */ int encrypted; /* if true, the media is encrypted */
int valid_key; /* if true, a valid encryption key has been set */ int valid_key; /* if true, a valid encryption key has been set */
...@@ -336,6 +342,7 @@ struct BlockDriverState { ...@@ -336,6 +342,7 @@ struct BlockDriverState {
/* long-running background operation */ /* long-running background operation */
BlockJob *job; BlockJob *job;
}; };
int get_tmp_filename(char *filename, int size); int get_tmp_filename(char *filename, int size);
......
...@@ -527,6 +527,8 @@ DriveInfo *drive_init(QemuOpts *opts, int default_to_scsi) ...@@ -527,6 +527,8 @@ DriveInfo *drive_init(QemuOpts *opts, int default_to_scsi)
if_name[type], mediastr, unit_id); if_name[type], mediastr, unit_id);
} }
dinfo->bdrv = bdrv_new(dinfo->id); dinfo->bdrv = bdrv_new(dinfo->id);
dinfo->bdrv->open_flags = snapshot ? BDRV_O_SNAPSHOT : 0;
dinfo->bdrv->read_only = ro;
dinfo->devaddr = devaddr; dinfo->devaddr = devaddr;
dinfo->type = type; dinfo->type = type;
dinfo->bus = bus_id; dinfo->bus = bus_id;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册