Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full Discoverable Partitions Specification compatibility (WIP) #98

Closed
wants to merge 3 commits into from

Conversation

rdvdev2
Copy link
Contributor

@rdvdev2 rdvdev2 commented Sep 6, 2021

The current booster Discoverable Partitions Specification support is pretty basic and doesn't work for every case. This PR aims to fully support the specification, allowing to auto-discover encrypted partitions or mount root read-only, among other features.

WARNING: This PR is really a WIP, and not properly tested. At the moment, this PR is only provided to get feedback while developing the feature(s).

Progress:

  • Unencrypted root support
  • Encrypted root support (LUKS)
  • Scan only the disk containing the used ESP
  • Honour the GPT flags (read-only mounting and partition skipping)
  • Dm-verity? (Not familiar with it neither sure if Arch or booster support it)
  • Documentation?
  • Tests? (Possibly on the GPT flag reading process)
  • Works on VM (Not tested yet)
  • Works on real hardware (Not tested yet)

Copy link
Owner

@anatol anatol left a comment

Choose a reason for hiding this comment

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

The idea of implementing discoverable partitions great. The code looks clean, good work! I left a few comments about this implementation.

It needs more tests and I can help you with it if needed (setting up integration tests is tricky currently).

@@ -619,6 +622,15 @@ func boost() error {
return err
}

