/* SPDX-License-Identifier: GPL-2.0-only */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef __MINGW32__
#include <sys/mman.h>
#endif
#include "common.h"
#include "opts.h"
#include "lbtable.h"
#include "layout.h"
#include "accessors/layout-text.h"
#include "input_file.h"
#include "cmos_ops.h"
#include "cmos_lowlevel.h"
#include "reg_expr.h"
#include "hexdump.h"
#include "cbfs.h"
#ifdef __MINGW32__
#include <windows.h>
#endif

typedef void (*op_fn_t) (void);

static void op_show_version(void);
static void op_show_usage(void);
static void op_lbtable_show_info(void);
static void op_lbtable_dump(void);
static void op_show_param_values(void);
static void op_cmos_show_one_param(void);
static void op_cmos_show_all_params(void);
static void op_cmos_set_one_param(void);
static void op_cmos_set_params_stdin(void);
static void op_cmos_set_params_file(void);
static void op_cmos_checksum(void);
static void op_show_layout(void);
static void op_write_cmos_dump(void);
static void op_read_cmos_dump(void);
static void op_show_cmos_hex_dump(void);
static void op_show_cmos_dumpfile(void);
static void op_write_cmos_layout_bin(void);
static void op_write_cmos_layout_header(void);
static int list_one_param(const char name[], int show_name);
static int list_all_params(void);
static void list_param_enums(const char name[]);
static void set_one_param(const char name[], const char value[]);
static void set_params(FILE * f);
static void parse_assignment(char arg[], const char **name, const char **value);
static int list_cmos_entry(const cmos_entry_t * e, int show_name);
static uint16_t convert_checksum_value(const char value[]);

static const op_fn_t op_fns[] = { op_show_version,
	op_show_usage,
	op_lbtable_show_info,
	op_lbtable_dump,
	op_show_param_values,
	op_cmos_show_one_param,
	op_cmos_show_all_params,
	op_cmos_set_one_param,
	op_cmos_set_params_stdin,
	op_cmos_set_params_file,
	op_cmos_checksum,
	op_show_layout,
	op_write_cmos_dump,
	op_read_cmos_dump,
	op_show_cmos_hex_dump,
	op_show_cmos_dumpfile,
	op_write_cmos_layout_bin,
	op_write_cmos_layout_header
};

static void op_write_cmos_layout_bin(void)
{
	get_layout_from_file();
	write_cmos_output_bin(nvramtool_op.param);
}

static void op_write_cmos_layout_header(void)
{
	get_layout_from_file();
	write_cmos_layout_header(nvramtool_op.param);
}

static const hexdump_format_t cmos_dump_format =
    { 16, 2, "", " | ", " ", " | ", '.' };

/****************************************************************************
 * main
 ****************************************************************************/
int main(int argc, char *argv[])
{
	void *cmos_default = NULL;
	cmos_layout_get_fn_t fn = get_layout_from_cmos_table;

	parse_nvramtool_args(argc, argv);

	/* If we should operate on a CBFS file default to reading the layout
	 * and CMOS contents from it. */
	if (nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CBFS_FILE].found) {
		open_cbfs(nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CBFS_FILE].param);
		if (!nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_FILE].found) {
			cmos_default = cbfs_find_file("cmos.default", CBFS_COMPONENT_CMOS_DEFAULT, NULL);
			if (cmos_default == NULL) {
				fprintf(stderr, "Need a cmos.default in the CBFS image or separate CMOS file (-D).\n");
				exit(1);
			}
		}
		fn = get_layout_from_cbfs_file;
	}

	/* If the user wants to use a specific layout file or explicitly use
	 * the coreboot option table allow him to override previous settings. */
	if (nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_LAYOUT_FILE].found) {
		set_layout_filename(nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_LAYOUT_FILE].param);
		fn = get_layout_from_file;
	} else if (nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_OPT_TABLE].found) {
		fn = get_layout_from_cmos_table;
	}

	/* Allow the user to use a file for the CMOS contents, possibly
	 * overriding a previously opened "cmos.default" file from the CBFS. */
	if (nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_FILE].found) {
		struct stat fd_stat;
		int fd;

		if ((fd = open(nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_FILE].param, O_RDWR | O_CREAT, 0666)) < 0) {
			fprintf(stderr, "Couldn't open '%s'\n", nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_FILE].param);
			exit(1);
		}

		if (fstat(fd, &fd_stat) == -1) {
			fprintf(stderr, "Couldn't stat '%s'\n", nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_FILE].param);
			exit(1);
		}

		if (fd_stat.st_size < CMOS_SIZE) {
			if ((lseek(fd, CMOS_SIZE - 1, SEEK_SET) == -1) ||
			    (write(fd, "\0", 1) != 1)) {
				fprintf(stderr, "Unable to extended '%s' to its full size.\n",
						nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_FILE].param);
				exit(1);
			}
