[PATCH v1 2/6] tools/libs/guest: decompress PAGE_DATA_LZ4 records on restore
Marcus Granado <[email protected]>
| Newsgroups | org.xenproject.lists.xen-devel |
|---|---|
| Message-ID | <[email protected]> |
This patch updates the restore side of the migration stream to accept the newly defined REC_TYPE_PAGE_DATA_LZ4 record. When HAVE_LZ4 is defined, handle_data_lz4() decodes the PAGE_DATA_LZ4 record's data_lz4 payload (containing the per-page [uint16 LE clen][clen octets] entries) into ctx->restore.lz4_decomp_buf and returns it, or NULL on error. clen is read via lz4_page_get_clen() (little-endian octet assembly instead of a pointer cast as the entries are byte-packed and therefore unaligned). Each entry's length and the running input offset are bounds-checked against the record payload, each page_data_lz4 must decompress to exactly PAGE_SIZE, and the walk must consume the payload exactly, before any byte reaches the guest. The decompressed page_data_lz4 is sent to the existing process_page_data() path of REC_TYPE_PAGE_DATA, so the rest of the restore code stays the same. The existing process_record() routes both types of page data records to the same handle_page_data(), which is refactored to dispatch on rec->type: * the original uncompresed REC_TYPE_PAGE_DATA case keeps its original exact-size check * the new REC_TYPE_PAGE_DATA_LZ4 case expects variable record lengths and decompresses the expected_payload The decompression buffer grows on demand via realloc() and is freed in cleanup(). Sizing is driven by the uncompressed byte count from the expected_payload of the header, so the buffer naturally converges to the largest batch size used during the stream. The existing per-object 'xg_dom_bzimageloader.o: CFLAGS += $(ZLIB_CFLAGS)' is broadened to a global 'CFLAGS += $(ZLIB_CFLAGS)' (tools/configure groups liblz4's -DHAVE_LZ4 and cflags into ZLIB_CFLAGS), so HAVE_LZ4 is defined consistently for every libxenguest object that includes xg_sr_common.h. Signed-off-by: Marcus Granado <[email protected]> --- tools/libs/guest/Makefile | 2 +- tools/libs/guest/xg_sr_common.h | 12 +++ tools/libs/guest/xg_sr_restore.c | 149 +++++++++++++++++++++++++++++-- 3 files changed, 154 insertions(+), 9 deletions(-) diff --git a/tools/libs/guest/Makefile b/tools/libs/guest/Makefile index 93338a9301..037f1f4de9 100644 --- a/tools/libs/guest/Makefile +++ b/tools/libs/guest/Makefile @@ -3,7 +3,7 @@ include $(XEN_ROOT)/tools/Rules.mk include Makefile.common -xg_dom_bzimageloader.o xg_dom_bzimageloader.opic: CFLAGS += $(ZLIB_CFLAGS) +CFLAGS += $(ZLIB_CFLAGS) $(LIBELF_OBJS:.o=.opic): CFLAGS += -Wno-pointer-sign diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h index f1573aefcb..e2321b25d5 100644 --- a/tools/libs/guest/xg_sr_common.h +++ b/tools/libs/guest/xg_sr_common.h @@ -209,6 +209,12 @@ static inline int update_blob(struct xc_sr_blob *blob, return 0; } +/* Little-endian accessors for the PAGE_DATA_LZ4 per-page clen prefix. */ +static inline uint16_t lz4_page_get_clen(const uint8_t *p) +{ + return (uint16_t)(p[0] | (p[1] << 8)); +} + struct xc_sr_context { xc_interface *xch; @@ -298,6 +304,12 @@ struct xc_sr_context /* memflags to pass to xc_domain_populate_physmap{_exact}(). */ unsigned int memflags; + +#ifdef HAVE_LZ4 + /* Persistent LZ4 decompression buffer */ + void *lz4_decomp_buf; + size_t lz4_decomp_buf_size; +#endif } restore; }; diff --git a/tools/libs/guest/xg_sr_restore.c b/tools/libs/guest/xg_sr_restore.c index 458eaa5992..be8a88288e 100644 --- a/tools/libs/guest/xg_sr_restore.c +++ b/tools/libs/guest/xg_sr_restore.c @@ -4,6 +4,10 @@ #include "xg_sr_common.h" +#ifdef HAVE_LZ4 +#include <lz4.h> +#endif + /* * Read and validate the Image and Domain headers. */ @@ -354,6 +358,104 @@ static int process_page_data(struct xc_sr_context *ctx, unsigned int count, return rc; } +/* + * Decode a PAGE_DATA_LZ4 record's data_lz4 (see xg_sr_stream_format.h): + * nr_pages per-page sub-blocks, each a uint16 LE clen then clen octets of + * page_data_lz4 (clen == 0 => a raw page), into ctx->restore.lz4_decomp_buf + * (grown as needed). Returns the decoded page data, or NULL on error. + */ +static void *handle_data_lz4(struct xc_sr_context *ctx, + const uint8_t *data_lz4, + size_t data_lz4_len, + unsigned int nr_pages) +{ + xc_interface *xch = ctx->xch; +#ifdef HAVE_LZ4 + size_t expected = (size_t)nr_pages * PAGE_SIZE; + size_t in_off = 0; + unsigned int p; + + if ( nr_pages == 0 ) + { + ERROR("Compressed PAGE_DATA record with zero present pages"); + return NULL; + } + + if ( expected > ctx->restore.lz4_decomp_buf_size ) + { + void *new_buf = realloc(ctx->restore.lz4_decomp_buf, expected); + if ( !new_buf ) + { + ERROR("Unable to allocate %zu bytes for LZ4 payload", expected); + return NULL; + } + ctx->restore.lz4_decomp_buf = new_buf; + ctx->restore.lz4_decomp_buf_size = expected; + } + + for ( p = 0; p < nr_pages; ++p ) + { + uint8_t *dst = (uint8_t *)ctx->restore.lz4_decomp_buf + p * PAGE_SIZE; + uint16_t clen; + int decomp_ret; + + if ( in_off + PAGE_DATA_LZ4_CLEN_SIZE > data_lz4_len ) + { + ERROR("PAGE_DATA_LZ4 record with truncated length prefix"); + return NULL; + } + clen = lz4_page_get_clen(data_lz4 + in_off); + in_off += PAGE_DATA_LZ4_CLEN_SIZE; + + /* top 4 bits reserved for future use; must be 0 */ + if ( clen & PAGE_DATA_LZ4_CLEN_RESERVED_MASK ) + { + ERROR("PAGE_DATA_LZ4 record with reserved clen bit set"); + return NULL; + } + + if ( clen == PAGE_DATA_LZ4_CLEN_RAW ) + { + if ( PAGE_SIZE > data_lz4_len - in_off ) + { + ERROR("PAGE_DATA_LZ4 record with truncated raw page"); + return NULL; + } + memcpy(dst, data_lz4 + in_off, PAGE_SIZE); + in_off += PAGE_SIZE; + continue; + } + + /* reserved-bit reject above already caps clen < PAGE_SIZE */ + if ( (size_t)clen > data_lz4_len - in_off ) + { + ERROR("PAGE_DATA_LZ4 record with bad block length"); + return NULL; + } + decomp_ret = LZ4_decompress_safe((const char *)(data_lz4 + in_off), + (char *)dst, (int)clen, + (int)PAGE_SIZE); + if ( decomp_ret != (int)PAGE_SIZE ) + { + ERROR("PAGE_DATA_LZ4 record decompressed to wrong size"); + return NULL; + } + in_off += clen; + } + + if ( in_off != data_lz4_len ) + { + ERROR("PAGE_DATA_LZ4 record with trailing bytes in payload"); + return NULL; + } + + return ctx->restore.lz4_decomp_buf; +#else + ERROR("LZ4 compressed PAGE_DATA record but LZ4 support not compiled in"); + return NULL; +#endif /* HAVE_LZ4 */ +} + /* * Validate a PAGE_DATA record from the stream, and pass the results to * process_page_data() to actually perform the legwork. @@ -448,18 +550,45 @@ static int handle_page_data(struct xc_sr_context *ctx, struct xc_sr_record *rec) types[i] = type; } - if ( rec->length != (sizeof(*pages) + - (sizeof(uint64_t) * pages->count) + - (PAGE_SIZE * pages_of_data)) ) + size_t header_len = sizeof(*pages) + (sizeof(uint64_t) * pages->count); + size_t expected_payload = PAGE_SIZE * pages_of_data; + + if ( rec->length < header_len ) { - ERROR("PAGE_DATA record wrong size: length %u, expected " - "%zu + %zu + %lu", rec->length, sizeof(*pages), - (sizeof(uint64_t) * pages->count), (PAGE_SIZE * pages_of_data)); + ERROR("PAGE_DATA record too short: header %zu > length %u", + header_len, rec->length); goto err; } - rc = process_page_data(ctx, pages->count, pfns, types, - &pages->pfn[pages->count]); + switch ( rec->type ) + { + case REC_TYPE_PAGE_DATA: + if ( rec->length != header_len + expected_payload ) + { + ERROR("PAGE_DATA record wrong size: length %u, expected " + "%zu + %zu + %lu", rec->length, sizeof(*pages), + (sizeof(uint64_t) * pages->count), expected_payload); + goto err; + } + + rc = process_page_data(ctx, pages->count, pfns, types, + &pages->pfn[pages->count]); + break; + + case REC_TYPE_PAGE_DATA_LZ4: + { + const uint8_t *data_lz4 = (const uint8_t *)&pages->pfn[pages->count]; + size_t data_lz4_len = rec->length - header_len; + void *page_data = handle_data_lz4(ctx, data_lz4, data_lz4_len, + pages_of_data); + + if ( !page_data ) + goto err; + + rc = process_page_data(ctx, pages->count, pfns, types, page_data); + break; + } + } err: free(types); free(pfns); @@ -705,6 +834,7 @@ static int process_record(struct xc_sr_context *ctx, struct xc_sr_record *rec) break; case REC_TYPE_PAGE_DATA: + case REC_TYPE_PAGE_DATA_LZ4: rc = handle_page_data(ctx, rec); break; @@ -796,6 +926,9 @@ static void cleanup(struct xc_sr_context *ctx) free(ctx->restore.buffered_records); free(ctx->restore.populated_pfns); +#ifdef HAVE_LZ4 + free(ctx->restore.lz4_decomp_buf); +#endif if ( ctx->restore.ops.cleanup(ctx) ) PERROR("Failed to clean up"); -- 2.43.0