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

many: when building initramfs, pull files from the run system #225

Merged
merged 13 commits into from
Jan 23, 2024

Conversation

alfonsosanchezbeato
Copy link
Member

Previously we were building a rootfs from files at debian package build time, and the ubuntu-core-initramfs was generating the initramfs from this file. This PR changes this script so it pulls files from other packages when invoked, instead. This removes most of the build dependencies and makes maintenance of the package easier, as it does not need to be updated when systemd or other run-time dependencies are updated anymore.

Copy link
Contributor

@xnox xnox left a comment

Choose a reason for hiding this comment

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

Did you create initrd before and after the change and do they look the same?

or would you like me to build multiple kernel snaps side by side, for us to compare?

install_files(rules, dest_dir)
# Other needed stuff
out = subprocess.check_output(["dpkg-architecture", "-q",
"DEB_BUILD_MULTIARCH"]).decode("utf-8")
Copy link
Contributor

Choose a reason for hiding this comment

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

you definitely want DEB_HOST_MULTIARCH here (as in the native, default, current architecture one is currently running under)

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed

@@ -272,7 +272,173 @@ def add_modules_from_file(dest_d, kernel_root, modules_d, fw_d, conf_file, db,
)


def install_files(files, dest_dir):
proc_env = os.environ.copy()
proc_env["LP_PRELOAD"] = ""
Copy link
Contributor

Choose a reason for hiding this comment

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

do you mean LD_PRELOAD here instead?

Also this is called so many times, it seems like it will be worthwhile to add a small wrapper def check_call(*args, **kwargs) that wraps check_call without LD_PRELOAD.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, changed, good catch.

Not sure though about the wrapper to subprocess.check_call as it would not save as much, instead I have now done from subprocess import run, check_call, check_output so the package prefix is not needed, which is afaiu what is slightly annoying.


# This is useful if the destination path inside dest_dir is different
# from the current file path.
def install_file_to_path(file, dest_dir, dest_path):
Copy link
Contributor

Choose a reason for hiding this comment

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

potentially can be merged with above function and make dest_path an optional kwarg argument.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that would complicate too much the function to make it worth it tbh, because of the different arguments, the need to specify dest_path for all files if we have a list, etc.

