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 "geography", "Geography-related software.",
105 "gnome", "Components of the Gnome Desktop environment.",
106 "gnustep", "Software for GNUstep desktop environment.",
107 "graphics", "Graphics libraries and utilities.",
108 "haskell", "Software related to the Haskell language.",
109 "hamradio", "Software for amateur radio.",
110 "hebrew", "Ported software for Hebrew language.",
111 "hungarian", "Ported software for the Hungarian market.",
112 "ipv6", "IPv6 related software.",
113 "irc", "Internet Relay Chat utilities.",
114 "japanese", "Ported software for the Japanese market.",
115 "java", "Java language support.",
116 "kde", "Software for the K Desktop Environment.",
117 "korean", "Ported software for the Korean market.",
118 "lang", "Computer languages.",
119 "linux", "Linux programs that can run under binary compatibility.",
120 "lisp", "Software related to the Lisp language.",
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-im", "Instant messaging software.",
128 "net-mgmt", "Network management tools.",
129 "net-p2p", "Peer to peer network applications.",
130 "news", "USENET News support software.",
131 "palm", "Software support for the Palm(tm) series.",
132 "parallel", "Applications dealing with parallelism in computing.",
133 "pear", "Software related to the Pear PHP framework.",
134 "perl5", "Utilities/modules for the PERL5 language.",
135 "plan9", "Software from the Plan9 operating system.",
136 "polish", "Ported software for the Polish market.",
137 "ports-mgmt", "Utilities for managing ports and packages.",
138 "portuguese", "Ported software for the Portuguese market.",
139 "print", "Utilities for dealing with printing.",
140 "python", "Software related to the Python language.",
141 "ruby", "Software related to the Ruby language.",
142 "rubygems", "Ports of RubyGems packages.",
143 "russian", "Ported software for the Russian market.",
144 "scheme", "Software related to the Scheme language.",
145 "science", "Scientific software.",
146 "security", "System security software.",
147 "shells", "Various shells (tcsh, bash, etc).",
148 "spanish", "Ported software for the Spanish market.",
149 "sysutils", "Various system utilities.",
150 "tcl", "TCL and packages that depend on it.",
151 "tcl80", "TCL v8.0 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 "tcl84", "TCL v8.4 and packages that depend on it.",
155 "textproc", "Text processing/search utilities.",
156 "tk", "Tk and packages that depend on it.",
157 "tk80", "Tk8.0 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 "tk84", "Tk8.4 and packages that depend on it.",
161 "tkstep80", "Ports to support the TkStep window manager.",
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 (browsers, HTTP servers, etc).",
166 "x11", "X Window System based utilities.",
167 "x11-clocks", "X Window System based clocks.",
168 "x11-drivers", "X Window System drivers.",
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-themes", "X Window System themes.",
173 "x11-toolkits", "X Window System based development toolkits.",
174 "x11-wm", "X Window System window managers.",
175 "xfce", "Software related to the Xfce Desktop Environment.",
176 "zope", "Software related to the Zope platform.",
181 fetch_desc(char *name)
185 for (i = 0; descrs[i]; i += 2) {
186 if (!strcmp(descrs[i], name))
187 return descrs[i + 1];
189 return "No description provided";
193 new_pkg_node(char *name, node_type type)
195 PkgNodePtr tmp = safe_malloc(sizeof(PkgNode));
197 tmp->name = _strdup(name);
207 for (i = 0; buf[i]; i++)
208 if (buf[i] == '\t' || buf[i] == '\n')
214 new_index(char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *deps, int volume)
216 IndexEntryPtr tmp = safe_malloc(sizeof(IndexEntry));
218 tmp->name = _strdup(name);
219 tmp->path = _strdup(pathto);
220 tmp->prefix = _strdup(prefix);
221 tmp->comment = _strdup(comment);
222 tmp->descrfile = strip(_strdup(descr));
223 tmp->maintainer = _strdup(maint);
224 tmp->deps = _strdup(deps);
226 tmp->installed = package_installed(name);
227 tmp->volume = volume;
232 index_register(PkgNodePtr top, char *where, IndexEntryPtr ptr)
236 for (q = NULL, p = top->kids; p; p = p->next) {
237 if (!strcmp(p->name, where)) {
243 /* Add new category */
244 q = new_pkg_node(where, PLACE);
245 q->desc = fetch_desc(where);
249 p = new_pkg_node(ptr->name, PACKAGE);
250 p->desc = ptr->comment;
257 copy_to_sep(char *to, char *from, int sep)
261 tok = strchr(from, sep);
268 return tok + 1 - from;
272 readline(FILE *fp, char *buf, int max)
277 while ((rv = fread(&ch, 1, 1, fp)) == 1 && ch != '\n' && i < max)
285 * XXX - this function should do error checking, and skip corrupted INDEX
286 * lines without a set number of '|' delimited fields.
290 index_parse(FILE *fp, char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *cats, char *rdeps, int *volume)
292 char line[10240 + 2048 * 7];
298 i = readline(fp, line, sizeof line);
302 cp += copy_to_sep(name, cp, '|'); /* package name */
303 cp += copy_to_sep(pathto, cp, '|'); /* ports directory */
304 cp += copy_to_sep(prefix, cp, '|'); /* prefix */
305 cp += copy_to_sep(comment, cp, '|'); /* comment */
306 cp += copy_to_sep(descr, cp, '|'); /* path to pkg-descr */
307 cp += copy_to_sep(maint, cp, '|'); /* maintainer */
308 cp += copy_to_sep(cats, cp, '|'); /* categories */
309 cp += copy_to_sep(junk, cp, '|'); /* build deps - not used */
310 cp += copy_to_sep(rdeps, cp, '|'); /* run deps */
312 cp += copy_to_sep(junk, cp, '|'); /* url - not used */
314 strncpy(junk, cp, 1023);
319 cp += copy_to_sep(junk, cp, '|'); /* extract deps - not used */
321 cp += copy_to_sep(junk, cp, '|'); /* patch deps - not used */
323 cp += copy_to_sep(junk, cp, '|'); /* fetch deps - not used */
325 cp += copy_to_sep(volstr, cp, '|'); /* media volume */
327 strncpy(volstr, cp, 1023);
329 *volume = atoi(volstr);
334 index_read(FILE *fp, PkgNodePtr papa)
336 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[2048 * 8];
340 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps, &volume) != EOF) {
341 char *cp, *cp2, tmp[1024];
344 idx = new_index(name, pathto, prefix, comment, descr, maint, deps, volume);
345 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
346 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
348 index_register(papa, cp, idx);
350 index_register(papa, cp, idx);
352 /* Add to special "All" category */
353 index_register(papa, "All", idx);
356 /* Adjust dependency counts */
357 for (i = papa->kids; i != NULL; i = i->next)
358 if (strcmp(i->name, "All") == 0)
360 for (i = i->kids; i != NULL; i = i->next)
361 if (((IndexEntryPtr)i->data)->installed)
362 index_recorddeps(TRUE, papa, i->data);
368 index_init(PkgNodePtr top, PkgNodePtr plist)
371 top->next = top->kids = NULL;
372 top->name = "Package Selection";
374 top->desc = fetch_desc(top->name);
378 plist->next = plist->kids = NULL;
379 plist->name = "Package Targets";
381 plist->desc = fetch_desc(plist->name);
387 index_print(PkgNodePtr top, int level)
392 for (i = 0; i < level; i++) putchar('\t');
393 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
394 for (i = 0; i < level; i++) putchar('\t');
395 printf("desc: %s\n", top->desc);
397 index_print(top->kids, level + 1);
402 /* Swap one node for another */
404 swap_nodes(PkgNodePtr a, PkgNodePtr b)
415 /* Use a disgustingly simplistic bubble sort to put our lists in order */
417 index_sort(PkgNodePtr top)
421 /* Sort everything at the top level */
422 for (p = top->kids; p; p = p->next) {
423 for (q = top->kids; q; q = q->next) {
424 if (q->next && strcmp(q->name, q->next->name) > 0)
425 swap_nodes(q, q->next);
429 /* Now sub-sort everything n levels down */
430 for (p = top->kids; p; p = p->next) {
436 /* Delete an entry out of the list it's in (only the plist, at present) */
438 index_delete(PkgNodePtr n)
441 PkgNodePtr p = n->next;
446 else /* Kludgy end sentinal */
451 * Search for a given node by name, returning the category in if
455 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
459 for (p = top->kids; p && p->name; p = p->next) {
460 if (p->type == PACKAGE) {
461 /* If tp == NULL, we're looking for an exact package match */
462 if (!tp && !strcmp(p->name, str))
465 /* If tp, we're looking for both a package and a pointer to the place it's in */
466 if (tp && !strncmp(p->name, str, strlen(str))) {
472 /* The usual recursion-out-of-laziness ploy */
473 if ((sp = index_search(p, str, tp)) != NULL)
483 pkg_checked(dialogMenuItem *self)
485 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
486 PkgNodePtr kp = self->data, plist = lists->plist;
489 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
490 if (kp->type == PACKAGE && plist) {
491 IndexEntryPtr ie = kp->data;
494 markD = ie->depc > 0; /* needed as dependency */
495 markX = i || ie->installed; /* selected or installed */
496 self->mark = markX ? 'X' : 'D';
497 return markD || markX;
503 pkg_fire(dialogMenuItem *self)
506 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
507 PkgNodePtr sp, kp = self->data, plist = lists->plist;
511 else if (kp->type == PACKAGE) {
512 IndexEntryPtr ie = kp->data;
514 sp = index_search(plist, kp->name, NULL);
515 /* Not already selected? */
517 if (!ie->installed) {
518 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
521 np->next = plist->kids;
523 index_recorddeps(TRUE, lists->root, ie);
524 msgInfo("Added %s to selection list", kp->name);
526 else if (ie->depc == 0) {
527 if (!msgNoYes("Do you really want to delete %s from the system?", kp->name)) {
528 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
529 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
533 index_recorddeps(FALSE, lists->root, ie);
538 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
539 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
542 index_recorddeps(FALSE, lists->root, ie);
543 msgInfo("Removed %s from selection list", kp->name);
547 /* Mark menu for redraw if we had dependencies */
548 if (strlen(ie->deps) > 0)
551 else { /* Not a package, must be a directory */
555 index_menu(lists->root, kp, plist, &p, &s);
556 ret = DITEM_SUCCESS | DITEM_CONTINUE;
562 pkg_selected(dialogMenuItem *self, int is_selected)
564 PkgNodePtr kp = self->data;
566 if (!is_selected || kp->type != PACKAGE)
568 msgInfo("%s", kp->desc);
572 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
574 struct ListPtrs lists;
579 dialogMenuItem *nitems;
591 /* Figure out if this menu is full of "leaves" or "branches" */
592 for (kp = top->kids; kp && kp->name; kp = kp->next) {
596 if (kp->type == PACKAGE && plist) {
598 if ((len = strlen(kp->name)) > maxname)
603 msgConfirm("The %s menu is empty.", top->name);
604 return DITEM_LEAVE_MENU;
614 if (!hasPackages && plist) {
615 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
616 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
618 while (kp && kp->name) {
620 IndexEntryPtr ie = kp->data;
622 /* Brutally adjust description to fit in menu */
623 if (kp->type == PACKAGE)
624 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
626 SAFE_STRCPY(buf, kp->desc);
627 if (strlen(buf) > (_MAX_DESC - maxname))
628 buf[_MAX_DESC - maxname] = '\0';
629 nitems = item_add(nitems, kp->name, (char *)buf, pkg_checked,
630 pkg_fire, pkg_selected, kp, (int *)(&lists),
635 /* NULL delimiter so item_free() knows when to stop later */
636 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
640 dialog_clear_norefresh();
642 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
644 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
645 if (rval == -1 && plist) {
650 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
651 PkgNodePtr p = index_search(top, cp, &menu);
656 /* These need to be set to point at the found item, actually. Hmmm! */
658 index_menu(root, menu, plist, &pos, &scroll);
661 msgConfirm("Search string: %s yielded no hits.", cp);
665 items_free(nitems, &curr, &max);
667 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
672 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
674 int status = DITEM_SUCCESS;
676 IndexEntryPtr id = who->data;
677 WINDOW *w = savescr();
680 * Short-circuit the package dependency checks. We're already
681 * maintaining a data structure of installed packages, so if a
682 * package is already installed, don't try to check to make sure
683 * that all of its dependencies are installed. At best this
684 * wastes a ton of cycles and can cause minor delays between
685 * package extraction. At worst it can cause an infinite loop with
686 * a certain faulty INDEX file.
689 if (id->installed == 1)
690 return DITEM_SUCCESS;
692 if (id && id->deps && strlen(id->deps)) {
693 char t[2048 * 8], *cp, *cp2;
695 SAFE_STRCPY(t, id->deps);
697 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
698 if ((cp2 = index(cp, ' ')) != NULL)
700 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
701 status = index_extract(dev, top, tmp2, TRUE);
702 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
703 if (variable_get(VAR_NO_CONFIRM))
704 msgNotify("Loading of dependent package %s failed", cp);
706 msgConfirm("Loading of dependent package %s failed", cp);
709 else if (!package_installed(cp)) {
710 if (variable_get(VAR_NO_CONFIRM))
711 msgNotify("Warning: %s is a required package but was not found.", cp);
713 msgConfirm("Warning: %s is a required package but was not found.", cp);
721 /* Done with the deps? Load the real m'coy */
722 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
723 /* Prompt user if the package is not available on the current volume. */
724 if(mediaDevice->type == DEVICE_TYPE_CDROM) {
725 while (id->volume != dev->volume) {
726 if (!msgYesNo("This is disc #%d. Package %s is on disc #%d\n"
727 "Would you like to switch discs now?\n", dev->volume,
728 id->name, id->volume)) {
729 DEVICE_SHUTDOWN(mediaDevice);
730 msgConfirm("Please remove disc #%d from your drive, and add disc #%d\n",
731 dev->volume, id->volume);
732 DEVICE_INIT(mediaDevice);
734 return DITEM_FAILURE;
738 status = package_extract(dev, who->name, depended);
739 if (DITEM_STATUS(status) == DITEM_SUCCESS)
747 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
749 char depends[1024 * 16], *space, *todo;
751 IndexEntryPtr found_ie;
753 SAFE_STRCPY(depends, ie->deps);
754 for (todo = depends; todo != NULL; ) {
755 space = index(todo, ' ');
759 if (strlen(todo) > 0) { /* only non-empty dependencies */
760 found = index_search(root, todo, NULL);
762 found_ie = found->data;
777 static Boolean index_initted;
779 /* Read and initialize global index */
781 index_initialize(char *path)
786 if (!index_initted) {
788 dialog_clear_norefresh();
791 if (!mediaVerify()) {
793 return DITEM_FAILURE;
796 /* Does it move when you kick it? */
797 if (!DEVICE_INIT(mediaDevice)) {
799 return DITEM_FAILURE;
802 dialog_clear_norefresh();
803 msgNotify("Attempting to fetch %s file from selected media.", path);
804 fp = DEVICE_GET(mediaDevice, path, TRUE);
806 msgConfirm("Unable to get packages/INDEX file from selected media.\n\n"
807 "This may be because the packages collection is not available\n"
808 "on the distribution media you've chosen, most likely an FTP site\n"
809 "without the packages collection mirrored. Please verify that\n"
810 "your media, or your path to the media, is correct and try again.");
811 DEVICE_SHUTDOWN(mediaDevice);
813 return DITEM_FAILURE;
815 dialog_clear_norefresh();
816 msgNotify("Located INDEX, now reading package data from it...");
817 index_init(&Top, &Plist);
818 if (index_read(fp, &Top)) {
819 msgConfirm("I/O or format error on packages/INDEX file.\n"
820 "Please verify media (or path to media) and try again.");
823 return DITEM_FAILURE;
827 index_initted = TRUE;
830 return DITEM_SUCCESS;