Submitted By: Oliver Brakmann <obrakmann at gmx dot de>
Date: 2003-08-17
Initial Package Version: 0.93
Origin: http://mail.gnu.org/archive/html/bug-grub/2002-02/msg00151.html
	http://mail.gnu.org/archive/html/bug-grub/2002-03/msg00011.html
	modifications for grub 0.93 by me

Description:
The following is patch to grub-0.90 which provides one-shot setting of the
default boot entry. If the 'savedentry' field in the stage2 file has a
ONCEONLY flag set, the entry is reset to zero at boot time.

The 'savedefault' function has been rewritten for the GRUB shell, do that
you can do something like:
# savedefault --default=1 --once
This specifies a one-shot change of the default to entry 1. A one-shot entry
is latched by the 'default' command at boot time, even if the 'default'
argument is not specified (ie. one-shot overrides a numeric argument).

The patch is pretty clean -- it works perfectly against 0.91 as well (except
the ChangeLog update). The rewritten 'savedefault' may need fixing up -- in
particular, it will only patch a stage2 file which is accessible via a
mounted filesystem.

[...]

The following is a slight modification to the patch I submitted a week or so
ago. This will cause GRUB to skip displaying the boot menu if a one-shot
default has been specified (just as lilo does with '-R').

Same caveats as for previous patch:
 * The new 'savedefault' function in the GRUB shell is incomplete.
   Only works if the filesystem containing stage2 file is mounted.
 * Patch is against grub-0.90, but patches directly to 0.91

I hope this goes into CVS in the next round of updates :-)

 -- Keir Fraser


diff -Naur grub-0.93-orig/ChangeLog grub-0.93/ChangeLog
--- grub-0.93-orig/ChangeLog	2003-08-17 11:27:16.000000000 +0000
+++ grub-0.93/ChangeLog	2003-08-17 11:27:39.000000000 +0000
@@ -672,6 +672,13 @@
 	* stage2/cmdline.c (run_script): Prompt a user's intervention,
 	only when FALLBACK_ENTRY is negative.
 	
+2002-02-22  Keir Fraser  <kaf24@bogus.example.com>
+
+	* stage2/shared.h: New flag STAGE2_ONCEONLY_ENTRY causes
+	SAVED_ENTRYNO to be reset to zero on next reboot.
+	* stage2/builtins.c: A new version of 'savedefault' function
+	for the GRUB shell. Provides 'lilo -R' functionality.
+
 2002-02-11  Pavel Roskin  <proski@gnu.org>
 
 	* util/grub-install.in (find_device): New function - find block
