From: David Kalnischkies <kalnischkies@gmail.com>
Date: Sun, 9 Sep 2012 14:03:52 +0000 (+0200)
Subject: handle packages without a mandatory architecture (debian-policy §5.3)
X-Git-Tag: 0.9.7.5~1^2~1
X-Git-Url: https://git.saurik.com/apt.git/commitdiff_plain/c919ad6e4d0de48acb60f2a1371ade9bfb0451f8?ds=inline

handle packages without a mandatory architecture (debian-policy §5.3)
by introducing a pseudo-architecture 'none' so that the small group of
users with these packages can get right of them without introducing too
much hassle for other users (Closes: #686346)
---

diff --git a/apt-pkg/algorithms.cc b/apt-pkg/algorithms.cc
index 2f5fcc7ab..1b0161ffd 100644
--- a/apt-pkg/algorithms.cc
+++ b/apt-pkg/algorithms.cc
@@ -194,6 +194,11 @@ bool pkgSimulate::Remove(PkgIterator iPkg,bool Purge)
 {
    // Adapt the iterator
    PkgIterator Pkg = Sim.FindPkg(iPkg.Name(), iPkg.Arch());
+   if (Pkg.end() == true)
+   {
+      std::cerr << (Purge ? "Purg" : "Remv") << " invalid package " << iPkg.FullName() << std::endl;
+      return false;
+   }
 
    Flags[Pkg->ID] = 3;
    Sim.MarkDelete(Pkg);
diff --git a/apt-pkg/deb/deblistparser.cc b/apt-pkg/deb/deblistparser.cc
index 12c6ab4c9..b84bd6fdd 100644
--- a/apt-pkg/deb/deblistparser.cc
+++ b/apt-pkg/deb/deblistparser.cc
@@ -645,6 +645,8 @@ bool debListParser::ParseDepends(pkgCache::VerIterator &Ver,
 	      a != Architectures.end(); ++a)
 	    if (NewDepends(Ver,Package,*a,Version,Op,Type) == false)
 	       return false;
+	 if (NewDepends(Ver,Package,"none",Version,Op,Type) == false)
+	    return false;
       }
       else if (MultiArchEnabled == true && found != string::npos &&
 	       strcmp(Package.c_str() + found, ":any") != 0)
@@ -658,8 +660,18 @@ bool debListParser::ParseDepends(pkgCache::VerIterator &Ver,
 	 if (NewDepends(Ver,Package,Arch,Version,Op,Type) == false)
 	    return false;
       }
-      else if (NewDepends(Ver,Package,pkgArch,Version,Op,Type) == false)
-	 return false;
+      else
+      {
+	 if (NewDepends(Ver,Package,pkgArch,Version,Op,Type) == false)
+	    return false;
+	 if ((Type == pkgCache::Dep::Conflicts ||
+	      Type == pkgCache::Dep::DpkgBreaks ||
+	      Type == pkgCache::Dep::Replaces) &&
+	     NewDepends(Ver, Package,
+			(pkgArch != "none") ? "none" : _config->Find("APT::Architecture"),
+			Version,Op,Type) == false)
+	    return false;
+      }
       if (Start == Stop)
 	 break;
    }
@@ -753,13 +765,15 @@ bool debListParser::Step()
          drop the whole section. A missing arch tag only happens (in theory)
          inside the Status file, so that is a positive return */
       string const Architecture = Section.FindS("Architecture");
