/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "2sysincludes.h"
#include "cgptlib.h"
#include "cgptlib_internal.h"
#include "crc32.h"
#include "gpt.h"
#include "gpt_misc.h"

const static int MIN_SECTOR_SIZE = 512;

size_t CalculateEntriesSectors(GptHeader* h, uint32_t sector_bytes)
{
	size_t bytes = h->number_of_entries * h->size_of_entry;
	size_t ret = (bytes + sector_bytes - 1) / sector_bytes;
	return ret;
}

int CheckParameters(GptData *gpt)
{
	/* Only support 512-byte or larger sectors that are a power of 2 */
	if (gpt->sector_bytes < MIN_SECTOR_SIZE ||
			(gpt->sector_bytes  & (gpt->sector_bytes  - 1)) != 0)
		return GPT_ERROR_INVALID_SECTOR_SIZE;

	/*
	 * gpt_drive_sectors should be reasonable. It cannot be unset, and it
	 * cannot differ from streaming_drive_sectors if the GPT structs are
	 * stored on same device.
	 */
	if (gpt->gpt_drive_sectors == 0 ||
		(!(gpt->flags & GPT_FLAG_EXTERNAL) &&
		 gpt->gpt_drive_sectors != gpt->streaming_drive_sectors)) {
		return GPT_ERROR_INVALID_SECTOR_NUMBER;
	}

	/*
	 * Sector count of a drive should be reasonable. If the given value is
	 * too small to contain basic GPT structure (PMBR + Headers + Entries),
	 * the value is wrong.
	 */
	if (gpt->gpt_drive_sectors <
		(1 + 2 * (1 + MIN_NUMBER_OF_ENTRIES /
				(gpt->sector_bytes / sizeof(GptEntry)))))
		return GPT_ERROR_INVALID_SECTOR_NUMBER;

	return GPT_SUCCESS;
}

uint32_t HeaderCrc(GptHeader *h)
{
	uint32_t crc32, original_crc32;

	/* Original CRC is calculated with the CRC field 0. */
	original_crc32 = h->header_crc32;
	h->header_crc32 = 0;
	crc32 = Crc32((const uint8_t *)h, h->size);
	h->header_crc32 = original_crc32;

	return crc32;
}

int CheckHeader(GptHeader *h, int is_secondary,
		uint64_t streaming_drive_sectors,
		uint64_t gpt_drive_sectors, uint32_t flags,
		uint32_t sector_bytes)
{
	if (!h)
		return 1;

	/*
	 * Make sure we're looking at a header of reasonable size before
	 * attempting to calculate CRC.
	 */
	if (memcmp(h->signature, GPT_HEADER_SIGNATURE,
		   GPT_HEADER_SIGNATURE_SIZE) &&
	    memcmp(h->signature, GPT_HEADER_SIGNATURE2,
		   GPT_HEADER_SIGNATURE_SIZE))
		return 1;
	if (h->revision != GPT_HEADER_REVISION)
		return 1;
	if (h->size < MIN_SIZE_OF_HEADER || h->size > MAX_SIZE_OF_HEADER)
		return 1;

	/* Check CRC before looking at remaining fields */
	if (HeaderCrc(h) != h->header_crc32)
		return 1;

	/* Reserved fields must be zero. */
	if (h->reserved_zero)
		return 1;

	/* Could check that padding is zero, but that doesn't matter to us. */

	/*
	 * If entry size is different than our struct, we won't be able to
	 * parse it.  Technically, any size 2^N where N>=7 is valid.
	 */
	if (h->size_of_entry != sizeof(GptEntry))
		return 1;
	if ((h->number_of_entries < MIN_NUMBER_OF_ENTRIES) ||
	    (h->number_of_entries > MAX_NUMBER_OF_ENTRIES) ||
	    (!(flags & GPT_FLAG_EXTERNAL) &&
	    h->number_of_entries != MAX_NUMBER_OF_ENTRIES))
		return 1;

	/*
	 * Check locations for the header and its entries.  The primary
	 * immediately follows the PMBR, and is followed by its entries.  The
	 * secondary is at the end of the drive, preceded by its entries.
	 */
	if (is_secondary) {
		if (h->my_lba != gpt_drive_sectors - GPT_HEADER_SECTORS)
			return 1;
		if (h->entries_lba != h->my_lba - CalculateEntriesSectors(h,
								sector_bytes))
			return 1;
	} else {
		if (h->my_lba != GPT_PMBR_SECTORS)
			return 1;
		if (h->entries_lba < h->my_lba + 1)
			return 1;
	}

	/* FirstUsableLBA <= LastUsableLBA. */
	if (h->first_usable_lba > h->last_usable_lba)
		return 1;

	if (flags & GPT_FLAG_EXTERNAL) {
		if (h->last_usable_lba >= streaming_drive_sectors) {
			return 1;
		}
		return 0;
	}

	/*
	 * FirstUsableLBA must be after the end of the primary GPT table array.
	 * LastUsableLBA must be before the start of the secondary GPT table
	 * array.
	 */
	/* TODO(namnguyen): Also check for padding between header & entries. */
	if (h->first_usable_lba < 2 + CalculateEntriesSectors(h, sector_bytes))
		return 1;
	if (h->last_usable_lba >=
			streaming_drive_sectors - 1 - CalculateEntriesSectors(h,
								sector_bytes))
		return 1;

	/* Success */
	return 0;
}

