X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/4f242a2289cc5db7a53afdb875291c94de64fd90..4e99adb0d3727c0ae41edc9b3f52448d0d5b7655:/apt-pkg/deb/dpkgpm.cc diff --git a/apt-pkg/deb/dpkgpm.cc b/apt-pkg/deb/dpkgpm.cc index 9c871d477..79c5f9bdf 100644 --- a/apt-pkg/deb/dpkgpm.cc +++ b/apt-pkg/deb/dpkgpm.cc @@ -61,6 +61,8 @@ #include /*}}}*/ +extern char **environ; + using namespace std; APT_PURE static string AptHistoryRequestingUser() /*{{{*/ @@ -141,6 +143,8 @@ namespace // of each array is the key, entry 1 is the value. const std::pair PackageProcessingOps[] = { std::make_pair("install", N_("Installing %s")), + // we don't care for the difference + std::make_pair("upgrade", N_("Installing %s")), std::make_pair("configure", N_("Configuring %s")), std::make_pair("remove", N_("Removing %s")), std::make_pair("purge", N_("Completely removing %s")), @@ -613,68 +617,154 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line) { pkgname = APT::String::Strip(list[2]); action = APT::String::Strip(list[1]); - // we don't care for the difference (as dpkg doesn't really either) - if (action == "upgrade") - action = "install"; } // "status" has the form: "status: pkg: state" - // with state in ["half-installed", "unpacked", "half-configured", + // with state in ["half-installed", "unpacked", "half-configured", // "installed", "config-files", "not-installed"] else if (prefix == "status") { pkgname = APT::String::Strip(list[1]); action = APT::String::Strip(list[2]); - } else { - if (Debug == true) - std::clog << "unknown prefix '" << prefix << "'" << std::endl; - return; - } + /* handle the special cases first: - /* handle the special cases first: - - errors look like this: - 'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data - and conffile-prompt like this - 'status:/etc/compiz.conf/compiz.conf : conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited - */ - if (prefix == "status") - { + errors look like this: + 'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data + and conffile-prompt like this + 'status:/etc/compiz.conf/compiz.conf : conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited + */ if(action == "error") { - d->progress->Error(pkgname, PackagesDone, PackagesTotal, - list[3]); - pkgFailures++; + d->progress->Error(pkgname, PackagesDone, PackagesTotal, list[3]); + ++pkgFailures; WriteApportReport(pkgname.c_str(), list[3].c_str()); return; } else if(action == "conffile-prompt") { - d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal, - list[3]); + d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal, list[3]); return; } + } else { + if (Debug == true) + std::clog << "unknown prefix '" << prefix << "'" << std::endl; + return; } - // at this point we know that we should have a valid pkgname, so build all - // the info from it - - // dpkg does not always send "pkgname:arch" so we add it here if needed + // At this point we have a pkgname, but it might not be arch-qualified ! if (pkgname.find(":") == std::string::npos) { - // find the package in the group that is touched by dpkg - // if there are multiple pkgs dpkg would send us a full pkgname:arch pkgCache::GrpIterator Grp = Cache.FindGrp(pkgname); - if (Grp.end() == false) - for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P)) - if(Cache[P].Keep() == false || Cache[P].ReInstall() == true) + /* No arch means that dpkg believes there can only be one package + this can refer to so lets see what could be candidates here: */ + std::vector candset; + for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P)) + { + if (PackageOps.find(P.FullName()) != PackageOps.end()) + candset.push_back(P); + // packages can disappear without them having any interaction itself + // so we have to consider these as candidates, too + else if (P->CurrentVer != 0 && action == "disappear") + candset.push_back(P); + } + if (unlikely(candset.empty())) + { + if (Debug == true) + std::clog << "unable to figure out which package is dpkg refering to with '" << pkgname << "'! (1)" << std::endl; + return; + } + else if (candset.size() == 1) // we are lucky + pkgname = candset.cbegin()->FullName(); + else + { + /* here be dragons^Wassumptions about dpkg: + - an M-A:same version is always arch-qualified + - a package from a foreign arch is (in newer versions) */ + size_t installedInstances = 0, wannabeInstances = 0; + for (auto const &P: candset) + { + if (P->CurrentVer != 0) { - auto fullname = P.FullName(); - if (Cache[P].Delete() && PackageOps[fullname].size() <= PackageOpsDone[fullname]) + ++installedInstances; + if (Cache[P].Delete() == false) + ++wannabeInstances; + } + else if (Cache[P].Install()) + ++wannabeInstances; + } + // the package becomes M-A:same, so we are still talking about current + if (installedInstances == 1 && wannabeInstances >= 2) + { + for (auto const &P: candset) + { + if (P->CurrentVer == 0) continue; - pkgname = std::move(fullname); + pkgname = P.FullName(); + break; + } + } + // the package was M-A:same, it isn't now, so we can only talk about that + else if (installedInstances >= 2 && wannabeInstances == 1) + { + for (auto const &P: candset) + { + auto const IV = Cache[P].InstVerIter(Cache); + if (IV.end()) + continue; + pkgname = P.FullName(); break; } + } + // that is a crossgrade + else if (installedInstances == 1 && wannabeInstances == 1 && candset.size() == 2) + { + auto const PkgHasCurrentVersion = [](pkgCache::PkgIterator const &P) { return P->CurrentVer != 0; }; + auto const P = std::find_if(candset.begin(), candset.end(), PkgHasCurrentVersion); + if (unlikely(P == candset.end())) + { + if (Debug == true) + std::clog << "situation for '" << pkgname << "' looked like a crossgrade, but no current version?!" << std::endl; + return; + } + auto fullname = P->FullName(); + if (PackageOps[fullname].size() != PackageOpsDone[fullname]) + pkgname = std::move(fullname); + else + pkgname = std::find_if_not(candset.begin(), candset.end(), PkgHasCurrentVersion)->FullName(); + } + // we are desperate: so "just" take the native one, but that might change mid-air, + // so we have to ask dpkg what it believes native is at the moment… all the time + else + { + std::vector sArgs = debSystem::GetDpkgBaseCommand(); + sArgs.push_back("--print-architecture"); + int outputFd = -1; + pid_t const dpkgNativeArch = debSystem::ExecDpkg(sArgs, nullptr, &outputFd, true); + if (unlikely(dpkgNativeArch == -1)) + { + if (Debug == true) + std::clog << "calling dpkg failed to ask it for its current native architecture to expand '" << pkgname << "'!" << std::endl; + return; + } + FILE *dpkg = fdopen(outputFd, "r"); + if(dpkg != NULL) + { + char* buf = NULL; + size_t bufsize = 0; + if (getline(&buf, &bufsize, dpkg) != -1) + pkgname += ':' + bufsize; + free(buf); + fclose(dpkg); + } + ExecWait(dpkgNativeArch, "dpkg --print-architecture", true); + if (pkgname.find(':') != std::string::npos) + { + if (Debug == true) + std::clog << "unable to figure out which package is dpkg refering to with '" << pkgname << "'! (2)" << std::endl; + return; + } + } + } } std::string arch = ""; @@ -688,10 +778,7 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line) // 'processing: action: pkg' if(prefix == "processing") { - const std::pair * const iter = - std::find_if(PackageProcessingOpsBegin, - PackageProcessingOpsEnd, - MatchProcessingOp(action.c_str())); + auto const iter = std::find_if(PackageProcessingOpsBegin, PackageProcessingOpsEnd, MatchProcessingOp(action.c_str())); if(iter == PackageProcessingOpsEnd) { if (Debug == true) @@ -707,24 +794,15 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line) // short pkgnames? if (action == "disappear") handleDisappearAction(pkgname); + else if (action == "upgrade") + handleCrossUpgradeAction(pkgname); return; } if (prefix == "status") { std::vector &states = PackageOps[pkgname]; - if (action == "triggers-pending") - { - if (Debug == true) - std::clog << "(parsed from dpkg) pkg: " << pkgname - << " action: " << action << " (prefix 2 to " - << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl; - - states.insert(states.begin(), {"installed", N_("Installed %s")}); - states.insert(states.begin(), {"half-configured", N_("Configuring %s")}); - PackagesTotal += 2; - } - else if(PackageOpsDone[pkgname] < states.size()) + if(PackageOpsDone[pkgname] < states.size()) { char const * next_action = states[PackageOpsDone[pkgname]].state; if (next_action) @@ -761,36 +839,19 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line) strprintf(msg, translation, i18n_pkgname.c_str()); d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg); } - else if (action == "unpacked" && strcmp(next_action, "config-files") == 0) - { - // in a crossgrade what looked like a remove first is really an unpack over it - ++PackageOpsDone[pkgname]; - ++PackagesDone; - - auto const Pkg = Cache.FindPkg(pkgname); - if (likely(Pkg.end() == false)) - { - auto const Grp = Pkg.Group(); - if (likely(Grp.end() == false)) - { - for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P)) - if(Cache[P].Install()) - { - auto && Ops = PackageOps[P.FullName()]; - auto const unpackOp = std::find_if(Ops.cbegin(), Ops.cend(), [](DpkgState const &s) { return strcmp(s.state, "unpacked") == 0; }); - if (unpackOp != Ops.cend()) - { - auto const skipped = std::distance(Ops.cbegin(), unpackOp); - PackagesDone += skipped; - PackageOpsDone[P.FullName()] += skipped; - break; - } - } - } - } - } } } + else if (action == "triggers-pending") + { + if (Debug == true) + std::clog << "(parsed from dpkg) pkg: " << pkgname + << " action: " << action << " (prefix 2 to " + << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl; + + states.insert(states.begin(), {"installed", N_("Installed %s")}); + states.insert(states.begin(), {"half-configured", N_("Configuring %s")}); + PackagesTotal += 2; + } } } /*}}}*/ @@ -801,6 +862,12 @@ void pkgDPkgPM::handleDisappearAction(string const &pkgname) if (unlikely(Pkg.end() == true)) return; + // a disappeared package has no further actions + auto const ROps = PackageOps[Pkg.FullName()].size(); + auto && ROpsDone = PackageOpsDone[Pkg.FullName()]; + PackagesDone += ROps - ROpsDone; + ROpsDone = ROps; + // record the package name for display and stuff later disappearedPkgs.insert(Pkg.FullName(true)); @@ -842,6 +909,38 @@ void pkgDPkgPM::handleDisappearAction(string const &pkgname) } } /*}}}*/ +void pkgDPkgPM::handleCrossUpgradeAction(string const &pkgname) /*{{{*/ +{ + // in a crossgrade what looked like a remove first is really an unpack over it + auto const Pkg = Cache.FindPkg(pkgname); + if (likely(Pkg.end() == false) && Cache[Pkg].Delete()) + { + auto const Grp = Pkg.Group(); + if (likely(Grp.end() == false)) + { + for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P)) + if(Cache[P].Install()) + { + auto && Ops = PackageOps[P.FullName()]; + auto const unpackOp = std::find_if(Ops.cbegin(), Ops.cend(), [](DpkgState const &s) { return strcmp(s.state, "unpacked") == 0; }); + if (unpackOp != Ops.cend()) + { + // skip ahead in the crossgraded packages + auto const skipped = std::distance(Ops.cbegin(), unpackOp); + PackagesDone += skipped; + PackageOpsDone[P.FullName()] += skipped; + // finish the crossremoved package + auto const ROps = PackageOps[Pkg.FullName()].size(); + auto && ROpsDone = PackageOpsDone[Pkg.FullName()]; + PackagesDone += ROps - ROpsDone; + ROpsDone = ROps; + break; + } + } + } + } +} + /*}}}*/ // DPkgPM::DoDpkgStatusFd /*{{{*/ void pkgDPkgPM::DoDpkgStatusFd(int statusfd) { @@ -1085,6 +1184,18 @@ void pkgDPkgPM::BuildPackagesProgressMap() ++PackagesTotal; return true; }); + if ((I.Op == Item::Remove || I.Op == Item::Purge) && I.Pkg->CurrentVer != 0) + { + if (I.Pkg->CurrentState == pkgCache::State::UnPacked || + I.Pkg->CurrentState == pkgCache::State::HalfInstalled) + { + if (likely(strcmp(PackageOps[name][0].state, "half-configured") == 0)) + { + ++PackageOpsDone[name]; + --PackagesTotal; + } + } + } } /* one extra: We don't want the progress bar to reach 100%, especially not if we call dpkg --configure --pending and process a bunch of triggers @@ -1274,7 +1385,9 @@ static void cleanUpTmpDir(char * const tmpdir) /*{{{*/ if (unlikely(Ent->d_type != DT_LNK && Ent->d_type != DT_UNKNOWN)) continue; #endif - if (unlikely(unlinkat(dfd, Ent->d_name, 0) != 0)) + char path[strlen(tmpdir) + 1 + strlen(Ent->d_name) + 1]; + sprintf(path, "%s/%s", tmpdir, Ent->d_name); + if (unlikely(unlink(path) != 0)) break; } closedir(D); @@ -1325,7 +1438,8 @@ bool pkgDPkgPM::ExpandPendingCalls(std::vector &List, pkgDepCache &Cache) if (I.Op == Item::Install && alreadyConfigured.insert(I.Pkg->ID).second == true) AppendList.emplace_back(Item::Configure, I.Pkg); for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg) - if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure && alreadyConfigured.insert(Pkg->ID).second == true) + if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure && + Cache[Pkg].Delete() == false && alreadyConfigured.insert(Pkg->ID).second == true) AppendList.emplace_back(Item::Configure, Pkg); std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List)); } @@ -1426,11 +1540,30 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) continue; auto const Grp = I->Pkg.Group(); - size_t installedInstances = 0; + size_t installedInstances = 0, wannabeInstances = 0; + bool multiArchInstances = false; for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg)) - if (Pkg->CurrentVer != 0 || Cache[Pkg].Install()) + { + if (Pkg->CurrentVer != 0) + { ++installedInstances; - if (installedInstances == 2) + if (Cache[Pkg].Delete() == false) + ++wannabeInstances; + } + else if (PackageOps.find(Pkg.FullName()) != PackageOps.end()) + ++wannabeInstances; + if (multiArchInstances == false) + { + auto const V = Cache[Pkg].InstVerIter(Cache); + if (V.end() == false && (Pkg->CurrentVer == 0 || V != Pkg.CurrentVer())) + multiArchInstances = ((V->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same); + } + } + /* theoretically the installed check would be enough as some wannabe will + be first and hence be the crossgrade we were looking for, but #844300 + prevents this so we keep these situations explicit removes. + It is also the reason why neither of them can be a M-A:same package */ + if (installedInstances == 1 && wannabeInstances == 1 && multiArchInstances == false) { auto const FirstInstall = std::find_if_not(I, List.end(), [](Item const &i) { return i.Op == Item::Remove || i.Op == Item::Purge; }); @@ -1992,6 +2125,24 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress) if (noopDPkgInvocation == false) { + if (d->dpkg_error.empty() && (PackagesDone + 1) != PackagesTotal) + { + std::string pkglist; + for (auto const &PO: PackageOps) + if (PO.second.size() != PackageOpsDone[PO.first]) + { + if (pkglist.empty() == false) + pkglist.append(" "); + pkglist.append(PO.first); + } + /* who cares about correct progress? As we depend on it for skipping actions + our parsing should be correct. People will no doubt be confused if they see + this message, but the dpkg warning about unknown packages isn't much better + from a user POV and combined we might have a chance to figure out what is wrong */ + _error->Warning("APT had planned for dpkg to do more than it reported back (%u vs %u).\n" + "Affected packages: %s", PackagesDone, PackagesTotal, pkglist.c_str()); + } + std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache"); if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true && RemoveFile("pkgDPkgPM::Go", oldpkgcache))