-      if (Architecture.empty() == true)
-	 return true;
 
       if (Arch.empty() == true || Arch == "any" || MultiArchEnabled == false)
       {
 	 if (APT::Configuration::checkArchitecture(Architecture) == true)
 	    return true;
+	 /* parse version stanzas without an architecture only in the status file
+	    (and as misfortune bycatch flat-archives) */
+	 if ((Arch.empty() == true || Arch == "any") && Architecture.empty() == true)
+	    return true;
       }
       else
       {
diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc
index ae9143e0d..c9df41d3a 100644
--- a/apt-pkg/deb/dpkgpm.cc
+++ b/apt-pkg/deb/dpkgpm.cc
@@ -1131,7 +1131,9 @@ bool pkgDPkgPM::Go(int OutStatusFd)
 	    if (I->Op == Item::Configure && disappearedPkgs.find(I->Pkg.Name()) != disappearedPkgs.end())
 	       continue;
 	    // We keep this here to allow "smooth" transitions from e.g. multiarch dpkg/ubuntu to dpkg/debian
-	    if (dpkgMultiArch == false && (I->Pkg.Arch() == nativeArch || !strcmp(I->Pkg.Arch(), "all")))
+	    if (dpkgMultiArch == false && (I->Pkg.Arch() == nativeArch ||
+					   strcmp(I->Pkg.Arch(), "all") == 0 ||
+					   strcmp(I->Pkg.Arch(), "none") == 0))
 	    {
 	       char const * const name = I->Pkg.Name();
 	       ADDARG(name);
@@ -1148,7 +1150,9 @@ bool pkgDPkgPM::Go(int OutStatusFd)
                }
 	       else
 		  PkgVer = Cache[I->Pkg].InstVerIter(Cache);
-               if (PkgVer.end() == false)
+	       if (strcmp(I->Pkg.Arch(), "none") == 0)
+		  ; // never arch-qualify a package without an arch
+	       else if (PkgVer.end() == false)
                   name.append(":").append(PkgVer.Arch());
                else
                   _error->Warning("Can not find PkgVer for '%s'", name.c_str());
diff --git a/apt-pkg/pkgcache.cc b/apt-pkg/pkgcache.cc
index 9acb7da72..353172d8a 100644
--- a/apt-pkg/pkgcache.cc
+++ b/apt-pkg/pkgcache.cc
@@ -238,7 +238,7 @@ 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) {
+	if (MultiArchCache() == false && Arch != "none") {
 		if (Arch == "native" || Arch == "all" || Arch == "any" ||
 		    Arch == NativeArch())
 			return SingleArchFindPkg(Name);
@@ -376,6 +376,10 @@ pkgCache::PkgIterator pkgCache::GrpIterator::FindPreferredPkg(bool const &Prefer
 		if (Pkg.end() == false && (PreferNonVirtual == false || Pkg->VersionList != 0))
 			return Pkg;
 	}
+	// packages without an architecture
+	Pkg = FindPkg("none");
+	if (Pkg.end() == false && (PreferNonVirtual == false || Pkg->VersionList != 0))
+		return Pkg;
 
 	if (PreferNonVirtual == true)
 		return FindPreferredPkg(false);
diff --git a/apt-pkg/pkgcachegen.cc b/apt-pkg/pkgcachegen.cc
index f70cbd02a..490c2ecbb 100644
--- a/apt-pkg/pkgcachegen.cc
+++ b/apt-pkg/pkgcachegen.cc
@@ -200,7 +200,19 @@ bool pkgCacheGenerator::MergeList(ListParser &List,
       }
 
       if (Arch.empty() == true)
-	 Arch = _config->Find("APT::Architecture");
+      {
+	 // use the pseudo arch 'none' for arch-less packages
+	 Arch = "none";
+	 /* We might built a SingleArchCache here, which we don't want to blow up
+	    just for these :none packages to a proper MultiArchCache, so just ensure
+	    that we have always a native package structure first for SingleArch */
+	 pkgCache::PkgIterator NP;
+	 if (NewPackage(NP, PackageName, _config->Find("APT::Architecture")) == false)
+	 // TRANSLATOR: The first placeholder is a package name,
+	 // the other two should be copied verbatim as they include debug info
+	 return _error->Error(_("Error occurred while processing %s (%s%d)"),
+			      PackageName.c_str(), "NewPackage", 0);
+      }
 
       // Get a pointer to the package structure
       pkgCache::PkgIterator Pkg;
@@ -418,6 +430,42 @@ bool pkgCacheGenerator::MergeListVersion(ListParser &List, pkgCache::PkgIterator
 	       return _error->Error(_("Error occurred while processing %s (%s%d)"),
 				    Pkg.Name(), "AddImplicitDepends", 1);
       }
+      /* :none packages are packages without an architecture. They are forbidden by
+	 debian-policy, so usually they will only be in (old) dpkg status files -
+	 and dpkg will complain about them - and are pretty rare. We therefore do
+	 usually not create conflicts while the parent is created, but only if a :none
+	 package (= the target) appears. This creates incorrect dependencies on :none
+	 for architecture-specific dependencies on the package we copy from, but we
+	 will ignore this bug as architecture-specific dependencies are only allowed
+	 in jessie and until then the :none packages should be extinct (hopefully).
+	 In other words: This should work long enough to allow graceful removal of
+	 these packages, it is not supposed to allow users to keep using them … */
+      if (strcmp(Pkg.Arch(), "none") == 0)
+      {
+	 pkgCache::PkgIterator M = Grp.FindPreferredPkg();
+	 if (M.end() == false && Pkg != M)
+	 {
+	    pkgCache::DepIterator D = M.RevDependsList();
+	    Dynamic<pkgCache::DepIterator> DynD(D);
+	    for (; D.end() == false; ++D)
+	    {
+	       if ((D->Type != pkgCache::Dep::Conflicts &&
+		    D->Type != pkgCache::Dep::DpkgBreaks &&
+		    D->Type != pkgCache::Dep::Replaces) ||
+		   D.ParentPkg().Group() == Grp)
+		  continue;
+
+	       map_ptrloc *OldDepLast = NULL;
+	       pkgCache::VerIterator ConVersion = D.ParentVer();
+	       // duplicate the Conflicts/Breaks/Replaces for :none arch
+	       if (D->Version == 0)
+		  NewDepends(Pkg, ConVersion, "", 0, D->Type, OldDepLast);
+	       else
+		  NewDepends(Pkg, ConVersion, D.TargetVer(),
+			     D->CompareOp, D->Type, OldDepLast);
+	    }
+	 }
+      }
    }
    if (unlikely(AddImplicitDepends(Grp, Pkg, Ver) == false))
       return _error->Error(_("Error occurred while processing %s (%s%d)"),
@@ -871,6 +919,9 @@ bool pkgCacheGenerator::ListParser::NewDepends(pkgCache::VerIterator &Ver,
 
    // Locate the target package
    pkgCache::PkgIterator Pkg = Grp.FindPkg(Arch);
+   // we don't create 'none' packages and their dependencies if we can avoid it …
+   if (Pkg.end() == true && Arch == "none")
+      return true;
    Dynamic<pkgCache::PkgIterator> DynPkg(Pkg);
    if (Pkg.end() == true) {
       if (unlikely(Owner->NewPackage(Pkg, PackageName, Arch) == false))
diff --git a/debian/changelog b/debian/changelog
index e62f0b681..90f3199b3 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -4,6 +4,10 @@ apt (0.9.7.5) UNRELEASED; urgency=low
   * Japanese (KURASAWA Nozomu) (Closes: #684435)
 
   [ David Kalnischkies ]
+  * handle packages without a mandatory architecture (debian-policy §5.3)
+    by introducing a pseudo-architecture 'none' so that the small group of
+    users with these packages can get right of them without introducing too
+    much hassle for other users (Closes: #686346)
   * apt-pkg/cdrom.cc:
     - copy only configured translation files from a CD-ROM and not all
       available translation files preventing new installs with d-i from
diff --git a/test/integration/framework b/test/integration/framework
index 57bf555af..1c4872c8e 100644
--- a/test/integration/framework
+++ b/test/integration/framework
@@ -468,7 +468,7 @@ insertpackage() {
 	local PRIORITY="${6:-optional}"
 	local ARCHS=""
 	for arch in $(echo "$ARCH" | sed -e 's#,#\n#g' | sed -e "s#^native\$#$(getarchitecture 'native')#"); do
-		if [ "$arch" = "all" ]; then
+		if [ "$arch" = 'all' -o "$arch" = 'none' ]; then
 			ARCHS="$(getarchitectures)"
 		else
 			ARCHS="$arch"
@@ -482,9 +482,9 @@ insertpackage() {
 Priority: $PRIORITY
 Section: other
 Installed-Size: 42
-Maintainer: Joe Sixpack <joe@example.org>
-Architecture: $arch
-Version: $VERSION
+Maintainer: Joe Sixpack <joe@example.org>" >> $FILE
+			test "$arch" = 'none' || echo "Architecture: $arch" >> $FILE
+			echo "Version: $VERSION
 Filename: pool/main/${NAME}/${NAME}_${VERSION}_${arch}.deb" >> $FILE
 			test -z "$DEPENDENCIES" || echo "$DEPENDENCIES" >> $FILE
 			echo "Description: an autogenerated dummy ${NAME}=${VERSION}/${RELEASE}
@@ -534,8 +534,8 @@ Priority: $PRIORITY
 Section: other
 Installed-Size: 42
 Maintainer: Joe Sixpack <joe@example.org>
-Architecture: $arch
 Version: $VERSION" >> $FILE
+		test "$arch" = 'none' || echo "Architecture: $arch" >> $FILE
 		test -z "$DEPENDENCIES" || echo "$DEPENDENCIES" >> $FILE
 		echo "Description: an autogenerated dummy ${NAME}=${VERSION}/installed
  If you find such a package installed on your system,
@@ -818,7 +818,7 @@ testnopackage() {
 
 testdpkginstalled() {
 	msgtest "Test for correctly installed package(s) with" "dpkg -l $*"
-	local PKGS="$(dpkg -l $* | grep '^i' | wc -l)"
+	local PKGS="$(dpkg -l $* 2>/dev/null | grep '^i' | wc -l)"
 	if [ "$PKGS" != $# ]; then
 		echo $PKGS
 		dpkg -l $* | grep '^[a-z]'
diff --git a/test/integration/test-bug-686346-package-missing-architecture b/test/integration/test-bug-686346-package-missing-architecture
new file mode 100755
index 000000000..b0e0aa3c4
--- /dev/null
+++ b/test/integration/test-bug-686346-package-missing-architecture
@@ -0,0 +1,87 @@
+#!/bin/sh
+set -e
+
+TESTDIR=$(readlink -f $(dirname $0))
+. $TESTDIR/framework
+setupenvironment
+configarchitecture 'amd64'
+
+insertinstalledpackage 'pkgb' 'none' '1'
+insertinstalledpackage 'pkgd' 'none' '1'
+insertpackage 'unstable' 'pkga' 'amd64' '2' 'Depends: pkgb'
+insertpackage 'unstable' 'pkgb' 'amd64' '2'
+insertpackage 'unstable' 'pkgc' 'amd64' '1' 'Conflicts: pkgb'
+insertpackage 'unstable' 'pkge' 'none' '1'
+
+setupaptarchive
+
+testequal 'Reading package lists...
+Building dependency tree...
+The following packages will be REMOVED:
+  pkgb:none
+The following NEW packages will be installed:
+  pkgc
+0 upgraded, 1 newly installed, 1 to remove and 0 not upgraded.
+Remv pkgb:none [1]
+Inst pkgc (1 unstable [amd64])
+Conf pkgc (1 unstable [amd64])' aptget install pkgc -s
+
+testequal 'Reading package lists...
+Building dependency tree...
+The following extra packages will be installed:
+  pkgb
+The following packages will be REMOVED:
+  pkgb:none
+The following NEW packages will be installed:
+  pkga pkgb
+0 upgraded, 2 newly installed, 1 to remove and 0 not upgraded.
+Remv pkgb:none [1]
+Inst pkgb (2 unstable [amd64])
+Inst pkga (2 unstable [amd64])
+Conf pkgb (2 unstable [amd64])
+Conf pkga (2 unstable [amd64])' aptget install pkga -s
+
+# ensure that arch-less stanzas from Packages files are ignored
+msgtest 'Package is distributed in the Packages files' 'pkge'
+grep -q 'Package: pkge' $(find aptarchive -name 'Packages') && msgpass || msgfail
+testnopackage pkge
+testnopackage pkge:none
+testnopackage pkge:*
+
+# do not automatically change from none-arch to whatever-arch as
+# this breaks other none packages and dpkg has this ruleset as
+# this difference seems so important that it has to be maintained …
+testequal 'Reading package lists...
+Building dependency tree...
+0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.' aptget dist-upgrade -s
+
+# pkgd has no update with an architecture
+testdpkginstalled pkgd
+msgtest 'Test apt-get purge' 'pkgd'
+aptget purge pkgd -y >/dev/null 2>&1 && msgpass || msgfail
+testdpkgnotinstalled pkgd
+
+# there is a pkgb with an architecture
+testdpkginstalled pkgb
+msgtest 'Test apt-get purge' 'pkgb:none'
+aptget purge pkgb:none -y >/dev/null 2>&1 && msgpass || msgfail
+testdpkgnotinstalled pkgb
+
+# check that dependencies are created after the none package exists in the cache
+rm rootdir/var/cache/apt/*.bin
+insertinstalledpackage 'pkgb' 'none' '1'
+insertinstalledpackage 'pkgf' 'none' '1' 'Conflicts: pkgb'
+insertinstalledpackage 'pkgg' 'amd64' '1' 'Conflicts: pkgb'
+insertinstalledpackage 'pkgb' 'amd64' '2'
+testequal "Reading package lists...
+Building dependency tree...
+Reading state information...
+You might want to run 'apt-get -f install' to correct these.
+The following packages have unmet dependencies:
+ pkgb : Conflicts: pkgb:none but 1 is installed
+ pkgb:none : Conflicts: pkgb but 2 is installed
+ pkgf:none : Conflicts: pkgb:none but 1 is installed
+             Conflicts: pkgb but 2 is installed
+ pkgg : Conflicts: pkgb but 2 is installed
+        Conflicts: pkgb:none but 1 is installed
+E: Unmet dependencies. Try using -f." aptget check