File diskdump-Support-flattened-diskdump-format.patch of Package libkdumpfile.36085

From: Petr Tesarik <petr@tesarici.cz>
Date: Mon, 6 Nov 2023 11:46:03 +0100
Subject: diskdump: Support flattened diskdump format
References: bsc#1223399
Upstream: merged
Git-commit: ee81ef499225feb4d8cbfa51a86123e036813297

Allow reading flattened diskdump files directly.

The mapping from rearranged file offsets to flattened file offsets is
implemented using libaddrxlat's addrxlat_map_t, which is normally used to
map an address to a translation method. Here it maps the rearranged file
offset to an index into an array of flattened file offset. This is a hack,
but it allows to reuse the non-trivial logic in addrxlat_map_set(), which
avoids the need to walk all segments in case some (rearranged) offset was
written more than once.

[ ptesarik: Drop tests and NEWS. Tests would require backporting
  many additional patches, and NEWS is pointless. ]
Signed-off-by: Petr Tesarik <ptesarik@suse.com>
---
 src/kdumpfile/diskdump.c |  303 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 289 insertions(+), 14 deletions(-)
 create mode 100755 tests/diskdump-flat-raw

--- a/src/kdumpfile/diskdump.c
+++ b/src/kdumpfile/diskdump.c
@@ -47,6 +47,27 @@
 
 /** @cond TARGET_ABI */
 