#ifndef __MINGW32__
			fsync(fd);
#else
			FlushFileBuffers ((HANDLE) _get_osfhandle (fd));
#endif
		}

		cmos_default = mmap(NULL, CMOS_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
		if (cmos_default == MAP_FAILED) {
			fprintf(stderr, "Couldn't map '%s'\n", nvramtool_op_modifiers[NVRAMTOOL_MOD_USE_CMOS_FILE].param);
			exit(1);
		}
	}

	/* Switch to memory based CMOS access. */
	if (cmos_default) {
		select_hal(HAL_MEMORY, cmos_default);
	}

	register_cmos_layout_get_fn(fn);
	op_fns[nvramtool_op.op]();

	return 0;
}

/****************************************************************************
 * op_show_version
 *
 * -v
 *
 * Show version information for this program.
 ****************************************************************************/
static void op_show_version(void)
{
	printf("This is %s version %s.\n", prog_name, prog_version);
}

/****************************************************************************
 * op_show_usage
 *
 * -h
 *
 * Show a usage message for this program.
 ****************************************************************************/
static void op_show_usage(void)
{
	usage(stdout);
}

/****************************************************************************
 * op_lbtable_show_info
 *
 * -l [ARG]
 *
 * If ARG is present, show coreboot table information specified by ARG.
 * Else show all possible values for ARG.
 ****************************************************************************/
static void op_lbtable_show_info(void)
{
	if (nvramtool_op.param == NULL)
		list_lbtable_choices();
	else {
		get_lbtable();
		list_lbtable_item(nvramtool_op.param);
	}
}

/****************************************************************************
 * op_lbtable_dump
 *
 * -d
 *
 * Do low-level dump of coreboot table.
 ****************************************************************************/
static void op_lbtable_dump(void)
{
	get_lbtable();
	dump_lbtable();
}

/****************************************************************************
 * op_show_param_values
 *
 * -e NAME option
 *
 * Show all possible values for parameter NAME.
 ****************************************************************************/
static void op_show_param_values(void)
{
	get_cmos_layout();
	list_param_enums(nvramtool_op.param);
}

/****************************************************************************
 * op_cmos_show_one_param
 *
 * [-n] -r NAME
 *
 * Show parameter NAME.  If -n is specified, show value only.  Else show name
 * and value.
 ****************************************************************************/
static void op_cmos_show_one_param(void)
{
	int result;

	get_cmos_layout();
	result = list_one_param(nvramtool_op.param,
				!nvramtool_op_modifiers
				[NVRAMTOOL_MOD_SHOW_VALUE_ONLY].found);
	cmos_checksum_verify();

	if (result)
		exit(1);
}

/****************************************************************************
 * op_cmos_show_all_params
 *
 * -a
 *
 * Show names and values for all parameters.
 ****************************************************************************/
static void op_cmos_show_all_params(void)
{
	int result;

	get_cmos_layout();
	result = list_all_params();
	cmos_checksum_verify();

	if (result)
		exit(1);
}

/****************************************************************************
 * op_cmos_set_one_param
 *
 * -w NAME=VALUE
 *
 * Set parameter NAME to VALUE.
 ****************************************************************************/
static void op_cmos_set_one_param(void)
{
	const char *name, *value;

	get_cmos_layout();

	/* Separate 'NAME=VALUE' syntax into two strings representing NAME and
	 * VALUE.
	 */
	parse_assignment(nvramtool_op.param, &name, &value);

	set_one_param(name, value);
}

