/* Copyright 2020 The ChromiumOS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* For strdup */
#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "common.h" /* from cbfstool for buffer API. */
#include "subprocess.h" /* from vboot_reference */
#include "flashrom.h"

#define FLASHROM_EXEC_NAME "flashrom"
#define FLASHROM_PROGRAMMER_INTERNAL_AP "internal"

/**
 * Helper to create a temporary file.
 *
 * @param path_out	An output pointer for the filename. Caller should free.
 *
 * @return 0 on success, -1 for file open error, or -2 for write error.
 */
static int create_temp_file(char **path_out)
{
	int fd;
	int rv;
	char *path;
	mode_t umask_save;

#if defined(__FreeBSD__)
#define P_tmpdir "/tmp"
#endif
	*path_out = NULL;
	path = strdup(P_tmpdir "/flashrom.XXXXXX");
	/* Set the umask before mkstemp for security considerations. */
	umask_save = umask(077);
	fd = mkstemp(path);
	umask(umask_save);
	if (fd < 0) {
		rv = -1;
		goto fail;
	}

	close(fd);
	*path_out = path;

	return 0;
fail:
	free(path);
	return rv;
}

static int run_flashrom(const char *const argv[])
{
	int status = subprocess_run(argv, &subprocess_null, &subprocess_null,
				    &subprocess_null);
	if (status) {
		fprintf(stderr, "Flashrom invocation failed (exit status %d):",
			status);
		for (const char *const *argp = argv; *argp; argp++)
			fprintf(stderr, " %s", *argp);
		fprintf(stderr, "\n");
		return -1;
	}

	return 0;
}

int flashrom_host_read(struct buffer *buffer, const char *region)
{
	char *tmpfile;
	char region_param[PATH_MAX];
	int rv;

	if (create_temp_file(&tmpfile) != 0)
		return -1;
	if (region)
		snprintf(region_param, sizeof(region_param), "%s:%s", region,
			 tmpfile);
	const char *const argv[] = {
		FLASHROM_EXEC_NAME,
		"-p",
		FLASHROM_PROGRAMMER_INTERNAL_AP,
		"-r",
		region ? "-i" : tmpfile,
		region ? region_param : NULL,
		NULL,
	};
	rv = run_flashrom(argv);
	if (!rv)
		rv = buffer_from_file(buffer, tmpfile);

	unlink(tmpfile);
	free(tmpfile);

	return rv;
}

int flashrom_host_write(struct buffer *buffer, const char *region)
{
	char *tmpfile;
	char region_param[PATH_MAX];
	int rv;

	if (create_temp_file(&tmpfile) != 0)
		return -1;
	if (buffer_write_file(buffer, tmpfile) != 0) {
		rv = -2;
		goto fail;
	}

	if (region)
		snprintf(region_param, sizeof(region_param), "%s:%s", region,
			 tmpfile);
	const char *const argv[] = {
		FLASHROM_EXEC_NAME,
		"-p",
		FLASHROM_PROGRAMMER_INTERNAL_AP,
		"--noverify-all",
		"-w",
		region ? "-i" : tmpfile,
		region ? region_param : NULL,
		NULL,
	};

	rv = run_flashrom(argv);

fail:
	unlink(tmpfile);
	free(tmpfile);

	return rv;
}