Kaynağa Gözat

manager: create QCOW2 image directly

We generate the QCOW2 disk image without having to rely on qemu-img. This means
on iOS, you no longer have to restart UTM to create multiple disk images.
osy 4 yıl önce
ebeveyn
işleme
fee16c72dd

+ 240 - 0
Managers/UTMQcow2.c

@@ -0,0 +1,240 @@
+//
+// Copyright © 2020 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include "UTMQcow2.h"
+
+#define QEMU_PACKED __attribute__((packed))
+
+#define BYTES_IN_MIB 1048576
+#define DEFAULT_CLUSTER_SIZE 65536
+#define L1E_SIZE (sizeof(uint64_t))
+#define L2E_SIZE_NORMAL   (sizeof(uint64_t))
+#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb)
+#define QCOW2_EXT_MAGIC_FEATURE_TABLE 0x6803f857
+
+typedef struct QCowHeader {
+    uint32_t magic;
+    uint32_t version;
+    uint64_t backing_file_offset;
+    uint32_t backing_file_size;
+    uint32_t cluster_bits;
+    uint64_t size; /* in bytes */
+    uint32_t crypt_method;
+    uint32_t l1_size; /* XXX: save number of clusters instead ? */
+    uint64_t l1_table_offset;
+    uint64_t refcount_table_offset;
+    uint32_t refcount_table_clusters;
+    uint32_t nb_snapshots;
+    uint64_t snapshots_offset;
+
+    /* The following fields are only valid for version >= 3 */
+    uint64_t incompatible_features;
+    uint64_t compatible_features;
+    uint64_t autoclear_features;
+
+    uint32_t refcount_order;
+    uint32_t header_length;
+
+    /* Additional fields */
+    uint8_t compression_type;
+
+    /* header must be a multiple of 8 */
+    uint8_t padding[7];
+} QEMU_PACKED QCowHeader;
+
+typedef struct {
+    uint32_t magic;
+    uint32_t len;
+} QEMU_PACKED QCowExtension;
+
+typedef struct Qcow2Feature {
+    uint8_t type;
+    uint8_t bit;
+    char    name[46];
+} QEMU_PACKED Qcow2Feature;
+
+typedef union {
+    struct {
+        QCowHeader header;
+        QCowExtension featuresHeader;
+        Qcow2Feature features[7];
+    } QEMU_PACKED;
+    char buffer[DEFAULT_CLUSTER_SIZE];
+} QEMU_PACKED QcowDefaultHeader;
+
+enum {
+    QCOW2_FEAT_TYPE_INCOMPATIBLE    = 0,
+    QCOW2_FEAT_TYPE_COMPATIBLE      = 1,
+    QCOW2_FEAT_TYPE_AUTOCLEAR       = 2,
+};
+
+/* Incompatible feature bits */
+enum {
+    QCOW2_INCOMPAT_DIRTY_BITNR      = 0,
+    QCOW2_INCOMPAT_CORRUPT_BITNR    = 1,
+    QCOW2_INCOMPAT_DATA_FILE_BITNR  = 2,
+    QCOW2_INCOMPAT_COMPRESSION_BITNR = 3,
+    QCOW2_INCOMPAT_EXTL2_BITNR      = 4,
+    QCOW2_INCOMPAT_DIRTY            = 1 << QCOW2_INCOMPAT_DIRTY_BITNR,
+    QCOW2_INCOMPAT_CORRUPT          = 1 << QCOW2_INCOMPAT_CORRUPT_BITNR,
+    QCOW2_INCOMPAT_DATA_FILE        = 1 << QCOW2_INCOMPAT_DATA_FILE_BITNR,
+    QCOW2_INCOMPAT_COMPRESSION      = 1 << QCOW2_INCOMPAT_COMPRESSION_BITNR,
+    QCOW2_INCOMPAT_EXTL2            = 1 << QCOW2_INCOMPAT_EXTL2_BITNR,
+
+    QCOW2_INCOMPAT_MASK             = QCOW2_INCOMPAT_DIRTY
+                                    | QCOW2_INCOMPAT_CORRUPT
+                                    | QCOW2_INCOMPAT_DATA_FILE
+                                    | QCOW2_INCOMPAT_COMPRESSION
+                                    | QCOW2_INCOMPAT_EXTL2,
+};
+
+/* Compatible feature bits */
+enum {
+    QCOW2_COMPAT_LAZY_REFCOUNTS_BITNR = 0,
+    QCOW2_COMPAT_LAZY_REFCOUNTS       = 1 << QCOW2_COMPAT_LAZY_REFCOUNTS_BITNR,
+
+    QCOW2_COMPAT_FEAT_MASK            = QCOW2_COMPAT_LAZY_REFCOUNTS,
+};
+
+/* Autoclear feature bits */
+enum {
+    QCOW2_AUTOCLEAR_BITMAPS_BITNR       = 0,
+    QCOW2_AUTOCLEAR_DATA_FILE_RAW_BITNR = 1,
+    QCOW2_AUTOCLEAR_BITMAPS             = 1 << QCOW2_AUTOCLEAR_BITMAPS_BITNR,
+    QCOW2_AUTOCLEAR_DATA_FILE_RAW       = 1 << QCOW2_AUTOCLEAR_DATA_FILE_RAW_BITNR,
+
+    QCOW2_AUTOCLEAR_MASK                = QCOW2_AUTOCLEAR_BITMAPS
+                                        | QCOW2_AUTOCLEAR_DATA_FILE_RAW,
+};
+
+static inline uint64_t cpu_to_be64(uint64_t x)
+{
+    return __builtin_bswap64(x);
+}
+
+static inline uint32_t cpu_to_be32(uint32_t x)
+{
+    return __builtin_bswap32(x);
+}
+
+static inline int ctz32(uint32_t val)
+{
+    return val ? __builtin_ctz(val) : 32;
+}
+
+static inline int64_t size_to_l1(int64_t size)
+{
+    int cluster_bits = ctz32(DEFAULT_CLUSTER_SIZE);
+    int shift = 2 * cluster_bits - ctz32(L2E_SIZE_NORMAL);
+    return (size + (1ULL << shift) - 1) >> shift;
+}
+
+static bool qcow2_create(CFWriteStreamRef writeStream, size_t size) {
+    uint32_t l1_size = (uint32_t)size_to_l1(size);
+    QcowDefaultHeader header = {
+        .header = {
+            .magic                      = cpu_to_be32(QCOW_MAGIC),
+            .version                    = cpu_to_be32(3),
+            .cluster_bits               = cpu_to_be32(ctz32(DEFAULT_CLUSTER_SIZE)),
+            .size                       = cpu_to_be64(size),
+            .l1_size                    = cpu_to_be32(l1_size),
+            .l1_table_offset            = cpu_to_be64(3 * DEFAULT_CLUSTER_SIZE),
+            .refcount_table_offset      = cpu_to_be64(DEFAULT_CLUSTER_SIZE),
+            .refcount_table_clusters    = cpu_to_be32(1),
+            .refcount_order             = cpu_to_be32(4),
+            .header_length              = cpu_to_be32(sizeof(header.header)),
+        },
+        .featuresHeader = {
+            .magic                      = cpu_to_be32(QCOW2_EXT_MAGIC_FEATURE_TABLE),
+            .len                        = cpu_to_be32(sizeof(header.features)),
+        },
+        .features = {
+            {
+                .type = QCOW2_FEAT_TYPE_INCOMPATIBLE,
+                .bit  = QCOW2_INCOMPAT_DIRTY_BITNR,
+                .name = "dirty bit",
+            },
+            {
+                .type = QCOW2_FEAT_TYPE_INCOMPATIBLE,
+                .bit  = QCOW2_INCOMPAT_CORRUPT_BITNR,
+                .name = "corrupt bit",
+            },
+            {
+                .type = QCOW2_FEAT_TYPE_INCOMPATIBLE,
+                .bit  = QCOW2_INCOMPAT_DATA_FILE_BITNR,
+                .name = "external data file",
+            },
+            {
+                .type = QCOW2_FEAT_TYPE_INCOMPATIBLE,
+                .bit  = QCOW2_INCOMPAT_COMPRESSION_BITNR,
+                .name = "compression type",
+            },
+            {
+                .type = QCOW2_FEAT_TYPE_COMPATIBLE,
+                .bit  = QCOW2_COMPAT_LAZY_REFCOUNTS_BITNR,
+                .name = "lazy refcounts",
+            },
+            {
+                .type = QCOW2_FEAT_TYPE_AUTOCLEAR,
+                .bit  = QCOW2_AUTOCLEAR_BITMAPS_BITNR,
+                .name = "bitmaps",
+            },
+            {
+                .type = QCOW2_FEAT_TYPE_AUTOCLEAR,
+                .bit  = QCOW2_AUTOCLEAR_DATA_FILE_RAW_BITNR,
+                .name = "raw external data",
+            },
+        },
+    };
+    
+    if (CFWriteStreamWrite(writeStream, (void *)&header.buffer, sizeof(header.buffer)) < 0) {
+        return false;
+    }
+    
+    size_t total = 3 * DEFAULT_CLUSTER_SIZE + L1E_SIZE * l1_size;
+    for (size_t i = DEFAULT_CLUSTER_SIZE; i < total; i += sizeof(uint64_t)) {
+        uint64_t data = 0;
+        if (i == DEFAULT_CLUSTER_SIZE) {
+            data = cpu_to_be64(0x20000); // initial reftable
+        } else if (i == 2 * DEFAULT_CLUSTER_SIZE) {
+            data = cpu_to_be64(0x1000100010001); // initial refblock
+        }
+        if (CFWriteStreamWrite(writeStream, (void *)&data, sizeof(uint64_t)) < 0) {
+            return false;
+        }
+    }
+    
+    return true;
+}
+
+bool GenerateDefaultQcow2File(CFURLRef path, size_t sizeInMib) {
+    bool success = false;
+    CFWriteStreamRef writeStream = CFWriteStreamCreateWithFile(kCFAllocatorDefault, path);
+    
+    if (!CFWriteStreamOpen(writeStream)) {
+        goto end;
+    }
+    
+    success = qcow2_create(writeStream, sizeInMib * BYTES_IN_MIB);
+
+end:
+    CFWriteStreamClose(writeStream);
+    CFRelease(writeStream);
+    return success;
+}