// Mount efivarfs if running in EFI mode
if _, err := os.Stat("/sys/firmware/efi"); errors.Is(err, os.ErrNotExist) {
if err := mount("efivarfs", "/sys/firmware/efi/efivars", "efivarfs", unix.MS_NOSUID|unix.MS_NOEXEC|unix.MS_NODEV, ""); err != nil {
Copy link
Owner

Choose a reason for hiding this comment

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

I think we need to unmount it as well before switching to the user's root fs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Where should I unmount (function)?

Copy link
Owner

Choose a reason for hiding this comment

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

SeemoveMountpointsToHost() function. First it tries to move the mountpoint to host. If it does not work (e.g. new root does not contain the directory) then it unmounts the filesystem from initram fs tree.

The moveMountpointsToHost() should handle /sys/firmware/efi mountpoint as well. I wonder if MS_MOVE and MNT_DETACH handle mountpoints recursively.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've been reading through the documentation and I think that moving the mountpoint isn't needed at all. It looks like systemd handles all the mountpoints but /run. Take a look at the specification, it may even be the case that the current code on moveMountpointsToHost() is mostly unneeded.

Copy link
Owner

Choose a reason for hiding this comment

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

It actually used to be like mentioned in this specification. But it turns out that other init systems like runit expect /dev/ to be mounted at the boot time. See #92 and 4c95058

I am fine to leave this code as-is and wait for integration tests results to see if runit tests pass.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then I'll try to get unit tests ready today and see if it works.

Copy link
Owner

Choose a reason for hiding this comment

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

I ran your PR with VoidLinux and ArchLinux+btrfs tests and they passed.

@rdvdev2
Copy link
Contributor Author

rdvdev2 commented Sep 8, 2021

It needs more tests and I can help you with it if needed (setting up integration tests is tricky currently).

I don't really know how testing works with this project, so help would be really appreciated.

@anatol
Copy link
Owner

anatol commented Sep 8, 2021

I don't really know how testing works with this project, so help would be really appreciated.

The integration test are located at tests directory. Each test contains from 2 parts:

  1. a bash script that creates a host image (see tests/generate_asset_*.sh)
  2. golang test that runs QEMU with this host image

If you can take care of 1 then I'll add QEMU tests for it. See generate_asset_gpt.sh that creates an image with a GPT and multiple partitions. You can even run this script from you shell with OUTPUT=assets/gpt.img FS_UUID=e5404205-ac6a-4e94-bb3b-14433d0af7d1 FS_LABEL=newpart bash -x -o errexit ./generate_asset_gpt.sh it will generate a valid image at assets/gpt.img. You probably want something similar to initialize an GPT with particular partition UUIDs.

As of formatting a partition with LUKS check generate_asset_luks.sh.

@anatol
Copy link
Owner

anatol commented Sep 23, 2021

Hey I just want to check with you how it is going. Do you need any help with testing?

@rdvdev2
Copy link
Contributor Author

rdvdev2 commented Sep 23, 2021

I'm sorry for the inactivity, last week I started university and I've been really busy since then. Now that I have some free days I'll hopefully be able to continue working on this. I'll let you know if I have any problems with the testing setup.

init/main.go Outdated
@@ -619,6 +622,15 @@ func boost() error {
return err
}

// Mount efivarfs if running in EFI mode
if _, err := os.Stat("/sys/firmware/efi"); errors.Is(err, os.ErrNotExist) {
Copy link
Owner

Choose a reason for hiding this comment

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

I believe it should be !errors.Is(err, os.ErrNotExist). if body should be executed if the efi dir not-not exists (i.e. exists).

@anatol
Copy link
Owner

anatol commented Sep 23, 2021

I pulled the latest changes from this PR and rebased on top of wip branch.

@anatol
Copy link
Owner

anatol commented Sep 23, 2021

I tried to ran a test that uses autodiscovery (e.g. go test -v -run TestBooster/Gpt.Autodiscovery) and I see that it requires EFI mode for QEMU now. One question here (and sorry if it is dumb one) - does autodiscovery work without EFI?

I enable QEMU EFI mode with -bios /usr/share/ovmf/x64/OVMF.fd parameters and now my test fails with open /sys/firmware/efi/efivars/LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f: no such file or directory. Is this efi var required for autodiscovery? What if it does not exist at a machine and no root= specified, should booster fail into emergency shell? Or is there a way to recover from this situation?

@rdvdev2
Copy link
Contributor Author

rdvdev2 commented Sep 24, 2021

The EFI var is needed to know which physical disk contains the auto-detected system. I think this is done to avoid loading the kernel from a LiveUSB and the root of a locally installed system (and other probably breaking situations). Knowing this, I'm not sure if it would be a good idea to go ahead and autodetect on all drives, unless there's another way to detect where the kernel was loaded from.

Maybe we could still autodetect if there's only one disk that follows the specification or only one of them contains partitions for the system architecture, but in any other case, I think that falling back to the emergency shell would be the most sensible option. Let me know what you think.

return &deviceRef{refName, calculateDevName(devPath, p.num)}
case refEspUUID:
if bytes.Equal(d.data.(UUID), p.uuid) {
return d.autodetectRoot(devPath, t)
Copy link
Owner

Choose a reason for hiding this comment

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

The autodetectRoot() function does not accept a partition but instead gets whole table. For me it looks like the autodetectRoot()` should be split into 2 parts

  1. calculate rootTypeGuid - move outside of the loop
  2. if bytes.Equal(rootTypeGuid, p.typeGUID) { - should be called inside the loop

anatol and others added 3 commits September 23, 2021 17:11
udev events might be too noisy. Move it to a separate kernel option
`booster.debug.udev` to make the main booster debug logs more readable.

Closes anatol#99
if enableAutodetect && efiVarsAvailable {
debug("%s= param is not specified. Use GPT partition autodiscovery.", name)

_, data, err := attributes.ReadEfivarsWithGuid("LoaderDevicePartUUID", *util.StringToGUID("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"))
Copy link
Owner

Choose a reason for hiding this comment

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

where does LoaderDevicePartUUID requirement come from? I checked https://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ and do not see any mention of this efi var.

Copy link
Owner

Choose a reason for hiding this comment

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

Ok, found it here the first partition with this type UUID on the disk containing the active EFI ESP is automatically mounted to the root directory /

Copy link
Owner

Choose a reason for hiding this comment

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

So basically we need to find a disk that contains the active EFI ESP. Is reading LoaderDevicePartUUID efivar and then iterating all GPT partitions the only way to do it?

@anatol
Copy link
Owner

anatol commented Sep 29, 2021

@rdvdev2 how do you feel if i pick this PR and complete it? I would like to do some refactoring of booster code along the way. I'll keep you as an author of this change.

@rdvdev2
Copy link
Contributor Author

rdvdev2 commented Sep 30, 2021

@rdvdev2 how do you feel if i pick this PR and complete it? I would like to do some refactoring of booster code along the way. I'll keep you as an author of this change.

Go ahead, I think I don't have enough spare time to work on this anymore. Thanks for understanding!

@anatol
Copy link
Owner

anatol commented Oct 2, 2021

@rdvdev2 I reworked your patches, made extra changes to booster codebase and added support for GPT partition attributes. I pushed the changes to wip branch. Please test it when you have a chance.

anatol pushed a commit that referenced this pull request Oct 3, 2021
Per the spec https://systemd.io/DISCOVERABLE_PARTITIONS/
only disks with active ESP need to be checked for discoverable partitions.

To find out what is active ESP we need to read efi variables at the
boot time. Then we skip all GPT tables that do not have this ESP.

This change has been started by Roger Díaz Viñolas as a part of PR #98
and later reworked by Anatol Pomozov.
anatol pushed a commit that referenced this pull request Oct 3, 2021
Per the spec https://systemd.io/DISCOVERABLE_PARTITIONS/
only disks with active ESP need to be checked for discoverable partitions.

To find out what is active ESP we need to read efi variables at the
boot time. Then we skip all GPT tables that do not have this ESP.

This change has been started by Roger Díaz Viñolas as a part of PR #98
and later reworked by Anatol Pomozov.
anatol pushed a commit that referenced this pull request Oct 3, 2021
Per the spec https://systemd.io/DISCOVERABLE_PARTITIONS/
only disks with active ESP need to be checked for discoverable partitions.

To find out what is active ESP we need to read efi variables at the
boot time. Then we skip all GPT tables that do not have this ESP.

This change has been started by Roger Díaz Viñolas as a part of PR #98
and later reworked by Anatol Pomozov.
anatol pushed a commit that referenced this pull request Oct 4, 2021
Per the spec https://systemd.io/DISCOVERABLE_PARTITIONS/
only disks with active ESP need to be checked for discoverable partitions.

To find out what is active ESP we need to read efi variables at the
boot time. Then we skip all GPT tables that do not have this ESP.

This change has been started by Roger Díaz Viñolas as a part of PR #98
and later reworked by Anatol Pomozov.
@anatol
Copy link
Owner

anatol commented Oct 7, 2021

The Discoverable Partitions support has been merged master and becomes part of 0.7 release.

Thank you for your work!

@anatol anatol closed this Oct 7, 2021
@rdvdev2
Copy link
Contributor Author

rdvdev2 commented Oct 10, 2021

Thank you too for your help (and sorry for not being able to finish this myself)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants