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

#define __STDC_FORMAT_MACROS

#include <string.h>

#include "cgpt.h"
#include "cgptlib_internal.h"
#include "crc32.h"
#include "vboot_host.h"

/* Generate output like:
 *
 *  [AB-CD-EF-01]   for group = 1
 *  [ABCD-EF01]     for group = 3  (low byte first)
 *
 * Needs (size*3-1+3) bytes of space in 'buf' (included the tailing '\0').
 */
#define BUFFER_SIZE(size) (size * 3 - 1 + 3)
static short Uint8To2Chars(const uint8_t t)
{
	int h = t >> 4;
	int l = t & 0xf;
	h = (h >= 0xA) ? h - 0xA + 'A' : h + '0';
	l = (l >= 0xA) ? l - 0xA + 'A' : l + '0';
	return (h << 8) + l;
}

static void RawDump(const uint8_t *memory, const int size, char *buf, int group)
{
	int i, outlen = 0;
	buf[outlen++] = '[';
	for (i = 0; i < size; ++i) {
		short c2 = Uint8To2Chars(memory[i]);
		buf[outlen++] = c2 >> 8;
		buf[outlen++] = c2 & 0xff;
		if (i != (size - 1) && ((i + 1) % group) == 0)
			buf[outlen++] = '-';
	}
	buf[outlen++] = ']';
	buf[outlen++] = '\0';
}

/* Output formatters */
#define TITLE_FMT "%12s%12s%8s  %s\n"
#define GPT_FMT "%12" PRId64 "%12" PRId64 "%8s  %s\n"
#define GPT_MORE "%12s%12s%8s  ", "", "", ""
#define PARTITION_FMT "%12" PRId64 "%12" PRId64 "%8d  %s\n"
#define PARTITION_MORE "%12s%12s%8s  %s%s\n", "", "", ""

static void PrintSignature(const char *indent, const char *sig, size_t n, int raw)
{
	size_t i;
	printf("%sSig: ", indent);
	if (!raw) {
		printf("[");
		for (i = 0; i < n; ++i)
			printf("%c", sig[i]);
		printf("]");
	} else {
		char *buf = malloc(BUFFER_SIZE(n));
		RawDump((uint8_t *)sig, n, buf, 1);
		printf("%s", buf);
		free(buf);
	}
	printf("\n");
}

static void HeaderDetails(GptHeader *header, GptEntry *entries, const char *indent, int raw)
{
	PrintSignature(indent, header->signature, sizeof(header->signature), raw);

	printf("%sRev: 0x%08x\n", indent, header->revision);
	printf("%sSize: %d (blocks)\n", indent, header->size);
	printf("%sHeader CRC: 0x%08x %s\n", indent, header->header_crc32,
	       (HeaderCrc(header) != header->header_crc32) ? "(INVALID)" : "");
	printf("%sMy LBA: %lld\n", indent, (long long)header->my_lba);
	printf("%sAlternate LBA: %lld\n", indent, (long long)header->alternate_lba);
	printf("%sFirst LBA: %lld\n", indent, (long long)header->first_usable_lba);
	printf("%sLast LBA: %lld\n", indent, (long long)header->last_usable_lba);

	{ /* For disk guid */
		char buf[GUID_STRLEN];
		GptGuidToStr(&header->disk_uuid, buf, GUID_STRLEN, GPT_GUID_UPPERCASE);
		printf("%sDisk UUID: %s\n", indent, buf);
	}

	printf("%sEntries LBA: %lld\n", indent, (long long)header->entries_lba);
	printf("%sNumber of entries: %d\n", indent, header->number_of_entries);
	printf("%sSize of entry: %d\n", indent, header->size_of_entry);
	printf("%sEntries CRC: 0x%08x %s\n", indent, header->entries_crc32,
	       header->entries_crc32 != Crc32((const uint8_t *)entries,
					      header->size_of_entry * header->number_of_entries)
		       ? "INVALID"
		       : "");
}

