diff --git a/ChangeLog b/ChangeLog index a7cc92b67..01b44d25a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,57 @@ +2004-06-20 Yoshinori K. Okuji + + This is a big change on saving a default entry. This change + makes it possible to set up a quite robust system using GRUB. + Now we do not use the second sector of Stage 2 to store an + entry number but use the file /boot/grub/default. This file + must be generated by grub-set-default, although this file is + plain-text. + + * util/grub-set-default.in: New file. + + * util/grub-install.in (grub_set_default): New variable. + Use /grub instead of /boot/grub on OpenBSD as well as NetBSD. + Run grub-set-default to make a default file. + + * util/Makefile.am (sbin_SCRIPTS): Added grub-set-default. + + * stage2/stage2.c (run_menu): Change the fallback handling to + support multiple fallback entries. + (cmain): Likewise. Also, get a saved entry from a default file + if possible, before reading a config file. + + * stage2/shared.h (DEFAULT_FILE_BUF): New macro. + (DEFAULT_FILE_BUFLEN): Likewise. + (CMDLINE_BUF): Set to DEFAULT_FILE_BUF + DEFAULT_FILE_BUFLEN. + (MENU_BUFLEN): Set to 0x8000 + PASSWORD_BUF - MENU_BUF. + (fallback_entry): Removed. + (fallback_entries): Declared. + (fallback_entryno): Likewise. + (MAX_FALLBACK_ENTRIES): New macro. + + * stage2/cmdline.c (run_script): Use FALLBACK_ENTRYNO instead of + FALLBACK_ENTRY. + + * stage2/builtins.c (fallback_entry): Removed. + (fallback_entryno): New variable. + (fallback_entries): Likewise. + (init_config): Initialize FALLBACK_ENTRYNO and FALLBACK_ENTRIES. + (fallback_func): Rewritten completely. + (savedefault_func): Likewise. + + * docs/grub.texi (grub-set-default): New direntry. + (Installation): Describe grub-set-default for manual + installations. + (Making your system robust): New section. + (Booting once-only): New subsection. + (Booting fallback systems): Likewise. + (fallback): Describe multiple fallback entries. + (savedefault): Describe an optional argument. + (Invoking grub-set-default): New chapter. + (Future): Replaced with a description about GRUB 2. + + * configure.ac (AC_CONFIG_FILES): Added util/grub-set-default. + 2004-06-19 Yoshinori K. Okuji * stage2/ufs2.h (int8_t): Renamed to ... diff --git a/NEWS b/NEWS index e9ca852c5..d0660c9e5 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,12 @@ NEWS - list of user-visible changes between releases of GRUB +New: +* The command "fallback" supports mutiple fallback entries. +* The command "savedefault" supports an optional argument which + is the number of next boot entry or the special keyword `fallback'. +* New utility "grub-set-default". +* New section "Making your system robust" in the manual. + New in 0.95 - 2004-06-13: * Add support for ReiserFS 3. * Fix support for FreeBSD 5. diff --git a/configure b/configure index 701c82205..c69ebf946 100644 --- a/configure +++ b/configure @@ -6109,7 +6109,7 @@ CCASFLAGS='$(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(CPPFLAGS) $(CFLAGS)' - ac_config_files="$ac_config_files Makefile stage1/Makefile stage2/Makefile docs/Makefile lib/Makefile util/Makefile grub/Makefile netboot/Makefile util/grub-image util/grub-install util/grub-md5-crypt util/grub-terminfo" + ac_config_files="$ac_config_files Makefile stage1/Makefile stage2/Makefile docs/Makefile lib/Makefile util/Makefile grub/Makefile netboot/Makefile util/grub-image util/grub-install util/grub-md5-crypt util/grub-terminfo util/grub-set-default" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure @@ -6728,6 +6728,7 @@ do "util/grub-install" ) CONFIG_FILES="$CONFIG_FILES util/grub-install" ;; "util/grub-md5-crypt" ) CONFIG_FILES="$CONFIG_FILES util/grub-md5-crypt" ;; "util/grub-terminfo" ) CONFIG_FILES="$CONFIG_FILES util/grub-terminfo" ;; + "util/grub-set-default" ) CONFIG_FILES="$CONFIG_FILES util/grub-set-default" ;; "depfiles" ) CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; "config.h" ) CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 diff --git a/configure.ac b/configure.ac index 2b378cea0..7e337e02a 100644 --- a/configure.ac +++ b/configure.ac @@ -666,5 +666,5 @@ AC_CONFIG_FILES([Makefile stage1/Makefile stage2/Makefile \ docs/Makefile lib/Makefile util/Makefile \ grub/Makefile netboot/Makefile util/grub-image \ util/grub-install util/grub-md5-crypt \ - util/grub-terminfo]) + util/grub-terminfo util/grub-set-default]) AC_OUTPUT diff --git a/docs/grub.texi b/docs/grub.texi index d9702fc7a..8a23e15b6 100644 --- a/docs/grub.texi +++ b/docs/grub.texi @@ -27,6 +27,8 @@ * grub-terminfo: (grub)Invoking grub-terminfo. Generate a terminfo command from a terminfo name +* grub-set-default: (grub)Invoking grub-set-default. Set a default boot + entry * mbchk: (grub)Invoking mbchk. Check for the format of a Multiboot kernel @end direntry @@ -113,6 +115,7 @@ This edition documents version @value{VERSION}. * Invoking grub-install:: How to use the GRUB installer * Invoking grub-md5-crypt:: How to generate a cryptic password * Invoking grub-terminfo:: How to generate a terminfo command +* Invoking grub-set-default:: How to set a default boot entry * Invoking mbchk:: How to use the Multiboot checker * Obtaining and Building GRUB:: How to obtain and build GRUB * Reporting bugs:: Where you should send a bug report @@ -475,11 +478,14 @@ if, by any chance, your hard drive becomes unusable (unbootable). GRUB comes with boot images, which are normally put in the directory @file{/usr/share/grub/i386-pc}. If you do not use grub-install, then you need to copy the files @file{stage1}, @file{stage2}, and -@file{*stage1_5} to the directory @file{/boot/grub}. Hereafter, the -directory where GRUB images are initially placed (normally -@file{/usr/share/grub/i386-pc}) will be called the @dfn{image -directory}, and the directory where the boot loader needs to find them -(usually @file{/boot/grub}) will be called the @dfn{boot directory}. +@file{*stage1_5} to the directory @file{/boot/grub}, and run the +@command{grub-set-default} (@pxref{Invoking grub-set-default}) if you +intend to use @samp{default saved} (@pxref{default}) in your +configuration file. Hereafter, the directory where GRUB images are +initially placed (normally @file{/usr/share/grub/i386-pc}) will be +called the @dfn{image directory}, and the directory where the boot +loader needs to find them (usually @file{/boot/grub}) will be called +the @dfn{boot directory}. @menu * Creating a GRUB boot floppy:: @@ -734,6 +740,7 @@ magic. @menu * General boot methods:: How to boot OSes with GRUB generally * OS-specific notes:: Notes on some operating systems +* Making your system robust:: How to make your system robust @end menu @@ -1068,6 +1075,184 @@ grub> @kbd{boot} @end example +@node Making your system robust +@section How to make your system robust + +When you test a new kernel or a new OS, it is important to make sure +that your computer can boot even if the new system is unbootable. This +is crucial especially if you maintain servers or remote systems. To +accomplish this goal, you need to set up two things: + +@enumerate +@item +You must maintain a system which is always bootable. For instance, if +you test a new kernel, you need to keep a working kernel in a +different place. And, it would sometimes be very nice to even have a +complete copy of a working system in a different partition or disk. + +@item +You must direct GRUB to boot a working system when the new system +fails. This is possible with the @dfn{fallback} system in GRUB. +@end enumerate + +The former requirement is very specific to each OS, so this +documentation does not cover that topic. It is better to consult some +backup tools. + +So let's see the GRUB part. There are two possibilities: one of them +is quite simple but not very robust, and the other is a bit complex to +set up but probably the best solution to make sure that your system +can start as long as GRUB itself is bootable. + +@menu +* Booting once-only:: +* Booting fallback systems:: +@end menu + + +@node Booting once-only +@subsection Booting once-only + +You can teach GRUB to boot an entry only at next boot time. Suppose +that your have an old kernel @file{old_kernel} and a new kernel +@file{new_kernel}. You know that @file{old_kernel} can boot +your system correctly, and you want to test @file{new_kernel}. + +To ensure that your system will go back to the old kernel even if the +new kernel fails (e.g. it panics), you can specify that GRUB should +try the new kernel only once and boot the old kernel after that. + +First, modify your configuration file. Here is an example: + +@group +@example +default saved # This is important!!! +timeout 10 + +title the old kernel +root (hd0,0) +kernel /old_kernel +savedefault + +title the new kernel +root (hd0,0) +kernel /new_kernel +savedefault 0 # This is important!!! +@end example +@end group + +Note that this configuration file uses @samp{default saved} +(@pxref{default}) at the head and @samp{savedefault 0} +(@pxref{savedefault}) in the entry for the new kernel. This means +that GRUB boots a saved entry by default, and booting the entry for the +new kernel saves @samp{0} as the saved entry. + +With this configuration file, after all, GRUB always tries to boot the +old kernel after it booted the new one, because @samp{0} is the entry +of @code{the old kernel}. + +The next step is to tell GRUB to boot the new kernel at next boot +time. For this, execute @command{grub-set-default} (@pxref{Invoking +grub-set-default}): + +@example +# @kbd{grub-set-default 1} +@end example + +This command sets the saved entry to @samp{1}, that is, to the new +kernel. + +This method is useful, but still not very robust, because GRUB stops +booting, if there is any error in the boot entry, such that the new +kernel has an invalid executable format. Thus, it it even better to +use the @dfn{fallback} mechanism of GRUB. Look at next subsection for +this feature. + + +@node Booting fallback systems +@subsection Booting fallback systems + +GRUB supports a fallback mechanism of booting one or more other +entries if a default boot entry fails. You can specify multiple +fallback entries if you wish. + +Suppose that you have three systems, @samp{A}, @samp{B} and +@samp{C}. @samp{A} is a system which you want to boot by +default. @samp{B} is a backup system which is supposed to boot +safely. @samp{C} is another backup system which is used in case where +@samp{B} is broken. + +Then you may want GRUB to boot the first system which is bootable +among @samp{A}, @samp{B} and @samp{C}. A configuration file can be +written in this way: + +@group +@example +default saved # This is important!!! +timeout 10 +fallback 1 2 # This is important!!! + +title A +root (hd0,0) +kernel /kernel +savedefault fallback # This is important!!! + +title B +root (hd1,0) +kernel /kernel +savedefault fallback # This is important!!! + +title C +root (hd2,0) +kernel /kernel +savedefault +@end example +@end group + +Note that @samp{default saved} (@pxref{default}), @samp{fallback 1 2} +and @samp{savedefault fallback} are used. GRUB will boot a saved entry +by default and save a fallback entry as next boot entry with this +configuration. + +When GRUB tries to boot @samp{A}, GRUB saves @samp{1} as next boot +entry, because the command @command{fallback} specifies that @samp{1} +is the first fallback entry. The entry @samp{1} is @samp{B}, so GRUB +will try to boot @samp{B} at next boot time. + +Likewise, when GRUB tries to boot @samp{B}, GRUB saves @samp{2} as +next boot entry, because @command{fallback} specifies @samp{2} as next +fallback entry. This makes sure that GRUB will boot @samp{C} after +booting @samp{B}. + +It is noteworthy that GRUB uses fallback entries both when GRUB +itself fails in booting an entry and when @samp{A} or @samp{B} fails +in starting up your system. So this solution ensures that your system +is started even if GRUB cannot find your kernel or if your kernel +panics. + +However, you need to run @command{grub-set-default} (@pxref{Invoking +grub-set-default}) when @samp{A} starts correctly or you fix @samp{A} +after it crashes, since GRUB always sets next boot entry to a fallback +entry. You should run this command in a startup script such as +@file{rc.local} to boot @samp{A} by default: + +@example +# @kbd{grub-set-default 0} +@end example + +where @samp{0} is the number of the boot entry for the system +@samp{A}. + +If you want to see what is current default entry, you can look at the +file @file{/boot/grub/default} (or @file{/grub/default} in +some systems). Because this file is plain-text, you can just +@command{cat} this file. But it is strongly recommended @strong{not to +modify this file directly}, because GRUB may fail in saving a default +entry in this file, if you change this file in an unintended +manner. Therefore, you should use @command{grub-set-default} when you +need to change the default entry. + + @node Configuration @chapter Configuration @@ -1952,12 +2137,13 @@ default entry is the entry saved with the command @node fallback @subsection fallback -@deffn Command fallback num +@deffn Command fallback num... Go into unattended boot mode: if the default boot entry has any errors, instead of waiting for the user to do something, immediately start over using the @var{num} entry (same numbering as the @code{default} command (@pxref{default})). This obviously won't help if the machine was -rebooted by a kernel that GRUB loaded. +rebooted by a kernel that GRUB loaded. You can specify multiple +fallback entry numbers. @end deffn @@ -2962,8 +3148,9 @@ derived from attempting the mount will @emph{not} work correctly. @node savedefault @subsection savedefault -@deffn Command savedefault -Save the current menu entry as a default entry. Here is an example: +@deffn Command savedefault num +Save the current menu entry or @var{num} if specified as a default +entry. Here is an example: @example @group @@ -2984,7 +3171,13 @@ savedefault @end example With this configuration, GRUB will choose the entry booted previously as -the default entry. See also @ref{default}. +the default entry. + +You can specify @samp{fallback} instead of a number. Then, next +fallback entry is saved. Next fallback entry is chosen from fallback +entries. Normally, this will be the first entry in fallback ones. + +See also @ref{default} and @ref{Invoking grub-set-default}. @end deffn @@ -3559,6 +3752,68 @@ You must specify one argument to this command. For example: @end example +@node Invoking grub-set-default +@chapter Invoking grub-set-default + +The program @command{grub-set-default} sets the default boot entry for +GRUB. This automatically creates a file named @file{default} under +your GRUB directory (i.e. @file{/boot/grub}), if it is not +present. This file is used to determine the default boot entry when +GRUB boots up your system when you use @samp{default saved} in your +configuration file (@pxref{default}), and to save next default boot +entry when you use @samp{savedefault} in a boot entry +(@pxref{savedefault}). + +@command{grub-set-default} accepts the following options: + +@table @option +@item --help +Print a summary of the command-line options and exit. + +@item --version +Print the version information and exit. + +@item --root-directory=@var{dir} +Use the directory @var{dir} instead of the root directory +(i.e. @file{/}) to define the location of the default file. This +is useful when you mount a disk which is used for another system. +@end table + +You must specify a single argument to @command{grub-set-default}. This +argument is normally the number of a default boot entry. For example, +if you have this configuration file: + +@group +@example +default saved +timeout 10 + +title GNU/Hurd +root (hd0,0) +... + +title GNU/Linux +root (hd0,1) +... +@end example +@end group + +and if you want to set the next default boot entry to GNU/Linux, you +may execute this command: + +@example +@kbd{grub-set-default 1} +@end example + +Because the entry for GNU/Linux is @samp{1}. Note that entries are +counted from zero. So, if you want to specify GNU/Hurd here, then you +should specify @samp{0}. + +This feature is very useful if you want to test a new kernel or to +make your system quite robust. @xref{Making your system robust}, for +more hints about how to set up a robust system. + + @node Invoking mbchk @chapter Invoking mbchk @@ -3695,27 +3950,12 @@ Once we get your report, we will try to fix the bugs. @node Future @chapter Where GRUB will go -Here are some ideas of what might happen in the future: - -@itemize @bullet -@item -Support dynamic loading. - -@item -Add real memory management. - -@item -Add a real scripting language. - -@item -Support internationalization. - -@item -Support other architectures than i386-pc. -@end itemize - -See the file @file{TODO} in the source distribution, for more -information. +We started the next generation of GRUB, GRUB 2. This will include +internationalization, dynamic module loading, real memory management, +multiple architecture support, a scripting language, and many other +nice feature. If you are interested in the development of GRUB 2, take +a look at @uref{http://www.gnu.org/software/grub/grub.html, the +homepage}. @c Separate the programming guide. diff --git a/docs/stamp-vti b/docs/stamp-vti index 7f71ab849..43a51afc8 100644 --- a/docs/stamp-vti +++ b/docs/stamp-vti @@ -1,4 +1,4 @@ -@set UPDATED 11 May 2004 -@set UPDATED-MONTH May 2004 +@set UPDATED 20 June 2004 +@set UPDATED-MONTH June 2004 @set EDITION 0.95 @set VERSION 0.95 diff --git a/docs/version.texi b/docs/version.texi index 7f71ab849..43a51afc8 100644 --- a/docs/version.texi +++ b/docs/version.texi @@ -1,4 +1,4 @@ -@set UPDATED 11 May 2004 -@set UPDATED-MONTH May 2004 +@set UPDATED 20 June 2004 +@set UPDATED-MONTH June 2004 @set EDITION 0.95 @set VERSION 0.95 diff --git a/stage2/asm.S b/stage2/asm.S index b4db6d58c..34b6e7d05 100644 --- a/stage2/asm.S +++ b/stage2/asm.S @@ -87,6 +87,7 @@ ENTRY(main) VARIABLE(install_partition) .long 0xFFFFFF +/* This variable is here only because of a historical reason. */ VARIABLE(saved_entryno) .long 0 VARIABLE(stage2_id) diff --git a/stage2/builtins.c b/stage2/builtins.c index 596506d34..535c713d2 100644 --- a/stage2/builtins.c +++ b/stage2/builtins.c @@ -59,7 +59,8 @@ int debug = 0; /* The default entry. */ int default_entry = 0; /* The fallback entry. */ -int fallback_entry = -1; +int fallback_entryno; +int fallback_entries[MAX_FALLBACK_ENTRIES]; /* The number of current entry. */ int current_entryno; /* The address for Multiboot command-line buffer. */ @@ -97,7 +98,8 @@ init_config (void) { default_entry = 0; password = 0; - fallback_entry = -1; + fallback_entryno = -1; + fallback_entries[0] = -1; grub_timeout = -1; } @@ -1143,9 +1145,35 @@ static struct builtin builtin_embed = static int fallback_func (char *arg, int flags) { - if (! safe_parse_maxint (&arg, &fallback_entry)) - return 1; + int i = 0; + while (*arg) + { + int entry; + int j; + + if (! safe_parse_maxint (&arg, &entry)) + return 1; + + /* Remove duplications to prevent infinite looping. */ + for (j = 0; j < i; j++) + if (entry == fallback_entries[j]) + break; + if (j != i) + continue; + + fallback_entries[i++] = entry; + if (i == MAX_FALLBACK_ENTRIES) + break; + + arg = skip_to (0, arg); + } + + if (i < MAX_FALLBACK_ENTRIES) + fallback_entries[i] = -1; + + fallback_entryno = (i == 0) ? -1 : 0; + return 0; } @@ -1155,7 +1183,7 @@ static struct builtin builtin_fallback = fallback_func, BUILTIN_MENU, #if 0 - "fallback NUM", + "fallback NUM...", "Go into unattended boot mode: if the default boot entry has any" " errors, instead of waiting for the user to do anything, it" " immediately starts over using the NUM entry (same numbering as the" @@ -3185,8 +3213,29 @@ static int savedefault_func (char *arg, int flags) { #if !defined(SUPPORT_DISKLESS) && !defined(GRUB_UTIL) - char buffer[512]; - int *entryno_ptr; + unsigned long tmp_drive = saved_drive; + unsigned long tmp_partition = saved_partition; + char *default_file = (char *) DEFAULT_FILE_BUF; + char buf[10]; + char sect[SECTOR_SIZE]; + int entryno; + int sector_count = 0; + int saved_sectors[2]; + int saved_offsets[2]; + int saved_lengths[2]; + + /* Save sector information about at most two sectors. */ + auto void disk_read_savesect_func (int sector, int offset, int length); + void disk_read_savesect_func (int sector, int offset, int length) + { + if (sector_count < 2) + { + saved_sectors[sector_count] = sector; + saved_offsets[sector_count] = offset; + saved_lengths[sector_count] = length; + } + sector_count++; + } /* This command is only useful when you boot an entry from the menu interface. */ @@ -3195,46 +3244,110 @@ savedefault_func (char *arg, int flags) errnum = ERR_UNRECOGNIZED; return 1; } - - /* Get the geometry of the boot drive (i.e. the disk which contains - this stage2). */ - if (get_diskinfo (boot_drive, &buf_geom)) - { - errnum = ERR_NO_DISK; - return 1; - } - /* Load the second sector of this stage2. */ - if (! rawread (boot_drive, install_second_sector, 0, SECTOR_SIZE, buffer)) + /* Determine a saved entry number. */ + if (*arg) { - return 1; - } + if (grub_memcmp (arg, "fallback", sizeof ("fallback") - 1) == 0) + { + int i; + int index = 0; + + for (i = 0; i < MAX_FALLBACK_ENTRIES; i++) + { + if (fallback_entries[i] < 0) + break; + if (fallback_entries[i] == current_entryno) + { + index = i + 1; + break; + } + } + + if (index >= MAX_FALLBACK_ENTRIES || fallback_entries[index] < 0) + { + /* This is the last. */ + errnum = ERR_BAD_ARGUMENT; + return 1; + } - /* Sanity check. */ - if (buffer[STAGE2_STAGE2_ID] != STAGE2_ID_STAGE2 - || *((short *) (buffer + STAGE2_VER_MAJ_OFFS)) != COMPAT_VERSION) - { - errnum = ERR_BAD_VERSION; - return 1; - } - - entryno_ptr = (int *) (buffer + STAGE2_SAVED_ENTRYNO); - - /* Check if the saved entry number differs from current entry number. */ - if (*entryno_ptr != current_entryno) - { - /* Overwrite the saved entry number. */ - *entryno_ptr = current_entryno; - - /* Save the image in the disk. */ - if (! rawwrite (boot_drive, install_second_sector, buffer)) + entryno = fallback_entries[index]; + } + else if (! safe_parse_maxint (&arg, &entryno)) return 1; + } + else + entryno = current_entryno; + + /* Open the default file. */ + saved_drive = boot_drive; + saved_partition = install_partition; + if (grub_open (default_file)) + { + int len; + disk_read_hook = disk_read_savesect_func; + len = grub_read (buf, sizeof (buf)); + disk_read_hook = 0; + grub_close (); + + if (len != sizeof (buf)) + { + /* This is too small. Do not modify the file manually, please! */ + errnum = ERR_READ; + goto fail; + } + + if (sector_count > 2) + { + /* Is this possible?! Too fragmented! */ + errnum = ERR_FSYS_CORRUPT; + goto fail; + } + + /* Set up a string to be written. */ + grub_memset (buf, '\n', sizeof (buf)); + grub_sprintf (buf, "%d", entryno); + + if (saved_lengths[0] < sizeof (buf)) + { + /* The file is anchored to another file and the first few bytes + are spanned in two sectors. Uggh... */ + if (! rawread (current_drive, saved_sectors[0], 0, SECTOR_SIZE, + sect)) + goto fail; + grub_memmove (sect + saved_offsets[0], buf, saved_lengths[0]); + if (! rawwrite (current_drive, saved_sectors[0], sect)) + goto fail; + + if (! rawread (current_drive, saved_sectors[1], 0, SECTOR_SIZE, + sect)) + goto fail; + grub_memmove (sect + saved_offsets[1], + buf + saved_lengths[0], + sizeof (buf) - saved_lengths[0]); + if (! rawwrite (current_drive, saved_sectors[1], sect)) + goto fail; + } + else + { + /* This is a simple case. It fits into a single sector. */ + if (! rawread (current_drive, saved_sectors[0], 0, SECTOR_SIZE, + sect)) + goto fail; + grub_memmove (sect + saved_offsets[0], buf, sizeof (buf)); + if (! rawwrite (current_drive, saved_sectors[0], sect)) + goto fail; + } + /* Clear the cache. */ buf_track = -1; } - return 0; + fail: + saved_drive = tmp_drive; + saved_partition = tmp_partition; + return errnum; #else /* ! SUPPORT_DISKLESS && ! GRUB_UTIL */ errnum = ERR_UNRECOGNIZED; return 1; @@ -3246,8 +3359,10 @@ static struct builtin builtin_savedefault = "savedefault", savedefault_func, BUILTIN_CMDLINE, - "savedefault", - "Save the current entry as the default boot entry." + "savedefault [NUM | `fallback']", + "Save the current entry as the default boot entry if no argument is" + " specified. If a number is specified, this number is saved. If" + " `fallback' is used, next fallback entry is saved." }; diff --git a/stage2/cmdline.c b/stage2/cmdline.c index d6ea1281c..b3b8c6161 100644 --- a/stage2/cmdline.c +++ b/stage2/cmdline.c @@ -201,7 +201,7 @@ run_script (char *script, char *heap) /* If a fallback entry is defined, don't prompt a user's intervention. */ - if (fallback_entry < 0) + if (fallback_entryno >= 0) { grub_printf ("\nPress any key to continue..."); (void) getkey (); diff --git a/stage2/shared.h b/stage2/shared.h index 1cc8e063e..77eef11fc 100644 --- a/stage2/shared.h +++ b/stage2/shared.h @@ -97,8 +97,12 @@ extern char *grub_scratch_mem; #define PASSWORD_BUF RAW_ADDR (0x78000) #define PASSWORD_BUFLEN 0x200 +/* THe buffer for the filename of "/boot/grub/default". */ +#define DEFAULT_FILE_BUF (PASSWORD_BUF + PASSWORD_BUFLEN) +#define DEFAULT_FILE_BUFLEN 0x60 + /* The buffer for the command-line. */ -#define CMDLINE_BUF (PASSWORD_BUF + PASSWORD_BUFLEN) +#define CMDLINE_BUF (DEFAULT_FILE_BUF + DEFAULT_FILE_BUFLEN) #define CMDLINE_BUFLEN MAX_CMDLINE /* The kill buffer for the command-line. */ @@ -120,7 +124,7 @@ extern char *grub_scratch_mem; /* The buffer for the menu entries. */ #define MENU_BUF (UNIQUE_BUF + UNIQUE_BUFLEN) -#define MENU_BUFLEN (0x8000 + PASSWORD_BUF - UNIQUE_BUF) +#define MENU_BUFLEN (0x8000 + PASSWORD_BUF - MENU_BUF) /* The size of the drive map. */ #define DRIVE_MAP_SIZE 8 @@ -585,7 +589,9 @@ extern void assign_device_name (int drive, const char *device); #ifndef STAGE1_5 /* GUI interface variables. */ -extern int fallback_entry; +# define MAX_FALLBACK_ENTRIES 8 +extern int fallback_entries[MAX_FALLBACK_ENTRIES]; +extern int fallback_entryno; extern int default_entry; extern int current_entryno; diff --git a/stage2/stage2.c b/stage2/stage2.c index cd497aa64..f5982b6ff 100644 --- a/stage2/stage2.c +++ b/stage2/stage2.c @@ -390,7 +390,7 @@ restart: gotoxy (3, 22); printf (" "); grub_timeout = -1; - fallback_entry = -1; + fallback_entryno = -1; if (! (current_term->flags & TERM_DUMB)) gotoxy (74, 4 + entryno); } @@ -731,15 +731,18 @@ restart: if (run_script (cur_entry, heap)) { - if (fallback_entry < 0) - break; - else + if (fallback_entryno >= 0) { cur_entry = NULL; first_entry = 0; - entryno = fallback_entry; - fallback_entry = -1; + entryno = fallback_entries[fallback_entryno]; + fallback_entryno++; + if (fallback_entryno >= MAX_FALLBACK_ENTRIES + || fallback_entries[fallback_entryno] < 0) + fallback_entryno = -1; } + else + break; } else break; @@ -844,7 +847,7 @@ cmain (void) menu_entries = (char *) MENU_BUF; init_config (); } - + /* Initialize the environment for restarting Stage 2. */ grub_setjmp (restart_env); @@ -864,6 +867,36 @@ cmain (void) if (use_config_file) #endif /* GRUB_UTIL */ { + char *default_file = (char *) DEFAULT_FILE_BUF; + int i; + + /* Get a saved default entry if possible. */ + saved_entryno = 0; + grub_strncat (default_file, config_file, DEFAULT_FILE_BUFLEN); + for (i = grub_strlen(default_file); i >= 0; i--) + if (default_file[i] == '/') + { + i++; + break; + } + grub_strncat (default_file + i, "default", DEFAULT_FILE_BUFLEN - i); + if (grub_open (default_file)) + { + char buf[10]; /* This is good enough. */ + char *p = buf; + int len; + + len = grub_read (buf, sizeof (buf)); + if (len > 0) + { + buf[sizeof (buf) - 1] = 0; + safe_parse_maxint (&p, &saved_entryno); + } + + grub_close (); + } + errnum = ERR_NONE; + do { /* STATE 0: Before any title command. @@ -969,16 +1002,42 @@ cmain (void) grub_memmove (config_entries + config_len, menu_entries, menu_len); menu_entries = config_entries + config_len; - + + /* Make sure that all fallback entries are valid. */ + if (fallback_entryno >= 0) + { + for (i = 0; i < MAX_FALLBACK_ENTRIES; i++) + { + if (fallback_entries[i] < 0) + break; + if (fallback_entries[i] >= num_entries) + { + grub_memmove (fallback_entries + i, + fallback_entries + i + 1, + ((MAX_FALLBACK_ENTRIES - i - 1) + * sizeof (int))); + i--; + } + } + + if (fallback_entries[0] < 0) + fallback_entryno = -1; + } /* Check if the default entry is present. Otherwise reset it to fallback if fallback is valid, or to DEFAULT_ENTRY if not. */ if (default_entry >= num_entries) { - if (fallback_entry < 0 || fallback_entry >= num_entries) - default_entry = 0; + if (fallback_entryno >= 0) + { + default_entry = fallback_entries[0]; + fallback_entryno++; + if (fallback_entryno >= MAX_FALLBACK_ENTRIES + || fallback_entries[fallback_entryno] < 0) + fallback_entryno = -1; + } else - default_entry = fallback_entry; + default_entry = 0; } if (is_preset) diff --git a/util/Makefile.am b/util/Makefile.am index 48604d6db..2e0471135 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -1,5 +1,6 @@ bin_PROGRAMS = mbchk -sbin_SCRIPTS = grub-install grub-md5-crypt grub-terminfo +sbin_SCRIPTS = grub-install grub-md5-crypt grub-terminfo \ + grub-set-default noinst_SCRIPTS = grub-image mkbimage EXTRA_DIST = mkbimage diff --git a/util/Makefile.in b/util/Makefile.in index a1bebc065..f643f7fd2 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -42,7 +42,8 @@ bin_PROGRAMS = mbchk$(EXEEXT) subdir = util DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \ $(srcdir)/grub-image.in $(srcdir)/grub-install.in \ - $(srcdir)/grub-md5-crypt.in $(srcdir)/grub-terminfo.in + $(srcdir)/grub-md5-crypt.in $(srcdir)/grub-set-default.in \ + $(srcdir)/grub-terminfo.in ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ $(top_srcdir)/configure.ac @@ -51,7 +52,7 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = grub-image grub-install grub-md5-crypt \ - grub-terminfo + grub-terminfo grub-set-default am__installdirs = $(DESTDIR)$(bindir) $(DESTDIR)$(sbindir) binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) PROGRAMS = $(bin_PROGRAMS) @@ -180,7 +181,9 @@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ -sbin_SCRIPTS = grub-install grub-md5-crypt grub-terminfo +sbin_SCRIPTS = grub-install grub-md5-crypt grub-terminfo \ + grub-set-default + noinst_SCRIPTS = grub-image mkbimage EXTRA_DIST = mkbimage @@ -229,6 +232,8 @@ grub-md5-crypt: $(top_builddir)/config.status $(srcdir)/grub-md5-crypt.in cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ grub-terminfo: $(top_builddir)/config.status $(srcdir)/grub-terminfo.in cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +grub-set-default: $(top_builddir)/config.status $(srcdir)/grub-set-default.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ install-binPROGRAMS: $(bin_PROGRAMS) @$(NORMAL_INSTALL) $(mkdir_p) $(DESTDIR)$(bindir) diff --git a/util/grub-install.in b/util/grub-install.in index 4a547f947..33101f4cd 100644 --- a/util/grub-install.in +++ b/util/grub-install.in @@ -30,6 +30,7 @@ host_vendor=@host_vendor@ pkgdatadir=${datadir}/${PACKAGE}/${host_cpu}-${host_vendor} grub_shell=${sbindir}/grub +grub_set_default=${sbindir}/grub-set-default log_file=/tmp/grub-install.log.$$ img_file=/tmp/grub-install.img.$$ rootdir= @@ -273,8 +274,8 @@ fi # Initialize these directories here, since ROOTDIR was initialized. case "$host_os" in -netbsd*) - # Because /boot is used for the boot block in NetBSD, use /grub +netbsd* | openbsd*) + # Because /boot is used for the boot block in NetBSD and OpenBSD, use /grub # instead of /boot/grub. grub_prefix=/grub bootdir=${rootdir} @@ -410,6 +411,9 @@ for file in \ cp -f $file ${grubdir} || exit 1 done +# Make a default file. +${grub_set_default} --root-directory=${rootdir} default + # Make sure that GRUB reads the same images as the host OS. test -n "$mkimg" && img_file=`$mkimg` test -n "$mklog" && log_file=`$mklog` diff --git a/util/grub-set-default.in b/util/grub-set-default.in new file mode 100644 index 000000000..1c7fdc381 --- /dev/null +++ b/util/grub-set-default.in @@ -0,0 +1,114 @@ +#! /bin/sh + +# Set a default boot entry for GRUB +# Copyright (C) 2004 Free Software Foundation, Inc. +# +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +# Initialize some variables. +PACKAGE=@PACKAGE@ +VERSION=@VERSION@ + +rootdir= +entry= + +# Usage: usage +# Print the usage. +usage () { + cat <. +EOF +} + +# Check the arguments. +for option in "$@"; do + case "$option" in + -h | --help) + usage + exit 0 ;; + -v | --version) + echo "grub-set-default (GNU GRUB ${VERSION})" + exit 0 ;; + --root-directory=*) + rootdir=`echo "$option" | sed 's/--root-directory=//'` ;; + -*) + echo "Unrecognized option \`$option'" 1>&2 + usage + exit 1 + ;; + *) + if test "x$entry" != x; then + echo "More than one entries?" 1>&2 + usage + exit 1 + fi + # We don't care about what the user specified actually. + entry="${option}" ;; + esac +done + +if test "x$entry" = x; then + echo "entry not specified." 1>&2 + usage + exit 1 +fi + +# Determine the GRUB directory. This is different among OSes. +grubdir=${rootdir}/boot/grub +if test -d ${grubdir}; then + : +else + grubdir=${rootdir}/grub + if test -d ${grubdir}; then + : + else + echo "No GRUB directory found under ${rootdir}/" 1>&2 + exit 1 + fi +fi + +file=${grubdir}/default +if test -f ${file}; then + chmod 0600 ${file} + rm -f ${file} +fi +cat < $file +$entry +# +# +# +# +# +# +# +# +# +# +# WARNING: If you want to edit this file directly, do not remove any line +# from this file, including this warning. Using `grub-set-default' is +# strongly recommended. +EOF + +# Bye. +exit 0