Added note about Mac "retina" display problems to todo.html file.
[refind] / refind-mkdefault
1 #!/usr/bin/env python3
2
3 """
4 Set rEFInd as the default boot loader, using Linux's efibootmgr tool.
5
6 Copyright (c) 2016 Roderick W. Smith
7
8 Authors:
9 Roderick W. Smith <rodsmith@rodsbooks.com>
10
11 This program is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License version 3, or
13 (at your option) any later version, as published by the Free Software
14 Foundation.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 """
24
25 import os
26 import shlex
27 import shutil
28 import sys
29
30 from subprocess import Popen, PIPE
31 from argparse import ArgumentParser
32
33
34 def discover_data():
35 """Extract boot entry and boot order information.
36
37 :returns:
38 boot_entries, boot_order
39 """
40 command = "efibootmgr -v"
41 bootinfo_bytes = (Popen(shlex.split(command), stdout=PIPE)
42 .communicate()[0])
43 bootinfo = (bootinfo_bytes.decode(encoding="utf-8", errors="ignore")
44 .splitlines())
45 boot_entries = {}
46 boot_order = []
47 if len(bootinfo) > 1:
48 for s in bootinfo:
49 if "BootOrder" in s:
50 try:
51 boot_order = s.split(":")[1].replace(" ", "").split(",")
52 except IndexError:
53 pass
54 else:
55 # On Boot#### lines, #### is characters 4-8....
56 hex_value = s[4:8]
57 # ....and the description starts at character 10
58 name = s[10:]
59 try:
60 # In normal efibootmgr output, only Boot#### entries
61 # have characters 4-8 that can be interpreted as
62 # hex values, so this will harmlessly error out on all
63 # but Boot#### entries....
64 int(hex_value, 16)
65 boot_entries[hex_value] = name
66 except ValueError:
67 pass
68 return boot_entries, boot_order
69
70
71 def add_unordered_entry(boot_entries, boot_order, label):
72 """Find a rEFInd boot_entry and add it to the boot_order list.
73
74 Run if the boot_order list includes no rEFInd entry, in the
75 hopes of finding an existing rEFInd boot_entry that can be
76 used.
77 :param boot_entries:
78 Dictionary of boot entries, with string (hex-encoded number) as
79 key and description as value
80 :param boot_order:
81 List of boot numbers as strings, in boot order
82 :param label:
83 String used to identify rEFInd entry in efibootmgr output
84 :returns:
85 True if an entry was added, False otherwise
86 """
87 added = False
88 for boot_num, description in boot_entries.items():
89 if label.lower() in description.lower():
90 print("Adding Boot{} from boot options list.".format(boot_num))
91 boot_order.insert(0, boot_num)
92 added = True
93 return added
94
95
96 def set_refind_first(boot_entries, boot_order, label):
97 """Adjust boot_order so that rEFInd is first.
98
99 :param boot_entries:
100 Dictionary of boot entries, with string (hex-encoded number) as
101 key and description as value
102 :param boot_order:
103 List of boot numbers as strings, in boot order
104 :param label:
105 String used to identify rEFInd entry in efibootmgr output
106 :returns:
107 * -1 if order already OK
108 * 0 if order adjusted
109 * 3 if label was not found in available entries
110 """
111 first_refind_number = i = -1
112 retval = 0
113 found_first_refind = ""
114 show_multiple_warning = True
115 for entry in boot_order:
116 i += 1
117 if label.lower() in boot_entries[entry].lower():
118 if found_first_refind:
119 if show_multiple_warning:
120 print("Found multiple {} entries! The earliest in the boot order will be made".format(label))
121 print("the default, but this may not be what you want. Manually checking with")
122 print("efibootmgr is advisable!\n")
123 show_multiple_warning = False
124 else:
125 found_first_refind = entry
126 first_refind_number = i
127 if first_refind_number == -1:
128 if not add_unordered_entry(boot_entries, boot_order, label):
129 print("{} was not found in the boot options list!".format(label))
130 print("You should create a {} entry with efibootmgr or by re-installing".format(label))
131 print("(with refind-install, for example)")
132 retval = 3
133 elif first_refind_number == 0:
134 print("{} is already the first entry".format(label))
135 retval = -1
136 elif first_refind_number > 0:
137 del boot_order[first_refind_number]
138 boot_order.insert(0, found_first_refind)
139
140 print("{} is not the first boot entry; adjusting....".format(label))
141 return retval
142
143
144 def save_changes(boot_order):
145 """Save an altered boot_order.
146
147 :param boot_order:
148 List of boot numbers as strings, in boot order
149 :returns:
150 0 if there were no problems, 1 otherwise
151 """
152 retval = 0
153 order_string = ",".join(boot_order)
154 command = "efibootmgr -o {}".format(order_string)
155 print("Setting a boot order of {}".format(order_string))
156 try:
157 Popen(shlex.split(command), stdout=PIPE).communicate()[0]
158 except:
159 print("An error occurred setting the new boot order!")
160 retval = 1
161 return retval
162
163
164 def main():
165 """Set rEFInd as the default boot option."""
166 description = "Sets rEFInd as the default EFI boot option"
167 parser = ArgumentParser(description=description)
168 parser.add_argument("-L", "--label",
169 default="rEFInd",
170 help=("The label used to identify rEFInd (default=rEFInd)"))
171 args = parser.parse_args()
172
173 if sys.platform != "linux":
174 print("This program is useful only under Linux; exiting!")
175 return(1)
176 if shutil.which("efibootmgr") is None:
177 print("The efibootmgr utility is not installed; exiting!")
178 return(1)
179 if not os.geteuid() == 0:
180 print("This program must be run as root (or via sudo); exiting!")
181 return(1)
182
183 retval = 0
184 boot_entries, boot_order = discover_data()
185 if boot_entries == {}:
186 print("No EFI boot entries are available. This may indicate a firmware problem.")
187 retval = 2
188 if boot_order == []:
189 print("The EFI BootOrder variable is not available. This may indicate a firmware")
190 print("problem.")
191 if (retval == 0):
192 changed = set_refind_first(boot_entries, boot_order, args.label)
193 if (changed == 0):
194 retval = save_changes(boot_order)
195 else:
196 print("No changes saved.")
197 if changed > 0:
198 retval = changed
199 else:
200 print("No changes saved.")
201 return(retval)
202
203 if __name__ == '__main__':
204 sys.exit(main())