void EntryDetails(GptEntry *entry, uint32_t index, int raw)
{
	char contents[256]; // scratch buffer for formatting output
	uint8_t label[GPT_PARTNAME_LEN];
	char type[GUID_STRLEN], unique[GUID_STRLEN];
	int clen;

	UTF16ToUTF8(entry->name, sizeof(entry->name) / sizeof(entry->name[0]), label,
		    sizeof(label));
	require(snprintf(contents, sizeof(contents), "Label: \"%s\"", label) <
		sizeof(contents));
	printf(PARTITION_FMT, (uint64_t)entry->starting_lba,
	       (uint64_t)(entry->ending_lba - entry->starting_lba + 1), index + 1, contents);

	if (!raw && CGPT_OK == ResolveType(&entry->type, type)) {
		printf(PARTITION_MORE, "Type: ", type);
	} else {
		GptGuidToStr(&entry->type, type, GUID_STRLEN, GPT_GUID_UPPERCASE);
		printf(PARTITION_MORE, "Type: ", type);
	}
	GptGuidToStr(&entry->unique, unique, GUID_STRLEN, GPT_GUID_UPPERCASE);
	printf(PARTITION_MORE, "UUID: ", unique);

	clen = 0;
	if (!raw) {
		if (GuidEqual(&guid_chromeos_kernel, &entry->type) ||
		    GuidEqual(&guid_android_vbmeta, &entry->type)) {
			int tries = (entry->attrs.fields.gpt_att & CGPT_ATTRIBUTE_TRIES_MASK) >>
				    CGPT_ATTRIBUTE_TRIES_OFFSET;
			int successful = (entry->attrs.fields.gpt_att &
					  CGPT_ATTRIBUTE_SUCCESSFUL_MASK) >>
					 CGPT_ATTRIBUTE_SUCCESSFUL_OFFSET;
			int priority =
				(entry->attrs.fields.gpt_att & CGPT_ATTRIBUTE_PRIORITY_MASK) >>
				CGPT_ATTRIBUTE_PRIORITY_OFFSET;
			int error_counter = (entry->attrs.fields.gpt_att &
					     CGPT_ATTRIBUTE_ERROR_COUNTER_MASK) >>
					    CGPT_ATTRIBUTE_ERROR_COUNTER_OFFSET;
			clen = snprintf(contents, sizeof(contents),
					"priority=%d tries=%d successful=%d error_counter=%d ",
					priority, tries, successful, error_counter);
		}

		if (entry->attrs.fields.required) {
			clen += snprintf(contents + clen, sizeof(contents) - clen,
					 "required=%d ", entry->attrs.fields.required);
			require(clen < sizeof(contents));
		}

		if (entry->attrs.fields.efi_ignore) {
			clen += snprintf(contents + clen, sizeof(contents) - clen,
					 "efi_ignore=%d ", entry->attrs.fields.efi_ignore);
			require(clen < sizeof(contents));
		}

		if (entry->attrs.fields.legacy_boot) {
			clen += snprintf(contents + clen, sizeof(contents) - clen,
					 "legacy_boot=%d ", entry->attrs.fields.legacy_boot);
			require(clen < sizeof(contents));
		}
	} else {
		clen = snprintf(contents, sizeof(contents), "[%x]",
				entry->attrs.fields.gpt_att);
	}
	require(clen < sizeof(contents));
	if (clen)
		printf(PARTITION_MORE, "Attr: ", contents);
}

static void EntriesDetails(struct drive *drive, const int secondary, int raw)
{
	uint32_t i;

	for (i = 0; i < GetNumberOfEntries(drive); ++i) {
		GptEntry *entry;
		entry = GetEntry(&drive->gpt, secondary, i);

		if (GuidIsZero(&entry->type))
			continue;

		EntryDetails(entry, i, raw);
	}
}

