2 * dselect - Debian package maintenance user interface
3 * pkglist.cc - package list administration
5 * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
6 * Copyright © 2001 Wichert Akkerman <wakkerma@debian.org>
7 * Copyright © 2008-2014 Guillem Jover <guillem@debian.org>
9 * This is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
32 #include <dpkg/i18n.h>
33 #include <dpkg/dpkg.h>
34 #include <dpkg/dpkg-db.h>
35 #include <dpkg/string.h>
40 int packagelist::compareentries(const struct perpackagestate *a,
41 const struct perpackagestate *b) {
42 switch (statsortorder) {
44 if (a->ssavail != b->ssavail) return a->ssavail - b->ssavail;
47 if (a->ssstate != b->ssstate) return a->ssstate - b->ssstate;
52 internerr("unknown statsortorder %d", statsortorder);
55 const char *asection= a->pkg->section;
56 if (!asection && a->pkg->set->name)
58 const char *bsection= b->pkg->section;
59 if (!bsection && b->pkg->set->name)
62 !asection || !bsection ?
63 (!bsection) - (!asection) :
64 !*asection || !*bsection ?
65 (!*asection) - (!*bsection) :
66 strcasecmp(asection,bsection);
68 a->pkg->priority - b->pkg->priority;
69 if (!c_priority && a->pkg->priority == PKG_PRIO_OTHER)
70 c_priority= strcasecmp(a->pkg->otherpriority, b->pkg->otherpriority);
72 a->pkg->set->name && b->pkg->set->name ?
73 strcasecmp(a->pkg->set->name, b->pkg->set->name) :
74 (!b->pkg->set->name) - (!a->pkg->set->name);
78 return c_section ? c_section : c_priority ? c_priority : c_name;
80 return c_priority ? c_priority : c_section ? c_section : c_name;
85 internerr("unsorted or unknown sort %d", sortorder);
87 /* never reached, make gcc happy */
91 void packagelist::discardheadings() {
93 for (a=0, b=0; a<nitems; a++) {
94 if (table[a]->pkg->set->name) {
100 struct perpackagestate *head, *next;
104 delete head->pkg->set;
111 void packagelist::addheading(enum ssavailval ssavail,
112 enum ssstateval ssstate,
113 pkgpriority priority,
114 const char *otherpriority,
115 const char *section) {
116 assert(nitems <= nallocated);
117 if (nitems == nallocated) {
118 nallocated += nallocated+50;
119 struct perpackagestate **newtable= new struct perpackagestate*[nallocated];
120 memcpy(newtable, table, nallocated * sizeof(struct perpackagestate *));
125 debug(dbg_general, "packagelist[%p]::addheading(%d,%d,%d,%s,%s)",
126 this, ssavail, ssstate, priority,
127 otherpriority ? otherpriority : "<null>",
128 section ? section : "<null>");
130 struct pkgset *newset = new pkgset;
131 newset->name = nullptr;
132 struct pkginfo *newhead = &newset->pkg;
133 newhead->set = newset;
134 newhead->priority= priority;
135 newhead->otherpriority= otherpriority;
136 newhead->section= section;
138 struct perpackagestate *newstate= new perpackagestate;
139 newstate->pkg= newhead;
140 newstate->uprec= headings;
142 newstate->ssavail= ssavail;
143 newstate->ssstate= ssstate;
144 newhead->clientdata= newstate;
146 table[nitems++]= newstate;
149 static packagelist *sortpackagelist;
151 int qsort_compareentries(const void *a, const void *b) {
152 return sortpackagelist->compareentries(*(const struct perpackagestate **)a,
153 *(const struct perpackagestate **)b);
156 void packagelist::sortinplace() {
157 sortpackagelist= this;
159 debug(dbg_general, "packagelist[%p]::sortinplace()", this);
160 qsort(table, nitems, sizeof(struct pkgbin *), qsort_compareentries);
163 void packagelist::ensurestatsortinfo() {
164 const struct dpkg_version *veri;
165 const struct dpkg_version *vera;
170 "packagelist[%p]::ensurestatsortinfos() sortorder=%d nitems=%d",
171 this, statsortorder, nitems);
173 switch (statsortorder) {
175 debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() unsorted", this);
178 debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() calcssadone=%d",
180 if (calcssadone) return;
181 for (index=0; index < nitems; index++) {
182 debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() i=%d pkg=%s",
183 this, index, pkg_name(table[index]->pkg, pnaw_always));
184 pkg= table[index]->pkg;
185 switch (pkg->status) {
186 case PKG_STAT_UNPACKED:
187 case PKG_STAT_HALFCONFIGURED:
188 case PKG_STAT_HALFINSTALLED:
189 case PKG_STAT_TRIGGERSAWAITED:
190 case PKG_STAT_TRIGGERSPENDING:
191 table[index]->ssavail= ssa_broken;
193 case PKG_STAT_NOTINSTALLED:
194 case PKG_STAT_CONFIGFILES:
195 if (!dpkg_version_is_informative(&pkg->available.version)) {
196 table[index]->ssavail= ssa_notinst_gone;
197 // FIXME: Disable for now as a workaround, until dselect knows how to properly
198 // store seen packages.
200 } else if (table[index]->original == PKG_WANT_UNKNOWN) {
201 table[index]->ssavail= ssa_notinst_unseen;
204 table[index]->ssavail= ssa_notinst_seen;
207 case PKG_STAT_INSTALLED:
208 veri= &table[index]->pkg->installed.version;
209 vera= &table[index]->pkg->available.version;
210 if (!dpkg_version_is_informative(vera)) {
211 table[index]->ssavail= ssa_installed_gone;
212 } else if (dpkg_version_compare(vera, veri) > 0) {
213 table[index]->ssavail= ssa_installed_newer;
215 table[index]->ssavail= ssa_installed_sameold;
219 internerr("unknown status %d on sso_avail", pkg->status);
222 "packagelist[%p]::ensurestatsortinfos() i=%d ssavail=%d",
223 this, index, table[index]->ssavail);
228 debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() calcsssdone=%d",
230 if (calcsssdone) return;
231 for (index=0; index < nitems; index++) {
232 debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() i=%d pkg=%s",
233 this, index, pkg_name(table[index]->pkg, pnaw_always));
234 switch (table[index]->pkg->status) {
235 case PKG_STAT_UNPACKED:
236 case PKG_STAT_HALFCONFIGURED:
237 case PKG_STAT_HALFINSTALLED:
238 case PKG_STAT_TRIGGERSAWAITED:
239 case PKG_STAT_TRIGGERSPENDING:
240 table[index]->ssstate= sss_broken;
242 case PKG_STAT_NOTINSTALLED:
243 table[index]->ssstate= sss_notinstalled;
245 case PKG_STAT_CONFIGFILES:
246 table[index]->ssstate= sss_configfiles;
248 case PKG_STAT_INSTALLED:
249 table[index]->ssstate= sss_installed;
252 internerr("unknown status %d on sso_state", table[index]->pkg->status);
255 "packagelist[%p]::ensurestatsortinfos() i=%d ssstate=%d",
256 this, index, table[index]->ssstate);
261 internerr("unknown statsortorder %d", statsortorder);
265 void packagelist::sortmakeheads() {
267 ensurestatsortinfo();
272 "packagelist[%p]::sortmakeheads() sortorder=%d statsortorder=%d",
273 this, sortorder, statsortorder);
275 int nrealitems= nitems;
276 addheading(ssa_none, sss_none, PKG_PRIO_UNSET, nullptr, nullptr);
278 assert(sortorder != so_unsorted);
279 if (sortorder == so_alpha && statsortorder == sso_unsorted) { sortinplace(); return; }
281 // Important: do not save pointers into table in this function, because
282 // addheading may need to reallocate table to make it larger !
284 struct pkginfo *lastpkg;
285 struct pkginfo *thispkg;
288 for (a=0; a<nrealitems; a++) {
289 thispkg= table[a]->pkg;
290 assert(thispkg->set->name);
292 ssavailval ssavail= ssa_none;
293 ssstateval ssstate= sss_none;
294 switch (statsortorder) {
296 ssavail= thispkg->clientdata->ssavail;
297 ssdiff= (!lastpkg || ssavail != lastpkg->clientdata->ssavail);
300 ssstate= thispkg->clientdata->ssstate;
301 ssdiff= (!lastpkg || ssstate != lastpkg->clientdata->ssstate);
306 internerr("unknown statsortorder %d", statsortorder);
309 int prioritydiff= (!lastpkg ||
310 thispkg->priority != lastpkg->priority ||
311 (thispkg->priority == PKG_PRIO_OTHER &&
312 strcasecmp(thispkg->otherpriority,lastpkg->otherpriority)));
313 int sectiondiff= (!lastpkg ||
314 strcasecmp(thispkg->section ? thispkg->section : "",
315 lastpkg->section ? lastpkg->section : ""));
318 "packagelist[%p]::sortmakeheads() pkg=%s state=%d avail=%d %s "
319 "priority=%d otherpriority=%s %s section=%s %s",
320 this, pkg_name(thispkg, pnaw_always),
321 thispkg->clientdata->ssavail, thispkg->clientdata->ssstate,
322 ssdiff ? "*diff" : "same",
324 thispkg->priority != PKG_PRIO_OTHER ? "<none>" :
325 thispkg->otherpriority ? thispkg->otherpriority : "<null>",
326 prioritydiff ? "*diff*" : "same",
327 thispkg->section ? thispkg->section : "<null>",
328 sectiondiff ? "*diff*" : "same");
331 addheading(ssavail,ssstate,
332 PKG_PRIO_UNSET, nullptr, nullptr);
334 if (sortorder == so_section && sectiondiff)
335 addheading(ssavail,ssstate,
336 PKG_PRIO_UNSET, nullptr,
337 thispkg->section ? thispkg->section : "");
339 if (sortorder == so_priority && prioritydiff)
340 addheading(ssavail,ssstate,
341 thispkg->priority, thispkg->otherpriority, nullptr);
343 if (sortorder != so_alpha && (prioritydiff || sectiondiff))
344 addheading(ssavail,ssstate,
345 thispkg->priority,thispkg->otherpriority,
346 thispkg->section ? thispkg->section : "");
358 void packagelist::initialsetup() {
359 debug(dbg_general, "packagelist[%p]::initialsetup()", this);
361 int allpackages = pkg_db_count_pkg();
362 datatable= new struct perpackagestate[allpackages];
364 nallocated= allpackages+150; // will realloc if necessary, so 150 not critical
365 table= new struct perpackagestate*[nallocated];
369 currentinfo = nullptr;
372 calcssadone = calcsssdone = false;
376 void packagelist::finalsetup() {
379 debug(dbg_general, "packagelist[%p]::finalsetup done; recursive=%d nitems=%d",
380 this, recursive, nitems);
383 packagelist::packagelist(keybindings *kb) : baselist(kb) {
386 struct pkgiterator *iter;
391 iter = pkg_db_iter_new();
392 while ((pkg = pkg_db_iter_next_pkg(iter))) {
393 struct perpackagestate *state= &datatable[nitems];
395 if (pkg->status == PKG_STAT_NOTINSTALLED &&
397 pkg->want != PKG_WANT_INSTALL) {
398 pkg->clientdata = nullptr;
401 // treat all unknown packages as already seen
402 state->direct = state->original = (pkg->want == PKG_WANT_UNKNOWN ? PKG_WANT_PURGE : pkg->want);
403 if (modstatdb_get_status() == msdbrw_write &&
404 state->original == PKG_WANT_UNKNOWN) {
406 pkg->status == PKG_STAT_INSTALLED ||
407 pkg->priority <= PKG_PRIO_STANDARD /* FIXME: configurable */
408 ? PKG_WANT_INSTALL : PKG_WANT_PURGE;
409 state->spriority= sp_inherit;
411 state->suggested= state->original;
412 state->spriority= sp_fixed;
414 state->dpriority= dp_must;
415 state->selected= state->suggested;
416 state->uprec = nullptr;
417 state->relations.init();
418 pkg->clientdata= state;
419 table[nitems]= state;
422 pkg_db_iter_free(iter);
425 ohshit(_("there are no packages"));
427 sortorder= so_priority;
428 statsortorder= sso_avail;
429 archdisplayopt = ado_both;
430 versiondisplayopt= vdo_both;
435 packagelist::packagelist(keybindings *kb, pkginfo **pkgltab) : baselist(kb) {
436 // takes over responsibility for pkgltab (recursive)
446 sortorder= so_unsorted;
447 statsortorder= sso_unsorted;
448 archdisplayopt = ado_none;
449 versiondisplayopt= vdo_none;
454 perpackagestate::free(bool recursive)
456 if (pkg->set->name) {
457 if (modstatdb_get_status() == msdbrw_write) {
460 uprec->selected= selected;
461 pkg->clientdata= uprec;
464 if (pkg->want != selected &&
465 !(pkg->want == PKG_WANT_UNKNOWN && selected == PKG_WANT_PURGE)) {
468 pkg->clientdata = nullptr;
475 packagelist::~packagelist() {
476 debug(dbg_general, "packagelist[%p]::~packagelist()", this);
484 for (index=0; index<nitems; index++) table[index]->free(recursive);
487 debug(dbg_general, "packagelist[%p]::~packagelist() tables freed", this);
489 doneent *search, *next;
490 for (search=depsdone; search; search=next) {
495 debug(dbg_general, "packagelist[%p]::~packagelist() done", this);
499 packagelist::checksearch(char *rx)
501 int rc, opt = REG_NOSUB;
504 if (str_is_unset(rx))
508 if (searchstring[0]) {
513 /* look for search options */
514 for (pos = strlen(rx) - 1; pos >= 0; pos--)
515 if ((rx[pos] == '/') && ((pos == 0) || (rx[pos - 1] != '\\')))
520 if (strcspn(rx + pos, "di") != 0) {
521 displayerror(_("invalid search option given"));
528 else if (rx[pos] == 'd')
534 rc = regcomp(&searchfsm, rx, opt);
536 displayerror(_("error in regular expression"));
544 packagelist::matchsearch(int index)
548 name = itemname(index);
550 return false; /* Skip things without a name (separators) */
552 if (regexec(&searchfsm, name, 0, nullptr, 0) == 0)
556 const char *descr = table[index]->pkg->available.description;
557 if (str_is_unset(descr))
560 if (regexec(&searchfsm, descr, 0, nullptr, 0) == 0)
567 pkginfo **packagelist::display() {
568 // returns list of packages as null-terminated array, which becomes owned
569 // by the caller, if a recursive check is desired.
570 // returns 0 if no recursive check is desired.
572 const keybindings::interpretation *interp;
575 debug(dbg_general, "packagelist[%p]::display()", this);
581 displayhelp(helpmenulist(),'i');
583 debug(dbg_general, "packagelist[%p]::display() entering loop", this);
585 if (whatinfo_height) wcursyncup(whatinfowin);
586 if (doupdate() == ERR)
587 ohshite(_("doupdate failed"));
589 sigwinch_mask(SIG_UNBLOCK);
592 while (response == ERR && errno == EINTR);
593 sigwinch_mask(SIG_BLOCK);
595 ohshite(_("getch failed"));
596 interp= (*bindings)(response);
597 debug(dbg_general, "packagelist[%p]::display() response=%d interp=%s",
598 this, response, interp ? interp->action : "[none]");
599 if (!interp) { beep(); continue; }
600 (this->*(interp->pfn))();
601 if (interp->qa != qa_noquit) break;
603 pop_cleanup(ehflag_normaltidy); // unset the SIGWINCH handler
606 if (interp->qa == qa_quitnochecksave ||
607 modstatdb_get_status() == msdbrw_readonly) {
608 debug(dbg_general, "packagelist[%p]::display() done - quitNOcheck", this);
613 retl= new pkginfo*[nitems+1];
614 for (index=0; index<nitems; index++) retl[index]= table[index]->pkg;
615 retl[nitems] = nullptr;
616 debug(dbg_general, "packagelist[%p]::display() done, retl=%p", this, retl);
619 packagelist *sub = new packagelist(bindings, nullptr);
620 for (index=0; index < nitems; index++)
621 if (table[index]->pkg->set->name)
622 sub->add(table[index]->pkg);
623 repeatedlydisplay(sub,dp_must);
625 "packagelist[%p]::display() done, not recursive no retl", this);