int IsKernelEntry(const GptEntry *e)
{
	static Guid chromeos_kernel = GPT_ENT_TYPE_CHROMEOS_KERNEL;
	return !memcmp(&e->type, &chromeos_kernel, sizeof(Guid));
}

int CheckEntries(GptEntry *entries, GptHeader *h)
{
	if (!entries)
		return GPT_ERROR_INVALID_ENTRIES;
	GptEntry *entry;
	uint32_t crc32;
	uint32_t i;

	/* Check CRC before examining entries. */
	crc32 = Crc32((const uint8_t *)entries,
		      h->size_of_entry * h->number_of_entries);
	if (crc32 != h->entries_crc32)
		return GPT_ERROR_CRC_CORRUPTED;

	/* Check all entries. */
	for (i = 0, entry = entries; i < h->number_of_entries; i++, entry++) {
		GptEntry *e2;
		uint32_t i2;

		if (IsUnusedEntry(entry))
			continue;

		/* Entry must be in valid region. */
		if ((entry->starting_lba < h->first_usable_lba) ||
		    (entry->ending_lba > h->last_usable_lba) ||
		    (entry->ending_lba < entry->starting_lba))
			return GPT_ERROR_OUT_OF_REGION;

		/* Entry must not overlap other entries. */
		for (i2 = 0, e2 = entries; i2 < h->number_of_entries;
		     i2++, e2++) {
			if (i2 == i || IsUnusedEntry(e2))
				continue;

			if ((entry->starting_lba >= e2->starting_lba) &&
			    (entry->starting_lba <= e2->ending_lba))
				return GPT_ERROR_START_LBA_OVERLAP;
			if ((entry->ending_lba >= e2->starting_lba) &&
			    (entry->ending_lba <= e2->ending_lba))
				return GPT_ERROR_END_LBA_OVERLAP;

			/* UniqueGuid field must be unique. */
			if (0 == memcmp(&entry->unique, &e2->unique,
					sizeof(Guid)))
				return GPT_ERROR_DUP_GUID;
		}
	}

	/* Success */
	return 0;
}

int HeaderFieldsSame(GptHeader *h1, GptHeader *h2)
{
	if (memcmp(h1->signature, h2->signature, sizeof(h1->signature)))
		return 1;
	if (h1->revision != h2->revision)
		return 1;
	if (h1->size != h2->size)
		return 1;
	if (h1->reserved_zero != h2->reserved_zero)
		return 1;
	if (h1->first_usable_lba != h2->first_usable_lba)
		return 1;
	if (h1->last_usable_lba != h2->last_usable_lba)
		return 1;
	if (memcmp(&h1->disk_uuid, &h2->disk_uuid, sizeof(Guid)))
		return 1;
	if (h1->number_of_entries != h2->number_of_entries)
		return 1;
	if (h1->size_of_entry != h2->size_of_entry)
		return 1;
	if (h1->entries_crc32 != h2->entries_crc32)
		return 1;

	return 0;
}

