diff --git a/cgptlib/cgpt.c b/cgptlib/cgpt.c index a8d449d13d..d509f00c40 100644 --- a/cgptlib/cgpt.c +++ b/cgptlib/cgpt.c @@ -465,6 +465,14 @@ void UpdateCrc(GptData *gpt) { primary_header = (GptHeader*)gpt->primary_header; secondary_header = (GptHeader*)gpt->secondary_header; + if (gpt->modified & GPT_MODIFIED_ENTRIES1) { + primary_header->entries_crc32 = + Crc32(gpt->primary_entries, TOTAL_ENTRIES_SIZE); + } + if (gpt->modified & GPT_MODIFIED_ENTRIES2) { + secondary_header->entries_crc32 = + Crc32(gpt->secondary_entries, TOTAL_ENTRIES_SIZE); + } if (gpt->modified & GPT_MODIFIED_HEADER1) { primary_header->header_crc32 = 0; primary_header->header_crc32 = Crc32( @@ -475,14 +483,6 @@ void UpdateCrc(GptData *gpt) { secondary_header->header_crc32 = Crc32( (const uint8_t *)secondary_header, secondary_header->size); } - if (gpt->modified & GPT_MODIFIED_ENTRIES1) { - primary_header->entries_crc32 = - Crc32(gpt->primary_entries, TOTAL_ENTRIES_SIZE); - } - if (gpt->modified & GPT_MODIFIED_ENTRIES2) { - secondary_header->entries_crc32 = - Crc32(gpt->secondary_entries, TOTAL_ENTRIES_SIZE); - } } /* Does every sanity check, and returns if any header/entries needs to be @@ -528,25 +528,219 @@ int GptInit(GptData *gpt) { UpdateCrc(gpt); - /* FIXME: will remove the next line soon. */ - gpt->current_kernel = 1; + gpt->current_kernel = CGPT_KERNEL_ENTRY_NOT_FOUND; + return GPT_SUCCESS; } -/* stub code */ -static int start[] = { 34, 10034 }; +/* Helper function to get a pointer to the partition entry. + * 'secondary' is either PRIMARY or SECONDARY. + * 'entry_index' is the partition index: [0, number_of_entries). + */ +GptEntry *GetEntry(GptData *gpt, int secondary, int entry_index) { + GptHeader *header; + uint8_t *entries; + if (secondary == PRIMARY) { + header = (GptHeader*)gpt->primary_header; + entries = gpt->primary_entries; + } else { + header = (GptHeader*)gpt->secondary_header; + entries = gpt->secondary_entries; + } + + return (GptEntry*)(&entries[header->size_of_entry * entry_index]); +} + +/* The following functions are helpers to access attributes bit more easily. + * 'secondary' is either PRIMARY or SECONDARY. + * 'entry_index' is the partition index: [0, number_of_entries). + * + * Get*() return the exact value (shifted and masked). + */ +void SetPriority(GptData *gpt, int secondary, int entry_index, int priority) { + GptEntry *entry; + entry = GetEntry(gpt, secondary, entry_index); + + assert(priority >= 0 && priority <= CGPT_ATTRIBUTE_MAX_PRIORITY); + entry->attributes &= ~CGPT_ATTRIBUTE_PRIORITY_MASK; + entry->attributes |= (uint64_t)priority << CGPT_ATTRIBUTE_PRIORITY_OFFSET; +} + +int GetPriority(GptData *gpt, int secondary, int entry_index) { + GptEntry *entry; + entry = GetEntry(gpt, secondary, entry_index); + return (entry->attributes & CGPT_ATTRIBUTE_PRIORITY_MASK) >> + CGPT_ATTRIBUTE_PRIORITY_OFFSET; +} + +void SetBad(GptData *gpt, int secondary, int entry_index, int bad) { + GptEntry *entry; + entry = GetEntry(gpt, secondary, entry_index); + + assert(bad >= 0 && bad <= CGPT_ATTRIBUTE_MAX_BAD); + entry->attributes &= ~CGPT_ATTRIBUTE_BAD_MASK; + entry->attributes |= (uint64_t)bad << CGPT_ATTRIBUTE_BAD_OFFSET; +} + +int GetBad(GptData *gpt, int secondary, int entry_index) { + GptEntry *entry; + entry = GetEntry(gpt, secondary, entry_index); + return (entry->attributes & CGPT_ATTRIBUTE_BAD_MASK) >> + CGPT_ATTRIBUTE_BAD_OFFSET; +} + +void SetTries(GptData *gpt, int secondary, int entry_index, int tries) { + GptEntry *entry; + entry = GetEntry(gpt, secondary, entry_index); + + assert(tries >= 0 && tries <= CGPT_ATTRIBUTE_MAX_TRIES); + entry->attributes &= ~CGPT_ATTRIBUTE_TRIES_MASK; + entry->attributes |= (uint64_t)tries << CGPT_ATTRIBUTE_TRIES_OFFSET; +} + +int GetTries(GptData *gpt, int secondary, int entry_index) { + GptEntry *entry; + entry = GetEntry(gpt, secondary, entry_index); + return (entry->attributes & CGPT_ATTRIBUTE_TRIES_MASK) >> + CGPT_ATTRIBUTE_TRIES_OFFSET; +} + +void SetSuccess(GptData *gpt, int secondary, int entry_index, int success) { + GptEntry *entry; + entry = GetEntry(gpt, secondary, entry_index); + + assert(success >= 0 && success <= CGPT_ATTRIBUTE_MAX_SUCCESS); + entry->attributes &= ~CGPT_ATTRIBUTE_SUCCESS_MASK; + entry->attributes |= (uint64_t)success << CGPT_ATTRIBUTE_SUCCESS_OFFSET; +} + +int GetSuccess(GptData *gpt, int secondary, int entry_index) { + GptEntry *entry; + entry = GetEntry(gpt, secondary, entry_index); + return (entry->attributes & CGPT_ATTRIBUTE_SUCCESS_MASK) >> + CGPT_ATTRIBUTE_SUCCESS_OFFSET; +} + +/* Compare two priority values. Actually it is a circular priority, which is: + * 3 > 2 > 1 > 0, but 0 > 3. (-1 means very low, and anyone is higher than -1) + * + * Return 1 if 'a' has higher priority than 'b'. + */ +int IsHigherPriority(int a, int b) { + if ((a == 0) && (b == CGPT_ATTRIBUTE_MAX_PRIORITY)) + return 1; + else if ((a == CGPT_ATTRIBUTE_MAX_PRIORITY) && (b == 0)) + return 0; + else + return (a > b) ? 1 : 0; +} + +/* This function walks through the whole partition table (see note below), + * and pick up the active and valid (not marked as bad) kernel entry with + * *highest* priority (except gpt->current_kernel itself). + * + * Returns start_sector and its size if a candidate kernel is found. + * + * Note: in the first walk (gpt->current_kernel==CGPT_KERNEL_ENTRY_NOT_FOUND), + * the scan range is whole table. But in later scans, we only scan + * (header->number_of_entries - 1) entries because we are looking for + * next kernel with lower priority (consider the case that highest + * priority kernel is still active and valid). + */ int GptNextKernelEntry(GptData *gpt, uint64_t *start_sector, uint64_t *size) { - /* FIXME: the following code is not really code, just returns anything */ - gpt->current_kernel ^= 1; - if (start_sector) *start_sector = start[gpt->current_kernel]; - if (size) *size = 10000; + GptHeader *header; + GptEntry *entry; + int scan, current_priority; + int begin, end; /* [begin, end], which end is included. */ + Guid chromeos_kernel = GPT_ENT_TYPE_CHROMEOS_KERNEL; + + header = (GptHeader*)gpt->primary_header; + current_priority = -1; /* pretty low priority */ + if (gpt->current_kernel == CGPT_KERNEL_ENTRY_NOT_FOUND) { + begin = 0; + end = header->number_of_entries - 1; + } else { + begin = (gpt->current_kernel + 1) % header->number_of_entries; + end = (gpt->current_kernel - 1 + header->number_of_entries) % + header->number_of_entries; + } + + scan = begin; + do { + entry = GetEntry(gpt, PRIMARY, scan); + if (!Memcmp(&entry->type, &chromeos_kernel, sizeof(Guid)) && + !GetBad(gpt, PRIMARY, scan) && + ((gpt->current_kernel == CGPT_KERNEL_ENTRY_NOT_FOUND) || + (IsHigherPriority(GetPriority(gpt, PRIMARY, scan), + current_priority)))) { + gpt->current_kernel = scan; + current_priority = GetPriority(gpt, PRIMARY, gpt->current_kernel); + } + + if (scan == end) break; + scan = (scan + 1) % header->number_of_entries; + } while (1); + + if (gpt->current_kernel == CGPT_KERNEL_ENTRY_NOT_FOUND) + return GPT_ERROR_NO_VALID_KERNEL; + + entry = GetEntry(gpt, PRIMARY, gpt->current_kernel); + assert(entry->starting_lba <= entry->ending_lba); + + if (start_sector) *start_sector = entry->starting_lba; + if (size) *size = entry->ending_lba - entry->starting_lba + 1; + return GPT_SUCCESS; } +/* Given a update_type, this function updates the corresponding bits in GptData. + * + * Returns GPT_SUCCESS if no error. gpt->modified is set if any header and + * entries needs to be updated to hard drive. + * GPT_ERROR_INVALID_UPDATE_TYPE if given an invalid update_type. + */ int GptUpdateKernelEntry(GptData *gpt, uint32_t update_type) { - /* FIXME: the following code is not really code, just return anything */ - gpt->modified |= (GPT_MODIFIED_HEADER1 | GPT_MODIFIED_ENTRIES1) << - gpt->current_kernel; + Guid chromeos_type = GPT_ENT_TYPE_CHROMEOS_KERNEL; + int primary_is_modified = 0; + + assert(gpt->current_kernel != CGPT_KERNEL_ENTRY_NOT_FOUND); + assert(!Memcmp(&(GetEntry(gpt, PRIMARY, gpt->current_kernel)->type), + &chromeos_type, sizeof(Guid))); + + /* Modify primary entries first, then copy to secondary later. */ + switch (update_type) { + case GPT_UPDATE_ENTRY_TRY: { + /* Increase tries value until CGPT_ATTRIBUTE_MAX_TRIES. */ + int tries; + tries = GetTries(gpt, PRIMARY, gpt->current_kernel); + if (tries < CGPT_ATTRIBUTE_MAX_TRIES) { + ++tries; + SetTries(gpt, PRIMARY, gpt->current_kernel, tries); + primary_is_modified = 1; + } + break; + } + case GPT_UPDATE_ENTRY_BAD: { + GetEntry(gpt, PRIMARY, gpt->current_kernel)->attributes |= + CGPT_ATTRIBUTE_BAD_MASK; + primary_is_modified = 1; + break; + } + default: { + return GPT_ERROR_INVALID_UPDATE_TYPE; + } + } + + if (primary_is_modified) { + /* Claim only primary is valid so that secondary is overwritten. */ + RepairEntries(gpt, MASK_PRIMARY); + /* Actually two entries are dirty now. + * Also two headers are dirty because entries_crc32 has been updated. */ + gpt->modified |= (GPT_MODIFIED_HEADER1 | GPT_MODIFIED_ENTRIES1 | + GPT_MODIFIED_HEADER2 | GPT_MODIFIED_ENTRIES2); + UpdateCrc(gpt); + } + return GPT_SUCCESS; } diff --git a/cgptlib/cgpt.h b/cgptlib/cgpt.h index bc81fd6289..1f9f230515 100644 --- a/cgptlib/cgpt.h +++ b/cgptlib/cgpt.h @@ -16,6 +16,7 @@ enum { GPT_ERROR_INVALID_ENTRIES, GPT_ERROR_INVALID_SECTOR_SIZE, GPT_ERROR_INVALID_SECTOR_NUMBER, + GPT_ERROR_INVALID_UPDATE_TYPE, }; /* Bit masks for GptData.modified field. */ @@ -24,12 +25,18 @@ enum { #define GPT_MODIFIED_ENTRIES1 0x04 #define GPT_MODIFIED_ENTRIES2 0x08 -#define GPT_UPDATE_ENTRY_TRY 1 +/* The 'update_type' of GptUpdateKernelEntry() + * We expose TRY and BAD only because those are what verified boot needs. + * For more precise control on GPT attribute bits, please refer to + * gpt_internal.h */ +enum { + GPT_UPDATE_ENTRY_TRY = 1, /* System will be trying to boot the currently selected kernel partition. * Update its try count if necessary. */ -#define GPT_UPDATE_ENTRY_BAD 2 + GPT_UPDATE_ENTRY_BAD = 2, /* The currently selected kernel partition failed validation. Mark entry as * invalid. */ +}; /* Defines ChromeOS-specific limitation on GPT */ #define MIN_SIZE_OF_HEADER 92 @@ -79,17 +86,25 @@ typedef struct { * 0x08 = table2 */ /* Internal state */ - uint8_t current_kernel; /* the current kernel index */ + int current_kernel; /* the current chromeos kernel index in partition table. + * -1 means not found on drive. */ } GptData; int GptInit(GptData *gpt); -/* Initializes the GPT data structure's internal state. The header1, header2, - * table1, table2, and drive_size fields should be filled in first. +/* Initializes the GPT data structure's internal state. The following fields + * must be filled before calling this function: + * + * primary_header + * secondary_header + * primary_entries + * secondary_entries + * sector_bytes + * drive_sectors * * On return the modified field may be set, if the GPT data has been modified * and should be written to disk. * - * Returns 0 if successful, non-zero if error: + * Returns GPT_SUCCESS if successful, non-zero if error: * GPT_ERROR_INVALID_HEADERS, both partition table headers are invalid, enters * recovery mode, * GPT_ERROR_INVALID_ENTRIES, both partition table entries are invalid, enters @@ -104,7 +119,7 @@ int GptNextKernelEntry(GptData *gpt, uint64_t *start_sector, uint64_t *size); * for the start of the kernel partition, and the size parameter contains the * size of the kernel partition in LBA sectors. * - * Returns 0 if successful, else + * Returns GPT_SUCCESS if successful, else * GPT_ERROR_NO_VALID_KERNEL, no avaliable kernel, enters recovery mode */ int GptUpdateKernelEntry(GptData *gpt, uint32_t update_type); @@ -114,6 +129,8 @@ int GptUpdateKernelEntry(GptData *gpt, uint32_t update_type); * On return the modified field may be set, if the GPT data has been modified * and should be written to disk. * - * Returns 0 if successful, 1 if error. */ + * Returns GPT_SUCCESS if successful, else + * GPT_ERROR_INVALID_UPDATE_TYPE, invalid 'update_type' is given. + */ #endif /* VBOOT_REFERENCE_CGPT_H_ */ diff --git a/cgptlib/cgpt_internal.h b/cgptlib/cgpt_internal.h index 0f56d44b3e..c65f466b93 100644 --- a/cgptlib/cgpt_internal.h +++ b/cgptlib/cgpt_internal.h @@ -9,8 +9,6 @@ #include #include "cgpt.h" -/* Internal use only. - * Don't use them unless you know what you are doing. */ int CheckParameters(GptData *gpt); uint32_t CheckHeaderSignature(GptData *gpt); uint32_t CheckRevision(GptData *gpt); @@ -33,4 +31,52 @@ typedef struct { uint64_t ending; } pair_t; +GptEntry *GetEntry(GptData *gpt, int secondary, int entry_index); +void SetPriority(GptData *gpt, int secondary, int entry_index, int priority); +int GetPriority(GptData *gpt, int secondary, int entry_index); +void SetBad(GptData *gpt, int secondary, int entry_index, int bad); +int GetBad(GptData *gpt, int secondary, int entry_index); +void SetTries(GptData *gpt, int secondary, int entry_index, int tries); +int GetTries(GptData *gpt, int secondary, int entry_index); +void SetSuccess(GptData *gpt, int secondary, int entry_index, int success); +int GetSuccess(GptData *gpt, int secondary, int entry_index); + +/* If gpt->current_kernel is this value, means either: + * 1. an initial value before scanning GPT entries, + * 2. after scanning, no any valid kernel is found. + */ +#define CGPT_KERNEL_ENTRY_NOT_FOUND (-1) + +/* Bit definitions and masks for GPT attributes. + * + * 63 -- do not automounting + * 62 -- hidden + * 60 -- read-only + * : + * 57 -- bad kernel entry + * 56 -- success + * 55,52 -- tries + * 51,48 -- priority + * 0 -- system partition + */ +#define CGPT_ATTRIBUTE_BAD_OFFSET 57 +#define CGPT_ATTRIBUTE_MAX_BAD (1ULL) +#define CGPT_ATTRIBUTE_BAD_MASK (CGPT_ATTRIBUTE_MAX_BAD << \ + CGPT_ATTRIBUTE_BAD_OFFSET) + +#define CGPT_ATTRIBUTE_SUCCESS_OFFSET 56 +#define CGPT_ATTRIBUTE_MAX_SUCCESS (1ULL) +#define CGPT_ATTRIBUTE_SUCCESS_MASK (CGPT_ATTRIBUTE_MAX_SUCCESS << \ + CGPT_ATTRIBUTE_SUCCESS_OFFSET) + +#define CGPT_ATTRIBUTE_TRIES_OFFSET 52 +#define CGPT_ATTRIBUTE_MAX_TRIES (15ULL) +#define CGPT_ATTRIBUTE_TRIES_MASK (CGPT_ATTRIBUTE_MAX_TRIES << \ + CGPT_ATTRIBUTE_TRIES_OFFSET) + +#define CGPT_ATTRIBUTE_PRIORITY_OFFSET 48 +#define CGPT_ATTRIBUTE_MAX_PRIORITY (15ULL) +#define CGPT_ATTRIBUTE_PRIORITY_MASK (CGPT_ATTRIBUTE_MAX_PRIORITY << \ + CGPT_ATTRIBUTE_PRIORITY_OFFSET) + #endif /* VBOOT_REFERENCE_CGPT_INTERNAL_H_ */ diff --git a/cgptlib/tests/cgpt_test.c b/cgptlib/tests/cgpt_test.c index 933d3a2cb9..964232d6e6 100644 --- a/cgptlib/tests/cgpt_test.c +++ b/cgptlib/tests/cgpt_test.c @@ -15,17 +15,23 @@ /* Testing partition layout (sector_bytes=512) * * LBA Size Usage + * --------------------------------------------------------- * 0 1 PMBR * 1 1 primary partition header * 2 32 primary partition entries (128B * 128) - * 34 100 kernel A - * 134 100 kernel B - * 234 100 root A - * 334 100 root B + * 34 100 kernel A (index: 0) + * 134 100 root A (index: 1) + * 234 100 root B (index: 2) + * 334 100 kernel B (index: 3) * 434 32 secondary partition entries * 466 1 secondary partition header * 467 */ +#define KERNEL_A 0 +#define ROOTFS_A 1 +#define ROOTFS_B 2 +#define KERNEL_B 3 + #define DEFAULT_SECTOR_SIZE 512 #define MAX_SECTOR_SIZE 4096 #define DEFAULT_DRIVE_SECTORS 467 @@ -87,6 +93,9 @@ GptData* GetEmptyGptData() { gpt.secondary_entries = secondary_entries; ZeroHeadersEntries(&gpt); + /* Initialize GptData internal states. */ + gpt.current_kernel = CGPT_KERNEL_ENTRY_NOT_FOUND; + return &gpt; } @@ -99,9 +108,11 @@ void BuildTestGptData(GptData *gpt) { GptHeader *header, *header2; GptEntry *entries, *entries2; Guid chromeos_kernel = GPT_ENT_TYPE_CHROMEOS_KERNEL; + Guid chromeos_rootfs = GPT_ENT_TYPE_CHROMEOS_ROOTFS; gpt->sector_bytes = DEFAULT_SECTOR_SIZE; gpt->drive_sectors = DEFAULT_DRIVE_SECTORS; + gpt->current_kernel = CGPT_KERNEL_ENTRY_NOT_FOUND; /* build primary */ header = (GptHeader*)gpt->primary_header; @@ -119,10 +130,10 @@ void BuildTestGptData(GptData *gpt) { Memcpy(&entries[0].type, &chromeos_kernel, sizeof(chromeos_kernel)); entries[0].starting_lba = 34; entries[0].ending_lba = 133; - Memcpy(&entries[1].type, &chromeos_kernel, sizeof(chromeos_kernel)); + Memcpy(&entries[1].type, &chromeos_rootfs, sizeof(chromeos_rootfs)); entries[1].starting_lba = 134; entries[1].ending_lba = 233; - Memcpy(&entries[2].type, &chromeos_kernel, sizeof(chromeos_kernel)); + Memcpy(&entries[2].type, &chromeos_rootfs, sizeof(chromeos_rootfs)); entries[2].starting_lba = 234; entries[2].ending_lba = 333; Memcpy(&entries[3].type, &chromeos_kernel, sizeof(chromeos_kernel)); @@ -888,6 +899,223 @@ int CorruptCombinationTest() { return TEST_OK; } +/* Invalidate all kernel entries and expect GptNextKernelEntry() cannot find + * any usable kernel entry. + */ +int NoValidKernelEntryTest() { + GptData *gpt; + GptEntry *entries, *entries2; + + gpt = GetEmptyGptData(); + entries = (GptEntry*)gpt->primary_entries; + entries2 = (GptEntry*)gpt->secondary_entries; + + BuildTestGptData(gpt); + entries[KERNEL_A].attributes |= CGPT_ATTRIBUTE_BAD_MASK; + Memset(&entries[KERNEL_B].type, 0, sizeof(Guid)); + RefreshCrc32(gpt); + + EXPECT(GPT_ERROR_NO_VALID_KERNEL == GptNextKernelEntry(gpt, NULL, NULL)); + + return TEST_OK; +} + +/* This is the combination test. Both kernel A and B could be either inactive + * or invalid. We expect GptNextKetnelEntry() returns good kernel or + * GPT_ERROR_NO_VALID_KERNEL if no kernel is available. */ +enum FAILURE_MASK { + MASK_INACTIVE = 1, + MASK_BAD_ENTRY = 2, + MASK_FAILURE_BOTH = 3, +}; +void BreakAnEntry(GptEntry *entry, enum FAILURE_MASK failure) { + if (failure & MASK_INACTIVE) + Memset(&entry->type, 0, sizeof(Guid)); + if (failure & MASK_BAD_ENTRY) + entry->attributes |= CGPT_ATTRIBUTE_BAD_MASK; +} + +int CombinationalNextKernelEntryTest() { + GptData *gpt; + enum { + MASK_KERNEL_A = 1, + MASK_KERNEL_B = 2, + MASK_KERNEL_BOTH = 3, + } kernel; + enum FAILURE_MASK failure; + uint64_t start_sector, size; + int retval; + + for (kernel = MASK_KERNEL_A; kernel <= MASK_KERNEL_BOTH; ++kernel) { + for (failure = MASK_INACTIVE; failure < MASK_FAILURE_BOTH; ++failure) { + gpt = GetEmptyGptData(); + BuildTestGptData(gpt); + + if (kernel & MASK_KERNEL_A) + BreakAnEntry(GetEntry(gpt, PRIMARY, KERNEL_A), failure); + if (kernel & MASK_KERNEL_B) + BreakAnEntry(GetEntry(gpt, PRIMARY, KERNEL_B), failure); + + retval = GptNextKernelEntry(gpt, &start_sector, &size); + + if (kernel == MASK_KERNEL_A) { + EXPECT(retval == GPT_SUCCESS); + EXPECT(start_sector == 334); + } else if (kernel == MASK_KERNEL_B) { + EXPECT(retval == GPT_SUCCESS); + EXPECT(start_sector == 34); + } else { /* MASK_KERNEL_BOTH */ + EXPECT(retval == GPT_ERROR_NO_VALID_KERNEL); + } + } + } + return TEST_OK; +} + +/* Increase tries value from zero, expect it won't explode/overflow after + * CGPT_ATTRIBUTE_TRIES_MASK. + */ +/* Tries would not count up after CGPT_ATTRIBUTE_MAX_TRIES. */ +#define EXPECTED_TRIES(tries) \ + ((tries >= CGPT_ATTRIBUTE_MAX_TRIES) ? CGPT_ATTRIBUTE_MAX_TRIES \ + : tries) +int IncreaseTriesTest() { + GptData *gpt; + int kernel_index[] = { + KERNEL_B, + KERNEL_A, + }; + int i, tries, j; + + gpt = GetEmptyGptData(); + for (i = 0; i < ARRAY_SIZE(kernel_index); ++i) { + GptEntry *entries[2] = { + (GptEntry*)gpt->primary_entries, + (GptEntry*)gpt->secondary_entries, + }; + int current; + + BuildTestGptData(gpt); + current = gpt->current_kernel = kernel_index[i]; + + for (tries = 0; tries < 2 * CGPT_ATTRIBUTE_MAX_TRIES; ++tries) { + for (j = 0; j < ARRAY_SIZE(entries); ++j) { + EXPECT(EXPECTED_TRIES(tries) == + ((entries[j][current].attributes & CGPT_ATTRIBUTE_TRIES_MASK) >> + CGPT_ATTRIBUTE_TRIES_OFFSET)); + } + + EXPECT(GPT_SUCCESS == GptUpdateKernelEntry(gpt, GPT_UPDATE_ENTRY_TRY)); + /* The expected tries value will be checked in next iteration. */ + + if (tries < CGPT_ATTRIBUTE_MAX_TRIES) + EXPECT((GPT_MODIFIED_HEADER1 | GPT_MODIFIED_ENTRIES1 | + GPT_MODIFIED_HEADER2 | GPT_MODIFIED_ENTRIES2) == gpt->modified); + gpt->modified = 0; /* reset before next test */ + EXPECT(0 == + Memcmp(entries[PRIMARY], entries[SECONDARY], TOTAL_ENTRIES_SIZE)); + } + } + return TEST_OK; +} + +/* Mark a kernel as bad. Expect: + * 1. the both bad bits of kernel A in primary and secondary entries are set. + * 2. headers and entries are marked as modified. + * 3. primary and secondary entries are identical. + */ +int MarkBadKernelEntryTest() { + GptData *gpt; + GptEntry *entries, *entries2; + + gpt = GetEmptyGptData(); + entries = (GptEntry*)gpt->primary_entries; + entries2 = (GptEntry*)gpt->secondary_entries; + + BuildTestGptData(gpt); + gpt->current_kernel = KERNEL_A; + EXPECT(GPT_SUCCESS == GptUpdateKernelEntry(gpt, GPT_UPDATE_ENTRY_BAD)); + EXPECT((GPT_MODIFIED_HEADER1 | GPT_MODIFIED_ENTRIES1 | + GPT_MODIFIED_HEADER2 | GPT_MODIFIED_ENTRIES2) == gpt->modified); + EXPECT(entries[KERNEL_A].attributes & CGPT_ATTRIBUTE_BAD_MASK); + EXPECT(entries2[KERNEL_A].attributes & CGPT_ATTRIBUTE_BAD_MASK); + EXPECT(0 == Memcmp(entries, entries2, TOTAL_ENTRIES_SIZE)); + + return TEST_OK; +} + +/* Given an invalid kernel type, and expect GptUpdateKernelEntry() returns + * GPT_ERROR_INVALID_UPDATE_TYPE. */ +int UpdateInvalidKernelTypeTest() { + GptData *gpt; + + gpt = GetEmptyGptData(); + BuildTestGptData(gpt); + gpt->current_kernel = 0; /* anything, but not CGPT_KERNEL_ENTRY_NOT_FOUND */ + EXPECT(GPT_ERROR_INVALID_UPDATE_TYPE == + GptUpdateKernelEntry(gpt, 99)); /* any invalid update_type value */ + + return TEST_OK; +} + +/* A normal boot case: + * GptInit() + * GptNextKernelEntry() + * GptUpdateKernelEntry() + */ +int NormalBootCase() { + GptData *gpt; + GptEntry *entries; + uint64_t start_sector, size; + + gpt = GetEmptyGptData(); + entries = (GptEntry*)gpt->primary_entries; + BuildTestGptData(gpt); + + EXPECT(GPT_SUCCESS == GptInit(gpt)); + EXPECT(GPT_SUCCESS == GptNextKernelEntry(gpt, &start_sector, &size)); + EXPECT(start_sector == 34); /* Kernel A, see top of this file. */ + EXPECT(size == 100); + + EXPECT(GPT_SUCCESS == GptUpdateKernelEntry(gpt, GPT_UPDATE_ENTRY_TRY)); + EXPECT(((entries[KERNEL_A].attributes & CGPT_ATTRIBUTE_TRIES_MASK) >> + CGPT_ATTRIBUTE_TRIES_OFFSET) == 1); + + return TEST_OK; +} + +/* Higher priority kernel should boot first. + * KERNEL_A is low priority + * KERNEL_B is high priority. + * We expect KERNEL_B is selected in first run, and then KERNEL_A. + * We also expect the GptNextKernelEntry() wraps back to KERNEL_B if it's called + * after twice. + */ +int HigherPriorityTest() { + GptData *gpt; + GptEntry *entries; + + gpt = GetEmptyGptData(); + entries = (GptEntry*)gpt->primary_entries; + BuildTestGptData(gpt); + + SetPriority(gpt, PRIMARY, KERNEL_A, 0); + SetPriority(gpt, PRIMARY, KERNEL_B, 1); + RefreshCrc32(gpt); + + EXPECT(GPT_SUCCESS == GptInit(gpt)); + EXPECT(GPT_SUCCESS == GptNextKernelEntry(gpt, NULL, NULL)); + EXPECT(KERNEL_B == gpt->current_kernel); + + EXPECT(GPT_SUCCESS == GptNextKernelEntry(gpt, NULL, NULL)); + EXPECT(KERNEL_A == gpt->current_kernel); + + EXPECT(GPT_SUCCESS == GptNextKernelEntry(gpt, NULL, NULL)); + EXPECT(KERNEL_B == gpt->current_kernel); + + return TEST_OK; +} + int main(int argc, char *argv[]) { int i; int error_count = 0; @@ -916,6 +1144,13 @@ int main(int argc, char *argv[]) { { TEST_CASE(CorruptCombinationTest), }, { TEST_CASE(TestQuickSortFixed), }, { TEST_CASE(TestQuickSortRandom), }, + { TEST_CASE(NoValidKernelEntryTest), }, + { TEST_CASE(CombinationalNextKernelEntryTest), }, + { TEST_CASE(IncreaseTriesTest), }, + { TEST_CASE(MarkBadKernelEntryTest), }, + { TEST_CASE(UpdateInvalidKernelTypeTest), }, + { TEST_CASE(NormalBootCase), }, + { TEST_CASE(HigherPriorityTest), }, }; for (i = 0; i < sizeof(test_cases)/sizeof(test_cases[0]); ++i) {