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 "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 "tcl84", "TCL v8.4 and packages that depend on it.",
155 "textproc", "Text processing/search utilities.",
156 "tk80", "Tk8.0 and packages that depend on it.",
157 "tk82", "Tk8.2 and packages that depend on it.",
158 "tk83", "Tk8.3 and packages that depend on it.",
159 "tk84", "Tk8.4 and packages that depend on it.",
160 "tkstep80", "Ports to support the TkStep window manager.",
161 "ukrainian", "Ported software for the Ukrainian market.",
162 "vietnamese", "Ported software for the Vietnamese market.",
163 "windowmaker", "Ports to support the WindowMaker window manager.",
164 "www", "Web utilities (browsers, HTTP servers, etc).",
165 "x11", "X Window System based utilities.",
166 "x11-clocks", "X Window System based clocks.",
167 "x11-fm", "X Window System based file managers.",
168 "x11-fonts", "X Window System fonts and font utilities.",
169 "x11-servers", "X Window System servers.",
170 "x11-themes", "X Window System themes.",
171 "x11-toolkits", "X Window System based development toolkits.",
172 "x11-wm", "X Window System window managers.",
173 "xfce", "Software related to the Xfce Desktop Environment.",
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_installed(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, '|'); /* package name */
301 cp += copy_to_sep(pathto, cp, '|'); /* ports directory */
302 cp += copy_to_sep(prefix, cp, '|'); /* prefix */
303 cp += copy_to_sep(comment, cp, '|'); /* comment */
304 cp += copy_to_sep(descr, cp, '|'); /* path to pkg-descr */
305 cp += copy_to_sep(maint, cp, '|'); /* maintainer */
306 cp += copy_to_sep(cats, cp, '|'); /* categories */
307 cp += copy_to_sep(junk, cp, '|'); /* build deps - not used */
308 cp += copy_to_sep(rdeps, cp, '|'); /* run deps */
310 cp += copy_to_sep(junk, cp, '|'); /* url - not used */
312 strncpy(junk, cp, 1023);
317 cp += copy_to_sep(junk, cp, '|'); /* extract deps - not used */
319 cp += copy_to_sep(junk, cp, '|'); /* patch deps - not used */
321 cp += copy_to_sep(junk, cp, '|'); /* fetch deps - not used */
323 cp += copy_to_sep(volstr, cp, '|'); /* media volume */
325 strncpy(volstr, cp, 1023);
327 *volume = atoi(volstr);
332 index_read(FILE *fp, PkgNodePtr papa)
334 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[2048 * 8];
338 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps, &volume) != EOF) {
339 char *cp, *cp2, tmp[1024];
342 idx = new_index(name, pathto, prefix, comment, descr, maint, deps, volume);
343 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
344 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
346 index_register(papa, cp, idx);
348 index_register(papa, cp, idx);
350 /* Add to special "All" category */
351 index_register(papa, "All", idx);
354 /* Adjust dependency counts */
355 for (i = papa->kids; i != NULL; i = i->next)
356 if (strcmp(i->name, "All") == 0)
358 for (i = i->kids; i != NULL; i = i->next)
359 if (((IndexEntryPtr)i->data)->installed)
360 index_recorddeps(TRUE, papa, i->data);
366 index_init(PkgNodePtr top, PkgNodePtr plist)
369 top->next = top->kids = NULL;
370 top->name = "Package Selection";
372 top->desc = fetch_desc(top->name);
376 plist->next = plist->kids = NULL;
377 plist->name = "Package Targets";
379 plist->desc = fetch_desc(plist->name);
385 index_print(PkgNodePtr top, int level)
390 for (i = 0; i < level; i++) putchar('\t');
391 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
392 for (i = 0; i < level; i++) putchar('\t');
393 printf("desc: %s\n", top->desc);
395 index_print(top->kids, level + 1);
400 /* Swap one node for another */
402 swap_nodes(PkgNodePtr a, PkgNodePtr b)
413 /* Use a disgustingly simplistic bubble sort to put our lists in order */
415 index_sort(PkgNodePtr top)
419 /* Sort everything at the top level */
420 for (p = top->kids; p; p = p->next) {
421 for (q = top->kids; q; q = q->next) {
422 if (q->next && strcmp(q->name, q->next->name) > 0)
423 swap_nodes(q, q->next);
427 /* Now sub-sort everything n levels down */
428 for (p = top->kids; p; p = p->next) {
434 /* Delete an entry out of the list it's in (only the plist, at present) */
436 index_delete(PkgNodePtr n)
439 PkgNodePtr p = n->next;
444 else /* Kludgy end sentinal */
449 * Search for a given node by name, returning the category in if
453 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
457 for (p = top->kids; p && p->name; p = p->next) {
458 if (p->type == PACKAGE) {
459 /* If tp == NULL, we're looking for an exact package match */
460 if (!tp && !strcmp(p->name, str))
463 /* If tp, we're looking for both a package and a pointer to the place it's in */
464 if (tp && !strncmp(p->name, str, strlen(str))) {
470 /* The usual recursion-out-of-laziness ploy */
471 if ((sp = index_search(p, str, tp)) != NULL)
481 pkg_checked(dialogMenuItem *self)
483 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
484 PkgNodePtr kp = self->data, plist = lists->plist;
487 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
488 if (kp->type == PACKAGE && plist) {
489 IndexEntryPtr ie = kp->data;
492 markD = ie->depc > 0; /* needed as dependency */
493 markX = i || ie->installed; /* selected or installed */
494 self->mark = markX ? 'X' : 'D';
495 return markD || markX;
501 pkg_fire(dialogMenuItem *self)
504 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
505 PkgNodePtr sp, kp = self->data, plist = lists->plist;
509 else if (kp->type == PACKAGE) {
510 IndexEntryPtr ie = kp->data;
512 sp = index_search(plist, kp->name, NULL);
513 /* Not already selected? */
515 if (!ie->installed) {
516 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
519 np->next = plist->kids;
521 index_recorddeps(TRUE, lists->root, ie);
522 msgInfo("Added %s to selection list", kp->name);
524 else if (ie->depc == 0) {
525 if (!msgNoYes("Do you really want to delete %s from the system?", kp->name)) {
526 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
527 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
531 index_recorddeps(FALSE, lists->root, ie);
536 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
537 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
540 index_recorddeps(FALSE, lists->root, ie);
541 msgInfo("Removed %s from selection list", kp->name);
545 /* Mark menu for redraw if we had dependencies */
546 if (strlen(ie->deps) > 0)
549 else { /* Not a package, must be a directory */
553 index_menu(lists->root, kp, plist, &p, &s);
554 ret = DITEM_SUCCESS | DITEM_CONTINUE;
560 pkg_selected(dialogMenuItem *self, int is_selected)
562 PkgNodePtr kp = self->data;
564 if (!is_selected || kp->type != PACKAGE)
566 msgInfo("%s", kp->desc);
570 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
572 struct ListPtrs lists;
577 dialogMenuItem *nitems;
589 /* Figure out if this menu is full of "leaves" or "branches" */
590 for (kp = top->kids; kp && kp->name; kp = kp->next) {
594 if (kp->type == PACKAGE && plist) {
596 if ((len = strlen(kp->name)) > maxname)
601 msgConfirm("The %s menu is empty.", top->name);
602 return DITEM_LEAVE_MENU;
612 if (!hasPackages && plist) {
613 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
614 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
616 while (kp && kp->name) {
618 IndexEntryPtr ie = kp->data;
620 /* Brutally adjust description to fit in menu */
621 if (kp->type == PACKAGE)
622 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
624 SAFE_STRCPY(buf, kp->desc);
625 if (strlen(buf) > (_MAX_DESC - maxname))
626 buf[_MAX_DESC - maxname] = '\0';
627 nitems = item_add(nitems, kp->name, (char *)buf, pkg_checked,
628 pkg_fire, pkg_selected, kp, (int *)(&lists),
633 /* NULL delimiter so item_free() knows when to stop later */
634 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
638 dialog_clear_norefresh();
640 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
642 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
643 if (rval == -1 && plist) {
648 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
649 PkgNodePtr p = index_search(top, cp, &menu);
654 /* These need to be set to point at the found item, actually. Hmmm! */
656 index_menu(root, menu, plist, &pos, &scroll);
659 msgConfirm("Search string: %s yielded no hits.", cp);
663 items_free(nitems, &curr, &max);
665 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
670 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
672 int status = DITEM_SUCCESS;
674 IndexEntryPtr id = who->data;
675 WINDOW *w = savescr();
678 * Short-circuit the package dependency checks. We're already
679 * maintaining a data structure of installed packages, so if a
680 * package is already installed, don't try to check to make sure
681 * that all of its dependencies are installed. At best this
682 * wastes a ton of cycles and can cause minor delays between
683 * package extraction. At worst it can cause an infinite loop with
684 * a certain faulty INDEX file.
687 if (id->installed == 1)
688 return DITEM_SUCCESS;
690 if (id && id->deps && strlen(id->deps)) {
691 char t[2048 * 8], *cp, *cp2;
693 SAFE_STRCPY(t, id->deps);
695 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
696 if ((cp2 = index(cp, ' ')) != NULL)
698 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
699 status = index_extract(dev, top, tmp2, TRUE);
700 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
701 if (variable_get(VAR_NO_CONFIRM))
702 msgNotify("Loading of dependent package %s failed", cp);
704 msgConfirm("Loading of dependent package %s failed", cp);
707 else if (!package_installed(cp)) {
708 if (variable_get(VAR_NO_CONFIRM))
709 msgNotify("Warning: %s is a required package but was not found.", cp);
711 msgConfirm("Warning: %s is a required package but was not found.", cp);
719 /* Done with the deps? Load the real m'coy */
720 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
721 /* Prompt user if the package is not available on the current volume. */
722 if(mediaDevice->type == DEVICE_TYPE_CDROM) {
723 while (id->volume != dev->volume) {
724 if (!msgYesNo("This is disc #%d. Package %s is on disc #%d\n"
725 "Would you like to switch discs now?\n", dev->volume,
726 id->name, id->volume)) {
727 DEVICE_SHUTDOWN(mediaDevice);
728 msgConfirm("Please remove disc #%d from your drive, and add disc #%d\n",
729 dev->volume, id->volume);
730 DEVICE_INIT(mediaDevice);
732 return DITEM_FAILURE;
736 status = package_extract(dev, who->name, depended);
737 if (DITEM_STATUS(status) == DITEM_SUCCESS)
745 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
747 char depends[1024 * 16], *space, *todo;
749 IndexEntryPtr found_ie;
751 SAFE_STRCPY(depends, ie->deps);
752 for (todo = depends; todo != NULL; ) {
753 space = index(todo, ' ');
757 if (strlen(todo) > 0) { /* only non-empty dependencies */
758 found = index_search(root, todo, NULL);
760 found_ie = found->data;
775 static Boolean index_initted;
777 /* Read and initialize global index */
779 index_initialize(char *path)
784 if (!index_initted) {
786 dialog_clear_norefresh();
789 if (!mediaVerify()) {
791 return DITEM_FAILURE;
794 /* Does it move when you kick it? */
795 if (!DEVICE_INIT(mediaDevice)) {
797 return DITEM_FAILURE;
800 dialog_clear_norefresh();
801 msgNotify("Attempting to fetch %s file from selected media.", path);
802 fp = DEVICE_GET(mediaDevice, path, TRUE);
804 msgConfirm("Unable to get packages/INDEX file from selected media.\n\n"
805 "This may be because the packages collection is not available\n"
806 "on the distribution media you've chosen, most likely an FTP site\n"
807 "without the packages collection mirrored. Please verify that\n"
808 "your media, or your path to the media, is correct and try again.");
809 DEVICE_SHUTDOWN(mediaDevice);
811 return DITEM_FAILURE;
813 dialog_clear_norefresh();
814 msgNotify("Located INDEX, now reading package data from it...");
815 index_init(&Top, &Plist);
816 if (index_read(fp, &Top)) {
817 msgConfirm("I/O or format error on packages/INDEX file.\n"
818 "Please verify media (or path to media) and try again.");
821 return DITEM_FAILURE;
825 index_initted = TRUE;
828 return DITEM_SUCCESS;