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;
578 int n, rval, maxname;
581 dialogMenuItem *nitems;
593 /* Figure out if this menu is full of "leaves" or "branches" */
594 for (kp = top->kids; kp && kp->name; kp = kp->next) {
598 if (kp->type == PACKAGE && plist) {
600 if ((len = strlen(kp->name)) > maxname)
605 msgConfirm("The %s menu is empty.", top->name);
606 return DITEM_LEAVE_MENU;
616 if (!hasPackages && plist) {
617 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
618 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
620 while (kp && kp->name) {
622 IndexEntryPtr ie = kp->data;
624 /* Brutally adjust description to fit in menu */
625 if (kp->type == PACKAGE)
626 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
628 SAFE_STRCPY(buf, kp->desc);
629 if (strlen(buf) > (_MAX_DESC - maxname))
630 buf[_MAX_DESC - maxname] = '\0';
631 nitems = item_add(nitems, kp->name, (char *)buf, pkg_checked,
632 pkg_fire, pkg_selected, kp, (int *)(&lists),
637 /* NULL delimiter so item_free() knows when to stop later */
638 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
642 dialog_clear_norefresh();
644 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
646 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
647 if (rval == -1 && plist) {
652 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
653 PkgNodePtr p = index_search(top, cp, &menu);
658 /* These need to be set to point at the found item, actually. Hmmm! */
660 index_menu(root, menu, plist, &pos, &scroll);
663 msgConfirm("Search string: %s yielded no hits.", cp);
667 items_free(nitems, &curr, &max);
669 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
674 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
676 int status = DITEM_SUCCESS;
678 IndexEntryPtr id = who->data;
679 WINDOW *w = savescr();
682 * Short-circuit the package dependency checks. We're already
683 * maintaining a data structure of installed packages, so if a
684 * package is already installed, don't try to check to make sure
685 * that all of its dependencies are installed. At best this
686 * wastes a ton of cycles and can cause minor delays between
687 * package extraction. At worst it can cause an infinite loop with
688 * a certain faulty INDEX file.
691 if (id->installed == 1)
692 return DITEM_SUCCESS;
695 * Prompt user if the package is not available on the current volume.
698 if(mediaDevice->type == DEVICE_TYPE_CDROM) {
699 while (id->volume != dev->volume) {
700 if (!msgYesNo("This is disc #%d. Package %s is on disc #%d\n"
701 "Would you like to switch discs now?\n", dev->volume,
702 id->name, id->volume)) {
703 DEVICE_SHUTDOWN(mediaDevice);
704 msgConfirm("Please remove disc #%d from your drive, and add disc #%d\n",
705 dev->volume, id->volume);
706 DEVICE_INIT(mediaDevice);
708 return DITEM_FAILURE;
713 if (id && id->deps && strlen(id->deps)) {
714 char t[2048 * 8], *cp, *cp2;
716 SAFE_STRCPY(t, id->deps);
718 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
719 if ((cp2 = index(cp, ' ')) != NULL)
721 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
722 status = index_extract(dev, top, tmp2, TRUE);
723 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
724 if (variable_get(VAR_NO_CONFIRM))
725 msgNotify("Loading of dependent package %s failed", cp);
727 msgConfirm("Loading of dependent package %s failed", cp);
730 else if (!package_installed(cp)) {
731 if (variable_get(VAR_NO_CONFIRM))
732 msgNotify("Warning: %s is a required package but was not found.", cp);
734 msgConfirm("Warning: %s is a required package but was not found.", cp);
742 /* Done with the deps? Load the real m'coy */
743 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
744 status = package_extract(dev, who->name, depended);
745 if (DITEM_STATUS(status) == DITEM_SUCCESS)
753 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
755 char depends[1024 * 16], *space, *todo;
757 IndexEntryPtr found_ie;
759 SAFE_STRCPY(depends, ie->deps);
760 for (todo = depends; todo != NULL; ) {
761 space = index(todo, ' ');
765 if (strlen(todo) > 0) { /* only non-empty dependencies */
766 found = index_search(root, todo, NULL);
768 found_ie = found->data;
783 static Boolean index_initted;
785 /* Read and initialize global index */
787 index_initialize(char *path)
792 if (!index_initted) {
794 dialog_clear_norefresh();
797 if (!mediaVerify()) {
799 return DITEM_FAILURE;
802 /* Does it move when you kick it? */
803 if (!DEVICE_INIT(mediaDevice)) {
805 return DITEM_FAILURE;
808 dialog_clear_norefresh();
809 msgNotify("Attempting to fetch %s file from selected media.", path);
810 fp = DEVICE_GET(mediaDevice, path, TRUE);
812 msgConfirm("Unable to get packages/INDEX file from selected media.\n\n"
813 "This may be because the packages collection is not available\n"
814 "on the distribution media you've chosen, most likely an FTP site\n"
815 "without the packages collection mirrored. Please verify that\n"
816 "your media, or your path to the media, is correct and try again.");
817 DEVICE_SHUTDOWN(mediaDevice);
819 return DITEM_FAILURE;
821 dialog_clear_norefresh();
822 msgNotify("Located INDEX, now reading package data from it...");
823 index_init(&Top, &Plist);
824 if (index_read(fp, &Top)) {
825 msgConfirm("I/O or format error on packages/INDEX file.\n"
826 "Please verify media (or path to media) and try again.");
829 return DITEM_FAILURE;
833 index_initted = TRUE;
836 return DITEM_SUCCESS;