int GptValidityCheck(GptData *gpt)
{
	int retval;
	GptHeader *header1 = (GptHeader *)(gpt->primary_header);
	GptHeader *header2 = (GptHeader *)(gpt->secondary_header);
	GptEntry *entries1 = (GptEntry *)(gpt->primary_entries);
	GptEntry *entries2 = (GptEntry *)(gpt->secondary_entries);
	GptHeader *goodhdr = NULL;

	gpt->valid_headers = 0;
	gpt->valid_entries = 0;
	gpt->ignored = 0;

	retval = CheckParameters(gpt);
	if (retval != GPT_SUCCESS)
		return retval;

	/* Check both headers; we need at least one valid header. */
	if (0 == CheckHeader(header1, 0, gpt->streaming_drive_sectors,
			     gpt->gpt_drive_sectors, gpt->flags,
			     gpt->sector_bytes)) {
		gpt->valid_headers |= MASK_PRIMARY;
		goodhdr = header1;
		if (0 == CheckEntries(entries1, goodhdr))
			gpt->valid_entries |= MASK_PRIMARY;
	} else if (header1 && !memcmp(header1->signature,
		   GPT_HEADER_SIGNATURE_IGNORED, GPT_HEADER_SIGNATURE_SIZE)) {
		gpt->ignored |= MASK_PRIMARY;
	}
	if (0 == CheckHeader(header2, 1, gpt->streaming_drive_sectors,
			     gpt->gpt_drive_sectors, gpt->flags,
			     gpt->sector_bytes)) {
		gpt->valid_headers |= MASK_SECONDARY;
		if (!goodhdr)
			goodhdr = header2;
		/* Check header1+entries2 if it was good, to catch mismatch. */
		if (0 == CheckEntries(entries2, goodhdr))
			gpt->valid_entries |= MASK_SECONDARY;
	} else if (header2 && !memcmp(header2->signature,
		   GPT_HEADER_SIGNATURE_IGNORED, GPT_HEADER_SIGNATURE_SIZE)) {
		gpt->ignored |= MASK_SECONDARY;
	}

	if (!gpt->valid_headers)
		return GPT_ERROR_INVALID_HEADERS;

	/*
	 * If both headers are good but neither entries were good, check the
	 * entries with the secondary header.
	 */
	if (MASK_BOTH == gpt->valid_headers && !gpt->valid_entries) {
		if (0 == CheckEntries(entries1, header2))
			gpt->valid_entries |= MASK_PRIMARY;
		if (0 == CheckEntries(entries2, header2))
			gpt->valid_entries |= MASK_SECONDARY;
		if (gpt->valid_entries) {
			/*
			 * Sure enough, header2 had a good CRC for one of the
			 * entries.  Mark header1 invalid, so we'll update its
			 * entries CRC.
			 */
			gpt->valid_headers &= ~MASK_PRIMARY;
			goodhdr = header2;
		}
	}

	if (!gpt->valid_entries)
		return GPT_ERROR_INVALID_ENTRIES;

	/*
	 * Now that we've determined which header contains a good CRC for
	 * the entries, make sure the headers are otherwise identical.
	 */
	if (MASK_BOTH == gpt->valid_headers &&
	    0 != HeaderFieldsSame(header1, header2))
		gpt->valid_headers &= ~MASK_SECONDARY;

	/*
	 * When we're ignoring a GPT, make it look in memory like the other one
	 * and pretend that everything is fine (until we try to save).
	 */
	if (MASK_NONE != gpt->ignored) {
		GptRepair(gpt);
		gpt->modified = 0;
	}

	return GPT_SUCCESS;
}

void GptRepair(GptData *gpt)
{
	GptHeader *header1 = (GptHeader *)(gpt->primary_header);
	GptHeader *header2 = (GptHeader *)(gpt->secondary_header);
	GptEntry *entries1 = (GptEntry *)(gpt->primary_entries);
	GptEntry *entries2 = (GptEntry *)(gpt->secondary_entries);
	int entries_size;

	/* Need at least one good header and one good set of entries. */
	if (MASK_NONE == gpt->valid_headers || MASK_NONE == gpt->valid_entries)
		return;

	/* Repair headers if necessary */
	if (MASK_PRIMARY == gpt->valid_headers) {
		/* Primary is good, secondary is bad */
		memcpy(header2, header1, sizeof(GptHeader));
		header2->my_lba = gpt->gpt_drive_sectors - GPT_HEADER_SECTORS;
		header2->alternate_lba = GPT_PMBR_SECTORS;  /* Second sector. */
		header2->entries_lba = header2->my_lba -
			CalculateEntriesSectors(header1, gpt->sector_bytes);
		header2->header_crc32 = HeaderCrc(header2);
		gpt->modified |= GPT_MODIFIED_HEADER2;
	}
	else if (MASK_SECONDARY == gpt->valid_headers) {
		/* Secondary is good, primary is bad */
		memcpy(header1, header2, sizeof(GptHeader));
		header1->my_lba = GPT_PMBR_SECTORS;  /* Second sector. */
		header1->alternate_lba =
			gpt->streaming_drive_sectors - GPT_HEADER_SECTORS;
		/* TODO (namnguyen): Preserve (header, entries) padding. */
		header1->entries_lba = header1->my_lba + 1;
		header1->header_crc32 = HeaderCrc(header1);
		gpt->modified |= GPT_MODIFIED_HEADER1;
	}
	gpt->valid_headers = MASK_BOTH;

	/* Repair entries if necessary */
	entries_size = header1->size_of_entry * header1->number_of_entries;
	if (MASK_PRIMARY == gpt->valid_entries) {
		/* Primary is good, secondary is bad */
		memcpy(entries2, entries1, entries_size);
		gpt->modified |= GPT_MODIFIED_ENTRIES2;
	}
	else if (MASK_SECONDARY == gpt->valid_entries) {
		/* Secondary is good, primary is bad */
		memcpy(entries1, entries2, entries_size);
		gpt->modified |= GPT_MODIFIED_ENTRIES1;
	}
	gpt->valid_entries = MASK_BOTH;
}