/****************************************************************************
 * op_cmos_set_params_stdin
 *
 * -i
 *
 * Set parameters according to standard input.
 ****************************************************************************/
static void op_cmos_set_params_stdin(void)
{
	get_cmos_layout();
	set_params(stdin);
}

/****************************************************************************
 * op_cmos_set_params_file
 *
 * -p INPUT_FILE
 *
 * Set parameters according to INPUT_FILE.
 ****************************************************************************/
static void op_cmos_set_params_file(void)
{
	FILE *f;

	if ((f = fopen(nvramtool_op.param, "r")) == NULL) {
		fprintf(stderr, "%s: Can not open file %s for reading: %s\n",
			prog_name, nvramtool_op.param, strerror(errno));
		exit(1);
	}

	get_cmos_layout();
	set_params(f);
	fclose(f);
}

/****************************************************************************
 * op_cmos_checksum
 *
 * -c [VALUE]
 *
 * If VALUE is present, set coreboot CMOS checksum to VALUE.  Else show
 * checksum value.
 ****************************************************************************/
static void op_cmos_checksum(void)
{
	uint16_t checksum;

	get_cmos_layout();

	if (nvramtool_op.param == NULL) {
		set_iopl(3);
		checksum = cmos_checksum_read();
		set_iopl(0);
		printf("0x%x\n", checksum);
	} else {
		checksum = convert_checksum_value(nvramtool_op.param);
		set_iopl(3);
		cmos_checksum_write(checksum);
		set_iopl(0);
	}
}

/****************************************************************************
 * op_show_layout
 *
 * -Y
 *
 * Write CMOS layout information to standard output.
 ****************************************************************************/
static void op_show_layout(void)
{
	get_cmos_layout();
	write_cmos_layout(stdout);
}

/****************************************************************************
 * op_write_cmos_dump
 *
 * -b OUTPUT_FILE
 *
 * Write the contents of CMOS memory to a binary file.
 ****************************************************************************/
static void op_write_cmos_dump(void)
{
	unsigned char data[CMOS_SIZE];
	FILE *f;

	if ((f = fopen(nvramtool_op.param, "wb")) == NULL) {
		fprintf(stderr, "%s: Can not open file %s for writing: %s\n",
			prog_name, nvramtool_op.param, strerror(errno));
		exit(1);
	}

	set_iopl(3);
	cmos_read_all(data);
	set_iopl(0);

	if (fwrite(data, 1, CMOS_SIZE, f) != CMOS_SIZE) {
		fprintf(stderr, "%s: Error writing CMOS data to file %s: %s\n",
			prog_name, nvramtool_op.param, strerror(errno));
		exit(1);
	}

	fclose(f);
}

/****************************************************************************
 * op_read_cmos_dump
 *
 * -B INPUT_FILE
 *
 * Read binary data from a file and write the data to CMOS memory.
 ****************************************************************************/
static void op_read_cmos_dump(void)
{
	unsigned char data[CMOS_SIZE];
	size_t nr_bytes;
	FILE *f;

	if ((f = fopen(nvramtool_op.param, "rb")) == NULL) {
		fprintf(stderr, "%s: Can not open file %s for reading: %s\n",
			prog_name, nvramtool_op.param, strerror(errno));
		exit(1);
	}

	if ((nr_bytes = fread(data, 1, CMOS_SIZE, f)) != CMOS_SIZE) {
		fprintf(stderr,
			"%s: Error: Only able to read %d bytes of CMOS data "
			"from file %s.  CMOS data is unchanged.\n", prog_name,
			(int)nr_bytes, nvramtool_op.param);
		exit(1);
	}

	fclose(f);
	set_iopl(3);
	cmos_write_all(data);
	set_iopl(0);
}

/****************************************************************************
 * op_show_cmos_hex_dump
 *
 * -x
 *
 * Write a hex dump of CMOS memory to standard output.
 ****************************************************************************/
static void op_show_cmos_hex_dump(void)
{
	unsigned char data[CMOS_SIZE];

	set_iopl(3);
	cmos_read_all(data);
	set_iopl(0);
	hexdump(data, CMOS_SIZE, 0, stdout, &cmos_dump_format);
}