+ 31 - 0
Managers/UTMQcow2.h

@@ -0,0 +1,31 @@
+//
+// Copyright © 2020 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UTMQcow2_h
+#define UTMQcow2_h
+
+#include <stdbool.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+CF_IMPLICIT_BRIDGING_ENABLED
+#pragma clang assume_nonnull begin
+
+bool GenerateDefaultQcow2File(CFURLRef path, size_t sizeInMib);
+
+#pragma clang assume_nonnull end
+CF_IMPLICIT_BRIDGING_DISABLED
+
+#endif /* UTMQcow2_h */

+ 0 - 1
Managers/UTMVirtualMachine.m

@@ -23,7 +23,6 @@
 #import "UTMConfiguration+Display.h"
 #import "UTMConfiguration+Miscellaneous.h"
 #import "UTMViewState.h"
-#import "UTMQemuImg.h"
 #import "UTMQemuManager.h"
 #import "UTMQemuSystem.h"
 #import "UTMTerminalIO.h"

+ 1 - 0
Platform/Swift-Bridging-Header.h

@@ -25,6 +25,7 @@
 #include "UTMConfiguration+System.h"
 #include "UTMConfigurationPortForward.h"
 #include "UTMDrive.h"
+#include "UTMQcow2.h"
 #include "UTMQemu.h"
 #include "UTMQemuImg.h"
 #include "UTMQemuSystem.h"

