Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions VirtualBuddy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@
F4C18A5328491B9D00335EC7 /* VirtualWormhole.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F4C189E02848F59F00335EC7 /* VirtualWormhole.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
F4C2374D2888A462001FF286 /* VolumeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C2374C2888A462001FF286 /* VolumeUtils.swift */; };
F4C237502888AF67001FF286 /* LogStreamer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C2374F2888AF67001FF286 /* LogStreamer.swift */; };
VB01DISKRESIZ00002A0102 /* VBDiskResizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = VB01DISKRESIZ00001A0101 /* VBDiskResizer.swift */; };
VB01DISKRESIZ00004A0104 /* VBVirtualMachine+DiskResize.swift in Sources */ = {isa = PBXBuildFile; fileRef = VB01DISKRESIZ00003A0103 /* VBVirtualMachine+DiskResize.swift */; };
F4C947BF2E0B0F71001ACC91 /* URL+ExtendedAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C947BE2E0B0F71001ACC91 /* URL+ExtendedAttributes.swift */; };
F4C947D62E0B12D0001ACC91 /* String+AppleOSBuild.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C947D52E0B12D0001ACC91 /* String+AppleOSBuild.swift */; };
F4C947DA2E0B1E5D001ACC91 /* SoftwareCatalog+DownloadMatching.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C947D92E0B1E5D001ACC91 /* SoftwareCatalog+DownloadMatching.swift */; };
Expand Down Expand Up @@ -828,6 +830,8 @@
F4C18A4D28491B8500335EC7 /* VirtualBuddyGuest.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VirtualBuddyGuest.entitlements; sourceTree = "<group>"; };
F4C2374C2888A462001FF286 /* VolumeUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeUtils.swift; sourceTree = "<group>"; };
F4C2374F2888AF67001FF286 /* LogStreamer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogStreamer.swift; sourceTree = "<group>"; };
VB01DISKRESIZ00001A0101 /* VBDiskResizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VBDiskResizer.swift; sourceTree = "<group>"; };
VB01DISKRESIZ00003A0103 /* VBVirtualMachine+DiskResize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VBVirtualMachine+DiskResize.swift"; sourceTree = "<group>"; };
F4C947BE2E0B0F71001ACC91 /* URL+ExtendedAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+ExtendedAttributes.swift"; sourceTree = "<group>"; };
F4C947D52E0B12D0001ACC91 /* String+AppleOSBuild.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AppleOSBuild.swift"; sourceTree = "<group>"; };
F4C947D92E0B1E5D001ACC91 /* SoftwareCatalog+DownloadMatching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SoftwareCatalog+DownloadMatching.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1629,6 +1633,7 @@
F4DE1C102D6F642E00603527 /* VBStorageDeviceContainer.swift */,
F46FFBA72804F07400D61023 /* VBNVRAMVariable.swift */,
F4D725FD286677B8001818F7 /* VBVirtualMachine+Metadata.swift */,
VB01DISKRESIZ00003A0103 /* VBVirtualMachine+DiskResize.swift */,
F4D0F71428667984004D5782 /* VBVirtualMachine+Screenshot.swift */,
);
path = Models;
Expand Down Expand Up @@ -1922,6 +1927,7 @@
F485B91E2BB2F4AC004B3C2B /* Bundle+Version.swift */,
F444D1332BB478AD00AB786F /* VBMemoryLeakDebugAssertions.swift */,
F453C4BA2DF231B7007EAD5F /* PreventTerminationAssertion.swift */,
VB01DISKRESIZ00001A0101 /* VBDiskResizer.swift */,
);
path = Utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -2701,6 +2707,7 @@
F453C4A22DF1D7F6007EAD5F /* SimulatedRestoreBackend.swift in Sources */,
F4DE1C0B2D6F54E700603527 /* VBSavedStateMetadata+Clone.swift in Sources */,
F4D725FE286677B8001818F7 /* VBVirtualMachine+Metadata.swift in Sources */,
VB01DISKRESIZ00004A0104 /* VBVirtualMachine+DiskResize.swift in Sources */,
F4A21BF428033102001072B8 /* VBError.swift in Sources */,
F4D0F71F2867517A004D5782 /* AppUpdateChannel.swift in Sources */,
F49FD8842DFB727B0019D638 /* VMImporter+Helpers.swift in Sources */,
Expand All @@ -2725,6 +2732,7 @@
F485B91D2BB2F0D9004B3C2B /* ProcessInfo+ECID.swift in Sources */,
F444D1342BB478AD00AB786F /* VBMemoryLeakDebugAssertions.swift in Sources */,
F4C237502888AF67001FF286 /* LogStreamer.swift in Sources */,
VB01DISKRESIZ00002A0102 /* VBDiskResizer.swift in Sources */,
F4F9B41A284CE37C00F21737 /* Logging.swift in Sources */,
F4B5C5D728870619005AA632 /* ConfigurationModels+Validation.swift in Sources */,
F4A7FB3B2BB5E79100E4C12A /* DirectoryObserver.swift in Sources */,
Expand Down
21 changes: 21 additions & 0 deletions VirtualCore/Source/Models/Configuration/ConfigurationModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ public struct VBManagedDiskImage: Identifiable, Hashable, Codable {
}
}
}

