package.bbclass: run pre/post installation/removal scriptlets using sh -e
PATCH 1/4 package.bbclass: run pre/post installation/removal scriptlets using sh -e
This allows catching errors in the scriptlets which would otherwise go unnoticed, e.g. this sequence:
====bogus_commandproper_command
====
would work just fine without any visible warnings or errors.
This was previously done only for rpm packages; this patch replaces the rpm-specific tweak with one that works for all package types.
--- meta/classes/package.bbclass | 12 ++++++++++++ meta/classes/package_rpm.bbclass | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-)
meta/classes/package.bbclass +12 -0
@@ -1343,6 +1343,17 @@ fi postinst += postinst_ontarget d.setVar('pkg_postinst_%s' % pkg, postinst) + def add_set_e_to_scriptlets(pkg): + for scriptlet_name in ('pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm'): + scriptlet = d.getVar('%s_%s' % (scriptlet_name, pkg)) + if scriptlet: + scriptlet_split = scriptlet.split('\n') + if scriptlet_split[0].startswith("#!"): + scriptlet = scriptlet_split[0] + "\nset -e\n" + "\n".join(scriptlet_split[1:]) + else: + scriptlet = "set -e\n" + "\n".join(scriptlet_split[0:]) + d.setVar('%s_%s' % (scriptlet_name, pkg), scriptlet) + def write_if_exists(f, pkg, var): def encode(str): import codecs
@@ -1439,6 +1450,7 @@ fi write_if_exists(sf, pkg, 'FILES') write_if_exists(sf, pkg, 'CONFFILES') process_postinst_on_target(pkg, d.getVar("MLPREFIX")) + add_set_e_to_scriptlets(pkg) write_if_exists(sf, pkg, 'pkg_postinst') write_if_exists(sf, pkg, 'pkg_postrm') write_if_exists(sf, pkg, 'pkg_preinst')
meta/classes/package_rpm.bbclass +4 -4
@@ -470,12 +470,12 @@ python write_specfile () { # Now process scriptlets if splitrpreinst: - spec_scriptlets_bottom.append('%%pre -n %s -p "/bin/sh -e"' % splitname) + spec_scriptlets_bottom.append('%%pre -n %s' % splitname) spec_scriptlets_bottom.append('# %s - preinst' % splitname) spec_scriptlets_bottom.append(splitrpreinst) spec_scriptlets_bottom.append('') if splitrpostinst: - spec_scriptlets_bottom.append('%%post -n %s -p "/bin/sh -e"' % splitname) + spec_scriptlets_bottom.append('%%post -n %s' % splitname) spec_scriptlets_bottom.append('# %s - postinst' % splitname) spec_scriptlets_bottom.append(splitrpostinst) spec_scriptlets_bottom.append('')
@@ -564,12 +564,12 @@ python write_specfile () { spec_preamble_top.append('') if srcrpreinst: - spec_scriptlets_top.append('%pre -p "/bin/sh -e"') + spec_scriptlets_top.append('%pre') spec_scriptlets_top.append('# %s - preinst' % srcname) spec_scriptlets_top.append(srcrpreinst) spec_scriptlets_top.append('') if srcrpostinst: - spec_scriptlets_top.append('%post -p "/bin/sh -e"') + spec_scriptlets_top.append('%post') spec_scriptlets_top.append('# %s - postinst' % srcname) spec_scriptlets_top.append(srcrpostinst) spec_scriptlets_top.append('')
Signature · 1 line
2.16.1
PATCH 2/4 meta/lib/oe/package_manager.py: warn about failing scriptlets for all package types
Previously this was done only for rpm packages; now also ipk/deb scriptlet failures are reported.
In the future this will become a hard error, but it can't yet happen due to the legacy 'exit 1' way of deferring scriptlet execution to first boot which needs a deprecation period.
--- meta/lib/oe/package_manager.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-)
meta/lib/oe/package_manager.py +15 -3
@@ -83,6 +83,11 @@ def opkg_query(cmd_output): return output +# Note: this should be bb.fatal in the future. +def failed_postinsts_warn(pkgs, log_path): + bb.warn("""Intentionally failing postinstall scriptlets of %s to defer them to first boot is deprecated. Please place them into pkg_postinst_ontarget_${PN} (). +If deferring to first boot wasn't the intent, then scriptlet failure may mean an issue in the recipe, or a regression elsewhere. +Details of the failure are in %s.""" %(pkgs, log_path)) class Indexer(object, metaclass=ABCMeta): def __init__(self, d, deploy_dir):
@@ -709,8 +714,7 @@ class RpmPM(PackageManager): failed_scriptlets_pkgnames[line.split()[-1]] = True if len(failed_scriptlets_pkgnames) > 0: - bb.warn("Intentionally failing postinstall scriptlets of %s to defer them to first boot is deprecated. Please place them into pkg_postinst_ontarget_${PN} ()." %(list(failed_scriptlets_pkgnames.keys()))) - bb.warn("If deferring to first boot wasn't the intent, then scriptlet failure may mean an issue in the recipe, or a regression elsewhere.") + failed_postinsts_warn(list(failed_scriptlets_pkgnames.keys()), self.d.expand("${T}/log.do_rootfs")) for pkg in failed_scriptlets_pkgnames.keys(): self.save_rpmpostinst(pkg)
@@ -1172,6 +1176,13 @@ class OpkgPM(OpkgDpkgPM): bb.note(cmd) output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT).decode("utf-8") bb.note(output) + failed_pkgs = [] + for line in output.split('\n'): + if line.endswith("configuration required on target."): + bb.warn(line) + failed_pkgs.append(line.split(".")[0]) + if failed_pkgs: + failed_postinsts_warn(failed_pkgs, self.d.expand("${T}/log.do_rootfs")) except subprocess.CalledProcessError as e: (bb.fatal, bb.warn)[attempt_only]("Unable to install packages. " "Command '%s' returned %d:\n%s" %
@@ -1435,9 +1446,10 @@ class DpkgPM(OpkgDpkgPM): stderr=subprocess.STDOUT).decode("utf-8") bb.note(output) except subprocess.CalledProcessError as e: - bb.note("%s for package %s failed with %d:\n%s" % + bb.warn("%s for package %s failed with %d:\n%s" % (control_script.name, pkg_name, e.returncode, e.output.decode("utf-8"))) + failed_postinsts_warn([pkg_name], self.d.expand("${T}/log.do_rootfs")) failed_pkgs.append(pkg_name) break
Signature · 1 line
2.16.1
PATCH 3/4 oe-selftest: add a test for failing package post-installation scriptlets
In reply to Alexander KanavinThe test runs a scriptlet that has an intentionally failing command in the middle and checks for two things:
1) that bitbake does warn the user about the failure
2) that scriptlet execution stops at that point.
The test is run for all three package types: rpm, deb, ipk.
--- .../recipes-test/postinst/postinst_1.0.bb | 14 +++++++- meta/lib/oeqa/selftest/cases/runtime_test.py | 37 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-)
meta-selftest/recipes-test/postinst/postinst_1.0.bb +13 -1
@@ -3,11 +3,12 @@ LICENSE = "MIT" inherit allarch -PACKAGES = "${PN}-rootfs ${PN}-delayed-a ${PN}-delayed-b" +PACKAGES = "${PN}-rootfs ${PN}-delayed-a ${PN}-delayed-b ${PN}-rootfs-failing" ALLOW_EMPTY_${PN}-rootfs = "1" ALLOW_EMPTY_${PN}-delayed-a = "1" ALLOW_EMPTY_${PN}-delayed-b = "1" +ALLOW_EMPTY_${PN}-rootfs-failing = "1" RDEPENDS_${PN}-delayed-a = "${PN}-rootfs" RDEPENDS_${PN}-delayed-b = "${PN}-delayed-a"
@@ -58,3 +59,14 @@ pkg_postinst_ontarget_${PN}-delayed-b () { touch ${TESTDIR}/delayed-b } + +# This scriptlet intentionally includes a bogus command in the middle to test +# that we catch and report such errors properly. +pkg_postinst_${PN}-rootfs-failing () { + mkdir -p $D${TESTDIR} + touch $D${TESTDIR}/rootfs-before-failure + run_a_really_broken_command + # Scriptlet execution should stop here; the following commands are NOT supposed to run. + # (oe-selftest checks for it). + touch $D${TESTDIR}/rootfs-after-failure +}
meta/lib/oeqa/selftest/cases/runtime_test.py +37 -0
@@ -221,3 +221,40 @@ class Postinst(OESelftestTestCase): for filename in ("rootfs", "delayed-a", "delayed-b"): status, output = qemu.run_serial("test -f %s && echo found" % os.path.join(targettestdir, filename)) self.assertEqual(output, "found", "%s was not present on boot" % filename) + + + + def test_failing_postinst(self): + """ + Summary: The purpose of this test case is to verify that post-installation + scripts that contain errors are properly reported. + Expected: The scriptlet failure is properly reported. + The file that is created after the error in the scriptlet is not present. + Product: oe-core + Author: Alexander Kanavin <[email protected]> + """ + + import oe.path + + vars = get_bb_vars(("IMAGE_ROOTFS", "sysconfdir"), "core-image-minimal") + rootfs = vars["IMAGE_ROOTFS"] + self.assertIsNotNone(rootfs) + sysconfdir = vars["sysconfdir"] + self.assertIsNotNone(sysconfdir) + # Need to use oe.path here as sysconfdir starts with / + hosttestdir = oe.path.join(rootfs, sysconfdir, "postinst-test") + + for classes in ("package_rpm", "package_deb", "package_ipk"): + with self.subTest(package_class=classes): + features = 'CORE_IMAGE_EXTRA_INSTALL = "postinst-rootfs-failing"\n' + features += 'PACKAGE_CLASSES = "%s"\n' % classes + self.write_config(features) + bb_result = bitbake('core-image-minimal') + self.assertGreaterEqual(bb_result.output.find("Intentionally failing postinstall scriptlets of ['postinst-rootfs-failing'] to defer them to first boot is deprecated."), 0, + "Warning about a failed scriptlet not found in bitbake output: %s" %(bb_result.output)) + + self.assertTrue(os.path.isfile(os.path.join(hosttestdir, "rootfs-before-failure")), + "rootfs-before-failure file was not created") + self.assertFalse(os.path.isfile(os.path.join(hosttestdir, "rootfs-after-failure")), + "rootfs-after-failure file was created") +
Signature · 1 line
2.16.1
PATCH 4/4 lib/oe/patch.py: add a warning if patch context was ignored
In reply to Alexander KanavinIgnoring patch context increases the chances of patches being
applied incorrectly. Depending on what code is being patched, this can go completely unnoticed and create subtle bugs, sometimes with security implications.
Please see here for a specific example:
https://bugzilla.yoctoproject.org/show_bug.cgi?id=10450
On the other hand, we cannot simply force all patch context to match exactly:
doing this would break a lot of recipes suddenly, across all layers.
So let's try a softer approach: issue a warning, and gently update patches over a longer span of time. When most of the warnings are eliminated, we can start enforcing a strict patch application policy.
I do understand that this patch creates a lot of warnings all of a sudden, however I believe the problem does need to be addressed. All of oe-core recipes have their context already fixed.
Sample warning:
WARNING: vulkan-1.0.61.1-r0 do_patch:
Some of the context lines in patches were ignored. This can lead to incorrectly applied patches. The context lines in the patches can be updated with devtool:
devtool modify <recipe>
devtool finish --force-patch-refresh <recipe> <layer_path>
Then the updated patches and the source tree (in devtool's workspace) should be reviewed to make sure the patches apply in the correct place and don't introduce duplicate lines (which can, and does happen when some of the context is ignored).
Details:
Applying patch demos-Don-t-build-tri-or-cube.patch
patching file demos/CMakeLists.txt
Hunk #1 succeeded at 63 (offset 2 lines).
Hunk #2 succeeded at 76 with fuzz 1 (offset 2 lines).
[YOCTO #10450]
--- meta/lib/oe/patch.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
meta/lib/oe/patch.py +16 -0
@@ -36,6 +36,22 @@ def runcmd(args, dir = None): (exitstatus, output) = oe.utils.getstatusoutput(cmd) if exitstatus != 0: raise CmdError(cmd, exitstatus >> 8, output) + if " fuzz " in output: + bb.warn(""" +Some of the context lines in patches were ignored. This can lead to incorrectly applied patches. +The context lines in the patches can be updated with devtool: + + devtool modify <recipe> + devtool finish --force-patch-refresh <recipe> <layer_path> + +Then the updated patches and the source tree (in devtool's workspace) +should be reviewed to make sure the patches apply in the correct place +and don't introduce duplicate lines (which can, and does happen +when some of the context is ignored). Further information: +http://lists.openembedded.org/pipermail/openembedded-core/2018-March/148675.html +https://bugzilla.yoctoproject.org/show_bug.cgi?id=10450 +Details: +{}""".format(output)) return output finally:
Signature · 1 line
2.16.1
Re: [PATCH 4/4] lib/oe/patch.py: add a warning if patch context was ignored
Now when more people will use devtool to refresh patches, would it make sense for devtool to use --no-signature when refreshing patches?
I think git version is not important in this context and causes extra changes in .patch files (when refreshed by different people with different git version) or extra work removing that chunk from resulting diff.
Quoted 88 lines · Alexander Kanavin
Ignoring patch context increases the chances of patches being applied incorrectly. Depending on what code is being patched, this can go completely unnoticed and create subtle bugs, sometimes with security implications.
Please see here for a specific example:
https://bugzilla.yoctoproject.org/show_bug.cgi?id=10450On the other hand, we cannot simply force all patch context to match exactly:
doing this would break a lot of recipes suddenly, across all layers.So let's try a softer approach: issue a warning, and gently update patches over a longer span of time. When most of the warnings are eliminated,
we can start enforcing a strict patch application policy.I do understand that this patch creates a lot of warnings all of a sudden, however
I believe the problem does need to be addressed. All of oe-core recipes have their
context already fixed.Sample warning:
WARNING: vulkan-1.0.61.1-r0
do_patch:
Some of the context lines in patches were ignored. This can lead to incorrectly applied patches.
The context lines in the patches can be updated with devtool:devtool modify <recipe>
devtool finish --force-patch-refresh <recipe> <layer_path>Then the updated patches and the source tree (in devtool's workspace) should be reviewed to make sure the patches apply in the correct place and don't introduce duplicate lines (which can, and does happen when some of the context is ignored).
Details:
Applying patch demos-Don-t-build-tri-or-cube.patch
patching filedemos/CMakeLists.txt
Hunk #1 succeeded at 63 (offset 2 lines).
Hunk #2 succeeded at 76 with fuzz 1 (offset 2 lines).[YOCTO #10450]
Signed-off-by Alexander Kanavin <[email protected]>--- meta/lib/oe/patch.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)meta/lib/oe/patch.py +3
-0@@ -36,6 +36,22 @@ def runcmd(args, dir = None): (exitstatus, output) = oe.utils.getstatusoutput(cmd) if exitstatus != 0: raise CmdError(cmd, exitstatus >> 8, output) + if " fuzz " in output: + bb.warn(""" +Some of the context lines in patches were ignored. This can lead toincorrectly applied patches.
meta/lib/oe/patch.py +10
-0+The context lines in the patches can be updated with devtool: + + devtool modify <recipe> + devtool finish --force-patch-refresh <recipe> <layer_path> + +Then the updated patches and the source tree (in devtool's workspace) +should be reviewed to make sure the patches apply in the correct place +and don't introduce duplicate lines (which can, and does happen +when some of the context is ignored). Further information: +http://lists.openembedded.org/pipermail/openembedded-core/2018-March/148675.html
meta/lib/oe/patch.py +3
-1+https://bugzilla.yoctoproject.org/show_bug.cgi?id=10450 +Details: +{}""".format(output)) return output finally: --2.16.1
--
_______________________________________________
Openembedded-core mailing list
[email protected]
http://lists.openembedded.org/mailman/listinfo/openembedded-core
Re: [PATCH 4/4] lib/oe/patch.py: add a warning if patch context was ignored
Quoted 1 line · Martin Jansa
Now when more people will use devtool to refresh patches, would it make sense for devtool to use --no-signature when refreshing patches?
I don't think that will help much to reduce the amount of change in the refresh. The refesh will also update the line numbers in chunks so that they match reality, and convert patches into git format - all useful things.
Alex
Re: [PATCH 4/4] lib/oe/patch.py: add a warning if patch context was ignored
Yes, this time it updates more things, but in future updates it might be just 1 line with line numbers and then different git version in the signature. Which will make the diff few times bigger than it would need to be.
Quoted 13 lines · Alexander Kanavin
Quoted 3 lines · Martin Jansa
Now when more people will use devtool to refresh patches, would it make sense for devtool to use --no-signature when refreshing patches?
I don't think that will help much to reduce the amount of change in the refresh. The refesh will also update the line numbers in chunks so that they match reality, and convert patches into git format - all useful things.
Alex
Re: [PATCH 4/4] lib/oe/patch.py: add a warning if patch context was ignored
Quoted 1 line · Martin Jansa
Yes, this time it updates more things, but in future updates it might be just 1 line with line numbers and then different git version in the signature. Which will make the diff few times bigger than it would need to be.
Right. I think the patch extraction is done by extractPatches() in meta/lib/oe/patch.py, so if you can make and test the change (should be trivial), would be great :)
Alex
Re: [PATCH 4/4] lib/oe/patch.py: add a warning if patch context was ignored
In reply to Martin JansaQuoted 7 lines · Martin Jansa
Now when more people will use devtool to refresh patches, would it make sense for devtool to use --no-signature when refreshing patches?
I think git version is not important in this context and causes extra changes in .patch files (when refreshed by different people with different git version) or extra work removing that chunk from resulting diff.
and also time/date in diff if they are not pruned already