/****************************************************************************
 * op_show_cmos_dumpfile
 *
 * -X DUMP_FILE
 *
 * Read binary data from a file (presumably a CMOS dump file) and display a
 * hex dump of the CMOS data from the file.
 ****************************************************************************/
static void op_show_cmos_dumpfile(void)
{
	unsigned char data[CMOS_SIZE];
	size_t nr_bytes;
	FILE *f;

	if ((f = fopen(nvramtool_op.param, "r")) == NULL) {
		fprintf(stderr, "%s: Can not open file %s for reading: %s\n",
			prog_name, nvramtool_op.param, strerror(errno));
		exit(1);
	}

	nr_bytes = fread(data, 1, CMOS_SIZE, f);
	fclose(f);
	hexdump(data, nr_bytes, 0, stdout, &cmos_dump_format);
}

/****************************************************************************
 * list_one_param
 *
 * Attempt to list one CMOS parameter given by 'name'.  'show_name' is a
 * boolean value indicating whether the parameter name should be displayed
 * along with its value.  Return 1 if error was encountered.  Else return OK.
 ****************************************************************************/
static int list_one_param(const char name[], int show_name)
{
	const cmos_entry_t *e;

	if (is_checksum_name(name) || ((e = find_cmos_entry(name)) == NULL)) {
		fprintf(stderr, "%s: CMOS parameter %s not found.\n", prog_name,
			name);
		exit(1);
	}

	if (e->config == CMOS_ENTRY_RESERVED) {
		fprintf(stderr, "%s: Parameter %s is reserved.\n", prog_name,
			name);
		exit(1);
	}

	return (list_cmos_entry(e, show_name) != 0);
}

/****************************************************************************
 * list_all_params
 *
 * Attempt to list all CMOS parameters.  Return 1 if error was encountered.
 * Else return OK.
 ****************************************************************************/
static int list_all_params(void)
{
	const cmos_entry_t *e;
	int result;

	result = OK;

	for (e = first_cmos_entry(); e != NULL; e = next_cmos_entry(e)) {
		if ((e->config == CMOS_ENTRY_RESERVED)
		    || is_checksum_name(e->name))
			continue;

		if (list_cmos_entry(e, TRUE))
			result = 1;
	}

	return result;
}

/****************************************************************************
 * list_param_enums
 *
 * List all possible values for CMOS parameter given by 'name'.
 ****************************************************************************/
static void list_param_enums(const char name[])
{
	const cmos_entry_t *e;
	const cmos_enum_t *p;

	if (is_checksum_name(name) || (e = find_cmos_entry(name)) == NULL) {
		fprintf(stderr, "%s: CMOS parameter %s not found.\n", prog_name,
			name);
		exit(1);
	}

	switch (e->config) {
	case CMOS_ENTRY_ENUM:
		for (p = first_cmos_enum_id(e->config_id);
		     p != NULL; p = next_cmos_enum_id(p))
			printf("%s\n", p->text);

		break;

	case CMOS_ENTRY_HEX:
		printf("Parameter %s requires a %u-bit unsigned integer.\n",
		       name, e->length);
		break;

	case CMOS_ENTRY_STRING:
		printf("Parameter %s requires a %u-byte string.\n", name,
		       e->length / 8);
		break;

	case CMOS_ENTRY_RESERVED:
		printf("Parameter %s is reserved.\n", name);
		break;

	default:
		BUG();
	}
}

/****************************************************************************
 * set_one_param
 *
 * Set the CMOS parameter given by 'name' to 'value'.  The 'name' parameter
 * is case-sensitive.  If we are setting an enum parameter, then 'value' is
 * interpreted as a case-sensitive string that must match the option name
 * exactly.  If we are setting a 'hex' parameter, then 'value' is treated as
 * a string representation of an unsigned integer that may be specified in
 * decimal, hex, or octal.
 ****************************************************************************/
