2 * The new sysinstall program.
4 * This is probably the last program in the `sysinstall' line - the next
5 * generation being essentially a complete rewrite.
10 * Jordan Hubbard. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer,
17 * verbatim and that no modifications are made prior to this
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
23 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 #include "sysinstall.h"
45 /* Macros and magic values */
49 /* A structure holding the root, top and plist pointer at once */
52 PkgNodePtr root; /* root of tree */
53 PkgNodePtr top; /* part of tree we handle */
54 PkgNodePtr plist; /* list of selected packages */
56 typedef struct ListPtrs* ListPtrsPtr;
58 static void index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie);
60 /* Shared between index_initialize() and the various clients of it */
67 return ptr ? strdup(ptr) : NULL;
70 static char *descrs[] = {
71 "Package Selection", "To mark a package, move to it and press SPACE. If the package is\n"
72 "already marked, it will be unmarked or deleted (if installed).\n"
73 "Items marked with a `D' are dependencies which will be auto-loaded.\n"
74 "To search for a package by name, press ESC. To select a category,\n"
75 "press RETURN. NOTE: The All category selection creates a very large\n"
76 "submenu! If you select it, please be patient while it comes up.",
77 "Package Targets", "These are the packages you've selected for extraction.\n\n"
78 "If you're sure of these choices, select OK.\n"
79 "If not, select Cancel to go back to the package selection menu.\n",
80 "All", "All available packages in all categories.",
81 "afterstep", "Ports to support the AfterStep window manager.",
82 "applications", "User application software.",
83 "archivers", "Utilities for archiving and unarchiving data.",
84 "astro", "Applications related to astronomy.",
85 "audio", "Audio utilities - most require a supported sound card.",
86 "benchmarks", "Utilities for measuring system performance.",
87 "biology", "Software related to biology.",
88 "cad", "Computer Aided Design utilities.",
89 "chinese", "Ported software for the Chinese market.",
90 "comms", "Communications utilities.",
91 "converters", "Format conversion utilities.",
92 "databases", "Database software.",
93 "deskutils", "Various Desktop utilities.",
94 "devel", "Software development utilities and libraries.",
95 "documentation", "Document preparation utilities.",
96 "editors", "Common text editors.",
97 "elisp", "Things related to Emacs Lisp.",
98 "emulators", "Utilities for emulating other OS types.",
99 "ftp", "FTP client and server utilities.",
100 "games", "Various and sundry amusements.",
101 "german", "Ported software for Germanic countries.",
102 "gnome", "Components of the Gnome Desktop environment.",
103 "graphics", "Graphics libraries and utilities.",
104 "ipv6", "IPv6 related software.",
105 "hebrew", "Ported software for Hebrew language.",
106 "irc", "Internet Relay Chat utilities.",
107 "japanese", "Ported software for the Japanese market.",
108 "java", "Java language support.",
109 "kde", "Software for the K Desktop Environment.",
110 "korean", "Ported software for the Korean market.",
111 "lang", "Computer languages.",
112 "languages", "Computer languages.",
113 "libraries", "Software development libraries.",
114 "mail", "Electronic mail packages and utilities.",
115 "math", "Mathematical computation software.",
116 "mbone", "Applications and utilities for the MBONE.",
117 "misc", "Miscellaneous utilities.",
118 "net", "Networking utilities.",
119 "news", "USENET News support software.",
120 "numeric", "Mathematical computation software.",
121 "offix", "An office automation suite of sorts.",
122 "orphans", "Packages without a home elsewhere.",
123 "palm", "Software support for the 3Com Palm Pilot(tm) series.",
124 "perl5", "Utilities/modules for the PERL5 language.",
125 "pilot", "Software support for the 3Com Palm Pilot(tm) series.",
126 "plan9", "Software from the Plan9 operating system.",
127 "print", "Utilities for dealing with printing.",
128 "printing", "Utilities for dealing with printing.",
129 "programming", "Software development utilities and libraries.",
130 "python", "Software related to the Python language.",
131 "ruby", "Software related to the Ruby language.",
132 "russian", "Ported software for the Russian market.",
133 "security", "System security software.",
134 "shells", "Various shells (tcsh, bash, etc).",
135 "sysutils", "Various system utilities.",
136 "tcl75", "TCL v7.5 and packages that depend on it.",
137 "tcl76", "TCL v7.6 and packages that depend on it.",
138 "tcl80", "TCL v8.0 and packages that depend on it.",
139 "tcl82", "TCL v8.2 and packages that depend on it.",
140 "textproc", "Text processing/search utilities.",
141 "tk41", "Tk4.1 and packages that depend on it.",
142 "tk42", "Tk4.2 and packages that depend on it.",
143 "tk80", "Tk8.0 and packages that depend on it.",
144 "tk81", "Tk8.1 and packages that depend on it.",
145 "tk82", "Tk8.2 and packages that depend on it.",
146 "tkstep80", "tkstep wm and packages that depend on it.",
147 "troff", "TROFF text formatting utilities.",
148 "vietnamese", "Ported software for the Vietnamese market.",
149 "windowmaker", "Ports to support the WindowMaker window manager.",
150 "www", "WEB utilities (browers, HTTP servers, etc).",
151 "x11", "X Window System based utilities.",
152 "x11-clocks", "X Window System based clocks.",
153 "x11-fm", "X Window System based file managers.",
154 "x11-fonts", "X Window System fonts and font utilities.",
155 "x11-servers", "X Window System servers.",
156 "x11-toolkits", "X Window System based development toolkits.",
157 "x11-wm", "X Window System window managers.",
162 fetch_desc(char *name)
166 for (i = 0; descrs[i]; i += 2) {
167 if (!strcmp(descrs[i], name))
168 return descrs[i + 1];
170 return "No description provided";
174 new_pkg_node(char *name, node_type type)
176 PkgNodePtr tmp = safe_malloc(sizeof(PkgNode));
178 tmp->name = _strdup(name);
188 for (i = 0; buf[i]; i++)
189 if (buf[i] == '\t' || buf[i] == '\n')
195 new_index(char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *deps)
197 IndexEntryPtr tmp = safe_malloc(sizeof(IndexEntry));
199 tmp->name = _strdup(name);
200 tmp->path = _strdup(pathto);
201 tmp->prefix = _strdup(prefix);
202 tmp->comment = _strdup(comment);
203 tmp->descrfile = strip(_strdup(descr));
204 tmp->maintainer = _strdup(maint);
205 tmp->deps = _strdup(deps);
207 tmp->installed = package_exists(name);
212 index_register(PkgNodePtr top, char *where, IndexEntryPtr ptr)
216 for (q = NULL, p = top->kids; p; p = p->next) {
217 if (!strcmp(p->name, where)) {
223 /* Add new category */
224 q = new_pkg_node(where, PLACE);
225 q->desc = fetch_desc(where);
229 p = new_pkg_node(ptr->name, PACKAGE);
230 p->desc = ptr->comment;
237 copy_to_sep(char *to, char *from, int sep)
241 tok = strchr(from, sep);
248 return tok + 1 - from;
252 readline(FILE *fp, char *buf, int max)
257 while ((rv = fread(&ch, 1, 1, fp)) == 1 && ch != '\n' && i < max)
265 index_parse(FILE *fp, char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *cats, char *rdeps)
272 i = readline(fp, line, sizeof line);
276 cp += copy_to_sep(name, cp, '|');
277 cp += copy_to_sep(pathto, cp, '|');
278 cp += copy_to_sep(prefix, cp, '|');
279 cp += copy_to_sep(comment, cp, '|');
280 cp += copy_to_sep(descr, cp, '|');
281 cp += copy_to_sep(maint, cp, '|');
282 cp += copy_to_sep(cats, cp, '|');
283 cp += copy_to_sep(junk, cp, '|'); /* build deps - not used */
285 copy_to_sep(rdeps, cp, '|');
287 strncpy(rdeps, cp, 1023);
292 index_read(FILE *fp, PkgNodePtr papa)
294 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[1024];
297 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps) != EOF) {
298 char *cp, *cp2, tmp[1024];
301 idx = new_index(name, pathto, prefix, comment, descr, maint, deps);
302 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
303 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
305 index_register(papa, cp, idx);
307 index_register(papa, cp, idx);
309 /* Add to special "All" category */
310 index_register(papa, "All", idx);
313 /* Adjust dependency counts */
314 for (i = papa->kids; i != NULL; i = i->next)
315 if (strcmp(i->name, "All") == 0)
317 for (i = i->kids; i != NULL; i = i->next)
318 if (((IndexEntryPtr)i->data)->installed)
319 index_recorddeps(TRUE, papa, i->data);
325 index_init(PkgNodePtr top, PkgNodePtr plist)
328 top->next = top->kids = NULL;
329 top->name = "Package Selection";
331 top->desc = fetch_desc(top->name);
335 plist->next = plist->kids = NULL;
336 plist->name = "Package Targets";
338 plist->desc = fetch_desc(plist->name);
344 index_print(PkgNodePtr top, int level)
349 for (i = 0; i < level; i++) putchar('\t');
350 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
351 for (i = 0; i < level; i++) putchar('\t');
352 printf("desc: %s\n", top->desc);
354 index_print(top->kids, level + 1);
359 /* Swap one node for another */
361 swap_nodes(PkgNodePtr a, PkgNodePtr b)
372 /* Use a disgustingly simplistic bubble sort to put our lists in order */
374 index_sort(PkgNodePtr top)
378 /* Sort everything at the top level */
379 for (p = top->kids; p; p = p->next) {
380 for (q = top->kids; q; q = q->next) {
381 if (q->next && strcmp(q->name, q->next->name) > 0)
382 swap_nodes(q, q->next);
386 /* Now sub-sort everything n levels down */
387 for (p = top->kids; p; p = p->next) {
393 /* Delete an entry out of the list it's in (only the plist, at present) */
395 index_delete(PkgNodePtr n)
398 PkgNodePtr p = n->next;
403 else /* Kludgy end sentinal */
408 * Search for a given node by name, returning the category in if
412 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
416 for (p = top->kids; p && p->name; p = p->next) {
417 if (p->type == PACKAGE) {
418 /* If tp == NULL, we're looking for an exact package match */
419 if (!tp && !strcmp(p->name, str))
422 /* If tp, we're looking for both a package and a pointer to the place it's in */
423 if (tp && !strncmp(p->name, str, strlen(str))) {
429 /* The usual recursion-out-of-laziness ploy */
430 if ((sp = index_search(p, str, tp)) != NULL)
440 pkg_checked(dialogMenuItem *self)
442 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
443 PkgNodePtr kp = self->data, plist = lists->plist;
446 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
447 if (kp->type == PACKAGE && plist) {
448 IndexEntryPtr ie = kp->data;
451 markD = ie->depc > 0; /* needed as dependency */
452 markX = i || ie->installed; /* selected or installed */
453 self->mark = markX ? 'X' : 'D';
454 return markD || markX;
460 pkg_fire(dialogMenuItem *self)
463 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
464 PkgNodePtr sp, kp = self->data, plist = lists->plist;
468 else if (kp->type == PACKAGE) {
469 IndexEntryPtr ie = kp->data;
471 sp = index_search(plist, kp->name, NULL);
472 /* Not already selected? */
474 if (!ie->installed) {
475 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
478 np->next = plist->kids;
480 index_recorddeps(TRUE, lists->root, ie);
481 msgInfo("Added %s to selection list", kp->name);
483 else if (ie->depc == 0) {
484 if (!msgYesNo("Do you really want to delete %s from the system?", kp->name)) {
485 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
486 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
490 index_recorddeps(FALSE, lists->root, ie);
495 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
496 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
499 index_recorddeps(FALSE, lists->root, ie);
500 msgInfo("Removed %s from selection list", kp->name);
504 /* Mark menu for redraw if we had dependencies */
505 if (strlen(ie->deps) > 0)
508 else { /* Not a package, must be a directory */
512 index_menu(lists->root, kp, plist, &p, &s);
513 ret = DITEM_SUCCESS | DITEM_CONTINUE;
519 pkg_selected(dialogMenuItem *self, int is_selected)
521 PkgNodePtr kp = self->data;
523 if (!is_selected || kp->type != PACKAGE)
529 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
531 struct ListPtrs lists;
532 int n, rval, maxname;
535 dialogMenuItem *nitems;
547 /* Figure out if this menu is full of "leaves" or "branches" */
548 for (kp = top->kids; kp && kp->name; kp = kp->next) {
552 if (kp->type == PACKAGE && plist) {
554 if ((len = strlen(kp->name)) > maxname)
559 msgConfirm("The %s menu is empty.", top->name);
560 return DITEM_LEAVE_MENU;
570 if (!hasPackages && plist) {
571 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, 0, &curr, &max);
572 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, 0, &curr, &max);
574 while (kp && kp->name) {
576 IndexEntryPtr ie = kp->data;
578 /* Brutally adjust description to fit in menu */
579 if (kp->type == PACKAGE)
580 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
582 SAFE_STRCPY(buf, kp->desc);
583 if (strlen(buf) > (_MAX_DESC - maxname))
584 buf[_MAX_DESC - maxname] = '\0';
585 nitems = item_add(nitems, kp->name, buf, pkg_checked, pkg_fire, pkg_selected, kp, (int)&lists, &curr, &max);
589 /* NULL delimiter so item_free() knows when to stop later */
590 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, 0, &curr, &max);
593 dialog_clear_norefresh();
595 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
597 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
598 if (rval == -1 && plist) {
603 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
604 PkgNodePtr p = index_search(top, cp, &menu);
609 /* These need to be set to point at the found item, actually. Hmmm! */
611 index_menu(root, menu, plist, &pos, &scroll);
614 msgConfirm("Search string: %s yielded no hits.", cp);
618 items_free(nitems, &curr, &max);
620 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
625 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
627 int status = DITEM_SUCCESS;
629 IndexEntryPtr id = who->data;
630 WINDOW *w = savescr();
632 if (id && id->deps && strlen(id->deps)) {
633 char t[1024], *cp, *cp2;
635 SAFE_STRCPY(t, id->deps);
637 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
638 if ((cp2 = index(cp, ' ')) != NULL)
640 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
641 status = index_extract(dev, top, tmp2, TRUE);
642 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
643 if (variable_get(VAR_NO_CONFIRM))
644 msgNotify("Loading of dependant package %s failed", cp);
646 msgConfirm("Loading of dependant package %s failed", cp);
649 else if (!package_exists(cp)) {
650 if (variable_get(VAR_NO_CONFIRM))
651 msgNotify("Warning: %s is a required package but was not found.", cp);
653 msgConfirm("Warning: %s is a required package but was not found.", cp);
661 /* Done with the deps? Load the real m'coy */
662 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
663 status = package_extract(dev, who->name, depended);
664 if (DITEM_STATUS(status) == DITEM_SUCCESS)
672 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
674 char depends[1024], *space, *todo;
676 IndexEntryPtr found_ie;
678 SAFE_STRCPY(depends, ie->deps);
679 for (todo = depends; todo != NULL; ) {
680 space = index(todo, ' ');
684 if (strlen(todo) > 0) { /* only non-empty dependencies */
685 found = index_search(root, todo, NULL);
687 found_ie = found->data;
702 static Boolean index_initted;
704 /* Read and initialize global index */
706 index_initialize(char *path)
711 if (!index_initted) {
713 dialog_clear_norefresh();
716 if (!mediaVerify()) {
718 return DITEM_FAILURE;
721 /* Does it move when you kick it? */
722 if (!mediaDevice->init(mediaDevice)) {
724 return DITEM_FAILURE;
727 dialog_clear_norefresh();
728 msgNotify("Attempting to fetch %s file from selected media.", path);
729 fp = mediaDevice->get(mediaDevice, path, TRUE);
731 msgConfirm("Unable to get packages/INDEX file from selected media.\n"
732 "This may be because the packages collection is not available at\n"
733 "on the distribution media you've chosen (most likely an FTP site\n"
734 "without the packages collection mirrored). Please verify media\n"
735 "(or path to media) and try again. If your local site does not\n"
736 "carry the packages collection, then we recommend either a CD\n"
737 "distribution or the master distribution on ftp.freebsd.org.");
738 mediaDevice->shutdown(mediaDevice);
740 return DITEM_FAILURE;
742 dialog_clear_norefresh();
743 msgNotify("Located INDEX, now reading package data from it...");
744 index_init(&Top, &Plist);
745 if (index_read(fp, &Top)) {
746 msgConfirm("I/O or format error on packages/INDEX file.\n"
747 "Please verify media (or path to media) and try again.");
750 return DITEM_FAILURE;
754 index_initted = TRUE;
757 return DITEM_SUCCESS;