Control: reassign -1 apt 1.0.9.8
Control: affects -1 aptitude
Control: severity -1 important
Control: tags -1 patch
Control: retitle -1 actions on specific packages don't work on single-arch 
systems

Hi,

(the mail starts with a LOT of technical bla bla a user doesn't need to
 concern itself with. I have cc'ed all reporters explicitly through as
 I would like them to try the "workarounds" mentioned further below, so
 feel free to skip over the technical reasoning straight to the 'meat'.
 And if you gonna reply, please ONLY to the bugreport, thanks!)

On Tue, Apr 21, 2015 at 08:17:36PM +0200, Axel Beckert wrote:
> David already seems to on to something...

After quite some hair-pulling and the occasional swearing which Axel
thankfully endured I finally arrived at a rather misfortune conclusion
(at least for me): I was wrong, which is quite a hurtful experience as
I had put quite a bit of energy into preventing it…

So, desperate me claiming the fix for #777760 would have a low
regression risk as there aren't that many packages in existence which
expect this behaviour, I forgot to consider a potential side-effect:

The correct creation of a package foo from architecture alien1 can if we
haven't seen the package foo from our native architecture yet claim the
price of being the first known package foo. The first place is assumed
to belong to the package of the native architecture through, so a libapt
application asking for the package foo of native architecture will get
unconditionally handed the first package foo found, which if we are
unlucky is the one for alien1 and not native…

This assumption used to be true, but with the advent of architecture
specific dependencies (and the deprecated policy-forbidden packages
without architecture) not so much anymore. It is to be noted that not
all architecture specific dependencies actually trigger this, they just
have the potential to do it, if they do depends very much on the order
of things, but we will get to that in a minute.


Analysing amd64 sid+experimental you can find the following effected:
libstdc++6-4.9-dbg arm64
libgcc-4.9-dev arm64
libgcc1-dbg arm64
libgomp1-dbg arm64
libitm1-dbg arm64
libatomic1-dbg arm64
libasan1-dbg arm64
liblsan0-dbg arm64
libtsan0-dbg arm64
libubsan0-dbg arm64
libcilkrts5-dbg arm64
libquadmath-dbg arm64
libgfortran-4.9-dev arm64
libgfortran3-dbg arm64
u-boot armhf

(the second column is the architecture the first package is created for,
which should have been amd64 if the made assumption would be valid).

We have seen the names of most of them in this bugreport already, Axel used
libgcc1-dbg in his example.

The situation is a bit different for jessie sources:
Only u-boot is effected here. u-boot:amd64 doesn't exist btw.
(The reason is that crossbuild-* doesn't exist there).

Note that this error doesn't add up, but can decrease with additional
sources. Having testing+sid+experimental enabled e.g. leaves you with
only two of them still on the list (if you have it in this exact order).
Architectures aren't created equally either, but they are all playing in
the same ballpark, so list isn't changing much if at all between archs.

Bug #782889 mentions other packages effected on jessie through, and Axel
has an example with libgnutls26 on an amd64 with sid+experimental, so
how does these happen?

It took me longer to realize than it should have, but it is actually
quite simple now that I had a good 'talk' with my pillow:
In my testing I was using either only the component "main" or the
components "main contrib non-free" (in that order), but if you happen to
do (lets say) "contrib main" you end up with these in addition:
libldap-2.4-2 i386
libdb5.3 i386
libgnutls26 i386
libsasl2-2 i386
libsasl2-modules i386
libsasl2-modules-db i386

Which minus the libgnutls26 are also exactly the packages #782889
mentions for jessie (libgnutls26 isn't existing nor even referenced in
this release, so the package comes never into existence for apt here).
[and they are the only packages mentioned by the bugreport, so not all
 jessie users are effected at all – see also "workaround" 1.]


So, what is the effect you might ask now:
There are more or less four ways to get to a package in libapt.
1. You can follow a dependency. There is no lookup involved here, this
will always point to the correct package as this one is prebuild with 2.
2. You get a Grp-structure, which has a FindPkg(arch) method giving you
the package you desire.
3. You iterate over ALL packages one after the other (that sounds
ridiculous stupid, but is done a lot as you usually have to touch them
all in some way anyway)
4. You are asking the cache directly with a FindPkg(name[, arch]) call
for a package.

Only the last one is effected as this path includes a detection for
single-arch which then has the incorrect assumption of the first package
being the native package. The attached patch removes the single-arch
special casing in this codepath, which means that the same code as on
multi-arch is used which will in turn use 2. to get the package.

apt itself it turns out uses way 4 very seldom as its dealing a lot
with Grps nowadays and hence does 2 (if not the other two), so, even
through the output in simulation is slightly off you can interact with
these packages quite normally (Axel e.g. showed already that he can
install such a package). Upgrades and such will work, too.

aptitude and other frontends potentially use 4 more as most of them
weren't changed much since the advent of Multi-Arch (and 2 is a thing
only since Multi-Arch), so I suspect interactive actions like in
aptitude the marking where one of these packages is picked specifically
to be effected, while non-interactive actions like upgrades where the
package is picked via dependencies (1) or whole system operations (3)
aren't.

