From c919ad6e4d0de48acb60f2a1371ade9bfb0451f8 Mon Sep 17 00:00:00 2001 From: David Kalnischkies Date: Sun, 9 Sep 2012 16:03:52 +0200 Subject: =?UTF-8?q?handle=20packages=20without=20a=20mandatory=20architect?= =?UTF-8?q?ure=20(debian-policy=20=C2=A75.3)=20by=20introducing=20a=20pseu?= =?UTF-8?q?do-architecture=20'none'=20so=20that=20the=20small=20group=20of?= =?UTF-8?q?=20users=20with=20these=20packages=20can=20get=20right=20of=20t?= =?UTF-8?q?hem=20without=20introducing=20too=20much=20hassle=20for=20other?= =?UTF-8?q?=20users=20(Closes:=20#686346)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apt-pkg/algorithms.cc | 5 ++ apt-pkg/deb/deblistparser.cc | 22 +++++- apt-pkg/deb/dpkgpm.cc | 8 +- apt-pkg/pkgcache.cc | 6 +- apt-pkg/pkgcachegen.cc | 53 ++++++++++++- debian/changelog | 4 + test/integration/framework | 12 +-- .../test-bug-686346-package-missing-architecture | 87 ++++++++++++++++++++++ 8 files changed, 183 insertions(+), 14 deletions(-) create mode 100755 test/integration/test-bug-686346-package-missing-architecture 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 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 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 -Architecture: $arch -Version: $VERSION +Maintainer: Joe Sixpack " >> $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 -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 -- cgit v1.2.3 From 8ec008808cd6083a633685a732dfe9b8a58a89da Mon Sep 17 00:00:00 2001 From: David Kalnischkies Date: Sun, 9 Sep 2012 21:22:54 +0200 Subject: * apt-pkg/pkgcachegen.cc: - do not create 'native' (or now 'none') package structures as a side effect of description translation parsing as it pollutes the cache --- apt-pkg/pkgcachegen.cc | 2 ++ debian/changelog | 3 +++ 2 files changed, 5 insertions(+) diff --git a/apt-pkg/pkgcachegen.cc b/apt-pkg/pkgcachegen.cc index 490c2ecbb..54b07c465 100644 --- a/apt-pkg/pkgcachegen.cc +++ b/apt-pkg/pkgcachegen.cc @@ -195,8 +195,10 @@ bool pkgCacheGenerator::MergeList(ListParser &List, string const Version = List.Version(); if (Version.empty() == true && Arch.empty() == true) { + // package descriptions if (MergeListGroup(List, PackageName) == false) return false; + continue; } if (Arch.empty() == true) diff --git a/debian/changelog b/debian/changelog index 90f3199b3..936f2557e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -32,6 +32,9 @@ apt (0.9.7.5) UNRELEASED; urgency=low - do not warn about files which have a record in the Release file, but are not present on the CD to mirror the behavior of the other methods and to allow uncompressed indexes to be dropped without scaring users + * apt-pkg/pkgcachegen.cc: + - do not create 'native' (or now 'none') package structures as a side + effect of description translation parsing as it pollutes the cache -- David Kalnischkies Sun, 26 Aug 2012 10:49:17 +0200 -- cgit v1.2.3