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.
7 * $FreeBSD: src/release/sysinstall/index.c,v 1.80.2.20 2003/03/03 09:17:43 sobomax Exp $
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 "accessibility", "Ports to help disabled users.",
82 "afterstep", "Ports to support the AfterStep window manager.",
83 "applications", "User application software.",
84 "archivers", "Utilities for archiving and unarchiving data.",
85 "astro", "Applications related to astronomy.",
86 "audio", "Audio utilities - most require a supported sound card.",
87 "benchmarks", "Utilities for measuring system performance.",
88 "biology", "Software related to biology.",
89 "cad", "Computer Aided Design utilities.",
90 "chinese", "Ported software for the Chinese market.",
91 "comms", "Communications utilities.",
92 "converters", "Format conversion utilities.",
93 "databases", "Database software.",
94 "deskutils", "Various Desktop utilities.",
95 "devel", "Software development utilities and libraries.",
96 "documentation", "Document preparation utilities.",
97 "editors", "Common text editors.",
98 "elisp", "Things related to Emacs Lisp.",
99 "emulators", "Utilities for emulating other OS types.",
100 "finance", "Monetary, financial and related applications.",
101 "french", "Ported software for French countries.",
102 "ftp", "FTP client and server utilities.",
103 "games", "Various and sundry amusements.",
104 "german", "Ported software for Germanic countries.",
105 "gnome", "Components of the Gnome Desktop environment.",
106 "graphics", "Graphics libraries and utilities.",
107 "haskell", "Software related to the Haskell language.",
108 "hebrew", "Ported software for Hebrew language.",
109 "hungarian", "Ported software for the Hungarian market.",
110 "ipv6", "IPv6 related software.",
111 "irc", "Internet Relay Chat utilities.",
112 "japanese", "Ported software for the Japanese market.",
113 "java", "Java language support.",
114 "kde", "Software for the K Desktop Environment.",
115 "korean", "Ported software for the Korean market.",
116 "lang", "Computer languages.",
117 "languages", "Computer languages.",
118 "libraries", "Software development libraries.",
119 "linux", "Linux programs that can be run under binary compatibility.",
120 "mail", "Electronic mail packages and utilities.",
121 "math", "Mathematical computation software.",
122 "mbone", "Applications and utilities for the MBONE.",
123 "misc", "Miscellaneous utilities.",
124 "multimedia", "Multimedia software.",
125 "net", "Networking utilities.",
126 "news", "USENET News support software.",
127 "numeric", "Mathematical computation software.",
128 "offix", "An office automation suite of sorts.",
129 "orphans", "Packages without a home elsewhere.",
130 "palm", "Software support for the Palm(tm) series.",
131 "parallel", "Applications dealing with parallelism in computing.",
132 "perl5", "Utilities/modules for the PERL5 language.",
133 "picobsd", "Ports to support PicoBSD.",
134 "pilot", "Software support for the 3Com Palm Pilot(tm) series.",
135 "plan9", "Software from the Plan9 operating system.",
136 "portuguese", "Ported software for the Portuguese market.",
137 "print", "Utilities for dealing with printing.",
138 "printing", "Utilities for dealing with printing.",
139 "programming", "Software development utilities and libraries.",
140 "python", "Software related to the Python language.",
141 "ruby", "Software related to the Ruby language.",
142 "russian", "Ported software for the Russian market.",
143 "science", "Scientific software.",
144 "security", "System security software.",
145 "shells", "Various shells (tcsh, bash, etc).",
146 "sysutils", "Various system utilities.",
147 "tcl75", "TCL v7.5 and packages that depend on it.",
148 "tcl76", "TCL v7.6 and packages that depend on it.",
149 "tcl80", "TCL v8.0 and packages that depend on it.",
150 "tcl81", "TCL v8.1 and packages that depend on it.",
151 "tcl82", "TCL v8.2 and packages that depend on it.",
152 "tcl83", "TCL v8.3 and packages that depend on it.",
153 "textproc", "Text processing/search utilities.",
154 "tk41", "Tk4.1 and packages that depend on it.",
155 "tk42", "Tk4.2 and packages that depend on it.",
156 "tk80", "Tk8.0 and packages that depend on it.",
157 "tk81", "Tk8.1 and packages that depend on it.",
158 "tk82", "Tk8.2 and packages that depend on it.",
159 "tk83", "Tk8.3 and packages that depend on it.",
160 "tkstep80", "tkstep wm and packages that depend on it.",
161 "troff", "TROFF text formatting utilities.",
162 "ukrainian", "Ported software for the Ukrainian market.",
163 "vietnamese", "Ported software for the Vietnamese market.",
164 "windowmaker", "Ports to support the WindowMaker window manager.",
165 "www", "WEB utilities (browers, HTTP servers, etc).",
166 "x11", "X Window System based utilities.",
167 "x11-clocks", "X Window System based clocks.",
168 "x11-fm", "X Window System based file managers.",
169 "x11-fonts", "X Window System fonts and font utilities.",
170 "x11-servers", "X Window System servers.",
171 "x11-toolkits", "X Window System based development toolkits.",
172 "x11-wm", "X Window System window managers.",
173 "zope", "Software related to the Zope platform.",
178 fetch_desc(char *name)
182 for (i = 0; descrs[i]; i += 2) {
183 if (!strcmp(descrs[i], name))
184 return descrs[i + 1];
186 return "No description provided";
190 new_pkg_node(char *name, node_type type)
192 PkgNodePtr tmp = safe_malloc(sizeof(PkgNode));
194 tmp->name = _strdup(name);
204 for (i = 0; buf[i]; i++)
205 if (buf[i] == '\t' || buf[i] == '\n')
211 new_index(char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *deps, int volume)
213 IndexEntryPtr tmp = safe_malloc(sizeof(IndexEntry));
215 tmp->name = _strdup(name);
216 tmp->path = _strdup(pathto);
217 tmp->prefix = _strdup(prefix);
218 tmp->comment = _strdup(comment);
219 tmp->descrfile = strip(_strdup(descr));
220 tmp->maintainer = _strdup(maint);
221 tmp->deps = _strdup(deps);
223 tmp->installed = package_exists(name);
224 tmp->volume = volume;
229 index_register(PkgNodePtr top, char *where, IndexEntryPtr ptr)
233 for (q = NULL, p = top->kids; p; p = p->next) {
234 if (!strcmp(p->name, where)) {
240 /* Add new category */
241 q = new_pkg_node(where, PLACE);
242 q->desc = fetch_desc(where);
246 p = new_pkg_node(ptr->name, PACKAGE);
247 p->desc = ptr->comment;
254 copy_to_sep(char *to, char *from, int sep)
258 tok = strchr(from, sep);
265 return tok + 1 - from;
269 readline(FILE *fp, char *buf, int max)
274 while ((rv = fread(&ch, 1, 1, fp)) == 1 && ch != '\n' && i < max)
282 * XXX - this function should do error checking, and skip corrupted INDEX
283 * lines without a set number of '|' delimited fields.
287 index_parse(FILE *fp, char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *cats, char *rdeps, int *volume)
289 char line[10240 + 2048 * 7];
295 i = readline(fp, line, sizeof line);
299 cp += copy_to_sep(name, cp, '|');
300 cp += copy_to_sep(pathto, cp, '|');
301 cp += copy_to_sep(prefix, cp, '|');
302 cp += copy_to_sep(comment, cp, '|');
303 cp += copy_to_sep(descr, cp, '|');
304 cp += copy_to_sep(maint, cp, '|');
305 cp += copy_to_sep(cats, cp, '|');
306 cp += copy_to_sep(junk, cp, '|'); /* build deps - not used */
307 cp += copy_to_sep(rdeps, cp, '|');
309 cp += copy_to_sep(junk, cp, '|'); /* url - not used */
311 strncpy(junk, cp, 1023);
316 cp += copy_to_sep(volstr, cp, '|'); /* media volume */
318 strncpy(volstr, cp, 1023);
320 *volume = atoi(volstr);
325 index_read(FILE *fp, PkgNodePtr papa)
327 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[2048 * 8];
331 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps, &volume) != EOF) {
332 char *cp, *cp2, tmp[1024];
335 idx = new_index(name, pathto, prefix, comment, descr, maint, deps, volume);
336 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
337 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
339 index_register(papa, cp, idx);
341 index_register(papa, cp, idx);
343 /* Add to special "All" category */
344 index_register(papa, "All", idx);
347 /* Adjust dependency counts */
348 for (i = papa->kids; i != NULL; i = i->next)
349 if (strcmp(i->name, "All") == 0)
351 for (i = i->kids; i != NULL; i = i->next)
352 if (((IndexEntryPtr)i->data)->installed)
353 index_recorddeps(TRUE, papa, i->data);
359 index_init(PkgNodePtr top, PkgNodePtr plist)
362 top->next = top->kids = NULL;
363 top->name = "Package Selection";
365 top->desc = fetch_desc(top->name);
369 plist->next = plist->kids = NULL;
370 plist->name = "Package Targets";
372 plist->desc = fetch_desc(plist->name);
378 index_print(PkgNodePtr top, int level)
383 for (i = 0; i < level; i++) putchar('\t');
384 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
385 for (i = 0; i < level; i++) putchar('\t');
386 printf("desc: %s\n", top->desc);
388 index_print(top->kids, level + 1);
393 /* Swap one node for another */
395 swap_nodes(PkgNodePtr a, PkgNodePtr b)
406 /* Use a disgustingly simplistic bubble sort to put our lists in order */
408 index_sort(PkgNodePtr top)
412 /* Sort everything at the top level */
413 for (p = top->kids; p; p = p->next) {
414 for (q = top->kids; q; q = q->next) {
415 if (q->next && strcmp(q->name, q->next->name) > 0)
416 swap_nodes(q, q->next);
420 /* Now sub-sort everything n levels down */
421 for (p = top->kids; p; p = p->next) {
427 /* Delete an entry out of the list it's in (only the plist, at present) */
429 index_delete(PkgNodePtr n)
432 PkgNodePtr p = n->next;
437 else /* Kludgy end sentinal */
442 * Search for a given node by name, returning the category in if
446 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
450 for (p = top->kids; p && p->name; p = p->next) {
451 if (p->type == PACKAGE) {
452 /* If tp == NULL, we're looking for an exact package match */
453 if (!tp && !strcmp(p->name, str))
456 /* If tp, we're looking for both a package and a pointer to the place it's in */
457 if (tp && !strncmp(p->name, str, strlen(str))) {
463 /* The usual recursion-out-of-laziness ploy */
464 if ((sp = index_search(p, str, tp)) != NULL)
474 pkg_checked(dialogMenuItem *self)
476 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
477 PkgNodePtr kp = self->data, plist = lists->plist;
480 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
481 if (kp->type == PACKAGE && plist) {
482 IndexEntryPtr ie = kp->data;
485 markD = ie->depc > 0; /* needed as dependency */
486 markX = i || ie->installed; /* selected or installed */
487 self->mark = markX ? 'X' : 'D';
488 return markD || markX;
494 pkg_fire(dialogMenuItem *self)
497 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
498 PkgNodePtr sp, kp = self->data, plist = lists->plist;
502 else if (kp->type == PACKAGE) {
503 IndexEntryPtr ie = kp->data;
505 sp = index_search(plist, kp->name, NULL);
506 /* Not already selected? */
508 if (!ie->installed) {
509 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
512 np->next = plist->kids;
514 index_recorddeps(TRUE, lists->root, ie);
515 msgInfo("Added %s to selection list", kp->name);
517 else if (ie->depc == 0) {
518 if (!msgNoYes("Do you really want to delete %s from the system?", kp->name)) {
519 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
520 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
524 index_recorddeps(FALSE, lists->root, ie);
529 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
530 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
533 index_recorddeps(FALSE, lists->root, ie);
534 msgInfo("Removed %s from selection list", kp->name);
538 /* Mark menu for redraw if we had dependencies */
539 if (strlen(ie->deps) > 0)
542 else { /* Not a package, must be a directory */
546 index_menu(lists->root, kp, plist, &p, &s);
547 ret = DITEM_SUCCESS | DITEM_CONTINUE;
553 pkg_selected(dialogMenuItem *self, int is_selected)
555 PkgNodePtr kp = self->data;
557 if (!is_selected || kp->type != PACKAGE)
559 msgInfo("%s", kp->desc);
563 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
565 struct ListPtrs lists;
566 int n, rval, maxname;
569 dialogMenuItem *nitems;
581 /* Figure out if this menu is full of "leaves" or "branches" */
582 for (kp = top->kids; kp && kp->name; kp = kp->next) {
586 if (kp->type == PACKAGE && plist) {
588 if ((len = strlen(kp->name)) > maxname)
593 msgConfirm("The %s menu is empty.", top->name);
594 return DITEM_LEAVE_MENU;
604 if (!hasPackages && plist) {
605 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
606 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
608 while (kp && kp->name) {
610 IndexEntryPtr ie = kp->data;
612 /* Brutally adjust description to fit in menu */
613 if (kp->type == PACKAGE)
614 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
616 SAFE_STRCPY(buf, kp->desc);
617 if (strlen(buf) > (_MAX_DESC - maxname))
618 buf[_MAX_DESC - maxname] = '\0';
619 nitems = item_add(nitems, kp->name, (char *)buf, pkg_checked,
620 pkg_fire, pkg_selected, kp, (int *)(&lists),
625 /* NULL delimiter so item_free() knows when to stop later */
626 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
630 dialog_clear_norefresh();
632 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
634 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
635 if (rval == -1 && plist) {
640 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
641 PkgNodePtr p = index_search(top, cp, &menu);
646 /* These need to be set to point at the found item, actually. Hmmm! */
648 index_menu(root, menu, plist, &pos, &scroll);
651 msgConfirm("Search string: %s yielded no hits.", cp);
655 items_free(nitems, &curr, &max);
657 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
662 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
664 int status = DITEM_SUCCESS;
666 IndexEntryPtr id = who->data;
667 WINDOW *w = savescr();
670 * Short-circuit the package dependency checks. We're already
671 * maintaining a data structure of installed packages, so if a
672 * package is already installed, don't try to check to make sure
673 * that all of its dependencies are installed. At best this
674 * wastes a ton of cycles and can cause minor delays between
675 * package extraction. At worst it can cause an infinite loop with
676 * a certain faulty INDEX file.
679 if (id->installed == 1)
680 return DITEM_SUCCESS;
683 * What if the package is not available on the current media volume?
686 if (id->volume != dev->volume) {
687 if (!msgYesNo("This is disc #%d. Package %s is on disc #%d\n"
688 "Would you like to switch discs now?\n", dev->volume,
689 id->name, id->volume)) {
690 DEVICE_SHUTDOWN(mediaDevice);
691 msgConfirm("Please remove disc #%d from your drive, and add disc #%d\n",
692 dev->volume, id->volume);
693 DEVICE_INIT(mediaDevice);
694 /* XXX, at this point we check to see if this is the
695 * correct disc, and if not, we loop */
697 return DITEM_FAILURE;
701 if (id && id->deps && strlen(id->deps)) {
702 char t[2048 * 8], *cp, *cp2;
704 SAFE_STRCPY(t, id->deps);
706 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
707 if ((cp2 = index(cp, ' ')) != NULL)
709 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
710 status = index_extract(dev, top, tmp2, TRUE);
711 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
712 if (variable_get(VAR_NO_CONFIRM))
713 msgNotify("Loading of dependent package %s failed", cp);
715 msgConfirm("Loading of dependent package %s failed", cp);
718 else if (!package_exists(cp)) {
719 if (variable_get(VAR_NO_CONFIRM))
720 msgNotify("Warning: %s is a required package but was not found.", cp);
722 msgConfirm("Warning: %s is a required package but was not found.", cp);
730 /* Done with the deps? Load the real m'coy */
731 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
732 status = package_extract(dev, who->name, depended);
733 if (DITEM_STATUS(status) == DITEM_SUCCESS)
741 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
743 char depends[1024 * 16], *space, *todo;
745 IndexEntryPtr found_ie;
747 SAFE_STRCPY(depends, ie->deps);
748 for (todo = depends; todo != NULL; ) {
749 space = index(todo, ' ');
753 if (strlen(todo) > 0) { /* only non-empty dependencies */
754 found = index_search(root, todo, NULL);
756 found_ie = found->data;
771 static Boolean index_initted;
773 /* Read and initialize global index */
775 index_initialize(char *path)
780 if (!index_initted) {
782 dialog_clear_norefresh();
785 if (!mediaVerify()) {
787 return DITEM_FAILURE;
790 /* Does it move when you kick it? */
791 if (!DEVICE_INIT(mediaDevice)) {
793 return DITEM_FAILURE;
796 dialog_clear_norefresh();
797 msgNotify("Attempting to fetch %s file from selected media.", path);
798 fp = DEVICE_GET(mediaDevice, path, TRUE);
800 msgConfirm("Unable to get packages/INDEX file from selected media.\n\n"
801 "This may be because the packages collection is not available\n"
802 "on the distribution media you've chosen, most likely an FTP site\n"
803 "without the packages collection mirrored. Please verify that\n"
804 "your media, or your path to the media, is correct and try again.");
805 DEVICE_SHUTDOWN(mediaDevice);
807 return DITEM_FAILURE;
809 dialog_clear_norefresh();
810 msgNotify("Located INDEX, now reading package data from it...");
811 index_init(&Top, &Plist);
812 if (index_read(fp, &Top)) {
813 msgConfirm("I/O or format error on packages/INDEX file.\n"
814 "Please verify media (or path to media) and try again.");
817 return DITEM_FAILURE;
821 index_initted = TRUE;
824 return DITEM_SUCCESS;