The worst thing which can happen is that effected packages can't be
installed with (e.g.) aptitude, not forgetting new or forgetting auto
(also with apt) while being slightly annoying isn't dangerous. I want to
highlight especially that it isn't possible to produce a broken system
with this as dependencies are created and evaluated correctly.


---- [ end of (most of) the techno-babble ] ----


Effected users have a cornucopia of options to deal with this, which
brings in other small benefits as well, so users might want to try:


1. Reorder components in the sources.list if you have activated
multiple, which means if you currently have e.g.:
deb http://http.debian.net/debian/ jessie contrib non-free main
change it to
deb http://http.debian.net/debian/ jessie main contrib non-free

For jessie users this will trim down the list to 'u-boot' mentioned
above, which as said has no native counterpart, and is therefore handled
(= ignored) correctly already, so this should make this "problem" go
away entirely already for jessie users without any downside.
(and is hence the reason why I am not crying RC-BUG and pull the
 emergency brake just yet. We can fix that in a point-release.)


2. Reorder sources.list lines:
man sources.list defines that the order of these lines effect from which
source a package is pulled if multiple provide the same package version.
Using here a pattern of (omitting the ones you don't want of course)
oldstable
stable
testing
unstable
experimental
security, -updates, -backports, third-parties, …
instead of the other way around allows for e.g. download progress output
to be better as it will report that a package which already migrated from
unstable to testing is downloaded from testing, rather than claiming it
comes from unstable (which can be confusing if you happen to have pinned
down unstable, but your package manager seems to install packages from
it anyhow… Its also good e.g. for the security mirrors to avoid
downloading from them if your usual mirror has the update already so
they can serve others in need instead).

This should help at least a bit in reducing the list as the cache is
filled with most packages already before apt encounters the rather new
architecture-specific dependencies in newer releases (see my example
above - the remaining two were our friend u-boot and libquadmath-dbg,
which is another non-existing package, so problem gone also here,
which explains why some systems with the 'same' sources are effected
while others aren't; order matters quite a bit sometimes…)


3. Ignore it for the time being
Especially if you have opted for the two previous options, the still
effected packages list should be small if not empty and include mostly
packages most users do not care too much about in the first place (*-dev
and *-dbg). So if you don't have a pressing need, an itchy trigger
finger or aren't on an at least immediate Debian experience level
sitting that one out until a point release fixes this is a very valid
option instead of rushing further down the list.


4. Enable Multi-Arch
This makes the problem go away entirely, but with all the advantages and
disadvantages Multi-Arch itself might have for you.
(In effect, it triggers the codepaths being used as the patch does)


5. Adventure seekers can try the attached patch. Axel already tried it
out for me and confirmed it fixes things and my tests are reassuring as
well, but additional feedback never hurts (still, this is for the
experienced Debian user to try only!)



A "thank you" goes out to everyone reporting this bug and especially to
Axel for helping me debug this – and of course a "sorry" to everyone
for breaking things, especially to the release team.


Best regards

David Kalnischkies
commit 6c9937da76b9155d166092b9dda22d06200510c1
Author: David Kalnischkies <da...@kalnischkies.de>
Date:   Wed Apr 22 12:06:40 2015 +0200

    remove "first package seen is native package" assumption
    
    The fix for #777760 causes packages of foreign (and the native)
    architectures, to be created correctly, but invalidates (like the
    previously existing, but policy-forbidden architecture-less packages
    we had to support for some upgrade scenarios) the assumption that the
    first (and only) package in the cache for a single architecture system
    must be the package for the native architecture (as, where should the
    other architectures come from, right? Wrong.).
    
    Depending on the order of parsing sources more or less packages can be
    effected by this. The effects are strange (for apt it mostly effects
    simulation/debug output, but also apt-mark on these specific packages),
    which complicates debugging, but relatively harmless if understood as
    most actions do not need direct named access to packages.
    
    The problem is fixed by removing the single-arch special casing in the
    paths who had them (Cache.FindPkg), so they use the same code as
    multi-arch systems, which use them as a wrapper for Grp.FindPkg.
    
    Note that single-arch system code was using Grp.FindPkg before as well
    if a Grp structure was handily available, so we don't introduce new
    untested code here: We remove more brittle special cases which are less
    tested instead (this was planed to be done for Stretch anyhow).
    
    Note further that the method with the assumption itself isn't fixed. As
    it is a private method I opted for declaring it deprecated instead and
    remove all its call positions. As it is private no-one can call this
    method legally (thanks to how c++ works by default its still an exported
    symbol through) and fixing it basically means reimplementing code we
    already have in Grp.FindPkg.
    
    Removing rather than fixing seems hence like a good solution.
    
    Closes: 782777
    Thanks: Axel Beckert for testing

diff --git a/apt-pkg/pkgcache.cc b/apt-pkg/pkgcache.cc
index d7c9656..a7b75da 100644
--- a/apt-pkg/pkgcache.cc
+++ b/apt-pkg/pkgcache.cc
@@ -230,12 +230,7 @@ pkgCache::PkgIterator pkgCache::SingleArchFindPkg(const string &Name)
 pkgCache::PkgIterator pkgCache::FindPkg(const string &Name) {
 	size_t const found = Name.find(':');
 	if (found == string::npos)
-	{
-		if (MultiArchCache() == false)
-			return SingleArchFindPkg(Name);
-		else
-			return FindPkg(Name, "native");
-	}
+	   return FindPkg(Name, "native");
 	string const Arch = Name.substr(found+1);
 	/* Beware: This is specialcased to handle pkg:any in dependencies as
 	   these are linked to virtual pkg:any named packages with all archs.
@@ -249,13 +244,6 @@ pkgCache::PkgIterator pkgCache::FindPkg(const string &Name) {
 // ---------------------------------------------------------------------
 /* Returns 0 on error, pointer to the package otherwise */
 pkgCache::PkgIterator pkgCache::FindPkg(const string &Name, string const &Arch) {
-	if (MultiArchCache() == false && Arch != "none") {
-		if (Arch == "native" || Arch == "all" || Arch == "any" ||
-		    Arch == NativeArch())
-			return SingleArchFindPkg(Name);
-		else
-			return PkgIterator(*this,0);
-	}
 	/* We make a detour via the GrpIterator here as
 	   on a multi-arch environment a group is easier to
 	   find than a package (less entries in the buckets) */
diff --git a/apt-pkg/pkgcache.h b/apt-pkg/pkgcache.h
index 5e8a963..a7e520b 100644
--- a/apt-pkg/pkgcache.h
+++ b/apt-pkg/pkgcache.h
@@ -218,7 +218,7 @@ class pkgCache								/*{{{*/
 
 private:
    bool MultiArchEnabled;
-   PkgIterator SingleArchFindPkg(const std::string &Name);
+   APT_DEPRECATED PkgIterator SingleArchFindPkg(const std::string &Name);
 };
 									/*}}}*/
 // Header structure							/*{{{*/
diff --git a/test/integration/test-bug-782777-single-arch-weirdness b/test/integration/test-bug-782777-single-arch-weirdness
new file mode 100755
index 0000000..0049033
--- /dev/null
+++ b/test/integration/test-bug-782777-single-arch-weirdness
@@ -0,0 +1,72 @@
+#!/bin/sh
+# Ensure that the order in which packages are in the binary cache
+# does not effect if they can be found or not
+set -e
+
+TESTDIR=$(readlink -f $(dirname $0))
+. $TESTDIR/framework
+setupenvironment
+configarchitecture 'i386'
+
+insertpackage 'unstable' 'abar' 'i386' '1'
+insertpackage 'unstable' 'foobar' 'i386' '1' 'Depends: abar:amd64, zfoo:amd64'
+insertpackage 'unstable' 'zfoo' 'i386' '1'
+
+setupaptarchive
+
+testrun() {
+	rm -f rootdir/var/lib/apt/extended_states
+
+	testequal 'Reading package lists...
+Building dependency tree...
+The following NEW packages will be installed:
+  abar zfoo
+0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
+Inst abar (1 unstable [i386])
+Inst zfoo (1 unstable [i386])
+Conf abar (1 unstable [i386])
+Conf zfoo (1 unstable [i386])' aptget install abar zfoo -s
+
+	testequal 'Reading package lists...
+Building dependency tree...
+The following NEW packages will be installed:
+  abar zfoo
+0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
+Inst abar (1 unstable [i386])
+Inst zfoo (1 unstable [i386])
+Conf abar (1 unstable [i386])
+Conf zfoo (1 unstable [i386])' aptget install abar:i386 zfoo:i386 -s
+
+	testequal "Reading package lists...
+Building dependency tree...
+Package abar:amd64 is not available, but is referred to by another package.
+This may mean that the package is missing, has been obsoleted, or
+is only available from another source
+
+Package zfoo:amd64 is not available, but is referred to by another package.
+This may mean that the package is missing, has been obsoleted, or
+is only available from another source
+
+E: Package 'abar:amd64' has no installation candidate
+E: Package 'zfoo:amd64' has no installation candidate" aptget install abar:amd64 zfoo:amd64 -s
+
+	cp -f rootdir/var/lib/dpkg/status status.backup
+	insertinstalledpackage 'abar' 'i386' '1'
+	insertinstalledpackage 'zfoo' 'i386' '1'
+
+	testequal 'abar
+zfoo' aptmark showmanual abar zfoo
+	testequal 'abar set to automatically installed.
+zfoo set to automatically installed.' aptmark auto abar zfoo
+	testempty aptmark showmanual abar zfoo
+	testequal 'abar
+zfoo' aptmark showauto abar zfoo
+
+	mv -f status.backup rootdir/var/lib/dpkg/status
+}
+
+msgmsg 'Single-Arch testrun'
+testrun
+msgmsg 'Multi-Arch testrun'
+configarchitecture 'i386' 'amd64'
+testrun

Attachment: signature.asc
Description: Digital signature

Reply via email to