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 $
8 * $DragonFly: src/release/sysinstall/Attic/index.c,v 1.2 2003/06/17 04:27:21 dillon Exp $
11 * Jordan Hubbard. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer,
18 * verbatim and that no modifications are made prior to this
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
24 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44 #include "sysinstall.h"
46 /* Macros and magic values */
50 /* A structure holding the root, top and plist pointer at once */
53 PkgNodePtr root; /* root of tree */
54 PkgNodePtr top; /* part of tree we handle */
55 PkgNodePtr plist; /* list of selected packages */
57 typedef struct ListPtrs* ListPtrsPtr;
59 static void index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie);
61 /* Shared between index_initialize() and the various clients of it */
68 return ptr ? strdup(ptr) : NULL;
71 static char *descrs[] = {
72 "Package Selection", "To mark a package, move to it and press SPACE. If the package is\n"
73 "already marked, it will be unmarked or deleted (if installed).\n"
74 "Items marked with a `D' are dependencies which will be auto-loaded.\n"
75 "To search for a package by name, press ESC. To select a category,\n"
76 "press RETURN. NOTE: The All category selection creates a very large\n"
77 "submenu! If you select it, please be patient while it comes up.",
78 "Package Targets", "These are the packages you've selected for extraction.\n\n"
79 "If you're sure of these choices, select OK.\n"
80 "If not, select Cancel to go back to the package selection menu.\n",
81 "All", "All available packages in all categories.",
82 "accessibility", "Ports to help disabled users.",
83 "afterstep", "Ports to support the AfterStep window manager.",
84 "applications", "User application software.",
85 "archivers", "Utilities for archiving and unarchiving data.",
86 "astro", "Applications related to astronomy.",
87 "audio", "Audio utilities - most require a supported sound card.",
88 "benchmarks", "Utilities for measuring system performance.",
89 "biology", "Software related to biology.",
90 "cad", "Computer Aided Design utilities.",
91 "chinese", "Ported software for the Chinese market.",
92 "comms", "Communications utilities.",
93 "converters", "Format conversion utilities.",
94 "databases", "Database software.",
95 "deskutils", "Various Desktop utilities.",
96 "devel", "Software development utilities and libraries.",
97 "documentation", "Document preparation utilities.",
98 "editors", "Common text editors.",
99 "elisp", "Things related to Emacs Lisp.",
100 "emulators", "Utilities for emulating other OS types.",
101 "finance", "Monetary, financial and related applications.",
102 "french", "Ported software for French countries.",
103 "ftp", "FTP client and server utilities.",
104 "games", "Various and sundry amusements.",
105 "german", "Ported software for Germanic countries.",
106 "gnome", "Components of the Gnome Desktop environment.",
107 "graphics", "Graphics libraries and utilities.",
108 "haskell", "Software related to the Haskell language.",
109 "hebrew", "Ported software for Hebrew language.",
110 "hungarian", "Ported software for the Hungarian market.",
111 "ipv6", "IPv6 related software.",
112 "irc", "Internet Relay Chat utilities.",
113 "japanese", "Ported software for the Japanese market.",
114 "java", "Java language support.",
115 "kde", "Software for the K Desktop Environment.",
116 "korean", "Ported software for the Korean market.",
117 "lang", "Computer languages.",
118 "languages", "Computer languages.",
119 "libraries", "Software development libraries.",
120 "linux", "Linux programs that can be run under binary compatibility.",
121 "mail", "Electronic mail packages and utilities.",
122 "math", "Mathematical computation software.",
123 "mbone", "Applications and utilities for the MBONE.",
124 "misc", "Miscellaneous utilities.",
125 "multimedia", "Multimedia software.",
126 "net", "Networking utilities.",
127 "news", "USENET News support software.",
128 "numeric", "Mathematical computation software.",
129 "offix", "An office automation suite of sorts.",
130 "orphans", "Packages without a home elsewhere.",
131 "palm", "Software support for the Palm(tm) series.",
132 "parallel", "Applications dealing with parallelism in computing.",
133 "perl5", "Utilities/modules for the PERL5 language.",
134 "picobsd", "Ports to support PicoBSD.",
135 "pilot", "Software support for the 3Com Palm Pilot(tm) series.",
136 "plan9", "Software from the Plan9 operating system.",
137 "portuguese", "Ported software for the Portuguese market.",
138 "print", "Utilities for dealing with printing.",
139 "printing", "Utilities for dealing with printing.",
140 "programming", "Software development utilities and libraries.",
141 "python", "Software related to the Python language.",
142 "ruby", "Software related to the Ruby language.",
143 "russian", "Ported software for the Russian market.",
144 "science", "Scientific software.",
145 "security", "System security software.",
146 "shells", "Various shells (tcsh, bash, etc).",
147 "sysutils", "Various system utilities.",
148 "tcl75", "TCL v7.5 and packages that depend on it.",
149 "tcl76", "TCL v7.6 and packages that depend on it.",
150 "tcl80", "TCL v8.0 and packages that depend on it.",
151 "tcl81", "TCL v8.1 and packages that depend on it.",
152 "tcl82", "TCL v8.2 and packages that depend on it.",
153 "tcl83", "TCL v8.3 and packages that depend on it.",
154 "textproc", "Text processing/search utilities.",
155 "tk41", "Tk4.1 and packages that depend on it.",
156 "tk42", "Tk4.2 and packages that depend on it.",
157 "tk80", "Tk8.0 and packages that depend on it.",
158 "tk81", "Tk8.1 and packages that depend on it.",
159 "tk82", "Tk8.2 and packages that depend on it.",
160 "tk83", "Tk8.3 and packages that depend on it.",
161 "tkstep80", "tkstep wm and packages that depend on it.",
162 "troff", "TROFF text formatting utilities.",
163 "ukrainian", "Ported software for the Ukrainian market.",
164 "vietnamese", "Ported software for the Vietnamese market.",
165 "windowmaker", "Ports to support the WindowMaker window manager.",
166 "www", "WEB utilities (browers, HTTP servers, etc).",
167 "x11", "X Window System based utilities.",
168 "x11-clocks", "X Window System based clocks.",
169 "x11-fm", "X Window System based file managers.",
170 "x11-fonts", "X Window System fonts and font utilities.",
171 "x11-servers", "X Window System servers.",
172 "x11-toolkits", "X Window System based development toolkits.",
173 "x11-wm", "X Window System window managers.",
174 "zope", "Software related to the Zope platform.",
179 fetch_desc(char *name)
183 for (i = 0; descrs[i]; i += 2) {
184 if (!strcmp(descrs[i], name))
185 return descrs[i + 1];
187 return "No description provided";
191 new_pkg_node(char *name, node_type type)
193 PkgNodePtr tmp = safe_malloc(sizeof(PkgNode));
195 tmp->name = _strdup(name);
205 for (i = 0; buf[i]; i++)
206 if (buf[i] == '\t' || buf[i] == '\n')
212 new_index(char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *deps, int volume)
214 IndexEntryPtr tmp = safe_malloc(sizeof(IndexEntry));
216 tmp->name = _strdup(name);
217 tmp->path = _strdup(pathto);
218 tmp->prefix = _strdup(prefix);
219 tmp->comment = _strdup(comment);
220 tmp->descrfile = strip(_strdup(descr));
221 tmp->maintainer = _strdup(maint);
222 tmp->deps = _strdup(deps);
224 tmp->installed = package_exists(name);
225 tmp->volume = volume;
230 index_register(PkgNodePtr top, char *where, IndexEntryPtr ptr)
234 for (q = NULL, p = top->kids; p; p = p->next) {
235 if (!strcmp(p->name, where)) {
241 /* Add new category */
242 q = new_pkg_node(where, PLACE);
243 q->desc = fetch_desc(where);
247 p = new_pkg_node(ptr->name, PACKAGE);
248 p->desc = ptr->comment;
255 copy_to_sep(char *to, char *from, int sep)
259 tok = strchr(from, sep);
266 return tok + 1 - from;
270 readline(FILE *fp, char *buf, int max)
275 while ((rv = fread(&ch, 1, 1, fp)) == 1 && ch != '\n' && i < max)
283 * XXX - this function should do error checking, and skip corrupted INDEX
284 * lines without a set number of '|' delimited fields.
288 index_parse(FILE *fp, char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *cats, char *rdeps, int *volume)
290 char line[10240 + 2048 * 7];
296 i = readline(fp, line, sizeof line);
300 cp += copy_to_sep(name, cp, '|');
301 cp += copy_to_sep(pathto, cp, '|');
302 cp += copy_to_sep(prefix, cp, '|');
303 cp += copy_to_sep(comment, cp, '|');
304 cp += copy_to_sep(descr, cp, '|');
305 cp += copy_to_sep(maint, cp, '|');
306 cp += copy_to_sep(cats, cp, '|');
307 cp += copy_to_sep(junk, cp, '|'); /* build deps - not used */
308 cp += copy_to_sep(rdeps, cp, '|');
310 cp += copy_to_sep(junk, cp, '|'); /* url - not used */
312 strncpy(junk, cp, 1023);
317 cp += copy_to_sep(volstr, cp, '|'); /* media volume */
319 strncpy(volstr, cp, 1023);
321 *volume = atoi(volstr);
326 index_read(FILE *fp, PkgNodePtr papa)
328 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[2048 * 8];
332 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps, &volume) != EOF) {
333 char *cp, *cp2, tmp[1024];
336 idx = new_index(name, pathto, prefix, comment, descr, maint, deps, volume);
337 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
338 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
340 index_register(papa, cp, idx);
342 index_register(papa, cp, idx);
344 /* Add to special "All" category */
345 index_register(papa, "All", idx);
348 /* Adjust dependency counts */
349 for (i = papa->kids; i != NULL; i = i->next)
350 if (strcmp(i->name, "All") == 0)
352 for (i = i->kids; i != NULL; i = i->next)
353 if (((IndexEntryPtr)i->data)->installed)
354 index_recorddeps(TRUE, papa, i->data);
360 index_init(PkgNodePtr top, PkgNodePtr plist)
363 top->next = top->kids = NULL;
364 top->name = "Package Selection";
366 top->desc = fetch_desc(top->name);
370 plist->next = plist->kids = NULL;
371 plist->name = "Package Targets";
373 plist->desc = fetch_desc(plist->name);
379 index_print(PkgNodePtr top, int level)
384 for (i = 0; i < level; i++) putchar('\t');
385 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
386 for (i = 0; i < level; i++) putchar('\t');
387 printf("desc: %s\n", top->desc);
389 index_print(top->kids, level + 1);
394 /* Swap one node for another */
396 swap_nodes(PkgNodePtr a, PkgNodePtr b)
407 /* Use a disgustingly simplistic bubble sort to put our lists in order */
409 index_sort(PkgNodePtr top)
413 /* Sort everything at the top level */
414 for (p = top->kids; p; p = p->next) {
415 for (q = top->kids; q; q = q->next) {
416 if (q->next && strcmp(q->name, q->next->name) > 0)
417 swap_nodes(q, q->next);
421 /* Now sub-sort everything n levels down */
422 for (p = top->kids; p; p = p->next) {
428 /* Delete an entry out of the list it's in (only the plist, at present) */
430 index_delete(PkgNodePtr n)
433 PkgNodePtr p = n->next;
438 else /* Kludgy end sentinal */
443 * Search for a given node by name, returning the category in if
447 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
451 for (p = top->kids; p && p->name; p = p->next) {
452 if (p->type == PACKAGE) {
453 /* If tp == NULL, we're looking for an exact package match */
454 if (!tp && !strcmp(p->name, str))
457 /* If tp, we're looking for both a package and a pointer to the place it's in */
458 if (tp && !strncmp(p->name, str, strlen(str))) {
464 /* The usual recursion-out-of-laziness ploy */
465 if ((sp = index_search(p, str, tp)) != NULL)
475 pkg_checked(dialogMenuItem *self)
477 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
478 PkgNodePtr kp = self->data, plist = lists->plist;
481 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
482 if (kp->type == PACKAGE && plist) {
483 IndexEntryPtr ie = kp->data;
486 markD = ie->depc > 0; /* needed as dependency */
487 markX = i || ie->installed; /* selected or installed */
488 self->mark = markX ? 'X' : 'D';
489 return markD || markX;
495 pkg_fire(dialogMenuItem *self)
498 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
499 PkgNodePtr sp, kp = self->data, plist = lists->plist;
503 else if (kp->type == PACKAGE) {
504 IndexEntryPtr ie = kp->data;
506 sp = index_search(plist, kp->name, NULL);
507 /* Not already selected? */
509 if (!ie->installed) {
510 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
513 np->next = plist->kids;
515 index_recorddeps(TRUE, lists->root, ie);
516 msgInfo("Added %s to selection list", kp->name);
518 else if (ie->depc == 0) {
519 if (!msgNoYes("Do you really want to delete %s from the system?", kp->name)) {
520 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
521 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
525 index_recorddeps(FALSE, lists->root, ie);
530 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
531 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
534 index_recorddeps(FALSE, lists->root, ie);
535 msgInfo("Removed %s from selection list", kp->name);
539 /* Mark menu for redraw if we had dependencies */
540 if (strlen(ie->deps) > 0)
543 else { /* Not a package, must be a directory */
547 index_menu(lists->root, kp, plist, &p, &s);
548 ret = DITEM_SUCCESS | DITEM_CONTINUE;
554 pkg_selected(dialogMenuItem *self, int is_selected)
556 PkgNodePtr kp = self->data;
558 if (!is_selected || kp->type != PACKAGE)
560 msgInfo("%s", kp->desc);
564 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
566 struct ListPtrs lists;
567 int n, rval, maxname;
570 dialogMenuItem *nitems;
582 /* Figure out if this menu is full of "leaves" or "branches" */
583 for (kp = top->kids; kp && kp->name; kp = kp->next) {
587 if (kp->type == PACKAGE && plist) {
589 if ((len = strlen(kp->name)) > maxname)
594 msgConfirm("The %s menu is empty.", top->name);
595 return DITEM_LEAVE_MENU;
605 if (!hasPackages && plist) {
606 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
607 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
609 while (kp && kp->name) {
611 IndexEntryPtr ie = kp->data;
613 /* Brutally adjust description to fit in menu */
614 if (kp->type == PACKAGE)
615 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
617 SAFE_STRCPY(buf, kp->desc);
618 if (strlen(buf) > (_MAX_DESC - maxname))
619 buf[_MAX_DESC - maxname] = '\0';
620 nitems = item_add(nitems, kp->name, (char *)buf, pkg_checked,
621 pkg_fire, pkg_selected, kp, (int *)(&lists),
626 /* NULL delimiter so item_free() knows when to stop later */
627 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
631 dialog_clear_norefresh();
633 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
635 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
636 if (rval == -1 && plist) {
641 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
642 PkgNodePtr p = index_search(top, cp, &menu);
647 /* These need to be set to point at the found item, actually. Hmmm! */
649 index_menu(root, menu, plist, &pos, &scroll);
652 msgConfirm("Search string: %s yielded no hits.", cp);
656 items_free(nitems, &curr, &max);
658 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
663 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
665 int status = DITEM_SUCCESS;
667 IndexEntryPtr id = who->data;
668 WINDOW *w = savescr();
671 * Short-circuit the package dependency checks. We're already
672 * maintaining a data structure of installed packages, so if a
673 * package is already installed, don't try to check to make sure
674 * that all of its dependencies are installed. At best this
675 * wastes a ton of cycles and can cause minor delays between
676 * package extraction. At worst it can cause an infinite loop with
677 * a certain faulty INDEX file.
680 if (id->installed == 1)
681 return DITEM_SUCCESS;
684 * What if the package is not available on the current media volume?
687 if (id->volume != dev->volume) {
688 if (!msgYesNo("This is disc #%d. Package %s is on disc #%d\n"
689 "Would you like to switch discs now?\n", dev->volume,
690 id->name, id->volume)) {
691 DEVICE_SHUTDOWN(mediaDevice);
692 msgConfirm("Please remove disc #%d from your drive, and add disc #%d\n",
693 dev->volume, id->volume);
694 DEVICE_INIT(mediaDevice);
695 /* XXX, at this point we check to see if this is the
696 * correct disc, and if not, we loop */
698 return DITEM_FAILURE;
702 if (id && id->deps && strlen(id->deps)) {
703 char t[2048 * 8], *cp, *cp2;
705 SAFE_STRCPY(t, id->deps);
707 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
708 if ((cp2 = index(cp, ' ')) != NULL)
710 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
711 status = index_extract(dev, top, tmp2, TRUE);
712 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
713 if (variable_get(VAR_NO_CONFIRM))
714 msgNotify("Loading of dependent package %s failed", cp);
716 msgConfirm("Loading of dependent package %s failed", cp);
719 else if (!package_exists(cp)) {
720 if (variable_get(VAR_NO_CONFIRM))
721 msgNotify("Warning: %s is a required package but was not found.", cp);
723 msgConfirm("Warning: %s is a required package but was not found.", cp);
731 /* Done with the deps? Load the real m'coy */
732 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
733 status = package_extract(dev, who->name, depended);
734 if (DITEM_STATUS(status) == DITEM_SUCCESS)
742 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
744 char depends[1024 * 16], *space, *todo;
746 IndexEntryPtr found_ie;
748 SAFE_STRCPY(depends, ie->deps);
749 for (todo = depends; todo != NULL; ) {
750 space = index(todo, ' ');
754 if (strlen(todo) > 0) { /* only non-empty dependencies */
755 found = index_search(root, todo, NULL);
757 found_ie = found->data;
772 static Boolean index_initted;
774 /* Read and initialize global index */
776 index_initialize(char *path)
781 if (!index_initted) {
783 dialog_clear_norefresh();
786 if (!mediaVerify()) {
788 return DITEM_FAILURE;
791 /* Does it move when you kick it? */
792 if (!DEVICE_INIT(mediaDevice)) {
794 return DITEM_FAILURE;
797 dialog_clear_norefresh();
798 msgNotify("Attempting to fetch %s file from selected media.", path);
799 fp = DEVICE_GET(mediaDevice, path, TRUE);
801 msgConfirm("Unable to get packages/INDEX file from selected media.\n\n"
802 "This may be because the packages collection is not available\n"
803 "on the distribution media you've chosen, most likely an FTP site\n"
804 "without the packages collection mirrored. Please verify that\n"
805 "your media, or your path to the media, is correct and try again.");
806 DEVICE_SHUTDOWN(mediaDevice);
808 return DITEM_FAILURE;
810 dialog_clear_norefresh();
811 msgNotify("Located INDEX, now reading package data from it...");
812 index_init(&Top, &Plist);
813 if (index_read(fp, &Top)) {
814 msgConfirm("I/O or format error on packages/INDEX file.\n"
815 "Please verify media (or path to media) and try again.");
818 return DITEM_FAILURE;
822 index_initted = TRUE;
825 return DITEM_SUCCESS;