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 "applications", "User application software.",
83 "arabic", "Ported software for Arab countries.",
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 "dns", "Domain Name Service tools.",
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 "net-mgmt", "Network Management",
128 "news", "USENET News support software.",
129 "numeric", "Mathematical computation software.",
130 "offix", "An office automation suite of sorts.",
131 "orphans", "Packages without a home elsewhere.",
132 "palm", "Software support for the Palm(tm) series.",
133 "parallel", "Applications dealing with parallelism in computing.",
134 "perl5", "Utilities/modules for the PERL5 language.",
135 "picobsd", "Ports to support PicoBSD.",
136 "pilot", "Software support for the 3Com Palm Pilot(tm) series.",
137 "plan9", "Software from the Plan9 operating system.",
138 "polish", "Ported software for the Polish market.",
139 "portuguese", "Ported software for the Portuguese market.",
140 "print", "Utilities for dealing with printing.",
141 "printing", "Utilities for dealing with printing.",
142 "programming", "Software development utilities and libraries.",
143 "python", "Software related to the Python language.",
144 "ruby", "Software related to the Ruby language.",
145 "russian", "Ported software for the Russian market.",
146 "science", "Scientific software.",
147 "scheme", "Software related to the Scheme language.",
148 "security", "System security software.",
149 "shells", "Various shells (tcsh, bash, etc).",
150 "sysutils", "Various system utilities.",
151 "tcl75", "TCL v7.5 and packages that depend on it.",
152 "tcl76", "TCL v7.6 and packages that depend on it.",
153 "tcl80", "TCL v8.0 and packages that depend on it.",
154 "tcl81", "TCL v8.1 and packages that depend on it.",
155 "tcl82", "TCL v8.2 and packages that depend on it.",
156 "tcl83", "TCL v8.3 and packages that depend on it.",
157 "tcl84", "TCL v8.4 and packages that depend on it.",
158 "textproc", "Text processing/search utilities.",
159 "tk41", "Tk4.1 and packages that depend on it.",
160 "tk42", "Tk4.2 and packages that depend on it.",
161 "tk80", "Tk8.0 and packages that depend on it.",
162 "tk81", "Tk8.1 and packages that depend on it.",
163 "tk82", "Tk8.2 and packages that depend on it.",
164 "tk83", "Tk8.3 and packages that depend on it.",
165 "tkstep80", "tkstep wm and packages that depend on it.",
166 "troff", "TROFF text formatting utilities.",
167 "ukrainian", "Ported software for the Ukrainian market.",
168 "vietnamese", "Ported software for the Vietnamese market.",
169 "windowmaker", "Ports to support the WindowMaker window manager.",
170 "www", "WEB utilities (browers, HTTP servers, etc).",
171 "x11", "X Window System based utilities.",
172 "x11-clocks", "X Window System based clocks.",
173 "x11-fm", "X Window System based file managers.",
174 "x11-fonts", "X Window System fonts and font utilities.",
175 "x11-servers", "X Window System servers.",
176 "x11-themes", "X Window System themes.",
177 "x11-toolkits", "X Window System based development toolkits.",
178 "x11-wm", "X Window System window managers.",
179 "zope", "Software related to the Zope platform.",
184 fetch_desc(char *name)
188 for (i = 0; descrs[i]; i += 2) {
189 if (!strcmp(descrs[i], name))
190 return descrs[i + 1];
192 return "No description provided";
196 new_pkg_node(char *name, node_type type)
198 PkgNodePtr tmp = safe_malloc(sizeof(PkgNode));
200 tmp->name = _strdup(name);
210 for (i = 0; buf[i]; i++)
211 if (buf[i] == '\t' || buf[i] == '\n')
217 new_index(char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *deps, int volume)
219 IndexEntryPtr tmp = safe_malloc(sizeof(IndexEntry));
221 tmp->name = _strdup(name);
222 tmp->path = _strdup(pathto);
223 tmp->prefix = _strdup(prefix);
224 tmp->comment = _strdup(comment);
225 tmp->descrfile = strip(_strdup(descr));
226 tmp->maintainer = _strdup(maint);
227 tmp->deps = _strdup(deps);
229 tmp->installed = package_installed(name);
230 tmp->volume = volume;
235 index_register(PkgNodePtr top, char *where, IndexEntryPtr ptr)
239 for (q = NULL, p = top->kids; p; p = p->next) {
240 if (!strcmp(p->name, where)) {
246 /* Add new category */
247 q = new_pkg_node(where, PLACE);
248 q->desc = fetch_desc(where);
252 p = new_pkg_node(ptr->name, PACKAGE);
253 p->desc = ptr->comment;
260 copy_to_sep(char *to, char *from, int sep)
264 tok = strchr(from, sep);
271 return tok + 1 - from;
275 readline(FILE *fp, char *buf, int max)
280 while ((rv = fread(&ch, 1, 1, fp)) == 1 && ch != '\n' && i < max)
288 * XXX - this function should do error checking, and skip corrupted INDEX
289 * lines without a set number of '|' delimited fields.
293 index_parse(FILE *fp, char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *cats, char *rdeps, int *volume)
295 char line[10240 + 2048 * 7];
301 i = readline(fp, line, sizeof line);
305 cp += copy_to_sep(name, cp, '|'); /* package name */
306 cp += copy_to_sep(pathto, cp, '|'); /* ports directory */
307 cp += copy_to_sep(prefix, cp, '|'); /* prefix */
308 cp += copy_to_sep(comment, cp, '|'); /* comment */
309 cp += copy_to_sep(descr, cp, '|'); /* path to pkg-descr */
310 cp += copy_to_sep(maint, cp, '|'); /* maintainer */
311 cp += copy_to_sep(cats, cp, '|'); /* categories */
312 cp += copy_to_sep(junk, cp, '|'); /* build deps - not used */
313 cp += copy_to_sep(rdeps, cp, '|'); /* run deps */
315 cp += copy_to_sep(junk, cp, '|'); /* url - not used */
317 strncpy(junk, cp, 1023);
322 cp += copy_to_sep(junk, cp, '|'); /* extract deps - not used */
324 cp += copy_to_sep(junk, cp, '|'); /* patch deps - not used */
326 cp += copy_to_sep(junk, cp, '|'); /* fetch deps - not used */
328 cp += copy_to_sep(volstr, cp, '|'); /* media volume */
330 strncpy(volstr, cp, 1023);
332 *volume = atoi(volstr);
337 index_read(FILE *fp, PkgNodePtr papa)
339 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[2048 * 8];
343 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps, &volume) != EOF) {
344 char *cp, *cp2, tmp[1024];
347 idx = new_index(name, pathto, prefix, comment, descr, maint, deps, volume);
348 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
349 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
351 index_register(papa, cp, idx);
353 index_register(papa, cp, idx);
355 /* Add to special "All" category */
356 index_register(papa, "All", idx);
359 /* Adjust dependency counts */
360 for (i = papa->kids; i != NULL; i = i->next)
361 if (strcmp(i->name, "All") == 0)
363 for (i = i->kids; i != NULL; i = i->next)
364 if (((IndexEntryPtr)i->data)->installed)
365 index_recorddeps(TRUE, papa, i->data);
371 index_init(PkgNodePtr top, PkgNodePtr plist)
374 top->next = top->kids = NULL;
375 top->name = "Package Selection";
377 top->desc = fetch_desc(top->name);
381 plist->next = plist->kids = NULL;
382 plist->name = "Package Targets";
384 plist->desc = fetch_desc(plist->name);
390 index_print(PkgNodePtr top, int level)
395 for (i = 0; i < level; i++) putchar('\t');
396 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
397 for (i = 0; i < level; i++) putchar('\t');
398 printf("desc: %s\n", top->desc);
400 index_print(top->kids, level + 1);
405 /* Swap one node for another */
407 swap_nodes(PkgNodePtr a, PkgNodePtr b)
418 /* Use a disgustingly simplistic bubble sort to put our lists in order */
420 index_sort(PkgNodePtr top)
424 /* Sort everything at the top level */
425 for (p = top->kids; p; p = p->next) {
426 for (q = top->kids; q; q = q->next) {
427 if (q->next && strcmp(q->name, q->next->name) > 0)
428 swap_nodes(q, q->next);
432 /* Now sub-sort everything n levels down */
433 for (p = top->kids; p; p = p->next) {
439 /* Delete an entry out of the list it's in (only the plist, at present) */
441 index_delete(PkgNodePtr n)
444 PkgNodePtr p = n->next;
449 else /* Kludgy end sentinal */
454 * Search for a given node by name, returning the category in if
458 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
462 for (p = top->kids; p && p->name; p = p->next) {
463 if (p->type == PACKAGE) {
464 /* If tp == NULL, we're looking for an exact package match */
465 if (!tp && !strcmp(p->name, str))
468 /* If tp, we're looking for both a package and a pointer to the place it's in */
469 if (tp && !strncmp(p->name, str, strlen(str))) {
475 /* The usual recursion-out-of-laziness ploy */
476 if ((sp = index_search(p, str, tp)) != NULL)
486 pkg_checked(dialogMenuItem *self)
488 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
489 PkgNodePtr kp = self->data, plist = lists->plist;
492 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
493 if (kp->type == PACKAGE && plist) {
494 IndexEntryPtr ie = kp->data;
497 markD = ie->depc > 0; /* needed as dependency */
498 markX = i || ie->installed; /* selected or installed */
499 self->mark = markX ? 'X' : 'D';
500 return markD || markX;
506 pkg_fire(dialogMenuItem *self)
509 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
510 PkgNodePtr sp, kp = self->data, plist = lists->plist;
514 else if (kp->type == PACKAGE) {
515 IndexEntryPtr ie = kp->data;
517 sp = index_search(plist, kp->name, NULL);
518 /* Not already selected? */
520 if (!ie->installed) {
521 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
524 np->next = plist->kids;
526 index_recorddeps(TRUE, lists->root, ie);
527 msgInfo("Added %s to selection list", kp->name);
529 else if (ie->depc == 0) {
530 if (!msgNoYes("Do you really want to delete %s from the system?", kp->name)) {
531 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
532 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
536 index_recorddeps(FALSE, lists->root, ie);
541 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
542 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
545 index_recorddeps(FALSE, lists->root, ie);
546 msgInfo("Removed %s from selection list", kp->name);
550 /* Mark menu for redraw if we had dependencies */
551 if (strlen(ie->deps) > 0)
554 else { /* Not a package, must be a directory */
558 index_menu(lists->root, kp, plist, &p, &s);
559 ret = DITEM_SUCCESS | DITEM_CONTINUE;
565 pkg_selected(dialogMenuItem *self, int is_selected)
567 PkgNodePtr kp = self->data;
569 if (!is_selected || kp->type != PACKAGE)
571 msgInfo("%s", kp->desc);
575 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
577 struct ListPtrs lists;
582 dialogMenuItem *nitems;
594 /* Figure out if this menu is full of "leaves" or "branches" */
595 for (kp = top->kids; kp && kp->name; kp = kp->next) {
599 if (kp->type == PACKAGE && plist) {
601 if ((len = strlen(kp->name)) > maxname)
606 msgConfirm("The %s menu is empty.", top->name);
607 return DITEM_LEAVE_MENU;
617 if (!hasPackages && plist) {
618 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
619 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
621 while (kp && kp->name) {
623 IndexEntryPtr ie = kp->data;
625 /* Brutally adjust description to fit in menu */
626 if (kp->type == PACKAGE)
627 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
629 SAFE_STRCPY(buf, kp->desc);
630 if (strlen(buf) > (_MAX_DESC - maxname))
631 buf[_MAX_DESC - maxname] = '\0';
632 nitems = item_add(nitems, kp->name, (char *)buf, pkg_checked,
633 pkg_fire, pkg_selected, kp, (int *)(&lists),
638 /* NULL delimiter so item_free() knows when to stop later */
639 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
643 dialog_clear_norefresh();
645 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
647 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
648 if (rval == -1 && plist) {
653 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
654 PkgNodePtr p = index_search(top, cp, &menu);
659 /* These need to be set to point at the found item, actually. Hmmm! */
661 index_menu(root, menu, plist, &pos, &scroll);
664 msgConfirm("Search string: %s yielded no hits.", cp);
668 items_free(nitems, &curr, &max);
670 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
675 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
677 int status = DITEM_SUCCESS;
679 IndexEntryPtr id = who->data;
680 WINDOW *w = savescr();
683 * Short-circuit the package dependency checks. We're already
684 * maintaining a data structure of installed packages, so if a
685 * package is already installed, don't try to check to make sure
686 * that all of its dependencies are installed. At best this
687 * wastes a ton of cycles and can cause minor delays between
688 * package extraction. At worst it can cause an infinite loop with
689 * a certain faulty INDEX file.
692 if (id->installed == 1)
693 return DITEM_SUCCESS;
696 * Prompt user if the package is not available on the current volume.
699 if(mediaDevice->type == DEVICE_TYPE_CDROM) {
700 while (id->volume != dev->volume) {
701 if (!msgYesNo("This is disc #%d. Package %s is on disc #%d\n"
702 "Would you like to switch discs now?\n", dev->volume,
703 id->name, id->volume)) {
704 DEVICE_SHUTDOWN(mediaDevice);
705 msgConfirm("Please remove disc #%d from your drive, and add disc #%d\n",
706 dev->volume, id->volume);
707 DEVICE_INIT(mediaDevice);
709 return DITEM_FAILURE;
714 if (id && id->deps && strlen(id->deps)) {
715 char t[2048 * 8], *cp, *cp2;
717 SAFE_STRCPY(t, id->deps);
719 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
720 if ((cp2 = index(cp, ' ')) != NULL)
722 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
723 status = index_extract(dev, top, tmp2, TRUE);
724 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
725 if (variable_get(VAR_NO_CONFIRM))
726 msgNotify("Loading of dependent package %s failed", cp);
728 msgConfirm("Loading of dependent package %s failed", cp);
731 else if (!package_installed(cp)) {
732 if (variable_get(VAR_NO_CONFIRM))
733 msgNotify("Warning: %s is a required package but was not found.", cp);
735 msgConfirm("Warning: %s is a required package but was not found.", cp);
743 /* Done with the deps? Load the real m'coy */
744 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
745 status = package_extract(dev, who->name, depended);
746 if (DITEM_STATUS(status) == DITEM_SUCCESS)
754 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
756 char depends[1024 * 16], *space, *todo;
758 IndexEntryPtr found_ie;
760 SAFE_STRCPY(depends, ie->deps);
761 for (todo = depends; todo != NULL; ) {
762 space = index(todo, ' ');
766 if (strlen(todo) > 0) { /* only non-empty dependencies */
767 found = index_search(root, todo, NULL);
769 found_ie = found->data;
784 static Boolean index_initted;
786 /* Read and initialize global index */
788 index_initialize(char *path)
793 if (!index_initted) {
795 dialog_clear_norefresh();
798 if (!mediaVerify()) {
800 return DITEM_FAILURE;
803 /* Does it move when you kick it? */
804 if (!DEVICE_INIT(mediaDevice)) {
806 return DITEM_FAILURE;
809 dialog_clear_norefresh();
810 msgNotify("Attempting to fetch %s file from selected media.", path);
811 fp = DEVICE_GET(mediaDevice, path, TRUE);
813 msgConfirm("Unable to get packages/INDEX file from selected media.\n\n"
814 "This may be because the packages collection is not available\n"
815 "on the distribution media you've chosen, most likely an FTP site\n"
816 "without the packages collection mirrored. Please verify that\n"
817 "your media, or your path to the media, is correct and try again.");
818 DEVICE_SHUTDOWN(mediaDevice);
820 return DITEM_FAILURE;
822 dialog_clear_norefresh();
823 msgNotify("Located INDEX, now reading package data from it...");
824 index_init(&Top, &Plist);
825 if (index_read(fp, &Top)) {
826 msgConfirm("I/O or format error on packages/INDEX file.\n"
827 "Please verify media (or path to media) and try again.");
830 return DITEM_FAILURE;
834 index_initted = TRUE;
837 return DITEM_SUCCESS;