From 64e3414e00906e6eaa72d7b63ca76d1c59ecadf6 Mon Sep 17 00:00:00 2001 From: David Kalnischkies Date: Fri, 25 Sep 2015 19:58:43 +0200 Subject: allow all dpkg selections to be set via apt-mark and libapt As we have support for 'hold', we need support for undoing a hold which in effect means that we implemented most other states as well, just that they weren't exposed in the interface directly so far. --- apt-pkg/statechanges.cc | 88 ++++++++++++++++++++++---------- apt-pkg/statechanges.h | 18 ++++--- apt-private/private-cmndline.cc | 28 +++++++--- cmdline/apt-mark.cc | 102 +++++++++++++++++++++++-------------- doc/apt-mark.8.xml | 110 +++++++++++++++++++++++----------------- doc/apt-verbatim.ent | 41 ++++++++++++--- test/integration/test-apt-mark | 40 +++++++++++++++ 7 files changed, 297 insertions(+), 130 deletions(-) diff --git a/apt-pkg/statechanges.cc b/apt-pkg/statechanges.cc index a20319d2d..dc446a665 100644 --- a/apt-pkg/statechanges.cc +++ b/apt-pkg/statechanges.cc @@ -14,46 +14,62 @@ class StateChanges::Private { public: APT::VersionVector hold; + APT::VersionVector unhold; APT::VersionVector install; + APT::VersionVector deinstall; + APT::VersionVector purge; APT::VersionVector error; }; -void StateChanges::Hold(pkgCache::VerIterator const &Ver) -{ - d->hold.push_back(Ver); -} -APT::VersionVector& StateChanges::Hold() -{ - return d->hold; -} -void StateChanges::Unhold(pkgCache::VerIterator const &Ver) -{ - d->install.push_back(Ver); -} -APT::VersionVector& StateChanges::Unhold() -{ - return d->install; +#define APT_GETTERSETTER(Name, Container) \ +void StateChanges::Name(pkgCache::VerIterator const &Ver) \ +{ \ + Container.push_back(Ver); \ +}\ +APT::VersionVector& StateChanges::Name() \ +{ \ + return Container; \ } +APT_GETTERSETTER(Hold, d->hold) +APT_GETTERSETTER(Unhold, d->unhold) +APT_GETTERSETTER(Install, d->install) +APT_GETTERSETTER(Remove, d->deinstall) +APT_GETTERSETTER(Purge, d->purge) +#undef APT_GETTERSETTER APT::VersionVector& StateChanges::Error() { return d->error; } -void StateChanges::Discard() +void StateChanges::clear() { d->hold.clear(); + d->unhold.clear(); d->install.clear(); + d->deinstall.clear(); + d->purge.clear(); d->error.clear(); } +bool StateChanges::empty() const +{ + return d->hold.empty() && + d->unhold.empty() && + d->install.empty() && + d->deinstall.empty() && + d->purge.empty() && + d->error.empty(); +} + bool StateChanges::Save(bool const DiscardOutput) { d->error.clear(); - if (d->hold.empty() && d->install.empty()) + if (d->hold.empty() && d->unhold.empty() && d->install.empty() && d->deinstall.empty() && d->purge.empty()) return true; std::vector Args = debSystem::GetDpkgBaseCommand(); // ensure dpkg knows about the package so that it keeps the status we set + if (d->hold.empty() == false || d->install.empty() == false) { APT::VersionVector makeDpkgAvailable; auto const notInstalled = [](pkgCache::VerIterator const &V) { return V.ParentPkg()->CurrentVer == 0; }; @@ -94,6 +110,24 @@ bool StateChanges::Save(bool const DiscardOutput) else fprintf(dpkg, "%s:%s %s\n", P.Name(), V.Arch(), state.c_str()); }; + for (auto const &V: d->unhold) + { + if (V.ParentPkg()->CurrentVer != 0) + state = "install"; + else + state = "deinstall"; + dpkgName(V); + } + if (d->purge.empty() == false) + { + state = "purge"; + std::for_each(d->purge.begin(), d->purge.end(), dpkgName); + } + if (d->deinstall.empty() == false) + { + state = "deinstall"; + std::for_each(d->deinstall.begin(), d->deinstall.end(), dpkgName); + } if (d->hold.empty() == false) { state = "hold"; @@ -108,16 +142,16 @@ bool StateChanges::Save(bool const DiscardOutput) if (ExecWait(dpkgSelections, "dpkg --set-selections") == false) { - if (d->hold.empty()) - std::swap(d->install, d->error); - else if (d->install.empty()) - std::swap(d->hold, d->error); - else - { - std::swap(d->hold, d->error); - std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error)); - d->install.clear(); - } + std::move(d->purge.begin(), d->purge.end(), std::back_inserter(d->error)); + d->purge.clear(); + std::move(d->deinstall.begin(), d->deinstall.end(), std::back_inserter(d->error)); + d->deinstall.clear(); + std::move(d->hold.begin(), d->hold.end(), std::back_inserter(d->error)); + d->hold.clear(); + std::move(d->unhold.begin(), d->unhold.end(), std::back_inserter(d->error)); + d->unhold.clear(); + std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error)); + d->install.clear(); } return d->error.empty(); } diff --git a/apt-pkg/statechanges.h b/apt-pkg/statechanges.h index fa60c5864..1eaf21a3a 100644 --- a/apt-pkg/statechanges.h +++ b/apt-pkg/statechanges.h @@ -14,14 +14,20 @@ class APT_PUBLIC StateChanges { public: // getter/setter for the different states - APT::VersionVector& Hold(); - void Hold(pkgCache::VerIterator const &Ver); - APT::VersionVector& Unhold(); - void Unhold(pkgCache::VerIterator const &Ver); +#define APT_GETTERSETTER(Name) \ + APT::VersionVector& Name(); \ + void Name(pkgCache::VerIterator const &Ver) + APT_GETTERSETTER(Hold); + APT_GETTERSETTER(Unhold); + APT_GETTERSETTER(Install); + APT_GETTERSETTER(Remove); + APT_GETTERSETTER(Purge); APT::VersionVector& Error(); +#undef APT_GETTERSETTER - // forgets all unsaved changes - void Discard(); + // operate on all containers at once + void clear(); + bool empty() const; /** commit the staged changes to the database(s). * diff --git a/apt-private/private-cmndline.cc b/apt-private/private-cmndline.cc index 7190fe50b..5d6fd3c2e 100644 --- a/apt-private/private-cmndline.cc +++ b/apt-private/private-cmndline.cc @@ -227,19 +227,31 @@ static bool addArgumentsAPTGet(std::vector &Args, char const static bool addArgumentsAPTMark(std::vector &Args, char const * const Cmd)/*{{{*/ { if (CmdMatches("auto", "manual", "hold", "unhold", "showauto", - "showmanual", "showhold", "showholds", "install", + "showmanual", "showhold", "showholds", "markauto", "unmarkauto")) + { + addArg('f',"file","Dir::State::extended_states",CommandLine::HasArg); + } + else if (CmdMatches("install", "remove", "deinstall", "purge", + "showinstall", "showinstalls", "showremove", "showremoves", + "showdeinstall", "showdeinstalls", "showpurge", "showpurges")) ; else return false; - addArg('v',"verbose","APT::MarkAuto::Verbose",0); - addArg('s',"simulate","APT::Mark::Simulate",0); - addArg('s',"just-print","APT::Mark::Simulate",0); - addArg('s',"recon","APT::Mark::Simulate",0); - addArg('s',"dry-run","APT::Mark::Simulate",0); - addArg('s',"no-act","APT::Mark::Simulate",0); - addArg('f',"file","Dir::State::extended_states",CommandLine::HasArg); + if (CmdMatches("markauto", "unmarkauto")) + { + addArg('v',"verbose","APT::MarkAuto::Verbose",0); + } + + if (strncmp(Cmd, "show", strlen("show")) != 0) + { + addArg('s',"simulate","APT::Mark::Simulate",0); + addArg('s',"just-print","APT::Mark::Simulate",0); + addArg('s',"recon","APT::Mark::Simulate",0); + addArg('s',"dry-run","APT::Mark::Simulate",0); + addArg('s',"no-act","APT::Mark::Simulate",0); + } return true; } diff --git a/cmdline/apt-mark.cc b/cmdline/apt-mark.cc index 9d1d0863e..6080c7ea3 100644 --- a/cmdline/apt-mark.cc +++ b/cmdline/apt-mark.cc @@ -167,8 +167,8 @@ static bool ShowAuto(CommandLine &CmdL) return true; } /*}}}*/ -/* DoHold - mark packages as hold by dpkg {{{*/ -static bool DoHold(CommandLine &CmdL) +// DoSelection - wrapping around dpkg selections /*{{{*/ +static bool DoSelection(CommandLine &CmdL) { pkgCacheFile CacheFile; pkgCache *Cache = CacheFile.GetPkgCache(); @@ -179,29 +179,39 @@ static bool DoHold(CommandLine &CmdL) if (pkgset.empty() == true) return _error->Error(_("No packages found")); - bool const MarkHold = strcasecmp(CmdL.FileList[0],"hold") == 0; - - auto const part = std::stable_partition(pkgset.begin(), pkgset.end(), - [](pkgCache::VerIterator const &V) { return V.ParentPkg()->SelectedState == pkgCache::State::Hold; }); - - auto const doneBegin = MarkHold ? pkgset.begin() : part; - auto const doneEnd = MarkHold ? part : pkgset.end(); - - std::for_each(doneBegin, doneEnd, [&MarkHold](pkgCache::VerIterator const &V) { - if (MarkHold == true) - ioprintf(c1out, _("%s was already set on hold.\n"), V.ParentPkg().FullName(true).c_str()); - else - ioprintf(c1out, _("%s was already not hold.\n"), V.ParentPkg().FullName(true).c_str()); - }); - - if (doneBegin == pkgset.begin() && doneEnd == pkgset.end()) - return true; - - auto const changeBegin = MarkHold ? part : pkgset.begin(); - auto const changeEnd = MarkHold ? pkgset.end() : part; - APT::StateChanges marks; - std::move(changeBegin, changeEnd, std::back_inserter(MarkHold ? marks.Hold() : marks.Unhold())); + if (strcasecmp(CmdL.FileList[0], "hold") == 0 || strcasecmp(CmdL.FileList[0], "unhold") == 0) + { + auto const part = std::stable_partition(pkgset.begin(), pkgset.end(), + [](pkgCache::VerIterator const &V) { return V.ParentPkg()->SelectedState == pkgCache::State::Hold; }); + + bool const MarkHold = strcasecmp(CmdL.FileList[0],"hold") == 0; + auto const doneBegin = MarkHold ? pkgset.begin() : part; + auto const doneEnd = MarkHold ? part : pkgset.end(); + std::for_each(doneBegin, doneEnd, [&MarkHold](pkgCache::VerIterator const &V) { + if (MarkHold == true) + ioprintf(c1out, _("%s was already set on hold.\n"), V.ParentPkg().FullName(true).c_str()); + else + ioprintf(c1out, _("%s was already not hold.\n"), V.ParentPkg().FullName(true).c_str()); + }); + + if (doneBegin == pkgset.begin() && doneEnd == pkgset.end()) + return true; + + auto const changeBegin = MarkHold ? part : pkgset.begin(); + auto const changeEnd = MarkHold ? pkgset.end() : part; + std::move(changeBegin, changeEnd, std::back_inserter(MarkHold ? marks.Hold() : marks.Unhold())); + } + else + { + // FIXME: Maybe show a message for unchanged states here as well? + if (strcasecmp(CmdL.FileList[0], "purge") == 0) + std::swap(marks.Purge(), pkgset); + else if (strcasecmp(CmdL.FileList[0], "deinstall") == 0 || strcasecmp(CmdL.FileList[0], "remove") == 0) + std::swap(marks.Remove(), pkgset); + else //if (strcasecmp(CmdL.FileList[0], "install") == 0) + std::swap(marks.Install(), pkgset); + } pkgset.clear(); bool success = true; @@ -211,30 +221,44 @@ static bool DoHold(CommandLine &CmdL) if (success == false) _error->Error(_("Executing dpkg failed. Are you root?")); } - for (auto Ver : marks.Hold()) ioprintf(c1out,_("%s set on hold.\n"), Ver.ParentPkg().FullName(true).c_str()); for (auto Ver : marks.Unhold()) ioprintf(c1out,_("Canceled hold on %s.\n"), Ver.ParentPkg().FullName(true).c_str()); - + for (auto Ver : marks.Purge()) + ioprintf(c1out,_("Selected %s for purge.\n"), Ver.ParentPkg().FullName(true).c_str()); + for (auto Ver : marks.Remove()) + ioprintf(c1out,_("Selected %s for removal.\n"), Ver.ParentPkg().FullName(true).c_str()); + for (auto Ver : marks.Install()) + ioprintf(c1out,_("Selected %s for installation.\n"), Ver.ParentPkg().FullName(true).c_str()); return success; } /*}}}*/ -/* ShowHold - show packages set on hold in dpkg status {{{*/ -static bool ShowHold(CommandLine &CmdL) +static bool ShowSelection(CommandLine &CmdL) /*{{{*/ { pkgCacheFile CacheFile; pkgCache *Cache = CacheFile.GetPkgCache(); if (unlikely(Cache == NULL)) return false; + pkgCache::State::PkgSelectedState selector; + if (strncasecmp(CmdL.FileList[0], "showpurge", strlen("showpurge")) == 0) + selector = pkgCache::State::Purge; + else if (strncasecmp(CmdL.FileList[0], "showdeinstall", strlen("showdeinstall")) == 0 || + strncasecmp(CmdL.FileList[0], "showremove", strlen("showremove")) == 0) + selector = pkgCache::State::DeInstall; + else if (strncasecmp(CmdL.FileList[0], "showhold", strlen("showhold")) == 0) + selector = pkgCache::State::Hold; + else //if (strcasecmp(CmdL.FileList[0], "showinstall", strlen("showinstall")) == 0) + selector = pkgCache::State::Install; + std::vector packages; if (CmdL.FileList[1] == 0) { packages.reserve(50); // how many holds are realistic? I hope just a few… for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P) - if (P->SelectedState == pkgCache::State::Hold) + if (P->SelectedState == selector) packages.push_back(P.FullName(true)); } else @@ -243,7 +267,7 @@ static bool ShowHold(CommandLine &CmdL) APT::PackageSet pkgset = APT::PackageSet::FromCommandLine(CacheFile, CmdL.FileList + 1, helper); packages.reserve(pkgset.size()); for (APT::PackageSet::const_iterator P = pkgset.begin(); P != pkgset.end(); ++P) - if (P->SelectedState == pkgCache::State::Hold) + if (P->SelectedState == selector) packages.push_back(P.FullName(true)); } @@ -295,15 +319,19 @@ int main(int argc,const char *argv[]) /*{{{*/ CommandLine::Dispatch Cmds[] = {{"help",&ShowHelp}, {"auto",&DoAuto}, {"manual",&DoAuto}, - {"hold",&DoHold}, - {"unhold",&DoHold}, + {"hold",&DoSelection}, + {"unhold",&DoSelection}, + {"install",&DoSelection}, + {"remove",&DoSelection}, // dpkg uses deinstall, but we use remove everywhere else + {"deinstall",&DoSelection}, + {"purge",&DoSelection}, {"showauto",&ShowAuto}, {"showmanual",&ShowAuto}, - {"showhold",&ShowHold}, - // be nice and forgive the typo - {"showholds",&ShowHold}, - // be nice and forgive it as it is technical right - {"install",&DoHold}, + {"showhold",&ShowSelection}, {"showholds",&ShowSelection}, + {"showinstall",&ShowSelection}, {"showinstalls",&ShowSelection}, + {"showdeinstall",&ShowSelection}, {"showdeinstalls",&ShowSelection}, + {"showremove",&ShowSelection}, {"showremoves",&ShowSelection}, + {"showpurge",&ShowSelection}, {"showpurges",&ShowSelection}, // obsolete commands for compatibility {"markauto", &DoMarkAuto}, {"unmarkauto", &DoMarkAuto}, diff --git a/doc/apt-mark.8.xml b/doc/apt-mark.8.xml index 94f24437e..8dd157a60 100644 --- a/doc/apt-mark.8.xml +++ b/doc/apt-mark.8.xml @@ -14,7 +14,7 @@ &apt-email; &apt-product; - 2012-06-09T00:00:00Z + 2015-09-25T00:00:00Z @@ -26,31 +26,37 @@ apt-mark - mark/unmark a package as being automatically-installed + show, set and unset various settings for a package &synopsis-command-apt-mark; Description - apt-mark will change whether a package has - been marked as being automatically installed. + apt-mark can be used as a unified frontend to set + various settings for a package like marking a package as being + automatically/manually installed or changing dpkg + selections such as hold, install, deinstall and purge which are respected + e.g. by apt-get dselect-upgrade or aptitude. + Automatically and manually installed packages When you request that a package is installed, and as a result other packages are installed to satisfy its dependencies, the - dependencies are marked as being automatically installed. Once - these automatically installed packages are no longer depended on - by any manually installed packages, they will be removed by e.g. - apt-get or aptitude. + dependencies are marked as being automatically installed, while + package you installed explicitely is marked as manually installed. + Once a automatically installed package is no longer depended on + by any manually installed package it is considered no longer needed + and e.g. apt-get or aptitude + will at least suggest removing them. - + auto is used to mark a package as being automatically installed, which will cause the package to be removed when no more manually installed packages depend on this package. - + manual is used to mark a @@ -58,23 +64,7 @@ package from being automatically removed if no other packages depend on it. - - - - hold is used to mark a - package as held back, which will prevent the package from being - automatically installed, upgraded or removed. - The command is only a wrapper around dpkg --set-selections - and the state is therefore maintained by &dpkg; and not affected - by the option. - - - - - unhold is used to cancel a - previously set hold on a package to allow all actions again. - - + showauto is used to print a @@ -82,38 +72,66 @@ All automatically installed packages will be listed if no package is given. If packages are given only those which are automatically installed will be shown. - + showmanual can be used in the same way as showauto except that it will print a list of manually installed packages instead. + + + + Options + + + + + + Read/Write package stats from the filename given with the parameter + &synopsis-param-filename; instead of from the default location, which + is extended_status in the directory defined + by the Configuration Item: Dir::State. + + + - - showhold is used to print a list - of packages on hold in the same way as for the other show commands. + Prevent changes for a package + + + hold is used to mark a + package as held back, which will prevent the package from being + automatically installed, upgraded or removed. - + + + unhold is used to cancel a + previously set hold on a package to allow all actions again. + + - - - - options - - - - - - - Read/Write package stats from the filename given with the parameter - &synopsis-param-filename; instead of from the default location, which - is extended_status in the directory defined - by the Configuration Item: Dir::State. + + showhold is used to print a list + of packages on hold in the same way as for the other show commands. + + + Shedule packages for install, remove and purge + + Some frontends like apt-get dselect-upgrade can be used to + apply previously sheduled changes to the install state of packages. Such changes + can be sheduled with the , + (also known as ) and commands. + Packages with a specific selection can be displayed with , + and respectively. + More information about these so called dpkg selections can be found in &dpkg;. + + + Options + &apt-commonoptions; diff --git a/doc/apt-verbatim.ent b/doc/apt-verbatim.ent index 448e6c2df..f5e322e11 100644 --- a/doc/apt-verbatim.ent +++ b/doc/apt-verbatim.ent @@ -394,18 +394,47 @@ "> apt-mark - + + + + + + auto + manual + + &synopsis-arg-pkg; + + + + showauto + showmanual + + &synopsis-pkg; + + + &synopsis-help; + +apt-mark - auto - manual - showauto - showmanual + hold + unhold + install + remove + purge &synopsis-arg-pkg; - &synopsis-help; + + + showhold + showinstall + showremove + showpurge + + &synopsis-pkg; + "> diff --git a/test/integration/test-apt-mark b/test/integration/test-apt-mark index ec4ed8316..47ade45b1 100755 --- a/test/integration/test-apt-mark +++ b/test/integration/test-apt-mark @@ -112,3 +112,43 @@ Inst uninstalled (1 unstable [all]) Inst uninstalled-native (1 unstable [amd64]) Conf uninstalled (1 unstable [all]) Conf uninstalled-native (1 unstable [amd64])' aptget install uninstalled uninstalled-native -s +testsuccess aptmark unhold uninstalled uninstalled-native + +testselections() { + testsuccess aptmark hold "$1" + testsuccessequal "$1" aptmark showholds "$1" + testsuccess aptmark unhold "$1" + testsuccessequal "$1" aptmark showinstalls "$1" + testsuccess aptmark hold "$1" + testsuccessequal "$1" aptmark showholds "$1" + testsuccess aptmark install "$1" + testsuccessequal "$1" aptmark showinstalls "$1" + testsuccess aptmark remove "$1" + testsuccessequal "$1" aptmark showremoves "$1" + testsuccess aptmark purge "$1" + testsuccessequal "$1" aptmark showpurges "$1" +} +testselections 'foo' +testselections 'bar' + +testsuccessequal 'Reading package lists... +Building dependency tree... +Reading state information... +The following packages will be REMOVED: + bar* foo* +0 upgraded, 0 newly installed, 2 to remove and 0 not upgraded. +Purg bar [1] +Purg foo [1]' aptget dselect-upgrade -s + +testuninstalledselections() { + testsuccess aptmark hold "$1" + testsuccessequal "$1" aptmark showholds "$1" + testsuccess aptmark unhold "$1" + testsuccessequal "$1" aptmark showremoves "$1" + testsuccess aptmark hold "$1" + testsuccessequal "$1" aptmark showholds "$1" + testsuccess aptmark install "$1" + testsuccessequal "$1" aptmark showinstalls "$1" +} +testuninstalledselections 'uninstalled' +testuninstalledselections 'uninstalled-native' -- cgit v1.2.3