+#define MDF_SIGNATURE		"makedumpfile"
+#define MDF_SIG_LEN		16
+#define MDF_TYPE_FLAT_HEADER	1
+#define MDF_VERSION_FLAT_HEADER	1
+#define MDF_HEADER_SIZE		4096
+
+/* Flattened format header. */
+struct makedumpfile_header {
+	char	signature[MDF_SIG_LEN];
+	int64_t	type;
+	int64_t	version;
+} __attribute__((packed));
+
+/* Flattened segment header */
+struct makedumpfile_data_header {
+        int64_t offset;
+        int64_t buf_size;
+} __attribute__((packed));
+
+#define MDF_OFFSET_END_FLAG	(-(int64_t)1)
+
 /* The header is architecture-dependent, unfortunately */
 struct disk_dump_header_32 {
 	char			signature[SIG_LEN];	/* = "DISKDUMP" */
@@ -168,6 +189,12 @@ struct disk_dump_priv {
 	/** Overridden methods for arch.page_size attribute. */
 	struct attr_override page_size_override;
 	int cbuf_slot;		/**< Compressed data per-context slot. */
+
+	/** File offset mapping for flattened files. */
+	addrxlat_map_t *flatmap;
+
+	/** Differences between flattened and rearranged file offsets. */
+	off_t *flatoffs;
 };
 
 struct setup_data {
@@ -190,6 +217,120 @@ struct setup_data {
 
 static void diskdump_cleanup(struct kdump_shared *shared);
 
+/** Read buffer from a flattened dump file.
+ * @param ctx   Dump file object.
+ * @param buf   Target I/O buffer.
+ * @param len   Length of data.
+ * @param pos   File position.
+ * @returns     Error status.
+ *
+ * Read data from the flattened segment(s) which contain(s) @p len bytes
+ * at position @p pos after rearrangement.
+ */
+static kdump_status
+flattened_pread(kdump_ctx_t *ctx, void *buf, size_t len, off_t pos)
+{
+	struct disk_dump_priv *ddp = ctx->shared->fmtdata;
+	const addrxlat_range_t *range, *end;
+	off_t off;
+
+	range = addrxlat_map_ranges(ddp->flatmap);
+	end = range + addrxlat_map_len(ddp->flatmap);
+	for (off = pos; range < end && off > range->endoff; ++range)
+		off -= range->endoff + 1;
+	while (range < end && len) {
+		size_t seglen;
+
+		seglen = range->endoff + 1 - off;
+		if (seglen > len)
+			seglen = len;
+
+		if (range->meth != ADDRXLAT_SYS_METH_NONE) {
+			unsigned segidx = range->meth;
+			kdump_status ret;
+
+			ret = fcache_pread(ctx->shared->fcache, buf, seglen,
+					   pos + ddp->flatoffs[segidx]);
+			if (ret != KDUMP_OK)
+				return ret;
+		} else
+			memset(buf, 0, seglen);
+
+		buf += seglen;
+		len -= seglen;
+		pos += seglen;
+		++range;
+		off = 0;
+	}
+
+	if (len)
+		memset(buf, 0, len);
+	return KDUMP_OK;
+}
+
+/** Read buffer from a diskdump file.
+ * @param ctx   Dump file object.
+ * @param buf   Target I/O buffer.
+ * @param len   Length of data.
+ * @param pos   File position.
+ * @returns     Error status.
+ *
+ * Read data from a diskdump file. If the file is flattened, interpret
+ * @p pos as if it was already rearranged.
+ */
+static inline kdump_status
+diskdump_pread(kdump_ctx_t *ctx, void *buf, size_t len, off_t pos)
+{
+	struct disk_dump_priv *ddp = ctx->shared->fmtdata;
+
+	return ddp->flatmap
+		? flattened_pread(ctx, buf, len, pos)
+		: fcache_pread(ctx->shared->fcache, buf, len, pos);
+}
+
+/** Get a contiguous data chunk from a flattened dump file.
+ * @param ctx   Dump file object.
+ * @param fch   File cache chunk, updated on success.
+ * @param len   Length of data.
+ * @param pos   File position.
+ * @returns     Error status.
+ *
+ * Get a contiguous data chunk from a flattened dump file. Currently, this is
+ * implemented using a dynamically allocated buffer, even if the underlying
+ * file cache buffer might be used.
+ */
+static inline kdump_status
+flattened_get_chunk(kdump_ctx_t *ctx, struct fcache_chunk *fch,
+		    size_t len, off_t pos)
+{
+	fch->data = malloc(len);
+	if (!fch->data)
+		return KDUMP_ERR_SYSTEM;
+	fch->nent = 0;
+	return flattened_pread(ctx, fch->data, len, pos);
+}
+
+/** Get a contiguous data chunk from a diskdump file.
+ * @param ctx   Dump file object.
+ * @param fch   File cache chunk, updated on success.
+ * @param len   Length of data.
+ * @param pos   File position.
+ * @returns     Error status.
+ *
+ * Get a contiguous data chunk from a diskdump file. If the file is
+ * flattened, interpret @p pos as if it was already rearranged.
+ */
+static inline kdump_status
+diskdump_get_chunk(kdump_ctx_t *ctx, struct fcache_chunk *fch,
+		   size_t len, off_t pos)
+{
+	struct disk_dump_priv *ddp = ctx->shared->fmtdata;
+
+	return ddp->flatmap
+		? flattened_get_chunk(ctx, fch, len, pos)
+		: fcache_get_chunk(ctx->shared->fcache, fch, len, pos);
+}
+
 /** Add a new PFN region.
  * @param ctx  Dump file context.
  * @param rgn  PFN region.
@@ -382,7 +523,7 @@ diskdump_read_page(kdump_ctx_t *ctx, str
 	}
 
 	mutex_lock(&ctx->shared->cache_lock);
-	ret = fcache_pread(ctx->shared->fcache, &pd, sizeof pd, pd_pos);
+	ret = diskdump_pread(ctx, &pd, sizeof pd, pd_pos);
 	mutex_unlock(&ctx->shared->cache_lock);
 	if (ret != KDUMP_OK)
 		return set_error(ctx, ret,
@@ -410,7 +551,7 @@ diskdump_read_page(kdump_ctx_t *ctx, str
 
 	/* read page data */
 	mutex_lock(&ctx->shared->cache_lock);
-	ret = fcache_pread(ctx->shared->fcache, buf, pd.size, pd.offset);
+	ret = diskdump_pread(ctx, buf, pd.size, pd.offset);
 	mutex_unlock(&ctx->shared->cache_lock);
 	if (ret != KDUMP_OK)
 		return set_error(ctx, ret,
@@ -509,7 +650,7 @@ read_vmcoreinfo(kdump_ctx_t *ctx, off_t
 	kdump_attr_value_t val;
 	kdump_status ret;
 
-	ret = fcache_get_chunk(ctx->shared->fcache, &fch, size, off);
+	ret = diskdump_get_chunk(ctx, &fch, size, off);
 	if (ret != KDUMP_OK)
 		return set_error(ctx, ret,
 				 "Cannot read %zu VMCOREINFO bytes at %llu",
@@ -535,7 +676,7 @@ read_notes(kdump_ctx_t *ctx, off_t off,
 	struct fcache_chunk fch;
 	kdump_status ret;
 
-	ret = fcache_get_chunk(ctx->shared->fcache, &fch, size, off);
+	ret = diskdump_get_chunk(ctx, &fch, size, off);
 	if (ret != KDUMP_OK)
 		return set_error(ctx, ret,
 				 "Cannot read %zu note bytes at %llu",
@@ -632,7 +773,7 @@ read_bitmap(kdump_ctx_t *ctx, int32_t su
 	if (get_max_pfn(ctx) > max_bitmap_pfn)
 		set_max_pfn(ctx, max_bitmap_pfn);
 
-	ret = fcache_get_chunk(ctx->shared->fcache, &fch, bitmapsize, off);
+	ret = diskdump_get_chunk(ctx, &fch, bitmapsize, off);
 	if (ret != KDUMP_OK)
 		return set_error(ctx, ret,
 				 "Cannot read %zu bytes of page bitmap"
@@ -707,8 +848,8 @@ read_sub_hdr_32(struct setup_data *sdp,
 	if (header_version < 1)
 		return KDUMP_OK;
 
-	ret = fcache_pread(ctx->shared->fcache, &subhdr, sizeof subhdr,
-			   get_page_size(ctx));
+	ret = diskdump_pread(ctx, &subhdr, sizeof subhdr,
+			     get_page_size(ctx));
 	if (ret != KDUMP_OK)
 		return set_error(ctx, ret,
 				 "Cannot read subheader");
@@ -790,8 +931,8 @@ read_sub_hdr_64(struct setup_data *sdp,
 	if (header_version < 1)
 		return KDUMP_OK;
 
-	ret = fcache_pread(ctx->shared->fcache, &subhdr, sizeof subhdr,
-			   get_page_size(ctx));
+	ret = diskdump_pread(ctx, &subhdr, sizeof subhdr,
+			     get_page_size(ctx));
 	if (ret != KDUMP_OK)
 		return set_error(ctx, ret,
 				 "Cannot read subheader");
@@ -940,32 +1081,162 @@ open_common(kdump_ctx_t *ctx, void *hdr)
 	return ret;
 }
 
+#define FLATOFFS_ALLOC_INC	32
+
+/** Initialize flattened dump maps for one file.
+ * @param ctx   Dump file object.
+ * @returns     Error status.
+ *
+ * Read all flattened segment headers and initialize
+ * @p flatmap and @p flatoffs.
+ */
+static kdump_status
+init_flattened_file(kdump_ctx_t *ctx)
+{
+	struct disk_dump_priv *ddp = ctx->shared->fmtdata;
+	struct makedumpfile_data_header hdr;
+	addrxlat_range_t range;
+	int64_t pos, size;
+	unsigned segidx;
+	off_t flatpos;
+	kdump_status status;
+
+	ddp->flatmap = addrxlat_map_new();
+	if (!ddp->flatmap)
+		return set_error(ctx, KDUMP_ERR_SYSTEM,
+				 "Cannot allocate %s", "flattened map");
+
+	segidx = 0;
+	flatpos = MDF_HEADER_SIZE;
+	for (;;) {
+		status = fcache_pread(ctx->shared->fcache, &hdr, sizeof(hdr),
+				      flatpos);
+		if (status != KDUMP_OK)
+			return set_error(ctx, status,
+					 "Cannot read flattened header at %llu",
+					 (unsigned long long) flatpos);
+		pos = be64toh(hdr.offset);
+		if (pos == MDF_OFFSET_END_FLAG)
+			break;
+                if (pos < 0)
+			return set_error(ctx, KDUMP_ERR_CORRUPT,
+					 "Wrong flattened %s %"PRId64" at %llu",
+					 "offset", pos,
+					 (unsigned long long) flatpos);
+		size = be64toh(hdr.buf_size);
+		if (size <= 0)
+			return set_error(ctx, KDUMP_ERR_CORRUPT,
+					 "Wrong flattened %s %"PRId64" at %llu",
+					 "segment size", size,
+					 (unsigned long long) flatpos);
+
+		if ((segidx % FLATOFFS_ALLOC_INC) == 0) {
+			unsigned newlen = segidx + FLATOFFS_ALLOC_INC;
+			off_t *newbuf;
+
+			newbuf = realloc(ddp->flatoffs,
+					 sizeof(*ddp->flatoffs) * newlen);
+			if (!newbuf)
+				return set_error(ctx, KDUMP_ERR_SYSTEM,
+						 "Cannot allocate %s",
+						 "flattened offset array");
+			ddp->flatoffs = newbuf;
+		}
+		flatpos += sizeof(hdr);
+		ddp->flatoffs[segidx] = flatpos - pos;
+
+		range.endoff = size - 1;
+		range.meth = segidx;
+		if (addrxlat_map_set(ddp->flatmap, pos, &range) != ADDRXLAT_OK)
+			return set_error(ctx, KDUMP_ERR_SYSTEM,
+					 "Cannot allocate %s",
+					 "flattened map entry");
+
+		++segidx;
+		flatpos += size;
+	}
+	return KDUMP_OK;
+}
+
+/** Initialize flattened dump maps for all files.
+ * @param ctx  Dump file object.
+ * @returns    Error status.
+ *
+ * Initialize flattened dump maps for all files.
+ */
+static kdump_status
+init_flattened_maps(kdump_ctx_t *ctx)
+{
+	kdump_status status;
+
+	status = init_flattened_file(ctx);
+	if (status != KDUMP_OK)
+		return set_error(ctx, status,
+				 "Cannot rearrange file");
+
+	return KDUMP_OK;
+}
+
 static kdump_status
 diskdump_probe(kdump_ctx_t *ctx)
 {
+	static const char magic_flattened[MDF_SIG_LEN] = MDF_SIGNATURE;
 	static const char magic_diskdump[] =
 		{ 'D', 'I', 'S', 'K', 'D', 'U', 'M', 'P' };
 	static const char magic_kdump[] =
 		{ 'K', 'D', 'U', 'M', 'P', ' ', ' ', ' ' };
 
 	char hdr[sizeof(struct disk_dump_header_64)];
+	char desc[32];
 	kdump_status status;
 
 	status = fcache_pread(ctx->shared->fcache, hdr, sizeof hdr, 0);
 	if (status != KDUMP_OK)
 		return set_error(ctx, status, "Cannot read dump header");
 
+	if (!memcmp(hdr, magic_flattened, sizeof magic_flattened)) {
+		struct makedumpfile_header *flathdr =
+			(struct makedumpfile_header*) hdr;
+
+		if (be64toh(flathdr->type) != MDF_TYPE_FLAT_HEADER)
+			return set_error(ctx, KDUMP_ERR_NOTIMPL,
+					 "Unknown flattened %s: %" PRId64 "\n",
+					 "type", be64toh(flathdr->type));
+		if (be64toh(flathdr->version) != MDF_VERSION_FLAT_HEADER)
+			return set_error(ctx, KDUMP_ERR_NOTIMPL,
+					 "Unknown flattened %s: %" PRId64 "\n",
+					 "version", be64toh(flathdr->version));
+
+		status = init_private(ctx);
+		if (status != KDUMP_OK)
+			return status;
+
+		status = init_flattened_maps(ctx);
+		if (status != KDUMP_OK)
+			return status;
+
+		status = flattened_pread(ctx, &hdr, sizeof hdr, 0);
+		if (status != KDUMP_OK)
+			return set_error(ctx, status, "Cannot read dump header");
+		strcpy(desc, "Flattened ");
+	} else
+		desc[0] = '\0';
+
 	if (!memcmp(hdr, magic_diskdump, sizeof magic_diskdump))
-		set_file_description(ctx, "Diskdump");
+		strcat(desc, "Diskdump");
 	else if (!memcmp(hdr, magic_kdump, sizeof magic_kdump))
-		set_file_description(ctx, "Compressed KDUMP");
+		strcat(desc, "Compressed KDUMP");
 	else
 		return set_error(ctx, KDUMP_NOPROBE,
 				 "Unrecognized diskdump signature");
 
-	status = init_private(ctx);
-	if (status != KDUMP_OK)
-		return status;
+	set_file_description(ctx, desc);
+
+	if (!ctx->shared->fmtdata) {
+		status = init_private(ctx);
+		if (status != KDUMP_OK)
+			return status;
+	}
 
 	return open_common(ctx, hdr);
 }
@@ -989,6 +1260,10 @@ diskdump_cleanup(struct kdump_shared *sh
 			free(ddp->pfn_rgn);
 		if (ddp->cbuf_slot >= 0)
 			per_ctx_free(shared, ddp->cbuf_slot);
+		if (ddp->flatmap)
+			addrxlat_map_decref(ddp->flatmap);
+		if (ddp->flatoffs)
+			free(ddp->flatoffs);
 		free(ddp);
 		shared->fmtdata = NULL;
 	}
openSUSE Build Service is sponsored by