From 3c7026b1802c7e00d30f33c458b67126396ad10a Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Fri, 1 Jul 2011 19:49:42 +0300 Subject: server/red_{record, replay}.[ch]: introduce MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently hand crafted with some sed scripts and alot of vim macros from red_parse_qxl after considering the logger in qemu/hw/qxl-logger.c and seeing it was incomplete. The only problem with logging from the server and not from qemu is that it requires coordinated shutdown to avoid half a message. Should be automatically generated from a declarative syntax, i.e. qxl.proto. Note: zlib compression is introduced in a disabled state, see ZLIB define Now with a simple versioned header and generated ids by the server instead of based on the recorded file, and doesn't use more then 1024 surfaces (configurable). Signed-off-by: Alon Levy Signed-off-by: Marc-AndrĂ© Lureau --- server/Makefile.am | 2 + server/red_record_qxl.c | 827 ++++++++++++++++++++++++++++++ server/red_record_qxl.h | 34 ++ server/red_replay_qxl.c | 1243 ++++++++++++++++++++++++++++++++++++++++++++++ server/red_replay_qxl.h | 34 ++ server/spice-server.syms | 4 + 6 files changed, 2144 insertions(+) create mode 100644 server/red_record_qxl.c create mode 100644 server/red_record_qxl.h create mode 100644 server/red_replay_qxl.c create mode 100644 server/red_replay_qxl.h diff --git a/server/Makefile.am b/server/Makefile.am index 61372499..09b44984 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -99,6 +99,8 @@ libspice_server_la_SOURCES = \ red_memslots.c \ red_memslots.h \ red_parse_qxl.c \ + red_record_qxl.c \ + red_replay_qxl.c \ red_parse_qxl.h \ red_time.h \ red_worker.c \ diff --git a/server/red_record_qxl.c b/server/red_record_qxl.c new file mode 100644 index 00000000..d96fb799 --- /dev/null +++ b/server/red_record_qxl.c @@ -0,0 +1,827 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009,2010 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include "red_worker.h" +#include "red_common.h" +#include "red_memslots.h" +#include "red_parse_qxl.h" +#include "zlib_encoder.h" + +#if 0 +static void hexdump_qxl(RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr, uint8_t bytes) +{ + uint8_t *hex; + int i; + int error; + + hex = (uint8_t*)get_virt(slots, addr, bytes, group_id, + &error); + for (i = 0; i < bytes; i++) { + if (0 == i % 16) { + fprintf(stderr, "%lx: ", addr+i); + } + if (0 == i % 4) { + fprintf(stderr, " "); + } + fprintf(stderr, " %02x", hex[i]); + if (15 == i % 16) { + fprintf(stderr, "\n"); + } + } +} +#endif + +#define WITH_ZLIB 0 + +/* TODO: make this thread safe (required for two qxl devices) */ + +#if WITH_ZLIB +typedef struct RecordEncoderData { + ZlibEncoderUsrContext base; + uint8_t *buf; + int size; +} RecordEncoderData; + +static int record_zlib_more_space(ZlibEncoderUsrContext *usr, uint8_t **io_ptr) +{ + return 0; +} + +static int record_zlib_more_input(ZlibEncoderUsrContext *usr, uint8_t **input) +{ + RecordEncoderData *data = SPICE_CONTAINEROF(usr, RecordEncoderData, base); + + if (data->buf == NULL) { + fprintf(stderr, "%s: error: no more data\n", __FUNCTION__); + exit(1); + } + *input = data->buf; + data->buf = 0; + return data->size; +} + +RecordEncoderData record_encoder_data = { + .base = { + record_zlib_more_space, + record_zlib_more_input, + }, + .buf = NULL, + .size = 0, +}; +#define RECORD_ZLIB_DEFAULT_COMPRESSION_LEVEL 3 +#endif + +#if WITH_ZLIB +static uint8_t output[1024*1024*4]; // static buffer for encoding, 4MB +#endif + +static void write_binary(FILE *fd, const char *prefix, size_t size, const uint8_t *buf) +{ + int n; + +#if WITH_ZLIB + ZlibEncoder *enc; + int zlib_size; + + record_encoder_data.buf = buf; + record_encoder_data.size = size; + enc = zlib_encoder_create(&record_encoder_data.base, + RECORD_ZLIB_DEFAULT_COMPRESSION_LEVEL); + if (!enc) { + fprintf(stderr, "%s: zlib encoder creation failed\n", __FUNCTION__); + exit(1); + } +#endif + + fprintf(fd, "binary %d %s %ld:", WITH_ZLIB, prefix, size); +#if WITH_ZLIB + zlib_size = zlib_encode(enc, RECORD_ZLIB_DEFAULT_COMPRESSION_LEVEL, size, + output, sizeof(output)); + fprintf(fd, "%d:", zlib_size); + n = fwrite(output, zlib_size, 1, fd); + zlib_encoder_destroy(enc); +#else + n = fwrite(buf, size, 1, fd); +#endif + (void)n; + fprintf(fd, "\n"); +} + +static size_t red_record_data_chunks_ptr(FILE *fd, const char *prefix, + RedMemSlotInfo *slots, int group_id, + int memslot_id, QXLDataChunk *qxl) +{ + size_t data_size = qxl->data_size; + int count_chunks = 0; + QXLDataChunk *cur = qxl; + int error; + + while (cur->next_chunk) { + cur = + (QXLDataChunk*)get_virt(slots, cur->next_chunk, sizeof(*cur), group_id, + &error); + data_size += cur->data_size; + count_chunks++; + } + fprintf(fd, "data_chunks %d %ld\n", count_chunks, data_size); + validate_virt(slots, (intptr_t)qxl->data, memslot_id, qxl->data_size, group_id); + write_binary(fd, prefix, qxl->data_size, qxl->data); + + while (qxl->next_chunk) { + memslot_id = get_memslot_id(slots, qxl->next_chunk); + qxl = (QXLDataChunk*)get_virt(slots, qxl->next_chunk, sizeof(*qxl), group_id, + &error); + + validate_virt(slots, (intptr_t)qxl->data, memslot_id, qxl->data_size, group_id); + write_binary(fd, prefix, qxl->data_size, qxl->data); + } + + return data_size; +} + +static size_t red_record_data_chunks(FILE *fd, const char *prefix, + RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLDataChunk *qxl; + int memslot_id = get_memslot_id(slots, addr); + int error; + + qxl = (QXLDataChunk*)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + return red_record_data_chunks_ptr(fd, prefix, slots, group_id, memslot_id, qxl); +} + +static void red_record_point_ptr(FILE *fd, QXLPoint *qxl) +{ + fprintf(fd, "point %d %d\n", qxl->x, qxl->y); +} + +static void red_record_point16_ptr(FILE *fd, QXLPoint16 *qxl) +{ + fprintf(fd, "point16 %d %d\n", qxl->x, qxl->y); +} + +static void red_record_rect_ptr(FILE *fd, const char *prefix, QXLRect *qxl) +{ + fprintf(fd, "rect %s %d %d %d %d\n", prefix, + qxl->top, qxl->left, qxl->bottom, qxl->right); +} + +static void red_record_path(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLPath *qxl; + int error; + + qxl = (QXLPath *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + red_record_data_chunks_ptr(fd, "path", slots, group_id, + get_memslot_id(slots, addr), + &qxl->chunk); +} + +static void red_record_clip_rects(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLClipRects *qxl; + int error; + + qxl = (QXLClipRects *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + fprintf(fd, "num_rects %d\n", qxl->num_rects); + red_record_data_chunks_ptr(fd, "clip_rects", slots, group_id, + get_memslot_id(slots, addr), + &qxl->chunk); +} + +static void red_record_virt_data_flat(FILE *fd, const char *prefix, + RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr, size_t size) +{ + int error; + + write_binary(fd, prefix, + size, (uint8_t*)get_virt(slots, addr, size, group_id, + &error)); +} + +static void red_record_image_data_flat(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr, size_t size) +{ + red_record_virt_data_flat(fd, "image_data_flat", slots, group_id, addr, size); +} + +static void red_record_transform(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + red_record_virt_data_flat(fd, "transform", slots, group_id, + addr, sizeof(SpiceTransform)); +} + +static void red_record_image(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr, uint32_t flags) +{ + QXLImage *qxl; + size_t bitmap_size, size; + uint8_t qxl_flags; + int error; + + fprintf(fd, "image %d\n", addr ? 1 : 0); + if (addr == 0) { + return; + } + + qxl = (QXLImage *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + fprintf(fd, "descriptor.id %ld\n", qxl->descriptor.id); + fprintf(fd, "descriptor.type %d\n", qxl->descriptor.type); + fprintf(fd, "descriptor.flags %d\n", qxl->descriptor.flags); + fprintf(fd, "descriptor.width %d\n", qxl->descriptor.width); + fprintf(fd, "descriptor.height %d\n", qxl->descriptor.height); + + switch (qxl->descriptor.type) { + case SPICE_IMAGE_TYPE_BITMAP: + fprintf(fd, "bitmap.format %d\n", qxl->bitmap.format); + fprintf(fd, "bitmap.flags %d\n", qxl->bitmap.flags); + fprintf(fd, "bitmap.x %d\n", qxl->bitmap.x); + fprintf(fd, "bitmap.y %d\n", qxl->bitmap.y); + fprintf(fd, "bitmap.stride %d\n", qxl->bitmap.stride); + qxl_flags = qxl->bitmap.flags; + fprintf(fd, "has_palette %d\n", qxl->bitmap.palette ? 1 : 0); + if (qxl->bitmap.palette) { + QXLPalette *qp; + int i, num_ents; + qp = (QXLPalette *)get_virt(slots, qxl->bitmap.palette, + sizeof(*qp), group_id, &error); + num_ents = qp->num_ents; + fprintf(fd, "qp.num_ents %d\n", qp->num_ents); + validate_virt(slots, (intptr_t)qp->ents, + get_memslot_id(slots, qxl->bitmap.palette), + num_ents * sizeof(qp->ents[0]), group_id); + fprintf(fd, "unique %ld\n", qp->unique); + for (i = 0; i < num_ents; i++) { + fprintf(fd, "ents %d\n", qp->ents[i]); + } + } + bitmap_size = qxl->bitmap.y * abs(qxl->bitmap.stride); + if (qxl_flags & QXL_BITMAP_DIRECT) { + red_record_image_data_flat(fd, slots, group_id, + qxl->bitmap.data, + bitmap_size); + } else { + size = red_record_data_chunks(fd, "bitmap.data", slots, group_id, + qxl->bitmap.data); + spice_assert(size == bitmap_size); + } + break; + case SPICE_IMAGE_TYPE_SURFACE: + fprintf(fd, "surface_image.surface_id %d\n", qxl->surface_image.surface_id); + break; + case SPICE_IMAGE_TYPE_QUIC: + fprintf(fd, "quic.data_size %d\n", qxl->quic.data_size); + size = red_record_data_chunks_ptr(fd, "quic.data", slots, group_id, + get_memslot_id(slots, addr), + (QXLDataChunk *)qxl->quic.data); + spice_assert(size == qxl->quic.data_size); + break; + default: + spice_error("%s: unknown type %d", __FUNCTION__, qxl->descriptor.type); + } +} + +static void red_record_brush_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLBrush *qxl, uint32_t flags) +{ + fprintf(fd, "type %d\n", qxl->type); + switch (qxl->type) { + case SPICE_BRUSH_TYPE_SOLID: + fprintf(fd, "u.color %d\n", qxl->u.color); + break; + case SPICE_BRUSH_TYPE_PATTERN: + red_record_image(fd, slots, group_id, qxl->u.pattern.pat, flags); + red_record_point_ptr(fd, &qxl->u.pattern.pos); + break; + } +} + +static void red_record_qmask_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLQMask *qxl, uint32_t flags) +{ + fprintf(fd, "flags %d\n", qxl->flags); + red_record_point_ptr(fd, &qxl->pos); + red_record_image(fd, slots, group_id, qxl->bitmap, flags); +} + +static void red_record_fill_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLFill *qxl, uint32_t flags) +{ + red_record_brush_ptr(fd, slots, group_id, &qxl->brush, flags); + fprintf(fd, "rop_descriptor %d\n", qxl->rop_descriptor); + red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags); +} + +static void red_record_opaque_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLOpaque *qxl, uint32_t flags) +{ + red_record_image(fd, slots, group_id, qxl->src_bitmap, flags); + red_record_rect_ptr(fd, "src_area", &qxl->src_area); + red_record_brush_ptr(fd, slots, group_id, &qxl->brush, flags); + fprintf(fd, "rop_descriptor %d\n", qxl->rop_descriptor); + fprintf(fd, "scale_mode %d\n", qxl->scale_mode); + red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags); +} + +static void red_record_copy_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLCopy *qxl, uint32_t flags) +{ + red_record_image(fd, slots, group_id, qxl->src_bitmap, flags); + red_record_rect_ptr(fd, "src_area", &qxl->src_area); + fprintf(fd, "rop_descriptor %d\n", qxl->rop_descriptor); + fprintf(fd, "scale_mode %d\n", qxl->scale_mode); + red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags); +} + +static void red_record_blend_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLBlend *qxl, uint32_t flags) +{ + red_record_image(fd, slots, group_id, qxl->src_bitmap, flags); + red_record_rect_ptr(fd, "src_area", &qxl->src_area); + fprintf(fd, "rop_descriptor %d\n", qxl->rop_descriptor); + fprintf(fd, "scale_mode %d\n", qxl->scale_mode); + red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags); +} + +static void red_record_transparent_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLTransparent *qxl, + uint32_t flags) +{ + red_record_image(fd, slots, group_id, qxl->src_bitmap, flags); + red_record_rect_ptr(fd, "src_area", &qxl->src_area); + fprintf(fd, "src_color %d\n", qxl->src_color); + fprintf(fd, "true_color %d\n", qxl->true_color); +} + +static void red_record_alpha_blend_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLAlphaBlend *qxl, + uint32_t flags) +{ + fprintf(fd, "alpha_flags %d\n", qxl->alpha_flags); + fprintf(fd, "alpha %d\n", qxl->alpha); + red_record_image(fd, slots, group_id, qxl->src_bitmap, flags); + red_record_rect_ptr(fd, "src_area", &qxl->src_area); +} + +static void red_record_alpha_blend_ptr_compat(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLCompatAlphaBlend *qxl, + uint32_t flags) +{ + fprintf(fd, "alpha %d\n", qxl->alpha); + red_record_image(fd, slots, group_id, qxl->src_bitmap, flags); + red_record_rect_ptr(fd, "src_area", &qxl->src_area); +} + +static void red_record_rop3_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLRop3 *qxl, uint32_t flags) +{ + red_record_image(fd, slots, group_id, qxl->src_bitmap, flags); + red_record_rect_ptr(fd, "src_area", &qxl->src_area); + red_record_brush_ptr(fd, slots, group_id, &qxl->brush, flags); + fprintf(fd, "rop3 %d\n", qxl->rop3); + fprintf(fd, "scale_mode %d\n", qxl->scale_mode); + red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags); +} + +static void red_record_stroke_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLStroke *qxl, uint32_t flags) +{ + int error; + + red_record_path(fd, slots, group_id, qxl->path); + fprintf(fd, "attr.flags %d\n", qxl->attr.flags); + if (qxl->attr.flags & SPICE_LINE_FLAGS_STYLED) { + int style_nseg = qxl->attr.style_nseg; + uint8_t *buf; + + fprintf(fd, "attr.style_nseg %d\n", qxl->attr.style_nseg); + spice_assert(qxl->attr.style); + buf = (uint8_t *)get_virt(slots, qxl->attr.style, + style_nseg * sizeof(QXLFIXED), group_id, + &error); + write_binary(fd, "style", style_nseg * sizeof(QXLFIXED), buf); + } + red_record_brush_ptr(fd, slots, group_id, &qxl->brush, flags); + fprintf(fd, "fore_mode %d\n", qxl->fore_mode); + fprintf(fd, "back_mode %d\n", qxl->back_mode); +} + +static void red_record_string(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLString *qxl; + size_t chunk_size; + int error; + + qxl = (QXLString *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + fprintf(fd, "data_size %d\n", qxl->data_size); + fprintf(fd, "length %d\n", qxl->length); + fprintf(fd, "flags %d\n", qxl->flags); + chunk_size = red_record_data_chunks_ptr(fd, "string", slots, group_id, + get_memslot_id(slots, addr), + &qxl->chunk); + spice_assert(chunk_size == qxl->data_size); +} + +static void red_record_text_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLText *qxl, uint32_t flags) +{ + red_record_string(fd, slots, group_id, qxl->str); + red_record_rect_ptr(fd, "back_area", &qxl->back_area); + red_record_brush_ptr(fd, slots, group_id, &qxl->fore_brush, flags); + red_record_brush_ptr(fd, slots, group_id, &qxl->back_brush, flags); + fprintf(fd, "fore_mode %d\n", qxl->fore_mode); + fprintf(fd, "back_mode %d\n", qxl->back_mode); +} + +static void red_record_whiteness_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLWhiteness *qxl, uint32_t flags) +{ + red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags); +} + +static void red_record_blackness_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLBlackness *qxl, uint32_t flags) +{ + red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags); +} + +static void red_record_invers_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLInvers *qxl, uint32_t flags) +{ + red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags); +} + +static void red_record_clip_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLClip *qxl) +{ + fprintf(fd, "type %d\n", qxl->type); + switch (qxl->type) { + case SPICE_CLIP_TYPE_RECTS: + red_record_clip_rects(fd, slots, group_id, qxl->data); + break; + } +} + +static void red_record_composite_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLComposite *qxl, uint32_t flags) +{ + fprintf(fd, "flags %d\n", qxl->flags); + + red_record_image(fd, slots, group_id, qxl->src, flags); + fprintf(fd, "src_transform %d\n", !!qxl->src_transform); + if (qxl->src_transform) + red_record_transform(fd, slots, group_id, qxl->src_transform); + fprintf(fd, "mask %d\n", !!qxl->mask); + if (qxl->mask) + red_record_image(fd, slots, group_id, qxl->mask, flags); + fprintf(fd, "mask_transform %d\n", !!qxl->mask_transform); + if (qxl->mask_transform) + red_record_transform(fd, slots, group_id, qxl->mask_transform); + + fprintf(fd, "src_origin %d %d\n", qxl->src_origin.x, qxl->src_origin.y); + fprintf(fd, "mask_origin %d %d\n", qxl->mask_origin.x, qxl->mask_origin.y); +} + +static void red_record_native_drawable(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr, uint32_t flags) +{ + QXLDrawable *qxl; + int i; + int error; + + qxl = (QXLDrawable *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + + red_record_rect_ptr(fd, "bbox", &qxl->bbox); + red_record_clip_ptr(fd, slots, group_id, &qxl->clip); + fprintf(fd, "effect %d\n", qxl->effect); + fprintf(fd, "mm_time %d\n", qxl->mm_time); + fprintf(fd, "self_bitmap %d\n", qxl->self_bitmap); + red_record_rect_ptr(fd, "self_bitmap_area", &qxl->self_bitmap_area); + fprintf(fd, "surface_id %d\n", qxl->surface_id); + + for (i = 0; i < 3; i++) { + fprintf(fd, "surfaces_dest %d\n", qxl->surfaces_dest[i]); + red_record_rect_ptr(fd, "surfaces_rects", &qxl->surfaces_rects[i]); + } + + fprintf(fd, "type %d\n", qxl->type); + switch (qxl->type) { + case QXL_DRAW_ALPHA_BLEND: + red_record_alpha_blend_ptr(fd, slots, group_id, + &qxl->u.alpha_blend, flags); + break; + case QXL_DRAW_BLACKNESS: + red_record_blackness_ptr(fd, slots, group_id, + &qxl->u.blackness, flags); + break; + case QXL_DRAW_BLEND: + red_record_blend_ptr(fd, slots, group_id, &qxl->u.blend, flags); + break; + case QXL_DRAW_COPY: + red_record_copy_ptr(fd, slots, group_id, &qxl->u.copy, flags); + break; + case QXL_COPY_BITS: + red_record_point_ptr(fd, &qxl->u.copy_bits.src_pos); + break; + case QXL_DRAW_FILL: + red_record_fill_ptr(fd, slots, group_id, &qxl->u.fill, flags); + break; + case QXL_DRAW_OPAQUE: + red_record_opaque_ptr(fd, slots, group_id, &qxl->u.opaque, flags); + break; + case QXL_DRAW_INVERS: + red_record_invers_ptr(fd, slots, group_id, &qxl->u.invers, flags); + break; + case QXL_DRAW_NOP: + break; + case QXL_DRAW_ROP3: + red_record_rop3_ptr(fd, slots, group_id, &qxl->u.rop3, flags); + break; + case QXL_DRAW_STROKE: + red_record_stroke_ptr(fd, slots, group_id, &qxl->u.stroke, flags); + break; + case QXL_DRAW_TEXT: + red_record_text_ptr(fd, slots, group_id, &qxl->u.text, flags); + break; + case QXL_DRAW_TRANSPARENT: + red_record_transparent_ptr(fd, slots, group_id, &qxl->u.transparent, flags); + break; + case QXL_DRAW_WHITENESS: + red_record_whiteness_ptr(fd, slots, group_id, &qxl->u.whiteness, flags); + break; + case QXL_DRAW_COMPOSITE: + red_record_composite_ptr(fd, slots, group_id, &qxl->u.composite, flags); + break; + default: + spice_error("%s: unknown type %d", __FUNCTION__, qxl->type); + break; + }; +} + +static void red_record_compat_drawable(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr, uint32_t flags) +{ + QXLCompatDrawable *qxl; + int error; + + qxl = (QXLCompatDrawable *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + + red_record_rect_ptr(fd, "bbox", &qxl->bbox); + red_record_clip_ptr(fd, slots, group_id, &qxl->clip); + fprintf(fd, "effect %d\n", qxl->effect); + fprintf(fd, "mm_time %d\n", qxl->mm_time); + + fprintf(fd, "bitmap_offset %d\n", qxl->bitmap_offset); + red_record_rect_ptr(fd, "bitmap_area", &qxl->bitmap_area); + + fprintf(fd, "type %d\n", qxl->type); + switch (qxl->type) { + case QXL_DRAW_ALPHA_BLEND: + red_record_alpha_blend_ptr_compat(fd, slots, group_id, + &qxl->u.alpha_blend, flags); + break; + case QXL_DRAW_BLACKNESS: + red_record_blackness_ptr(fd, slots, group_id, + &qxl->u.blackness, flags); + break; + case QXL_DRAW_BLEND: + red_record_blend_ptr(fd, slots, group_id, &qxl->u.blend, flags); + break; + case QXL_DRAW_COPY: + red_record_copy_ptr(fd, slots, group_id, &qxl->u.copy, flags); + break; + case QXL_COPY_BITS: + red_record_point_ptr(fd, &qxl->u.copy_bits.src_pos); + break; + case QXL_DRAW_FILL: + red_record_fill_ptr(fd, slots, group_id, &qxl->u.fill, flags); + break; + case QXL_DRAW_OPAQUE: + red_record_opaque_ptr(fd, slots, group_id, &qxl->u.opaque, flags); + break; + case QXL_DRAW_INVERS: + red_record_invers_ptr(fd, slots, group_id, &qxl->u.invers, flags); + break; + case QXL_DRAW_NOP: + break; + case QXL_DRAW_ROP3: + red_record_rop3_ptr(fd, slots, group_id, &qxl->u.rop3, flags); + break; + case QXL_DRAW_STROKE: + red_record_stroke_ptr(fd, slots, group_id, &qxl->u.stroke, flags); + break; + case QXL_DRAW_TEXT: + red_record_text_ptr(fd, slots, group_id, &qxl->u.text, flags); + break; + case QXL_DRAW_TRANSPARENT: + red_record_transparent_ptr(fd, slots, group_id, &qxl->u.transparent, flags); + break; + case QXL_DRAW_WHITENESS: + red_record_whiteness_ptr(fd, slots, group_id, &qxl->u.whiteness, flags); + break; + default: + spice_error("%s: unknown type %d", __FUNCTION__, qxl->type); + break; + }; +} + +static void red_record_drawable(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr, uint32_t flags) +{ + fprintf(fd, "drawable\n"); + if (flags & QXL_COMMAND_FLAG_COMPAT) { + red_record_compat_drawable(fd, slots, group_id, addr, flags); + } else { + red_record_native_drawable(fd, slots, group_id, addr, flags); + } +} + +static void red_record_update_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLUpdateCmd *qxl; + int error; + + qxl = (QXLUpdateCmd *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + + fprintf(fd, "update\n"); + red_record_rect_ptr(fd, "area", &qxl->area); + fprintf(fd, "update_id %d\n", qxl->update_id); + fprintf(fd, "surface_id %d\n", qxl->surface_id); +} + +static void red_record_message(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLMessage *qxl; + int error; + + /* + * security alert: + * qxl->data[0] size isn't specified anywhere -> can't verify + * luckily this is for debug logging only, + * so we can just ignore it by default. + */ + qxl = (QXLMessage *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + write_binary(fd, "message", strlen((char*)qxl->data), (uint8_t*)qxl->data); +} + +static void red_record_surface_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLSurfaceCmd *qxl; + size_t size; + int error; + + qxl = (QXLSurfaceCmd *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + + fprintf(fd, "surface_cmd\n"); + fprintf(fd, "surface_id %d\n", qxl->surface_id); + fprintf(fd, "type %d\n", qxl->type); + fprintf(fd, "flags %d\n", qxl->flags); + + switch (qxl->type) { + case QXL_SURFACE_CMD_CREATE: + fprintf(fd, "u.surface_create.format %d\n", qxl->u.surface_create.format); + fprintf(fd, "u.surface_create.width %d\n", qxl->u.surface_create.width); + fprintf(fd, "u.surface_create.height %d\n", qxl->u.surface_create.height); + fprintf(fd, "u.surface_create.stride %d\n", qxl->u.surface_create.stride); + size = qxl->u.surface_create.height * abs(qxl->u.surface_create.stride); + if (qxl->flags && QXL_SURF_FLAG_KEEP_DATA) { + write_binary(fd, "data", size, + (uint8_t*)get_virt(slots, qxl->u.surface_create.data, size, group_id, + &error)); + } + break; + } +} + +static void red_record_cursor(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLCursor *qxl; + int error; + + qxl = (QXLCursor *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + + fprintf(fd, "header.unique %ld\n", qxl->header.unique); + fprintf(fd, "header.type %d\n", qxl->header.type); + fprintf(fd, "header.width %d\n", qxl->header.width); + fprintf(fd, "header.height %d\n", qxl->header.height); + fprintf(fd, "header.hot_spot_x %d\n", qxl->header.hot_spot_x); + fprintf(fd, "header.hot_spot_y %d\n", qxl->header.hot_spot_y); + + fprintf(fd, "data_size %d\n", qxl->data_size); + red_record_data_chunks_ptr(fd, "cursor", slots, group_id, + get_memslot_id(slots, addr), + &qxl->chunk); +} + +void red_record_cursor_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id, + QXLPHYSICAL addr) +{ + QXLCursorCmd *qxl; + int error; + + qxl = (QXLCursorCmd *)get_virt(slots, addr, sizeof(*qxl), group_id, + &error); + + fprintf(fd, "cursor_cmd\n"); + fprintf(fd, "type %d\n", qxl->type); + switch (qxl->type) { + case QXL_CURSOR_SET: + red_record_point16_ptr(fd, &qxl->u.set.position); + fprintf(fd, "u.set.visible %d\n", qxl->u.set.visible); + red_record_cursor(fd, slots, group_id, qxl->u.set.shape); + break; + case QXL_CURSOR_MOVE: + red_record_point16_ptr(fd, &qxl->u.position); + break; + case QXL_CURSOR_TRAIL: + fprintf(fd, "u.trail.length %d\n", qxl->u.trail.length); + fprintf(fd, "u.trail.frequency %d\n", qxl->u.trail.frequency); + break; + } +} + +void red_record_dev_input_primary_surface_create(FILE *fd, + QXLDevSurfaceCreate* surface, uint8_t *line_0) +{ + fprintf(fd, "%d %d %d %d\n", surface->width, surface->height, + surface->stride, surface->format); + fprintf(fd, "%d %d %d %d\n", surface->position, surface->mouse_mode, + surface->flags, surface->type); + write_binary(fd, "data", line_0 ? abs(surface->stride)*surface->height : 0, + line_0); +} + +void red_record_event(FILE *fd, int what, uint32_t type, unsigned long ts) +{ + static int counter = 0; + + // TODO: record the size of the packet in the header. This would make + // navigating it much faster (well, I can add an index while I'm at it..) + // and make it trivial to get a histogram from a file. + // But to implement that I would need some temporary buffer for each event. + // (that can be up to VGA_FRAMEBUFFER large) + fprintf(fd, "event %d %d %u %lu\n", counter++, what, type, ts); +} + +void red_record_qxl_command(FILE *fd, RedMemSlotInfo *slots, + QXLCommandExt ext_cmd, unsigned long ts) +{ + red_record_event(fd, 0, ext_cmd.cmd.type, ts); + + switch (ext_cmd.cmd.type) { + case QXL_CMD_DRAW: + red_record_drawable(fd, slots, ext_cmd.group_id, ext_cmd.cmd.data, ext_cmd.flags); + break; + case QXL_CMD_UPDATE: + red_record_update_cmd(fd, slots, ext_cmd.group_id, ext_cmd.cmd.data); + break; + case QXL_CMD_MESSAGE: + red_record_message(fd, slots, ext_cmd.group_id, ext_cmd.cmd.data); + break; + case QXL_CMD_SURFACE: + red_record_surface_cmd(fd, slots, ext_cmd.group_id, ext_cmd.cmd.data); + break; + } +} diff --git a/server/red_record_qxl.h b/server/red_record_qxl.h new file mode 100644 index 00000000..b737db89 --- /dev/null +++ b/server/red_record_qxl.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009,2010 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef RED_ABI_RECORD_H +#define RED_ABI_RECORD_H + +#include +#include "red_common.h" +#include "red_memslots.h" + +void red_record_dev_input_primary_surface_create( + FILE *fd, QXLDevSurfaceCreate *surface, uint8_t *line_0); + +void red_record_event(FILE *fd, int what, uint32_t type, unsigned long ts); + +void red_record_qxl_command(FILE *fd, RedMemSlotInfo *slots, + QXLCommandExt ext_cmd, unsigned long ts); + +#endif diff --git a/server/red_replay_qxl.c b/server/red_replay_qxl.c new file mode 100644 index 00000000..a010a58d --- /dev/null +++ b/server/red_replay_qxl.c @@ -0,0 +1,1243 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009,2010 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include "reds.h" +#include "red_worker.h" +#include "red_common.h" +#include "red_memslots.h" +#include "red_parse_qxl.h" +#include "red_replay_qxl.h" +#include + +typedef enum { + REPLAY_OK = 0, + REPLAY_EOF, +} replay_t; + +struct SpiceReplay { + FILE *fd; + int eof; + int counter; + bool created_primary; + + GArray *id_map; // record id -> replay id + GArray *id_map_inv; // replay id -> record id + GArray *id_free; // free list + int nsurfaces; + + /* FIXME: some API requires 2.32 */ + GMutex mutex; + GCond cond; +}; + +static int replay_fread(SpiceReplay *replay, uint8_t *buf, size_t size) +{ + if (replay->eof) { + return 0; + } + if (feof(replay->fd)) { + replay->eof = 1; + return 0; + } + return fread(buf, size, 1, replay->fd); +} + +__attribute__((format(scanf, 2, 3))) +static replay_t replay_fscanf(SpiceReplay *replay, const char *fmt, ...) +{ + va_list ap; + int ret; + + if (replay->eof) { + return REPLAY_EOF; + } + if (feof(replay->fd)) { + replay->eof = 1; + return REPLAY_EOF; + } + va_start(ap, fmt); + ret = vfscanf(replay->fd, fmt, ap); + va_end(ap); + if (ret == EOF) { + replay->eof = 1; + } + return replay->eof ? REPLAY_EOF : REPLAY_OK; +} + +static uint32_t replay_id_get(SpiceReplay *replay, uint32_t id) +{ + uint32_t newid = 0; + + /* TODO: this should be avoided, perhaps in recording? */ + if (id == -1) + return id; + + g_mutex_lock(&replay->mutex); + if (replay->id_map->len <= id) { + spice_warn_if_reached(); + } else { + newid = g_array_index(replay->id_map, uint32_t, id); + } + g_mutex_unlock(&replay->mutex); + + return newid; +} + +static uint32_t replay_id_new(SpiceReplay *replay, uint32_t id) +{ + uint32_t new_id; + uint32_t *map; + + g_mutex_lock(&replay->mutex); + while (1) { + if (replay->id_free->len > 0) { + new_id = g_array_index(replay->id_free, uint32_t, 0); + g_array_remove_index_fast(replay->id_free, 0); + } else { + new_id = replay->id_map_inv->len; + } + + if (new_id < replay->nsurfaces) + break; + g_cond_wait(&replay->cond, &replay->mutex); + } + + if (replay->id_map->len <= id) + g_array_set_size(replay->id_map, id + 1); + if (replay->id_map_inv->len <= new_id) + g_array_set_size(replay->id_map_inv, new_id + 1); + + map = &g_array_index(replay->id_map, uint32_t, id); + *map = new_id; + map = &g_array_index(replay->id_map_inv, uint32_t, new_id); + *map = id; + g_mutex_unlock(&replay->mutex); + + spice_debug("%u -> %u (map %u, inv %u)", id, new_id, + replay->id_map->len, replay->id_map_inv->len); + + return new_id; +} + +static void replay_id_free(SpiceReplay *replay, uint32_t id) +{ + uint32_t old_id; + uint32_t *map; + + g_mutex_lock(&replay->mutex); + map = &g_array_index(replay->id_map_inv, uint32_t, id); + old_id = *map; + *map = -1; + + if (old_id != -1) { + map = &g_array_index(replay->id_map, uint32_t, old_id); + if (*map == id) + *map = -1; + + g_array_append_val(replay->id_free, id); + } + g_cond_signal(&replay->cond); + g_mutex_unlock(&replay->mutex); +} + + +#if 0 +static void hexdump(uint8_t *hex, uint8_t bytes) +{ + int i; + + for (i = 0; i < bytes; i++) { + if (0 == i % 16) { + fprintf(stderr, "%lx: ", (size_t)hex+i); + } + if (0 == i % 4) { + fprintf(stderr, " "); + } + fprintf(stderr, " %02x", hex[i]); + if (15 == i % 16) { + fprintf(stderr, "\n"); + } + } +} +#endif + +static replay_t read_binary(SpiceReplay *replay, const char *prefix, size_t *size, uint8_t + **buf, size_t base_size) +{ + char template[1024]; + int with_zlib = -1; + int zlib_size; + uint8_t *zlib_buffer; + z_stream strm; + + snprintf(template, sizeof(template), "binary %%d %s %%ld:", prefix); + if (replay_fscanf(replay, template, &with_zlib, size) == REPLAY_EOF) + return REPLAY_EOF; + + if (*buf == NULL) { + *buf = malloc(*size + base_size); + if (*buf == NULL) { + spice_error("allocation error for %ld", *size); + exit(1); + } + } +#if 0 + { + int num_read = fread(*buf + base_size, *size, 1, fd); + spice_error("num_read = %d", num_read); + hexdump(*buf + base_size, *size); + } +#else + spice_return_val_if_fail(with_zlib != -1, REPLAY_EOF); + if (with_zlib) { + int ret; + + replay_fscanf(replay, "%d:", &zlib_size); + zlib_buffer = malloc(zlib_size); + replay_fread(replay, zlib_buffer, zlib_size); + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = zlib_size; + strm.next_in = zlib_buffer; + strm.avail_out = *size; + strm.next_out = *buf + base_size; + if ((ret = inflateInit(&strm)) != Z_OK) { + spice_error("inflateInit failed"); + exit(1); + } + if ((ret = inflate(&strm, Z_NO_FLUSH)) != Z_STREAM_END) { + spice_error("inflate error %d (disc: %ld)", ret, *size - strm.total_out); + if (ret == Z_DATA_ERROR) { + /* last operation may be wrong. since we do the recording + * in red_worker, when there is a shutdown from the vcpu/io thread + * it seems it may kill the red_worker thread (so a chunk is + * left hanging and the rest of the message is never written). + * Let it pass */ + return REPLAY_EOF; + } + if (ret != Z_OK) { + spice_warn_if_reached(); + } + } + (void)inflateEnd(&strm); + free(zlib_buffer); // TODO - avoid repeat alloc/dealloc by keeping last + } else { + replay_fread(replay, *buf + base_size, *size); + } +#endif + replay_fscanf(replay, "\n"); + return REPLAY_OK; +} + +static size_t red_replay_data_chunks(SpiceReplay *replay, const char *prefix, + uint8_t **mem, size_t base_size) +{ + size_t data_size; + int count_chunks; + size_t next_data_size; + QXLDataChunk *cur; + + replay_fscanf(replay, "data_chunks %d %ld\n", &count_chunks, &data_size); + if (base_size == 0) { + base_size = sizeof(QXLDataChunk); + } + + if (read_binary(replay, prefix, &next_data_size, mem, base_size) == REPLAY_EOF) { + return 0; + } + cur = (QXLDataChunk*)(*mem + base_size - sizeof(QXLDataChunk)); + cur->data_size = next_data_size; + data_size = cur->data_size; + cur->next_chunk = cur->prev_chunk = 0; + while (count_chunks-- > 0) { + if (read_binary(replay, prefix, &next_data_size, (uint8_t**)&cur->next_chunk, + sizeof(QXLDataChunk)) == REPLAY_EOF) { + return 0; + } + data_size += next_data_size; + ((QXLDataChunk*)cur->next_chunk)->prev_chunk = (QXLPHYSICAL)cur; + cur = (QXLDataChunk*)cur->next_chunk; + cur->data_size = next_data_size; + cur->next_chunk = 0; + } + + return data_size; +} + +static void red_replay_data_chunks_free(SpiceReplay *replay, void *data, size_t base_size) +{ + QXLDataChunk *cur = (QXLDataChunk *)((uint8_t*)data + + (base_size ? base_size - sizeof(QXLDataChunk) : 0)); + + cur = (QXLDataChunk *)cur->next_chunk; + while (cur) { + QXLDataChunk *next = (QXLDataChunk *)cur->next_chunk; + free(cur); + cur = next; + } + + free(data); +} + +static void red_replay_point_ptr(SpiceReplay *replay, QXLPoint *qxl) +{ + replay_fscanf(replay, "point %d %d\n", &qxl->x, &qxl->y); +} + +static void red_replay_rect_ptr(SpiceReplay *replay, const char *prefix, QXLRect *qxl) +{ + char template[1024]; + + snprintf(template, sizeof(template), "rect %s %%d %%d %%d %%d %%d", prefix); + replay_fscanf(replay, template, &qxl->top, &qxl->left, &qxl->bottom, &qxl->right); +} + +static QXLPath *red_replay_path(SpiceReplay *replay) +{ + QXLPath *qxl = NULL; + size_t data_size; + + data_size = red_replay_data_chunks(replay, "path", (uint8_t**)&qxl, sizeof(QXLPath)); + qxl->data_size = data_size; + return qxl; +} + +static void red_replay_path_free(SpiceReplay *replay, QXLPHYSICAL p) +{ + QXLPath *qxl = (QXLPath *)p; + + red_replay_data_chunks_free(replay, qxl, sizeof(*qxl)); +} + +static QXLClipRects *red_replay_clip_rects(SpiceReplay *replay) +{ + QXLClipRects *qxl = NULL; + int num_rects; + + replay_fscanf(replay, "num_rects %d\n", &num_rects); + red_replay_data_chunks(replay, "clip_rects", (uint8_t**)&qxl, sizeof(QXLClipRects)); + qxl->num_rects = num_rects; + return qxl; +} + +static void red_replay_clip_rects_free(SpiceReplay *replay, QXLClipRects *qxl) +{ + red_replay_data_chunks_free(replay, qxl, sizeof(*qxl)); +} + +static uint8_t *red_replay_image_data_flat(SpiceReplay *replay, size_t *size) +{ + uint8_t *data = NULL; + + read_binary(replay, "image_data_flat", size, &data, 0); + return data; +} + +static QXLImage *red_replay_image(SpiceReplay *replay, uint32_t flags) +{ + QXLImage* qxl = NULL; + size_t bitmap_size, size; + uint8_t qxl_flags; + int temp; + int has_palette; + int has_image; + + replay_fscanf(replay, "image %d\n", &has_image); + if (!has_image) { + return NULL; + } + + qxl = (QXLImage*)malloc(sizeof(QXLImage)); + replay_fscanf(replay, "descriptor.id %ld\n", &qxl->descriptor.id); + replay_fscanf(replay, "descriptor.type %d\n", &temp); qxl->descriptor.type = temp; + replay_fscanf(replay, "descriptor.flags %d\n", &temp); qxl->descriptor.flags = temp; + replay_fscanf(replay, "descriptor.width %d\n", &qxl->descriptor.width); + replay_fscanf(replay, "descriptor.height %d\n", &qxl->descriptor.height); + + switch (qxl->descriptor.type) { + case SPICE_IMAGE_TYPE_BITMAP: + replay_fscanf(replay, "bitmap.format %d\n", &temp); qxl->bitmap.format = temp; + replay_fscanf(replay, "bitmap.flags %d\n", &temp); qxl->bitmap.flags = temp; + replay_fscanf(replay, "bitmap.x %d\n", &qxl->bitmap.x); + replay_fscanf(replay, "bitmap.y %d\n", &qxl->bitmap.y); + replay_fscanf(replay, "bitmap.stride %d\n", &qxl->bitmap.stride); + qxl_flags = qxl->bitmap.flags; + replay_fscanf(replay, "has_palette %d\n", &has_palette); + if (has_palette) { + QXLPalette *qp; + int i, num_ents; + + replay_fscanf(replay, "qp.num_ents %d\n", &num_ents); + qp = malloc(sizeof(QXLPalette) + num_ents * sizeof(qp->ents[0])); + qp->num_ents = num_ents; + qxl->bitmap.palette = (QXLPHYSICAL)qp; + replay_fscanf(replay, "unique %ld\n", &qp->unique); + for (i = 0; i < num_ents; i++) { + replay_fscanf(replay, "ents %d\n", &qp->ents[i]); + } + } else { + qxl->bitmap.palette = 0; + } + bitmap_size = qxl->bitmap.y * abs(qxl->bitmap.stride); + qxl->bitmap.data = 0; + if (qxl_flags & QXL_BITMAP_DIRECT) { + qxl->bitmap.data = (QXLPHYSICAL)red_replay_image_data_flat(replay, &bitmap_size); + } else { + size = red_replay_data_chunks(replay, "bitmap.data", (uint8_t**)&qxl->bitmap.data, 0); + if (size != bitmap_size) { + spice_printerr("bad image, %ld != %ld", size, bitmap_size); + return NULL; + } + } + break; + case SPICE_IMAGE_TYPE_SURFACE: + replay_fscanf(replay, "surface_image.surface_id %d\n", &qxl->surface_image.surface_id); + qxl->surface_image.surface_id = replay_id_get(replay, qxl->surface_image.surface_id); + break; + case SPICE_IMAGE_TYPE_QUIC: + // TODO - make this much nicer (precompute size and allocs, store them during + // record, then reread into them. and use MPEG-4). + replay_fscanf(replay, "quic.data_size %d\n", &qxl->quic.data_size); + qxl = realloc(qxl, sizeof(QXLImageDescriptor) + sizeof(QXLQUICData) + + qxl->quic.data_size); + size = red_replay_data_chunks(replay, "quic.data", (uint8_t**)&qxl->quic.data, 0); + spice_assert(size == qxl->quic.data_size); + break; + default: + spice_warn_if_reached(); + } + return qxl; +} + +static void red_replay_image_free(SpiceReplay *replay, QXLPHYSICAL p, uint32_t flags) +{ + QXLImage *qxl = (QXLImage *)p; + if (!qxl) + return; + + switch (qxl->descriptor.type) { + case SPICE_IMAGE_TYPE_BITMAP: + free((void*)qxl->bitmap.palette); + if (qxl->bitmap.flags & QXL_BITMAP_DIRECT) { + free((void*)qxl->bitmap.data); + } else { + red_replay_data_chunks_free(replay, (void*)qxl->bitmap.data, 0); + } + break; + case SPICE_IMAGE_TYPE_SURFACE: + break; + case SPICE_IMAGE_TYPE_QUIC: + red_replay_data_chunks_free(replay, qxl, 0); + break; + default: + spice_warn_if_reached(); + } + + free(qxl); +} + +static void red_replay_brush_ptr(SpiceReplay *replay, QXLBrush *qxl, uint32_t flags) +{ + replay_fscanf(replay, "type %d\n", &qxl->type); + switch (qxl->type) { + case SPICE_BRUSH_TYPE_SOLID: + replay_fscanf(replay, "u.color %d\n", &qxl->u.color); + break; + case SPICE_BRUSH_TYPE_PATTERN: + qxl->u.pattern.pat = (QXLPHYSICAL)red_replay_image(replay, flags); + red_replay_point_ptr(replay, &qxl->u.pattern.pos); + break; + } +} + +static void red_replay_brush_free(SpiceReplay *replay, QXLBrush *qxl, uint32_t flags) +{ + switch (qxl->type) { + case SPICE_BRUSH_TYPE_PATTERN: + red_replay_image_free(replay, qxl->u.pattern.pat, flags); + break; + } +} + +static void red_replay_qmask_ptr(SpiceReplay *replay, QXLQMask *qxl, uint32_t flags) +{ + int temp; + + replay_fscanf(replay, "flags %d\n", &temp); qxl->flags = temp; + red_replay_point_ptr(replay, &qxl->pos); + qxl->bitmap = (QXLPHYSICAL)red_replay_image(replay, flags); +} + +static void red_replay_qmask_free(SpiceReplay *replay, QXLQMask *qxl, uint32_t flags) +{ + red_replay_image_free(replay, qxl->bitmap, flags); +} + +static void red_replay_fill_ptr(SpiceReplay *replay, QXLFill *qxl, uint32_t flags) +{ + int temp; + + red_replay_brush_ptr(replay, &qxl->brush, flags); + replay_fscanf(replay, "rop_descriptor %d\n", &temp); qxl->rop_descriptor = temp; + red_replay_qmask_ptr(replay, &qxl->mask, flags); +} + +static void red_replay_fill_free(SpiceReplay *replay, QXLFill *qxl, uint32_t flags) +{ + red_replay_brush_free(replay, &qxl->brush, flags); + red_replay_qmask_free(replay, &qxl->mask, flags); +} + +static void red_replay_opaque_ptr(SpiceReplay *replay, QXLOpaque *qxl, uint32_t flags) +{ + int temp; + + qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags); + red_replay_rect_ptr(replay, "src_area", &qxl->src_area); + red_replay_brush_ptr(replay, &qxl->brush, flags); + replay_fscanf(replay, "rop_descriptor %d\n", &temp); qxl->rop_descriptor = temp; + replay_fscanf(replay, "scale_mode %d\n", &temp); qxl->scale_mode = temp; + red_replay_qmask_ptr(replay, &qxl->mask, flags); +} + +static void red_replay_opaque_free(SpiceReplay *replay, QXLOpaque *qxl, uint32_t flags) +{ + red_replay_image_free(replay, qxl->src_bitmap, flags); + red_replay_brush_free(replay, &qxl->brush, flags); + red_replay_qmask_free(replay, &qxl->mask, flags); +} + +static void red_replay_copy_ptr(SpiceReplay *replay, QXLCopy *qxl, uint32_t flags) +{ + int temp; + + qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags); + red_replay_rect_ptr(replay, "src_area", &qxl->src_area); + replay_fscanf(replay, "rop_descriptor %d\n", &temp); qxl->rop_descriptor = temp; + replay_fscanf(replay, "scale_mode %d\n", &temp); qxl->scale_mode = temp; + red_replay_qmask_ptr(replay, &qxl->mask, flags); +} + +static void red_replay_copy_free(SpiceReplay *replay, QXLCopy *qxl, uint32_t flags) +{ + red_replay_image_free(replay, qxl->src_bitmap, flags); + red_replay_qmask_free(replay, &qxl->mask, flags); +} + +static void red_replay_blend_ptr(SpiceReplay *replay, QXLBlend *qxl, uint32_t flags) +{ + int temp; + + qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags); + red_replay_rect_ptr(replay, "src_area", &qxl->src_area); + replay_fscanf(replay, "rop_descriptor %d\n", &temp); qxl->rop_descriptor = temp; + replay_fscanf(replay, "scale_mode %d\n", &temp); qxl->scale_mode = temp; + red_replay_qmask_ptr(replay, &qxl->mask, flags); +} + +static void red_replay_blend_free(SpiceReplay *replay, QXLBlend *qxl, uint32_t flags) +{ + red_replay_image_free(replay, qxl->src_bitmap, flags); + red_replay_qmask_free(replay, &qxl->mask, flags); +} + +static void red_replay_transparent_ptr(SpiceReplay *replay, QXLTransparent *qxl, uint32_t flags) +{ + qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags); + red_replay_rect_ptr(replay, "src_area", &qxl->src_area); + replay_fscanf(replay, "src_color %d\n", &qxl->src_color); + replay_fscanf(replay, "true_color %d\n", &qxl->true_color); +} + +static void red_replay_transparent_free(SpiceReplay *replay, QXLTransparent *qxl, uint32_t flags) +{ + red_replay_image_free(replay, qxl->src_bitmap, flags); +} + +static void red_replay_alpha_blend_ptr(SpiceReplay *replay, QXLAlphaBlend *qxl, uint32_t flags) +{ + int temp; + + replay_fscanf(replay, "alpha_flags %d\n", &temp); qxl->alpha_flags = temp; + replay_fscanf(replay, "alpha %d\n", &temp); qxl->alpha = temp; + qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags); + red_replay_rect_ptr(replay, "src_area", &qxl->src_area); +} + +static void red_replay_alpha_blend_free(SpiceReplay *replay, QXLAlphaBlend *qxl, uint32_t flags) +{ + red_replay_image_free(replay, qxl->src_bitmap, flags); +} + +static void red_replay_alpha_blend_ptr_compat(SpiceReplay *replay, QXLCompatAlphaBlend *qxl, uint32_t flags) +{ + int temp; + + replay_fscanf(replay, "alpha %d\n", &temp); qxl->alpha = temp; + qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags); + red_replay_rect_ptr(replay, "src_area", &qxl->src_area); +} + +static void red_replay_rop3_ptr(SpiceReplay *replay, QXLRop3 *qxl, uint32_t flags) +{ + int temp; + + qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags); + red_replay_rect_ptr(replay, "src_area", &qxl->src_area); + red_replay_brush_ptr(replay, &qxl->brush, flags); + replay_fscanf(replay, "rop3 %d\n", &temp); qxl->rop3 = temp; + replay_fscanf(replay, "scale_mode %d\n", &temp); qxl->scale_mode = temp; + red_replay_qmask_ptr(replay, &qxl->mask, flags); +} + +static void red_replay_rop3_free(SpiceReplay *replay, QXLRop3 *qxl, uint32_t flags) +{ + red_replay_image_free(replay, qxl->src_bitmap, flags); + red_replay_brush_free(replay, &qxl->brush, flags); + red_replay_qmask_free(replay, &qxl->mask, flags); +} + +static void red_replay_stroke_ptr(SpiceReplay *replay, QXLStroke *qxl, uint32_t flags) +{ + int temp; + + qxl->path = (QXLPHYSICAL)red_replay_path(replay); + replay_fscanf(replay, "attr.flags %d\n", &temp); qxl->attr.flags = temp; + if (qxl->attr.flags & SPICE_LINE_FLAGS_STYLED) { + size_t size; + + replay_fscanf(replay, "attr.style_nseg %d\n", &temp); qxl->attr.style_nseg = temp; + read_binary(replay, "style", &size, (uint8_t**)&qxl->attr.style, 0); + } + red_replay_brush_ptr(replay, &qxl->brush, flags); + replay_fscanf(replay, "fore_mode %d\n", &temp); qxl->fore_mode = temp; + replay_fscanf(replay, "back_mode %d\n", &temp); qxl->back_mode = temp; +} + +static void red_replay_stroke_free(SpiceReplay *replay, QXLStroke *qxl, uint32_t flags) +{ + red_replay_path_free(replay, qxl->path); + if (qxl->attr.flags & SPICE_LINE_FLAGS_STYLED) { + free((void*)qxl->attr.style); + } + red_replay_brush_free(replay, &qxl->brush, flags); +} + +static QXLString *red_replay_string(SpiceReplay *replay) +{ + int temp; + uint32_t data_size; + uint16_t length; + uint16_t flags; + size_t chunk_size; + QXLString *qxl = NULL; + + replay_fscanf(replay, "data_size %d\n", &data_size); + replay_fscanf(replay, "length %d\n", &temp); length = temp; + replay_fscanf(replay, "flags %d\n", &temp); flags = temp; + chunk_size = red_replay_data_chunks(replay, "string", (uint8_t**)&qxl, sizeof(QXLString)); + qxl->data_size = data_size; + qxl->length = length; + qxl->flags = flags; + spice_assert(chunk_size == qxl->data_size); + return qxl; +} + +static void red_replay_string_free(SpiceReplay *replay, QXLString *qxl) +{ + red_replay_data_chunks_free(replay, qxl, sizeof(*qxl)); +} + +static void red_replay_text_ptr(SpiceReplay *replay, QXLText *qxl, uint32_t flags) +{ + int temp; + + qxl->str = (QXLPHYSICAL)red_replay_string(replay); + red_replay_rect_ptr(replay, "back_area", &qxl->back_area); + red_replay_brush_ptr(replay, &qxl->fore_brush, flags); + red_replay_brush_ptr(replay, &qxl->back_brush, flags); + replay_fscanf(replay, "fore_mode %d\n", &temp); qxl->fore_mode = temp; + replay_fscanf(replay, "back_mode %d\n", &temp); qxl->back_mode = temp; +} + +static void red_replay_text_free(SpiceReplay *replay, QXLText *qxl, uint32_t flags) +{ + red_replay_string_free(replay, (QXLString *)qxl->str); + red_replay_brush_free(replay, &qxl->fore_brush, flags); + red_replay_brush_free(replay, &qxl->back_brush, flags); +} + +static void red_replay_whiteness_ptr(SpiceReplay *replay, QXLWhiteness *qxl, uint32_t flags) +{ + red_replay_qmask_ptr(replay, &qxl->mask, flags); +} + +static void red_replay_whiteness_free(SpiceReplay *replay, QXLWhiteness *qxl, uint32_t flags) +{ + red_replay_qmask_free(replay, &qxl->mask, flags); +} + +static void red_replay_blackness_ptr(SpiceReplay *replay, QXLBlackness *qxl, uint32_t flags) +{ + red_replay_qmask_ptr(replay, &qxl->mask, flags); +} + +static void red_replay_blackness_free(SpiceReplay *replay, QXLBlackness *qxl, uint32_t flags) +{ + red_replay_qmask_free(replay, &qxl->mask, flags); +} + +static void red_replay_invers_ptr(SpiceReplay *replay, QXLInvers *qxl, uint32_t flags) +{ + red_replay_qmask_ptr(replay, &qxl->mask, flags); +} + +static void red_replay_invers_free(SpiceReplay *replay, QXLInvers *qxl, uint32_t flags) +{ + red_replay_qmask_free(replay, &qxl->mask, flags); +} + +static void red_replay_clip_ptr(SpiceReplay *replay, QXLClip *qxl) +{ + replay_fscanf(replay, "type %d\n", &qxl->type); + switch (qxl->type) { + case SPICE_CLIP_TYPE_RECTS: + qxl->data = (QXLPHYSICAL)red_replay_clip_rects(replay); + break; + } +} + +static void red_replay_clip_free(SpiceReplay *replay, QXLClip *qxl) +{ + switch (qxl->type) { + case SPICE_CLIP_TYPE_RECTS: + red_replay_clip_rects_free(replay, (QXLClipRects*)qxl->data); + break; + } +} + +static uint8_t *red_replay_transform(SpiceReplay *replay) +{ + uint8_t *data = NULL; + size_t size; + + read_binary(replay, "transform", &size, &data, 0); + spice_warn_if_fail(size == sizeof(SpiceTransform)); + + return data; +} + +static void red_replay_composite_ptr(SpiceReplay *replay, QXLComposite *qxl, uint32_t flags) +{ + int enabled; + + replay_fscanf(replay, "flags %d\n", &qxl->flags); + qxl->src = (QXLPHYSICAL)red_replay_image(replay, flags); + + replay_fscanf(replay, "src_transform %d\n", &enabled); + qxl->src_transform = enabled ? (QXLPHYSICAL)red_replay_transform(replay) : 0; + + replay_fscanf(replay, "mask %d\n", &enabled); + qxl->mask = enabled ? (QXLPHYSICAL)red_replay_image(replay, flags) : 0; + + replay_fscanf(replay, "mask_transform %d\n", &enabled); + qxl->mask_transform = enabled ? (QXLPHYSICAL)red_replay_transform(replay) : 0; + + replay_fscanf(replay, "src_origin %" SCNi16 " %" SCNi16 "\n", &qxl->src_origin.x, &qxl->src_origin.y); + replay_fscanf(replay, "mask_origin %" SCNi16 " %" SCNi16 "\n", &qxl->mask_origin.x, &qxl->mask_origin.y); +} + +static void red_replay_composite_free(SpiceReplay *replay, QXLComposite *qxl, uint32_t flags) +{ + red_replay_image_free(replay, qxl->src, flags); + free((void*)qxl->src_transform); + red_replay_image_free(replay, qxl->mask, flags); + free((void*)qxl->mask_transform); + +} + +static QXLDrawable *red_replay_native_drawable(SpiceReplay *replay, uint32_t flags) +{ + QXLDrawable *qxl = malloc(sizeof(QXLDrawable)); // TODO - this is too large usually + int i; + int temp; + + red_replay_rect_ptr(replay, "bbox", &qxl->bbox); + red_replay_clip_ptr(replay, &qxl->clip); + replay_fscanf(replay, "effect %d\n", &temp); qxl->effect = temp; + replay_fscanf(replay, "mm_time %d\n", &qxl->mm_time); + replay_fscanf(replay, "self_bitmap %d\n", &temp); qxl->self_bitmap = temp; + red_replay_rect_ptr(replay, "self_bitmap_area", &qxl->self_bitmap_area); + replay_fscanf(replay, "surface_id %d\n", &qxl->surface_id); + qxl->surface_id = replay_id_get(replay, qxl->surface_id); + + for (i = 0; i < 3; i++) { + replay_fscanf(replay, "surfaces_dest %d\n", &qxl->surfaces_dest[i]); + qxl->surfaces_dest[i] = replay_id_get(replay, qxl->surfaces_dest[i]); + red_replay_rect_ptr(replay, "surfaces_rects", &qxl->surfaces_rects[i]); + } + + replay_fscanf(replay, "type %d\n", &temp); qxl->type = temp; + switch (qxl->type) { + case QXL_DRAW_ALPHA_BLEND: + red_replay_alpha_blend_ptr(replay, &qxl->u.alpha_blend, flags); + break; + case QXL_DRAW_BLACKNESS: + red_replay_blackness_ptr(replay, &qxl->u.blackness, flags); + break; + case QXL_DRAW_BLEND: + red_replay_blend_ptr(replay, &qxl->u.blend, flags); + break; + case QXL_DRAW_COPY: + red_replay_copy_ptr(replay, &qxl->u.copy, flags); + break; + case QXL_COPY_BITS: + red_replay_point_ptr(replay, &qxl->u.copy_bits.src_pos); + break; + case QXL_DRAW_FILL: + red_replay_fill_ptr(replay, &qxl->u.fill, flags); + break; + case QXL_DRAW_OPAQUE: + red_replay_opaque_ptr(replay, &qxl->u.opaque, flags); + break; + case QXL_DRAW_INVERS: + red_replay_invers_ptr(replay, &qxl->u.invers, flags); + break; + case QXL_DRAW_NOP: + break; + case QXL_DRAW_ROP3: + red_replay_rop3_ptr(replay, &qxl->u.rop3, flags); + break; + case QXL_DRAW_STROKE: + red_replay_stroke_ptr(replay, &qxl->u.stroke, flags); + break; + case QXL_DRAW_TEXT: + red_replay_text_ptr(replay, &qxl->u.text, flags); + break; + case QXL_DRAW_TRANSPARENT: + red_replay_transparent_ptr(replay, &qxl->u.transparent, flags); + break; + case QXL_DRAW_WHITENESS: + red_replay_whiteness_ptr(replay, &qxl->u.whiteness, flags); + break; + case QXL_DRAW_COMPOSITE: + red_replay_composite_ptr(replay, &qxl->u.composite, flags); + break; + default: + spice_warn_if_reached(); + break; + }; + return qxl; +} + +static void red_replay_native_drawable_free(SpiceReplay *replay, QXLDrawable *qxl, uint32_t flags) +{ + red_replay_clip_free(replay, &qxl->clip); + + switch (qxl->type) { + case QXL_DRAW_ALPHA_BLEND: + red_replay_alpha_blend_free(replay, &qxl->u.alpha_blend, flags); + break; + case QXL_DRAW_BLACKNESS: + red_replay_blackness_free(replay, &qxl->u.blackness, flags); + break; + case QXL_DRAW_BLEND: + red_replay_blend_free(replay, &qxl->u.blend, flags); + break; + case QXL_DRAW_COPY: + red_replay_copy_free(replay, &qxl->u.copy, flags); + break; + case QXL_COPY_BITS: + break; + case QXL_DRAW_FILL: + red_replay_fill_free(replay, &qxl->u.fill, flags); + break; + case QXL_DRAW_OPAQUE: + red_replay_opaque_free(replay, &qxl->u.opaque, flags); + break; + case QXL_DRAW_INVERS: + red_replay_invers_free(replay, &qxl->u.invers, flags); + break; + case QXL_DRAW_NOP: + break; + case QXL_DRAW_ROP3: + red_replay_rop3_free(replay, &qxl->u.rop3, flags); + break; + case QXL_DRAW_STROKE: + red_replay_stroke_free(replay, &qxl->u.stroke, flags); + break; + case QXL_DRAW_TEXT: + red_replay_text_free(replay, &qxl->u.text, flags); + break; + case QXL_DRAW_TRANSPARENT: + red_replay_transparent_free(replay, &qxl->u.transparent, flags); + break; + case QXL_DRAW_WHITENESS: + red_replay_whiteness_free(replay, &qxl->u.whiteness, flags); + break; + case QXL_DRAW_COMPOSITE: + red_replay_composite_free(replay, &qxl->u.composite, flags); + break; + default: + spice_warn_if_reached(); + break; + }; + + free(qxl); +} + +static QXLCompatDrawable *red_replay_compat_drawable(SpiceReplay *replay, uint32_t flags) +{ + int temp; + QXLCompatDrawable *qxl = malloc(sizeof(QXLCompatDrawable)); // TODO - too large usually + + red_replay_rect_ptr(replay, "bbox", &qxl->bbox); + red_replay_clip_ptr(replay, &qxl->clip); + replay_fscanf(replay, "effect %d\n", &temp); qxl->effect = temp; + replay_fscanf(replay, "mm_time %d\n", &qxl->mm_time); + + replay_fscanf(replay, "bitmap_offset %d\n", &temp); qxl->bitmap_offset = temp; + red_replay_rect_ptr(replay, "bitmap_area", &qxl->bitmap_area); + + replay_fscanf(replay, "type %d\n", &temp); qxl->type = temp; + switch (qxl->type) { + case QXL_DRAW_ALPHA_BLEND: + red_replay_alpha_blend_ptr_compat(replay, &qxl->u.alpha_blend, flags); + break; + case QXL_DRAW_BLACKNESS: + red_replay_blackness_ptr(replay, &qxl->u.blackness, flags); + break; + case QXL_DRAW_BLEND: + red_replay_blend_ptr(replay, &qxl->u.blend, flags); + break; + case QXL_DRAW_COPY: + red_replay_copy_ptr(replay, &qxl->u.copy, flags); + break; + case QXL_COPY_BITS: + red_replay_point_ptr(replay, &qxl->u.copy_bits.src_pos); + break; + case QXL_DRAW_FILL: + red_replay_fill_ptr(replay, &qxl->u.fill, flags); + break; + case QXL_DRAW_OPAQUE: + red_replay_opaque_ptr(replay, &qxl->u.opaque, flags); + break; + case QXL_DRAW_INVERS: + red_replay_invers_ptr(replay, &qxl->u.invers, flags); + break; + case QXL_DRAW_NOP: + break; + case QXL_DRAW_ROP3: + red_replay_rop3_ptr(replay, &qxl->u.rop3, flags); + break; + case QXL_DRAW_STROKE: + red_replay_stroke_ptr(replay, &qxl->u.stroke, flags); + break; + case QXL_DRAW_TEXT: + red_replay_text_ptr(replay, &qxl->u.text, flags); + break; + case QXL_DRAW_TRANSPARENT: + red_replay_transparent_ptr(replay, &qxl->u.transparent, flags); + break; + case QXL_DRAW_WHITENESS: + red_replay_whiteness_ptr(replay, &qxl->u.whiteness, flags); + break; + default: + spice_error("%s: unknown type %d", __FUNCTION__, qxl->type); + break; + }; + return qxl; +} + +static QXLPHYSICAL red_replay_drawable(SpiceReplay *replay, uint32_t flags) +{ + if (replay->eof) { + return 0; + } + replay_fscanf(replay, "drawable\n"); + if (flags & QXL_COMMAND_FLAG_COMPAT) { + return (QXLPHYSICAL)red_replay_compat_drawable(replay, flags); + } else { + return (QXLPHYSICAL)red_replay_native_drawable(replay, flags); + } +} + +static QXLUpdateCmd *red_replay_update_cmd(SpiceReplay *replay) +{ + QXLUpdateCmd *qxl = malloc(sizeof(QXLUpdateCmd)); + + replay_fscanf(replay, "update\n"); + red_replay_rect_ptr(replay, "area", &qxl->area); + replay_fscanf(replay, "update_id %d\n", &qxl->update_id); + replay_fscanf(replay, "surface_id %d\n", &qxl->surface_id); + qxl->surface_id = replay_id_get(replay, qxl->surface_id); + + return qxl; +} + +static QXLMessage *red_replay_message(SpiceReplay *replay) +{ + QXLMessage *qxl; + size_t size; + + read_binary(replay, "message", &size, (uint8_t**)&qxl, sizeof(QXLMessage)); + return qxl; +} + +static QXLSurfaceCmd *red_replay_surface_cmd(SpiceReplay *replay) +{ + size_t size; + size_t read_size; + int temp; + QXLSurfaceCmd *qxl = calloc(1, sizeof(QXLSurfaceCmd)); + + replay_fscanf(replay, "surface_cmd\n"); + replay_fscanf(replay, "surface_id %d\n", &qxl->surface_id); + replay_fscanf(replay, "type %d\n", &temp); qxl->type = temp; + replay_fscanf(replay, "flags %d\n", &qxl->flags); + + switch (qxl->type) { + case QXL_SURFACE_CMD_CREATE: + replay_fscanf(replay, "u.surface_create.format %d\n", &qxl->u.surface_create.format); + replay_fscanf(replay, "u.surface_create.width %d\n", &qxl->u.surface_create.width); + replay_fscanf(replay, "u.surface_create.height %d\n", &qxl->u.surface_create.height); + replay_fscanf(replay, "u.surface_create.stride %d\n", &qxl->u.surface_create.stride); + size = qxl->u.surface_create.height * abs(qxl->u.surface_create.stride); + if (qxl->flags && QXL_SURF_FLAG_KEEP_DATA) { + read_binary(replay, "data", &read_size, (uint8_t**)&qxl->u.surface_create.data, 0); + if (read_size != size) { + spice_printerr("mismatch %ld != %ld", size, read_size); + } + } else { + qxl->u.surface_create.data = (QXLPHYSICAL)malloc(size); + } + qxl->surface_id = replay_id_new(replay, qxl->surface_id); + break; + case QXL_SURFACE_CMD_DESTROY: + qxl->u.surface_create.data = (QXLPHYSICAL)NULL; + qxl->surface_id = replay_id_get(replay, qxl->surface_id); + } + return qxl; +} + +static void red_replay_surface_cmd_free(SpiceReplay *replay, QXLSurfaceCmd *qxl) +{ + if (qxl->type == QXL_SURFACE_CMD_DESTROY) { + replay_id_free(replay, qxl->surface_id); + } + + free((void*)qxl->u.surface_create.data); + free(qxl); +} + +static void replay_handle_create_primary(QXLWorker *worker, SpiceReplay *replay) +{ + QXLDevSurfaceCreate surface = { 0, }; + size_t size; + uint8_t *mem = NULL; + + if (replay->created_primary) { + spice_printerr( + "WARNING: %d: orignal recording event not preceded by a destroy primary", + replay->counter); + worker->destroy_primary_surface(worker, 0); + } + replay->created_primary = TRUE; + + replay_fscanf(replay, "%d %d %d %d\n", &surface.width, &surface.height, + &surface.stride, &surface.format); + replay_fscanf(replay, "%d %d %d %d\n", &surface.position, &surface.mouse_mode, + &surface.flags, &surface.type); + read_binary(replay, "data", &size, &mem, 0); + surface.group_id = 0; + surface.mem = (QXLPHYSICAL)mem; + worker->create_primary_surface(worker, 0, &surface); +} + +static void replay_handle_dev_input(QXLWorker *worker, SpiceReplay *replay, + RedWorkerMessage message) +{ + switch (message) { + case RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE: + case RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE_ASYNC: + replay_handle_create_primary(worker, replay); + break; + case RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE: + replay->created_primary = FALSE; + worker->destroy_primary_surface(worker, 0); + break; + case RED_WORKER_MESSAGE_DESTROY_SURFACES: + replay->created_primary = FALSE; + worker->destroy_surfaces(worker); + break; + case RED_WORKER_MESSAGE_UPDATE: + // XXX do anything? we record the correct bitmaps already. + case RED_WORKER_MESSAGE_DISPLAY_CONNECT: + // we want to ignore this one - it is sent on client connection, we + // shall have our own clients + case RED_WORKER_MESSAGE_WAKEUP: + // safe to ignore + break; + default: + spice_debug("unhandled %d\n", message); + } +} + +/* + * NOTE: This reads from a saved file and performs all io actions, calling the + * dispatcher, until it sees a command, at which point it returns it via the + * last parameter [ext_cmd]. Hence you cannot call this from the worker thread + * since it will block reading from the dispatcher pipe. + */ +SPICE_GNUC_VISIBLE QXLCommandExt* spice_replay_next_cmd(SpiceReplay *replay, + QXLWorker *worker) +{ + QXLCommandExt* cmd; + uint64_t timestamp; + int type; + int what = -1; + int counter; + + while (what != 0) { + replay_fscanf(replay, "event %d %d %d %ld\n", &counter, + &what, &type, ×tamp); + if (replay->eof) { + return NULL; + } + if (what == 1) { + replay_handle_dev_input(worker, replay, type); + } + } + cmd = g_slice_new(QXLCommandExt); + cmd->cmd.type = type; + cmd->group_id = 0; + spice_debug("command %ld, %d\r", timestamp, cmd->cmd.type); + switch (cmd->cmd.type) { + case QXL_CMD_DRAW: + cmd->flags = 0; + cmd->cmd.data = red_replay_drawable(replay, cmd->flags); + break; + case QXL_CMD_UPDATE: + cmd->cmd.data = (QXLPHYSICAL)red_replay_update_cmd(replay); + break; + case QXL_CMD_MESSAGE: + cmd->cmd.data = (QXLPHYSICAL)red_replay_message(replay); + break; + case QXL_CMD_SURFACE: + cmd->cmd.data = (QXLPHYSICAL)red_replay_surface_cmd(replay); + break; + } + + QXLReleaseInfo *info; + switch (cmd->cmd.type) { + case QXL_CMD_DRAW: + case QXL_CMD_UPDATE: + case QXL_CMD_SURFACE: + info = (QXLReleaseInfo *)cmd->cmd.data; + info->id = (uint64_t)cmd; + } + + replay->counter++; + + return cmd; +} + +SPICE_GNUC_VISIBLE void spice_replay_free_cmd(SpiceReplay *replay, QXLCommandExt *cmd) +{ + spice_return_if_fail(replay); + spice_return_if_fail(cmd); + + switch (cmd->cmd.type) { + case QXL_CMD_DRAW: { + // FIXME: compat flag must be save somewhere... + spice_return_if_fail(cmd->flags == 0); + QXLDrawable *qxl = (QXLDrawable *)cmd->cmd.data; + red_replay_native_drawable_free(replay, qxl, cmd->flags); + break; + } + case QXL_CMD_UPDATE: { + QXLUpdateCmd *qxl = (QXLUpdateCmd *)cmd->cmd.data; + free(qxl); + break; + } + case QXL_CMD_SURFACE: { + QXLSurfaceCmd *qxl = (QXLSurfaceCmd *)cmd->cmd.data; + red_replay_surface_cmd_free(replay, qxl); + break; + } + default: + break; + } + + g_slice_free(QXLCommandExt, cmd); +} + +/* caller is incharge of closing the replay when done and releasing the SpiceReplay + * memory */ +SPICE_GNUC_VISIBLE +SpiceReplay *spice_replay_new(FILE *file, int nsurfaces) +{ + unsigned int version = 0; + SpiceReplay *replay; + + spice_return_val_if_fail(file != NULL, NULL); + + if (fscanf(file, "SPICE_REPLAY %u\n", &version) == 1) { + if (version > 1) { + spice_warning("Replay file version unsupported"); + return NULL; + } + } + + replay = malloc(sizeof(SpiceReplay)); + + replay->eof = 0; + replay->fd = file; + replay->created_primary = FALSE; + g_mutex_init(&replay->mutex); + g_cond_init(&replay->cond); + replay->id_map = g_array_new(FALSE, FALSE, sizeof(uint32_t)); + replay->id_map_inv = g_array_new(FALSE, FALSE, sizeof(uint32_t)); + replay->id_free = g_array_new(FALSE, FALSE, sizeof(uint32_t)); + replay->nsurfaces = nsurfaces; + + /* reserve id 0 */ + replay_id_new(replay, 0); + + return replay; +} + +SPICE_GNUC_VISIBLE void spice_replay_free(SpiceReplay *replay) +{ + spice_return_if_fail(replay != NULL); + + g_mutex_clear(&replay->mutex); + g_cond_clear(&replay->cond); + g_array_free(replay->id_map, TRUE); + g_array_free(replay->id_map_inv, TRUE); + g_array_free(replay->id_free, TRUE); + fclose(replay->fd); + free(replay); +} diff --git a/server/red_replay_qxl.h b/server/red_replay_qxl.h new file mode 100644 index 00000000..a89b3d44 --- /dev/null +++ b/server/red_replay_qxl.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + Copyright (C) 2009-2015 Red Hat, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see . +*/ +#ifndef RED_REPLAY_QXL_H +#define RED_REPLAY_QXL_H + +#include +#include +#include + +typedef struct SpiceReplay SpiceReplay; + +/* reads until encountering a cmd, processing any recorded messages (io) on the + * way */ +QXLCommandExt* spice_replay_next_cmd(SpiceReplay *replay, QXLWorker *worker); +void spice_replay_free_cmd(SpiceReplay *replay, QXLCommandExt *cmd); +void spice_replay_free(SpiceReplay *replay); +SpiceReplay * spice_replay_new(FILE *file, int nsurfaces); + +#endif // RED_REPLAY_QXL_H diff --git a/server/spice-server.syms b/server/spice-server.syms index 3c7d9456..d65e14d9 100644 --- a/server/spice-server.syms +++ b/server/spice-server.syms @@ -157,4 +157,8 @@ global: SPICE_SERVER_0.12.6 { global: spice_qxl_set_max_monitors; + spice_replay_free; + spice_replay_new; + spice_replay_next_cmd; + spice_replay_free_cmd; } SPICE_SERVER_0.12.5; -- cgit