Skip to content

Commit 177625b

Browse files
committed
main & co: Support upgrading m1n1 stage 1
This is finally useful now that we've run into and fixed NVMe timeout related issues in stage 1. Signed-off-by: Hector Martin <[email protected]>
1 parent cd6b27f commit 177625b

4 files changed

Lines changed: 82 additions & 24 deletions

File tree

src/m1n1.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SPDX-License-Identifier: MIT
2+
3+
def build(src, dest, vars):
4+
if isinstance(vars, (list, tuple)):
5+
vars = b"".join(i.encode("ascii") + b"\n" for i in vars) + b"\0\0\0\0"
6+
7+
with open(src, "rb") as fd:
8+
m1n1_data = fd.read()
9+
10+
with open(dest, "wb") as fd:
11+
fd.write(m1n1_data + vars)
12+
13+
def extract_vars(src):
14+
with open(src, "rb") as fd:
15+
m1n1_data = fd.read()
16+
17+
try:
18+
vars = m1n1_data.split(b"STACKBOT")[1].split(b"\0")[0].decode("ascii")
19+
except Exception:
20+
return None
21+
22+
return [i for i in vars.split("\n") if i]
23+
24+
def get_version(path):
25+
data = open(path, "rb").read()
26+
if b"##m1n1_ver##" in data:
27+
return data.split(b"##m1n1_ver##")[1].split(b"\0")[0].decode("ascii")
28+
else:
29+
return None

src/main.py

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os, os.path, shlex, subprocess, sys, time, termios, json, getpass
44
from dataclasses import dataclass
55

6-
import system, osenum, stub, diskutil, osinstall, asahi_firmware
6+
import system, osenum, stub, diskutil, osinstall, asahi_firmware, m1n1
77
from util import *
88

99
PART_ALIGN = psize("1MiB")
@@ -89,6 +89,8 @@ def __init__(self):
8989
self.ipsw = None
9090
self.osins = None
9191
self.osi = None
92+
self.m1n1 = "boot/m1n1.bin"
93+
self.m1n1_ver = m1n1.get_version(self.m1n1)
9294

9395
def input(self):
9496
self.flush_input()
@@ -307,30 +309,58 @@ def action_install_into_free(self, avail_free):
307309

308310
self.do_install(os_size)
309311

310-
def action_resume(self, oses):
312+
def action_resume_or_upgrade(self, oses, upgrade):
311313
choices = {str(i): f"{p.desc}\n {str(o)}" for i, (p, o) in enumerate(oses)}
312314

313315
if len(choices) > 1:
314316
print()
315-
p_question("Choose an incomplete install to resume:")
317+
if upgrade:
318+
p_question("Choose an existing install to upgrade:")
319+
else:
320+
p_question("Choose an incomplete install to resume:")
316321
idx = self.choice("Installed OS", choices)
317322
else:
318323
idx = list(choices.keys())[0]
319324

320325
self.part, osi = oses[int(idx)]
321326

322-
p_progress(f"Resuming installation into {self.part.name} ({self.part.label})")
327+
if upgrade:
328+
p_progress(f"Upgrading installation {self.part.name} ({self.part.label})")
329+
p_info(f" Old m1n1 stage 1 version: {osi.m1n1_ver}")
330+
p_info(f" New m1n1 stage 1 version: {self.m1n1_ver}")
331+
print()
332+
else:
333+
p_progress(f"Resuming installation into {self.part.name} ({self.part.label})")
323334

324335
self.ins = stub.StubInstaller(self.sysinfo, self.dutil, self.osinfo)
325336
if not self.ins.check_existing_install(osi):
326-
p_error("The existing installation is missing files.")
327-
p_message("This tool can only resume installations that completed the first")
328-
p_message("stage of the installation process. If it was interrupted, please")
329-
p_message("delete the partitions manually and reinstall from scratch.")
330-
return True
337+
op = "upgrade" if upgrade else "resume"
338+
p_error( "The existing installation is missing files.")
339+
p_message(f"This tool can only {op} installations that completed the first")
340+
p_message( "stage of the installation process. If it was interrupted, please")
341+
p_message( "delete the partitions manually and reinstall from scratch.")
342+
return False
331343

332344
self.dutil.remount_rw(self.ins.osi.system)
333345

