Skip to content

Conversation

guenhter
Copy link

@guenhter guenhter commented Feb 11, 2025

Support extending primary partitions to the maximum available free trailing space + fix some clippy warnings.

I've to admit not to be the expert in the whole MBR area. Review and comments appreciated.

Closes #32

When primary partitions have some trailing free
space, this PR allows to extend the partition
to use up all trailing free space. Currently,
only primary partitions are supported to be
extended.
@cecton
Copy link
Member

cecton commented Feb 11, 2025

Ok I'm even more confused now because I don't think this answers to the original issue (yours) that was to resize the "disk" (I assumed the MBR partition table) and not a partition.

Let's first go back to the original issue to clear out everything first.

@guenhter
Copy link
Author

:) Yeah, I somehow expressed myself not clearly. I really meant to extend the partition.

@guenhter guenhter closed this Feb 11, 2025
@guenhter guenhter deleted the support-extending branch February 11, 2025 12:50
Copy link
Member

@cecton cecton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be way shorter after the changes so you can also reduce the amount and the size of the tests.

/// .expect("could not read the partition table");
/// ```
pub fn read_from<R: ?Sized>(mut reader: &mut R, sector_size: u32) -> Result<MBR>
pub fn read_from<S>(mut reader: &mut S, sector_size: u32) -> Result<MBR>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we definitely need an extra function to make it easy for users to update the disk_size field of the MBR. So, we still need to add a function—similar to the one in gptman—that updates this information using the provided seekable object.

For reference, in gptman, this function updates various fields based on the seekable object:

/// Updates the header to match the specifications of the seeker given in argument.
/// `first_usable_lba`, `last_usable_lba`, `primary_lba`, `backup_lba`, `partition_entry_lba`
/// will be updated after this operation.
pub fn update_from<S: ?Sized>(&mut self, seeker: &mut S, sector_size: u64) -> Result<()>
where
    S: Seek,
{

In MBR, it's actually much simpler because there’s only one field to update: disk_size. However, it’s important to remember that this value is expressed in sectors! The function should look something like this:

self.disk_size = u32::try_from(reader.seek(SeekFrom::End(0))? / u64::from(sector_size))
    .unwrap_or(u32::max_value());

Why is this needed?

find_free_sectors() relies on disk_size to determine available space. It will never suggest free sectors beyond what the MBR partition table is supposed to handle.

A practical example:
Imagine swapping out your hard drive for a larger one and cloning (dd) your entire old disk onto the new one. The first thing you'd need to do is update the MBR header with the new disk size. Without this, the system wouldn’t recognize the newly available free space.

Let me know what you think! Happy to discuss further. 😊

Comment on lines +967 to +968
/// Currently, this function only supports primary partitions (index 1-4).
/// Logical partitions (index > 4) are not supported and will return an InvalidIndex error.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use find_free_sectors, this will work on all partitions. No need to implement anything special.

/// assert_eq!(mbr[1].sectors, 40, "Partition should now be 40 sectors");
/// assert_eq!(mbr[1].starting_lba + mbr[1].sectors, mbr[2].starting_lba);
/// ```
pub fn extend_partition(&mut self, partition_index: usize) -> Result<u32> {
Copy link
Member

@cecton cecton Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function currently only extends a partition to its full possible size. I think we should split it into two separate functions for better clarity and usability:

  1. A function to resize a partition to a specific size.
    This function should take a target size as an argument and update the partition’s sectors field accordingly. No need to update first_chs and last_chs as this is done for us already when writing the partition table on disk anyway.
    [edit: does not sound very useful, it just does sectors = ... so I don't see the point of adding it at all actually]

  2. A function to determine the maximum possible size for a given partition.
    This function should use find_free_sectors() to identify the available space, as demonstrated in the test example below:

    let ss = 512_u32;
    let data = vec![0; 10 * ss as usize];
    let mut cur = Cursor::new(data);
    let mut mbr = MBR::new_from(&mut cur, ss, [0xff; 4]).unwrap();
    mbr.align = 1;
    mbr.header.partition_1.sys = 0x83;
    mbr.header.partition_1.starting_lba = 1;
    mbr.header.partition_1.sectors = 1;
    mbr.header.partition_3.sys = 0x0f;
    mbr.header.partition_3.starting_lba = 5;
    mbr.header.partition_3.sectors = 5;
    let spots = mbr.find_free_sectors();
    let p = &mut mbr.header.partition_1;
    let the_spot = spots.into_iter().find(|(i, _)| *i == p.starting_lba + p.sectors);
    assert_eq!(the_spot, Some((2, 3)));
    let (_, free_sectors) = the_spot.unwrap();

By separating these concerns, we make the API clearer and more flexible, allowing users to either resize a partition to a specific size or extend it as much as possible based on available space. Let me know what you think!

Comment on lines +2309 to +2316
// Verify CHS addresses were updated
assert!(!mbr[1].last_chs.is_empty(), "Expected last_chs to be set");
assert!(
mbr[1]
.last_chs
.is_valid(mbr.cylinders, mbr.heads, mbr.sectors),
"Expected last_chs to be valid for disk geometry"
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is already tested elsewhere, no need to to this

Comment on lines +2260 to +2266
// Set disk geometry
mbr.cylinders = total_cylinders as u16;
mbr.heads = 16;
mbr.sectors = 63;

// Calculate cylinder size
let cylinder_size = (mbr.sectors as u32) * (mbr.heads as u32);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't bother with geometry at all. It's being taken care of and tested elsewhere. I prefer to keep the test as short as possible and really focus on what it is testing.

@cecton
Copy link
Member

cecton commented Feb 11, 2025

So you're sure you don't want to this? I'm going to create another PR now unless you want to do the contribution.

@guenhter
Copy link
Author

Yes. I'm sure this is way to complicated and your proposal is sufficient for me.

@cecton cecton mentioned this pull request Feb 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Question: Resize

2 participants