+ 2 - 21
Platform/UTMData.swift

@@ -356,27 +356,8 @@ class UTMData: ObservableObject {
             }
             
             // create drive
-            // TODO: implement custom qcow2 creation
-            let sema = DispatchSemaphore(value: 0)
-            let imgCreate = UTMQemuImg()
-            var success = false
-            var msg = ""
-            imgCreate.op = .create
-            imgCreate.outputPath = dstPath
-            imgCreate.sizeMiB = drive.size
-            imgCreate.compressed = true
-            #if os(macOS)
-            imgCreate.setupXpc()
-            #endif
-            imgCreate.logging = UTMLogging()
-            imgCreate.start { (_success, _msg) in
-                success = _success
-                msg = _msg
-                sema.signal()
-            }
-            sema.wait()
-            if !success {
-                throw msg
+            if !GenerateDefaultQcow2File(dstPath as CFURL, drive.size) {
+                throw NSLocalizedString("Disk creation failed.", comment: "UTMData")
             }
         }
         

+ 7 - 20
Platform/iOS/Legacy/VMConfigDriveCreateViewController.m

@@ -15,7 +15,7 @@
 //
 
 #import "VMConfigDriveCreateViewController.h"
-#import "UTMQemuImg.h"
+#import "UTMQcow2.h"
 
 extern NSString *const kUTMErrorDomain;
 
@@ -62,27 +62,14 @@ extern NSString *const kUTMErrorDomain;
 }
 
 - (BOOL)createDisk:(NSError * _Nullable *)err {
-    __block NSError *perr = nil;
-    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
-    UTMQemuImg *imgCreate = [[UTMQemuImg alloc] init];
-    imgCreate.op = kUTMQemuImgCreate;
-    imgCreate.outputPath = self.changePath;
-    imgCreate.sizeMiB = self.size;
-    imgCreate.compressed = self.imageExpanding;
-    [imgCreate startWithCompletion:^(BOOL success, NSString *msg){
-        if (!success) {
-            if (!msg) {
-                msg = NSLocalizedString(@"Disk creation failed.", @"VMConfigDriveCreateViewController");
-            }
-            perr = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: msg}];
+    if (!GenerateDefaultQcow2File((__bridge CFURLRef)self.changePath, self.size)) {
+        if (err) {
+            NSString *msg = NSLocalizedString(@"Disk creation failed.", @"VMConfigDriveCreateViewController");
+            *err = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: msg}];
         }
-        dispatch_semaphore_signal(sema);
-    }];
-    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
-    if (err) {
-        *err = perr;
+        return NO;
     }