[
r"dpkg -L systemd | "
r"grep -E '(^/lib/systemd/system|/modprobe\.d/|/sysctl\.d/|"
r"/rules\.d/|/tmpfiles\.d/|/bin/)'",
Copy link
Contributor

Choose a reason for hiding this comment

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

whilst grep works, using more pythonic functions for filtering might have been better. I would rather just slurp all the lines from dpkg, and then use python for filtering by using glob or re. Ideally one pattern at a time, explaining why we need it.

Copy link
Contributor

Choose a reason for hiding this comment

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

also multiple packages can be specified to dpkg call, which might work better for the set of related systemd .deb packages (i.e. systemd udev libudev1 systemd-sysv).

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed now

def install_busybox(dest_dir):
# Use busybox shell instead of dash
os.remove(os.path.join(dest_dir, "usr/bin/sh"))
os.remove(os.path.join(dest_dir, "usr/bin/dash"))
Copy link
Contributor

Choose a reason for hiding this comment

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

i would want to install these first, such that dractu-install later just uses existing ones, rather than try to override them. Because getting this at the start matters.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I've pulled now busybox first so there is no need to remove sh/dash.

"/sbin/fsck.vfat",
"/sbin/mkfs.ext4",
"/sbin/mkfs.vfat",
"/sbin/sfdisk",
Copy link
Contributor

Choose a reason for hiding this comment

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

we require merged-usr for many releases now, it would help static lists like these to use "/usr" prefixed paths already.

Copy link
Contributor

Choose a reason for hiding this comment

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

contents of .deb packages is moving to have everything built with "/usr" paths too in noble, but it is a slow process as packages upgrade.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed

# Copy systemd bits
install_systemd_files(main)
# copy busybox
install_busybox(main)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think installing busybox before systemd is safer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

bin/ubuntu-core-initramfs Show resolved Hide resolved
Copy link
Member Author

@alfonsosanchezbeato alfonsosanchezbeato left a comment

Choose a reason for hiding this comment

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

@xnox thanks for the quick review, I've addressed your comments now.

FTR, yes, to check correctness I have compared the final list of files with what the old approach produced. The are just some small differences that should be fine:

  1. Some links/binaries that used to be in /usr/bin are now in /usr/sbin: reboot, rmmod, e2fsck and others, which seems more correct to me as that is their path in the rootfs. Not sure how they were originally placed in /usr/bin.
  2. Due to an error in an invocation of dracut-install, we were pulling more libraries than needed in the initramfs (concretely, when calling it on /usr/lib/$(DEB_HOST_MULTIARCH)/plymouth/*.so, where we should have run it only for the plugins we are pulling - two-step, script, label-ft). I've fixed this so these unneeded libraries are not included anymore.

Fwiw, I've tested this locally by replacing initramfs in a 24 snap kernel and things were good, also CI seems to be happy.

@@ -272,7 +272,173 @@ def add_modules_from_file(dest_d, kernel_root, modules_d, fw_d, conf_file, db,
)


def install_files(files, dest_dir):
proc_env = os.environ.copy()
proc_env["LP_PRELOAD"] = ""
Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, changed, good catch.

Not sure though about the wrapper to subprocess.check_call as it would not save as much, instead I have now done from subprocess import run, check_call, check_output so the package prefix is not needed, which is afaiu what is slightly annoying.


# This is useful if the destination path inside dest_dir is different
# from the current file path.
def install_file_to_path(file, dest_dir, dest_path):
Copy link
Member Author

Choose a reason for hiding this comment

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

I think that would complicate too much the function to make it worth it tbh, because of the different arguments, the need to specify dest_path for all files if we have a list, etc.

[
r"dpkg -L systemd | "
r"grep -E '(^/lib/systemd/system|/modprobe\.d/|/sysctl\.d/|"
r"/rules\.d/|/tmpfiles\.d/|/bin/)'",
Copy link
Member Author

Choose a reason for hiding this comment

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

Changed now

def install_busybox(dest_dir):
# Use busybox shell instead of dash
os.remove(os.path.join(dest_dir, "usr/bin/sh"))
os.remove(os.path.join(dest_dir, "usr/bin/dash"))
Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I've pulled now busybox first so there is no need to remove sh/dash.

install_files(rules, dest_dir)
# Other needed stuff
out = subprocess.check_output(["dpkg-architecture", "-q",
"DEB_BUILD_MULTIARCH"]).decode("utf-8")
Copy link
Member Author

Choose a reason for hiding this comment

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

Changed

"/sbin/fsck.vfat",
"/sbin/mkfs.ext4",
"/sbin/mkfs.vfat",
"/sbin/sfdisk",
Copy link
Member Author

Choose a reason for hiding this comment

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

Changed

# Copy systemd bits
install_systemd_files(main)
# copy busybox
install_busybox(main)
Copy link
Member Author

Choose a reason for hiding this comment

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

Done

bin/ubuntu-core-initramfs Show resolved Hide resolved
@alfonsosanchezbeato
Copy link
Member Author

Hm, ubuntu-image in edge is failing now...

@alfonsosanchezbeato
Copy link
Member Author

There is an issue with the just merged assertions, see snapcore/models#17 (comment)

Copy link
Collaborator

@valentindavid valentindavid left a comment

Choose a reason for hiding this comment

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

Looks good. Some minor comments. The $$ is the only one that requires a fix before merge.

I would have kept /usr/sbin as symlink change it in a follow up PR so we do not have to revert the whole thing if we find an issue. But maybe it is safe enough.

# remove redundant definitions after that.
check_call(["systemd-hwdb", "--root", dest_dir,
"update", "--usr", "--strict"])
check_call(["rm -rf " + os.path.join(dest_dir,
Copy link
Collaborator

Choose a reason for hiding this comment

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

shutil.rmtree?

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed

def install_systemd_files(dest_dir):
# Build list of files and directories

out = check_output(["dpkg", "-L", "systemd", "systemd-sysv"])
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems we have 3 places where we do dpkg -L then filtering. I wonder if we could have a function for that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Function added

@@ -1 +0,0 @@
usr/lib
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is it replaced with? I did not find it.

Copy link
Member Author

Choose a reason for hiding this comment

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

This symlink is created now by dracut-install when the u-c-i script runs, so it is not necessary to include it inside the deb (same for the other symlinks I removed).

# This hack should be removed with PR#113
check_call([r"sed -i '/^After=/"
r"{;s, *plymouth-start[.]service *, ,;/"
r"^After= *$$/d;}' "
Copy link
Collaborator

Choose a reason for hiding this comment

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

$$? This was probably copied from the makefile.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, extra $ removed now

out = check_output([bb_cmd, "--list-long"])
cmds = out.decode("utf-8").splitlines()
# Remove command we do not want
to_remove = ["busybox", "reboot", "mount", "umount"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems you fixed "modinfo" probably because of ordering. Now it uses kmod, But maybe we should make explicit and remove it from here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Excellent point, I've removed it explicitly (it was still in usr/bin, although the one in usr/sbin already pointed to kmod)

debian/rules Show resolved Hide resolved
Copy link
Member Author

@alfonsosanchezbeato alfonsosanchezbeato left a comment

Choose a reason for hiding this comment

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

@valentindavid thanks for the review, comments addressed now

@@ -1 +0,0 @@
usr/lib
Copy link
Member Author

Choose a reason for hiding this comment

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

This symlink is created now by dracut-install when the u-c-i script runs, so it is not necessary to include it inside the deb (same for the other symlinks I removed).

def install_systemd_files(dest_dir):
# Build list of files and directories

out = check_output(["dpkg", "-L", "systemd", "systemd-sysv"])
Copy link
Member Author

Choose a reason for hiding this comment

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

Function added

# This hack should be removed with PR#113
check_call([r"sed -i '/^After=/"
r"{;s, *plymouth-start[.]service *, ,;/"
r"^After= *$$/d;}' "
Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, extra $ removed now

# remove redundant definitions after that.
check_call(["systemd-hwdb", "--root", dest_dir,
"update", "--usr", "--strict"])
check_call(["rm -rf " + os.path.join(dest_dir,
Copy link
Member Author

Choose a reason for hiding this comment

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

Changed

out = check_output([bb_cmd, "--list-long"])
cmds = out.decode("utf-8").splitlines()
# Remove command we do not want
to_remove = ["busybox", "reboot", "mount", "umount"]
Copy link
Member Author

Choose a reason for hiding this comment

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

Excellent point, I've removed it explicitly (it was still in usr/bin, although the one in usr/sbin already pointed to kmod)

debian/rules Show resolved Hide resolved
@alfonsosanchezbeato
Copy link
Member Author

@valentindavid @xnox this is ready for another review round.

Copy link
Collaborator

@valentindavid valentindavid left a comment

Choose a reason for hiding this comment

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

Looks good. Thank you!

and use it to install some of files in the initramfs.
Install systemd files from the system where the script is run instead
of using the ones included in the ubuntu-core-initramfs package.
These folders get created anyway by create-initrd.
As now we pull most of the files at run time.
filtering in the script instead of by using grep.
dracut-install installs the interpreters detected in shebangs
(see resolve_deps() in dracut-install.c) - but we want busybox, not
the system default.
Copy link
Contributor

@xnox xnox left a comment

Choose a reason for hiding this comment

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

Here be dragons!

@xnox xnox merged commit dc9e042 into canonical:main Jan 23, 2024
3 checks passed
@alfonsosanchezbeato alfonsosanchezbeato deleted the systemd-run-time branch May 16, 2024 09:14
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.

3 participants