11"""
22macOS Boot Sector module for SmartBoot
33
4- Strategy:
5- BIOS: dd MBR (best we can do without a Mac-native bootloader)
6- UEFI: copy system EFI → syslinux efi64 → stub
7- FreeDOS: delegates to BIOS
4+ Strategy (Rufus-parity where possible on macOS):
5+
6+ BIOS:
7+ 1. dd generic MBR (446 bytes, conv=notrunc) — best we can do without
8+ a native x86 bootloader package on macOS
9+ 2. Set boot flag via pdisk / diskutil (last resort)
10+
11+ UEFI:
12+ 1. Copy known macOS EFI files (apfs.efi, boot.efi, gummiboot)
13+ 2. Walk /usr/share/efi, /usr/lib/efi
14+ 3. Minimal PE32+ stub
15+
16+ FreeDOS:
17+ Delegates to BIOS chain.
818"""
919
1020import os
@@ -30,25 +40,25 @@ def _sudo(cmd: List[str], **kwargs) -> subprocess.CompletedProcess:
3040
3141
3242class MacOSBootSector (BaseBootSector ):
33- """macOS-specific boot sector implementation ."""
43+ """macOS-specific boot sector writer ."""
3444
3545 _EFI_SOURCES = [
46+ "/System/Library/CoreServices/boot.efi" ,
3647 "/usr/standalone/i386/apfs.efi" ,
3748 "/usr/standalone/i386/EfiLoginUI.efi" ,
38- "/System/Library/CoreServices/boot.efi" ,
39- "/usr/share/syslinux/efi64/syslinux.efi" ,
4049 "/usr/local/lib/syslinux/efi64/syslinux.efi" ,
50+ "/opt/local/share/syslinux/efi64/syslinux.efi" ,
51+ "/usr/local/share/refind/refind_x64.efi" ,
52+ "/opt/homebrew/share/refind/refind_x64.efi" ,
4153 ]
4254
55+
4356 def check_admin_privileges (self ) -> bool :
4457 try :
4558 return _sudo (["true" ], timeout = 5 ).returncode == 0
4659 except Exception :
4760 return False
4861
49- # ------------------------------------------------------------------
50- # Helpers
51- # ------------------------------------------------------------------
5262
5363 def _dev_path (self , device : Dict [str , Any ]) -> Optional [str ]:
5464 name = device .get ("name" , "" )
@@ -63,17 +73,25 @@ def _get_partition(self, dev: str, number: int = 1) -> Optional[str]:
6373 return candidate
6474 return None
6575
66- def _mount_partition (self , partition : str ) -> Optional [str ]:
67- """Mount via diskutil and return the mount point."""
76+ def _wait_for_partition (self , dev : str , number : int = 1 ,
77+ retries : int = 6 ) -> Optional [str ]:
78+ for _ in range (retries ):
79+ p = self ._get_partition (dev , number )
80+ if p :
81+ return p
82+ time .sleep (1 )
83+ return None
84+
85+ def _diskutil_mount (self , partition : str ) -> Optional [str ]:
86+ """Mount partition via diskutil and return mount point."""
6887 try :
6988 r = _run (["diskutil" , "mount" , partition ], timeout = 20 )
7089 if r .returncode != 0 :
7190 return None
72- # Parse "Volume XXX on /dev/disk1s1 mounted"
7391 for line in r .stdout .splitlines ():
7492 if "mounted" in line .lower () and " on " in line .lower ():
7593 mp = line .split (" on " , 1 )[- 1 ].strip ().rstrip ("." )
76- if os .path .exists (mp ):
94+ if mp and os .path .exists (mp ):
7795 return mp
7896 except Exception as exc :
7997 logger .warning (f"diskutil mount { partition } : { exc } " )
@@ -82,26 +100,22 @@ def _mount_partition(self, partition: str) -> Optional[str]:
82100 def _resolve_mount_point (
83101 self , device : Dict [str , Any ], partition : str
84102 ) -> Optional [str ]:
85- """Return an existing mount point for the partition."""
86103 mp = device .get ("drive_letter" , "" )
87- if mp and os .path .exists (mp ):
104+ if mp and os .path .isdir (mp ):
88105 return mp
89- # Ask diskutil
106+
90107 try :
91108 r = _run (["diskutil" , "info" , partition ], timeout = 10 )
92109 for line in r .stdout .splitlines ():
93110 if "Mount Point:" in line :
94111 candidate = line .split (":" , 1 )[1 ].strip ()
95- if candidate and os .path .exists (candidate ):
112+ if candidate and os .path .isdir (candidate ):
96113 return candidate
97114 except Exception :
98115 pass
99- # Try mounting
100- return self ._mount_partition (partition )
101116
102- # ------------------------------------------------------------------
103- # BIOS boot
104- # ------------------------------------------------------------------
117+ return self ._diskutil_mount (partition )
118+
105119
106120 def write_bios_boot (
107121 self ,
@@ -114,38 +128,49 @@ def write_bios_boot(
114128 self ._update (progress_callback , 0 , "Device name not found" )
115129 return False
116130
117- self ._update (progress_callback , 10 ,
131+ self ._update (progress_callback , 8 ,
118132 f"Writing BIOS boot sector to { dev } …" )
119133
134+ _run (["diskutil" , "unmountDisk" , dev ], timeout = 20 )
135+
120136 mbr_bin = self ._find_or_create_mbr ()
121137 if not mbr_bin or not os .path .exists (mbr_bin ):
122138 self ._update (progress_callback , 0 ,
123139 "Error: Could not create MBR binary" )
124140 return False
125141
126- # Unmount so dd can write
127- _run (["diskutil" , "unmountDisk" , dev ], timeout = 20 )
128-
129142 try :
130143 r = _sudo (
131144 ["dd" , f"if={ mbr_bin } " , f"of={ dev } " ,
132145 "bs=446" , "count=1" , "conv=notrunc" ],
133- timeout = 30
146+ timeout = 30 ,
134147 )
135148 if r .returncode == 0 :
136- self ._update (progress_callback , 100 ,
137- "Generic MBR written (dd)" )
138- return True
139- self ._update (progress_callback , 0 ,
140- f"dd MBR failed: { r .stderr .strip ()} " )
149+ self ._update (progress_callback , 80 , "Generic MBR written (dd)" )
150+ else :
151+ self ._update (progress_callback , 40 ,
152+ f"dd MBR failed: { r .stderr .strip ()} " )
141153 except Exception as exc :
142- self ._update (progress_callback , 0 , f"dd MBR error: { exc } " )
154+ self ._update (progress_callback , 40 , f"dd MBR error: { exc } " )
155+
156+ partition = self ._wait_for_partition (dev )
157+ if partition and shutil .which ("pdisk" ):
158+ try :
159+ proc = subprocess .Popen (
160+ ["sudo" , "pdisk" , dev , "-e" ],
161+ stdin = subprocess .PIPE , stdout = subprocess .PIPE ,
162+ stderr = subprocess .PIPE ,
163+ )
164+ proc .communicate (input = b"f 1\n w\n y\n q\n " , timeout = 15 )
165+ self ._update (progress_callback , 90 ,
166+ "Partition boot flag set via pdisk" )
167+ except Exception :
168+ pass
169+
170+ self ._update (progress_callback , 100 ,
171+ "BIOS boot setup complete (MBR written)" )
172+ return True
143173
144- return False
145-
146- # ------------------------------------------------------------------
147- # UEFI boot
148- # ------------------------------------------------------------------
149174
150175 def write_uefi_boot (
151176 self ,
@@ -163,13 +188,14 @@ def write_uefi_boot(
163188 self ._update (progress_callback , 0 , "Device name not found" )
164189 return False
165190
166- partition = self ._get_partition (dev )
191+ partition = self ._wait_for_partition (dev )
167192 if not partition :
168193 self ._update (progress_callback , 0 ,
169194 f"Could not find first partition on { dev } " )
170195 return False
171196
172- self ._update (progress_callback , 10 , "Preparing UEFI boot files…" )
197+ iso_type = options .get ("iso_type" , "generic" ).lower ()
198+ self ._update (progress_callback , 8 , "Preparing UEFI boot files…" )
173199
174200 mount_point = self ._resolve_mount_point (device , partition )
175201 if not mount_point :
@@ -179,11 +205,9 @@ def write_uefi_boot(
179205
180206 efi_boot_dir = os .path .join (mount_point , "EFI" , "BOOT" )
181207 os .makedirs (efi_boot_dir , exist_ok = True )
182- self ._update (progress_callback , 20 , "EFI directory structure created" )
183-
208+ self ._update (progress_callback , 18 , "EFI directory structure created" )
184209 bootx64 = os .path .join (efi_boot_dir , "BOOTX64.EFI" )
185210
186- # Layer 1: known macOS EFI files
187211 for src in self ._EFI_SOURCES :
188212 if os .path .exists (src ):
189213 try :
@@ -192,13 +216,13 @@ def write_uefi_boot(
192216 f"UEFI bootloader installed from { src } " )
193217 return True
194218 except Exception as exc :
195- logger .warning (f"Copy { src } : { exc } " )
219+ logger .warning (f"copy { src } : { exc } " )
196220
197- # Layer 2: walk /usr/share/efi, /usr/lib/efi
198- for efi_dir in ( "/usr/share/efi " , "/usr/lib/efi " ):
199- if not os .path .isdir (efi_dir ):
221+ for efi_search in ( " /usr/share/efi" , " /usr/lib/efi" ,
222+ "/usr/local/share " , "/opt/homebrew/share " ):
223+ if not os .path .isdir (efi_search ):
200224 continue
201- for root , _ , files in os .walk (efi_dir ):
225+ for root , _ , files in os .walk (efi_search ):
202226 for fname in files :
203227 if fname .lower ().endswith (".efi" ):
204228 src = os .path .join (root , fname )
@@ -210,23 +234,18 @@ def write_uefi_boot(
210234 except Exception :
211235 continue
212236
213- # Layer 3: Minimal stub
214- self ._update (progress_callback , 90 ,
215- "No bootloader found — writing minimal stub…" )
237+ self ._update (progress_callback , 88 ,
238+ "No bootloader found — writing minimal UEFI stub…" )
216239 stub = self ._find_or_create_uefi_stub ()
217240 try :
218241 shutil .copy2 (stub , bootx64 )
219- self ._update (progress_callback , 100 ,
220- "Minimal UEFI stub installed" )
242+ self ._update (progress_callback , 100 , "Minimal UEFI stub installed" )
221243 return True
222244 except Exception as exc :
223245 self ._update (progress_callback , 0 ,
224246 f"Stub install failed: { exc } " )
225247 return False
226248
227- # ------------------------------------------------------------------
228- # FreeDOS boot
229- # ------------------------------------------------------------------
230249
231250 def write_freedos_boot (
232251 self ,
0 commit comments