static void set_one_param(const char name[], const char value[])
{
	const cmos_entry_t *e;
	unsigned long long n;

	if (is_checksum_name(name) || (e = find_cmos_entry(name)) == NULL) {
		fprintf(stderr, "%s: CMOS parameter %s not found.\n", prog_name,
			name);
		exit(1);
	}

	switch (prepare_cmos_write(e, value, &n)) {
	case OK:
		break;

	case CMOS_OP_BAD_ENUM_VALUE:
		fprintf(stderr, "%s: Bad value for parameter %s.", prog_name,
			name);
		goto fail;

	case CMOS_OP_NEGATIVE_INT:
		fprintf(stderr,
			"%s: This program does not support assignment of negative "
			"numbers to coreboot parameters.", prog_name);
		goto fail;

	case CMOS_OP_INVALID_INT:
		fprintf(stderr, "%s: %s is not a valid integer.", prog_name,
			value);
		goto fail;

	case CMOS_OP_RESERVED:
		fprintf(stderr,
			"%s: Can not modify reserved coreboot parameter %s.",
			prog_name, name);
		goto fail;

	case CMOS_OP_VALUE_TOO_WIDE:
		fprintf(stderr,
			"%s: Can not write value %s to CMOS parameter %s that is "
			"only %d bits wide.", prog_name, value, name,
			e->length);
		goto fail;

	case CMOS_OP_NO_MATCHING_ENUM:
		fprintf(stderr,
			"%s: coreboot parameter %s has no matching enums.",
			prog_name, name);
		goto fail;

	case CMOS_AREA_OUT_OF_RANGE:
		fprintf(stderr,
			"%s: The CMOS area specified by the layout info for "
			"coreboot parameter %s is out of range.", prog_name,
			name);
		goto fail;

	case CMOS_AREA_OVERLAPS_RTC:
		fprintf(stderr,
			"%s: The CMOS area specified by the layout info for "
			"coreboot parameter %s overlaps the realtime clock area.",
			prog_name, name);
		goto fail;

	case CMOS_AREA_TOO_WIDE:
		fprintf(stderr,
			"%s: The CMOS area specified by the layout info for "
			"coreboot parameter %s is too wide.", prog_name, name);
		goto fail;

	default:
		fprintf(stderr,
			"%s: Unknown error encountered while attempting to modify "
			"coreboot parameter %s.", prog_name, name);
		goto fail;
	}

	/* write the value to nonvolatile RAM */
	set_iopl(3);
	cmos_write(e, n);
	cmos_checksum_write(cmos_checksum_compute());
	set_iopl(0);
	return;

      fail:
	fprintf(stderr, "  CMOS write not performed.\n");
	exit(1);
}

/****************************************************************************
 * set_params
 *
 * Set coreboot parameters according to the contents of file 'f'.
 ****************************************************************************/
static void set_params(FILE * f)
{				/* First process the input file.  Then perform writes only if there were
				 * no problems processing the input.  Either all values will be written
				 * successfully or no values will be written.
				 */
	do_cmos_writes(process_input_file(f));
}

/****************************************************************************
 * parse_assignment
 *
 * Parse the string 'arg' (which supposedly represents an assignment) into a
 * NAME and a VALUE.  If 'arg' does not conform to the proper assignment
 * syntax, exit with a usage message.  Otherwise, on return, 'arg' is broken
 * into substrings representing NAME and VALUE, and *name and *value are set
 * to point to these two substrings.
 ****************************************************************************/
static void parse_assignment(char arg[], const char **name, const char **value)
{
	static const size_t N_MATCHES = 4;
	regmatch_t match[N_MATCHES];
	regex_t assignment;

	compile_reg_expr(REG_EXTENDED | REG_NEWLINE, assignment_regex, &assignment);

	/* Does 'arg' conform to proper assignment syntax?  If not, exit with a
	 * usage message.
	 */
	if (regexec(&assignment, arg, N_MATCHES, match, 0))
		usage(stderr);

	/* Ok, we found a valid assignment.  Break it into two strings
	 * representing NAME and VALUE.
	 */
	arg[match[1].rm_eo] = '\0';
	arg[match[2].rm_eo] = '\0';
	*name = &arg[match[1].rm_so];
	*value = &arg[match[2].rm_so];

	regfree(&assignment);
}