int GetEntryRequired(const GptEntry *e)
{
	return e->attrs.fields.required;
}

int GetEntryLegacyBoot(const GptEntry *e)
{
	return e->attrs.fields.legacy_boot;
}

int GetEntrySuccessful(const GptEntry *e)
{
	return (e->attrs.fields.gpt_att & CGPT_ATTRIBUTE_SUCCESSFUL_MASK) >>
		CGPT_ATTRIBUTE_SUCCESSFUL_OFFSET;
}

int GetEntryPriority(const GptEntry *e)
{
	return (e->attrs.fields.gpt_att & CGPT_ATTRIBUTE_PRIORITY_MASK) >>
		CGPT_ATTRIBUTE_PRIORITY_OFFSET;
}

int GetEntryTries(const GptEntry *e)
{
	return (e->attrs.fields.gpt_att & CGPT_ATTRIBUTE_TRIES_MASK) >>
		CGPT_ATTRIBUTE_TRIES_OFFSET;
}

void SetEntryRequired(GptEntry *e, int required)
{
	e->attrs.fields.required = required;
}

void SetEntryLegacyBoot(GptEntry *e, int legacy_boot)
{
	e->attrs.fields.legacy_boot = legacy_boot;
}

void SetEntrySuccessful(GptEntry *e, int successful)
{
	if (successful)
		e->attrs.fields.gpt_att |= CGPT_ATTRIBUTE_SUCCESSFUL_MASK;
	else
		e->attrs.fields.gpt_att &= ~CGPT_ATTRIBUTE_SUCCESSFUL_MASK;
}

void SetEntryPriority(GptEntry *e, int priority)
{
	e->attrs.fields.gpt_att &= ~CGPT_ATTRIBUTE_PRIORITY_MASK;
	e->attrs.fields.gpt_att |=
		(priority << CGPT_ATTRIBUTE_PRIORITY_OFFSET) &
		CGPT_ATTRIBUTE_PRIORITY_MASK;
}

void SetEntryTries(GptEntry *e, int tries)
{
	e->attrs.fields.gpt_att &= ~CGPT_ATTRIBUTE_TRIES_MASK;
	e->attrs.fields.gpt_att |= (tries << CGPT_ATTRIBUTE_TRIES_OFFSET) &
		CGPT_ATTRIBUTE_TRIES_MASK;
}

void GetCurrentKernelUniqueGuid(GptData *gpt, void *dest)
{
	GptEntry *entries = (GptEntry *)gpt->primary_entries;
	GptEntry *e = entries + gpt->current_kernel;
	memcpy(dest, &e->unique, sizeof(Guid));
}

void GptModified(GptData *gpt) {
	GptHeader *header = (GptHeader *)gpt->primary_header;

	/* Update the CRCs */
	header->entries_crc32 = Crc32(gpt->primary_entries,
				      header->size_of_entry *
				      header->number_of_entries);
	header->header_crc32 = HeaderCrc(header);
	gpt->modified |= GPT_MODIFIED_HEADER1 | GPT_MODIFIED_ENTRIES1;

	/*
	 * Use the repair function to update the other copy of the GPT.  This
	 * is a tad inefficient, but is much faster than the disk I/O to update
	 * the GPT on disk so it doesn't matter.
	 */
	gpt->valid_headers = MASK_PRIMARY;
	gpt->valid_entries = MASK_PRIMARY;
	GptRepair(gpt);
}


const char *GptErrorText(int error_code)
{
	switch(error_code) {
	case GPT_SUCCESS:
		return "none";

	case GPT_ERROR_NO_VALID_KERNEL:
		return "Invalid kernel";

	case GPT_ERROR_INVALID_HEADERS:
		return "Invalid headers";

	case GPT_ERROR_INVALID_ENTRIES:
		return "Invalid entries";

	case GPT_ERROR_INVALID_SECTOR_SIZE:
		return "Invalid sector size";

	case GPT_ERROR_INVALID_SECTOR_NUMBER:
		return "Invalid sector number";

	case GPT_ERROR_INVALID_UPDATE_TYPE:
		return "Invalid update type";

	case GPT_ERROR_CRC_CORRUPTED:
		return "Entries' crc corrupted";

	case GPT_ERROR_OUT_OF_REGION:
		return "Entry outside of valid region";

	case GPT_ERROR_START_LBA_OVERLAP:
		return "Starting LBA overlaps";

	case GPT_ERROR_END_LBA_OVERLAP:
		return "Ending LBA overlaps";

	case GPT_ERROR_DUP_GUID:
		return "Duplicated GUID";

	case GPT_ERROR_INVALID_FLASH_GEOMETRY:
		return "Invalid flash geometry";

	case GPT_ERROR_NO_SUCH_ENTRY:
		return "No entry found";

	default:
		break;
	};
	return "Unknown";
}
