Browse Source

helper: persistent bookmarks for drive images

osy 5 years ago
parent
commit
b11daf83c4

+ 2 - 1
Configuration/UTMViewState.h

@@ -36,9 +36,10 @@ NS_ASSUME_NONNULL_BEGIN
 - (instancetype)initDefaults NS_DESIGNATED_INITIALIZER;
 - (instancetype)initWithDictionary:(NSMutableDictionary *)dictionary NS_DESIGNATED_INITIALIZER;
 
-- (void)setBookmark:(NSData *)bookmark forRemovableDrive:(NSString *)drive;
+- (void)setBookmark:(NSData *)bookmark forRemovableDrive:(NSString *)drive persistent:(BOOL)persistent;
 - (void)removeBookmarkForRemovableDrive:(NSString *)drive;
 - (nullable NSData *)bookmarkForRemovableDrive:(NSString *)drive;
+- (nullable NSData *)bookmarkForRemovableDrive:(NSString *)drive persistent:(out BOOL *)persistent;
 
 @end
 

+ 25 - 4
Configuration/UTMViewState.m

@@ -37,6 +37,7 @@ const NSString *const kUTMViewStateRemovableDrivesKey = @"RemovableDrives";
 @implementation UTMViewState {
     NSMutableDictionary *_rootDict;
     NSMutableDictionary<NSString *, NSData *> *_removableDrives;
+    NSMutableDictionary<NSString *, NSData *> *_removableDrivesTemp;
 }
 
 #pragma mark - Properties
@@ -132,18 +133,36 @@ const NSString *const kUTMViewStateRemovableDrivesKey = @"RemovableDrives";
 
 #pragma mark - Removable drives
 