/****************************************************************************
 * list_cmos_entry
 *
 * Attempt to list the CMOS entry represented by 'e'.  'show_name' is a
 * boolean value indicating whether the parameter name should be displayed
 * along with its value.  On success, return OK.  On error, print an error
 * message and return 1.
 ****************************************************************************/
static int list_cmos_entry(const cmos_entry_t * e, int show_name)
{
	const cmos_enum_t *p;
	unsigned long long value;
	char *w;

	/* sanity check CMOS entry */
	switch (prepare_cmos_read(e)) {
	case OK:
		break;

	case CMOS_OP_RESERVED:
		fprintf(stderr,
			"%s: Cannot access reserved CMOS area (for %s).\n",
			prog_name, e->name);
		return 1;

	case CMOS_AREA_OUT_OF_RANGE:
		fprintf(stderr,
			"%s: Can not read coreboot parameter %s because "
			"layout info specifies out of range CMOS area.\n",
			prog_name, e->name);
		return 1;

	case CMOS_AREA_OVERLAPS_RTC:
		fprintf(stderr,
			"%s: Can not read coreboot parameter %s because "
			"layout info specifies CMOS area that overlaps realtime "
			"clock area.\n", prog_name, e->name);
		return 1;

	case CMOS_AREA_TOO_WIDE:
		fprintf(stderr,
			"%s: Can not read coreboot parameter %s because "
			"layout info specifies CMOS area that is too wide.\n",
			prog_name, e->name);
		return 1;

	default:
		fprintf(stderr,
			"%s: Unknown error encountered while attempting to "
			"read coreboot parameter %s\n", prog_name, e->name);
		return 1;
	}

	/* read the value from CMOS */
	set_iopl(3);
	value = cmos_read(e);
	set_iopl(0);

	/* display the value */
	switch (e->config) {
	case CMOS_ENTRY_ENUM:
		if ((p = find_cmos_enum(e->config_id, value)) == NULL) {
			if (show_name)
				printf("# Bad value -> %s = 0x%llx\n", e->name,
				       value);
			else
				printf("Bad value -> 0x%llx\n", value);
		} else {
			if (show_name)
				printf("%s = %s\n", e->name, p->text);
			else
				printf("%s\n", p->text);
		}

		break;

	case CMOS_ENTRY_HEX:
		if (show_name)
			printf("%s = 0x%llx\n", e->name, value);
		else
			printf("0x%llx\n", value);

		break;

	case CMOS_ENTRY_STRING:
		w = (char *)(unsigned long)value;
		while (*w) {
			if(!isprint((int)(unsigned char)*w)) {
				if (show_name)
					printf("# Bad value -> %s\n", e->name);
				else
					printf("Bad value\n");
				break;
			}
			w++;
		}

		if (!*w) {

			if (show_name)
				printf("%s = %s\n", e->name,
				       (char *)(unsigned long)value);
			else
				printf("%s\n", (char *)(unsigned long)value);
		}

		free((void *)(unsigned long)value);

		break;

	case CMOS_ENTRY_RESERVED:
	default:
		BUG();
	}

	return OK;
}

/****************************************************************************
 * convert_checksum_value
 *
 * 'value' is the string representation of a checksum value that the user
 * wishes to set using the -c option.  Convert the string to a 16-bit
 * unsigned integer and return the result.  Exit with an error message if
 * 'value' is invalid.
 ****************************************************************************/
static uint16_t convert_checksum_value(const char value[])
{
	unsigned long n;
	const char *p;
	uint16_t result;
	int negative;

	for (p = value; isspace((int)(unsigned char)*p); p++) ;

	negative = (*p == '-');
	n = strtoul(value, (char **)&p, 0);

	if (*p) {
		fprintf(stderr,
			"%s: Checksum value %s is not a valid integer.\n",
			prog_name, value);
		exit(1);
	}

	if (negative) {
		fprintf(stderr,
			"%s: Checksum must be an unsigned integer.\n",
			prog_name);
		exit(1);
	}

	result = (uint16_t) n;

	if (result != n) {
		fprintf(stderr,
			"%s: Checksum value must fit within 16 bits.\n",
			prog_name);
		exit(1);
	}

	return result;
}