public var displayName: String {
switch self {
case .raw: "Raw Image"
case .dmg: "Disk Image (DMG)"
case .sparse: "Sparse Image"
case .asif: "Apple Sparse Image Format (ASIF)"
}
}
}
Comment thread
balcsida marked this conversation as resolved.

public var id: String = UUID().uuidString
Expand All @@ -135,6 +144,13 @@ public struct VBManagedDiskImage: Identifiable, Hashable, Codable {
format: .raw
)
}

public var canBeResized: Bool {
switch format {
case .raw, .sparse: true
case .dmg, .asif: false
}
}
}
Comment thread
balcsida marked this conversation as resolved.

/// Configures a storage device.
Expand Down Expand Up @@ -202,6 +218,11 @@ public struct VBStorageDevice: Identifiable, Hashable, Codable {
)
}

public var canBeResized: Bool {
guard case .managedImage(let image) = backing else { return false }
return image.canBeResized
}

public var displayName: String {
guard !isBootVolume else { return "Boot" }

Expand Down
53 changes: 53 additions & 0 deletions VirtualCore/Source/Models/VBVirtualMachine+DiskResize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// VBVirtualMachine+DiskResize.swift
// VirtualCore
//
// Created by VirtualBuddy on 25/05/26.
//

import Foundation
import OSLog

private let diskResizeLogger = Logger(for: VBVirtualMachine.self, label: "DiskResize")

public extension VBVirtualMachine {

typealias DiskResizeProgressHandler = @MainActor (_ message: String) -> Void

/// Expands managed disk images whose configured size is larger than the image on disk.
/// Returns `true` if any disk image was expanded.
@discardableResult
func checkAndResizeDiskImages(progressHandler: DiskResizeProgressHandler? = nil) async throws -> Bool {
let resizableImages = configuration.hardware.storageDevices.compactMap { device -> (name: String, image: VBManagedDiskImage)? in
guard case .managedImage(let image) = device.backing, image.canBeResized else { return nil }
return (device.displayName, image)
}

let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useGB, .useMB, .useTB]
formatter.countStyle = .binary

var didResize = false

for (name, image) in resizableImages {
let imageURL = diskImageURL(for: image)

guard FileManager.default.fileExists(atPath: imageURL.path) else { continue }

let actualSize = try await VBDiskResizer.currentImageSize(at: imageURL, format: image.format)
guard image.size > actualSize else { continue }

let targetDescription = formatter.string(fromByteCount: Int64(image.size))
if let progressHandler {
await progressHandler("Expanding \(name) to \(targetDescription)...")
}

diskResizeLogger.debug("Resizing disk image at \(imageURL.path, privacy: .public) to \(image.size, privacy: .public) bytes")
try await VBDiskResizer.resizeDiskImage(at: imageURL, format: image.format, newSize: image.size)

didResize = true
}

return didResize
}
}
Loading