Skip to content

Commit 0375c21

Browse files
committed
fix(resize): use explicit selection logic for main APFS container
1 parent f469291 commit 0375c21

File tree

1 file changed

+45
-14
lines changed

1 file changed

+45
-14
lines changed

VirtualCore/Source/Utilities/VBDiskResizer.swift

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -683,28 +683,37 @@ public struct VBDiskResizer {
683683
}
684684

685685
let cleanDeviceNode = sanitizeDeviceIdentifier(deviceNode)
686-
var candidates: [(info: APFSContainerInfo, size: UInt64, isLikelyISC: Bool)] = []
686+
var candidates: [(info: APFSContainerInfo, size: UInt64, isMainContainer: Bool)] = []
687687

688688
for container in containers {
689689
guard let containerRef = container["ContainerReference"] as? String else { continue }
690690
let volumes = container["Volumes"] as? [[String: Any]] ?? []
691691
let roles = volumes.compactMap { $0["Roles"] as? [String] }.flatMap { $0 }
692-
let hasSystemOrData = roles.contains(where: { $0 == "System" }) || roles.contains(where: { $0 == "Data" })
693692
let hasLockedVolumes = volumes.contains { ($0["Locked"] as? Bool) == true }
694693

694+
// Detect MAIN container: has "System" or "Data" role (the boot/data container)
695+
let hasSystemOrData = roles.contains(where: { $0 == "System" }) || roles.contains(where: { $0 == "Data" })
696+
697+
// Detect ISC container: has "xART" or "Hardware" roles (unique to Internal Shared Cache)
698+
let hasISCRoles = roles.contains(where: { $0 == "xART" }) || roles.contains(where: { $0 == "Hardware" })
699+
700+
// The main container is the one with System/Data and NOT ISC
701+
let isMainContainer = hasSystemOrData && !hasISCRoles
702+
695703
let physicalStores = container["PhysicalStores"] as? [[String: Any]] ?? []
696704
for store in physicalStores {
697705
guard let storeIdentifier = store["DeviceIdentifier"] as? String else { continue }
698706
guard storeIdentifier.hasPrefix(cleanDeviceNode) || containerRef == cleanDeviceNode else { continue }
699707
let size = store["Size"] as? UInt64 ?? 0
700708
let info = APFSContainerInfo(container: containerRef, physicalStore: storeIdentifier, hasLockedVolumes: hasLockedVolumes)
701-
candidates.append((info: info, size: size, isLikelyISC: !hasSystemOrData))
709+
candidates.append((info: info, size: size, isMainContainer: isMainContainer))
710+
NSLog("APFS candidate: container=\(containerRef), store=\(storeIdentifier), size=\(size), isMain=\(isMainContainer), hasSystemOrData=\(hasSystemOrData), hasISCRoles=\(hasISCRoles), roles=\(roles)")
702711
}
703712

704713
if containerRef == cleanDeviceNode {
705714
let size = (physicalStores.first?["Size"] as? UInt64) ?? 0
706715
let info = APFSContainerInfo(container: containerRef, physicalStore: nil, hasLockedVolumes: hasLockedVolumes)
707-
candidates.append((info: info, size: size, isLikelyISC: !hasSystemOrData))
716+
candidates.append((info: info, size: size, isMainContainer: isMainContainer))
708717
}
709718
}
710719

@@ -713,17 +722,39 @@ public struct VBDiskResizer {
713722
return nil
714723
}
715724

716-
let preferred = candidates.sorted { lhs, rhs in
717-
if lhs.info.hasLockedVolumes != rhs.info.hasLockedVolumes {
718-
return lhs.info.hasLockedVolumes == false
719-
}
720-
if lhs.isLikelyISC != rhs.isLikelyISC {
721-
return lhs.isLikelyISC == false
722-
}
723-
return lhs.size > rhs.size
724-
}.first
725+
// Selection priority:
726+
// 1. Find the MAIN container (has System/Data, not ISC) that is unlocked
727+
// 2. Fall back to largest unlocked container
728+
// 3. Fall back to any container
729+
730+
let selected: (info: APFSContainerInfo, size: UInt64, isMainContainer: Bool)?
731+
732+
// First priority: unlocked main container
733+
if let mainUnlocked = candidates.first(where: { $0.isMainContainer && !$0.info.hasLockedVolumes }) {
734+
selected = mainUnlocked
735+
NSLog("Selected unlocked main APFS container: \(mainUnlocked.info.container)")
736+
}
737+
// Second priority: any main container (even if locked)
738+
else if let mainAny = candidates.first(where: { $0.isMainContainer }) {
739+
selected = mainAny
740+
NSLog("Selected main APFS container (locked): \(mainAny.info.container)")
741+
}
742+
// Third priority: largest unlocked non-main container
743+
else if let largestUnlocked = candidates.filter({ !$0.info.hasLockedVolumes }).max(by: { $0.size < $1.size }) {
744+
selected = largestUnlocked
745+
NSLog("Selected largest unlocked APFS container: \(largestUnlocked.info.container)")
746+
}
747+
// Last resort: any container
748+
else {
749+
selected = candidates.first
750+
NSLog("Selected fallback APFS container: \(selected?.info.container ?? "none")")
751+
}
752+
753+
if let selected = selected {
754+
NSLog("Final APFS container selection: \(selected.info.container) (store: \(selected.info.physicalStore ?? "none"), size: \(selected.size), isMain: \(selected.isMainContainer))")
755+
}
725756

726-
return preferred?.info
757+
return selected?.info
727758
}
728759

729760
private static func findAPFSContainer(in diskutilOutput: String, deviceNode: String) -> APFSContainerInfo? {

0 commit comments

Comments
 (0)