#line 1 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later

static struct drgn_error *parse_vmcoreinfo_u64(const char *value,
					       const char *newline, int base,
					       uint64_t *ret)
{
	errno = 0;
	char *end;
	*ret = strtoull(value, &end, base);
	if (errno == ERANGE) {
		return drgn_error_create(DRGN_ERROR_OVERFLOW,
					 "number in VMCOREINFO is too large");
	} else if (errno || end == value || end != newline) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "number in VMCOREINFO is invalid");
	}
	return NULL;
}

// Linux kernel commit 905415ff3ffb ("lib/buildid: harden build ID parsing
// logic") (in v6.12) contains a bug that results in a garbage build ID in
// VMCOREINFO. It was fixed in the same patch series in commits de3ec364c3c3
// ("lib/buildid: add single folio-based file reader abstraction") and
// d4deb8242341 ("lib/buildid: take into account e_phoff when fetching program
// headers"). However, the broken commit was backported to several stable
// kernels. Some branches were fixed by "lib/buildid: Fix build ID parsing
// logic", but a couple reached their end-of-life while broken. See
// https://lore.kernel.org/all/20241104175256.2327164-1-jolsa@kernel.org/.
//
// The very sad workaround is to ignore the build ID based on a version check.
static void ignore_broken_vmcoreinfo_build_id(struct drgn_program *prog)
{
	char *p = (char *)prog->vmcoreinfo.osrelease;
	long major = strtol(p, &p, 10), minor = 0, patch = 0;
	if (*p == '.') {
		minor = strtol(p + 1, &p, 10);
		if (*p == '.')
			patch = strtol(p + 1, NULL, 10);
	}
	if ((major == 6 && minor == 11 && patch >= 3 && patch < 10)
	    || (major == 6 && minor == 10 && patch >= 14)
	    || (major == 6 && minor == 6 && patch >= 55 && patch < 63)
	    || (major == 6 && minor == 1 && patch >= 113 && patch < 119)
	    || (major == 5 && minor == 15 && patch >= 168))
		prog->vmcoreinfo.build_id_len = 0;
}

struct drgn_error *drgn_program_parse_vmcoreinfo(struct drgn_program *prog,
						 const char *desc,
						 size_t descsz)
{
	struct drgn_error *err;

	prog->vmcoreinfo.raw_size = descsz;
	prog->vmcoreinfo.raw = memdup(desc, descsz);
	if (!prog->vmcoreinfo.raw)
		return &drgn_enomem;
	for (const char *line = desc, *end = &desc[descsz], *newline;
	     (newline = memchr(line, '\n', end - line));
	     line = newline + 1) {
		const char *equals = memchr(line, '=', newline - line);
		if (!equals)
			continue;

		const char *value = equals + 1;
#line 69 "drgn_program_parse_vmcoreinfo.inc"
		/* Generated by libdrgn/build-aux/gen_strswitch.py. */
		switch (1) {
		default: {
			#define memswitch0_args(ptr, len) \
				const void *memswitch0_ptr = (ptr); \
				const char *memswitch0_str = memswitch0_ptr; \
				size_t memswitch0_len = (len);
			memswitch0_args(
#line 67 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
		            line, equals - line
#line 80 "drgn_program_parse_vmcoreinfo.inc"
			)
			#undef memswitch0_args
			if (memswitch0_len == 8) {
				if (memswitch0_str[0] == 'B') {
					if (memcmp(&memswitch0_str[1], "UILD-ID", sizeof("UILD-ID") - 1) == 0) {
#line 69 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
		{
			size_t build_id_len = (newline - value) / 2;
			if (build_id_len > sizeof(prog->vmcoreinfo.build_id)) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "BUILD-ID in VMCOREINFO is too long");
			}
			if (!unhexlify(value, newline - value,
				       &prog->vmcoreinfo.build_id)) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "couldn't parse BUILD-ID in VMCOREINFO");
			}
			prog->vmcoreinfo.build_id_len = build_id_len;
			break;
		}
#line 101 "drgn_program_parse_vmcoreinfo.inc"
						goto memswitch0_case1;
					}
				} else if (memswitch0_str[0] == 'P') {
					if (memcmp(&memswitch0_str[1], "AGESIZE", sizeof("AGESIZE") - 1) == 0) {
						memswitch0_case3:
#line 97 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.page_size);
			if (err)
				return err;
			break;
#line 113 "drgn_program_parse_vmcoreinfo.inc"
						goto memswitch0_case4;
					}
				}
			} else if (memswitch0_len == 9) {
				if (memswitch0_str[0] == 'C') {
					if (memcmp(&memswitch0_str[1], "RASHTIME", sizeof("RASHTIME") - 1) == 0) {
						memswitch0_case1:
#line 84 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			prog->vmcoreinfo.have_crashtime = true;
			break;
#line 124 "drgn_program_parse_vmcoreinfo.inc"
						goto memswitch0_case2;
					}
				} else if (memswitch0_str[0] == 'O') {
					if (memcmp(&memswitch0_str[1], "SRELEASE", sizeof("SRELEASE") - 1) == 0) {
						memswitch0_case2:
#line 87 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			if ((size_t)(newline - value) >=
			    sizeof(prog->vmcoreinfo.osrelease)) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "OSRELEASE in VMCOREINFO is too long");
			}
			memcpy(prog->vmcoreinfo.osrelease, value,
			       newline - value);
			prog->vmcoreinfo.osrelease[newline - value] = '\0';
			break;
