| 1 | #include <apt-pkg/pkgcache.h> |
| 2 | #include <apt-pkg/cacheset.h> |
| 3 | #include <apt-pkg/debsystem.h> |
| 4 | #include <apt-pkg/fileutl.h> |
| 5 | #include <apt-pkg/statechanges.h> |
| 6 | |
| 7 | #include <algorithm> |
| 8 | #include <memory> |
| 9 | |
| 10 | namespace APT |
| 11 | { |
| 12 | |
| 13 | class StateChanges::Private |
| 14 | { |
| 15 | public: |
| 16 | APT::VersionVector hold; |
| 17 | APT::VersionVector unhold; |
| 18 | APT::VersionVector install; |
| 19 | APT::VersionVector deinstall; |
| 20 | APT::VersionVector purge; |
| 21 | APT::VersionVector error; |
| 22 | }; |
| 23 | |
| 24 | #define APT_GETTERSETTER(Name, Container) \ |
| 25 | void StateChanges::Name(pkgCache::VerIterator const &Ver) \ |
| 26 | { \ |
| 27 | Container.push_back(Ver); \ |
| 28 | }\ |
| 29 | APT::VersionVector& StateChanges::Name() \ |
| 30 | { \ |
| 31 | return Container; \ |
| 32 | } |
| 33 | APT_GETTERSETTER(Hold, d->hold) |
| 34 | APT_GETTERSETTER(Unhold, d->unhold) |
| 35 | APT_GETTERSETTER(Install, d->install) |
| 36 | APT_GETTERSETTER(Remove, d->deinstall) |
| 37 | APT_GETTERSETTER(Purge, d->purge) |
| 38 | #undef APT_GETTERSETTER |
| 39 | APT::VersionVector& StateChanges::Error() |
| 40 | { |
| 41 | return d->error; |
| 42 | } |
| 43 | |
| 44 | void StateChanges::clear() |
| 45 | { |
| 46 | d->hold.clear(); |
| 47 | d->unhold.clear(); |
| 48 | d->install.clear(); |
| 49 | d->deinstall.clear(); |
| 50 | d->purge.clear(); |
| 51 | d->error.clear(); |
| 52 | } |
| 53 | |
| 54 | bool StateChanges::empty() const |
| 55 | { |
| 56 | return d->hold.empty() && |
| 57 | d->unhold.empty() && |
| 58 | d->install.empty() && |
| 59 | d->deinstall.empty() && |
| 60 | d->purge.empty() && |
| 61 | d->error.empty(); |
| 62 | } |
| 63 | |
| 64 | bool StateChanges::Save(bool const DiscardOutput) |
| 65 | { |
| 66 | d->error.clear(); |
| 67 | if (d->hold.empty() && d->unhold.empty() && d->install.empty() && d->deinstall.empty() && d->purge.empty()) |
| 68 | return true; |
| 69 | |
| 70 | std::vector<std::string> Args = debSystem::GetDpkgBaseCommand(); |
| 71 | // ensure dpkg knows about the package so that it keeps the status we set |
| 72 | if (d->hold.empty() == false || d->install.empty() == false) |
| 73 | { |
| 74 | APT::VersionVector makeDpkgAvailable; |
| 75 | auto const notInstalled = [](pkgCache::VerIterator const &V) { return V.ParentPkg()->CurrentVer == 0; }; |
| 76 | std::copy_if(d->hold.begin(), d->hold.end(), std::back_inserter(makeDpkgAvailable), notInstalled); |
| 77 | std::copy_if(d->install.begin(), d->install.end(), std::back_inserter(makeDpkgAvailable), notInstalled); |
| 78 | |
| 79 | if (makeDpkgAvailable.empty() == false) |
| 80 | { |
| 81 | auto const BaseArgs = Args.size(); |
| 82 | Args.push_back("--merge-avail"); |
| 83 | // FIXME: supported only since 1.17.7 in dpkg |
| 84 | Args.push_back("-"); |
| 85 | int dummyAvail = -1; |
| 86 | pid_t const dpkgMergeAvail = debSystem::ExecDpkg(Args, &dummyAvail, nullptr, true); |
| 87 | |
| 88 | FILE* dpkg = fdopen(dummyAvail, "w"); |
| 89 | for (auto const &V: makeDpkgAvailable) |
| 90 | fprintf(dpkg, "Package: %s\nVersion: 0~\nArchitecture: %s\nMaintainer: Dummy Example <dummy@example.org>\n" |
| 91 | "Description: dummy package record\n A record is needed to put a package on hold, so here it is.\n\n", V.ParentPkg().Name(), V.Arch()); |
| 92 | fclose(dpkg); |
| 93 | |
| 94 | ExecWait(dpkgMergeAvail, "dpkg --merge-avail", true); |
| 95 | Args.erase(Args.begin() + BaseArgs, Args.end()); |
| 96 | } |
| 97 | } |
| 98 | bool const dpkgMultiArch = _system->MultiArchSupported(); |
| 99 | |
| 100 | Args.push_back("--set-selections"); |
| 101 | int selections = -1; |
| 102 | pid_t const dpkgSelections = debSystem::ExecDpkg(Args, &selections, nullptr, DiscardOutput); |
| 103 | |
| 104 | FILE* dpkg = fdopen(selections, "w"); |
| 105 | std::string state; |
| 106 | auto const dpkgName = [&](pkgCache::VerIterator const &V) { |
| 107 | pkgCache::PkgIterator P = V.ParentPkg(); |
| 108 | if (dpkgMultiArch == false) |
| 109 | fprintf(dpkg, "%s %s\n", P.FullName(true).c_str(), state.c_str()); |
| 110 | else |
| 111 | fprintf(dpkg, "%s:%s %s\n", P.Name(), V.Arch(), state.c_str()); |
| 112 | }; |
| 113 | for (auto const &V: d->unhold) |
| 114 | { |
| 115 | if (V.ParentPkg()->CurrentVer != 0) |
| 116 | state = "install"; |
| 117 | else |
| 118 | state = "deinstall"; |
| 119 | dpkgName(V); |
| 120 | } |
| 121 | if (d->purge.empty() == false) |
| 122 | { |
| 123 | state = "purge"; |
| 124 | std::for_each(d->purge.begin(), d->purge.end(), dpkgName); |
| 125 | } |
| 126 | if (d->deinstall.empty() == false) |
| 127 | { |
| 128 | state = "deinstall"; |
| 129 | std::for_each(d->deinstall.begin(), d->deinstall.end(), dpkgName); |
| 130 | } |
| 131 | if (d->hold.empty() == false) |
| 132 | { |
| 133 | state = "hold"; |
| 134 | std::for_each(d->hold.begin(), d->hold.end(), dpkgName); |
| 135 | } |
| 136 | if (d->install.empty() == false) |
| 137 | { |
| 138 | state = "install"; |
| 139 | std::for_each(d->install.begin(), d->install.end(), dpkgName); |
| 140 | } |
| 141 | fclose(dpkg); |
| 142 | |
| 143 | if (ExecWait(dpkgSelections, "dpkg --set-selections") == false) |
| 144 | { |
| 145 | std::move(d->purge.begin(), d->purge.end(), std::back_inserter(d->error)); |
| 146 | d->purge.clear(); |
| 147 | std::move(d->deinstall.begin(), d->deinstall.end(), std::back_inserter(d->error)); |
| 148 | d->deinstall.clear(); |
| 149 | std::move(d->hold.begin(), d->hold.end(), std::back_inserter(d->error)); |
| 150 | d->hold.clear(); |
| 151 | std::move(d->unhold.begin(), d->unhold.end(), std::back_inserter(d->error)); |
| 152 | d->unhold.clear(); |
| 153 | std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error)); |
| 154 | d->install.clear(); |
| 155 | } |
| 156 | return d->error.empty(); |
| 157 | } |
| 158 | |
| 159 | StateChanges::StateChanges() : d(new StateChanges::Private()) {} |
| 160 | StateChanges::StateChanges(StateChanges&&) = default; |
| 161 | StateChanges& StateChanges::operator=(StateChanges&&) = default; |
| 162 | StateChanges::~StateChanges() = default; |
| 163 | |
| 164 | } |