/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2022 Oxide Computer Company
 */

/*
 * This tool is used to try and figure out what the value of various static
 * buffers should be and if the current compile time defaults are correct. In
 * particular, this covers the various elfcap values and is used to drive how we
 * calculate the overall size definition.
 *
 * To calculate this, we assume the following:
 *
 *   o We are using the ELFCAP_FMT_PIPSPACE as that is the longest. We don't
 *     have access to the actual strings right now.
 *   o We are using the ELFCAP_STYLE_FULL variant of the name as that's the
 *     longest.
 *   o We are going to have leftover bits that we don't know (unless we have
 *     32-bits defined). This uses the 0x%x format and therefore is 10
 *     characters.
 *   o We check all architectures set of values and take the largest.
 *
 * While elfcap related information is in multiple places in the build, sgs and
 * libconv are the places that seem most intertwined. In particular, we believe
 * it's important that this program execute as part of make check and also get
 * rebuilt normally as part of a build. This also allows one to iterate in
 * cmd/sgs which is the most common place that you're working in when adding new
 * hardware capabilities. By making it a part of the cmd/sgs suite, that also
 * ensures that normal build logic always rebuilds this program with changes to
 * elfcap.[ch].
 */

#include <stdio.h>
#include <elfcap.h>
#include <sys/sysmacros.h>
#include <stdlib.h>

/*
 * The length of 0x%x.
 */
#define	ECS_UNKNOWN	10

typedef const elfcap_desc_t *(*elfcap_getdesc_f)(void);

typedef struct elfcap_getdesc {
	uint32_t eg_nents;
	elfcap_getdesc_f eg_func;
} elfcap_getdesc_t;

#define	NUM_PLATS 2 /* i386, sparc */

typedef struct elfcap_case {
	const char *ec_tag;
	size_t ec_header;
	elfcap_getdesc_t ec_descs[NUM_PLATS];
} elfcap_case_t;

const elfcap_case_t elfcaps[] = {
	{ "ELFCAP_SF1_BUFSIZE", ELFCAP_SF1_BUFSIZE, {
	    { ELFCAP_NUM_SF1, elfcap_getdesc_sf1 },
	    { 0, NULL }
	} },
	{ "ELFCAP_HW1_BUFSIZE", ELFCAP_HW1_BUFSIZE, {
	    { ELFCAP_NUM_HW1_386, elfcap_getdesc_hw1_386 },
	    { ELFCAP_NUM_HW1_SPARC, elfcap_getdesc_hw1_sparc }
	} },
	{ "ELFCAP_HW2_BUFSIZE", ELFCAP_HW2_BUFSIZE, {
	    { ELFCAP_NUM_HW2_386, elfcap_getdesc_hw2_386 },
	    { 0, NULL }
	} },
	{ "ELFCAP_HW3_BUFSIZE", ELFCAP_HW3_BUFSIZE, {
	    { ELFCAP_NUM_HW3_386, elfcap_getdesc_hw3_386 },
	    { 0, NULL }
	} },
};

static size_t
elfcap_calc_len(const elfcap_desc_t *desc, uint32_t nents, size_t space)
{
	size_t len = 0;

	for (uint32_t i = 0; i < nents; i++) {
		len += desc[i].c_full.s_len;
		if (i > 0) {
			len += space;
		}
	}

	if (nents < 32) {
		len += space + ECS_UNKNOWN;
	}

	/*
	 * Finally, add one for a terminator and we add an 8 character buffer in
	 * case we screwed up.
	 */
	len += 9;

	return (len);
}

static size_t
elfcap_max_len(const elfcap_case_t *ec, size_t slen)
{
	size_t max = 0;

	for (size_t i = 0; i < ARRAY_SIZE(ec->ec_descs); i++) {
		const elfcap_desc_t *desc;
		size_t len;

		if (ec->ec_descs[i].eg_func == NULL)
			continue;

		desc = ec->ec_descs[i].eg_func();
		len = elfcap_calc_len(desc, ec->ec_descs[i].eg_nents, slen);
		if (len > max)
			max = len;
	}

	return (max);
}

int
main(void)
{
	size_t slen;
	const elfcap_str_t *strs;
	int ret = EXIT_SUCCESS;

	strs = elfcap_getdesc_formats();
	slen = strs[ELFCAP_FMT_PIPSPACE].s_len;

	for (size_t i = 0; i < ARRAY_SIZE(elfcaps); i++) {
		size_t out = elfcap_max_len(&elfcaps[i], slen);

		if (out != elfcaps[i].ec_header) {
			(void) fprintf(stderr, "elfcap size for %s is not "
			    "expected value!\n\tCurrent value is %zu, should "
			    "be %zu\n", elfcaps[i].ec_tag, elfcaps[i].ec_header,
			    out);
			ret = EXIT_FAILURE;
		}
	}

	if (ret != EXIT_SUCCESS) {
		(void) fprintf(stderr, "please update $SRC/common/elfcap/"
		    "elfcap.h and $SRC/cmd/sgs/include/conv.h with the new "
		    "values reported above\n");
	}

	return (ret);
}