-- (void)setBookmark:(NSData *)bookmark forRemovableDrive:(NSString *)drive {
+- (void)setBookmark:(NSData *)bookmark forRemovableDrive:(NSString *)drive persistent:(BOOL)persistent {
     [self propertyWillChange];
-    _removableDrives[drive] = bookmark;
+    if (persistent) {
+        _removableDrives[drive] = bookmark;
+        [_removableDrivesTemp removeObjectForKey:drive];
+    } else {
+        _removableDrivesTemp[drive] = bookmark;
+    }
 }
 
 - (void)removeBookmarkForRemovableDrive:(NSString *)drive {
     [self propertyWillChange];
     [_removableDrives removeObjectForKey:drive];
+    [_removableDrivesTemp removeObjectForKey:drive];
+}
+
+- (NSData *)bookmarkForRemovableDrive:(NSString *)drive {
+    BOOL persistent;
+    return [self bookmarkForRemovableDrive:drive persistent:&persistent];
 }
 
-- (nullable NSData *)bookmarkForRemovableDrive:(NSString *)drive {
-    return _removableDrives[drive];
+- (nullable NSData *)bookmarkForRemovableDrive:(NSString *)drive persistent:(out BOOL *)persistent {
+    NSData *temp = _removableDrivesTemp[drive];
+    if (temp) {
+        *persistent = NO;
+        return temp;
+    } else {
+        *persistent = YES;
+        return _removableDrives[drive];
+    }
 }
 
 #pragma mark - Init
@@ -153,6 +172,7 @@ const NSString *const kUTMViewStateRemovableDrivesKey = @"RemovableDrives";
     if (self) {
         _rootDict = [NSMutableDictionary dictionary];
         _removableDrives = [NSMutableDictionary dictionary];
+        _removableDrivesTemp = [NSMutableDictionary dictionary];
         _rootDict[kUTMViewStateRemovableDrivesKey] = _removableDrives;
         self.displayScale = 1.0;
         self.displayOriginX = 0;
@@ -170,6 +190,7 @@ const NSString *const kUTMViewStateRemovableDrivesKey = @"RemovableDrives";
     if (self) {
         _rootDict = dictionary;
         _removableDrives = dictionary[kUTMViewStateRemovableDrivesKey];
+        _removableDrivesTemp = [NSMutableDictionary dictionary];
         if (!_removableDrives) {
             _removableDrives = [NSMutableDictionary dictionary];
             _rootDict[kUTMViewStateRemovableDrivesKey] = _removableDrives;

+ 1 - 0
Managers/UTMQemu.h

@@ -40,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN
 - (void)startDylib:(nonnull NSString *)dylib completion:(void(^)(BOOL,NSString *))completion;
 - (void)ping:(void (^)(BOOL))onResponse;
 - (void)accessDataWithBookmark:(NSData *)bookmark;
+- (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion;
 
 @end
 

+ 41 - 8
Managers/UTMQemu.m

@@ -18,6 +18,15 @@
 #import "UTMLogging.h"
 #import <dlfcn.h>
 #import <pthread.h>
+#import <TargetConditionals.h>
+
+#if TARGET_OS_IPHONE
+static const NSURLBookmarkCreationOptions kBookmarkCreationOptions = 0;
+static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = 0;
+#else
+static const NSURLBookmarkCreationOptions kBookmarkCreationOptions = NSURLBookmarkCreationWithSecurityScope;
+static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBookmarkResolutionWithSecurityScope;
+#endif
 
 @implementation UTMQemu {
     NSMutableArray<NSString *> *_argv;
@@ -158,27 +167,51 @@
     }
 }
 
-- (void)accessDataWithBookmarkThread:(NSData *)bookmark {
-    BOOL stale;
+- (void)accessDataWithBookmarkThread:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion  {
+    BOOL stale = NO;
     NSError *err;
     NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
-                                           options:0
+                                           options:(securityScoped ? kBookmarkResolutionOptions : 0)
                                      relativeToURL:nil
                                bookmarkDataIsStale:&stale
                                              error:&err];
-    if (err) {
+    if (!url) {
         UTMLog(@"Failed to access bookmark data.");
+        completion(NO, nil, nil);
         return;
     }
-    [_urls addObject:url];
-    [url startAccessingSecurityScopedResource];
+    if (stale || !securityScoped) {
+        bookmark = [url bookmarkDataWithOptions:kBookmarkCreationOptions
+                 includingResourceValuesForKeys:nil
+                                  relativeToURL:nil
+                                          error:&err];
+        if (!bookmark) {
+            UTMLog(@"Failed to create new bookmark!");
+            completion(NO, bookmark, url.path);
+            return;
+        }
+    }
+    if ([url startAccessingSecurityScopedResource]) {
+        [_urls addObject:url];
+    } else {
+        UTMLog(@"Failed to access security scoped resource for: %@", url);
+    }
+    completion(YES, bookmark, url.path);
 }
 
 - (void)accessDataWithBookmark:(NSData *)bookmark {
+    [self accessDataWithBookmark:bookmark securityScoped:NO completion:^(BOOL success, NSData *bookmark, NSString *path) {
+        if (!success) {
+            UTMLog(@"Access bookmark failed for: %@", path);
+        }
+    }];
+}
+
+- (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion {
     if (_connection) {
-        [[_connection remoteObjectProxy] accessDataWithBookmark:bookmark];
+        [[_connection remoteObjectProxy] accessDataWithBookmark:bookmark securityScoped:securityScoped completion:completion];
     } else {
-        [self accessDataWithBookmarkThread:bookmark];
+        [self accessDataWithBookmarkThread:bookmark securityScoped:securityScoped completion:completion];
     }
 }
 

+ 33 - 57
Managers/UTMVirtualMachine+Drives.m

@@ -14,7 +14,6 @@
 // limitations under the License.
 //
 
-#import <TargetConditionals.h>
 #import "UTMVirtualMachine+Drives.h"
 #import "UTMLogging.h"
 #import "UTMViewState.h"
@@ -22,13 +21,7 @@
 #import "UTMQemu.h"
 #import "UTMQemuManager+BlockDevices.h"
 
-#if TARGET_OS_IPHONE
-static const NSURLBookmarkCreationOptions kBookmarkCreationOptions = 0;
-static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = 0;
-#else
-static const NSURLBookmarkCreationOptions kBookmarkCreationOptions = NSURLBookmarkCreationWithSecurityScope;
-static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBookmarkResolutionWithSecurityScope;
-#endif
+extern NSString *const kUTMErrorDomain;
 
 @interface UTMVirtualMachine ()
 
@@ -69,9 +62,7 @@ static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBo
 }
 
 - (BOOL)ejectDrive:(UTMDrive *)drive force:(BOOL)force error:(NSError * _Nullable __autoreleasing *)error {
-    if (![self saveBookmarkForDrive:drive url:nil error:error]) {
-        return NO;
-    }
+    [self.viewState removeBookmarkForRemovableDrive:drive.name];
     if (!self.qemu.isConnected) {
         return YES; // not ready yet
     }
@@ -79,19 +70,6 @@ static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBo
 }
 
 - (BOOL)changeMediumForDrive:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
-    if (![self saveBookmarkForDrive:drive url:url error:error]) {
-        return NO;
-    }
-    if (!self.qemu.isConnected) {
-        return YES; // not ready yet
-    }
-    if (![self changeMediumForDriveInternal:drive url:url error:error]) {
-        return NO;
-    }
-    return [self saveBookmarkForDrive:drive url:url error:error];
-}
-
-- (BOOL)changeMediumForDriveInternal:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
     NSData *bookmark = [url bookmarkDataWithOptions:0
                      includingResourceValuesForKeys:nil
                                       relativeToURL:nil
@@ -99,52 +77,50 @@ static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBo
     if (!bookmark) {
         return NO;
     }
-    [self.system accessDataWithBookmark:bookmark];
-    return [self.qemu changeMediumForDrive:drive.name path:url.path error:error];
-}
-
-- (BOOL)saveBookmarkForDrive:(UTMDrive *)drive url:(nullable NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
-    NSData *bookmark = [url bookmarkDataWithOptions:kBookmarkCreationOptions
-                     includingResourceValuesForKeys:nil
-                                      relativeToURL:nil
-                                              error:error];
-    if (bookmark) {
-        [self.viewState setBookmark:bookmark forRemovableDrive:drive.name];
+    [self.viewState setBookmark:bookmark forRemovableDrive:drive.name persistent:NO];
+    if (!self.qemu.isConnected) {
+        return YES; // not ready yet
     } else {
-        [self.viewState removeBookmarkForRemovableDrive:drive.name];
+        return [self changeMediumForDriveInternal:drive bookmark:bookmark persistent:NO error:error];
     }
-    return (bookmark != nil);
+}
+
+- (BOOL)changeMediumForDriveInternal:(UTMDrive *)drive bookmark:(NSData *)bookmark persistent:(BOOL)persistent error:(NSError * _Nullable __autoreleasing *)error {
+    __block BOOL ret = NO;
+    dispatch_semaphore_t sema = dispatch_semaphore_create(0);
+    [self.system accessDataWithBookmark:bookmark
+                         securityScoped:persistent
+                             completion:^(BOOL success, NSData *newBookmark, NSString *path) {
+        if (success) {
+            [self.viewState setBookmark:newBookmark forRemovableDrive:drive.name persistent:YES];
+            [self.qemu changeMediumForDrive:drive.name path:path error:error];
+        } else {
+            if (error) {
+                *error = [NSError errorWithDomain:kUTMErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to access drive image path.", "UTMVirtualMachine+Drives")}];
+            }
+        }
+        ret = success;
+        dispatch_semaphore_signal(sema);
+    }];
+    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
+    return ret;
 }
 
 - (void)restoreRemovableDrivesFromBookmarks {
     NSArray<UTMDrive *> *drives = self.drives;
     for (UTMDrive *drive in drives) {
-        NSData *bookmark = [self.viewState bookmarkForRemovableDrive:drive.name];
+        BOOL persistent = NO;
+        NSData *bookmark = [self.viewState bookmarkForRemovableDrive:drive.name persistent:&persistent];
         if (bookmark) {
+            NSString *path = nil;
             UTMLog(@"found bookmark for %@", drive.name);
             if (drive.status == UTMDriveStatusFixed) {
                 UTMLog(@"%@ is no longer removable, removing bookmark", drive.name);
-                [self saveBookmarkForDrive:drive url:nil error:nil];
+                [self.viewState removeBookmarkForRemovableDrive:drive.name];
                 continue;
             }
-            BOOL stale;
-            NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
-                                                   options:kBookmarkResolutionOptions
-                                             relativeToURL:nil
-                                       bookmarkDataIsStale:&stale
-                                                     error:nil];
-            if (!url) {
-                UTMLog(@"failed to resolve bookmark for %@", drive.name);
-                continue;
-            }
-            if (stale) {
-                UTMLog(@"bookmark is stale, attempting to re-create");
-                if (![self saveBookmarkForDrive:drive url:url error:nil]) {
-                    UTMLog(@"bookmark re-creation failed");
-                }
-            }
-            if (![self changeMediumForDriveInternal:drive url:url error:nil]) {
-                UTMLog(@"failed to change %@ image to %@", drive.name, url);
+            if (![self changeMediumForDriveInternal:drive bookmark:bookmark persistent:persistent error:nil]) {
+                UTMLog(@"failed to change %@ image to %@", drive.name, path);
             }
         }
     }

+ 2 - 0
QEMUHelper/QEMUHelper.entitlements

@@ -4,6 +4,8 @@
 <dict>
 	<key>com.apple.security.app-sandbox</key>
 	<true/>
+	<key>com.apple.security.files.bookmarks.app-scope</key>
+	<true/>
 	<key>com.apple.security.hypervisor</key>
 	<true/>
 	<key>com.apple.security.network.client</key>

+ 30 - 3
QEMUHelper/QEMUHelper.m

@@ -33,11 +33,34 @@
     return self;
 }
 
-- (void)accessDataWithBookmark:(NSData *)bookmark {
+- (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion {
+    BOOL stale = false;
+    NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark
+                                           options:(securityScoped ? NSURLBookmarkResolutionWithSecurityScope : 0)
+                                     relativeToURL:nil
+                               bookmarkDataIsStale:&stale
+                                             error:nil];
+    if (!url) {
+        UTMLog(@"Failed to resolve bookmark!");
+        completion(NO, nil, nil);
+        return;
+    }
+    if (stale || !securityScoped) {
+        bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
+                 includingResourceValuesForKeys:nil
+                                  relativeToURL:nil
+                                          error:nil];
+        if (!bookmark) {
+            UTMLog(@"Failed to create new bookmark!");
+            completion(NO, bookmark, url.path);
+            return;
+        }
+    }
     if (_qemu == nil) {
         [_bookmarks addObject:bookmark];
+        completion(YES, bookmark, url.path);
     } else {
-        [_qemu accessDataWithBookmark:bookmark];
+        [_qemu accessDataWithBookmark:bookmark securityScoped:YES completion:completion];
     }
 }
 
@@ -61,7 +84,11 @@
     // pass in any bookmarks in queue
     if (_bookmarks.count > 0) {
         for (NSData *bookmark in _bookmarks) {
-            [_qemu accessDataWithBookmark:bookmark];
+            [_qemu accessDataWithBookmark:bookmark securityScoped:YES completion:^(BOOL success, NSData *bookmark, NSString *path) {
+                if (!success) {
+                    UTMLog(@"Access bookmark failed for: %@", path);
+                }
+            }];
         }
         [_bookmarks removeAllObjects];
     }

+ 1 - 1
QEMUHelper/QEMUHelperProtocol.h

@@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
 // The protocol that this service will vend as its API. This header file will also need to be visible to the process hosting the service.
 @protocol QEMUHelperProtocol
 
-- (void)accessDataWithBookmark:(NSData *)bookmark;
+- (void)accessDataWithBookmark:(NSData *)bookmark securityScoped:(BOOL)securityScoped completion:(void(^)(BOOL, NSData * _Nullable, NSString * _Nullable))completion;
 - (void)ping:(void (^)(BOOL))onResponse;
 - (void)startDylib:(NSString *)dylib type:(QEMUHelperType)type argv:(NSArray<NSString *> *)argv completion:(void(^)(BOOL,NSString *))completion;