]> code.delx.au - refind/commitdiff
Added refind-mkdefault script to reset rEFInd as the default boot
authorsrs5694 <srs5694@users.sourceforge.net>
Thu, 28 Jan 2016 04:23:43 +0000 (23:23 -0500)
committersrs5694 <srs5694@users.sourceforge.net>
Thu, 28 Jan 2016 04:23:43 +0000 (23:23 -0500)
program. (Currently useful only in Linux.)

NEWS.txt
docs/man/refind-mkdefault.8 [new file with mode: 0644]
refind-mkdefault [new file with mode: 0755]

index 6426604519a74874447310bf9f1fb4afc7e8dc98..013a8bcf66518490482c0fb751af54c1297ea6a5 100644 (file)
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -1,3 +1,11 @@
+0.10.3 (?/??/2016):
+-------------------
+
+- Added refind-mkdefault script to simplify resetting rEFInd as the default
+  boot program in Linux. The intent is to run this after GRUB, Windows, OS
+  X, or some other tool takes over as the primary boot manager. It can be
+  called from a startup script to handle this task automatically.
+
 0.10.2 (1/26/2016):
 -------------------
 
diff --git a/docs/man/refind-mkdefault.8 b/docs/man/refind-mkdefault.8
new file mode 100644 (file)
index 0000000..ae7818e
--- /dev/null
@@ -0,0 +1,108 @@
+.\" Copyright 2016 Roderick W. Smith (rodsmith@rodsbooks.com)
+.\" May be distributed under the GNU Free Documentation License version 1.3 or
+any later version
+.TH "REFIND-MKDEFAULT" "8" "0.10.3" "Roderick W. Smith" "rEFInd Manual"
+.SH "NAME"
+refind-mkdefault \- Set rEFInd as the default EFI boot option
+.SH "SYNOPSIS"
+.BI "refind-mkdefault "
+[ \-L|\-\-label <name> ]
+
+.SH "DESCRIPTION"
+
+EFI booting normally relies on boot manager entries stored in NVRAM, which
+describe the locations of EFI boot programs and the order in which the
+firmware will attempt to launch them. In Linux, these entries can be
+created, deleted, and manipulated with the \fIefibootmgr\fR utility.
+
+Many OSes and Linux packages assume that they should control the boot
+process, and so both create NVRAM boot entries for themselves and set these
+entries first in the boot order. If you intend rEFInd to control the boot
+process, though, such changes are undesirable and require adjustment via
+\fIefibootmgr\fR. Such adjustments are annoying to make and can be
+intimidating to non-experts.
+
+The \fIrefind-mkdefault\fR script simplifies matters: Running this script
+with no options sets rEFInd as the default boot program. The details of what
+the script does depends on the current state of the boot order list and
+existing boot entries:
+
+.TP
+.B *
+If a rEFInd entry already exists in the boot order and is already first
+in the list, no changes are made.
+
+.TP
+.B *
+If a rEFInd entry already exists in the boot order but is not first in
+the list, that entry is moved to the first position in the boot order.
+
+.TP
+.B *
+If more than one rEFInd entry exists in the boot order,
+\fIrefind-mkdefault\fR moves the one that comes earliest to the front of the
+boot order list.
+
+.TP
+.B *
+If no rEFInd entry exists in the boot order but a rEFInd boot entry
+can be found in the list of \fBBoot####\fR entries, it is added to the boot
+order and placed at the front of the list.
+
+.TP
+.B *
+If multiple rEFInd boot entries exist but none is in the boot order, all the
+entries are added to the boot order, but which one is first is uncontrolled.
+
+.PP
+
+A rEFInd entry is defined as one that contains the string \fBrefind\fR
+(case-insensitive). This string could exist in the description or in the
+filename. The string used to define the rEFInd entry can be changed via the
+\fI\-\-label\fR (\fI\-L\fR) option.
+
+The intent is that \fIrefind-mkdefault\fR can be called after booting via
+GRUB or some other means to restore rEFInd as the default boot program.  It
+can also be placed in a startup and/or shutdown script to restore rEFInd to
+its default position automatically. Because it does not re-write the boot
+order if rEFInd is listed as the first boot entry, this practice should be
+low in risk.
+
+.SH "OPTIONS"
+
+.TP
+.B \-L | \-\-label \fI<name>\fR
+Instead of searching for the string \fBrefind\fR in \fIefibootmgr\fR output
+as a way to identify rEFInd, search for the string \fBname\fR.
+
+.SH "LIMITATIONS"
+
+.TP
+.B *
+\fIrefind-mkdefault\fR does not work when booted in BIOS mode (including
+via a Compatibility Support Module, or CSM, on an EFI-based computer).
+Similarly, it does not work if \fIefibootmgr\fR is not installed or fails
+to work for any reason.
+
+.TP
+.B *
+The script uses a very simple algorithm to determine what to move to
+the start of the boot order list. This algorithm may fail if the system
+has redundant or non-functional rEFInd boot entries or if those entries
+are not named in an expected fashion. Cleaning up the boot entries by
+manual use of \fIefibootmgr\fR may be necessary in such cases.
+
+.SH "AUTHORS"
+Primary author: Roderick W. Smith (rodsmith@rodsbooks.com)
+
+.SH "SEE ALSO"
+\fBmvrefind (8)\fR,
+\fBmkrlconf (8)\fR,
+\fBrefind-install (8)\fR,
+\fBefibootmgr (8)\fR
+
+\fIhttp://www.rodsbooks.com/refind/\fR
+
+.SH "AVAILABILITY"
+The \fBrefind-mkdefault\fR command is part of the \fIrEFInd\fR package and is
+available from Roderick W. Smith.
diff --git a/refind-mkdefault b/refind-mkdefault
new file mode 100755 (executable)
index 0000000..325d382
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+
+"""
+Set rEFInd as the default boot loader, using Linux's efibootmgr tool.
+
+Copyright (c) 2016 Roderick W. Smith
+
+Authors:
+  Roderick W. Smith <rodsmith@rodsbooks.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 3, or
+(at your option) any later version, as published by the Free Software
+Foundation.
+
+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, see <http://www.gnu.org/licenses/>.
+"""
+
+import os
+import shutil
+import sys
+
+from subprocess import Popen, PIPE
+from argparse import ArgumentParser
+
+
+def discover_data():
+    """Extract boot entry and boot order information.
+
+    :returns:
+        boot_entries, boot_order
+    """
+    command = "efibootmgr -v"
+    bootinfo_bytes = (Popen(command, stdout=PIPE, shell=True)
+                      .communicate()[0])
+    bootinfo = (bootinfo_bytes.decode(encoding="utf-8", errors="ignore")
+                .splitlines())
+    boot_entries = {}
+    boot_order = []
+    if len(bootinfo) > 1:
+        for s in bootinfo:
+            if "BootOrder" in s:
+                try:
+                    boot_order = s.split(":")[1].replace(" ", "").split(",")
+                except IndexError:
+                    pass
+            else:
+                # On Boot#### lines, #### is characters 4-8....
+                hex_value = s[4:8]
+                # ....and the description starts at character 10
+                name = s[10:]
+                try:
+                    # In normal efibootmgr output, only Boot#### entries
+                    # have characters 4-8 that can be interpreted as
+                    # hex values, so this will harmlessly error out on all
+                    # but Boot#### entries....
+                    int(hex_value, 16)
+                    boot_entries[hex_value] = name
+                except ValueError:
+                    pass
+    return boot_entries, boot_order
+
+
+def add_unordered_entry(boot_entries, boot_order, label):
+    """Find a rEFInd boot_entry and add it to the boot_order list.
+
+    Run if the boot_order list includes no rEFInd entry, in the
+    hopes of finding an existing rEFInd boot_entry that can be
+    used.
+    :param boot_entries:
+        Dictionary of boot entries, with string (hex-encoded number) as
+        key and description as value
+    :param boot_order:
+        List of boot numbers as strings, in boot order
+    :param label:
+        String used to identify rEFInd entry in efibootmgr output
+    :returns:
+        True if an entry was added, False otherwise
+    """
+    added = False
+    for boot_num, description in boot_entries.items():
+        if label.lower() in description.lower():
+            print("Adding Boot{} from boot options list.".format(boot_num))
+            boot_order.insert(0, boot_num)
+            added = True
+    return added
+
+
+def set_refind_first(boot_entries, boot_order, label):
+    """Adjust boot_order so that rEFInd is first.
+
+    :param boot_entries:
+        Dictionary of boot entries, with string (hex-encoded number) as
+        key and description as value
+    :param boot_order:
+        List of boot numbers as strings, in boot order
+    :param label:
+        String used to identify rEFInd entry in efibootmgr output
+    :returns:
+        True if order adjusted, False otherwise
+    """
+    first_refind_number = i = -1
+    changed_order = False
+    found_first_refind = ""
+    show_multiple_warning = True
+    for entry in boot_order:
+        i += 1
+        if label.lower() in boot_entries[entry].lower():
+            if found_first_refind:
+                if show_multiple_warning:
+                    print("Found multiple {} entries! The earliest in the boot order will be made".format(label))
+                    print("the default, but this may not be what you want. Manually checking with")
+                    print("efibootmgr is advisable!\n")
+                    show_multiple_warning = False
+            else:
+                found_first_refind = entry
+                first_refind_number = i
+    if first_refind_number == -1:
+        if add_unordered_entry(boot_entries, boot_order, label):
+            changed_order = True
+        else:
+            print("{} was not found in the boot options list!".format(label))
+            print("You should create a {} entry with efibootmgr or by re-installing".format(label))
+            print("(with refind-install, for example)")
+    elif first_refind_number == 0:
+        print("{} is already the first entry".format(label))
+    elif first_refind_number > 0:
+        del boot_order[first_refind_number]
+        boot_order.insert(0, found_first_refind)
+        changed_order = True
+
+        print("{} is not the first boot entry; adjusting....".format(label))
+    return changed_order
+
+
+def save_changes(boot_order):
+    """Save an altered boot_order.
+
+    :returns:
+        True if there were no problems, False otherwise
+    """
+    order_string = ",".join(boot_order)
+    command = "efibootmgr -o {}".format(order_string)
+    print("Setting a boot order of {}".format(order_string))
+    try:
+        Popen(command, stdout=PIPE, shell=True).communicate()[0]
+    except:
+        print("An error occurred setting the new boot order!")
+
+
+def main():
+    """Set rEFInd as the default boot option."""
+    description = "Sets rEFInd as the default EFI boot option"
+    parser = ArgumentParser(description=description)
+    parser.add_argument("-L", "--label",
+                        default="rEFInd",
+                        help=("The label used to identify rEFInd"))
+    args = parser.parse_args()
+
+    if sys.platform != "linux":
+        print("This program is useful only under Linux; exiting!")
+        return(1)
+    if shutil.which("efibootmgr") is None:
+        print("The efibootmgr utility is not installed; exiting!")
+        return(1)
+    if not os.geteuid() == 0:
+        print("You must be root to run this program")
+        return(1)
+
+    problems = False
+    retval = 0
+    boot_entries, boot_order = discover_data()
+    if boot_entries == {}:
+        problems = True
+        print("No EFI boot entries available. This may indicate a firmware problem.")
+    if boot_order == []:
+        problems = True
+        print("The EFI BootOrder variable is not available. This may indicate a firmware")
+        print("problem.")
+    if (boot_entries != {} and boot_order != [] and
+            set_refind_first(boot_entries, boot_order, args.label)):
+        save_changes(boot_order)
+    else:
+        if problems:
+            retval = 1
+        print("No changes saved.")
+    return(retval)
+
+if __name__ == '__main__':
+    sys.exit(main())