static int GptShow(struct drive *drive, CgptShowParams *params)
{
	int gpt_retval;
	if (GPT_SUCCESS != (gpt_retval = GptValidityCheck(&drive->gpt))) {
		Error("GptValidityCheck() returned %d: %s\n", gpt_retval, GptError(gpt_retval));
		return CGPT_FAILED;
	}

	if (params->partition) { // show single partition

		if (params->partition > GetNumberOfEntries(drive)) {
			Error("invalid partition number: %d\n", params->partition);
			return CGPT_FAILED;
		}

		uint32_t index = params->partition - 1;
		GptEntry *entry = GetEntry(&drive->gpt, ANY_VALID, index);
		char buf[256]; // scratch buffer for string conversion

		if (params->single_item) {
			switch (params->single_item) {
			case 'b':
				printf("%" PRId64 "\n", entry->starting_lba);
				break;
			case 's': {
				uint64_t size = 0;
				// If these aren't actually defined, don't show anything
				if (entry->ending_lba || entry->starting_lba)
					size = entry->ending_lba - entry->starting_lba + 1;
				printf("%" PRId64 "\n", size);
				break;
			}
			case 't':
				GptGuidToStr(&entry->type, buf, sizeof(buf),
					     GPT_GUID_UPPERCASE);
				printf("%s\n", buf);
				break;
			case 'u':
				GptGuidToStr(&entry->unique, buf, sizeof(buf),
					     GPT_GUID_UPPERCASE);
				printf("%s\n", buf);
				break;
			case 'l':
				UTF16ToUTF8(entry->name,
					    sizeof(entry->name) / sizeof(entry->name[0]),
					    (uint8_t *)buf, sizeof(buf));
				printf("%s\n", buf);
				break;
			case 'S':
				printf("%d\n", GetSuccessful(drive, ANY_VALID, index));
				break;
			case 'T':
				printf("%d\n", GetTries(drive, ANY_VALID, index));
				break;
			case 'P':
				printf("%d\n", GetPriority(drive, ANY_VALID, index));
				break;
			case 'R':
				printf("%d\n", GetRequired(drive, ANY_VALID, index));
				break;
			case 'B':
				printf("%d\n", GetLegacyBoot(drive, ANY_VALID, index));
				break;
			case 'A':
				printf("%#x\n", entry->attrs.fields.gpt_att);
				break;
			}
		} else {
			printf(TITLE_FMT, "start", "size", "part", "contents");
			EntryDetails(entry, index, params->numeric);
		}

	} else if (params->quick) { // show all partitions, quickly
		uint32_t i;
		GptEntry *entry;
		char type[GUID_STRLEN];

		for (i = 0; i < GetNumberOfEntries(drive); ++i) {
			entry = GetEntry(&drive->gpt, ANY_VALID, i);

			if (GuidIsZero(&entry->type))
				continue;

			if (!params->numeric && CGPT_OK == ResolveType(&entry->type, type)) {
			} else {
				GptGuidToStr(&entry->type, type, GUID_STRLEN,
					     GPT_GUID_UPPERCASE);
			}
			printf(PARTITION_FMT, (uint64_t)entry->starting_lba,
			       (uint64_t)(entry->ending_lba - entry->starting_lba + 1), i + 1,
			       type);
		}
	} else { // show all partitions
		GptEntry *entries;

		if (params->debug || params->verbose) {
			printf("Drive details:\n");
			printf("    Total Size (bytes): %" PRIu64 "\n", drive->size);
			printf("    LBA Size (bytes): %d\n", drive->gpt.sector_bytes);
			if (drive->gpt.flags & GPT_FLAG_EXTERNAL) {
				printf("    Drive (where GPT lives) Size (blocks): %" PRIu64
				       "\n",
				       drive->gpt.gpt_drive_sectors);
				printf("    Drive (where partitions live) Size (blocks): "
				       "%" PRIu64 "\n",
				       drive->gpt.streaming_drive_sectors);
			} else {
				// We know gpt_drive_sectors == streaming_drive_sectors here.
				printf("    Drive Size (blocks): %" PRIu64 "\n",
				       drive->gpt.gpt_drive_sectors);
			}
			printf("\n");
		}

		if (CGPT_OK != ReadPMBR(drive)) {
			Error("Unable to read PMBR\n");
			return CGPT_FAILED;
		}

		printf(TITLE_FMT, "start", "size", "part", "contents");
		char buf[256];				   // buffer for formatted PMBR content
		PMBRToStr(&drive->pmbr, buf, sizeof(buf)); // will exit if buf is too small
		printf(GPT_FMT, (uint64_t)0, (uint64_t)GPT_PMBR_SECTORS, "", buf);

		if (drive->gpt.ignored & MASK_PRIMARY) {
			printf(GPT_FMT, (uint64_t)GPT_PMBR_SECTORS,
			       (uint64_t)GPT_HEADER_SECTORS, "IGNORED", "Pri GPT header");
		} else {
			if (drive->gpt.valid_headers & MASK_PRIMARY) {
				printf(GPT_FMT, (uint64_t)GPT_PMBR_SECTORS,
				       (uint64_t)GPT_HEADER_SECTORS, "", "Pri GPT header");
			} else {
				printf(GPT_FMT, (uint64_t)GPT_PMBR_SECTORS,
				       (uint64_t)GPT_HEADER_SECTORS, "INVALID",
				       "Pri GPT header");
			}

			if (params->debug ||
			    ((drive->gpt.valid_headers & MASK_PRIMARY) && params->verbose)) {
				GptHeader *header;
				char indent[64];

				require(snprintf(indent, sizeof(indent), GPT_MORE) <
					sizeof(indent));
				header = (GptHeader *)drive->gpt.primary_header;
				entries = (GptEntry *)drive->gpt.primary_entries;
				HeaderDetails(header, entries, indent, params->numeric);
			}

			GptHeader *primary_header = (GptHeader *)drive->gpt.primary_header;
			printf(GPT_FMT, (uint64_t)primary_header->entries_lba,
			       (uint64_t)CalculateEntriesSectors(primary_header,
								 drive->gpt.sector_bytes),
			       drive->gpt.valid_entries & MASK_PRIMARY ? "" : "INVALID",
			       "Pri GPT table");

			if (params->debug || (drive->gpt.valid_entries & MASK_PRIMARY))
				EntriesDetails(drive, PRIMARY, params->numeric);
		}

		/****************************** Secondary *************************/
		if (drive->gpt.ignored & MASK_SECONDARY) {
			printf(GPT_FMT,
			       (uint64_t)(drive->gpt.gpt_drive_sectors - GPT_HEADER_SECTORS),
			       (uint64_t)GPT_HEADER_SECTORS, "IGNORED", "Sec GPT header");
		} else {
			GptHeader *secondary_header = (GptHeader *)drive->gpt.secondary_header;
			printf(GPT_FMT, (uint64_t)secondary_header->entries_lba,
			       (uint64_t)CalculateEntriesSectors(secondary_header,
								 drive->gpt.sector_bytes),
			       drive->gpt.valid_entries & MASK_SECONDARY ? "" : "INVALID",
			       "Sec GPT table");
			/* We show secondary table details if any of following is true.
			 *   1. in debug mode.
			 *   2. primary table is being ignored
			 *   3. only secondary is valid.
			 *   4. secondary is not identical to primary.
			 */
			if (params->debug || (drive->gpt.ignored & MASK_PRIMARY) ||
			    ((drive->gpt.valid_entries & MASK_SECONDARY) &&
			     (!(drive->gpt.valid_entries & MASK_PRIMARY) ||
			      memcmp(drive->gpt.primary_entries, drive->gpt.secondary_entries,
				     secondary_header->number_of_entries *
					     secondary_header->size_of_entry)))) {
				EntriesDetails(drive, SECONDARY, params->numeric);
			}

			if (drive->gpt.valid_headers & MASK_SECONDARY) {
				printf(GPT_FMT,
				       (uint64_t)(drive->gpt.gpt_drive_sectors -
						  GPT_HEADER_SECTORS),
				       (uint64_t)GPT_HEADER_SECTORS, "", "Sec GPT header");
			} else {
				printf(GPT_FMT, (uint64_t)GPT_PMBR_SECTORS,
				       (uint64_t)GPT_HEADER_SECTORS, "INVALID",
				       "Sec GPT header");
			}
			/* We show secondary header if any of following is true:
			 *   1. in debug mode.
			 *   2. primary table is being ignored
			 *   3. only secondary is valid.
			 *   4. secondary is not synonymous to primary and not ignored.
			 */
			if (params->debug || (drive->gpt.ignored & MASK_PRIMARY) ||
			    ((drive->gpt.valid_headers & MASK_SECONDARY) &&
			     (!(drive->gpt.valid_headers & MASK_PRIMARY) ||
			      !IsSynonymous((GptHeader *)drive->gpt.primary_header,
					    (GptHeader *)drive->gpt.secondary_header)) &&
			     params->verbose)) {
				GptHeader *header;
				char indent[64];

				require(snprintf(indent, sizeof(indent), GPT_MORE) <
					sizeof(indent));
				header = (GptHeader *)drive->gpt.secondary_header;
				entries = (GptEntry *)drive->gpt.secondary_entries;
				HeaderDetails(header, entries, indent, params->numeric);
			}
		}
	}

	CheckValid(drive);

	return CGPT_OK;
}

int CgptShow(CgptShowParams *params)
{
	struct drive drive;

	if (params == NULL)
		return CGPT_FAILED;

	if (CGPT_OK != DriveOpen(params->drive_name, &drive, O_RDONLY, params->drive_size))
		return CGPT_FAILED;

	int ret = GptShow(&drive, params);
	DriveClose(&drive, 0);
	return ret;
}
