/* * zgetdump - Tool for copying and converting System z dumps * * S390 dump input format * * Copyright IBM Corp. 2001, 2017 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include #include #include #include "lib/util_log.h" #include "dump/s390_dump.h" #include "zgetdump.h" #include "zg.h" #include "dfi.h" #include "dfi_mem_chunk.h" #include "df_elf.h" #include "df_s390.h" /* * File local static data */ 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 */ static void dfi_s390_mem_chunk_read(struct dfi_mem_chunk *mem_chunk, u64 off, void *buf, u64 cnt) { (void) mem_chunk; zg_seek(g.fh, off + DF_S390_HDR_SIZE, ZG_CHECK); zg_read(g.fh, buf, cnt, ZG_CHECK); } /* * s390_ext mem chunk read callback */ 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 */ static int read_s390_hdr(void) { u64 magic_number; magic_number = l.extended ? DF_S390_MAGIC_EXT : DF_S390_MAGIC; if ((zg_type(g.fh) == ZG_TYPE_FILE) && (zg_size(g.fh) < sizeof(l.hdr))) return -ENODEV; if (zg_read(g.fh, &l.hdr, sizeof(l.hdr), ZG_CHECK_ERR) != sizeof(l.hdr)) return -ENODEV; if (l.hdr.magic != magic_number) 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; } /* * Init end marker */ static int read_s390_em(void) { u64 rc; rc = zg_read(g.fh, &l.em, sizeof(l.em), ZG_CHECK_ERR); if (rc != sizeof(l.em)) return -EINVAL; if (df_s390_em_verify(&l.em, &l.hdr) != 0) return -EINVAL; df_s390_em_add(&l.em); return 0; } /* * Register memory chunks and verify the end marker */ static int mem_chunks_add(void) { u64 rc; util_log_print(UTIL_LOG_DEBUG, "DFI S390 mem_size 0x%016lx\n", l.hdr.mem_size); /* Single memory chunk for non-extended dump format */ dfi_mem_chunk_add(0, l.hdr.mem_size, NULL, dfi_s390_mem_chunk_read, NULL); rc = zg_seek(g.fh, DF_S390_HDR_SIZE + l.hdr.mem_size, ZG_CHECK_NONE); if (rc != DF_S390_HDR_SIZE + l.hdr.mem_size) return -EINVAL; /* Read and verify the end marker */ 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 */ static int mem_chunks_add_ext(void) { struct df_s390_dump_segm_hdr dump_segm = { 0 }; u64 rc, off, old = 0, dump_size = 0; 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) { 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 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 */ 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; if (dump_segm.stop_marker) break; } /* Check if the last dump segment found */ if (!dump_segm.stop_marker) return -EINVAL; /* Add zero memory chunk at the end */ dfi_mem_chunk_add(old, l.hdr.mem_size - old, NULL, dfi_mem_chunk_read_zero, NULL); /* Set the actual size of the dump file */ dfi_attr_file_size_set(dump_size); /* Read and verify the end marker */ return read_s390_em(); } /* * Initialize s390 single-volume DFI general function */ int dfi_s390_init_gen(bool extended) { int rc; util_log_print(UTIL_LOG_DEBUG, "DFI S390 %sinitialization\n", extended ? "extended " : ""); l.extended = extended; if (read_s390_hdr() != 0) return -ENODEV; if (!extended) { rc = mem_chunks_add(); } else { /* * A device block size is required for a decompression of * s390_ext dump with compressed dump segments. * Since dumps in s390_ext format can reside on DASD partition * only, bail out upon ioctl error on BLKSSZGET. */ if (zg_ioctl(g.fh, BLKSSZGET, &l.blk_size, "BLKSSZGET", ZG_CHECK_NONE)) return -ENODEV; rc = mem_chunks_add_ext(); } if (rc) return rc; rc = df_s390_cpu_info_add(&l.hdr, l.hdr.mem_size); if (rc) return rc; zg_seek(g.fh, sizeof(l.hdr), ZG_CHECK); return 0; } /* * Initialize s390 single-volume DFI (non-extended) */ static int dfi_s390_init(void) { return dfi_s390_init_gen(DUMP_NON_EXTENDED); } /* * s390 single-volume DFI (non-extended) operations */ struct dfi dfi_s390 = { .name = "s390", .init = dfi_s390_init, .feat_bits = DFI_FEAT_COPY | DFI_FEAT_SEEK, };