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.
8 * Jordan Hubbard. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer,
15 * verbatim and that no modifications are made prior to this
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
21 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42 #include "sysinstall.h"
44 /* Macros and magic values */
48 /* A structure holding the root, top and plist pointer at once */
51 PkgNodePtr root; /* root of tree */
52 PkgNodePtr top; /* part of tree we handle */
53 PkgNodePtr plist; /* list of selected packages */
55 typedef struct ListPtrs* ListPtrsPtr;
57 static void index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie);
59 /* Shared between index_initialize() and the various clients of it */
66 return ptr ? strdup(ptr) : NULL;
69 static char *descrs[] = {
70 "Package Selection", "To mark a package, move to it and press SPACE. If the package is\n"
71 "already marked, it will be unmarked or deleted (if installed).\n"
72 "Items marked with a `D' are dependencies which will be auto-loaded.\n"
73 "To search for a package by name, press ESC. To select a category,\n"
74 "press RETURN. NOTE: The All category selection creates a very large\n"
75 "submenu! If you select it, please be patient while it comes up.",
76 "Package Targets", "These are the packages you've selected for extraction.\n\n"
77 "If you're sure of these choices, select OK.\n"
78 "If not, select Cancel to go back to the package selection menu.\n",
79 "All", "All available packages in all categories.",
80 "accessibility", "Ports to help disabled users.",
81 "afterstep", "Ports to support the AfterStep window manager.",
82 "arabic", "Ported software for Arab countries.",
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 "dns", "Domain Name Service tools.",
96 "editors", "Editors.",
97 "elisp", "Things related to Emacs Lisp.",
98 "emulators", "Utilities for emulating other operating systems.",
99 "finance", "Monetary, financial and related applications.",
100 "french", "Ported software for French countries.",
101 "ftp", "FTP client and server utilities.",
102 "games", "Various and sundry amusements.",
103 "german", "Ported software for Germanic countries.",
104 "gnome", "Components of the Gnome Desktop environment.",
105 "graphics", "Graphics libraries and utilities.",
106 "haskell", "Software related to the Haskell language.",
107 "hamradio", "Software for amateur radio.",
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 "linux", "Linux programs that can run under binary compatibility.",
118 "lisp", "Software related to the Lisp language.",
119 "mail", "Electronic mail packages and utilities.",
120 "math", "Mathematical computation software.",
121 "mbone", "Applications and utilities for the MBONE.",
122 "misc", "Miscellaneous utilities.",
123 "multimedia", "Multimedia software.",
124 "net", "Networking utilities.",
125 "net-im", "Instant messaging software.",
126 "net-mgmt", "Network management tools.",
127 "net-p2p", "Peer to peer network applications.",
128 "news", "USENET News support software.",
129 "palm", "Software support for the Palm(tm) series.",
130 "parallel", "Applications dealing with parallelism in computing.",
131 "pear", "Software related to the Pear PHP framework.",
132 "perl5", "Utilities/modules for the PERL5 language.",
133 "plan9", "Software from the Plan9 operating system.",
134 "polish", "Ported software for the Polish market.",
135 "portuguese", "Ported software for the Portuguese market.",
136 "print", "Utilities for dealing with printing.",
137 "python", "Software related to the Python language.",
138 "ruby", "Software related to the Ruby language.",
139 "rubygems", "Ports of RubyGems packages.",
140 "russian", "Ported software for the Russian market.",
141 "scheme", "Software related to the Scheme language.",
142 "science", "Scientific software.",
143 "security", "System security software.",
144 "shells", "Various shells (tcsh, bash, etc).",
145 "sysutils", "Various system utilities.",
146 "tcl80", "TCL v8.0 and packages that depend on it.",
147 "tcl81", "TCL v8.1 and packages that depend on it.",
148 "tcl82", "TCL v8.2 and packages that depend on it.",
149 "tcl83", "TCL v8.3 and packages that depend on it.",
150 "tcl84", "TCL v8.4 and packages that depend on it.",
151 "textproc", "Text processing/search utilities.",
152 "tk80", "Tk8.0 and packages that depend on it.",
153 "tk82", "Tk8.2 and packages that depend on it.",
154 "tk83", "Tk8.3 and packages that depend on it.",
155 "tk84", "Tk8.4 and packages that depend on it.",
156 "tkstep80", "Ports to support the TkStep window manager.",
157 "ukrainian", "Ported software for the Ukrainian market.",
158 "vietnamese", "Ported software for the Vietnamese market.",
159 "windowmaker", "Ports to support the WindowMaker window manager.",
160 "www", "Web utilities (browers, HTTP servers, etc).",
161 "x11", "X Window System based utilities.",
162 "x11-clocks", "X Window System based clocks.",
163 "x11-fm", "X Window System based file managers.",
164 "x11-fonts", "X Window System fonts and font utilities.",
165 "x11-servers", "X Window System servers.",
166 "x11-themes", "X Window System themes.",
167 "x11-toolkits", "X Window System based development toolkits.",
168 "x11-wm", "X Window System window managers.",
169 "xfce", "Software relating to the Xfce Desktop Environment.",
170 "zope", "Software related to the Zope platform.",
175 fetch_desc(char *name)
179 for (i = 0; descrs[i]; i += 2) {
180 if (!strcmp(descrs[i], name))
181 return descrs[i + 1];
183 return "No description provided";
187 new_pkg_node(char *name, node_type type)
189 PkgNodePtr tmp = safe_malloc(sizeof(PkgNode));
191 tmp->name = _strdup(name);
201 for (i = 0; buf[i]; i++)
202 if (buf[i] == '\t' || buf[i] == '\n')
208 new_index(char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *deps, int volume)
210 IndexEntryPtr tmp = safe_malloc(sizeof(IndexEntry));
212 tmp->name = _strdup(name);
213 tmp->path = _strdup(pathto);
214 tmp->prefix = _strdup(prefix);
215 tmp->comment = _strdup(comment);
216 tmp->descrfile = strip(_strdup(descr));
217 tmp->maintainer = _strdup(maint);
218 tmp->deps = _strdup(deps);
220 tmp->installed = package_installed(name);
221 tmp->volume = volume;
226 index_register(PkgNodePtr top, char *where, IndexEntryPtr ptr)
230 for (q = NULL, p = top->kids; p; p = p->next) {
231 if (!strcmp(p->name, where)) {
237 /* Add new category */
238 q = new_pkg_node(where, PLACE);
239 q->desc = fetch_desc(where);
243 p = new_pkg_node(ptr->name, PACKAGE);
244 p->desc = ptr->comment;
251 copy_to_sep(char *to, char *from, int sep)
255 tok = strchr(from, sep);
262 return tok + 1 - from;
266 readline(FILE *fp, char *buf, int max)
271 while ((rv = fread(&ch, 1, 1, fp)) == 1 && ch != '\n' && i < max)
279 * XXX - this function should do error checking, and skip corrupted INDEX
280 * lines without a set number of '|' delimited fields.
284 index_parse(FILE *fp, char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *cats, char *rdeps, int *volume)
286 char line[10240 + 2048 * 7];
292 i = readline(fp, line, sizeof line);
296 cp += copy_to_sep(name, cp, '|'); /* package name */
297 cp += copy_to_sep(pathto, cp, '|'); /* ports directory */
298 cp += copy_to_sep(prefix, cp, '|'); /* prefix */
299 cp += copy_to_sep(comment, cp, '|'); /* comment */
300 cp += copy_to_sep(descr, cp, '|'); /* path to pkg-descr */
301 cp += copy_to_sep(maint, cp, '|'); /* maintainer */
302 cp += copy_to_sep(cats, cp, '|'); /* categories */
303 cp += copy_to_sep(junk, cp, '|'); /* build deps - not used */
304 cp += copy_to_sep(rdeps, cp, '|'); /* run deps */
306 cp += copy_to_sep(junk, cp, '|'); /* url - not used */
308 strncpy(junk, cp, 1023);
313 cp += copy_to_sep(junk, cp, '|'); /* extract deps - not used */
315 cp += copy_to_sep(junk, cp, '|'); /* patch deps - not used */
317 cp += copy_to_sep(junk, cp, '|'); /* fetch deps - not used */
319 cp += copy_to_sep(volstr, cp, '|'); /* media volume */
321 strncpy(volstr, cp, 1023);
323 *volume = atoi(volstr);
328 index_read(FILE *fp, PkgNodePtr papa)
330 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[2048 * 8];
334 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps, &volume) != EOF) {
335 char *cp, *cp2, tmp[1024];
338 idx = new_index(name, pathto, prefix, comment, descr, maint, deps, volume);
339 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
340 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
342 index_register(papa, cp, idx);
344 index_register(papa, cp, idx);
346 /* Add to special "All" category */
347 index_register(papa, "All", idx);
350 /* Adjust dependency counts */
351 for (i = papa->kids; i != NULL; i = i->next)
352 if (strcmp(i->name, "All") == 0)
354 for (i = i->kids; i != NULL; i = i->next)
355 if (((IndexEntryPtr)i->data)->installed)
356 index_recorddeps(TRUE, papa, i->data);
362 index_init(PkgNodePtr top, PkgNodePtr plist)
365 top->next = top->kids = NULL;
366 top->name = "Package Selection";
368 top->desc = fetch_desc(top->name);
372 plist->next = plist->kids = NULL;
373 plist->name = "Package Targets";
375 plist->desc = fetch_desc(plist->name);
381 index_print(PkgNodePtr top, int level)
386 for (i = 0; i < level; i++) putchar('\t');
387 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
388 for (i = 0; i < level; i++) putchar('\t');
389 printf("desc: %s\n", top->desc);
391 index_print(top->kids, level + 1);
396 /* Swap one node for another */
398 swap_nodes(PkgNodePtr a, PkgNodePtr b)
409 /* Use a disgustingly simplistic bubble sort to put our lists in order */
411 index_sort(PkgNodePtr top)
415 /* Sort everything at the top level */
416 for (p = top->kids; p; p = p->next) {
417 for (q = top->kids; q; q = q->next) {
418 if (q->next && strcmp(q->name, q->next->name) > 0)
419 swap_nodes(q, q->next);
423 /* Now sub-sort everything n levels down */
424 for (p = top->kids; p; p = p->next) {
430 /* Delete an entry out of the list it's in (only the plist, at present) */
432 index_delete(PkgNodePtr n)
435 PkgNodePtr p = n->next;
440 else /* Kludgy end sentinal */
445 * Search for a given node by name, returning the category in if
449 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
453 for (p = top->kids; p && p->name; p = p->next) {
454 if (p->type == PACKAGE) {
455 /* If tp == NULL, we're looking for an exact package match */
456 if (!tp && !strcmp(p->name, str))
459 /* If tp, we're looking for both a package and a pointer to the place it's in */
460 if (tp && !strncmp(p->name, str, strlen(str))) {
466 /* The usual recursion-out-of-laziness ploy */
467 if ((sp = index_search(p, str, tp)) != NULL)
477 pkg_checked(dialogMenuItem *self)
479 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
480 PkgNodePtr kp = self->data, plist = lists->plist;
483 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
484 if (kp->type == PACKAGE && plist) {
485 IndexEntryPtr ie = kp->data;
488 markD = ie->depc > 0; /* needed as dependency */
489 markX = i || ie->installed; /* selected or installed */
490 self->mark = markX ? 'X' : 'D';
491 return markD || markX;
497 pkg_fire(dialogMenuItem *self)
500 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
501 PkgNodePtr sp, kp = self->data, plist = lists->plist;
505 else if (kp->type == PACKAGE) {
506 IndexEntryPtr ie = kp->data;
508 sp = index_search(plist, kp->name, NULL);
509 /* Not already selected? */
511 if (!ie->installed) {
512 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
515 np->next = plist->kids;
517 index_recorddeps(TRUE, lists->root, ie);
518 msgInfo("Added %s to selection list", kp->name);
520 else if (ie->depc == 0) {
521 if (!msgNoYes("Do you really want to delete %s from the system?", kp->name)) {
522 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
523 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
527 index_recorddeps(FALSE, lists->root, ie);
532 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
533 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
536 index_recorddeps(FALSE, lists->root, ie);
537 msgInfo("Removed %s from selection list", kp->name);
541 /* Mark menu for redraw if we had dependencies */
542 if (strlen(ie->deps) > 0)
545 else { /* Not a package, must be a directory */
549 index_menu(lists->root, kp, plist, &p, &s);
550 ret = DITEM_SUCCESS | DITEM_CONTINUE;
556 pkg_selected(dialogMenuItem *self, int is_selected)
558 PkgNodePtr kp = self->data;
560 if (!is_selected || kp->type != PACKAGE)
562 msgInfo("%s", kp->desc);
566 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
568 struct ListPtrs lists;
573 dialogMenuItem *nitems;
585 /* Figure out if this menu is full of "leaves" or "branches" */
586 for (kp = top->kids; kp && kp->name; kp = kp->next) {
590 if (kp->type == PACKAGE && plist) {
592 if ((len = strlen(kp->name)) > maxname)
597 msgConfirm("The %s menu is empty.", top->name);
598 return DITEM_LEAVE_MENU;
608 if (!hasPackages && plist) {
609 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
610 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
612 while (kp && kp->name) {
614 IndexEntryPtr ie = kp->data;
616 /* Brutally adjust description to fit in menu */
617 if (kp->type == PACKAGE)
618 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
620 SAFE_STRCPY(buf, kp->desc);
621 if (strlen(buf) > (_MAX_DESC - maxname))
622 buf[_MAX_DESC - maxname] = '\0';
623 nitems = item_add(nitems, kp->name, (char *)buf, pkg_checked,
624 pkg_fire, pkg_selected, kp, (int *)(&lists),
629 /* NULL delimiter so item_free() knows when to stop later */
630 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
634 dialog_clear_norefresh();
636 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
638 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
639 if (rval == -1 && plist) {
644 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
645 PkgNodePtr p = index_search(top, cp, &menu);
650 /* These need to be set to point at the found item, actually. Hmmm! */
652 index_menu(root, menu, plist, &pos, &scroll);
655 msgConfirm("Search string: %s yielded no hits.", cp);
659 items_free(nitems, &curr, &max);
661 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
666 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
668 int status = DITEM_SUCCESS;
670 IndexEntryPtr id = who->data;
671 WINDOW *w = savescr();
674 * Short-circuit the package dependency checks. We're already
675 * maintaining a data structure of installed packages, so if a
676 * package is already installed, don't try to check to make sure
677 * that all of its dependencies are installed. At best this
678 * wastes a ton of cycles and can cause minor delays between
679 * package extraction. At worst it can cause an infinite loop with
680 * a certain faulty INDEX file.
683 if (id->installed == 1)
684 return DITEM_SUCCESS;
686 if (id && id->deps && strlen(id->deps)) {
687 char t[2048 * 8], *cp, *cp2;
689 SAFE_STRCPY(t, id->deps);
691 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
692 if ((cp2 = index(cp, ' ')) != NULL)
694 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
695 status = index_extract(dev, top, tmp2, TRUE);
696 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
697 if (variable_get(VAR_NO_CONFIRM))
698 msgNotify("Loading of dependent package %s failed", cp);
700 msgConfirm("Loading of dependent package %s failed", cp);
703 else if (!package_installed(cp)) {
704 if (variable_get(VAR_NO_CONFIRM))
705 msgNotify("Warning: %s is a required package but was not found.", cp);
707 msgConfirm("Warning: %s is a required package but was not found.", cp);
715 /* Done with the deps? Load the real m'coy */
716 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
717 /* Prompt user if the package is not available on the current volume. */
718 if(mediaDevice->type == DEVICE_TYPE_CDROM) {
719 while (id->volume != dev->volume) {
720 if (!msgYesNo("This is disc #%d. Package %s is on disc #%d\n"
721 "Would you like to switch discs now?\n", dev->volume,
722 id->name, id->volume)) {
723 DEVICE_SHUTDOWN(mediaDevice);
724 msgConfirm("Please remove disc #%d from your drive, and add disc #%d\n",
725 dev->volume, id->volume);
726 DEVICE_INIT(mediaDevice);
728 return DITEM_FAILURE;
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;