-    return (perr == nil);
+    return YES;
 }
 
 - (void)workWithIndicator:(NSString *)msg block:(void(^)(void))block completion:(void (^)(void))completion  {

+ 8 - 0
UTM.xcodeproj/project.pbxproj

@@ -13,6 +13,8 @@
 		2C6D9E06256F0646003298E6 /* hterm_all.js in Resources */ = {isa = PBXBuildFile; fileRef = E28394BD240C22F1006742E2 /* hterm_all.js */; };
 		2C6D9E07256F0646003298E6 /* terminal.html in Resources */ = {isa = PBXBuildFile; fileRef = E28394B8240C219F006742E2 /* terminal.html */; };
 		2C6D9E08256F0646003298E6 /* terminal.js in Resources */ = {isa = PBXBuildFile; fileRef = E28394B9240C219F006742E2 /* terminal.js */; };
+		2C6D9E142571AFE5003298E6 /* UTMQcow2.c in Sources */ = {isa = PBXBuildFile; fileRef = 2C6D9E132571AFE5003298E6 /* UTMQcow2.c */; };
+		2C6D9E152571AFE5003298E6 /* UTMQcow2.c in Sources */ = {isa = PBXBuildFile; fileRef = 2C6D9E132571AFE5003298E6 /* UTMQcow2.c */; };
 		CE020BA324AEDC7C00B44AB6 /* UTMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE020BA224AEDC7C00B44AB6 /* UTMData.swift */; };
 		CE020BA424AEDC7C00B44AB6 /* UTMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE020BA224AEDC7C00B44AB6 /* UTMData.swift */; };
 		CE020BA724AEDEF000B44AB6 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = CE020BA624AEDEF000B44AB6 /* Logging */; };
@@ -934,6 +936,8 @@
 /* Begin PBXFileReference section */
 		2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMContextMenuModifier.swift; sourceTree = "<group>"; };
 		2C6D9E02256EE454003298E6 /* VMDisplayTerminalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayTerminalWindowController.swift; sourceTree = "<group>"; };
+		2C6D9E122571AFE5003298E6 /* UTMQcow2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMQcow2.h; sourceTree = "<group>"; };
+		2C6D9E132571AFE5003298E6 /* UTMQcow2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = UTMQcow2.c; sourceTree = "<group>"; };
 		423BCE65240F6A80001989AC /* VMConfigSystemArgumentsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VMConfigSystemArgumentsViewController.m; sourceTree = "<group>"; };
 		423BCE67240F6A8A001989AC /* VMConfigSystemArgumentsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMConfigSystemArgumentsViewController.h; sourceTree = "<group>"; };
 		521F3EFA2414F73800130500 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
@@ -2274,6 +2278,8 @@
 				CE6EDCE1241DA0E900A719DC /* UTMLogging.m */,
 				CEF83ED124FDEA9400557D15 /* UTMPortAllocator.h */,
 				CEF83ED224FDEA9400557D15 /* UTMPortAllocator.m */,
+				2C6D9E122571AFE5003298E6 /* UTMQcow2.h */,
+				2C6D9E132571AFE5003298E6 /* UTMQcow2.c */,
 				CE9D197A226542FE00355E14 /* UTMQemu.h */,
 				CE9D197B226542FE00355E14 /* UTMQemu.m */,
 				CED33AE12267893D00FF1977 /* UTMQemuImg.h */,
@@ -2821,6 +2827,7 @@
 				CE2D92B724AD46670059923A /* qapi-visit-common.c in Sources */,
 				CEF83EBE24F9C3BF00557D15 /* UTMVirtualMachine+Drives.m in Sources */,
 				CE2D92B824AD46670059923A /* qapi-events-trace.c in Sources */,
+				2C6D9E142571AFE5003298E6 /* UTMQcow2.c in Sources */,
 				CE2D92B924AD46670059923A /* qapi-events-qdev.c in Sources */,
 				2C33B3A92566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */,
 				CEB63A9424F4747900CAF323 /* VMListViewController.m in Sources */,
@@ -3108,6 +3115,7 @@
 				CE2D956C24AD4F990059923A /* VMCardView.swift in Sources */,
 				CE0B6D7124AD584D00FE012D /* qapi-types-introspect.c in Sources */,
 				CE0B6D0D24AD56C300FE012D /* qapi-util.c in Sources */,
+				2C6D9E152571AFE5003298E6 /* UTMQcow2.c in Sources */,
 				CE2D955C24AD4F980059923A /* VMConfigQEMUView.swift in Sources */,
 				CE2D953324AD4F040059923A /* UTMConfigurationExtension.swift in Sources */,
 				CE0B6D7024AD584D00FE012D /* qapi-types-job.c in Sources */,