/*
 * dmidecode-chassis-type-builtin — read chassis type from Linux sysfs SMBIOS only.
 *
 * Equivalent for vdev's use to:
 *   dmidecode -t chassis | grep Type | cut -d' ' -f2
 * (first line of that pipeline is the chassis type word, e.g. Tower, Desktop).
 *
 * Derived from dmidecode 3.6 (dmidecode.c: DMI type 3 / dmi_chassis_type, and
 * entry-point + table handling for sysfs). Linked with vendored util.c
 * (read_file, checksum) under vendor/.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 *   Copyright (C) 2000-2002 Alan Cox <alan@redhat.com>
 *   Copyright (C) 2002-2024 Jean Delvare <jdelvare@suse.de>
 *   Copyright (C) 2026 Aitor C.Z. (sysfs-only subset for vdev)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "types.h"
#include "util.h"

#define SYS_ENTRY "/sys/firmware/dmi/tables/smbios_entry_point"
#define SYS_TABLE "/sys/firmware/dmi/tables/DMI"

static const char * const out_of_spec = "<OUT OF SPEC>";

/* 7.4.1 Chassis Types — same table as dmidecode dmi_chassis_type() */
static const char *dmi_chassis_type_word(u8 code)
{
	static const char * const type[] = {
		"Other",
		"Unknown",
		"Desktop",
		"Low Profile Desktop",
		"Pizza Box",
		"Mini Tower",
		"Tower",
		"Portable",
		"Laptop",
		"Notebook",
		"Hand Held",
		"Docking Station",
		"All In One",
		"Sub Notebook",
		"Space-saving",
		"Lunch Box",
		"Main Server Chassis",
		"Expansion Chassis",
		"Sub Chassis",
		"Bus Expansion Chassis",
		"Peripheral Chassis",
		"RAID Chassis",
		"Rack Mount Chassis",
		"Sealed-case PC",
		"Multi-system",
		"CompactPCI",
		"AdvancedTCA",
		"Blade",
		"Blade Enclosing",
		"Tablet",
		"Convertible",
		"Detachable",
		"IoT Gateway",
		"Embedded PC",
		"Mini PC",
		"Stick PC"
	};

	code &= 0x7F;
	if (code >= 0x01 && code <= 0x24)
		return type[code - 0x01];
	return out_of_spec;
}

/*
 * Walk raw DMI table; print first type-3 chassis type and return 0, or 1 if none.
 * stop_at_eot: SMBIOS 3 — stop after structure type 127.
 */
static int walk_chassis(const u8 *table, u32 len, u16 num, int stop_at_eot)
{
	u32 i = 0;
	const u8 *data = table;

	while ((!num || i < num) && data + 4 <= table + len) {
		u8 type = data[0];
		u8 hlen = data[1];
		const u8 *next;

		if (hlen < 4)
			return 1;
		i++;

		next = data + hlen;
		while ((size_t)(next - table + 1) < len && (next[0] != 0 || next[1] != 0))
			next++;
		next += 2;
		if ((size_t)(next - table) > len)
			return 1;

		if (type == 3 && hlen >= 9) {
			puts(dmi_chassis_type_word(data[0x05]));
			return 0;
		}
		data = next;
		if (type == 127 && stop_at_eot)
			break;
	}
	return 1;
}

static int smbios2_sysfs(const u8 *ep, size_t ep_len)
{
	u16 num;
	u32 len;
	size_t read_len;
	u8 *tbl;
	int rc;

	if (ep[0x05] > ep_len)
		return 1;
	if (ep[0x05] < 0x1E
	    || !checksum(ep, ep[0x05])
	    || memcmp(ep + 0x10, "_DMI_", 5) != 0
	    || !checksum(ep + 0x10, 0x0F))
		return 1;

	len = WORD(ep + 0x16);
	num = WORD(ep + 0x1C);
	read_len = len;
	tbl = read_file(0, &read_len, SYS_TABLE);
	if (!tbl)
		return 1;
	rc = walk_chassis(tbl, (u32)read_len, num, 0);
	free(tbl);
	return rc;
}

static int smbios3_sysfs(const u8 *ep, size_t ep_len)
{
	u32 len;
	size_t read_len;
	u8 *tbl;
	int rc;

	if (ep[0x06] > ep_len)
		return 1;
	if (ep[0x06] < 0x18 || !checksum(ep, ep[0x06]))
		return 1;

	len = DWORD(ep + 0x0C);
	read_len = len;
	tbl = read_file(0, &read_len, SYS_TABLE);
	if (!tbl)
		return 1;
	rc = walk_chassis(tbl, (u32)read_len, 0, 1);
	free(tbl);
	return rc;
}

static int legacy_sysfs(const u8 *ep)
{
	u16 num;
	u32 len;
	size_t read_len;
	u8 *tbl;
	int rc;

	if (!checksum(ep, 0x0F))
		return 1;

	len = WORD(ep + 0x06);
	num = WORD(ep + 0x0C);
	read_len = len;
	tbl = read_file(0, &read_len, SYS_TABLE);
	if (!tbl)
		return 1;
	rc = walk_chassis(tbl, (u32)read_len, num, 0);
	free(tbl);
	return rc;
}

int main(void)
{
	size_t z = 256;
	u8 *buf;
	int ret = 1;

	buf = read_file(0, &z, SYS_ENTRY);
	if (!buf)
		return 1;

	if (z >= 24 && memcmp(buf, "_SM3_", 5) == 0) {
		if (smbios3_sysfs(buf, z) == 0)
			ret = 0;
	} else if (z >= 31 && memcmp(buf, "_SM_", 4) == 0) {
		if (smbios2_sysfs(buf, z) == 0)
			ret = 0;
	} else if (z >= 15 && memcmp(buf, "_DMI_", 5) == 0) {
		if (legacy_sysfs(buf) == 0)
			ret = 0;
	}

	free(buf);
	return ret;
}
