home *** CD-ROM | disk | FTP | other *** search
/ Chip 1998 February / CHIP_2_98.iso / misc / src / install / lilo.c < prev    next >
C/C++ Source or Header  |  1997-11-03  |  16KB  |  636 lines

  1. #include <alloca.h>
  2. #include <newt.h>
  3. #include <popt.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <stdlib.h>
  7. #include <sys/stat.h>
  8. #include <unistd.h>
  9.  
  10. #include "devices.h"
  11. #include "install.h"
  12. #include "kickstart.h"
  13. #include "lilo.h"
  14. #include "log.h"
  15. #include "run.h"
  16. #include "windows.h"
  17.  
  18. #ifdef __i386__
  19. #define KERNEL_IMAGE "/boot/vmlinuz-%s"
  20. #elif __sparc__
  21. #define KERNEL_IMAGE "/boot/vmlinux-%s.gz"
  22. #else
  23. #error unsupported architecture
  24. #endif
  25.  
  26. static int mkinitrd(char * kernelVersion, char * initrdImage) {
  27.     char * argv[] = { "/sbin/mkinitrd", "-f", initrdImage, "--ifneeded", 
  28.             kernelVersion, NULL };
  29.     int rc;
  30.     static alreadyHappened = 0;
  31.  
  32.     #ifdef __sparc__
  33.     return 0;
  34.     #endif
  35.  
  36.     if (alreadyHappened) return 0;
  37.  
  38.     if (loadModule("loop", DRIVER_OTHER, DRIVER_MINOR_NONE, NULL)) 
  39.     return INST_ERROR;
  40.  
  41.     winStatus(32, 3, "LILO", "Creating initial ramdisk...");
  42.     rc = runProgramRoot(RUN_LOG, "/mnt", "/sbin/mkinitrd", argv);
  43.     newtPopWindow();
  44.  
  45.     removeModule("loop");
  46.  
  47.     if (rc) {
  48.     unlink("/mnt/boot/initrd");
  49.     } else {
  50.     alreadyHappened = 1;
  51.     }
  52.  
  53.     return rc;
  54. }
  55.  
  56. #define SKIP_LILO 1000
  57.  
  58. #if defined(__i386__)
  59. static int liloWhere(char * hdName, char * bootDevice, char ** where) {
  60.     newtComponent form, okay, listbox, cancel, answer, skip;
  61.     char * format = "/dev/%-7s    %s";
  62.     char buf[200];
  63.     void * which;
  64.  
  65.     newtCenteredWindow(55, 11, "Lilo Installation");
  66.  
  67.     form = newtForm(NULL, NULL, 0);
  68.     newtFormAddComponent(form, 
  69.         newtLabel(1, 1, "Where do you want to install "
  70.                 "the bootloader?"));
  71.  
  72.     listbox = newtListbox(2, 3, 3, NEWT_LISTBOX_RETURNEXIT);
  73.     sprintf(buf, format, hdName, 
  74.             "Master Boot Record");
  75.     newtListboxAddEntry(listbox, buf, (void *) 1);
  76.     sprintf(buf, format, bootDevice, 
  77.             "First sector of boot partition");
  78.     newtListboxAddEntry(listbox, buf, (void *) 2);
  79.  
  80.     okay = newtButton(6, 7, "Ok");
  81.     skip = newtButton(22, 7, "Skip");
  82.     cancel = newtButton(38, 7, "Cancel");
  83.     newtFormAddComponents(form, listbox, okay, skip, cancel, NULL);
  84.  
  85.     answer = newtRunForm(form);
  86.  
  87.     which = newtListboxGetCurrent(listbox);
  88.     
  89.     newtFormDestroy(form);
  90.     newtPopWindow();
  91.  
  92.     if (answer == cancel) return INST_CANCEL;
  93.     if (answer == skip) return SKIP_LILO;
  94.  
  95.     switch ((int) which) {
  96.       case 1:         *where = hdName; break;
  97.       case 2:         *where = bootDevice; break;
  98.     }
  99.  
  100.     return 0;
  101. }
  102. #elif defined(__sparc__)
  103. static int liloWhere(char * hdName, char * bootDevice, char ** where) {
  104.     newtComponent text, yes, no, cancel, f, answer;
  105.     int rc;
  106.  
  107.     rc = newtWinTernary("SILO Installation", "Yes", "No", "Cancel",
  108.         "Would you like to install or configure the SILO bootloader on "
  109.         "your system?");
  110.  
  111.     if (rc == 0 || rc == 1) {
  112.         *where = bootDevice; 
  113.     rc = 0;
  114.     } else if (rc == 3) {
  115.     rc = INST_CANCEL;
  116.     } else {
  117.     rc = SKIP_LILO;
  118.     }
  119.  
  120.     return rc;
  121. }
  122. #endif
  123.  
  124. static void editBootLabel(struct partition * item) {
  125.     newtComponent form, entry, okay, cancel, clear, answer;
  126.     char buf[50];
  127.     char * entryValue;
  128.  
  129.     newtCenteredWindow(50, 10, "Edit Boot Label");
  130.  
  131.     form = newtForm(NULL, NULL, 0);
  132.  
  133.     strcpy(buf,"Device      : /dev/");
  134.     strcat(buf, item->device);
  135.     newtFormAddComponent(form, newtLabel(1, 1, buf));
  136.     newtFormAddComponent(form, newtLabel(1, 3, "Boot label :"));
  137.  
  138.     entry = newtEntry(17, 3, item->bootLabel, 20, &entryValue, 
  139.               NEWT_ENTRY_SCROLL | NEWT_ENTRY_RETURNEXIT);
  140.  
  141.     okay = newtButton(5, 6, "Ok");
  142.     clear = newtButton(20, 6, "Clear");
  143.     cancel = newtButton(35, 6, "Cancel");
  144.  
  145.     newtFormAddComponents(form, entry, okay, clear, cancel, NULL);
  146.  
  147.     do {
  148.     answer = newtRunForm(form);
  149.  
  150.     if (answer == clear)
  151.         newtEntrySet(entry, "", 1);
  152.     } while (answer == clear);
  153.  
  154.     if (answer != cancel) {
  155.     if (item->bootLabel) free(item->bootLabel);
  156.  
  157.     if (strlen(entryValue))
  158.         item->bootLabel = strdup(entryValue);
  159.     else
  160.         item->bootLabel = NULL;
  161.     }
  162.  
  163.     newtPopWindow();
  164. }        
  165.  
  166. static int doinstallLilo(char * prefix, char * dev, char * rootdev,
  167.              struct partitionTable table,
  168.              char * append, char * kernelVersion, 
  169.              char * hdname, int linear) {
  170.     char filename[100];
  171.     FILE * f;
  172.     char * argv[] = { "/mnt/sbin/lilo", NULL };
  173.     int i;
  174.     int rc;
  175.     struct stat sb;
  176.     int useInitrd = 0;
  177.     char relinitrdImage[50], absinitrdImage[55];
  178.     int pass;
  179.  
  180.     sprintf(relinitrdImage, "/boot/initrd-%s.img", kernelVersion);
  181.     strcpy(absinitrdImage, "/mnt");
  182.     strcat(absinitrdImage, relinitrdImage);
  183.  
  184.     if (mkinitrd(kernelVersion, relinitrdImage))
  185.     return INST_ERROR;
  186.  
  187.     if (testing) return 0;
  188.  
  189.     if (!stat(absinitrdImage, &sb))
  190.     useInitrd = 1;
  191.  
  192.     #ifdef __sparc__
  193.     sprintf(filename, "%s/silo.conf", prefix);
  194.     #else
  195.     sprintf(filename, "%s/lilo.conf", prefix);
  196.     #endif
  197.  
  198.     /* why not? */
  199.     rename("/mnt/etc/lilo.conf", "/mnt/etc/lilo.conf.orig");
  200.     rename("/mnt/etc/silo.conf", "/mnt/etc/silo.conf.orig");
  201.     
  202.     f = fopen(filename, "w");
  203.     if (!f) {
  204.     errorWindow("cannot create [ls]ilo config file: %s");
  205.     return INST_ERROR;
  206.     }
  207.  
  208.     logMessage("writing [sl]ilo config to %s", filename);
  209.  
  210.     #ifdef __i386__
  211.     fprintf(f, "boot=/dev/%s\n", dev);
  212.     fprintf(f, "map=/boot/map\n");
  213.     fprintf(f, "install=/boot/boot.b\n");
  214.     fprintf(f, "prompt\n");
  215.     if (linear) fprintf(f, "linear\n");
  216.     fprintf(f, "timeout=50\n");
  217.     #elif __sparc__
  218.     fprintf(f, "timeout=50\n");
  219.     fprintf(f, "partition=%s\n", rootdev + 3);
  220.     fprintf(f, "root=/dev/%s\n", rootdev);
  221.     #else
  222.     #error "unsupported architecture";
  223.     #endif
  224.  
  225.     for (pass = 0; pass < 2; pass++) {
  226.     for (i = 0; i < table.count; i++) {
  227.         if (!table.parts[i].bootLabel) continue;
  228.         if (pass == 0 && !table.parts[i].defaultBoot) continue;
  229.         if (pass == 1 && table.parts[i].defaultBoot) continue;
  230.  
  231.         if (table.parts[i].type == PART_EXT2) {
  232.         fprintf(f, "image=" KERNEL_IMAGE "\n", kernelVersion);
  233.         fprintf(f, "\tlabel=%s\n", table.parts[i].bootLabel);
  234.         
  235.         fprintf(f, "\troot=/dev/%s\n", rootdev);
  236.         if (useInitrd)
  237.             fprintf(f, "\tinitrd=%s\n", relinitrdImage);
  238.         if (append) fprintf(f, "\tappend=\"%s\"\n", append);
  239.         fprintf(f, "\tread-only\n");
  240.       #ifdef __i386__
  241.         } else {
  242.         fprintf(f, "other=/dev/%s\n", table.parts[i].device);
  243.         fprintf(f, "\tlabel=%s\n", table.parts[i].bootLabel);
  244.         fprintf(f, "\ttable=/dev/%.3s\n", table.parts[i].device);
  245.         if (strncmp(table.parts[i].device, hdname, 3)) 
  246.             fprintf(f, "\tloader=/boot/any_d.b\n");
  247.       #endif
  248.         }
  249.     }
  250.     }
  251.  
  252.     fclose(f);
  253.  
  254.     winStatus(35, 3, "Running", "Installing boot loader...");
  255.  
  256.     #ifdef __i386__
  257.     rc = runProgramRoot(RUN_LOG, "/mnt", "sbin/lilo", argv);
  258.     #elif __sparc__ 
  259.     rc = doMount("/proc", "/mnt/proc", "proc", 0, 0);
  260.     if (rc) {
  261.         newtPopWindow();
  262.         return rc;
  263.     }
  264.     rc = runProgramRoot(RUN_LOG, "/mnt", "sbin/silo", argv);
  265.     umount("/mnt/proc");
  266.     #else
  267.     #error unsupported architectures
  268.     #endif
  269.  
  270.     newtPopWindow();
  271.  
  272.     if (rc)
  273.     return INST_ERROR;
  274.  
  275.     return 0;
  276. }
  277.  
  278. static void formatEntry(char * buf, struct partition * part) {
  279.     sprintf(buf, "/dev/%-5s  %-25s %-7s %-10s", part->device, 
  280.         part->tagName, 
  281.         part->defaultBoot ? "   *" : "",
  282.         part->bootLabel ? part->bootLabel : "");
  283. }
  284.  
  285. static int getBootLabels(struct partitionTable table, struct fstab fstab) {
  286.     newtComponent f, okay, text, listbox, label, cancel, edit;
  287.     struct newtExitStruct answer;
  288.     char buf[80];
  289.     int i, j;
  290.     int foundDos = 0;
  291.     int mustAsk = 0;
  292.     int * map;
  293.     struct partition * curr;
  294.     int * currNum;
  295.     int count;
  296.     int done;
  297.     int defaultBootPart = 0;
  298.  
  299.     f = newtForm(NULL, NULL, 0);
  300.     text = newtTextbox(1, 1, 60, 4, NEWT_TEXTBOX_WRAP);
  301.     newtTextboxSetText(text, 
  302.                "The boot manager Red Hat uses can boot other " 
  303.                        "operating systems as well. You need to tell me " 
  304.                        "what partitions you would like to be able to boot " 
  305.                        "and what label you want to use for each of them.");
  306.  
  307.  
  308.     sprintf(buf, "%-10s  %-25s %-7s %-10s", "Device", "Partition type", 
  309.             "Default", "Boot label");
  310.     label = newtLabel(1, 6, buf);
  311.  
  312.     listbox = newtListbox(0, 7, 7, NEWT_LISTBOX_RETURNEXIT);
  313.     map = alloca(sizeof(int) * table.count);
  314.     
  315.     for (i = 0, count = 0; i < table.count; i++) {
  316.     if (table.parts[i].type != PART_SWAP && 
  317.         table.parts[i].type != PART_IGNORE &&
  318. #ifdef __sparc__
  319.         table.parts[i].type != PART_OTHER &&
  320. #endif
  321.         (table.parts[i].type != PART_FAT32 || !foundDos) &&
  322.         (table.parts[i].type != PART_DOS || !foundDos)) {
  323.  
  324.         if (table.parts[i].type == PART_DOS || 
  325.             table.parts[i].type == PART_FAT32) {
  326.         table.parts[i].bootLabel = strdup("dos");
  327.         foundDos = 1;
  328.         }
  329.  
  330.         if (table.parts[i].type == PART_EXT2) {
  331.         for (j = 0; j < fstab.numEntries; j++) {
  332.             if (!strcmp(table.parts[i].device, fstab.entries[j].device))
  333.             break;
  334.         }
  335.  
  336.         if (j < fstab.numEntries && !table.parts[i].bootLabel) 
  337.             continue;
  338.         }
  339.  
  340.         if (!table.parts[i].bootLabel ||
  341.         strcmp(table.parts[i].bootLabel, "linux")) mustAsk = 1;
  342.  
  343.         if (table.parts[i].defaultBoot)
  344.         defaultBootPart = count;
  345.  
  346.         map[count] = i;
  347.         formatEntry(buf, table.parts + i);
  348.         newtListboxAddEntry(listbox, buf, map + count++);
  349.     } 
  350.     }
  351.  
  352.     newtFormAddComponents(f, text, label, listbox, NULL);
  353.  
  354.     if (!mustAsk) {
  355.     newtFormDestroy(f);
  356.     return 0;
  357.     }
  358.  
  359.     newtCenteredWindow(64, 19, "Bootable Partitions");
  360.  
  361.     newtPushHelpLine("<F2> Selects the default partition");
  362.  
  363.     okay = newtButton(8, 15, "Ok");
  364.     edit = newtButton(26, 15, "Edit");
  365.     cancel = newtButton(44, 15, "Cancel");
  366.  
  367.     newtFormAddComponents(f, okay, edit, cancel, NULL);
  368.  
  369.     newtFormAddHotKey(f, NEWT_KEY_F2);
  370.  
  371.     done = 0;
  372.     while (!done) {
  373.     newtFormRun(f, &answer);
  374.  
  375.     if (answer.reason == NEWT_EXIT_HOTKEY) {
  376.         if (answer.u.key == NEWT_KEY_F12) {
  377.         done = 1;
  378.         } else if (answer.u.key == NEWT_KEY_F2) {
  379.         currNum = newtListboxGetCurrent(listbox);
  380.         curr = table.parts + *currNum;
  381.  
  382.         if (!curr->bootLabel) {
  383.             newtWinMessage("Boot Partition", "Ok", "You cannot mark a "
  384.                   "partition as the default partition to "
  385.                   "boot from unless that partition has "
  386.                   "been assigned a boot label.");
  387.         } else{
  388.             for (i = 0; i < count; i++) {
  389.             if (table.parts[map[i]].defaultBoot) {
  390.                 table.parts[map[i]].defaultBoot = 0;
  391.                 formatEntry(buf, table.parts + map[i]);
  392.                 newtListboxSetEntry(listbox, i, buf);
  393.                 break;
  394.             }
  395.             }
  396.  
  397.             curr->defaultBoot = 1;
  398.             formatEntry(buf, curr);
  399.             newtListboxSetEntry(listbox, currNum - map, buf);
  400.         }
  401.         }
  402.     } else {
  403.         if (answer.u.co == edit || answer.u.co== listbox) {
  404.         currNum = newtListboxGetCurrent(listbox);
  405.         curr = table.parts + *currNum;
  406.         editBootLabel(curr);
  407.  
  408.         if (!curr->bootLabel && curr->defaultBoot) {
  409.             curr->defaultBoot = 0;
  410.             if (table.parts[map[defaultBootPart]].bootLabel) {
  411.             table.parts[map[defaultBootPart]].defaultBoot = 1;
  412.  
  413.             formatEntry(buf, table.parts + map[defaultBootPart]);
  414.             newtListboxSetEntry(listbox, defaultBootPart, buf);
  415.             }
  416.         }
  417.  
  418.         formatEntry(buf, curr);
  419.         newtListboxSetEntry(listbox, currNum - map, buf);
  420.         } else 
  421.         done = 1;
  422.     }
  423.     }
  424.  
  425.     newtPopHelpLine();
  426.  
  427.     newtFormDestroy(f);
  428.     newtPopWindow();
  429.  
  430.     if (answer.reason == NEWT_EXIT_COMPONENT && answer.u.co == cancel)
  431.     return INST_CANCEL;
  432.     else
  433.     return 0;
  434. }
  435.  
  436. static int getAppendLine(char ** line, int * linear) {
  437.     newtComponent form, text, entry, okay, cancel, answer;
  438.     newtComponent linearCheck;
  439.     char * result = NULL;
  440.     char linearChar = (*linear) ? '*' : ' ';
  441.     int buttonLine = 9;
  442.  
  443.     #ifdef __sparc__
  444.     newtCenteredWindow(55, 13, "Silo Installation");
  445.     #else
  446.     /* this is bigger on the Intel to leave room for the linear checkbox */
  447.     newtCenteredWindow(55, 15, "Lilo Installation");
  448.     #endif
  449.  
  450.     form = newtForm(NULL, NULL, 0);
  451.     text = newtTextbox(1, 1, 53, 5, NEWT_TEXTBOX_WRAP);
  452.     newtTextboxSetText(text, 
  453.                "A few systems will need to pass special options "
  454.                "to the kernel at boot time for the system to function "
  455.                "properly. If you need to pass boot options to the "
  456.                "kernel, enter them now. If you don't need any or "
  457.                "aren't sure, leave this blank.");
  458.  
  459.     entry = newtEntry(1, 7, *line, 48, &result, 
  460.             NEWT_ENTRY_SCROLL | NEWT_ENTRY_RETURNEXIT);
  461.  
  462.     #ifndef __sparc__
  463.     buttonLine = 11;
  464.     linearCheck = newtCheckbox(1, 9, 
  465.                    "Use linear mode (needed for some SCSI drives)",
  466.                    linearChar, NULL, &linearChar);
  467.     #endif
  468.  
  469.     okay = newtButton(12, buttonLine, "Ok");
  470.     cancel = newtButton(35, buttonLine, "Cancel");
  471.  
  472.     newtFormAddComponents(form, text, entry, NULL);
  473.     #ifndef __sparc__
  474.     newtFormAddComponent(form, linearCheck);
  475.     #endif
  476.     newtFormAddComponents(form, okay, cancel, NULL);
  477.     newtFormSetCurrent(form, okay);
  478.  
  479.     answer = newtRunForm(form);
  480.  
  481.     newtPopWindow();
  482.  
  483.     if (answer == cancel) {
  484.     newtFormDestroy(form);
  485.     return INST_CANCEL;
  486.     }
  487.  
  488.     *linear = linearChar != ' ';
  489.  
  490.     if (!strlen(result)) 
  491.     *line = NULL;
  492.     else 
  493.     *line = strdup(result);
  494.  
  495.     newtFormDestroy(form);
  496.  
  497.     return 0;
  498. }
  499.  
  500. #define LILO_WHERE 2
  501. #define LILO_LABELS 3
  502. #define LILO_INSTALL 4
  503. #define LILO_APPEND 5
  504. #define LILO_DONE 20
  505.     
  506. int installLilo(char * prefix, struct partitionTable table, 
  507.             struct fstab fstab, char * kernelVersion) {
  508.     char * rootDevice, * bootDevice = NULL;
  509.     char * hdName;
  510.     char * where = NULL;
  511.     char * append = NULL;
  512.     char * chptr = NULL;
  513.     int i;
  514.     int rc;
  515.     int stage = LILO_WHERE;
  516.     static int linear = 0;
  517.     char ** argv;
  518.     int argc;
  519.     char * location = NULL;
  520.     poptContext optCon;
  521.     struct poptOption ksOptions[] = {
  522.         { "append", '\0', POPT_ARG_STRING, &append, 0 },
  523. #ifdef __i386__
  524.         { "linear", '\0', 0, &linear, 0 },
  525.         { "location", '\0', POPT_ARG_STRING, &location, 0 },
  526. #endif
  527.         { 0, 0, 0, 0, 0 }
  528.     };
  529.  
  530.     hdName = alloca(4);
  531.     strncpy(hdName, table.parts[0].device, 3);
  532.     hdName[3] = '\0';
  533.     
  534.     for (i = 0; i < fstab.numEntries; i++) {
  535.     if (!strcmp(fstab.entries[i].mntpoint, "/boot")) break;
  536.     }
  537.  
  538.     if (i < fstab.numEntries) 
  539.     bootDevice = fstab.entries[i].device;
  540.  
  541.     for (i = 0; i < fstab.numEntries; i++) {
  542.     if (!strcmp(fstab.entries[i].mntpoint, "/")) break;
  543.     }
  544.  
  545.     rootDevice = fstab.entries[i].device;
  546.     if (!bootDevice) {
  547.     bootDevice = rootDevice;
  548.     }
  549.  
  550.     for (i = 0; i < table.count; i++) {
  551.     if (!strcmp(table.parts[i].device, bootDevice)) {
  552.         table.parts[i].bootLabel = strdup("linux");
  553.         table.parts[i].defaultBoot = 1;
  554.         break;
  555.     }
  556.     }
  557.  
  558.     if (kickstart) {
  559.     if (!ksGetCommand(KS_CMD_LILO, NULL, &argc, &argv)) {
  560.         optCon = poptGetContext(NULL, argc, argv, ksOptions, 0);
  561.  
  562.         if ((rc = poptGetNextOpt(optCon)) < -1) {
  563.         newtWinMessage("lilo command",  "Ok",
  564.                    "bad argument to kickstart lilo command %s: %s",
  565.                    poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
  566.                    poptStrerror(rc));
  567.         }
  568.     }
  569.  
  570.     if (!location || !strcmp(location, "mbr"))
  571.         where = hdName;
  572.     else if (!strcmp(location, "partition"))
  573.         where = bootDevice;
  574.     else if (!strcmp(location, "none"))
  575.         return 0;
  576.  
  577.     if (where) {
  578.         rc = doinstallLilo(prefix, where, rootDevice, table, append, 
  579.                 kernelVersion, hdName, linear);
  580.         if (rc == INST_ERROR) return INST_ERROR;
  581.         stage = LILO_DONE;
  582.     }
  583.     }
  584.  
  585.     while (stage != LILO_DONE) {
  586.     switch (stage) {
  587.       case LILO_WHERE:
  588.         rc = liloWhere(hdName, bootDevice, &where);
  589.             if (rc == SKIP_LILO ) return 0;
  590.         if (rc) return rc;
  591.         stage = LILO_APPEND;
  592.         break;
  593.  
  594.       case LILO_APPEND:
  595.         chptr = append;
  596.         rc = getAppendLine(&chptr, &linear);
  597.  
  598.         if (rc == INST_ERROR) return INST_ERROR;
  599.         if (rc == INST_CANCEL)
  600.         stage = LILO_WHERE;
  601.         else {
  602.         stage = LILO_LABELS;
  603.  
  604.         if (append) free(append);
  605.         if (chptr) {
  606.             append = alloca(strlen(chptr) + 1);
  607.             strcpy(append, chptr);
  608.             free(chptr);
  609.         } else {
  610.             append = NULL;
  611.         }
  612.         }
  613.  
  614.         break;
  615.  
  616.       case LILO_LABELS:
  617.         rc = getBootLabels(table, fstab);
  618.         if (rc == INST_ERROR) return INST_ERROR;
  619.         if (rc == INST_CANCEL)
  620.         stage = LILO_APPEND;
  621.         else
  622.         stage = LILO_INSTALL;
  623.         break;
  624.  
  625.       case LILO_INSTALL:
  626.         rc = doinstallLilo(prefix, where, rootDevice, table, append, 
  627.                 kernelVersion, hdName, linear);
  628.         if (rc == INST_ERROR) return INST_ERROR;
  629.         stage = LILO_DONE;
  630.         break;
  631.     }
  632.     }
  633.  
  634.     return 0;
  635. }
  636.