chiark / gitweb /
lib/dpkg/tarfn.c: Kludge `tar_header_decode' to handle spurious `errno'.
[dpkg] / dselect / main.cc
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
63 static const char printforhelp[] = N_("Type dselect --help for help.");
64
65 bool expertmode = false;
66
67 static const char *admindir = ADMINDIR;
68
69 static keybindings packagelistbindings(packagelist_kinterps,packagelist_korgbindings);
70
71 struct table_t {
72   const char *name;
73   const int num;
74 };
75
76 static 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
88 static 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.*/
102 static 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. */
119 struct 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
136 struct menuentry {
137   const char *command;
138   const char *key;
139   const char *option;
140   const char *menuent;
141   urqfunction *fn;
142 };
143
144 static 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
156 static const char programdesc[]=
157       N_("Debian '%s' package handling frontend version %s.\n");
158
159 static 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
163 static void DPKG_ATTR_NORET
164 printversion(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
174 static void DPKG_ATTR_NORET
175 usage(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 */
225 extern "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
314 static 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
325 static bool cursesareon = false;
326 void 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
348 void cursesoff() {
349   if (cursesareon) {
350     clear();
351     refresh();
352     endwin();
353   }
354   cursesareon = false;
355 }
356
357 urqresult 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
373 static void
374 dme(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
392 static int
393 refreshmenu(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
431 urqresult 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
495 urqresult urq_quit(void) {
496   /* FIXME: check packages OK. */
497   return urqr_quitmenu;
498 }
499
500 static void
501 dselect_catch_fatal_error()
502 {
503   cursesoff();
504   catch_fatal_error();
505 }
506
507 int
508 main(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 }