#line 140 "drgn_program_parse_vmcoreinfo.inc"
						goto memswitch0_case3;
					}
				}
			} else if (memswitch0_len == 12) {
				if (memcmp(&memswitch0_str[0], "KERNELOFFSET", sizeof("KERNELOFFSET") - 1) == 0) {
					memswitch0_case4:
#line 103 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			err = parse_vmcoreinfo_u64(value, newline, 16,
						   &prog->vmcoreinfo.kaslr_offset);
			if (err)
				return err;
			break;
#line 153 "drgn_program_parse_vmcoreinfo.inc"
					goto memswitch0_case5;
				}
			} else if (memswitch0_len == 15) {
				if (memswitch0_str[0] == 'C') {
					if (memcmp(&memswitch0_str[1], "ONFIG_ARM_LPAE", sizeof("ONFIG_ARM_LPAE") - 1) == 0) {
						memswitch0_case15:
#line 191 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			prog->vmcoreinfo.arm_lpae = value[0] == 'y';
			break;
#line 163 "drgn_program_parse_vmcoreinfo.inc"
					}
				} else if (memswitch0_str[0] == 'N') {
					if (memcmp(&memswitch0_str[1], "UMBER(VA_BITS)", sizeof("UMBER(VA_BITS)") - 1) == 0) {
						memswitch0_case13:
#line 179 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.va_bits);
			if (err)
				return err;
			break;
#line 174 "drgn_program_parse_vmcoreinfo.inc"
						goto memswitch0_case14;
					}
				}
			} else if (memswitch0_len == 17) {
				if (memcmp(&memswitch0_str[0], "NUMBER(phys_base)", sizeof("NUMBER(phys_base)") - 1) == 0) {
					memswitch0_case11:
#line 164 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
		{
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.phys_base);
			if (err)
				return err;
			prog->vmcoreinfo.have_phys_base = true;
			break;
		}
#line 190 "drgn_program_parse_vmcoreinfo.inc"
					goto memswitch0_case12;
				}
			} else if (memswitch0_len == 19) {
				if (memcmp(&memswitch0_str[0], "LENGTH(mem_section)", sizeof("LENGTH(mem_section)") - 1) == 0) {
					memswitch0_case6:
#line 115 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.mem_section_length);
			if (err)
				return err;
			break;
#line 202 "drgn_program_parse_vmcoreinfo.inc"
					goto memswitch0_case7;
				}
			} else if (memswitch0_len == 20) {
				if (memcmp(&memswitch0_str[0], "NUMBER(TCR_EL1_T1SZ)", sizeof("NUMBER(TCR_EL1_T1SZ)") - 1) == 0) {
					memswitch0_case14:
#line 185 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.tcr_el1_t1sz);
			if (err)
				return err;
			break;
#line 214 "drgn_program_parse_vmcoreinfo.inc"
					goto memswitch0_case15;
				}
			} else if (memswitch0_len == 21) {
				if (memcmp(&memswitch0_str[0], "NUMBER(KERNELPACMASK)", sizeof("NUMBER(KERNELPACMASK)") - 1) == 0) {
					memswitch0_case12:
#line 173 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			err = parse_vmcoreinfo_u64(value, newline, 16,
						   &prog->aarch64_insn_pac_mask);
			if (err)
				return err;
			break;
#line 226 "drgn_program_parse_vmcoreinfo.inc"
					goto memswitch0_case13;
				}
			} else if (memswitch0_len == 22) {
				if (memswitch0_str[0] == 'N') {
					if (memcmp(&memswitch0_str[1], "UMBER(kimage_voffset)", sizeof("UMBER(kimage_voffset)") - 1) == 0) {
						memswitch0_case10:
#line 156 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
		{
			err = parse_vmcoreinfo_u64(value, newline, 0,
						   &prog->vmcoreinfo.kimage_voffset);
			if (err)
				return err;
			break;
		}
#line 241 "drgn_program_parse_vmcoreinfo.inc"
						goto memswitch0_case11;
					}
				} else if (memswitch0_str[0] == 'S') {
					if (memcmp(&memswitch0_str[1], "YMBOL(swapper_pg_dir)", sizeof("YMBOL(swapper_pg_dir)") - 1) == 0) {
						memswitch0_case5:
#line 109 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
			err = parse_vmcoreinfo_u64(value, newline, 16,
						   &prog->vmcoreinfo.swapper_pg_dir);
			if (err)
				return err;
			break;
#line 253 "drgn_program_parse_vmcoreinfo.inc"
						goto memswitch0_case6;
					}
				}
			} else if (memswitch0_len == 24) {
				if (memcmp(&memswitch0_str[0], "NUMBER(MAX_PHYSMEM_BITS)", sizeof("NUMBER(MAX_PHYSMEM_BITS)") - 1) == 0) {
					memswitch0_case8:
#line 134 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
		{
			uint64_t tmp;
			err = parse_vmcoreinfo_u64(value, newline, 0, &tmp);
			if (err)
				return err;
			if (tmp == 0 || tmp > 64) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "MAX_PHYSMEM_BITS in VMCOREINFO is invalid");
			}
			prog->vmcoreinfo.max_physmem_bits = tmp;
			break;
		}
