blob: 5d45520c9a8620627c0c32cc282d510660713dac [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "Unpacker.h"
#import <AppKit/AppKit.h>
#include <DiskArbitration/DiskArbitration.h>
#include <dispatch/dispatch.h>
#import "Downloader.h"
@interface Unpacker () {
NSURL* temporaryDirectoryURL_;
NSString* mountPath_;
NSTask* __weak mountTask_;
DASessionRef session_;
dispatch_queue_t unpack_dq_;
}
- (void)didFinishEjectingDisk:(DADiskRef)disk
withDissenter:(DADissenterRef)dissenter;
@end
static void eject_callback(DADiskRef disk,
DADissenterRef dissenter,
void* context) {
Unpacker* unpacker = (__bridge_transfer Unpacker*)context;
[unpacker didFinishEjectingDisk:disk withDissenter:dissenter];
}
static void unmount_callback(DADiskRef disk,
DADissenterRef dissenter,
void* context) {
if (dissenter) {
Unpacker* unpacker = (__bridge Unpacker*)context;
[unpacker didFinishEjectingDisk:disk withDissenter:dissenter];
} else {
DADiskEject(disk, kDADiskEjectOptionDefault, eject_callback, context);
}
}
@implementation Unpacker
@synthesize delegate = delegate_;
@synthesize appPath = appPath_;
- (void)cleanUp {
[mountTask_ terminate];
// It's not the end of the world if this temporary directory is not removed
// here. The directory will be deleted when the operating system itself
// decides to anyway.
[[NSFileManager defaultManager] removeItemAtURL:temporaryDirectoryURL_
error:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// TODO: The failure delegate methods need to be revised to meaningfully deal
// with the errors (pipe in stderr / stdout to handle the error according to
// what the error was).
- (void)mountDMGFromURL:(NSURL*)fileURL {
NSError* error = nil;
temporaryDirectoryURL_ = [[NSFileManager defaultManager]
URLForDirectory:NSItemReplacementDirectory
inDomain:NSUserDomainMask
appropriateForURL:[NSURL fileURLWithPath:@"/" isDirectory:YES]
create:YES
error:&error];
if (error) {
[delegate_ unpacker:self onMountFailure:error];
return;
}
NSURL* temporaryDiskImageURL =
[temporaryDirectoryURL_ URLByAppendingPathComponent:@"GoogleChrome.dmg"];
mountPath_ = [[temporaryDirectoryURL_ URLByAppendingPathComponent:@"mnt"
isDirectory:YES] path];
[[NSFileManager defaultManager] createDirectoryAtPath:mountPath_
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
[delegate_ unpacker:self onMountFailure:error];
return;
}
// If the user closes the app at any time, we make sure that the cleanUp
// function deletes the temporary folder we just created.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(cleanUp)
name:NSApplicationWillTerminateNotification
object:nil];
[[NSFileManager defaultManager] moveItemAtURL:fileURL
toURL:temporaryDiskImageURL
error:nil];
NSString* path = @"/usr/bin/hdiutil";
NSArray* args = @[
@"attach", temporaryDiskImageURL, @"-nobrowse", @"-noverify",
@"-mountpoint", mountPath_
];
NSTask* mountTask = [[NSTask alloc] init];
mountTask.launchPath = path;
mountTask.arguments = args;
mountTask.terminationHandler = ^void(NSTask* task) {
NSError* error = nil;
NSString* diskAppPath =
[NSString pathWithComponents:@[ mountPath_, @"Google Chrome.app" ]];
NSString* tempAppPath = [[temporaryDirectoryURL_
URLByAppendingPathComponent:@"Google Chrome.app"] path];
[[NSFileManager defaultManager] copyItemAtPath:diskAppPath
toPath:tempAppPath
error:&error];
if (error) {
[delegate_ unpacker:self onMountFailure:error];
} else {
[delegate_ unpacker:self onMountSuccess:tempAppPath];
}
};
mountTask_ = mountTask;
[mountTask launch];
}
- (void)unmountDMG {
session_ = DASessionCreate(nil);
unpack_dq_ =
dispatch_queue_create("com.google.chrome.unpack", DISPATCH_QUEUE_SERIAL);
DASessionSetDispatchQueue(session_, unpack_dq_);
DADiskRef child_disk = DADiskCreateFromVolumePath(
nil, session_,
(__bridge CFURLRef)[NSURL fileURLWithPath:mountPath_ isDirectory:YES]);
DADiskRef whole_disk = DADiskCopyWholeDisk(child_disk);
DADiskUnmount(whole_disk,
kDADiskUnmountOptionWhole | kDADiskUnmountOptionForce,
unmount_callback, (__bridge_retained void*)self);
CFRelease(whole_disk);
CFRelease(child_disk);
}
- (void)didFinishEjectingDisk:(DADiskRef)disk
withDissenter:(DADissenterRef)dissenter {
DASessionSetDispatchQueue(session_, NULL);
CFRelease(session_);
NSError* error = nil;
if (dissenter) {
DAReturn status = DADissenterGetStatus(dissenter);
error = [NSError
errorWithDomain:@"ChromeErrorDomain"
code:err_get_code(status)
userInfo:@{
NSLocalizedDescriptionKey :
(__bridge NSString*)DADissenterGetStatusString(dissenter)
}];
[delegate_ unpacker:self onUnmountFailure:error];
} else {
[self cleanUp];
[delegate_ unpacker:self onUnmountSuccess:mountPath_];
}
}
@end