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 "kld", "Kernel loadable modules",
118 "korean", "Ported software for the Korean market.",
119 "lang", "Computer languages.",
120 "linux", "Linux programs that can run under binary compatibility.",
121 "lisp", "Software related to the Lisp language.",
122 "mail", "Electronic mail packages and utilities.",
123 "math", "Mathematical computation software.",
124 "mbone", "Applications and utilities for the MBONE.",
125 "misc", "Miscellaneous utilities.",
126 "multimedia", "Multimedia software.",
127 "net", "Networking utilities.",
128 "net-im", "Instant messaging software.",
129 "net-mgmt", "Network management tools.",
130 "net-p2p", "Peer to peer network applications.",
131 "news", "USENET News support software.",
132 "palm", "Software support for the Palm(tm) series.",
133 "parallel", "Applications dealing with parallelism in computing.",
134 "pear", "Software related to the Pear PHP framework.",
135 "perl5", "Utilities/modules for the PERL5 language.",
136 "plan9", "Software from the Plan9 operating system.",
137 "polish", "Ported software for the Polish market.",
138 "ports-mgmt", "Utilities for managing ports and packages.",
139 "portuguese", "Ported software for the Portuguese market.",
140 "print", "Utilities for dealing with printing.",
141 "python", "Software related to the Python language.",
142 "ruby", "Software related to the Ruby language.",
143 "rubygems", "Ports of RubyGems packages.",
144 "russian", "Ported software for the Russian market.",
145 "scheme", "Software related to the Scheme language.",
146 "science", "Scientific software.",
147 "security", "System security software.",
148 "shells", "Various shells (tcsh, bash, etc).",
149 "spanish", "Ported software for the Spanish market.",
150 "sysutils", "Various system utilities.",
151 "tcl", "TCL and packages that depend on it.",
152 "tcl80", "TCL v8.0 and packages that depend on it.",
153 "tcl82", "TCL v8.2 and packages that depend on it.",
154 "tcl83", "TCL v8.3 and packages that depend on it.",
155 "tcl84", "TCL v8.4 and packages that depend on it.",
156 "textproc", "Text processing/search utilities.",
157 "tk", "Tk and packages that depend on it.",
158 "tk80", "Tk8.0 and packages that depend on it.",
159 "tk82", "Tk8.2 and packages that depend on it.",
160 "tk83", "Tk8.3 and packages that depend on it.",
161 "tk84", "Tk8.4 and packages that depend on it.",
162 "tkstep80", "Ports to support the TkStep window manager.",
163 "ukrainian", "Ported software for the Ukrainian market.",
164 "vietnamese", "Ported software for the Vietnamese market.",
165 "windowmaker", "Ports to support the WindowMaker window manager.",
166 "www", "Web utilities (browsers, HTTP servers, etc).",
167 "x11", "X Window System based utilities.",
168 "x11-clocks", "X Window System based clocks.",
169 "x11-drivers", "X Window System drivers.",
170 "x11-fm", "X Window System based file managers.",
171 "x11-fonts", "X Window System fonts and font utilities.",
172 "x11-servers", "X Window System servers.",
173 "x11-themes", "X Window System themes.",
174 "x11-toolkits", "X Window System based development toolkits.",
175 "x11-wm", "X Window System window managers.",
176 "xfce", "Software related to the Xfce Desktop Environment.",
177 "zope", "Software related to the Zope platform.",
182 fetch_desc(char *name)
186 for (i = 0; descrs[i]; i += 2) {
187 if (!strcmp(descrs[i], name))
188 return descrs[i + 1];
190 return "No description provided";
194 new_pkg_node(char *name, node_type type)
196 PkgNodePtr tmp = safe_malloc(sizeof(PkgNode));
198 tmp->name = _strdup(name);
208 for (i = 0; buf[i]; i++)
209 if (buf[i] == '\t' || buf[i] == '\n')
215 new_index(char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *deps, int volume)
217 IndexEntryPtr tmp = safe_malloc(sizeof(IndexEntry));
219 tmp->name = _strdup(name);
220 tmp->path = _strdup(pathto);
221 tmp->prefix = _strdup(prefix);
222 tmp->comment = _strdup(comment);
223 tmp->descrfile = strip(_strdup(descr));
224 tmp->maintainer = _strdup(maint);
225 tmp->deps = _strdup(deps);
227 tmp->installed = package_installed(name);
228 tmp->volume = volume;
233 index_register(PkgNodePtr top, char *where, IndexEntryPtr ptr)
237 for (q = NULL, p = top->kids; p; p = p->next) {
238 if (!strcmp(p->name, where)) {
244 /* Add new category */
245 q = new_pkg_node(where, PLACE);
246 q->desc = fetch_desc(where);
250 p = new_pkg_node(ptr->name, PACKAGE);
251 p->desc = ptr->comment;
258 copy_to_sep(char *to, char *from, int sep)
262 tok = strchr(from, sep);
269 return tok + 1 - from;
273 skip_to_sep(char *from, int sep)
277 tok = strchr(from, sep);
281 return tok + 1 - from;
285 readline(FILE *fp, char *buf, int max)
290 while ((rv = fread(&ch, 1, 1, fp)) == 1 && ch != '\n' && i < max)
298 * XXX - this function should do error checking, and skip corrupted INDEX
299 * lines without a set number of '|' delimited fields.
303 index_parse(FILE *fp, char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *cats, char *rdeps, int *volume)
305 char line[10240 + 2048 * 7];
311 i = readline(fp, line, sizeof line);
315 cp += copy_to_sep(name, cp, '|'); /* package name */
316 cp += copy_to_sep(pathto, cp, '|'); /* ports directory */
317 cp += copy_to_sep(prefix, cp, '|'); /* prefix */
318 cp += copy_to_sep(comment, cp, '|'); /* comment */
319 cp += copy_to_sep(descr, cp, '|'); /* path to pkg-descr */
320 cp += copy_to_sep(maint, cp, '|'); /* maintainer */
321 cp += copy_to_sep(cats, cp, '|'); /* categories */
322 cp += skip_to_sep(cp, '|'); /* build deps - not used */
323 cp += copy_to_sep(rdeps, cp, '|'); /* run deps */
325 cp += skip_to_sep(cp, '|'); /* url - not used */
327 strncpy(junk, cp, 1023);
332 cp += skip_to_sep(cp, '|'); /* extract deps - not used */
334 cp += skip_to_sep(cp, '|'); /* patch deps - not used */
336 cp += skip_to_sep(cp, '|'); /* fetch deps - not used */
338 cp += copy_to_sep(volstr, cp, '|'); /* media volume */
340 strncpy(volstr, cp, 1023);
342 *volume = atoi(volstr);
347 index_read(FILE *fp, PkgNodePtr papa)
349 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[2048 * 8];
353 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps, &volume) != EOF) {
354 char *cp, *cp2, tmp[1024];
357 idx = new_index(name, pathto, prefix, comment, descr, maint, deps, volume);
358 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
359 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
361 index_register(papa, cp, idx);
363 index_register(papa, cp, idx);
365 /* Add to special "All" category */
366 index_register(papa, "All", idx);
369 /* Adjust dependency counts */
370 for (i = papa->kids; i != NULL; i = i->next)
371 if (strcmp(i->name, "All") == 0)
373 for (i = i->kids; i != NULL; i = i->next)
374 if (((IndexEntryPtr)i->data)->installed)
375 index_recorddeps(TRUE, papa, i->data);
381 index_init(PkgNodePtr top, PkgNodePtr plist)
384 top->next = top->kids = NULL;
385 top->name = "Package Selection";
387 top->desc = fetch_desc(top->name);
391 plist->next = plist->kids = NULL;
392 plist->name = "Package Targets";
394 plist->desc = fetch_desc(plist->name);
400 index_print(PkgNodePtr top, int level)
405 for (i = 0; i < level; i++) putchar('\t');
406 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
407 for (i = 0; i < level; i++) putchar('\t');
408 printf("desc: %s\n", top->desc);
410 index_print(top->kids, level + 1);
415 /* Swap one node for another */
417 swap_nodes(PkgNodePtr a, PkgNodePtr b)
428 /* Use a disgustingly simplistic bubble sort to put our lists in order */
430 index_sort(PkgNodePtr top)
434 /* Sort everything at the top level */
435 for (p = top->kids; p; p = p->next) {
436 for (q = top->kids; q; q = q->next) {
437 if (q->next && strcmp(q->name, q->next->name) > 0)
438 swap_nodes(q, q->next);
442 /* Now sub-sort everything n levels down */
443 for (p = top->kids; p; p = p->next) {
449 /* Delete an entry out of the list it's in (only the plist, at present) */
451 index_delete(PkgNodePtr n)
454 PkgNodePtr p = n->next;
459 else /* Kludgy end sentinal */
464 * Search for a given node by name, returning the category in if
468 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
472 for (p = top->kids; p && p->name; p = p->next) {
473 if (p->type == PACKAGE) {
474 /* If tp == NULL, we're looking for an exact package match */
475 if (!tp && !strcmp(p->name, str))
478 /* If tp, we're looking for both a package and a pointer to the place it's in */
479 if (tp && !strncmp(p->name, str, strlen(str))) {
485 /* The usual recursion-out-of-laziness ploy */
486 if ((sp = index_search(p, str, tp)) != NULL)
496 pkg_checked(dialogMenuItem *self)
498 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
499 PkgNodePtr kp = self->data, plist = lists->plist;
502 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
503 if (kp->type == PACKAGE && plist) {
504 IndexEntryPtr ie = kp->data;
507 markD = ie->depc > 0; /* needed as dependency */
508 markX = i || ie->installed; /* selected or installed */
509 self->mark = markX ? 'X' : 'D';
510 return markD || markX;
516 pkg_fire(dialogMenuItem *self)
519 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
520 PkgNodePtr sp, kp = self->data, plist = lists->plist;
524 else if (kp->type == PACKAGE) {
525 IndexEntryPtr ie = kp->data;
527 sp = index_search(plist, kp->name, NULL);
528 /* Not already selected? */
530 if (!ie->installed) {
531 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
534 np->next = plist->kids;
536 index_recorddeps(TRUE, lists->root, ie);
537 msgInfo("Added %s to selection list", kp->name);
539 else if (ie->depc == 0) {
540 if (!msgNoYes("Do you really want to delete %s from the system?", kp->name)) {
541 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
542 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
546 index_recorddeps(FALSE, lists->root, ie);
551 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
552 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
555 index_recorddeps(FALSE, lists->root, ie);
556 msgInfo("Removed %s from selection list", kp->name);
560 /* Mark menu for redraw if we had dependencies */
561 if (strlen(ie->deps) > 0)
564 else { /* Not a package, must be a directory */
568 index_menu(lists->root, kp, plist, &p, &s);
569 ret = DITEM_SUCCESS | DITEM_CONTINUE;
575 pkg_selected(dialogMenuItem *self, int is_selected)
577 PkgNodePtr kp = self->data;
579 if (!is_selected || kp->type != PACKAGE)
581 msgInfo("%s", kp->desc);
585 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
587 struct ListPtrs lists;
588 int n, rval, maxname;
591 dialogMenuItem *nitems;
603 /* Figure out if this menu is full of "leaves" or "branches" */
604 for (kp = top->kids; kp && kp->name; kp = kp->next) {
608 if (kp->type == PACKAGE && plist) {
610 if ((len = strlen(kp->name)) > maxname)
615 msgConfirm("The %s menu is empty.", top->name);
616 return DITEM_LEAVE_MENU;
626 if (!hasPackages && plist) {
627 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
628 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, NULL, &curr, &max);
630 while (kp && kp->name) {
632 IndexEntryPtr ie = kp->data;
634 /* Brutally adjust description to fit in menu */
635 if (kp->type == PACKAGE)
636 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
638 SAFE_STRCPY(buf, kp->desc);
639 if (strlen(buf) > (_MAX_DESC - maxname))
640 buf[_MAX_DESC - maxname] = '\0';
641 nitems = item_add(nitems, kp->name, (char *)buf, pkg_checked,
642 pkg_fire, pkg_selected, kp, (int *)(&lists),
647 /* NULL delimiter so item_free() knows when to stop later */
648 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
652 dialog_clear_norefresh();
654 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
656 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
657 if (rval == -1 && plist) {
662 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
663 PkgNodePtr p = index_search(top, cp, &menu);
668 /* These need to be set to point at the found item, actually. Hmmm! */
670 index_menu(root, menu, plist, &pos, &scroll);
673 msgConfirm("Search string: %s yielded no hits.", cp);
677 items_free(nitems, &curr, &max);
679 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
684 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
686 int status = DITEM_SUCCESS;
688 IndexEntryPtr id = who->data;
692 * Short-circuit the package dependency checks. We're already
693 * maintaining a data structure of installed packages, so if a
694 * package is already installed, don't try to check to make sure
695 * that all of its dependencies are installed. At best this
696 * wastes a ton of cycles and can cause minor delays between
697 * package extraction. At worst it can cause an infinite loop with
698 * a certain faulty INDEX file.
701 if (id->installed == 1)
702 return DITEM_SUCCESS;
705 if (id && id->deps && strlen(id->deps)) {
706 char t[2048 * 8], *cp, *cp2;
708 SAFE_STRCPY(t, id->deps);
710 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
711 if ((cp2 = index(cp, ' ')) != NULL)
713 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
714 status = index_extract(dev, top, tmp2, TRUE);
715 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
716 if (variable_get(VAR_NO_CONFIRM))
717 msgNotify("Loading of dependent package %s failed", cp);
719 msgConfirm("Loading of dependent package %s failed", cp);
722 else if (!package_installed(cp)) {
723 if (variable_get(VAR_NO_CONFIRM))
724 msgNotify("Warning: %s is a required package but was not found.", cp);
726 msgConfirm("Warning: %s is a required package but was not found.", cp);
734 /* Done with the deps? Load the real m'coy */
735 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
736 /* Prompt user if the package is not available on the current volume. */
737 if(mediaDevice->type == DEVICE_TYPE_CDROM) {
738 while (id->volume != dev->volume) {
739 if (!msgYesNo("This is disc #%d. Package %s is on disc #%d\n"
740 "Would you like to switch discs now?\n", dev->volume,
741 id->name, id->volume)) {
742 DEVICE_SHUTDOWN(mediaDevice);
743 msgConfirm("Please remove disc #%d from your drive, and add disc #%d\n",
744 dev->volume, id->volume);
745 DEVICE_INIT(mediaDevice);
748 return DITEM_FAILURE;
752 status = package_extract(dev, who->name, depended);
753 if (DITEM_STATUS(status) == DITEM_SUCCESS)
761 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
763 char depends[1024 * 16], *space, *todo;
765 IndexEntryPtr found_ie;
767 SAFE_STRCPY(depends, ie->deps);
768 for (todo = depends; todo != NULL; ) {
769 space = index(todo, ' ');
773 if (strlen(todo) > 0) { /* only non-empty dependencies */
774 found = index_search(root, todo, NULL);
776 found_ie = found->data;
791 static Boolean index_initted;
793 /* Read and initialize global index */
795 index_initialize(char *path)
800 if (!index_initted) {
802 dialog_clear_norefresh();
805 if (!mediaVerify()) {
807 return DITEM_FAILURE;
810 /* Does it move when you kick it? */
811 if (!DEVICE_INIT(mediaDevice)) {
813 return DITEM_FAILURE;
816 dialog_clear_norefresh();
817 msgNotify("Attempting to fetch %s file from selected media.", path);
818 fp = DEVICE_GET(mediaDevice, path, TRUE);
820 msgConfirm("Unable to get packages/INDEX file from selected media.\n\n"
821 "This may be because the packages collection is not available\n"
822 "on the distribution media you've chosen, most likely an FTP site\n"
823 "without the packages collection mirrored. Please verify that\n"
824 "your media, or your path to the media, is correct and try again.");
825 DEVICE_SHUTDOWN(mediaDevice);
827 return DITEM_FAILURE;
829 dialog_clear_norefresh();
830 msgNotify("Located INDEX, now reading package data from it...");
831 index_init(&Top, &Plist);
832 if (index_read(fp, &Top)) {
833 msgConfirm("I/O or format error on packages/INDEX file.\n"
834 "Please verify media (or path to media) and try again.");
837 return DITEM_FAILURE;
841 index_initted = TRUE;
844 return DITEM_SUCCESS;