#include <apti18n.h>
/*}}}*/
+extern char **environ;
+
using namespace std;
APT_PURE static string AptHistoryRequestingUser() /*{{{*/
// of each array is the key, entry 1 is the value.
const std::pair<const char *, const char *> 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")),
{
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<pkgCache::PkgIterator> 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<std::string> 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 = "";
// 'processing: action: pkg'
if(prefix == "processing")
{
- const std::pair<const char *, const char *> * 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)
// short pkgnames?
if (action == "disappear")
handleDisappearAction(pkgname);
+ else if (action == "upgrade")
+ handleCrossUpgradeAction(pkgname);
return;
}
if (prefix == "status")
{
std::vector<struct DpkgState> &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)
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;
+ }
}
}
/*}}}*/
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));
}
}
/*}}}*/
+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)
{
++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
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);
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));
}
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; });
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))