From 271b809495eed3ab3fb5e86ec0bb67df29ebd529 Mon Sep 17 00:00:00 2001 From: Mikhail Zaslonko Date: Thu, 5 Jan 2023 15:06:30 +0100 Subject: [PATCH] zdump/dfi_s390: Support reading compressed s390_ext dumps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update dfi_s390.c to support reading of compressed dump segments. For this, introduce a callback function for reading memory chunks associated with compressed dump segments. Apart from the segment location on disk this function requires the entry_offset array from the dump segment header in order to process each compressed entry separately, thus allowing fast seek processing for zgetdump (no need to decompress a big dump segment to extract a single piece of data). In addition, split mem_chunks_add_ext() in several functions. Signed-off-by: Mikhail Zaslonko Reviewed-by: Alexander Egorenkov Signed-off-by: Jan Höppner --- zdump/dfi_s390.c | 192 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 179 insertions(+), 13 deletions(-) diff --git a/zdump/dfi_s390.c b/zdump/dfi_s390.c index f9fc8f9f..64ebcee6 100644 --- a/zdump/dfi_s390.c +++ b/zdump/dfi_s390.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "lib/util_log.h" #include "dump/s390_dump.h" @@ -36,8 +37,21 @@ static struct { struct df_s390_hdr hdr; /* s390 dump header */ struct df_s390_em em; /* s390 end marker */ bool extended; /* Extended input dump format */ + int blk_size; /* Dump device block size */ } l; +struct mem_chunk_compressed_data { + /* Offset of the dump segment data on disk in bytes */ + u64 offset_on_disk; + /* Number of compressed entries */ + u32 entry_count; + /* + * Offsets to compressed entries from + * the start of mem_chunk in blocks + */ + u32 entry_offset[]; +}; + /* * s390 mem chunk read callback */ @@ -57,11 +71,118 @@ static void dfi_s390_ext_mem_chunk_read(struct dfi_mem_chunk *mem_chunk, u64 off, void *buf, u64 cnt) { u64 *mem_chunk_off = mem_chunk->data; + util_log_print(UTIL_LOG_DEBUG, + "DFI S390 mem_chunk_read: start=0x%016lx, off=0x%016lx, cnt=0x%08lx\n", + mem_chunk->start, off, cnt); zg_seek(g.fh, *mem_chunk_off + off, ZG_CHECK); zg_read(g.fh, buf, cnt, ZG_CHECK); } +static inline unsigned long b2m(unsigned long blk) +{ + return blk * l.blk_size; +} + +/* + * Read compressed data entry using global file handle and decompress up to + * count bytes to the output buffer. The size of the output buffer considered + * to be enough to fit the decompressed data. + */ +static unsigned long read_decompress_entry(void *buf_out, u64 count) +{ + unsigned char buf_in[8 * PAGE_SIZE]; + z_stream strm = { 0 }; + u64 total_out; + int rc; + + /* Initialize inflate stream */ + strm.next_in = NULL; + strm.avail_in = 0; + rc = inflateInit2(&strm, MAX_WBITS); + if (rc != Z_OK) + ERR_EXIT("Decompression failed, inflateInit RC = %d", rc); + strm.next_out = buf_out; + strm.avail_out = count; + /* Decompress data entry to the output buffer */ + while (rc != Z_STREAM_END) { + /* Read in more compressed data */ + if (strm.avail_in == 0) { + strm.next_in = buf_in; + rc = zg_read(g.fh, buf_in, sizeof(buf_in), ZG_CHECK_NONE); + if (rc < 0) + ERR_EXIT("Decompression failed, read error encountered"); + strm.avail_in = rc; + } + rc = inflate(&strm, Z_SYNC_FLUSH); + if (rc != Z_OK && rc != Z_STREAM_END) + ERR_EXIT("Decompression failed, inflate RC = %d", rc); + /* No need to decompress more data */ + if (strm.avail_out == 0) + break; + } + total_out = strm.total_out; + inflateEnd(&strm); + return total_out; +} + +/* + * s390_ext compressed mem chunk read callback + */ +static void dfi_s390_ext_mem_chunk_read_decompress(struct dfi_mem_chunk *mem_chunk, + u64 off, void *buf, u64 cnt) +{ + u64 bytes_to_copy, start_offset, compressed_data_addr, copied, decompressed_out; + struct mem_chunk_compressed_data *data = mem_chunk->data; + u32 uncompressed, entry_size, entry_index; + void *buf_out; + + util_log_print(UTIL_LOG_DEBUG, + "DFI S390 mem_chunk_read_decomp: start=0x%016lx, offset_on_disk=0x%016lx, off=0x%016lx, cnt=0x%08lx\n", + mem_chunk->start, data->offset_on_disk, off, cnt); + /* + * The dump is compressed in data pieces of equal size (stored in the dump header). + * Identify the first compressed entry of interest within a memory chunk. + */ + entry_size = l.hdr.zlib_entry_size; + entry_index = off / entry_size; + /* Offset within the decompressed data of the first entry */ + start_offset = off % entry_size; + buf_out = zg_alloc(entry_size); + copied = 0; + while (copied < cnt) { + if (entry_index >= data->entry_count) + ERR_EXIT("Decompression failed, compressed entry index out of bounds"); + /* Check if the entry is actually compressed */ + uncompressed = data->entry_offset[entry_index] & DUMP_SEGM_ENTRY_UNCOMPRESSED; + /* Calculate entry location on disk */ + compressed_data_addr = data->offset_on_disk + + b2m(~DUMP_SEGM_ENTRY_UNCOMPRESSED & data->entry_offset[entry_index]); + if (uncompressed) { + /* Read entire uncompressed entry from disk */ + zg_seek(g.fh, compressed_data_addr + start_offset, ZG_CHECK); + bytes_to_copy = MIN(cnt - copied, entry_size - start_offset); + zg_read(g.fh, buf + copied, bytes_to_copy, ZG_CHECK); + } else { + /* + * Decompress entry to the output buffer. Limit the size of the + * output data if needed. + */ + zg_seek(g.fh, compressed_data_addr, ZG_CHECK); + decompressed_out = MIN(start_offset + cnt - copied, entry_size); + decompressed_out = read_decompress_entry(buf_out, decompressed_out); + /* Copy the decompressed output produced so far */ + bytes_to_copy = MIN(decompressed_out - start_offset, cnt - copied); + memcpy(buf + copied, buf_out + start_offset, bytes_to_copy); + } + copied += bytes_to_copy; + entry_index++; + /* For further entries, copy from the start of the decompressed data */ + start_offset = 0; + } + zg_free(buf_out); +} + /* * Read s390 dump header @@ -79,6 +200,8 @@ static int read_s390_hdr(void) return -ENODEV; if (l.hdr.cpu_cnt > DF_S390_CPU_MAX) return -ENODEV; + if (l.hdr.zlib_version_s390 && l.hdr.version != 2) + return -ENODEV; util_log_print(UTIL_LOG_INFO, "DFI S390 version %u\n", l.hdr.version); df_s390_hdr_add(&l.hdr); return 0; @@ -122,6 +245,44 @@ static int mem_chunks_add(void) return read_s390_em(); } +/* + * Add memory chunk for a compressed dump segment. Set file handle position + * and return the offset to the next dump segment. + */ +static void create_compressed_mem_chunk(const struct df_s390_dump_segm_hdr *dump_segm, + u64 offset) +{ + /* + * Need to preserve entry_offset array from the header of a + * compressed segment along with the segment offset on disk + * for the read callback function. + */ + struct mem_chunk_compressed_data *data = zg_alloc(sizeof(*data) + + dump_segm->entry_count * sizeof(u32)); + data->offset_on_disk = offset; + data->entry_count = dump_segm->entry_count; + memcpy(&data->entry_offset, &dump_segm->entry_offset, + data->entry_count * sizeof(u32)); + dfi_mem_chunk_add(dump_segm->start, dump_segm->len, data, + dfi_s390_ext_mem_chunk_read_decompress, zg_free); + data = NULL; +} + +/* + * Add memory chunk for a non-compressed dump segment. Set file handle position + * and return the offset to the next dump segment. + */ +static void create_uncompressed_mem_chunk(const struct df_s390_dump_segm_hdr *dump_segm, + u64 offset) +{ + /* For non-compressed segment just an offset is needed for a callback */ + u64 *off_ptr = zg_alloc(sizeof(*off_ptr)); + *off_ptr = offset; + dfi_mem_chunk_add(dump_segm->start, dump_segm->len, off_ptr, + dfi_s390_ext_mem_chunk_read, zg_free); + off_ptr = NULL; +} + /* * Register memory chunks (extended dump format) and verify the end marker */ @@ -133,26 +294,30 @@ static int mem_chunks_add_ext(void) off = zg_seek(g.fh, DF_S390_HDR_SIZE, ZG_CHECK_NONE); if (off != DF_S390_HDR_SIZE) return -EINVAL; - while (off < DF_S390_HDR_SIZE + l.hdr.mem_size - PAGE_SIZE) { - rc = zg_read(g.fh, &dump_segm, PAGE_SIZE, ZG_CHECK_ERR); - if (rc != PAGE_SIZE) + while (off < DF_S390_HDR_SIZE + l.hdr.mem_size) { + rc = zg_read(g.fh, &dump_segm, sizeof(dump_segm), ZG_CHECK_ERR); + if (rc != sizeof(dump_segm)) return -EINVAL; util_log_print(UTIL_LOG_DEBUG, - "DFI S390 dump segment start 0x%016lx size 0x%016lx stop marker %d\n", - dump_segm.start, dump_segm.len, dump_segm.stop_marker); - off += PAGE_SIZE; + "DFI S390 dump segment start 0x%016lx size 0x%016lx compr_size 0x%08lx stop marker %d\n", + dump_segm.start, dump_segm.len, b2m(dump_segm.size_on_disk), + dump_segm.stop_marker ? 1 : 0); + off += sizeof(dump_segm); /* Add zero memory chunk */ dfi_mem_chunk_add(old, dump_segm.start - old, NULL, dfi_mem_chunk_read_zero, NULL); /* Add memory chunk for a dump segment */ - u64 *off_ptr = zg_alloc(sizeof(*off_ptr)); - *off_ptr = off; - dfi_mem_chunk_add(dump_segm.start, dump_segm.len, off_ptr, - dfi_s390_ext_mem_chunk_read, zg_free); - off_ptr = NULL; + if (dump_segm.size_on_disk) { + /* Compressed dump segment detected */ + create_compressed_mem_chunk(&dump_segm, off); + off = zg_seek_cur(g.fh, b2m(dump_segm.size_on_disk), ZG_CHECK_NONE); + dump_size += b2m(dump_segm.size_on_disk); + } else { + create_uncompressed_mem_chunk(&dump_segm, off); + off = zg_seek_cur(g.fh, dump_segm.len, ZG_CHECK_NONE); + dump_size += dump_segm.len; + } old = dump_segm.start + dump_segm.len; - dump_size += dump_segm.len; - off = zg_seek_cur(g.fh, dump_segm.len, ZG_CHECK_NONE); if (dump_segm.stop_marker) break; } @@ -181,6 +346,7 @@ int dfi_s390_init_gen(bool extended) l.extended = extended; if (read_s390_hdr() != 0) return -ENODEV; + zg_ioctl(g.fh, BLKSSZGET, &l.blk_size, "BLKSSZGET", ZG_CHECK); if (!extended) rc = mem_chunks_add(); else