dpkg (1.18.25) stretch; urgency=medium
[dpkg] / dselect / main.cc
CommitLineData
1479465f
GJ
1/*
2 * dselect - Debian package maintenance user interface
3 * main.cc - main program
4 *
5 * Copyright © 1994-1996 Ian Jackson <ijackson@chiark.greenend.org.uk>
6 * Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org>
7 * Copyright © 2006-2015 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 <sys/types.h>
27#include <sys/wait.h>
28
29#include <assert.h>
30#include <errno.h>
31#include <limits.h>
32#if HAVE_LOCALE_H
33#include <locale.h>
34#endif
35#include <ctype.h>
36#include <string.h>
37#include <fcntl.h>
38#include <dirent.h>
39#include <unistd.h>
40#include <stdlib.h>
41#include <stdio.h>
42
43// Solaris requires curses.h to be included before term.h
44#include "dselect-curses.h"
45
46#if defined(HAVE_NCURSESW_TERM_H)
47#include <ncursesw/term.h>
48#elif defined(HAVE_NCURSES_TERM_H)
49#include <ncurses/term.h>
50#else
51#include <term.h>
52#endif
53
54#include <dpkg/i18n.h>
55#include <dpkg/dpkg.h>
56#include <dpkg/dpkg-db.h>
57#include <dpkg/options.h>
58
59#include "dselect.h"
60#include "bindings.h"
61#include "pkglist.h"
62
63static const char printforhelp[] = N_("Type dselect --help for help.");
64
65bool expertmode = false;
66
67static const char *admindir = ADMINDIR;
68
69static keybindings packagelistbindings(packagelist_kinterps,packagelist_korgbindings);
70
71struct table_t {
72 const char *name;
73 const int num;
74};
75
76static const struct table_t colourtable[]= {
77 {"black", COLOR_BLACK },
78 {"red", COLOR_RED },
79 {"green", COLOR_GREEN },
80 {"yellow", COLOR_YELLOW },
81 {"blue", COLOR_BLUE },
82 {"magenta", COLOR_MAGENTA },
83 {"cyan", COLOR_CYAN },
84 {"white", COLOR_WHITE },
85 {nullptr, 0},
86};
87
88static const struct table_t attrtable[]= {
89 {"normal", A_NORMAL },
90 {"standout", A_STANDOUT },
91 {"underline", A_UNDERLINE },
92 {"reverse", A_REVERSE },
93 {"blink", A_BLINK },
94 {"bright", A_BLINK }, // on some terminals
95 {"dim", A_DIM },
96 {"bold", A_BOLD },
97 {nullptr, 0},
98};
99
100/* A slightly confusing mapping from dselect's internal names to
101 * the user-visible names.*/
102static const struct table_t screenparttable[]= {
103 {"list", list },
104 {"listsel", listsel },
105 {"title", title },
106 {"infohead", thisstate },
107 {"pkgstate", selstate },
108 {"pkgstatesel", selstatesel },
109 {"listhead", colheads },
110 {"query", query },
111 {"info", info_body },
112 {"infodesc", info_head },
113 {"infofoot", whatinfo },
114 {"helpscreen", helpscreen },
115 {nullptr, 0},
116};
117
118/* Historical (patriotic?) colours. */
119struct colordata color[]= {
120 /* fore back attr */
121 {COLOR_WHITE, COLOR_BLACK, 0 }, // default, not used
122 {COLOR_WHITE, COLOR_BLACK, 0 }, // list
123 {COLOR_WHITE, COLOR_BLACK, A_REVERSE }, // listsel
124 {COLOR_WHITE, COLOR_RED, 0 }, // title
125 {COLOR_WHITE, COLOR_BLUE, 0 }, // thisstate
126 {COLOR_WHITE, COLOR_BLACK, A_BOLD }, // selstate
127 {COLOR_WHITE, COLOR_BLACK, A_REVERSE | A_BOLD }, // selstatesel
128 {COLOR_WHITE, COLOR_BLUE, 0 }, // colheads
129 {COLOR_WHITE, COLOR_RED, 0 }, // query
130 {COLOR_WHITE, COLOR_BLACK, 0 }, // info_body
131 {COLOR_WHITE, COLOR_BLACK, A_BOLD }, // info_head
132 {COLOR_WHITE, COLOR_BLUE, 0 }, // whatinfo
133 {COLOR_WHITE, COLOR_BLACK, 0 }, // help
134};
135
136struct menuentry {
137 const char *command;
138 const char *key;
139 const char *option;
140 const char *menuent;
141 urqfunction *fn;
142};
143
144static const menuentry menuentries[]= {
145 { "access", N_("a"), N_("[A]ccess"), N_("Choose the access method to use."), &urq_setup },
146 { "update", N_("u"), N_("[U]pdate"), N_("Update list of available packages, if possible."), &urq_update },
147 { "select", N_("s"), N_("[S]elect"), N_("Request which packages you want on your system."), &urq_list },
148 { "install", N_("i"), N_("[I]nstall"),N_("Install and upgrade wanted packages."), &urq_install },
149 { "config", N_("c"), N_("[C]onfig"), N_("Configure any packages that are unconfigured."), &urq_config },
150 { "remove", N_("r"), N_("[R]emove"), N_("Remove unwanted software."), &urq_remove },
151 { "quit", N_("q"), N_("[Q]uit"), N_("Quit dselect."), &urq_quit },
152 { nullptr, nullptr, N_("menu"), nullptr, &urq_menu },
153 { nullptr }
154};
155
156static const char programdesc[]=
157 N_("Debian '%s' package handling frontend version %s.\n");
158
159static const char licensestring[]= N_(
160 "This is free software; see the GNU General Public License version 2 or\n"
161 "later for copying conditions. There is NO warranty.\n");
162
163static void DPKG_ATTR_NORET
164printversion(const struct cmdinfo *ci, const char *value)
165{
166 printf(gettext(programdesc), DSELECT, PACKAGE_RELEASE);
167 printf("%s", gettext(licensestring));
168
169 m_output(stdout, _("<standard output>"));
170
171 exit(0);
172}
173
174static void DPKG_ATTR_NORET
175usage(const struct cmdinfo *ci, const char *value)
176{
177 int i;
178
179 printf(_(
180"Usage: %s [<option>...] [<command>...]\n"
181"\n"), DSELECT);
182
183 printf(_("Commands:\n"));
184 for (i = 0; menuentries[i].command; i++)
185 printf(" %-10s %s\n", menuentries[i].command, menuentries[i].menuent);
186 fputs("\n", stdout);
187
188 printf(_(
189"Options:\n"
190" --admindir <directory> Use <directory> instead of %s.\n"
191" --expert Turn on expert mode.\n"
192" -D, --debug <file> Turn on debugging, send output to <file>.\n"
193" --color <color-spec> Configure screen colors.\n"
194" --colour <color-spec> Ditto.\n"
195), ADMINDIR);
196
197 printf(_(
198" -?, --help Show this help message.\n"
199" --version Show the version.\n"
200"\n"));
201
202 printf(_("<color-spec> is <screen-part>:[<foreground>],[<background>][:<attr>[+<attr>]...]\n"));
203
204 printf(_("<screen-part> is:"));
205 for (i=0; screenparttable[i].name; i++)
206 printf(" %s", screenparttable[i].name);
207 fputs("\n", stdout);
208
209 printf(_("<color> is:"));
210 for (i=0; colourtable[i].name; i++)
211 printf(" %s", colourtable[i].name);
212 fputs("\n", stdout);
213
214 printf(_("<attr> is:"));
215 for (i=0; attrtable[i].name; i++)
216 printf(" %s", attrtable[i].name);
217 fputs("\n", stdout);
218
219 m_output(stdout, _("<standard output>"));
220
221 exit(0);
222}
223
224/* These are called by C code, so need to have C calling convention */
225extern "C" {
226
227 static void
228 set_debug(const struct cmdinfo*, const char *v)
229 {
230 FILE *fp;
231
232 fp = fopen(v, "a");
233 if (!fp)
234 ohshite(_("couldn't open debug file '%.255s'\n"), v);
235
236 debug_set_output(fp, v);
237 debug_set_mask(dbg_general | dbg_depcon);
238 }
239
240 static void
241 set_expert(const struct cmdinfo*, const char *v)
242 {
243 expertmode = true;
244 }
245
246 static int
247 findintable(const struct table_t *table, const char *item, const char *tablename)
248 {
249 int i;
250
251 for (i = 0; item && (table[i].name != nullptr); i++)
252 if (strcasecmp(item, table[i].name) == 0)
253 return table[i].num;
254
255 ohshit(_("invalid %s '%s'"), tablename, item);
256 }
257
258 /*
259 * The string's format is:
260 * screenpart:[forecolor][,backcolor][:[<attr>, ...]
261 * Examples: --color title:black,cyan:bright+underline
262 * --color list:red,yellow
263 * --color colheads:,green:bright
264 * --color selstate::reverse // doesn't work FIXME
265 */
266 static void
267 set_color(const struct cmdinfo*, const char *string)
268 {
269 char *s;
270 char *colours, *attributes, *attrib, *colourname;
271 int screenpart, aval;
272
273 s = m_strdup(string); // strtok modifies strings, keep string const
274 screenpart= findintable(screenparttable, strtok(s, ":"), _("screen part"));
275 colours = strtok(nullptr, ":");
276 attributes = strtok(nullptr, ":");
277
278 if ((colours == nullptr || ! strlen(colours)) &&
279 (attributes == nullptr || ! strlen(attributes))) {
280 ohshit(_("null colour specification"));
281 }
282
283 if (colours != nullptr && strlen(colours)) {
284 colourname= strtok(colours, ",");
285 if (colourname != nullptr && strlen(colourname)) {
286 // normalize attributes to prevent confusion
287 color[screenpart].attr= A_NORMAL;
288 color[screenpart].fore=findintable(colourtable, colourname, _("colour"));
289 }
290 colourname = strtok(nullptr, ",");
291 if (colourname != nullptr && strlen(colourname)) {
292 color[screenpart].attr= A_NORMAL;
293 color[screenpart].back=findintable(colourtable, colourname, _("colour"));
294 }
295 }
296
297 if (attributes != nullptr && strlen(attributes)) {
298 for (attrib= strtok(attributes, "+");
299 attrib != nullptr && strlen(attrib);
300 attrib = strtok(nullptr, "+")) {
301 aval=findintable(attrtable, attrib, _("colour attribute"));
302 if (aval == A_NORMAL) // set to normal
303 color[screenpart].attr= aval;
304 else // add to existing attribs
305 color[screenpart].attr= color[screenpart].attr | aval;
306 }
307 }
308
309 free(s);
310 }
311
312} /* End of extern "C" */
313
314static const struct cmdinfo cmdinfos[]= {
315 { "admindir", 0, 1, nullptr, &admindir, nullptr },
316 { "debug", 'D', 1, nullptr, nullptr, set_debug },
317 { "expert", 'E', 0, nullptr, nullptr, set_expert },
318 { "help", '?', 0, nullptr, nullptr, usage },
319 { "version", 0, 0, nullptr, nullptr, printversion },
320 { "color", 0, 1, nullptr, nullptr, set_color }, /* US spelling */
321 { "colour", 0, 1, nullptr, nullptr, set_color }, /* UK spelling */
322 { nullptr, 0, 0, nullptr, nullptr, nullptr }
323};
324
325static bool cursesareon = false;
326void curseson() {
327 if (!cursesareon) {
328 const char *cup, *smso;
329 initscr();
330 cup= tigetstr("cup");
331 smso= tigetstr("smso");
332 if (!cup || !smso) {
333 endwin();
334 if (!cup)
335 fputs(_("Terminal does not appear to support cursor addressing.\n"),stderr);
336 if (!smso)
337 fputs(_("Terminal does not appear to support highlighting.\n"),stderr);
338 fprintf(stderr,
339 _("Set your TERM variable correctly, use a better terminal,\n"
340 "or make do with the per-package management tool %s.\n"),
341 DPKG);
342 ohshit(_("terminal lacks necessary features, giving up"));
343 }
344 }
345 cursesareon = true;
346}
347
348void cursesoff() {
349 if (cursesareon) {
350 clear();
351 refresh();
352 endwin();
353 }
354 cursesareon = false;
355}
356
357urqresult urq_list(void) {
358 modstatdb_open((modstatdb_rw)(msdbrw_writeifposs |
359 msdbrw_available_readonly));
360
361 curseson();
362
363 packagelist *l= new packagelist(&packagelistbindings);
364 l->resolvesuggest();
365 l->display();
366 delete l;
367
368 modstatdb_shutdown();
369
370 return urqr_normal;
371}
372
373static void
374dme(int i, int so)
375{
376 const menuentry *me= &menuentries[i];
377
378 varbuf buf;
379 buf.fmt(" %c %d. %-11.11s %-80.80s ",
380 so ? '*' : ' ', i,
381 gettext(me->option),
382 gettext(me->menuent));
383
384 int x, y DPKG_ATTR_UNUSED;
385 getmaxyx(stdscr,y,x);
386
387 attrset(so ? A_REVERSE : A_NORMAL);
388 mvaddnstr(i + 2, 0, buf.string(), x - 1);
389 attrset(A_NORMAL);
390}
391
392static int
393refreshmenu(void)
394{
395 curseson(); cbreak(); noecho(); nonl(); keypad(stdscr,TRUE);
396
397 int x, y DPKG_ATTR_UNUSED;
398 getmaxyx(stdscr,y,x);
399
400 varbuf buf;
401 buf.fmt(gettext(programdesc), DSELECT, PACKAGE_RELEASE);
402
403 clear();
404 attrset(A_BOLD);
405 mvaddnstr(0, 0, buf.string(), x - 1);
406
407 attrset(A_NORMAL);
408 const struct menuentry *mep; int i;
409 for (mep=menuentries, i=0; mep->option && mep->menuent; mep++, i++)
410 dme(i,0);
411
412 attrset(A_BOLD);
413 addstr(_("\n\n"
414 "Move around with ^P and ^N, cursor keys, initial letters, or digits;\n"
415 "Press <enter> to confirm selection. ^L redraws screen.\n\n"));
416
417 attrset(A_NORMAL);
418 addstr(_("Copyright (C) 1994-1996 Ian Jackson.\n"
419 "Copyright (C) 2000,2001 Wichert Akkerman.\n"));
420 addstr(gettext(licensestring));
421
422 modstatdb_init();
423 if (!modstatdb_can_lock())
424 addstr(_("\n\n"
425 "Read-only access: only preview of selections is available!"));
426 modstatdb_done();
427
428 return i;
429}
430
431urqresult urq_menu(void) {
432 int entries, c;
433 entries= refreshmenu();
434 int cursor=0;
435 dme(0,1);
436 for (;;) {
437 refresh();
438 do
439 c= getch();
440 while (c == ERR && errno == EINTR);
441 if (c==ERR) {
442 if(errno != 0)
443 ohshite(_("failed to getch in main menu"));
444 else {
445 clearok(stdscr,TRUE); clear(); refreshmenu(); dme(cursor,1);
446 }
447 }
448
449 if (c == CTRL('n') || c == KEY_DOWN || c == ' ' || c == 'j') {
450 dme(cursor,0); cursor++; cursor %= entries; dme(cursor,1);
451 } else if (c == CTRL('p') || c == KEY_UP || c == CTRL('h') ||
452 c==KEY_BACKSPACE || c==KEY_DC || c=='k') {
453 dme(cursor,0); cursor+= entries-1; cursor %= entries; dme(cursor,1);
454 } else if (c=='\n' || c=='\r' || c==KEY_ENTER) {
455 clear(); refresh();
456
457 /* FIXME: trap errors in urq_... */
458 urqresult res = menuentries[cursor].fn();
459 switch (res) {
460 case urqr_quitmenu:
461 return urqr_quitmenu;
462 case urqr_normal:
463 cursor++; cursor %= entries;
464 case urqr_fail:
465 break;
466 default:
467 internerr("unknown menufn %d", res);
468 }
469 refreshmenu(); dme(cursor,1);
470 } else if (c == CTRL('l')) {
471 clearok(stdscr,TRUE); clear(); refreshmenu(); dme(cursor,1);
472 } else if (isdigit(c)) {
473 char buf[2]; buf[0]=c; buf[1]=0; c=atoi(buf);
474 if (c < entries) {
475 dme(cursor,0); cursor=c; dme(cursor,1);
476 } else {
477 beep();
478 }
479 } else if (isalpha(c)) {
480 c= tolower(c);
481 int i = 0;
482 while (i < entries && gettext(menuentries[i].key)[0] != c)
483 i++;
484 if (i < entries) {
485 dme(cursor,0); cursor=i; dme(cursor,1);
486 } else {
487 beep();
488 }
489 } else {
490 beep();
491 }
492 }
493}
494
495urqresult urq_quit(void) {
496 /* FIXME: check packages OK. */
497 return urqr_quitmenu;
498}
499
500static void
501dselect_catch_fatal_error()
502{
503 cursesoff();
504 catch_fatal_error();
505}
506
507int
508main(int, const char *const *argv)
509{
510 dpkg_locales_init(DSELECT);
511 dpkg_set_progname(DSELECT);
512
513 push_error_context_func(dselect_catch_fatal_error, print_fatal_error, nullptr);
514
515 dpkg_options_load(DSELECT, cmdinfos);
516 dpkg_options_parse(&argv, cmdinfos, printforhelp);
517
518 admindir = dpkg_db_set_dir(admindir);
519
520 if (*argv) {
521 const char *a;
522 while ((a = *argv++) != nullptr) {
523 const menuentry *me = menuentries;
524 while (me->command && strcmp(me->command, a))
525 me++;
526 if (!me->command)
527 badusage(_("unknown action string '%.50s'"), a);
528 me->fn();
529 }
530 } else {
531 urq_menu();
532 }
533
534 cursesoff();
535 dpkg_program_done();
536
537 return(0);
538}