#line 273 "drgn_program_parse_vmcoreinfo.inc"
					goto memswitch0_case9;
				}
			} else if (memswitch0_len == 25) {
				if (memcmp(&memswitch0_str[0], "NUMBER(SECTION_SIZE_BITS)", sizeof("NUMBER(SECTION_SIZE_BITS)") - 1) == 0) {
					memswitch0_case7:
#line 121 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
		{
			uint64_t tmp;
			err = parse_vmcoreinfo_u64(value, newline, 0, &tmp);
			if (err)
				return err;
			if (tmp == 0 || tmp > 64) {
				return drgn_error_create(DRGN_ERROR_OTHER,
							 "SECTION_SIZE_BITS in VMCOREINFO is invalid");
			}
			prog->vmcoreinfo.section_size_bits = tmp;
			break;
		}
#line 292 "drgn_program_parse_vmcoreinfo.inc"
					goto memswitch0_case8;
				}
			} else if (memswitch0_len == 26) {
				if (memcmp(&memswitch0_str[0], "NUMBER(pgtable_l5_enabled)", sizeof("NUMBER(pgtable_l5_enabled)") - 1) == 0) {
					memswitch0_case9:
#line 147 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
		{
			uint64_t tmp;
			err = parse_vmcoreinfo_u64(value, newline, 0, &tmp);
			if (err)
				return err;
			prog->vmcoreinfo.pgtable_l5_enabled = tmp;
			break;
		}
#line 307 "drgn_program_parse_vmcoreinfo.inc"
					goto memswitch0_case10;
				}
			}
		}
		}
#line 194 "../../libdrgn/drgn_program_parse_vmcoreinfo.inc.strswitch"
	}
	if (!prog->vmcoreinfo.osrelease[0]) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "VMCOREINFO does not contain valid OSRELEASE");
	}
	ignore_broken_vmcoreinfo_build_id(prog);
	if (!is_power_of_two(prog->vmcoreinfo.page_size)) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "VMCOREINFO does not contain valid PAGESIZE");
	}
	prog->vmcoreinfo.page_shift = ctz(prog->vmcoreinfo.page_size);
	if (!prog->vmcoreinfo.swapper_pg_dir) {
		return drgn_error_create(DRGN_ERROR_OTHER,
					 "VMCOREINFO does not contain valid swapper_pg_dir");
	}
	// Everything else is optional.
	return NULL;
}
