migration/next for 20141015
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCAAGBQJUPi6iAAoJEPSH7xhYctcjb0IP/iWbk5B+D/BR81x6AAM/4VBO McDUOzvQ4Hipo/HSvjWloC1YOOYdpd9XpRHEESULooXNrwJJIvz+TW1RJ4Lba2dt AmdPjGAGcvzTwgtOCFNPmvGGIxTO8s/6KRKQW51AxQEkfh8fPsx44ksfZdajK6id lizlXfP3gnwciaNZP4lmH5Nsq4sARQ9g/IIoulkL0itXRjBzlpLhjS4S/81mvZxg hW9thG8ml/mjLfNuKUKfRCDtmjzdlZClLWeFvZ3hbfR21mH5JaCt5YKjp4x6mimg TGfbzJOXQPbo5n/xLxBHGP+LrTgTQEJWgFTSDpS9pI3Z16+xP7mvNWlgSUxLPvEc 9gXwRuEbXpiMOQzofi+SX4x7HhhUxR0hHVM4dNsR2d520B2EbuZlAn2hVOwUyOpf mAT+eCODHCuzJLH4s4Yx7RUFzLS0LVfBgOnqvUYEiPD6qYsj/tTdw3yIdS0ySASw 0Abh0rqk6yUvDSQVZowVliudS5jOp8YQR/2WdYCEBH+r8Vk0i6Jm+MvScGDUOZmc TJCUgAQWRPZcUA4CnbbBiA3O3mQQxf6MzwCbqF2BcF5oMVutql7dnF6NFuN/wc8j AlFS2QE/5RX86RT4cjVQSfNk2oMNmmtjJRY4m7Ub0XacU4REznG1AgZf54cMTt1E B7TiocinEF0k9i/OTU8u =Rlo8 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/juanquintela/tags/migration/20141015' into staging migration/next for 20141015 # gpg: Signature made Wed 15 Oct 2014 09:21:54 BST using RSA key ID 5872D723 # gpg: Can't check signature: public key not found * remotes/juanquintela/tags/migration/20141015: migration: catch unknown flag combinations in ram_load qemu-file: Move stdio implementation to qemu-file-stdio.c qemu-file: Move unix and socket implementations to qemu-file-unix.c qemu-file: Use qemu_file_is_writable() on stdio_fclose() qemu-file: Make qemu_file_is_writable() non-static qemu-file: Add copyright header to qemu-file.c vmstate: Allow dynamic allocation for VBUFFER during migration block/migration: Disable cache invalidate for incoming migration Tests: QEMUSizedBuffer/QEMUBuffer QEMUSizedBuffer based QEMUFile Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
32d9c5613e
@ -50,7 +50,7 @@ common-obj-$(CONFIG_LINUX) += fsdev/
|
||||
|
||||
common-obj-y += migration.o migration-tcp.o
|
||||
common-obj-y += vmstate.o
|
||||
common-obj-y += qemu-file.o
|
||||
common-obj-y += qemu-file.o qemu-file-unix.o qemu-file-stdio.o
|
||||
common-obj-$(CONFIG_RDMA) += migration-rdma.o
|
||||
common-obj-y += qemu-char.o #aio.o
|
||||
common-obj-y += block-migration.o
|
||||
|
62
arch_init.c
62
arch_init.c
@ -1040,8 +1040,7 @@ void ram_handle_compressed(void *host, uint8_t ch, uint64_t size)
|
||||
|
||||
static int ram_load(QEMUFile *f, void *opaque, int version_id)
|
||||
{
|
||||
ram_addr_t addr;
|
||||
int flags, ret = 0;
|
||||
int flags = 0, ret = 0;
|
||||
static uint64_t seq_iter;
|
||||
|
||||
seq_iter++;
|
||||
@ -1050,21 +1049,24 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id)
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
while (!ret) {
|
||||
addr = qemu_get_be64(f);
|
||||
while (!ret && !(flags & RAM_SAVE_FLAG_EOS)) {
|
||||
ram_addr_t addr, total_ram_bytes;
|
||||
void *host;
|
||||
uint8_t ch;
|
||||
|
||||
addr = qemu_get_be64(f);
|
||||
flags = addr & ~TARGET_PAGE_MASK;
|
||||
addr &= TARGET_PAGE_MASK;
|
||||
|
||||
if (flags & RAM_SAVE_FLAG_MEM_SIZE) {
|
||||
switch (flags & ~RAM_SAVE_FLAG_CONTINUE) {
|
||||
case RAM_SAVE_FLAG_MEM_SIZE:
|
||||
/* Synchronize RAM block list */
|
||||
char id[256];
|
||||
ram_addr_t length;
|
||||
ram_addr_t total_ram_bytes = addr;
|
||||
|
||||
while (total_ram_bytes) {
|
||||
total_ram_bytes = addr;
|
||||
while (!ret && total_ram_bytes) {
|
||||
RAMBlock *block;
|
||||
uint8_t len;
|
||||
char id[256];
|
||||
ram_addr_t length;
|
||||
|
||||
len = qemu_get_byte(f);
|
||||
qemu_get_buffer(f, (uint8_t *)id, len);
|
||||
@ -1088,16 +1090,11 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id)
|
||||
"accept migration", id);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
if (ret) {
|
||||
break;
|
||||
}
|
||||
|
||||
total_ram_bytes -= length;
|
||||
}
|
||||
} else if (flags & RAM_SAVE_FLAG_COMPRESS) {
|
||||
void *host;
|
||||
uint8_t ch;
|
||||
|
||||
break;
|
||||
case RAM_SAVE_FLAG_COMPRESS:
|
||||
host = host_from_stream_offset(f, addr, flags);
|
||||
if (!host) {
|
||||
error_report("Illegal RAM offset " RAM_ADDR_FMT, addr);
|
||||
@ -1107,9 +1104,8 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id)
|
||||
|
||||
ch = qemu_get_byte(f);
|
||||
ram_handle_compressed(host, ch, TARGET_PAGE_SIZE);
|
||||
} else if (flags & RAM_SAVE_FLAG_PAGE) {
|
||||
void *host;
|
||||
|
||||
break;
|
||||
case RAM_SAVE_FLAG_PAGE:
|
||||
host = host_from_stream_offset(f, addr, flags);
|
||||
if (!host) {
|
||||
error_report("Illegal RAM offset " RAM_ADDR_FMT, addr);
|
||||
@ -1118,8 +1114,9 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id)
|
||||
}
|
||||
|
||||
qemu_get_buffer(f, host, TARGET_PAGE_SIZE);
|
||||
} else if (flags & RAM_SAVE_FLAG_XBZRLE) {
|
||||
void *host = host_from_stream_offset(f, addr, flags);
|
||||
break;
|
||||
case RAM_SAVE_FLAG_XBZRLE:
|
||||
host = host_from_stream_offset(f, addr, flags);
|
||||
if (!host) {
|
||||
error_report("Illegal RAM offset " RAM_ADDR_FMT, addr);
|
||||
ret = -EINVAL;
|
||||
@ -1132,17 +1129,22 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id)
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
} else if (flags & RAM_SAVE_FLAG_HOOK) {
|
||||
ram_control_load_hook(f, flags);
|
||||
} else if (flags & RAM_SAVE_FLAG_EOS) {
|
||||
break;
|
||||
case RAM_SAVE_FLAG_EOS:
|
||||
/* normal exit */
|
||||
break;
|
||||
} else {
|
||||
error_report("Unknown migration flags: %#x", flags);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
default:
|
||||
if (flags & RAM_SAVE_FLAG_HOOK) {
|
||||
ram_control_load_hook(f, flags);
|
||||
} else {
|
||||
error_report("Unknown combination of migration flags: %#x",
|
||||
flags);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
if (!ret) {
|
||||
ret = qemu_file_get_error(f);
|
||||
}
|
||||
ret = qemu_file_get_error(f);
|
||||
}
|
||||
|
||||
DPRINTF("Completed load of VM with exit code %d seq iteration "
|
||||
|
18
block.c
18
block.c
@ -5043,6 +5043,11 @@ void bdrv_invalidate_cache(BlockDriverState *bs, Error **errp)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(bs->open_flags & BDRV_O_INCOMING)) {
|
||||
return;
|
||||
}
|
||||
bs->open_flags &= ~BDRV_O_INCOMING;
|
||||
|
||||
if (bs->drv->bdrv_invalidate_cache) {
|
||||
bs->drv->bdrv_invalidate_cache(bs, &local_err);
|
||||
} else if (bs->file) {
|
||||
@ -5078,19 +5083,6 @@ void bdrv_invalidate_cache_all(Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
void bdrv_clear_incoming_migration_all(void)
|
||||
{
|
||||
BlockDriverState *bs;
|
||||
|
||||
QTAILQ_FOREACH(bs, &bdrv_states, device_list) {
|
||||
AioContext *aio_context = bdrv_get_aio_context(bs);
|
||||
|
||||
aio_context_acquire(aio_context);
|
||||
bs->open_flags = bs->open_flags & ~(BDRV_O_INCOMING);
|
||||
aio_context_release(aio_context);
|
||||
}
|
||||
}
|
||||
|
||||
int bdrv_flush(BlockDriverState *bs)
|
||||
{
|
||||
Coroutine *co;
|
||||
|
@ -25,6 +25,8 @@
|
||||
#define QEMU_FILE_H 1
|
||||
#include "exec/cpu-common.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* This function writes a chunk of data to a file at the given position.
|
||||
* The pos argument can be ignored if the file is only being used for
|
||||
* streaming. The handler should try to write all of the data it can.
|
||||
@ -94,11 +96,19 @@ typedef struct QEMUFileOps {
|
||||
QEMURamSaveFunc *save_page;
|
||||
} QEMUFileOps;
|
||||
|
||||
struct QEMUSizedBuffer {
|
||||
struct iovec *iov;
|
||||
size_t n_iov;
|
||||
size_t size; /* total allocated size in all iov's */
|
||||
size_t used; /* number of used bytes */
|
||||
};
|
||||
|
||||
QEMUFile *qemu_fopen_ops(void *opaque, const QEMUFileOps *ops);
|
||||
QEMUFile *qemu_fopen(const char *filename, const char *mode);
|
||||
QEMUFile *qemu_fdopen(int fd, const char *mode);
|
||||
QEMUFile *qemu_fopen_socket(int fd, const char *mode);
|
||||
QEMUFile *qemu_popen_cmd(const char *command, const char *mode);
|
||||
QEMUFile *qemu_bufopen(const char *mode, QEMUSizedBuffer *input);
|
||||
int qemu_get_fd(QEMUFile *f);
|
||||
int qemu_fclose(QEMUFile *f);
|
||||
int64_t qemu_ftell(QEMUFile *f);
|
||||
@ -110,6 +120,23 @@ void qemu_put_byte(QEMUFile *f, int v);
|
||||
*/
|
||||
void qemu_put_buffer_async(QEMUFile *f, const uint8_t *buf, int size);
|
||||
bool qemu_file_mode_is_not_valid(const char *mode);
|
||||
bool qemu_file_is_writable(QEMUFile *f);
|
||||
|
||||
QEMUSizedBuffer *qsb_create(const uint8_t *buffer, size_t len);
|
||||
QEMUSizedBuffer *qsb_clone(const QEMUSizedBuffer *);
|
||||
void qsb_free(QEMUSizedBuffer *);
|
||||
size_t qsb_set_length(QEMUSizedBuffer *qsb, size_t length);
|
||||
size_t qsb_get_length(const QEMUSizedBuffer *qsb);
|
||||
ssize_t qsb_get_buffer(const QEMUSizedBuffer *, off_t start, size_t count,
|
||||
uint8_t *buf);
|
||||
ssize_t qsb_write_at(QEMUSizedBuffer *qsb, const uint8_t *buf,
|
||||
off_t pos, size_t count);
|
||||
|
||||
|
||||
/*
|
||||
* For use on files opened with qemu_bufopen
|
||||
*/
|
||||
const QEMUSizedBuffer *qemu_buf_get(QEMUFile *f);
|
||||
|
||||
static inline void qemu_put_ubyte(QEMUFile *f, unsigned int v)
|
||||
{
|
||||
|
@ -484,6 +484,17 @@ extern const VMStateInfo vmstate_info_bitmap;
|
||||
.start = (_start), \
|
||||
}
|
||||
|
||||
#define VMSTATE_VBUFFER_ALLOC_UINT32(_field, _state, _version, _test, _start, _field_size) { \
|
||||
.name = (stringify(_field)), \
|
||||
.version_id = (_version), \
|
||||
.field_exists = (_test), \
|
||||
.size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\
|
||||
.info = &vmstate_info_buffer, \
|
||||
.flags = VMS_VBUFFER|VMS_POINTER|VMS_ALLOC, \
|
||||
.offset = offsetof(_state, _field), \
|
||||
.start = (_start), \
|
||||
}
|
||||
|
||||
#define VMSTATE_BUFFER_UNSAFE_INFO(_field, _state, _version, _info, _size) { \
|
||||
.name = (stringify(_field)), \
|
||||
.version_id = (_version), \
|
||||
|
@ -71,6 +71,7 @@ typedef struct SSIBus SSIBus;
|
||||
typedef struct EventNotifier EventNotifier;
|
||||
typedef struct VirtIODevice VirtIODevice;
|
||||
typedef struct QEMUSGList QEMUSGList;
|
||||
typedef struct QEMUSizedBuffer QEMUSizedBuffer;
|
||||
typedef struct SHPCDevice SHPCDevice;
|
||||
typedef struct FWCfgState FWCfgState;
|
||||
typedef struct PcGuestInfo PcGuestInfo;
|
||||
|
@ -103,7 +103,6 @@ static void process_incoming_migration_co(void *opaque)
|
||||
}
|
||||
qemu_announce_self();
|
||||
|
||||
bdrv_clear_incoming_migration_all();
|
||||
/* Make sure all file formats flush their mutable metadata */
|
||||
bdrv_invalidate_cache_all(&local_err);
|
||||
if (local_err) {
|
||||
|
6
nbd.c
6
nbd.c
@ -972,6 +972,12 @@ NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset,
|
||||
exp->ctx = bdrv_get_aio_context(bs);
|
||||
bdrv_ref(bs);
|
||||
bdrv_add_aio_context_notifier(bs, bs_aio_attached, bs_aio_detach, exp);
|
||||
/*
|
||||
* NBD exports are used for non-shared storage migration. Make sure
|
||||
* that BDRV_O_INCOMING is cleared and the image is ready for write
|
||||
* access since the export could be available before migration handover.
|
||||
*/
|
||||
bdrv_invalidate_cache(bs, NULL);
|
||||
return exp;
|
||||
}
|
||||
|
||||
|
194
qemu-file-stdio.c
Normal file
194
qemu-file-stdio.c
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* QEMU System Emulator
|
||||
*
|
||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "qemu-common.h"
|
||||
#include "block/coroutine.h"
|
||||
#include "migration/qemu-file.h"
|
||||
|
||||
typedef struct QEMUFileStdio {
|
||||
FILE *stdio_file;
|
||||
QEMUFile *file;
|
||||
} QEMUFileStdio;
|
||||
|
||||
static int stdio_get_fd(void *opaque)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
|
||||
return fileno(s->stdio_file);
|
||||
}
|
||||
|
||||
static int stdio_put_buffer(void *opaque, const uint8_t *buf, int64_t pos,
|
||||
int size)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
int res;
|
||||
|
||||
res = fwrite(buf, 1, size, s->stdio_file);
|
||||
|
||||
if (res != size) {
|
||||
return -errno;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int stdio_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
FILE *fp = s->stdio_file;
|
||||
int bytes;
|
||||
|
||||
for (;;) {
|
||||
clearerr(fp);
|
||||
bytes = fread(buf, 1, size, fp);
|
||||
if (bytes != 0 || !ferror(fp)) {
|
||||
break;
|
||||
}
|
||||
if (errno == EAGAIN) {
|
||||
yield_until_fd_readable(fileno(fp));
|
||||
} else if (errno != EINTR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int stdio_pclose(void *opaque)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
int ret;
|
||||
ret = pclose(s->stdio_file);
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
} else if (!WIFEXITED(ret) || WEXITSTATUS(ret) != 0) {
|
||||
/* close succeeded, but non-zero exit code: */
|
||||
ret = -EIO; /* fake errno value */
|
||||
}
|
||||
g_free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stdio_fclose(void *opaque)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
int ret = 0;
|
||||
|
||||
if (qemu_file_is_writable(s->file)) {
|
||||
int fd = fileno(s->stdio_file);
|
||||
struct stat st;
|
||||
|
||||
ret = fstat(fd, &st);
|
||||
if (ret == 0 && S_ISREG(st.st_mode)) {
|
||||
/*
|
||||
* If the file handle is a regular file make sure the
|
||||
* data is flushed to disk before signaling success.
|
||||
*/
|
||||
ret = fsync(fd);
|
||||
if (ret != 0) {
|
||||
ret = -errno;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fclose(s->stdio_file) == EOF) {
|
||||
ret = -errno;
|
||||
}
|
||||
g_free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const QEMUFileOps stdio_pipe_read_ops = {
|
||||
.get_fd = stdio_get_fd,
|
||||
.get_buffer = stdio_get_buffer,
|
||||
.close = stdio_pclose
|
||||
};
|
||||
|
||||
static const QEMUFileOps stdio_pipe_write_ops = {
|
||||
.get_fd = stdio_get_fd,
|
||||
.put_buffer = stdio_put_buffer,
|
||||
.close = stdio_pclose
|
||||
};
|
||||
|
||||
QEMUFile *qemu_popen_cmd(const char *command, const char *mode)
|
||||
{
|
||||
FILE *stdio_file;
|
||||
QEMUFileStdio *s;
|
||||
|
||||
if (mode == NULL || (mode[0] != 'r' && mode[0] != 'w') || mode[1] != 0) {
|
||||
fprintf(stderr, "qemu_popen: Argument validity check failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stdio_file = popen(command, mode);
|
||||
if (stdio_file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUFileStdio));
|
||||
|
||||
s->stdio_file = stdio_file;
|
||||
|
||||
if (mode[0] == 'r') {
|
||||
s->file = qemu_fopen_ops(s, &stdio_pipe_read_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &stdio_pipe_write_ops);
|
||||
}
|
||||
return s->file;
|
||||
}
|
||||
|
||||
static const QEMUFileOps stdio_file_read_ops = {
|
||||
.get_fd = stdio_get_fd,
|
||||
.get_buffer = stdio_get_buffer,
|
||||
.close = stdio_fclose
|
||||
};
|
||||
|
||||
static const QEMUFileOps stdio_file_write_ops = {
|
||||
.get_fd = stdio_get_fd,
|
||||
.put_buffer = stdio_put_buffer,
|
||||
.close = stdio_fclose
|
||||
};
|
||||
|
||||
QEMUFile *qemu_fopen(const char *filename, const char *mode)
|
||||
{
|
||||
QEMUFileStdio *s;
|
||||
|
||||
if (qemu_file_mode_is_not_valid(mode)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUFileStdio));
|
||||
|
||||
s->stdio_file = fopen(filename, mode);
|
||||
if (!s->stdio_file) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (mode[0] == 'w') {
|
||||
s->file = qemu_fopen_ops(s, &stdio_file_write_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &stdio_file_read_ops);
|
||||
}
|
||||
return s->file;
|
||||
fail:
|
||||
g_free(s);
|
||||
return NULL;
|
||||
}
|
223
qemu-file-unix.c
Normal file
223
qemu-file-unix.c
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* QEMU System Emulator
|
||||
*
|
||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "qemu-common.h"
|
||||
#include "qemu/iov.h"
|
||||
#include "qemu/sockets.h"
|
||||
#include "block/coroutine.h"
|
||||
#include "migration/qemu-file.h"
|
||||
|
||||
typedef struct QEMUFileSocket {
|
||||
int fd;
|
||||
QEMUFile *file;
|
||||
} QEMUFileSocket;
|
||||
|
||||
static ssize_t socket_writev_buffer(void *opaque, struct iovec *iov, int iovcnt,
|
||||
int64_t pos)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
ssize_t len;
|
||||
ssize_t size = iov_size(iov, iovcnt);
|
||||
|
||||
len = iov_send(s->fd, iov, iovcnt, 0, size);
|
||||
if (len < size) {
|
||||
len = -socket_error();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int socket_get_fd(void *opaque)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
|
||||
return s->fd;
|
||||
}
|
||||
|
||||
static int socket_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
ssize_t len;
|
||||
|
||||
for (;;) {
|
||||
len = qemu_recv(s->fd, buf, size, 0);
|
||||
if (len != -1) {
|
||||
break;
|
||||
}
|
||||
if (socket_error() == EAGAIN) {
|
||||
yield_until_fd_readable(s->fd);
|
||||
} else if (socket_error() != EINTR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == -1) {
|
||||
len = -socket_error();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int socket_close(void *opaque)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
closesocket(s->fd);
|
||||
g_free(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t unix_writev_buffer(void *opaque, struct iovec *iov, int iovcnt,
|
||||
int64_t pos)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
ssize_t len, offset;
|
||||
ssize_t size = iov_size(iov, iovcnt);
|
||||
ssize_t total = 0;
|
||||
|
||||
assert(iovcnt > 0);
|
||||
offset = 0;
|
||||
while (size > 0) {
|
||||
/* Find the next start position; skip all full-sized vector elements */
|
||||
while (offset >= iov[0].iov_len) {
|
||||
offset -= iov[0].iov_len;
|
||||
iov++, iovcnt--;
|
||||
}
|
||||
|
||||
/* skip `offset' bytes from the (now) first element, undo it on exit */
|
||||
assert(iovcnt > 0);
|
||||
iov[0].iov_base += offset;
|
||||
iov[0].iov_len -= offset;
|
||||
|
||||
do {
|
||||
len = writev(s->fd, iov, iovcnt);
|
||||
} while (len == -1 && errno == EINTR);
|
||||
if (len == -1) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
/* Undo the changes above */
|
||||
iov[0].iov_base -= offset;
|
||||
iov[0].iov_len += offset;
|
||||
|
||||
/* Prepare for the next iteration */
|
||||
offset += len;
|
||||
total += len;
|
||||
size -= len;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static int unix_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
ssize_t len;
|
||||
|
||||
for (;;) {
|
||||
len = read(s->fd, buf, size);
|
||||
if (len != -1) {
|
||||
break;
|
||||
}
|
||||
if (errno == EAGAIN) {
|
||||
yield_until_fd_readable(s->fd);
|
||||
} else if (errno != EINTR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == -1) {
|
||||
len = -errno;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int unix_close(void *opaque)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
close(s->fd);
|
||||
g_free(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const QEMUFileOps unix_read_ops = {
|
||||
.get_fd = socket_get_fd,
|
||||
.get_buffer = unix_get_buffer,
|
||||
.close = unix_close
|
||||
};
|
||||
|
||||
static const QEMUFileOps unix_write_ops = {
|
||||
.get_fd = socket_get_fd,
|
||||
.writev_buffer = unix_writev_buffer,
|
||||
.close = unix_close
|
||||
};
|
||||
|
||||
QEMUFile *qemu_fdopen(int fd, const char *mode)
|
||||
{
|
||||
QEMUFileSocket *s;
|
||||
|
||||
if (mode == NULL ||
|
||||
(mode[0] != 'r' && mode[0] != 'w') ||
|
||||
mode[1] != 'b' || mode[2] != 0) {
|
||||
fprintf(stderr, "qemu_fdopen: Argument validity check failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUFileSocket));
|
||||
s->fd = fd;
|
||||
|
||||
if (mode[0] == 'r') {
|
||||
s->file = qemu_fopen_ops(s, &unix_read_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &unix_write_ops);
|
||||
}
|
||||
return s->file;
|
||||
}
|
||||
|
||||
static const QEMUFileOps socket_read_ops = {
|
||||
.get_fd = socket_get_fd,
|
||||
.get_buffer = socket_get_buffer,
|
||||
.close = socket_close
|
||||
};
|
||||
|
||||
static const QEMUFileOps socket_write_ops = {
|
||||
.get_fd = socket_get_fd,
|
||||
.writev_buffer = socket_writev_buffer,
|
||||
.close = socket_close
|
||||
};
|
||||
|
||||
QEMUFile *qemu_fopen_socket(int fd, const char *mode)
|
||||
{
|
||||
QEMUFileSocket *s;
|
||||
|
||||
if (qemu_file_mode_is_not_valid(mode)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUFileSocket));
|
||||
s->fd = fd;
|
||||
if (mode[0] == 'w') {
|
||||
qemu_set_block(s->fd);
|
||||
s->file = qemu_fopen_ops(s, &socket_write_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &socket_read_ops);
|
||||
}
|
||||
return s->file;
|
||||
}
|
843
qemu-file.c
843
qemu-file.c
@ -1,3 +1,26 @@
|
||||
/*
|
||||
* QEMU System Emulator
|
||||
*
|
||||
* Copyright (c) 2003-2008 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include "qemu-common.h"
|
||||
#include "qemu/iov.h"
|
||||
#include "qemu/sockets.h"
|
||||
@ -28,324 +51,6 @@ struct QEMUFile {
|
||||
int last_error;
|
||||
};
|
||||
|
||||
typedef struct QEMUFileStdio {
|
||||
FILE *stdio_file;
|
||||
QEMUFile *file;
|
||||
} QEMUFileStdio;
|
||||
|
||||
typedef struct QEMUFileSocket {
|
||||
int fd;
|
||||
QEMUFile *file;
|
||||
} QEMUFileSocket;
|
||||
|
||||
static ssize_t socket_writev_buffer(void *opaque, struct iovec *iov, int iovcnt,
|
||||
int64_t pos)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
ssize_t len;
|
||||
ssize_t size = iov_size(iov, iovcnt);
|
||||
|
||||
len = iov_send(s->fd, iov, iovcnt, 0, size);
|
||||
if (len < size) {
|
||||
len = -socket_error();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int socket_get_fd(void *opaque)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
|
||||
return s->fd;
|
||||
}
|
||||
|
||||
static int socket_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
ssize_t len;
|
||||
|
||||
for (;;) {
|
||||
len = qemu_recv(s->fd, buf, size, 0);
|
||||
if (len != -1) {
|
||||
break;
|
||||
}
|
||||
if (socket_error() == EAGAIN) {
|
||||
yield_until_fd_readable(s->fd);
|
||||
} else if (socket_error() != EINTR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == -1) {
|
||||
len = -socket_error();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int socket_close(void *opaque)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
closesocket(s->fd);
|
||||
g_free(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stdio_get_fd(void *opaque)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
|
||||
return fileno(s->stdio_file);
|
||||
}
|
||||
|
||||
static int stdio_put_buffer(void *opaque, const uint8_t *buf, int64_t pos,
|
||||
int size)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
int res;
|
||||
|
||||
res = fwrite(buf, 1, size, s->stdio_file);
|
||||
|
||||
if (res != size) {
|
||||
return -errno;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int stdio_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
FILE *fp = s->stdio_file;
|
||||
int bytes;
|
||||
|
||||
for (;;) {
|
||||
clearerr(fp);
|
||||
bytes = fread(buf, 1, size, fp);
|
||||
if (bytes != 0 || !ferror(fp)) {
|
||||
break;
|
||||
}
|
||||
if (errno == EAGAIN) {
|
||||
yield_until_fd_readable(fileno(fp));
|
||||
} else if (errno != EINTR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int stdio_pclose(void *opaque)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
int ret;
|
||||
ret = pclose(s->stdio_file);
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
} else if (!WIFEXITED(ret) || WEXITSTATUS(ret) != 0) {
|
||||
/* close succeeded, but non-zero exit code: */
|
||||
ret = -EIO; /* fake errno value */
|
||||
}
|
||||
g_free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int stdio_fclose(void *opaque)
|
||||
{
|
||||
QEMUFileStdio *s = opaque;
|
||||
int ret = 0;
|
||||
|
||||
if (s->file->ops->put_buffer || s->file->ops->writev_buffer) {
|
||||
int fd = fileno(s->stdio_file);
|
||||
struct stat st;
|
||||
|
||||
ret = fstat(fd, &st);
|
||||
if (ret == 0 && S_ISREG(st.st_mode)) {
|
||||
/*
|
||||
* If the file handle is a regular file make sure the
|
||||
* data is flushed to disk before signaling success.
|
||||
*/
|
||||
ret = fsync(fd);
|
||||
if (ret != 0) {
|
||||
ret = -errno;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fclose(s->stdio_file) == EOF) {
|
||||
ret = -errno;
|
||||
}
|
||||
g_free(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const QEMUFileOps stdio_pipe_read_ops = {
|
||||
.get_fd = stdio_get_fd,
|
||||
.get_buffer = stdio_get_buffer,
|
||||
.close = stdio_pclose
|
||||
};
|
||||
|
||||
static const QEMUFileOps stdio_pipe_write_ops = {
|
||||
.get_fd = stdio_get_fd,
|
||||
.put_buffer = stdio_put_buffer,
|
||||
.close = stdio_pclose
|
||||
};
|
||||
|
||||
QEMUFile *qemu_popen_cmd(const char *command, const char *mode)
|
||||
{
|
||||
FILE *stdio_file;
|
||||
QEMUFileStdio *s;
|
||||
|
||||
if (mode == NULL || (mode[0] != 'r' && mode[0] != 'w') || mode[1] != 0) {
|
||||
fprintf(stderr, "qemu_popen: Argument validity check failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stdio_file = popen(command, mode);
|
||||
if (stdio_file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUFileStdio));
|
||||
|
||||
s->stdio_file = stdio_file;
|
||||
|
||||
if (mode[0] == 'r') {
|
||||
s->file = qemu_fopen_ops(s, &stdio_pipe_read_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &stdio_pipe_write_ops);
|
||||
}
|
||||
return s->file;
|
||||
}
|
||||
|
||||
static const QEMUFileOps stdio_file_read_ops = {
|
||||
.get_fd = stdio_get_fd,
|
||||
.get_buffer = stdio_get_buffer,
|
||||
.close = stdio_fclose
|
||||
};
|
||||
|
||||
static const QEMUFileOps stdio_file_write_ops = {
|
||||
.get_fd = stdio_get_fd,
|
||||
.put_buffer = stdio_put_buffer,
|
||||
.close = stdio_fclose
|
||||
};
|
||||
|
||||
static ssize_t unix_writev_buffer(void *opaque, struct iovec *iov, int iovcnt,
|
||||
int64_t pos)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
ssize_t len, offset;
|
||||
ssize_t size = iov_size(iov, iovcnt);
|
||||
ssize_t total = 0;
|
||||
|
||||
assert(iovcnt > 0);
|
||||
offset = 0;
|
||||
while (size > 0) {
|
||||
/* Find the next start position; skip all full-sized vector elements */
|
||||
while (offset >= iov[0].iov_len) {
|
||||
offset -= iov[0].iov_len;
|
||||
iov++, iovcnt--;
|
||||
}
|
||||
|
||||
/* skip `offset' bytes from the (now) first element, undo it on exit */
|
||||
assert(iovcnt > 0);
|
||||
iov[0].iov_base += offset;
|
||||
iov[0].iov_len -= offset;
|
||||
|
||||
do {
|
||||
len = writev(s->fd, iov, iovcnt);
|
||||
} while (len == -1 && errno == EINTR);
|
||||
if (len == -1) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
/* Undo the changes above */
|
||||
iov[0].iov_base -= offset;
|
||||
iov[0].iov_len += offset;
|
||||
|
||||
/* Prepare for the next iteration */
|
||||
offset += len;
|
||||
total += len;
|
||||
size -= len;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static int unix_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
ssize_t len;
|
||||
|
||||
for (;;) {
|
||||
len = read(s->fd, buf, size);
|
||||
if (len != -1) {
|
||||
break;
|
||||
}
|
||||
if (errno == EAGAIN) {
|
||||
yield_until_fd_readable(s->fd);
|
||||
} else if (errno != EINTR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (len == -1) {
|
||||
len = -errno;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int unix_close(void *opaque)
|
||||
{
|
||||
QEMUFileSocket *s = opaque;
|
||||
close(s->fd);
|
||||
g_free(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const QEMUFileOps unix_read_ops = {
|
||||
.get_fd = socket_get_fd,
|
||||
.get_buffer = unix_get_buffer,
|
||||
.close = unix_close
|
||||
};
|
||||
|
||||
static const QEMUFileOps unix_write_ops = {
|
||||
.get_fd = socket_get_fd,
|
||||
.writev_buffer = unix_writev_buffer,
|
||||
.close = unix_close
|
||||
};
|
||||
|
||||
QEMUFile *qemu_fdopen(int fd, const char *mode)
|
||||
{
|
||||
QEMUFileSocket *s;
|
||||
|
||||
if (mode == NULL ||
|
||||
(mode[0] != 'r' && mode[0] != 'w') ||
|
||||
mode[1] != 'b' || mode[2] != 0) {
|
||||
fprintf(stderr, "qemu_fdopen: Argument validity check failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUFileSocket));
|
||||
s->fd = fd;
|
||||
|
||||
if (mode[0] == 'r') {
|
||||
s->file = qemu_fopen_ops(s, &unix_read_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &unix_write_ops);
|
||||
}
|
||||
return s->file;
|
||||
}
|
||||
|
||||
static const QEMUFileOps socket_read_ops = {
|
||||
.get_fd = socket_get_fd,
|
||||
.get_buffer = socket_get_buffer,
|
||||
.close = socket_close
|
||||
};
|
||||
|
||||
static const QEMUFileOps socket_write_ops = {
|
||||
.get_fd = socket_get_fd,
|
||||
.writev_buffer = socket_writev_buffer,
|
||||
.close = socket_close
|
||||
};
|
||||
|
||||
bool qemu_file_mode_is_not_valid(const char *mode)
|
||||
{
|
||||
if (mode == NULL ||
|
||||
@ -358,51 +63,6 @@ bool qemu_file_mode_is_not_valid(const char *mode)
|
||||
return false;
|
||||
}
|
||||
|
||||
QEMUFile *qemu_fopen_socket(int fd, const char *mode)
|
||||
{
|
||||
QEMUFileSocket *s;
|
||||
|
||||
if (qemu_file_mode_is_not_valid(mode)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUFileSocket));
|
||||
s->fd = fd;
|
||||
if (mode[0] == 'w') {
|
||||
qemu_set_block(s->fd);
|
||||
s->file = qemu_fopen_ops(s, &socket_write_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &socket_read_ops);
|
||||
}
|
||||
return s->file;
|
||||
}
|
||||
|
||||
QEMUFile *qemu_fopen(const char *filename, const char *mode)
|
||||
{
|
||||
QEMUFileStdio *s;
|
||||
|
||||
if (qemu_file_mode_is_not_valid(mode)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUFileStdio));
|
||||
|
||||
s->stdio_file = fopen(filename, mode);
|
||||
if (!s->stdio_file) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (mode[0] == 'w') {
|
||||
s->file = qemu_fopen_ops(s, &stdio_file_write_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &stdio_file_read_ops);
|
||||
}
|
||||
return s->file;
|
||||
fail:
|
||||
g_free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QEMUFile *qemu_fopen_ops(void *opaque, const QEMUFileOps *ops)
|
||||
{
|
||||
QEMUFile *f;
|
||||
@ -433,7 +93,7 @@ void qemu_file_set_error(QEMUFile *f, int ret)
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool qemu_file_is_writable(QEMUFile *f)
|
||||
bool qemu_file_is_writable(QEMUFile *f)
|
||||
{
|
||||
return f->ops->writev_buffer || f->ops->put_buffer;
|
||||
}
|
||||
@ -878,3 +538,458 @@ uint64_t qemu_get_be64(QEMUFile *f)
|
||||
v |= qemu_get_be32(f);
|
||||
return v;
|
||||
}
|
||||
|
||||
#define QSB_CHUNK_SIZE (1 << 10)
|
||||
#define QSB_MAX_CHUNK_SIZE (16 * QSB_CHUNK_SIZE)
|
||||
|
||||
/**
|
||||
* Create a QEMUSizedBuffer
|
||||
* This type of buffer uses scatter-gather lists internally and
|
||||
* can grow to any size. Any data array in the scatter-gather list
|
||||
* can hold different amount of bytes.
|
||||
*
|
||||
* @buffer: Optional buffer to copy into the QSB
|
||||
* @len: size of initial buffer; if @buffer is given, buffer must
|
||||
* hold at least len bytes
|
||||
*
|
||||
* Returns a pointer to a QEMUSizedBuffer or NULL on allocation failure
|
||||
*/
|
||||
QEMUSizedBuffer *qsb_create(const uint8_t *buffer, size_t len)
|
||||
{
|
||||
QEMUSizedBuffer *qsb;
|
||||
size_t alloc_len, num_chunks, i, to_copy;
|
||||
size_t chunk_size = (len > QSB_MAX_CHUNK_SIZE)
|
||||
? QSB_MAX_CHUNK_SIZE
|
||||
: QSB_CHUNK_SIZE;
|
||||
|
||||
num_chunks = DIV_ROUND_UP(len ? len : QSB_CHUNK_SIZE, chunk_size);
|
||||
alloc_len = num_chunks * chunk_size;
|
||||
|
||||
qsb = g_try_new0(QEMUSizedBuffer, 1);
|
||||
if (!qsb) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qsb->iov = g_try_new0(struct iovec, num_chunks);
|
||||
if (!qsb->iov) {
|
||||
g_free(qsb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qsb->n_iov = num_chunks;
|
||||
|
||||
for (i = 0; i < num_chunks; i++) {
|
||||
qsb->iov[i].iov_base = g_try_malloc0(chunk_size);
|
||||
if (!qsb->iov[i].iov_base) {
|
||||
/* qsb_free is safe since g_free can cope with NULL */
|
||||
qsb_free(qsb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qsb->iov[i].iov_len = chunk_size;
|
||||
if (buffer) {
|
||||
to_copy = (len - qsb->used) > chunk_size
|
||||
? chunk_size : (len - qsb->used);
|
||||
memcpy(qsb->iov[i].iov_base, &buffer[qsb->used], to_copy);
|
||||
qsb->used += to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
qsb->size = alloc_len;
|
||||
|
||||
return qsb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the QEMUSizedBuffer
|
||||
*
|
||||
* @qsb: The QEMUSizedBuffer to free
|
||||
*/
|
||||
void qsb_free(QEMUSizedBuffer *qsb)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!qsb) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < qsb->n_iov; i++) {
|
||||
g_free(qsb->iov[i].iov_base);
|
||||
}
|
||||
g_free(qsb->iov);
|
||||
g_free(qsb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of used bytes in the QEMUSizedBuffer
|
||||
*
|
||||
* @qsb: A QEMUSizedBuffer
|
||||
*
|
||||
* Returns the number of bytes currently used in this buffer
|
||||
*/
|
||||
size_t qsb_get_length(const QEMUSizedBuffer *qsb)
|
||||
{
|
||||
return qsb->used;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the length of the buffer; the primary usage of this
|
||||
* function is to truncate the number of used bytes in the buffer.
|
||||
* The size will not be extended beyond the current number of
|
||||
* allocated bytes in the QEMUSizedBuffer.
|
||||
*
|
||||
* @qsb: A QEMUSizedBuffer
|
||||
* @new_len: The new length of bytes in the buffer
|
||||
*
|
||||
* Returns the number of bytes the buffer was truncated or extended
|
||||
* to.
|
||||
*/
|
||||
size_t qsb_set_length(QEMUSizedBuffer *qsb, size_t new_len)
|
||||
{
|
||||
if (new_len <= qsb->size) {
|
||||
qsb->used = new_len;
|
||||
} else {
|
||||
qsb->used = qsb->size;
|
||||
}
|
||||
return qsb->used;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the iovec that holds the data for a given position @pos.
|
||||
*
|
||||
* @qsb: A QEMUSizedBuffer
|
||||
* @pos: The index of a byte in the buffer
|
||||
* @d_off: Pointer to an offset that this function will indicate
|
||||
* at what position within the returned iovec the byte
|
||||
* is to be found
|
||||
*
|
||||
* Returns the index of the iovec that holds the byte at the given
|
||||
* index @pos in the byte stream; a negative number if the iovec
|
||||
* for the given position @pos does not exist.
|
||||
*/
|
||||
static ssize_t qsb_get_iovec(const QEMUSizedBuffer *qsb,
|
||||
off_t pos, off_t *d_off)
|
||||
{
|
||||
ssize_t i;
|
||||
off_t curr = 0;
|
||||
|
||||
if (pos > qsb->used) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < qsb->n_iov; i++) {
|
||||
if (curr + qsb->iov[i].iov_len > pos) {
|
||||
*d_off = pos - curr;
|
||||
return i;
|
||||
}
|
||||
curr += qsb->iov[i].iov_len;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the QEMUSizedBuffer into a flat buffer.
|
||||
*
|
||||
* Note: If at all possible, try to avoid this function since it
|
||||
* may unnecessarily copy memory around.
|
||||
*
|
||||
* @qsb: pointer to QEMUSizedBuffer
|
||||
* @start: offset to start at
|
||||
* @count: number of bytes to copy
|
||||
* @buf: a pointer to a buffer to write into (at least @count bytes)
|
||||
*
|
||||
* Returns the number of bytes copied into the output buffer
|
||||
*/
|
||||
ssize_t qsb_get_buffer(const QEMUSizedBuffer *qsb, off_t start,
|
||||
size_t count, uint8_t *buffer)
|
||||
{
|
||||
const struct iovec *iov;
|
||||
size_t to_copy, all_copy;
|
||||
ssize_t index;
|
||||
off_t s_off;
|
||||
off_t d_off = 0;
|
||||
char *s;
|
||||
|
||||
if (start > qsb->used) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
all_copy = qsb->used - start;
|
||||
if (all_copy > count) {
|
||||
all_copy = count;
|
||||
} else {
|
||||
count = all_copy;
|
||||
}
|
||||
|
||||
index = qsb_get_iovec(qsb, start, &s_off);
|
||||
if (index < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (all_copy > 0) {
|
||||
iov = &qsb->iov[index];
|
||||
|
||||
s = iov->iov_base;
|
||||
|
||||
to_copy = iov->iov_len - s_off;
|
||||
if (to_copy > all_copy) {
|
||||
to_copy = all_copy;
|
||||
}
|
||||
memcpy(&buffer[d_off], &s[s_off], to_copy);
|
||||
|
||||
d_off += to_copy;
|
||||
all_copy -= to_copy;
|
||||
|
||||
s_off = 0;
|
||||
index++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grow the QEMUSizedBuffer to the given size and allocate
|
||||
* memory for it.
|
||||
*
|
||||
* @qsb: A QEMUSizedBuffer
|
||||
* @new_size: The new size of the buffer
|
||||
*
|
||||
* Return:
|
||||
* a negative error code in case of memory allocation failure
|
||||
* or
|
||||
* the new size of the buffer. The returned size may be greater or equal
|
||||
* to @new_size.
|
||||
*/
|
||||
static ssize_t qsb_grow(QEMUSizedBuffer *qsb, size_t new_size)
|
||||
{
|
||||
size_t needed_chunks, i;
|
||||
|
||||
if (qsb->size < new_size) {
|
||||
struct iovec *new_iov;
|
||||
size_t size_diff = new_size - qsb->size;
|
||||
size_t chunk_size = (size_diff > QSB_MAX_CHUNK_SIZE)
|
||||
? QSB_MAX_CHUNK_SIZE : QSB_CHUNK_SIZE;
|
||||
|
||||
needed_chunks = DIV_ROUND_UP(size_diff, chunk_size);
|
||||
|
||||
new_iov = g_try_new(struct iovec, qsb->n_iov + needed_chunks);
|
||||
if (new_iov == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Allocate new chunks as needed into new_iov */
|
||||
for (i = qsb->n_iov; i < qsb->n_iov + needed_chunks; i++) {
|
||||
new_iov[i].iov_base = g_try_malloc0(chunk_size);
|
||||
new_iov[i].iov_len = chunk_size;
|
||||
if (!new_iov[i].iov_base) {
|
||||
size_t j;
|
||||
|
||||
/* Free previously allocated new chunks */
|
||||
for (j = qsb->n_iov; j < i; j++) {
|
||||
g_free(new_iov[j].iov_base);
|
||||
}
|
||||
g_free(new_iov);
|
||||
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now we can't get any allocation errors, copy over to new iov
|
||||
* and switch.
|
||||
*/
|
||||
for (i = 0; i < qsb->n_iov; i++) {
|
||||
new_iov[i] = qsb->iov[i];
|
||||
}
|
||||
|
||||
qsb->n_iov += needed_chunks;
|
||||
g_free(qsb->iov);
|
||||
qsb->iov = new_iov;
|
||||
qsb->size += (needed_chunks * chunk_size);
|
||||
}
|
||||
|
||||
return qsb->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write into the QEMUSizedBuffer at a given position and a given
|
||||
* number of bytes. This function will automatically grow the
|
||||
* QEMUSizedBuffer.
|
||||
*
|
||||
* @qsb: A QEMUSizedBuffer
|
||||
* @source: A byte array to copy data from
|
||||
* @pos: The position within the @qsb to write data to
|
||||
* @size: The number of bytes to copy into the @qsb
|
||||
*
|
||||
* Returns @size or a negative error code in case of memory allocation failure,
|
||||
* or with an invalid 'pos'
|
||||
*/
|
||||
ssize_t qsb_write_at(QEMUSizedBuffer *qsb, const uint8_t *source,
|
||||
off_t pos, size_t count)
|
||||
{
|
||||
ssize_t rc = qsb_grow(qsb, pos + count);
|
||||
size_t to_copy;
|
||||
size_t all_copy = count;
|
||||
const struct iovec *iov;
|
||||
ssize_t index;
|
||||
char *dest;
|
||||
off_t d_off, s_off = 0;
|
||||
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (pos + count > qsb->used) {
|
||||
qsb->used = pos + count;
|
||||
}
|
||||
|
||||
index = qsb_get_iovec(qsb, pos, &d_off);
|
||||
if (index < 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
while (all_copy > 0) {
|
||||
iov = &qsb->iov[index];
|
||||
|
||||
dest = iov->iov_base;
|
||||
|
||||
to_copy = iov->iov_len - d_off;
|
||||
if (to_copy > all_copy) {
|
||||
to_copy = all_copy;
|
||||
}
|
||||
|
||||
memcpy(&dest[d_off], &source[s_off], to_copy);
|
||||
|
||||
s_off += to_copy;
|
||||
all_copy -= to_copy;
|
||||
|
||||
d_off = 0;
|
||||
index++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of the given QEMUSizedBuffer.
|
||||
*
|
||||
* @qsb: A QEMUSizedBuffer
|
||||
*
|
||||
* Returns a clone of @qsb or NULL on allocation failure
|
||||
*/
|
||||
QEMUSizedBuffer *qsb_clone(const QEMUSizedBuffer *qsb)
|
||||
{
|
||||
QEMUSizedBuffer *out = qsb_create(NULL, qsb_get_length(qsb));
|
||||
size_t i;
|
||||
ssize_t res;
|
||||
off_t pos = 0;
|
||||
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < qsb->n_iov; i++) {
|
||||
res = qsb_write_at(out, qsb->iov[i].iov_base,
|
||||
pos, qsb->iov[i].iov_len);
|
||||
if (res < 0) {
|
||||
qsb_free(out);
|
||||
return NULL;
|
||||
}
|
||||
pos += res;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
typedef struct QEMUBuffer {
|
||||
QEMUSizedBuffer *qsb;
|
||||
QEMUFile *file;
|
||||
} QEMUBuffer;
|
||||
|
||||
static int buf_get_buffer(void *opaque, uint8_t *buf, int64_t pos, int size)
|
||||
{
|
||||
QEMUBuffer *s = opaque;
|
||||
ssize_t len = qsb_get_length(s->qsb) - pos;
|
||||
|
||||
if (len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (len > size) {
|
||||
len = size;
|
||||
}
|
||||
return qsb_get_buffer(s->qsb, pos, len, buf);
|
||||
}
|
||||
|
||||
static int buf_put_buffer(void *opaque, const uint8_t *buf,
|
||||
int64_t pos, int size)
|
||||
{
|
||||
QEMUBuffer *s = opaque;
|
||||
|
||||
return qsb_write_at(s->qsb, buf, pos, size);
|
||||
}
|
||||
|
||||
static int buf_close(void *opaque)
|
||||
{
|
||||
QEMUBuffer *s = opaque;
|
||||
|
||||
qsb_free(s->qsb);
|
||||
|
||||
g_free(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const QEMUSizedBuffer *qemu_buf_get(QEMUFile *f)
|
||||
{
|
||||
QEMUBuffer *p;
|
||||
|
||||
qemu_fflush(f);
|
||||
|
||||
p = f->opaque;
|
||||
|
||||
return p->qsb;
|
||||
}
|
||||
|
||||
static const QEMUFileOps buf_read_ops = {
|
||||
.get_buffer = buf_get_buffer,
|
||||
.close = buf_close,
|
||||
};
|
||||
|
||||
static const QEMUFileOps buf_write_ops = {
|
||||
.put_buffer = buf_put_buffer,
|
||||
.close = buf_close,
|
||||
};
|
||||
|
||||
QEMUFile *qemu_bufopen(const char *mode, QEMUSizedBuffer *input)
|
||||
{
|
||||
QEMUBuffer *s;
|
||||
|
||||
if (mode == NULL || (mode[0] != 'r' && mode[0] != 'w') ||
|
||||
mode[1] != '\0') {
|
||||
error_report("qemu_bufopen: Argument validity check failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = g_malloc0(sizeof(QEMUBuffer));
|
||||
if (mode[0] == 'r') {
|
||||
s->qsb = input;
|
||||
}
|
||||
|
||||
if (s->qsb == NULL) {
|
||||
s->qsb = qsb_create(NULL, 0);
|
||||
}
|
||||
if (!s->qsb) {
|
||||
g_free(s);
|
||||
error_report("qemu_bufopen: qsb_create failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
if (mode[0] == 'r') {
|
||||
s->file = qemu_fopen_ops(s, &buf_read_ops);
|
||||
} else {
|
||||
s->file = qemu_fopen_ops(s, &buf_write_ops);
|
||||
}
|
||||
return s->file;
|
||||
}
|
||||
|
@ -258,8 +258,8 @@ tests/test-qdev-global-props$(EXESUF): tests/test-qdev-global-props.o \
|
||||
$(test-qapi-obj-y) \
|
||||
libqemuutil.a libqemustub.a
|
||||
tests/test-vmstate$(EXESUF): tests/test-vmstate.o \
|
||||
vmstate.o qemu-file.o \
|
||||
libqemuutil.a
|
||||
vmstate.o qemu-file.o qemu-file-unix.o \
|
||||
libqemuutil.a libqemustub.a
|
||||
|
||||
tests/test-qapi-types.c tests/test-qapi-types.h :\
|
||||
$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py
|
||||
|
@ -43,6 +43,12 @@ void yield_until_fd_readable(int fd)
|
||||
select(fd + 1, &fds, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Some tests use 'open_test_file' to work on a real fd, some use
|
||||
* an in memory file (QEMUSizedBuffer+qemu_bufopen); we could pick one
|
||||
* but this way we test both.
|
||||
*/
|
||||
|
||||
/* Duplicate temp_fd and seek to the beginning of the file */
|
||||
static QEMUFile *open_test_file(bool write)
|
||||
{
|
||||
@ -54,6 +60,30 @@ static QEMUFile *open_test_file(bool write)
|
||||
return qemu_fdopen(fd, write ? "wb" : "rb");
|
||||
}
|
||||
|
||||
/* Open a read-only qemu-file from an existing memory block */
|
||||
static QEMUFile *open_mem_file_read(const void *data, size_t len)
|
||||
{
|
||||
/* The qsb gets freed by qemu_fclose */
|
||||
QEMUSizedBuffer *qsb = qsb_create(data, len);
|
||||
g_assert(qsb);
|
||||
|
||||
return qemu_bufopen("r", qsb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that the contents of the memory-buffered file f match
|
||||
* the given size/data.
|
||||
*/
|
||||
static void check_mem_file(QEMUFile *f, void *data, size_t size)
|
||||
{
|
||||
uint8_t *result = g_malloc(size);
|
||||
const QEMUSizedBuffer *qsb = qemu_buf_get(f);
|
||||
g_assert_cmpint(qsb_get_length(qsb), ==, size);
|
||||
g_assert_cmpint(qsb_get_buffer(qsb, 0, size, result), ==, size);
|
||||
g_assert_cmpint(memcmp(result, data, size), ==, 0);
|
||||
g_free(result);
|
||||
}
|
||||
|
||||
#define SUCCESS(val) \
|
||||
g_assert_cmpint((val), ==, 0)
|
||||
|
||||
@ -371,14 +401,12 @@ static const VMStateDescription vmstate_skipping = {
|
||||
|
||||
static void test_save_noskip(void)
|
||||
{
|
||||
QEMUFile *fsave = open_test_file(true);
|
||||
QEMUFile *fsave = qemu_bufopen("w", NULL);
|
||||
TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6,
|
||||
.skip_c_e = false };
|
||||
vmstate_save_state(fsave, &vmstate_skipping, &obj);
|
||||
g_assert(!qemu_file_get_error(fsave));
|
||||
qemu_fclose(fsave);
|
||||
|
||||
QEMUFile *loading = open_test_file(false);
|
||||
uint8_t expected[] = {
|
||||
0, 0, 0, 1, /* a */
|
||||
0, 0, 0, 2, /* b */
|
||||
@ -387,52 +415,31 @@ static void test_save_noskip(void)
|
||||
0, 0, 0, 5, /* e */
|
||||
0, 0, 0, 0, 0, 0, 0, 6, /* f */
|
||||
};
|
||||
uint8_t result[sizeof(expected)];
|
||||
g_assert_cmpint(qemu_get_buffer(loading, result, sizeof(result)), ==,
|
||||
sizeof(result));
|
||||
g_assert(!qemu_file_get_error(loading));
|
||||
g_assert_cmpint(memcmp(result, expected, sizeof(result)), ==, 0);
|
||||
|
||||
/* Must reach EOF */
|
||||
qemu_get_byte(loading);
|
||||
g_assert_cmpint(qemu_file_get_error(loading), ==, -EIO);
|
||||
|
||||
qemu_fclose(loading);
|
||||
check_mem_file(fsave, expected, sizeof(expected));
|
||||
qemu_fclose(fsave);
|
||||
}
|
||||
|
||||
static void test_save_skip(void)
|
||||
{
|
||||
QEMUFile *fsave = open_test_file(true);
|
||||
QEMUFile *fsave = qemu_bufopen("w", NULL);
|
||||
TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6,
|
||||
.skip_c_e = true };
|
||||
vmstate_save_state(fsave, &vmstate_skipping, &obj);
|
||||
g_assert(!qemu_file_get_error(fsave));
|
||||
qemu_fclose(fsave);
|
||||
|
||||
QEMUFile *loading = open_test_file(false);
|
||||
uint8_t expected[] = {
|
||||
0, 0, 0, 1, /* a */
|
||||
0, 0, 0, 2, /* b */
|
||||
0, 0, 0, 0, 0, 0, 0, 4, /* d */
|
||||
0, 0, 0, 0, 0, 0, 0, 6, /* f */
|
||||
};
|
||||
uint8_t result[sizeof(expected)];
|
||||
g_assert_cmpint(qemu_get_buffer(loading, result, sizeof(result)), ==,
|
||||
sizeof(result));
|
||||
g_assert(!qemu_file_get_error(loading));
|
||||
g_assert_cmpint(memcmp(result, expected, sizeof(result)), ==, 0);
|
||||
check_mem_file(fsave, expected, sizeof(expected));
|
||||
|
||||
|
||||
/* Must reach EOF */
|
||||
qemu_get_byte(loading);
|
||||
g_assert_cmpint(qemu_file_get_error(loading), ==, -EIO);
|
||||
|
||||
qemu_fclose(loading);
|
||||
qemu_fclose(fsave);
|
||||
}
|
||||
|
||||
static void test_load_noskip(void)
|
||||
{
|
||||
QEMUFile *fsave = open_test_file(true);
|
||||
uint8_t buf[] = {
|
||||
0, 0, 0, 10, /* a */
|
||||
0, 0, 0, 20, /* b */
|
||||
@ -442,10 +449,8 @@ static void test_load_noskip(void)
|
||||
0, 0, 0, 0, 0, 0, 0, 60, /* f */
|
||||
QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */
|
||||
};
|
||||
qemu_put_buffer(fsave, buf, sizeof(buf));
|
||||
qemu_fclose(fsave);
|
||||
|
||||
QEMUFile *loading = open_test_file(false);
|
||||
QEMUFile *loading = open_mem_file_read(buf, sizeof(buf));
|
||||
TestStruct obj = { .skip_c_e = false };
|
||||
vmstate_load_state(loading, &vmstate_skipping, &obj, 2);
|
||||
g_assert(!qemu_file_get_error(loading));
|
||||
@ -460,7 +465,6 @@ static void test_load_noskip(void)
|
||||
|
||||
static void test_load_skip(void)
|
||||
{
|
||||
QEMUFile *fsave = open_test_file(true);
|
||||
uint8_t buf[] = {
|
||||
0, 0, 0, 10, /* a */
|
||||
0, 0, 0, 20, /* b */
|
||||
@ -468,10 +472,8 @@ static void test_load_skip(void)
|
||||
0, 0, 0, 0, 0, 0, 0, 60, /* f */
|
||||
QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */
|
||||
};
|
||||
qemu_put_buffer(fsave, buf, sizeof(buf));
|
||||
qemu_fclose(fsave);
|
||||
|
||||
QEMUFile *loading = open_test_file(false);
|
||||
QEMUFile *loading = open_mem_file_read(buf, sizeof(buf));
|
||||
TestStruct obj = { .skip_c_e = true, .c = 300, .e = 500 };
|
||||
vmstate_load_state(loading, &vmstate_skipping, &obj, 2);
|
||||
g_assert(!qemu_file_get_error(loading));
|
||||
|
13
vmstate.c
13
vmstate.c
@ -49,9 +49,16 @@ static void *vmstate_base_addr(void *opaque, VMStateField *field, bool alloc)
|
||||
|
||||
if (field->flags & VMS_POINTER) {
|
||||
if (alloc && (field->flags & VMS_ALLOC)) {
|
||||
int n_elems = vmstate_n_elems(opaque, field);
|
||||
if (n_elems) {
|
||||
gsize size = n_elems * field->size;
|
||||
gsize size = 0;
|
||||
if (field->flags & VMS_VBUFFER) {
|
||||
size = vmstate_size(opaque, field);
|
||||
} else {
|
||||
int n_elems = vmstate_n_elems(opaque, field);
|
||||
if (n_elems) {
|
||||
size = n_elems * field->size;
|
||||
}
|
||||
}
|
||||
if (size) {
|
||||
*((void **)base_addr + field->start) = g_malloc(size);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user