diff options
Diffstat (limited to 'dselect/pkglist.cc')
-rw-r--r-- | dselect/pkglist.cc | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/dselect/pkglist.cc b/dselect/pkglist.cc new file mode 100644 index 0000000..9ffe855 --- /dev/null +++ b/dselect/pkglist.cc @@ -0,0 +1,639 @@ +/* + * dselect - Debian package maintenance user interface + * pkglist.cc - package list administration + * + * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk> + * Copyright © 2001 Wichert Akkerman <wakkerma@debian.org> + * Copyright © 2008-2014 Guillem Jover <guillem@debian.org> + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <compat.h> + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include <dpkg/i18n.h> +#include <dpkg/dpkg.h> +#include <dpkg/dpkg-db.h> +#include <dpkg/string.h> + +#include "dselect.h" +#include "bindings.h" + +int packagelist::compareentries(const struct perpackagestate *a, + const struct perpackagestate *b) { + switch (statsortorder) { + case sso_avail: + if (a->ssavail != b->ssavail) return a->ssavail - b->ssavail; + break; + case sso_state: + if (a->ssstate != b->ssstate) return a->ssstate - b->ssstate; + break; + case sso_unsorted: + break; + default: + internerr("unknown statsortorder %d", statsortorder); + } + + const char *asection= a->pkg->section; + if (!asection && a->pkg->set->name) + asection = ""; + const char *bsection= b->pkg->section; + if (!bsection && b->pkg->set->name) + bsection = ""; + int c_section= + !asection || !bsection ? + (!bsection) - (!asection) : + !*asection || !*bsection ? + (!*asection) - (!*bsection) : + strcasecmp(asection,bsection); + int c_priority= + a->pkg->priority - b->pkg->priority; + if (!c_priority && a->pkg->priority == PKG_PRIO_OTHER) + c_priority= strcasecmp(a->pkg->otherpriority, b->pkg->otherpriority); + int c_name= + a->pkg->set->name && b->pkg->set->name ? + strcasecmp(a->pkg->set->name, b->pkg->set->name) : + (!b->pkg->set->name) - (!a->pkg->set->name); + + switch (sortorder) { + case so_section: + return c_section ? c_section : c_priority ? c_priority : c_name; + case so_priority: + return c_priority ? c_priority : c_section ? c_section : c_name; + case so_alpha: + return c_name; + case so_unsorted: + default: + internerr("unsorted or unknown sort %d", sortorder); + } + /* never reached, make gcc happy */ + return 1; +} + +void packagelist::discardheadings() { + int a,b; + for (a=0, b=0; a<nitems; a++) { + if (table[a]->pkg->set->name) { + table[b++]= table[a]; + } + } + nitems= b; + + struct perpackagestate *head, *next; + head= headings; + while (head) { + next= head->uprec; + delete head->pkg->set; + delete head; + head= next; + } + headings = nullptr; +} + +void packagelist::addheading(enum ssavailval ssavail, + enum ssstateval ssstate, + pkgpriority priority, + const char *otherpriority, + const char *section) { + if (nitems > nallocated) + internerr("inconsistent state: ntimes=%d > nallocated=%d", + nitems, nallocated); + + if (nitems == nallocated) { + nallocated += nallocated+50; + struct perpackagestate **newtable= new struct perpackagestate*[nallocated]; + memcpy(newtable, table, nallocated * sizeof(struct perpackagestate *)); + delete[] table; + table= newtable; + } + + debug(dbg_general, "packagelist[%p]::addheading(%d,%d,%d,%s,%s)", + this, ssavail, ssstate, priority, + otherpriority ? otherpriority : "<null>", + section ? section : "<null>"); + + struct pkgset *newset = new pkgset; + newset->name = nullptr; + struct pkginfo *newhead = &newset->pkg; + newhead->set = newset; + newhead->priority= priority; + newhead->otherpriority= otherpriority; + newhead->section= section; + + struct perpackagestate *newstate= new perpackagestate; + newstate->pkg= newhead; + newstate->uprec= headings; + headings= newstate; + newstate->ssavail= ssavail; + newstate->ssstate= ssstate; + newhead->clientdata= newstate; + + table[nitems++]= newstate; +} + +static packagelist *sortpackagelist; + +int qsort_compareentries(const void *a, const void *b) { + const struct perpackagestate *pa = *static_cast<const struct perpackagestate * const *>(a); + const struct perpackagestate *pb = *static_cast<const struct perpackagestate * const *>(b); + + return sortpackagelist->compareentries(pa, pb); +} + +void packagelist::sortinplace() { + sortpackagelist= this; + + debug(dbg_general, "packagelist[%p]::sortinplace()", this); + qsort(table, nitems, sizeof(struct pkgbin *), qsort_compareentries); +} + +void packagelist::ensurestatsortinfo() { + const struct dpkg_version *veri; + const struct dpkg_version *vera; + struct pkginfo *pkg; + int index; + + debug(dbg_general, + "packagelist[%p]::ensurestatsortinfos() sortorder=%d nitems=%d", + this, statsortorder, nitems); + + switch (statsortorder) { + case sso_unsorted: + debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() unsorted", this); + return; + case sso_avail: + debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() calcssadone=%d", + this, calcssadone); + if (calcssadone) return; + for (index=0; index < nitems; index++) { + debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() i=%d pkg=%s", + this, index, pkg_name(table[index]->pkg, pnaw_always)); + pkg= table[index]->pkg; + switch (pkg->status) { + case PKG_STAT_UNPACKED: + case PKG_STAT_HALFCONFIGURED: + case PKG_STAT_HALFINSTALLED: + case PKG_STAT_TRIGGERSAWAITED: + case PKG_STAT_TRIGGERSPENDING: + table[index]->ssavail= ssa_broken; + break; + case PKG_STAT_NOTINSTALLED: + case PKG_STAT_CONFIGFILES: + if (!dpkg_version_is_informative(&pkg->available.version)) { + table[index]->ssavail= ssa_notinst_gone; +// FIXME: Disable for now as a workaround, until dselect knows how to properly +// store seen packages. +#if 0 + } else if (table[index]->original == PKG_WANT_UNKNOWN) { + table[index]->ssavail= ssa_notinst_unseen; +#endif + } else { + table[index]->ssavail= ssa_notinst_seen; + } + break; + case PKG_STAT_INSTALLED: + veri= &table[index]->pkg->installed.version; + vera= &table[index]->pkg->available.version; + if (!dpkg_version_is_informative(vera)) { + table[index]->ssavail= ssa_installed_gone; + } else if (dpkg_version_compare(vera, veri) > 0) { + table[index]->ssavail= ssa_installed_newer; + } else { + table[index]->ssavail= ssa_installed_sameold; + } + break; + default: + internerr("unknown status %d on sso_avail", pkg->status); + } + debug(dbg_general, + "packagelist[%p]::ensurestatsortinfos() i=%d ssavail=%d", + this, index, table[index]->ssavail); + } + calcssadone = true; + break; + case sso_state: + debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() calcsssdone=%d", + this, calcsssdone); + if (calcsssdone) return; + for (index=0; index < nitems; index++) { + debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() i=%d pkg=%s", + this, index, pkg_name(table[index]->pkg, pnaw_always)); + switch (table[index]->pkg->status) { + case PKG_STAT_UNPACKED: + case PKG_STAT_HALFCONFIGURED: + case PKG_STAT_HALFINSTALLED: + case PKG_STAT_TRIGGERSAWAITED: + case PKG_STAT_TRIGGERSPENDING: + table[index]->ssstate= sss_broken; + break; + case PKG_STAT_NOTINSTALLED: + table[index]->ssstate= sss_notinstalled; + break; + case PKG_STAT_CONFIGFILES: + table[index]->ssstate= sss_configfiles; + break; + case PKG_STAT_INSTALLED: + table[index]->ssstate= sss_installed; + break; + default: + internerr("unknown status %d on sso_state", table[index]->pkg->status); + } + debug(dbg_general, + "packagelist[%p]::ensurestatsortinfos() i=%d ssstate=%d", + this, index, table[index]->ssstate); + } + calcsssdone = true; + break; + default: + internerr("unknown statsortorder %d", statsortorder); + } +} + +void packagelist::sortmakeheads() { + discardheadings(); + ensurestatsortinfo(); + sortinplace(); + + if (nitems == 0) + internerr("cannot sort 0 items"); + + debug(dbg_general, + "packagelist[%p]::sortmakeheads() sortorder=%d statsortorder=%d", + this, sortorder, statsortorder); + + int nrealitems= nitems; + addheading(ssa_none, sss_none, PKG_PRIO_UNSET, nullptr, nullptr); + + if (sortorder == so_unsorted) + internerr("cannot sort unsorted order"); + + if (sortorder == so_alpha && statsortorder == sso_unsorted) { sortinplace(); return; } + + // Important: do not save pointers into table in this function, because + // addheading may need to reallocate table to make it larger ! + + struct pkginfo *lastpkg; + struct pkginfo *thispkg; + lastpkg = nullptr; + int a; + for (a=0; a<nrealitems; a++) { + thispkg= table[a]->pkg; + if (thispkg->set->name == nullptr) + internerr("package set has no name at table index %d", a); + int ssdiff= 0; + ssavailval ssavail= ssa_none; + ssstateval ssstate= sss_none; + switch (statsortorder) { + case sso_avail: + ssavail= thispkg->clientdata->ssavail; + ssdiff= (!lastpkg || ssavail != lastpkg->clientdata->ssavail); + break; + case sso_state: + ssstate= thispkg->clientdata->ssstate; + ssdiff= (!lastpkg || ssstate != lastpkg->clientdata->ssstate); + break; + case sso_unsorted: + break; + default: + internerr("unknown statsortorder %d", statsortorder); + } + + int prioritydiff= (!lastpkg || + thispkg->priority != lastpkg->priority || + (thispkg->priority == PKG_PRIO_OTHER && + strcasecmp(thispkg->otherpriority,lastpkg->otherpriority))); + int sectiondiff= (!lastpkg || + strcasecmp(thispkg->section ? thispkg->section : "", + lastpkg->section ? lastpkg->section : "")); + + debug(dbg_general, + "packagelist[%p]::sortmakeheads() pkg=%s state=%d avail=%d %s " + "priority=%d otherpriority=%s %s section=%s %s", + this, pkg_name(thispkg, pnaw_always), + thispkg->clientdata->ssavail, thispkg->clientdata->ssstate, + ssdiff ? "*diff" : "same", + thispkg->priority, + thispkg->priority != PKG_PRIO_OTHER ? "<none>" : + thispkg->otherpriority ? thispkg->otherpriority : "<null>", + prioritydiff ? "*diff*" : "same", + thispkg->section ? thispkg->section : "<null>", + sectiondiff ? "*diff*" : "same"); + + if (ssdiff) + addheading(ssavail,ssstate, + PKG_PRIO_UNSET, nullptr, nullptr); + + if (sortorder == so_section && sectiondiff) + addheading(ssavail,ssstate, + PKG_PRIO_UNSET, nullptr, + thispkg->section ? thispkg->section : ""); + + if (sortorder == so_priority && prioritydiff) + addheading(ssavail,ssstate, + thispkg->priority, thispkg->otherpriority, nullptr); + + if (sortorder != so_alpha && (prioritydiff || sectiondiff)) + addheading(ssavail,ssstate, + thispkg->priority,thispkg->otherpriority, + thispkg->section ? thispkg->section : ""); + + lastpkg= thispkg; + } + + if (listpad) { + werase(listpad); + } + + sortinplace(); +} + +void packagelist::initialsetup() { + debug(dbg_general, "packagelist[%p]::initialsetup()", this); + + int allpackages = pkg_hash_count_pkg(); + datatable= new struct perpackagestate[allpackages]; + + nallocated= allpackages+150; // will realloc if necessary, so 150 not critical + table= new struct perpackagestate*[nallocated]; + + depsdone = nullptr; + unavdone = nullptr; + currentinfo = nullptr; + headings = nullptr; + verbose = false; + calcssadone = calcsssdone = false; + searchdescr = false; +} + +void packagelist::finalsetup() { + setcursor(0); + + debug(dbg_general, "packagelist[%p]::finalsetup done; recursive=%d nitems=%d", + this, recursive, nitems); +} + +packagelist::packagelist(keybindings *kb) : baselist(kb) { + // nonrecursive + initialsetup(); + struct pkg_hash_iter *iter; + struct pkginfo *pkg; + + nitems = 0; + + iter = pkg_hash_iter_new(); + while ((pkg = pkg_hash_iter_next_pkg(iter))) { + struct perpackagestate *state= &datatable[nitems]; + state->pkg= pkg; + if (pkg->status == PKG_STAT_NOTINSTALLED && + !pkg->archives && + pkg->want != PKG_WANT_INSTALL) { + pkg->clientdata = nullptr; + continue; + } + // treat all unknown packages as already seen + state->direct = state->original = (pkg->want == PKG_WANT_UNKNOWN ? PKG_WANT_PURGE : pkg->want); + if (modstatdb_get_status() == msdbrw_write && + state->original == PKG_WANT_UNKNOWN) { + state->suggested= + pkg->status == PKG_STAT_INSTALLED || + pkg->priority <= PKG_PRIO_STANDARD /* FIXME: configurable */ + ? PKG_WANT_INSTALL : PKG_WANT_PURGE; + state->spriority= sp_inherit; + } else { + state->suggested= state->original; + state->spriority= sp_fixed; + } + state->dpriority= dp_must; + state->selected= state->suggested; + state->uprec = nullptr; + state->relations.init(); + pkg->clientdata= state; + table[nitems]= state; + nitems++; + } + pkg_hash_iter_free(iter); + + if (!nitems) + ohshit(_("there are no packages")); + recursive = false; + sortorder= so_priority; + statsortorder= sso_avail; + archdisplayopt = ado_both; + versiondisplayopt= vdo_both; + sortmakeheads(); + finalsetup(); +} + +packagelist::packagelist(keybindings *kb, pkginfo **pkgltab) : baselist(kb) { + // takes over responsibility for pkgltab (recursive) + initialsetup(); + + recursive = true; + nitems= 0; + if (pkgltab) { + add(pkgltab); + delete[] pkgltab; + } + + sortorder= so_unsorted; + statsortorder= sso_unsorted; + archdisplayopt = ado_none; + versiondisplayopt= vdo_none; + finalsetup(); +} + +void +perpackagestate::free(bool recursive) +{ + if (pkg->set->name) { + if (modstatdb_get_status() == msdbrw_write) { + if (uprec) { + if (!recursive) + internerr("unexpected non-recursive free requested"); + uprec->selected= selected; + pkg->clientdata= uprec; + } else { + if (recursive) + internerr("unexpected recursive free requested"); + if (pkg->want != selected && + !(pkg->want == PKG_WANT_UNKNOWN && selected == PKG_WANT_PURGE)) { + pkg->want= selected; + } + pkg->clientdata = nullptr; + } + } + relations.destroy(); + } +} + +packagelist::~packagelist() { + debug(dbg_general, "packagelist[%p]::~packagelist()", this); + + if (searchstring[0]) + regfree(&searchfsm); + + discardheadings(); + + int index; + for (index=0; index<nitems; index++) table[index]->free(recursive); + delete[] table; + delete[] datatable; + debug(dbg_general, "packagelist[%p]::~packagelist() tables freed", this); + + doneent *search, *next; + for (search=depsdone; search; search=next) { + next= search->next; + delete search; + } + + debug(dbg_general, "packagelist[%p]::~packagelist() done", this); +} + +bool +packagelist::checksearch(char *rx) +{ + int rc, opt = REG_NOSUB; + int pos; + + if (str_is_unset(rx)) + return false; + + searchdescr = false; + if (searchstring[0]) { + regfree(&searchfsm); + searchstring[0]=0; + } + + /* look for search options */ + for (pos = strlen(rx) - 1; pos >= 0; pos--) + if ((rx[pos] == '/') && ((pos == 0) || (rx[pos - 1] != '\\'))) + break; + + if (pos >= 0) { + rx[pos++] = '\0'; + if (strcspn(rx + pos, "di") != 0) { + displayerror(_("invalid search option given")); + return false; + } + + while (rx[pos]) { + if (rx[pos] == 'i') + opt|=REG_ICASE; + else if (rx[pos] == 'd') + searchdescr = true; + pos++; + } + } + + rc = regcomp(&searchfsm, rx, opt); + if (rc != 0) { + displayerror(_("error in regular expression")); + return false; + } + + return true; +} + +bool +packagelist::matchsearch(int index) +{ + const char *name; + + name = itemname(index); + if (!name) + return false; /* Skip things without a name (separators) */ + + if (regexec(&searchfsm, name, 0, nullptr, 0) == 0) + return true; + + if (searchdescr) { + const char *descr = table[index]->pkg->available.description; + if (str_is_unset(descr)) + return false; + + if (regexec(&searchfsm, descr, 0, nullptr, 0) == 0) + return true; + } + + return false; +} + +pkginfo **packagelist::display() { + // returns list of packages as null-terminated array, which becomes owned + // by the caller, if a recursive check is desired. + // returns 0 if no recursive check is desired. + int response, index; + const keybindings::interpretation *interp; + pkginfo **retl; + + debug(dbg_general, "packagelist[%p]::display()", this); + + setupsigwinch(); + startdisplay(); + + if (!expertmode) + displayhelp(helpmenulist(),'i'); + + debug(dbg_general, "packagelist[%p]::display() entering loop", this); + for (;;) { + if (whatinfo_height) wcursyncup(whatinfowin); + if (doupdate() == ERR) + ohshite(_("doupdate failed")); + signallist= this; + sigwinch_mask(SIG_UNBLOCK); + do + response= getch(); + while (response == ERR && errno == EINTR); + sigwinch_mask(SIG_BLOCK); + if (response == ERR) + ohshite(_("getch failed")); + interp= (*bindings)(response); + debug(dbg_general, "packagelist[%p]::display() response=%d interp=%s", + this, response, interp ? interp->action : "[none]"); + if (!interp) { beep(); continue; } + (this->*(interp->pfn))(); + if (interp->qa != qa_noquit) break; + } + pop_cleanup(ehflag_normaltidy); // unset the SIGWINCH handler + enddisplay(); + + if (interp->qa == qa_quitnochecksave || + modstatdb_get_status() == msdbrw_readonly) { + debug(dbg_general, "packagelist[%p]::display() done - quitNOcheck", this); + return nullptr; + } + + if (recursive) { + retl= new pkginfo*[nitems+1]; + for (index=0; index<nitems; index++) retl[index]= table[index]->pkg; + retl[nitems] = nullptr; + debug(dbg_general, "packagelist[%p]::display() done, retl=%p", this, retl); + return retl; + } else { + packagelist *sub = new packagelist(bindings, nullptr); + for (index=0; index < nitems; index++) + if (table[index]->pkg->set->name) + sub->add(table[index]->pkg); + repeatedlydisplay(sub,dp_must); + debug(dbg_general, + "packagelist[%p]::display() done, not recursive no retl", this); + return nullptr; + } +} |