]> code.delx.au - refind/blobdiff - refind-mkdefault
Added refind-mkdefault script to reset rEFInd as the default boot
[refind] / refind-mkdefault
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())