| /* |
| * dselect - Debian package maintenance user interface |
| * pkglist.cc - package list administration |
| * |
| * Copyright © 1995 Ian Jackson <ian@chiark.greenend.org.uk> |
| * Copyright © 2001 Wichert Akkerman <wakkerma@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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <config.h> |
| #include <compat.h> |
| |
| #include <assert.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 "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 in compareentries"); |
| } |
| |
| const char *asection= a->pkg->section; |
| if (!asection && a->pkg->name) asection= ""; |
| const char *bsection= b->pkg->section; |
| if (!bsection && b->pkg->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 == pkginfo::pri_other) |
| c_priority= strcasecmp(a->pkg->otherpriority, b->pkg->otherpriority); |
| int c_name= |
| a->pkg->name && b->pkg->name ? |
| strcasecmp(a->pkg->name, b->pkg->name) : |
| (!b->pkg->name) - (!a->pkg->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 in compareentries"); |
| } |
| /* 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->name) { |
| table[b++]= table[a]; |
| } |
| } |
| nitems= b; |
| |
| struct perpackagestate *head, *next; |
| head= headings; |
| while (head) { |
| next= head->uprec; |
| delete head->pkg; |
| delete head; |
| head= next; |
| } |
| headings= 0; |
| } |
| |
| void packagelist::addheading(enum ssavailval ssavail, |
| enum ssstateval ssstate, |
| pkginfo::pkgpriority priority, |
| const char *otherpriority, |
| const char *section) { |
| assert(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 pkginfo *newhead= new pkginfo; |
| newhead->name= 0; |
| 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) { |
| return sortpackagelist->compareentries(*(const struct perpackagestate**)a, |
| *(const struct perpackagestate**)b); |
| } |
| |
| 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 versionrevision *veri; |
| const struct versionrevision *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, table[index]->pkg->name); |
| pkg= table[index]->pkg; |
| switch (pkg->status) { |
| case pkginfo::stat_unpacked: |
| case pkginfo::stat_halfconfigured: |
| case pkginfo::stat_halfinstalled: |
| case pkginfo::stat_triggersawaited: |
| case pkginfo::stat_triggerspending: |
| table[index]->ssavail= ssa_broken; |
| break; |
| case pkginfo::stat_notinstalled: |
| case pkginfo::stat_configfiles: |
| if (!informativeversion(&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 == pkginfo::want_unknown) { |
| table[index]->ssavail= ssa_notinst_unseen; |
| #endif |
| } else { |
| table[index]->ssavail= ssa_notinst_seen; |
| } |
| break; |
| case pkginfo::stat_installed: |
| veri= &table[index]->pkg->installed.version; |
| vera= &table[index]->pkg->available.version; |
| if (!informativeversion(vera)) { |
| table[index]->ssavail= ssa_installed_gone; |
| } else if (versioncompare(vera,veri) > 0) { |
| table[index]->ssavail= ssa_installed_newer; |
| } else { |
| table[index]->ssavail= ssa_installed_sameold; |
| } |
| break; |
| default: |
| internerr("unknown stat in ensurestatsortinfo sso_avail"); |
| } |
| debug(dbg_general, |
| "packagelist[%p]::ensurestatsortinfos() i=%d ssavail=%d", |
| this, index, table[index]->ssavail); |
| } |
| calcssadone= 1; |
| 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, table[index]->pkg->name); |
| switch (table[index]->pkg->status) { |
| case pkginfo::stat_unpacked: |
| case pkginfo::stat_halfconfigured: |
| case pkginfo::stat_halfinstalled: |
| case pkginfo::stat_triggersawaited: |
| case pkginfo::stat_triggerspending: |
| table[index]->ssstate= sss_broken; |
| break; |
| case pkginfo::stat_notinstalled: |
| table[index]->ssstate= sss_notinstalled; |
| break; |
| case pkginfo::stat_configfiles: |
| table[index]->ssstate= sss_configfiles; |
| break; |
| case pkginfo::stat_installed: |
| table[index]->ssstate= sss_installed; |
| break; |
| default: |
| internerr("unknown stat in ensurestatsortinfo sso_state"); |
| } |
| debug(dbg_general, |
| "packagelist[%p]::ensurestatsortinfos() i=%d ssstate=%d", |
| this, index, table[index]->ssstate); |
| } |
| calcsssdone= 1; |
| break; |
| default: |
| internerr("unknown statsortorder in ensurestatsortinfo"); |
| } |
| } |
| |
| void packagelist::sortmakeheads() { |
| discardheadings(); |
| ensurestatsortinfo(); |
| sortinplace(); |
| assert(nitems); |
| |
| debug(dbg_general, |
| "packagelist[%p]::sortmakeheads() sortorder=%d statsortorder=%d", |
| this, sortorder, statsortorder); |
| |
| int nrealitems= nitems; |
| addheading(ssa_none,sss_none,pkginfo::pri_unset,0,0); |
| |
| assert(sortorder != so_unsorted); |
| 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= 0; |
| int a; |
| for (a=0; a<nrealitems; a++) { |
| thispkg= table[a]->pkg; |
| assert(thispkg->name); |
| 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 in sortmakeheads"); |
| } |
| |
| int prioritydiff= (!lastpkg || |
| thispkg->priority != lastpkg->priority || |
| (thispkg->priority == pkginfo::pri_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, thispkg->name, |
| thispkg->clientdata->ssavail, thispkg->clientdata->ssstate, |
| ssdiff ? "*diff" : "same", |
| thispkg->priority, |
| thispkg->priority != pkginfo::pri_other ? "<none>" : |
| thispkg->otherpriority ? thispkg->otherpriority : "<null>", |
| prioritydiff ? "*diff*" : "same", |
| thispkg->section ? thispkg->section : "<null>", |
| sectiondiff ? "*diff*" : "same"); |
| |
| if (ssdiff) |
| addheading(ssavail,ssstate, |
| pkginfo::pri_unset,0, 0); |
| |
| if (sortorder == so_section && sectiondiff) |
| addheading(ssavail,ssstate, |
| pkginfo::pri_unset,0, thispkg->section ? thispkg->section : ""); |
| |
| if (sortorder == so_priority && prioritydiff) |
| addheading(ssavail,ssstate, |
| thispkg->priority,thispkg->otherpriority, 0); |
| |
| 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_db_count(); |
| datatable= new struct perpackagestate[allpackages]; |
| |
| nallocated= allpackages+150; // will realloc if necessary, so 150 not critical |
| table= new struct perpackagestate*[nallocated]; |
| |
| depsdone= 0; |
| unavdone= 0; |
| currentinfo= 0; |
| headings= 0; |
| verbose= 0; |
| calcssadone= calcsssdone= 0; |
| searchdescr= 0; |
| } |
| |
| 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 pkgiterator *iter; |
| struct pkginfo *pkg; |
| |
| nitems = 0; |
| |
| iter = pkg_db_iter_new(); |
| while ((pkg = pkg_db_iter_next(iter))) { |
| struct perpackagestate *state= &datatable[nitems]; |
| state->pkg= pkg; |
| if (pkg->status == pkginfo::stat_notinstalled && |
| !pkg->files && |
| pkg->want != pkginfo::want_install) { |
| pkg->clientdata= 0; continue; |
| } |
| // treat all unknown packages as already seen |
| state->direct= state->original= (pkg->want == pkginfo::want_unknown ? pkginfo::want_purge : pkg->want); |
| if (readwrite && state->original == pkginfo::want_unknown) { |
| state->suggested= |
| pkg->status == pkginfo::stat_installed || |
| pkg->priority <= pkginfo::pri_standard /* FIXME: configurable */ |
| ? pkginfo::want_install : pkginfo::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= 0; |
| state->relations.init(); |
| pkg->clientdata= state; |
| table[nitems]= state; |
| nitems++; |
| } |
| pkg_db_iter_free(iter); |
| |
| if (!nitems) |
| ohshit(_("There are no packages.")); |
| recursive= 0; |
| sortorder= so_priority; |
| statsortorder= sso_avail; |
| versiondisplayopt= vdo_both; |
| sortmakeheads(); |
| finalsetup(); |
| } |
| |
| packagelist::packagelist(keybindings *kb, pkginfo **pkgltab) : baselist(kb) { |
| // takes over responsibility for pkgltab (recursive) |
| initialsetup(); |
| |
| recursive= 1; |
| nitems= 0; |
| if (pkgltab) { |
| add(pkgltab); |
| delete[] pkgltab; |
| } |
| |
| sortorder= so_unsorted; |
| statsortorder= sso_unsorted; |
| versiondisplayopt= vdo_none; |
| finalsetup(); |
| } |
| |
| void perpackagestate::free(int recursive) { |
| if (pkg->name) { |
| if (readwrite) { |
| if (uprec) { |
| assert(recursive); |
| uprec->selected= selected; |
| pkg->clientdata= uprec; |
| } else { |
| assert(!recursive); |
| if (pkg->want != selected && |
| !(pkg->want == pkginfo::want_unknown && selected == pkginfo::want_purge)) { |
| pkg->want= selected; |
| } |
| pkg->clientdata= 0; |
| } |
| } |
| 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 r,opt = REG_NOSUB; |
| |
| if (!rx || !*rx) |
| return false; |
| |
| searchdescr=0; |
| if (searchstring[0]) { |
| regfree(&searchfsm); |
| searchstring[0]=0; |
| } |
| |
| /* look for search options */ |
| for (r=strlen(rx)-1; r>=0; r--) |
| if ((rx[r]=='/') && ((r==0) || (rx[r-1]!='\\'))) |
| break; |
| |
| if (r>=0) { |
| rx[r++]='\0'; |
| if (strcspn(rx+r, "di")!=0) { |
| displayerror(_("invalid search option given")); |
| return false; |
| } |
| |
| while (rx[r]) { |
| if (rx[r]=='i') |
| opt|=REG_ICASE; |
| else if (rx[r]=='d') |
| searchdescr=1; |
| r++; |
| } |
| } |
| |
| if ((r=regcomp(&searchfsm, rx, opt))!=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 (seperators) */ |
| |
| if (regexec(&searchfsm, name, 0, NULL, 0) == 0) |
| return true; |
| |
| if (searchdescr) { |
| const char* descr = table[index]->pkg->available.description; |
| if (!descr || !*descr) |
| return false; |
| |
| if (regexec(&searchfsm, descr, 0, NULL, 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; |
| if (sigprocmask(SIG_UNBLOCK, &sigwinchset, 0)) |
| ohshite(_("failed to unblock SIGWINCH")); |
| do |
| response= getch(); |
| while (response == ERR && errno == EINTR); |
| if (sigprocmask(SIG_BLOCK, &sigwinchset, 0)) |
| ohshite(_("failed to re-block SIGWINCH")); |
| 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 || !readwrite) { |
| debug(dbg_general, "packagelist[%p]::display() done - quitNOcheck", this); |
| return 0; |
| } |
| |
| if (recursive) { |
| retl= new pkginfo*[nitems+1]; |
| for (index=0; index<nitems; index++) retl[index]= table[index]->pkg; |
| retl[nitems]= 0; |
| debug(dbg_general, "packagelist[%p]::display() done, retl=%p", this, retl); |
| return retl; |
| } else { |
| packagelist *sub= new packagelist(bindings,0); |
| for (index=0; index < nitems; index++) |
| if (table[index]->pkg->name) |
| sub->add(table[index]->pkg); |
| repeatedlydisplay(sub,dp_must); |
| debug(dbg_general, |
| "packagelist[%p]::display() done, not recursive no retl", this); |
| return 0; |
| } |
| } |
| |
| /* vi: sw=2 ts=8 |
| */ |