-
Notifications
You must be signed in to change notification settings - Fork 2
Use gptprio to select boot target #13
Conversation
bgilbert
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be fine to defer tries and shim handoff to a followup PR.
src/util/gpt.rs
Outdated
| } | ||
|
|
||
| block_io | ||
| .read_blocks(self.my_lba, 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaked.
Also, why does this function read the block again rather than validating the data in the struct it was asked to validate?
src/util/gpt.rs
Outdated
| (self.num_partition_entries * self.sizeof_partition_entry) as usize; | ||
|
|
||
| block_io | ||
| .read_bytes(self.partition_entry_lba, partition_entry_table_size) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaked.
src/util/gpt.rs
Outdated
| pub struct GptDisk<'a> { | ||
| block_device: &'static BlockIOProtocol, | ||
| primary_header: &'a GptHeader, | ||
| alternate_header: &'a GptHeader, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be freed on drop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are; see lines 43-47. When a struct is dropped, its Drop implementation is run, and then each of its children has their Drop implementation run (see here for details). Since GptDisk itself requires no special logic to drop, it doesn't need a Drop implementation since the Drop for GptHeader will automatically be run for its children.
src/util/gpt.rs
Outdated
| self.block_device | ||
| .read_blocks(self.primary_header.alternate_lba, 1) | ||
| .and_then(|block| { | ||
| self.alternate_header = &*(block.as_ptr() as *const GptHeader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be happening in a function called validate. If the alternate header needs to be loaded only after the primary has been validated, this function should be renamed or merged into read_from.
src/util/gpt.rs
Outdated
| .read_bytes( | ||
| self.primary_header.partition_entry_lba, | ||
| partition_entry_table_size, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaked.
Also, this is duplicated in the validation code. Perhaps read_from should load all of the bits, validate should check them without loading anything itself, and read_from can generate the partition data structures as well. (validate can be split into two parts for this if necessary.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually isn't leaked; read_partitions returns a slice of GptPartitionEntry references, not GptPartitionEntrys, since GPT partition entries are variable length, and therefore cannot be stored directly in a slice that way.
src/picker.rs
Outdated
| .and_then(parent_device_path) | ||
| .and_then(|parent_path| { | ||
| util::GptDisk::read_from(parent_path) | ||
| .and_then(|disk| disk.read_partitions().map(util::gptprio::next)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the nesting here?
src/picker.rs
Outdated
| let boot_result = menu::boot_menu(&option_a, &option_b) | ||
| .and_then(|option| { | ||
| match option { | ||
| Some(user_choice) => Ok(user_choice), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily a user_choice, since it could have defaulted.
src/picker.rs
Outdated
| Some(user_choice) => Ok(user_choice), | ||
| None => { | ||
| cons.write( | ||
| "User chose nothing and no default was set. Can't proceed.\r\n", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like no option selected and... would be a bit better.
On boot, determine the partition gptprio specifies, and use it as the default. As part of the boot menu, display the default choice to the user.
|
@bgilbert Updated. I think the new |
src/util/gpt.rs
Outdated
| let alternate_header = unsafe { &mut *(alternate_block.as_mut_ptr() as *mut GptHeader) }; | ||
|
|
||
| out.primary_header = primary_header; | ||
| out.alternate_header = alternate_header; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you're not using out before now, it seems that you can defer its creation until after the read_blocks calls and avoid the null_mut initializations.
src/util/gpt.rs
Outdated
| self.alternate_header | ||
| .validate(self.primary_header.alternate_lba)?; | ||
|
|
||
| let partitions = self.read_partitions_raw()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaked.
It still seems as though these should be loaded once and stored, rather than loaded in validate and then thrown away.
src/util/gpt.rs
Outdated
| } | ||
|
|
||
| impl Drop for GptHeader { | ||
| fn drop(&mut self) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces a leak if let alternate_block = ... fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, there's no way for a GptHeader to know how to free itself due to the allocate_pages/allocate_pool split. The best way to get around this is probably to introduce a type wrapping GptHeader that also stores how to free itself.
src/util/gpt.rs
Outdated
| let bs = uefi::get_system_table().boot_services(); | ||
|
|
||
| let num_partitions = self.primary_header.num_partition_entries as usize; | ||
| let partition_entry_table = self.read_partitions_raw()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaked. On error we can do something about that, and on success... it would be nice if we had a way to clean this up after the return value is dropped.
I believe the caller has an undocumented responsibility to free the returned array.
By splitting up the validation stage into validate_headers() and validate_partitions(), and splitting each of those into a reading and validation stage, leaks are avoided.
| partitions: &[], | ||
| }; | ||
|
|
||
| unsafe { out.validate_headers()? }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if we fail here and Drop tries to free raw_partitions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of free_pages (and free_pool) in rust-uefi ignores failure, so the actual UEFI call will fail, but that failure won't be passed along to picker, so from picker's perspective, everything will be fine.
| bs.free_pool(self.alternate_header); | ||
| } | ||
|
|
||
| self.block_device.free_read(self.raw_partitions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The partitions array is not freed.
| let primary_block = protocol.read_blocks(1, 1)?; | ||
| let primary_header = unsafe { &mut *(primary_block.as_mut_ptr() as *mut GptHeader) }; | ||
|
|
||
| let alternate_block = protocol.read_blocks(primary_header.alternate_lba, 1)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you spell out the error check here you can avoid the primary_header leak.
|
Since I have a few more things to change with this PR and there's apparently no way for me to update this one, I'll reopen a new PR once I have things figured out. |
This implementation of gptprio is read-only, in that it doesn't update the
triesfield of gptprio. It also does not pass anything to shim instructing it what to boot; if desired I can add that for this PR.Before this PR is merged, csssuf/rust-uefi#18 will need to be merged and a commit bumping
Cargo.lockwill need to be added to this PR.