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.
10 * Jordan Hubbard. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer,
17 * verbatim and that no modifications are made prior to this
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
23 * THIS SOFTWARE IS PROVIDED BY JORDAN HUBBARD ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL JORDAN HUBBARD OR HIS PETS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, LIFE OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 #include "sysinstall.h"
45 /* Macros and magic values */
49 /* A structure holding the root, top and plist pointer at once */
52 PkgNodePtr root; /* root of tree */
53 PkgNodePtr top; /* part of tree we handle */
54 PkgNodePtr plist; /* list of selected packages */
56 typedef struct ListPtrs* ListPtrsPtr;
58 static void index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie);
60 /* Shared between index_initialize() and the various clients of it */
67 return ptr ? strdup(ptr) : NULL;
70 static char *descrs[] = {
71 "Package Selection", "To mark a package, move to it and press SPACE. If the package is\n"
72 "already marked, it will be unmarked or deleted (if installed).\n"
73 "Items marked with a `D' are dependencies which will be auto-loaded.\n"
74 "To search for a package by name, press ESC. To select a category,\n"
75 "press RETURN. NOTE: The All category selection creates a very large\n"
76 "submenu! If you select it, please be patient while it comes up.",
77 "Package Targets", "These are the packages you've selected for extraction.\n\n"
78 "If you're sure of these choices, select OK.\n"
79 "If not, select Cancel to go back to the package selection menu.\n",
80 "All", "All available packages in all categories.",
81 "afterstep", "Ports to support the AfterStep window manager.",
82 "applications", "User application software.",
83 "astro", "Applications related to astronomy.",
84 "archivers", "Utilities for archiving and unarchiving data.",
85 "audio", "Audio utilities - most require a supported sound card.",
86 "biology", "Software related to biology.",
87 "benchmarks", "Utilities for measuring system performance.",
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 "devel", "Software development utilities and libraries.",
94 "deskutils", "Various Desktop utilities.",
95 "documentation", "Document preparation utilities.",
96 "editors", "Common text editors.",
97 "elisp", "Things related to Emacs Lisp.",
98 "elisp", "Emacs lisp ports.",
99 "emulators", "Utilities for emulating other OS types.",
100 "ftp", "FTP client and server utilities.",
101 "games", "Various and sundry amusements.",
102 "german", "Ported software for Germanic countries.",
103 "graphics", "Graphics libraries and utilities.",
104 "gnome", "Components of the Gnome Desktop environment.",
105 "irc", "Internet Relay Chat utilities.",
106 "japanese", "Ported software for the Japanese market.",
107 "java", "Java language support.",
108 "kde", "Software for the K Desktop Environment.",
109 "korean", "Ported software for the Korean market.",
110 "lang", "Computer languages.",
111 "languages", "Computer languages.",
112 "libraries", "Software development libraries.",
113 "mail", "Electronic mail packages and utilities.",
114 "math", "Mathematical computation software.",
115 "mbone", "Applications and utilities for the MBONE.",
116 "misc", "Miscellaneous utilities.",
117 "net", "Networking utilities.",
118 "news", "USENET News support software.",
119 "numeric", "Mathematical computation software.",
120 "offix", "An office automation suite of sorts.",
121 "orphans", "Packages without a home elsewhere.",
122 "palm", "Software support for the 3Com Palm Pilot(tm) series.",
123 "pilot", "Software support for the 3Com Palm Pilot(tm) series.",
124 "perl5", "Utilities/modules for the PERL5 language.",
125 "plan9", "Software from the Plan9 operating system.",
126 "print", "Utilities for dealing with printing.",
127 "printing", "Utilities for dealing with printing.",
128 "programming", "Software development utilities and libraries.",
129 "python", "Software related to the Python language.",
130 "russian", "Ported software for the Russian market.",
131 "security", "System security software.",
132 "shells", "Various shells (tcsh, bash, etc).",
133 "sysutils", "Various system utilities.",
134 "textproc", "Text processing/search utilities.",
135 "tcl75", "TCL v7.5 and packages that depend on it.",
136 "tcl76", "TCL v7.6 and packages that depend on it.",
137 "tcl80", "TCL v8.0 and packages that depend on it.",
138 "tcl80", "TCL v8.0 and packages that depend on it.",
139 "tcl82", "TCL v8.2 and packages that depend on it.",
140 "tk41", "Tk4.1 and packages that depend on it.",
141 "tk42", "Tk4.2 and packages that depend on it.",
142 "tk80", "Tk8.0 and packages that depend on it.",
143 "tk81", "Tk8.1 and packages that depend on it.",
144 "tk82", "Tk8.2 and packages that depend on it.",
145 "tkstep80", "tkstep wm and packages that depend on it.",
146 "troff", "TROFF text formatting utilities.",
147 "vietnamese", "Ported software for the Vietnamese market.",
148 "windowmaker", "Ports to support the WindowMaker window manager.",
149 "www", "WEB utilities (browers, HTTP servers, etc).",
150 "x11", "X Window System based utilities.",
151 "x11-clocks", "X Window System based clocks.",
152 "x11-fm", "X Window System based file managers.",
153 "x11-fonts", "X Window System fonts and font utilities.",
154 "x11-servers", "X Window System servers.",
155 "x11-toolkits", "X Window System based development toolkits.",
156 "x11-wm", "X Window System window managers.",
161 fetch_desc(char *name)
165 for (i = 0; descrs[i]; i += 2) {
166 if (!strcmp(descrs[i], name))
167 return descrs[i + 1];
169 return "No description provided";
173 new_pkg_node(char *name, node_type type)
175 PkgNodePtr tmp = safe_malloc(sizeof(PkgNode));
177 tmp->name = _strdup(name);
187 for (i = 0; buf[i]; i++)
188 if (buf[i] == '\t' || buf[i] == '\n')
194 new_index(char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *deps)
196 IndexEntryPtr tmp = safe_malloc(sizeof(IndexEntry));
198 tmp->name = _strdup(name);
199 tmp->path = _strdup(pathto);
200 tmp->prefix = _strdup(prefix);
201 tmp->comment = _strdup(comment);
202 tmp->descrfile = strip(_strdup(descr));
203 tmp->maintainer = _strdup(maint);
204 tmp->deps = _strdup(deps);
206 tmp->installed = package_exists(name);
211 index_register(PkgNodePtr top, char *where, IndexEntryPtr ptr)
215 for (q = NULL, p = top->kids; p; p = p->next) {
216 if (!strcmp(p->name, where)) {
222 /* Add new category */
223 q = new_pkg_node(where, PLACE);
224 q->desc = fetch_desc(where);
228 p = new_pkg_node(ptr->name, PACKAGE);
229 p->desc = ptr->comment;
236 copy_to_sep(char *to, char *from, int sep)
240 tok = strchr(from, sep);
247 return tok + 1 - from;
251 readline(FILE *fp, char *buf, int max)
256 while ((rv = fread(&ch, 1, 1, fp)) == 1 && ch != '\n' && i < max)
264 index_parse(FILE *fp, char *name, char *pathto, char *prefix, char *comment, char *descr, char *maint, char *cats, char *rdeps)
271 i = readline(fp, line, 1024);
275 cp += copy_to_sep(name, cp, '|');
276 cp += copy_to_sep(pathto, cp, '|');
277 cp += copy_to_sep(prefix, cp, '|');
278 cp += copy_to_sep(comment, cp, '|');
279 cp += copy_to_sep(descr, cp, '|');
280 cp += copy_to_sep(maint, cp, '|');
281 cp += copy_to_sep(cats, cp, '|');
282 cp += copy_to_sep(junk, cp, '|'); /* build deps - not used */
284 copy_to_sep(rdeps, cp, '|');
286 strncpy(rdeps, cp, 510);
291 index_read(FILE *fp, PkgNodePtr papa)
293 char name[127], pathto[255], prefix[255], comment[255], descr[127], maint[127], cats[511], deps[511];
296 while (index_parse(fp, name, pathto, prefix, comment, descr, maint, cats, deps) != EOF) {
297 char *cp, *cp2, tmp[511];
300 idx = new_index(name, pathto, prefix, comment, descr, maint, deps);
301 /* For now, we only add things to menus if they're in categories. Keywords are ignored */
302 for (cp = strcpy(tmp, cats); (cp2 = strchr(cp, ' ')) != NULL; cp = cp2 + 1) {
304 index_register(papa, cp, idx);
306 index_register(papa, cp, idx);
308 /* Add to special "All" category */
309 index_register(papa, "All", idx);
312 /* Adjust dependency counts */
313 for (i = papa->kids; i != NULL; i = i->next)
314 if (strcmp(i->name, "All") == 0)
316 for (i = i->kids; i != NULL; i = i->next)
317 if (((IndexEntryPtr)i->data)->installed)
318 index_recorddeps(TRUE, papa, i->data);
324 index_init(PkgNodePtr top, PkgNodePtr plist)
327 top->next = top->kids = NULL;
328 top->name = "Package Selection";
330 top->desc = fetch_desc(top->name);
334 plist->next = plist->kids = NULL;
335 plist->name = "Package Targets";
337 plist->desc = fetch_desc(plist->name);
343 index_print(PkgNodePtr top, int level)
348 for (i = 0; i < level; i++) putchar('\t');
349 printf("name [%s]: %s\n", top->type == PLACE ? "place" : "package", top->name);
350 for (i = 0; i < level; i++) putchar('\t');
351 printf("desc: %s\n", top->desc);
353 index_print(top->kids, level + 1);
358 /* Swap one node for another */
360 swap_nodes(PkgNodePtr a, PkgNodePtr b)
371 /* Use a disgustingly simplistic bubble sort to put our lists in order */
373 index_sort(PkgNodePtr top)
377 /* Sort everything at the top level */
378 for (p = top->kids; p; p = p->next) {
379 for (q = top->kids; q; q = q->next) {
380 if (q->next && strcmp(q->name, q->next->name) > 0)
381 swap_nodes(q, q->next);
385 /* Now sub-sort everything n levels down */
386 for (p = top->kids; p; p = p->next) {
392 /* Delete an entry out of the list it's in (only the plist, at present) */
394 index_delete(PkgNodePtr n)
397 PkgNodePtr p = n->next;
402 else /* Kludgy end sentinal */
407 * Search for a given node by name, returning the category in if
411 index_search(PkgNodePtr top, char *str, PkgNodePtr *tp)
415 for (p = top->kids; p && p->name; p = p->next) {
416 if (p->type == PACKAGE) {
417 /* If tp == NULL, we're looking for an exact package match */
418 if (!tp && !strcmp(p->name, str))
421 /* If tp, we're looking for both a package and a pointer to the place it's in */
422 if (tp && !strncmp(p->name, str, strlen(str))) {
428 /* The usual recursion-out-of-laziness ploy */
429 if ((sp = index_search(p, str, tp)) != NULL)
439 pkg_checked(dialogMenuItem *self)
441 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
442 PkgNodePtr kp = self->data, plist = lists->plist;
445 i = index_search(plist, kp->name, NULL) ? TRUE : FALSE;
446 if (kp->type == PACKAGE && plist) {
447 IndexEntryPtr ie = kp->data;
450 markD = ie->depc > 0; /* needed as dependency */
451 markX = i || ie->installed; /* selected or installed */
452 self->mark = markX ? 'X' : 'D';
453 return markD || markX;
459 pkg_fire(dialogMenuItem *self)
462 ListPtrsPtr lists = (ListPtrsPtr)self->aux;
463 PkgNodePtr sp, kp = self->data, plist = lists->plist;
467 else if (kp->type == PACKAGE) {
468 IndexEntryPtr ie = kp->data;
470 sp = index_search(plist, kp->name, NULL);
471 /* Not already selected? */
473 if (!ie->installed) {
474 PkgNodePtr np = (PkgNodePtr)safe_malloc(sizeof(PkgNode));
477 np->next = plist->kids;
479 index_recorddeps(TRUE, lists->root, ie);
480 msgInfo("Added %s to selection list", kp->name);
482 else if (ie->depc == 0) {
483 WINDOW *save = savescr();
485 if (!msgYesNo("Do you really want to delete %s from the system?", kp->name)) {
486 if (vsystem("pkg_delete %s %s", isDebug() ? "-v" : "", kp->name)) {
487 msgConfirm("Warning: pkg_delete of %s failed.\n Check debug output for details.", kp->name);
491 index_recorddeps(FALSE, lists->root, ie);
497 msgConfirm("Warning: Package %s is needed by\n %d other installed package%s.",
498 kp->name, ie->depc, (ie->depc != 1) ? "s" : "");
501 index_recorddeps(FALSE, lists->root, ie);
502 msgInfo("Removed %s from selection list", kp->name);
506 /* Mark menu for redraw if we had dependencies */
507 if (strlen(ie->deps) > 0)
510 else { /* Not a package, must be a directory */
514 index_menu(lists->root, kp, plist, &p, &s);
515 ret = DITEM_SUCCESS | DITEM_CONTINUE;
521 pkg_selected(dialogMenuItem *self, int is_selected)
523 PkgNodePtr kp = self->data;
525 if (!is_selected || kp->type != PACKAGE)
531 index_menu(PkgNodePtr root, PkgNodePtr top, PkgNodePtr plist, int *pos, int *scroll)
533 struct ListPtrs lists;
534 int n, rval, maxname;
537 dialogMenuItem *nitems;
550 /* Figure out if this menu is full of "leaves" or "branches" */
551 for (kp = top->kids; kp && kp->name; kp = kp->next) {
555 if (kp->type == PACKAGE && plist) {
557 if ((len = strlen(kp->name)) > maxname)
562 msgConfirm("The %s menu is empty.", top->name);
564 return DITEM_LEAVE_MENU;
573 if (!hasPackages && plist) {
574 nitems = item_add(nitems, "OK", NULL, NULL, NULL, NULL, NULL, 0, &curr, &max);
575 nitems = item_add(nitems, "Install", NULL, NULL, NULL, NULL, NULL, 0, &curr, &max);
577 while (kp && kp->name) {
579 IndexEntryPtr ie = kp->data;
581 /* Brutally adjust description to fit in menu */
582 if (kp->type == PACKAGE)
583 snprintf(buf, sizeof buf, "[%s]", ie->path ? ie->path : "External vendor");
585 SAFE_STRCPY(buf, kp->desc);
586 if (strlen(buf) > (_MAX_DESC - maxname))
587 buf[_MAX_DESC - maxname] = '\0';
588 nitems = item_add(nitems, kp->name, buf, pkg_checked, pkg_fire, pkg_selected, kp, (int)&lists, &curr, &max);
592 /* NULL delimiter so item_free() knows when to stop later */
593 nitems = item_add(nitems, NULL, NULL, NULL, NULL, NULL, NULL, 0, &curr, &max);
596 dialog_clear_norefresh();
598 rval = dialog_checklist(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems, NULL);
600 rval = dialog_menu(top->name, top->desc, -1, -1, n > MAX_MENU ? MAX_MENU : n, -n, nitems + (plist ? 2 : 0), (char *)plist, pos, scroll);
601 if (rval == -1 && plist) {
606 if ((cp = msgGetInput(cp, "Search by package name. Please enter search string:")) != NULL) {
607 PkgNodePtr p = index_search(top, cp, &menu);
612 /* These need to be set to point at the found item, actually. Hmmm! */
614 index_menu(root, menu, plist, &pos, &scroll);
617 msgConfirm("Search string: %s yielded no hits.", cp);
621 items_free(nitems, &curr, &max);
623 return rval ? DITEM_FAILURE : DITEM_SUCCESS;
628 index_extract(Device *dev, PkgNodePtr top, PkgNodePtr who, Boolean depended)
630 int status = DITEM_SUCCESS;
632 IndexEntryPtr id = who->data;
634 if (id && id->deps && strlen(id->deps)) {
635 char t[1024], *cp, *cp2;
637 SAFE_STRCPY(t, id->deps);
639 while (cp && DITEM_STATUS(status) == DITEM_SUCCESS) {
640 if ((cp2 = index(cp, ' ')) != NULL)
642 if ((tmp2 = index_search(top, cp, NULL)) != NULL) {
643 status = index_extract(dev, top, tmp2, TRUE);
644 if (DITEM_STATUS(status) != DITEM_SUCCESS) {
645 if (variable_get(VAR_NO_CONFIRM))
646 msgNotify("Loading of dependant package %s failed", cp);
648 msgConfirm("Loading of dependant package %s failed", cp);
651 else if (!package_exists(cp)) {
652 if (variable_get(VAR_NO_CONFIRM))
653 msgNotify("Warning: %s is a required package but was not found.", cp);
655 msgConfirm("Warning: %s is a required package but was not found.", cp);
663 /* Done with the deps? Load the real m'coy */
664 if (DITEM_STATUS(status) == DITEM_SUCCESS) {
665 status = package_extract(dev, who->name, depended);
666 if (DITEM_STATUS(status) == DITEM_SUCCESS)
673 index_recorddeps(Boolean add, PkgNodePtr root, IndexEntryPtr ie)
675 char depends[1024], *space, *todo;
677 IndexEntryPtr found_ie;
679 SAFE_STRCPY(depends, ie->deps);
680 for (todo = depends; todo != NULL; ) {
681 space = index(todo, ' ');
685 if (strlen(todo) > 0) { /* only non-empty dependencies */
686 found = index_search(root, todo, NULL);
688 found_ie = found->data;
703 static Boolean index_initted;
705 /* Read and initialize global index */
707 index_initialize(char *path)
711 if (!index_initted) {
714 return DITEM_FAILURE;
716 /* Does it move when you kick it? */
717 if (!mediaDevice->init(mediaDevice))
718 return DITEM_FAILURE;
720 msgNotify("Attempting to fetch %s file from selected media.", path);
721 fp = mediaDevice->get(mediaDevice, path, TRUE);
723 dialog_clear_norefresh();
724 msgConfirm("Unable to get packages/INDEX file from selected media.\n"
725 "This may be because the packages collection is not available at\n"
726 "on the distribution media you've chosen (most likely an FTP site\n"
727 "without the packages collection mirrored). Please verify media\n"
728 "(or path to media) and try again. If your local site does not\n"
729 "carry the packages collection, then we recommend either a CD\n"
730 "distribution or the master distribution on ftp.freebsd.org.");
731 mediaDevice->shutdown(mediaDevice);
732 return DITEM_FAILURE | DITEM_RESTORE;
734 msgNotify("Located INDEX, now reading package data from it...");
735 index_init(&Top, &Plist);
736 if (index_read(fp, &Top)) {
737 msgConfirm("I/O or format error on packages/INDEX file.\n"
738 "Please verify media (or path to media) and try again.");
740 return DITEM_FAILURE | DITEM_RESTORE;
744 index_initted = TRUE;
746 return DITEM_SUCCESS | DITEM_RESTORE;