diff -Naur grub-0.93-orig/stage2/builtins.c grub-0.93/stage2/builtins.c
--- grub-0.93-orig/stage2/builtins.c	2003-08-17 11:27:16.000000000 +0000
+++ grub-0.93/stage2/builtins.c	2003-08-17 11:27:39.000000000 +0000
@@ -760,6 +760,17 @@
 default_func (char *arg, int flags)
 {
 #ifndef SUPPORT_DISKLESS
+#ifndef GRUB_UTIL
+  /* Has a forced once-only default been specified? */
+  static int savedefault_helper(int);
+  if ((saved_entryno & STAGE2_ONCEONLY_ENTRY) != 0)
+    {
+      grub_timeout = 0;
+      default_entry = saved_entryno & ~STAGE2_ONCEONLY_ENTRY;
+      savedefault_helper(0);
+      return 0;
+    }
+#endif
   if (grub_strcmp (arg, "saved") == 0)
     {
       default_entry = saved_entryno;
@@ -3175,21 +3186,14 @@
 
 
 /* savedefault */
+#if !defined(SUPPORT_DISKLESS) && !defined(GRUB_UTIL)
+/* Write specified default entry number into stage2 file. */
 static int
-savedefault_func (char *arg, int flags)
+savedefault_helper(int new_default)
 {
-#if !defined(SUPPORT_DISKLESS) && !defined(GRUB_UTIL)
   char buffer[512];
   int *entryno_ptr;
-  
-  /* This command is only useful when you boot an entry from the menu
-     interface.  */
-  if (! (flags & BUILTIN_SCRIPT))
-    {
-      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))
@@ -3215,10 +3219,10 @@
   entryno_ptr = (int *) (buffer + STAGE2_SAVED_ENTRYNO);
 
   /* Check if the saved entry number differs from current entry number.  */
-  if (*entryno_ptr != current_entryno)
+  if (*entryno_ptr != new_default)
     {
       /* Overwrite the saved entry number.  */
-      *entryno_ptr = current_entryno;
+      *entryno_ptr = new_default;
       
       /* Save the image in the disk.  */
       if (! rawwrite (boot_drive, install_second_sector, buffer))
@@ -3229,6 +3233,117 @@
     }
 
   return 0;
+}
+#endif
+
+#if !defined(SUPPORT_DISKLESS) && defined(GRUB_UTIL)
+/*
+ * Full implementation of new avedefault' for GRUB shell.
+ * XXX This needs fixing for stage2 files which aren't accessible
+ *     through a mounted filesystem.
+ */
+static int
+savedefault_shell(char *arg, int flags)
+{
+  char *stage2_os_file = "/boot/grub/stage2"; /* Default filename */
+  FILE *fp;
+  char buffer[512];
+  int *entryno_ptr;
+  int new_default = 0;
+
+  while (1)
+    {
+      if (grub_memcmp ("--stage2=", arg, sizeof ("--stage2=") - 1) == 0)
+        {
+          stage2_os_file = arg + sizeof ("--stage2=") - 1;
+          arg = skip_to (0, arg);
+          nul_terminate (stage2_os_file);
+        }
+      else if (grub_memcmp ("--default=", arg, sizeof ("--default=") - 1) == 0)
+        {
+          char *p = arg + sizeof ("--default=") - 1;
+          if (! safe_parse_maxint (&p, &new_default))
+            return 1;
+          arg = skip_to (0, arg);
+        }
+      else if (grub_memcmp ("--once", arg, sizeof ("--once") - 1) == 0)
+        {
+          new_default |= STAGE2_ONCEONLY_ENTRY;
+          arg = skip_to (0, arg);
+        }
+      else
+        break;
+    }
+
+  if (! (fp = fopen(stage2_os_file, "r+")))
+    {
+      errnum = ERR_FILE_NOT_FOUND;
+      return 1;
+    }
+  
+  if (fseek (fp, SECTOR_SIZE, SEEK_SET) != 0)
+    {
+      fclose (fp);
+      errnum = ERR_BAD_VERSION;
+      return 1;
+    }
+  
+  if (fread (buffer, 1, SECTOR_SIZE, fp) != SECTOR_SIZE)
+    {
+      fclose (fp);
+      errnum = ERR_READ;
+      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);
+  *entryno_ptr = new_default;
+
+  if (fseek (fp, SECTOR_SIZE, SEEK_SET) != 0)
+    {
+      fclose (fp);
+      errnum = ERR_BAD_VERSION;
+      return 1;
+    }
+  
+  if (fwrite (buffer, 1, SECTOR_SIZE, fp) != SECTOR_SIZE)
+    {
+      fclose (fp);
+      errnum = ERR_WRITE;
+      return 1;
+    }
+  
+  (void)fflush (fp);
+  fclose (fp);
+  return 0;
+}
+#endif
+
+/* savedefault */
+static int
+savedefault_func (char *arg, int flags)
+{
+#if !defined(SUPPORT_DISKLESS)
+#if !defined(GRUB_UTIL)
+  /* This command is only useful when you boot an entry from the menu
+     interface.  */
+  if (! (flags & BUILTIN_SCRIPT))
+    {
+      errnum = ERR_UNRECOGNIZED;
+      return 1;
+    }
+
+  return savedefault_helper(current_entryno);
+#else /* defined(GRUB_UTIL) */
+  return savedefault_shell(arg, flags);
+#endif
 #else /* ! SUPPORT_DISKLESS && ! GRUB_UTIL */
   errnum = ERR_UNRECOGNIZED;
   return 1;
@@ -3240,8 +3355,14 @@
   "savedefault",
   savedefault_func,
   BUILTIN_CMDLINE,
+#ifdef GRUB_UTIL
+  "savedefault [--stage2=STAGE2_FILE] [--default=DEFAULT] [--once]",
+  "Save DEFAULT as the default boot entry in STAGE2_FILE. If '--once'"
+  " is specified, the default is reset after the next reboot."
+#else
   "savedefault",
   "Save the current entry as the default boot entry."
+#endif
 };
 
 
@@ -4458,6 +4579,15 @@
 static int
 timeout_func (char *arg, int flags)
 {
+  /* One-shot default shenanigans -- don't piss around with the menu! */
+  if (grub_timeout != -1)
+    return 0;
+  if ((saved_entryno & STAGE2_ONCEONLY_ENTRY) != 0)
+    {
+      grub_timeout = 0;
+      return 0;
+    }
+
   if (! safe_parse_maxint (&arg, &grub_timeout))
     return 1;
 
diff -Naur grub-0.93-orig/stage2/shared.h grub-0.93/stage2/shared.h
--- grub-0.93-orig/stage2/shared.h	2003-08-17 11:27:16.000000000 +0000
+++ grub-0.93/stage2/shared.h	2003-08-17 11:27:39.000000000 +0000
@@ -195,6 +195,8 @@
 #define STAGE2_FORCE_LBA	0x11
 #define STAGE2_VER_STR_OFFS	0x12
 
+#define STAGE2_ONCEONLY_ENTRY   0x10000
+
 /* Stage 2 identifiers */
 #define STAGE2_ID_STAGE2		0
 #define STAGE2_ID_FFS_STAGE1_5		1
