awful debugging hacking
[dpkg] / dselect / pkglist.cc
1 /*
2 * dselect - Debian package maintenance user interface
3 * pkglist.cc - package list administration
4 *
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>
8 *
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.
13 *
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.
18 *
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/>.
21 */
22
23 #include <config.h>
24 #include <compat.h>
25
26 #include <assert.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31
32 #include <dpkg/i18n.h>
33 #include <dpkg/dpkg.h>
34 #include <dpkg/dpkg-db.h>
35 #include <dpkg/string.h>
36
37 #include "dselect.h"
38 #include "bindings.h"
39
40 int packagelist::compareentries(const struct perpackagestate *a,
41 const struct perpackagestate *b) {
42 switch (statsortorder) {
43 case sso_avail:
44 if (a->ssavail != b->ssavail) return a->ssavail - b->ssavail;
45 break;
46 case sso_state:
47 if (a->ssstate != b->ssstate) return a->ssstate - b->ssstate;
48 break;
49 case sso_unsorted:
50 break;
51 default:
52 internerr("unknown statsortorder %d", statsortorder);
53 }
54
55 const char *asection= a->pkg->section;
56 if (!asection && a->pkg->set->name)
57 asection = "";
58 const char *bsection= b->pkg->section;
59 if (!bsection && b->pkg->set->name)
60 bsection = "";
61 int c_section=
62 !asection || !bsection ?
63 (!bsection) - (!asection) :
64 !*asection || !*bsection ?
65 (!*asection) - (!*bsection) :
66 strcasecmp(asection,bsection);
67 int c_priority=
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);
71 int c_name=
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);
75
76 switch (sortorder) {
77 case so_section:
78 return c_section ? c_section : c_priority ? c_priority : c_name;
79 case so_priority:
80 return c_priority ? c_priority : c_section ? c_section : c_name;
81 case so_alpha:
82 return c_name;
83 case so_unsorted:
84 default:
85 internerr("unsorted or unknown sort %d", sortorder);
86 }
87 /* never reached, make gcc happy */
88 return 1;
89 }
90
91 void packagelist::discardheadings() {
92 int a,b;
93 for (a=0, b=0; a<nitems; a++) {
94 if (table[a]->pkg->set->name) {
95 table[b++]= table[a];
96 }
97 }
98 nitems= b;
99
100 struct perpackagestate *head, *next;
101 head= headings;
102 while (head) {
103 next= head->uprec;
104 delete head->pkg->set;
105 delete head;
106 head= next;
107 }
108 headings = nullptr;
109 }
110
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 *));
121 delete[] table;
122 table= newtable;
123 }
124
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>");
129
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;
137
138 struct perpackagestate *newstate= new perpackagestate;
139 newstate->pkg= newhead;
140 newstate->uprec= headings;
141 headings= newstate;
142 newstate->ssavail= ssavail;
143 newstate->ssstate= ssstate;
144 newhead->clientdata= newstate;
145
146 table[nitems++]= newstate;
147 }
148
149 static packagelist *sortpackagelist;
150
151 int qsort_compareentries(const void *a, const void *b) {
152 return sortpackagelist->compareentries(*(const struct perpackagestate **)a,
153 *(const struct perpackagestate **)b);
154 }
155
156 void packagelist::sortinplace() {
157 sortpackagelist= this;
158
159 debug(dbg_general, "packagelist[%p]::sortinplace()", this);
160 qsort(table, nitems, sizeof(struct pkgbin *), qsort_compareentries);
161 }
162
163 void packagelist::ensurestatsortinfo() {
164 const struct dpkg_version *veri;
165 const struct dpkg_version *vera;
166 struct pkginfo *pkg;
167 int index;
168
169 debug(dbg_general,
170 "packagelist[%p]::ensurestatsortinfos() sortorder=%d nitems=%d",
171 this, statsortorder, nitems);
172
173 switch (statsortorder) {
174 case sso_unsorted:
175 debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() unsorted", this);
176 return;
177 case sso_avail:
178 debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() calcssadone=%d",
179 this, calcssadone);
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;
192 break;
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.
199 #if 0
200 } else if (table[index]->original == PKG_WANT_UNKNOWN) {
201 table[index]->ssavail= ssa_notinst_unseen;
202 #endif
203 } else {
204 table[index]->ssavail= ssa_notinst_seen;
205 }
206 break;
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;
214 } else {
215 table[index]->ssavail= ssa_installed_sameold;
216 }
217 break;
218 default:
219 internerr("unknown status %d on sso_avail", pkg->status);
220 }
221 debug(dbg_general,
222 "packagelist[%p]::ensurestatsortinfos() i=%d ssavail=%d",
223 this, index, table[index]->ssavail);
224 }
225 calcssadone = true;
226 break;
227 case sso_state:
228 debug(dbg_general, "packagelist[%p]::ensurestatsortinfos() calcsssdone=%d",
229 this, calcsssdone);
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;
241 break;
242 case PKG_STAT_NOTINSTALLED:
243 table[index]->ssstate= sss_notinstalled;
244 break;
245 case PKG_STAT_CONFIGFILES:
246 table[index]->ssstate= sss_configfiles;
247 break;
248 case PKG_STAT_INSTALLED:
249 table[index]->ssstate= sss_installed;
250 break;
251 default:
252 internerr("unknown status %d on sso_state", table[index]->pkg->status);
253 }
254 debug(dbg_general,
255 "packagelist[%p]::ensurestatsortinfos() i=%d ssstate=%d",
256 this, index, table[index]->ssstate);
257 }
258 calcsssdone = true;
259 break;
260 default:
261 internerr("unknown statsortorder %d", statsortorder);
262 }
263 }
264
265 void packagelist::sortmakeheads() {
266 discardheadings();
267 ensurestatsortinfo();
268 sortinplace();
269 assert(nitems);
270
271 debug(dbg_general,
272 "packagelist[%p]::sortmakeheads() sortorder=%d statsortorder=%d",
273 this, sortorder, statsortorder);
274
275 int nrealitems= nitems;
276 addheading(ssa_none, sss_none, PKG_PRIO_UNSET, nullptr, nullptr);
277
278 assert(sortorder != so_unsorted);
279 if (sortorder == so_alpha && statsortorder == sso_unsorted) { sortinplace(); return; }
280
281 // Important: do not save pointers into table in this function, because
282 // addheading may need to reallocate table to make it larger !
283
284 struct pkginfo *lastpkg;
285 struct pkginfo *thispkg;
286 lastpkg = nullptr;
287 int a;
288 for (a=0; a<nrealitems; a++) {
289 thispkg= table[a]->pkg;
290 assert(thispkg->set->name);
291 int ssdiff= 0;
292 ssavailval ssavail= ssa_none;
293 ssstateval ssstate= sss_none;
294 switch (statsortorder) {
295 case sso_avail:
296 ssavail= thispkg->clientdata->ssavail;
297 ssdiff= (!lastpkg || ssavail != lastpkg->clientdata->ssavail);
298 break;
299 case sso_state:
300 ssstate= thispkg->clientdata->ssstate;
301 ssdiff= (!lastpkg || ssstate != lastpkg->clientdata->ssstate);
302 break;
303 case sso_unsorted:
304 break;
305 default:
306 internerr("unknown statsortorder %d", statsortorder);
307 }
308
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 : ""));
316
317 debug(dbg_general,
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",
323 thispkg->priority,
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");
329
330 if (ssdiff)
331 addheading(ssavail,ssstate,
332 PKG_PRIO_UNSET, nullptr, nullptr);
333
334 if (sortorder == so_section && sectiondiff)
335 addheading(ssavail,ssstate,
336 PKG_PRIO_UNSET, nullptr,
337 thispkg->section ? thispkg->section : "");
338
339 if (sortorder == so_priority && prioritydiff)
340 addheading(ssavail,ssstate,
341 thispkg->priority, thispkg->otherpriority, nullptr);
342
343 if (sortorder != so_alpha && (prioritydiff || sectiondiff))
344 addheading(ssavail,ssstate,
345 thispkg->priority,thispkg->otherpriority,
346 thispkg->section ? thispkg->section : "");
347
348 lastpkg= thispkg;
349 }
350
351 if (listpad) {
352 werase(listpad);
353 }
354
355 sortinplace();
356 }
357
358 void packagelist::initialsetup() {
359 debug(dbg_general, "packagelist[%p]::initialsetup()", this);
360
361 int allpackages = pkg_db_count_pkg();
362 datatable= new struct perpackagestate[allpackages];
363
364 nallocated= allpackages+150; // will realloc if necessary, so 150 not critical
365 table= new struct perpackagestate*[nallocated];
366
367 depsdone = nullptr;
368 unavdone = nullptr;
369 currentinfo = nullptr;
370 headings = nullptr;
371 verbose = false;
372 calcssadone = calcsssdone = false;
373 searchdescr = false;
374 }
375
376 void packagelist::finalsetup() {
377 setcursor(0);
378
379 debug(dbg_general, "packagelist[%p]::finalsetup done; recursive=%d nitems=%d",
380 this, recursive, nitems);
381 }
382
383 packagelist::packagelist(keybindings *kb) : baselist(kb) {
384 // nonrecursive
385 initialsetup();
386 struct pkgiterator *iter;
387 struct pkginfo *pkg;
388
389 nitems = 0;
390
391 iter = pkg_db_iter_new();
392 while ((pkg = pkg_db_iter_next_pkg(iter))) {
393 struct perpackagestate *state= &datatable[nitems];
394 state->pkg= pkg;
395 if (pkg->status == PKG_STAT_NOTINSTALLED &&
396 !pkg->files &&
397 pkg->want != PKG_WANT_INSTALL) {
398 pkg->clientdata = nullptr;
399 continue;
400 }
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) {
405 state->suggested=
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;
410 } else {
411 state->suggested= state->original;
412 state->spriority= sp_fixed;
413 }
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;
420 nitems++;
421 }
422 pkg_db_iter_free(iter);
423
424 if (!nitems)
425 ohshit(_("there are no packages"));
426 recursive = false;
427 sortorder= so_priority;
428 statsortorder= sso_avail;
429 archdisplayopt = ado_both;
430 versiondisplayopt= vdo_both;
431 sortmakeheads();
432 finalsetup();
433 }
434
435 packagelist::packagelist(keybindings *kb, pkginfo **pkgltab) : baselist(kb) {
436 // takes over responsibility for pkgltab (recursive)
437 initialsetup();
438
439 recursive = true;
440 nitems= 0;
441 if (pkgltab) {
442 add(pkgltab);
443 delete[] pkgltab;
444 }
445
446 sortorder= so_unsorted;
447 statsortorder= sso_unsorted;
448 archdisplayopt = ado_none;
449 versiondisplayopt= vdo_none;
450 finalsetup();
451 }
452
453 void
454 perpackagestate::free(bool recursive)
455 {
456 if (pkg->set->name) {
457 if (modstatdb_get_status() == msdbrw_write) {
458 if (uprec) {
459 assert(recursive);
460 uprec->selected= selected;
461 pkg->clientdata= uprec;
462 } else {
463 assert(!recursive);
464 if (pkg->want != selected &&
465 !(pkg->want == PKG_WANT_UNKNOWN && selected == PKG_WANT_PURGE)) {
466 pkg->want= selected;
467 }
468 pkg->clientdata = nullptr;
469 }
470 }
471 relations.destroy();
472 }
473 }
474
475 packagelist::~packagelist() {
476 debug(dbg_general, "packagelist[%p]::~packagelist()", this);
477
478 if (searchstring[0])
479 regfree(&searchfsm);
480
481 discardheadings();
482
483 int index;
484 for (index=0; index<nitems; index++) table[index]->free(recursive);
485 delete[] table;
486 delete[] datatable;
487 debug(dbg_general, "packagelist[%p]::~packagelist() tables freed", this);
488
489 doneent *search, *next;
490 for (search=depsdone; search; search=next) {
491 next= search->next;
492 delete search;
493 }
494
495 debug(dbg_general, "packagelist[%p]::~packagelist() done", this);
496 }
497
498 bool
499 packagelist::checksearch(char *rx)
500 {
501 int rc, opt = REG_NOSUB;
502 int pos;
503
504 if (str_is_unset(rx))
505 return false;
506
507 searchdescr = false;
508 if (searchstring[0]) {
509 regfree(&searchfsm);
510 searchstring[0]=0;
511 }
512
513 /* look for search options */
514 for (pos = strlen(rx) - 1; pos >= 0; pos--)
515 if ((rx[pos] == '/') && ((pos == 0) || (rx[pos - 1] != '\\')))
516 break;
517
518 if (pos >= 0) {
519 rx[pos++] = '\0';
520 if (strcspn(rx + pos, "di") != 0) {
521 displayerror(_("invalid search option given"));
522 return false;
523 }
524
525 while (rx[pos]) {
526 if (rx[pos] == 'i')
527 opt|=REG_ICASE;
528 else if (rx[pos] == 'd')
529 searchdescr = true;
530 pos++;
531 }
532 }
533
534 rc = regcomp(&searchfsm, rx, opt);
535 if (rc != 0) {
536 displayerror(_("error in regular expression"));
537 return false;
538 }
539
540 return true;
541 }
542
543 bool
544 packagelist::matchsearch(int index)
545 {
546 const char *name;
547
548 name = itemname(index);
549 if (!name)
550 return false; /* Skip things without a name (separators) */
551
552 if (regexec(&searchfsm, name, 0, nullptr, 0) == 0)
553 return true;
554
555 if (searchdescr) {
556 const char *descr = table[index]->pkg->available.description;
557 if (str_is_unset(descr))
558 return false;
559
560 if (regexec(&searchfsm, descr, 0, nullptr, 0) == 0)
561 return true;
562 }
563
564 return false;
565 }
566
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.
571 int response, index;
572 const keybindings::interpretation *interp;
573 pkginfo **retl;
574
575 debug(dbg_general, "packagelist[%p]::display()", this);
576
577 setupsigwinch();
578 startdisplay();
579
580 if (!expertmode)
581 displayhelp(helpmenulist(),'i');
582
583 debug(dbg_general, "packagelist[%p]::display() entering loop", this);
584 for (;;) {
585 if (whatinfo_height) wcursyncup(whatinfowin);
586 if (doupdate() == ERR)
587 ohshite(_("doupdate failed"));
588 signallist= this;
589 sigwinch_mask(SIG_UNBLOCK);
590 do
591 response= getch();
592 while (response == ERR && errno == EINTR);
593 sigwinch_mask(SIG_BLOCK);
594 if (response == ERR)
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;
602 }
603 pop_cleanup(ehflag_normaltidy); // unset the SIGWINCH handler
604 enddisplay();
605
606 if (interp->qa == qa_quitnochecksave ||
607 modstatdb_get_status() == msdbrw_readonly) {
608 debug(dbg_general, "packagelist[%p]::display() done - quitNOcheck", this);
609 return nullptr;
610 }
611
612 if (recursive) {
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);
617 return retl;
618 } else {
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);
624 debug(dbg_general,
625 "packagelist[%p]::display() done, not recursive no retl", this);
626 return nullptr;
627 }
628 }