346+
if upgrade:
347+
# Note: we get the vars out of the boot.bin in the system volume instead of the
348+
# actual installed fuOS. This is arguably the better option, since it allows
349+
# users to fix their install using this functionality if they messed up the boot
350+
# object.
351+
vars = m1n1.extract_vars(self.ins.boot_obj_path)
352+
if vars is None:
353+
p_error("Could not get variables from the installed m1n1")
354+
p_message(f"Path: {self.ins.boot_obj_path}")
355+
return False
356+
357+
p_progress(f"Transferring m1n1 variables:")
358+
for v in vars:
359+
p_info(f" {v}")
360+
361+
print()
362+
m1n1.build(self.m1n1, self.ins.boot_obj_path, vars)
363+
334364
# Unhide the SystemVersion, if hidden
335365
self.ins.prepare_for_bless()
336366

@@ -792,6 +822,7 @@ def main_loop(self):
792822
parts_empty_apfs = []
793823
parts_resizable = []
794824
oses_incomplete = []
825+
oses_upgradable = []
795826

796827
for i, p in enumerate(self.parts):
797828
if p.type in ("Apple_APFS_ISC",):
@@ -853,7 +884,9 @@ def main_loop(self):
853884
else:
854885
state += " "
855886
p_plain(f" OS: [{state}] {os}")
856-
if os.stub and not (os.bp and os.bp.get("coih", None)):
887+
if os.stub and os.m1n1_ver and os.m1n1_ver != self.m1n1_ver:
888+
oses_upgradable.append((p, os))
889+
elif os.stub and not (os.bp and os.bp.get("coih", None)):
857890
oses_incomplete.append((p, os))
858891

859892
print()
@@ -879,8 +912,9 @@ def main_loop(self):
879912
if parts_resizable:
880913
actions["r"] = "Resize an existing partition to make space for a new OS"
881914
default = default or "r"
882-
if self.sysinfo.boot_mode == "one true recoveryOS" and False:
883-
actions["m"] = "Upgrade bootloader of an existing OS"
915+
if oses_upgradable:
916+
actions["m"] = "Upgrade m1n1 on an existing OS"
917+
default = default or "m"
884918

885919
if not actions:
886920
p_error("No actions available on this system.")
@@ -900,10 +934,9 @@ def main_loop(self):
900934
elif act == "r":
901935
return self.action_resize(parts_resizable)
902936
elif act == "m":
903-
p_error("Unimplemented")
904-
sys.exit(1)
937+
return self.action_resume_or_upgrade(oses_upgradable, upgrade=True)
905938
elif act == "p":
906-
return self.action_resume(oses_incomplete)
939+
return self.action_resume_or_upgrade(oses_incomplete, upgrade=False)
907940
elif act == "q":
908941
return False
909942

src/osenum.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os, os.path, plistlib, subprocess, logging
33
from dataclasses import dataclass
44

5+
import m1n1
56
from util import *
67

78
UUID_SROS = "3D3287DE-280D-4619-AAAB-D97469CA9C71"
@@ -204,9 +205,8 @@ def collect_os(self, part, volumes, vgid):
204205
osi.bp["nsih"],
205206
"System/Library/Caches/com.apple.kernelcaches",
206207
"kernelcache.custom." + coih)
207-
fuos = open(fuos_path, "rb").read()
208-
if b"##m1n1_ver##" in fuos:
209-
osi.m1n1_ver = fuos.split(b"##m1n1_ver##")[1].split(b"\0")[0].decode("ascii")
208+
osi.m1n1_ver = m1n1.get_version(fuos_path)
209+
if osi.m1n1_ver:
210210
logging.info(f" m1n1 version found: {osi.m1n1_ver}")
211211

212212
if b": Paired" in bps:

src/osinstall.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SPDX-License-Identifier: MIT
22
import os, shutil, sys, stat, subprocess, urlcache, zipfile, logging
33

4+
import m1n1
45
from util import *
56

67
class OSInstaller(PackageInstaller):
@@ -138,8 +139,6 @@ def install(self, boot_obj_path):
138139
next_object = self.template.get("next_object", None)
139140
logging.info(f" Boot object: {boot_object}")
140141
logging.info(f" Next object: {next_object}")
141-
with open(os.path.join("boot", boot_object), "rb") as fd:
142-
m1n1_data = fd.read()
143142

144143
m1n1_vars = []
145144
if self.efi_part:
@@ -152,9 +151,6 @@ def install(self, boot_obj_path):
152151
for i in m1n1_vars:
153152
logging.info(f" {i}")
154153

155-
m1n1_data += b"".join(i.encode("ascii") + b"\n" for i in m1n1_vars) + b"\0\0\0\0"
156-
157-
with open(boot_obj_path, "wb") as fd:
158-
fd.write(m1n1_data)
154+
m1n1.build(os.path.join("boot", boot_object), boot_obj_path, m1n1_vars)
159155

160156
logging.info(f"Built boot object at {boot_obj_path}")

0 commit comments

Comments
 (0)