Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3268)

Unified Diff: chrome/browser/mac/install_from_dmg.mm

Issue 7275009: Install from disk image: eject original disk image volume and trash disk image file (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/browser/mac/install_from_dmg.h ('k') | chrome/browser/mac/relauncher.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/mac/install_from_dmg.mm
===================================================================
--- chrome/browser/mac/install_from_dmg.mm (revision 90773)
+++ chrome/browser/mac/install_from_dmg.mm (working copy)
@@ -8,17 +8,23 @@
#import <AppKit/AppKit.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
+#include <DiskArbitration/DiskArbitration.h>
#include <IOKit/IOKitLib.h>
+#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/mount.h>
+#include "base/auto_reset.h"
#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/logging.h"
#import "base/mac/mac_util.h"
+#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
#include "chrome/browser/mac/authorization_util.h"
#include "chrome/browser/mac/scoped_authorizationref.h"
#include "chrome/browser/mac/scoped_ioobject.h"
@@ -41,9 +47,117 @@
namespace {
+// Given an io_service_t (expected to be of class IOMedia), walks the ancestor
+// chain, returning the closest ancestor that implements class IOHDIXHDDrive,
+// if any. If no such ancestor is found, returns NULL. Following the "copy"
+// rule, the caller assumes ownership of the returned value.
+//
+// Note that this looks for a class that inherits from IOHDIXHDDrive, but it
+// will not likely find a concrete IOHDIXHDDrive. It will be
+// IOHDIXHDDriveOutKernel for disk images mounted "out-of-kernel" or
+// IOHDIXHDDriveInKernel for disk images mounted "in-kernel." Out-of-kernel is
+// the default as of Mac OS X 10.5. See the documentation for "hdiutil attach
+// -kernel" for more information.
+io_service_t CopyHDIXDriveServiceForMedia(io_service_t media) {
+ const char disk_image_class[] = "IOHDIXHDDrive";
+
+ // This is highly unlikely. media as passed in is expected to be of class
+ // IOMedia. Since the media service's entire ancestor chain will be checked,
+ // though, check it as well.
+ if (IOObjectConformsTo(media, disk_image_class)) {
+ IOObjectRetain(media);
+ return media;
+ }
+
+ io_iterator_t iterator_ref;
+ kern_return_t kr =
+ IORegistryEntryCreateIterator(media,
+ kIOServicePlane,
+ kIORegistryIterateRecursively |
+ kIORegistryIterateParents,
+ &iterator_ref);
+ if (kr != KERN_SUCCESS) {
+ LOG(ERROR) << "IORegistryEntryCreateIterator: " << kr;
+ return NULL;
+ }
+ ScopedIOObject<io_iterator_t> iterator(iterator_ref);
+ iterator_ref = NULL;
+
+ // Look at each of the ancestor services, beginning with the parent,
+ // iterating all the way up to the device tree's root. If any ancestor
+ // service matches the class used for disk images, the media resides on a
+ // disk image, and the disk image file's path can be determined by examining
+ // the image-path property.
+ for (ScopedIOObject<io_service_t> ancestor(IOIteratorNext(iterator));
+ ancestor;
+ ancestor.reset(IOIteratorNext(iterator))) {
+ if (IOObjectConformsTo(ancestor, disk_image_class)) {
+ return ancestor.release();
+ }
+ }
+
+ // The media does not reside on a disk image.
+ return NULL;
+}
+
+// Given an io_service_t (expected to be of class IOMedia), determines whether
+// that service is on a disk image. If it is, returns true. If image_path is
+// present, it will be set to the pathname of the disk image file, encoded in
+// filesystem encoding.
+bool MediaResidesOnDiskImage(io_service_t media, std::string* image_path) {
+ if (image_path) {
+ image_path->clear();
+ }
+
+ ScopedIOObject<io_service_t> hdix_drive(CopyHDIXDriveServiceForMedia(media));
+ if (!hdix_drive) {
+ return false;
+ }
+
+ if (image_path) {
+ base::mac::ScopedCFTypeRef<CFTypeRef> image_path_cftyperef(
+ IORegistryEntryCreateCFProperty(hdix_drive,
+ CFSTR("image-path"),
+ NULL,
+ 0));
+ if (!image_path_cftyperef) {
+ LOG(ERROR) << "IORegistryEntryCreateCFProperty";
+ return true;
+ }
+ if (CFGetTypeID(image_path_cftyperef) != CFDataGetTypeID()) {
+ base::mac::ScopedCFTypeRef<CFStringRef> observed_type_cf(
+ CFCopyTypeIDDescription(CFGetTypeID(image_path_cftyperef)));
+ std::string observed_type;
+ if (observed_type_cf) {
+ observed_type.assign(", observed ");
+ observed_type.append(base::SysCFStringRefToUTF8(observed_type_cf));
+ }
+ LOG(ERROR) << "image-path: expected CFData" << observed_type;
+ return true;
+ }
+
+ CFDataRef image_path_data = static_cast<CFDataRef>(
+ image_path_cftyperef.get());
+ CFIndex length = CFDataGetLength(image_path_data);
+ char* image_path_c = WriteInto(image_path, length + 1);
+ CFDataGetBytes(image_path_data,
+ CFRangeMake(0, length),
+ reinterpret_cast<UInt8*>(image_path_c));
+ }
+
+ return true;
+}
+
// Returns true if |path| is located on a read-only filesystem of a disk
-// image. Returns false if not, or in the event of an error.
-bool IsPathOnReadOnlyDiskImage(const char path[]) {
+// image. Returns false if not, or in the event of an error. If
+// out_dmg_bsd_device_name is present, it will be set to the BSD device name
+// for the disk image's device, in "diskNsM" form.
+bool IsPathOnReadOnlyDiskImage(const char path[],
+ std::string* out_dmg_bsd_device_name) {
+ if (out_dmg_bsd_device_name) {
+ out_dmg_bsd_device_name->clear();
+ }
+
struct statfs statfs_buf;
if (statfs(path, &statfs_buf) != 0) {
PLOG(ERROR) << "statfs " << path;
@@ -63,7 +177,10 @@
}
// BSD names in IOKit don't include dev_root.
- const char* bsd_device_name = statfs_buf.f_mntfromname + dev_root_length;
+ const char* dmg_bsd_device_name = statfs_buf.f_mntfromname + dev_root_length;
+ if (out_dmg_bsd_device_name) {
+ out_dmg_bsd_device_name->assign(dmg_bsd_device_name);
+ }
const mach_port_t master_port = kIOMasterPortDefault;
@@ -71,9 +188,9 @@
// IOServiceGetMatchingServices will assume that reference.
CFMutableDictionaryRef match_dict = IOBSDNameMatching(master_port,
0,
- bsd_device_name);
+ dmg_bsd_device_name);
if (!match_dict) {
- LOG(ERROR) << "IOBSDNameMatching " << bsd_device_name;
+ LOG(ERROR) << "IOBSDNameMatching " << dmg_bsd_device_name;
return false;
}
@@ -82,70 +199,37 @@
match_dict,
&iterator_ref);
if (kr != KERN_SUCCESS) {
- LOG(ERROR) << "IOServiceGetMatchingServices " << bsd_device_name
- << ": kernel error " << kr;
+ LOG(ERROR) << "IOServiceGetMatchingServices: " << kr;
return false;
}
ScopedIOObject<io_iterator_t> iterator(iterator_ref);
iterator_ref = NULL;
// There needs to be exactly one matching service.
- ScopedIOObject<io_service_t> filesystem_service(IOIteratorNext(iterator));
- if (!filesystem_service) {
- LOG(ERROR) << "IOIteratorNext " << bsd_device_name << ": no service";
+ ScopedIOObject<io_service_t> media(IOIteratorNext(iterator));
+ if (!media) {
+ LOG(ERROR) << "IOIteratorNext: no service";
return false;
}
ScopedIOObject<io_service_t> unexpected_service(IOIteratorNext(iterator));
if (unexpected_service) {
- LOG(ERROR) << "IOIteratorNext " << bsd_device_name << ": too many services";
+ LOG(ERROR) << "IOIteratorNext: too many services";
return false;
}
iterator.reset();
- const char disk_image_class[] = "IOHDIXController";
-
- // This is highly unlikely. The filesystem service is expected to be of
- // class IOMedia. Since the filesystem service's entire ancestor chain
- // will be checked, though, check the filesystem service's class itself.
- if (IOObjectConformsTo(filesystem_service, disk_image_class)) {
- return true;
- }
-
- kr = IORegistryEntryCreateIterator(filesystem_service,
- kIOServicePlane,
- kIORegistryIterateRecursively |
- kIORegistryIterateParents,
- &iterator_ref);
- if (kr != KERN_SUCCESS) {
- LOG(ERROR) << "IORegistryEntryCreateIterator " << bsd_device_name
- << ": kernel error " << kr;
- return false;
- }
- iterator.reset(iterator_ref);
- iterator_ref = NULL;
-
- // Look at each of the filesystem service's ancestor services, beginning
- // with the parent, iterating all the way up to the device tree's root. If
- // any ancestor service matches the class used for disk images, the
- // filesystem resides on a disk image.
- for (ScopedIOObject<io_service_t> ancestor_service(IOIteratorNext(iterator));
- ancestor_service;
- ancestor_service.reset(IOIteratorNext(iterator))) {
- if (IOObjectConformsTo(ancestor_service, disk_image_class)) {
- return true;
- }
- }
-
- // The filesystem does not reside on a disk image.
- return false;
+ return MediaResidesOnDiskImage(media, NULL);
}
// Returns true if the application is located on a read-only filesystem of a
-// disk image. Returns false if not, or in the event of an error.
-bool IsAppRunningFromReadOnlyDiskImage() {
+// disk image. Returns false if not, or in the event of an error. If
+// dmg_bsd_device_name is present, it will be set to the BSD device name for
+// the disk image's device, in "diskNsM" form.
+bool IsAppRunningFromReadOnlyDiskImage(std::string* dmg_bsd_device_name) {
return IsPathOnReadOnlyDiskImage(
- [[[NSBundle mainBundle] bundlePath] fileSystemRepresentation]);
+ [[[NSBundle mainBundle] bundlePath] fileSystemRepresentation],
+ dmg_bsd_device_name);
}
// Shows a dialog asking the user whether or not to install from the disk
@@ -259,8 +343,10 @@
// Launches the application at installed_path. The helper application
// contained within install_path will be used for the relauncher process. This
// keeps Launch Services from ever having to see or think about the helper
-// application on the disk image.
-bool LaunchInstalledApp(NSString* installed_path) {
+// application on the disk image. The relauncher process will be asked to
+// call EjectAndTrashDiskImage on dmg_bsd_device_name.
+bool LaunchInstalledApp(NSString* installed_path,
+ const std::string& dmg_bsd_device_name) {
FilePath browser_path([installed_path fileSystemRepresentation]);
FilePath helper_path = browser_path.Append("Contents/Versions");
@@ -271,7 +357,16 @@
CommandLine::ForCurrentProcess()->argv();
args[0] = browser_path.value();
- return mac_relauncher::RelaunchAppWithHelper(helper_path.value(), args);
+ std::vector<std::string> relauncher_args;
+ if (!dmg_bsd_device_name.empty()) {
+ std::string dmg_arg(mac_relauncher::kRelauncherDMGDeviceArg);
+ dmg_arg.append(dmg_bsd_device_name);
+ relauncher_args.push_back(dmg_arg);
+ }
+
+ return mac_relauncher::RelaunchAppWithHelper(helper_path.value(),
+ relauncher_args,
+ args);
}
void ShowErrorDialog() {
@@ -296,7 +391,8 @@
bool MaybeInstallFromDiskImage() {
base::mac::ScopedNSAutoreleasePool autorelease_pool;
- if (!IsAppRunningFromReadOnlyDiskImage()) {
+ std::string dmg_bsd_device_name;
+ if (!IsAppRunningFromReadOnlyDiskImage(&dmg_bsd_device_name)) {
return false;
}
@@ -353,10 +449,217 @@
installer_path,
source_path,
target_path) ||
- !LaunchInstalledApp(target_path)) {
+ !LaunchInstalledApp(target_path, dmg_bsd_device_name)) {
ShowErrorDialog();
return false;
}
return true;
}
+
+namespace {
+
+// A simple scoper that calls DASessionScheduleWithRunLoop when created and
+// DASessionUnscheduleFromRunLoop when destroyed.
+class ScopedDASessionScheduleWithRunLoop {
+ public:
+ ScopedDASessionScheduleWithRunLoop(DASessionRef session,
+ CFRunLoopRef run_loop,
+ CFStringRef run_loop_mode)
+ : session_(session),
+ run_loop_(run_loop),
+ run_loop_mode_(run_loop_mode) {
+ DASessionScheduleWithRunLoop(session_, run_loop_, run_loop_mode_);
+ }
+
+ ~ScopedDASessionScheduleWithRunLoop() {
+ DASessionUnscheduleFromRunLoop(session_, run_loop_, run_loop_mode_);
+ }
+
+ private:
+ DASessionRef session_;
+ CFRunLoopRef run_loop_;
+ CFStringRef run_loop_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedDASessionScheduleWithRunLoop);
+};
+
+// A small structure used to ferry data between SynchronousDAOperation and
+// SynchronousDACallbackAdapter.
+struct SynchronousDACallbackData {
+ public:
+ SynchronousDACallbackData()
+ : callback_called(false),
+ run_loop_running(false) {
+ }
+
+ base::mac::ScopedCFTypeRef<DADissenterRef> dissenter;
+ bool callback_called;
+ bool run_loop_running;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SynchronousDACallbackData);
+};
+
+// The callback target for SynchronousDAOperation. Set the fields in
+// SynchronousDACallbackData properly and then stops the run loop so that
+// SynchronousDAOperation may proceed.
+void SynchronousDACallbackAdapter(DADiskRef disk,
+ DADissenterRef dissenter,
+ void* context) {
+ SynchronousDACallbackData* callback_data =
+ static_cast<SynchronousDACallbackData*>(context);
+ callback_data->callback_called = true;
+
+ if (dissenter) {
+ CFRetain(dissenter);
+ callback_data->dissenter.reset(dissenter);
+ }
+
+ // Only stop the run loop if SynchronousDAOperation started it. Don't stop
+ // anything if this callback was reached synchronously from DADiskUnmount or
+ // DADiskEject.
+ if (callback_data->run_loop_running) {
+ CFRunLoopStop(CFRunLoopGetCurrent());
+ }
+}
+
+// Performs a DiskArbitration operation synchronously. After the operation is
+// requested by SynchronousDADiskUnmount or SynchronousDADiskEject, those
+// functions will call this one to run a run loop for a period of time,
+// waiting for the callback to be called. When the callback is called, the
+// run loop will be stopped, and this function will examine the result. If
+// a dissenter prevented the operation from completing, or if the run loop
+// timed out without the callback being called, this function will return
+// false. When the callback completes successfully with no dissenters within
+// the time allotted, this function returns true. This function requires that
+// the DASession being used for the operation being performed has been added
+// to the current run loop with DASessionScheduleWithRunLoop.
+bool SynchronousDAOperation(const char* name,
+ SynchronousDACallbackData* callback_data) {
+ // The callback may already have been called synchronously. In that case,
+ // avoid spinning the run loop at all.
+ if (!callback_data->callback_called) {
+ const CFTimeInterval kOperationTimeoutSeconds = 15;
+ AutoReset<bool> running_reset(&callback_data->run_loop_running, true);
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, kOperationTimeoutSeconds, FALSE);
+ }
+
+ if (!callback_data->callback_called) {
+ LOG(ERROR) << name << ": timed out";
+ return false;
+ } else if (callback_data->dissenter) {
+ CFStringRef status_string_cf =
+ DADissenterGetStatusString(callback_data->dissenter);
+ std::string status_string;
+ if (status_string_cf) {
+ status_string.assign(" ");
+ status_string.append(base::SysCFStringRefToUTF8(status_string_cf));
+ }
+ LOG(ERROR) << name << ": dissenter: "
+ << DADissenterGetStatus(callback_data->dissenter)
+ << status_string;
+ return false;
+ }
+
+ return true;
+}
+
+// Calls DADiskUnmount synchronously, returning the result.
+bool SynchronousDADiskUnmount(DADiskRef disk, DADiskUnmountOptions options) {
+ SynchronousDACallbackData callback_data;
+ DADiskUnmount(disk, options, SynchronousDACallbackAdapter, &callback_data);
+ return SynchronousDAOperation("DADiskUnmount", &callback_data);
+}
+
+// Calls DADiskEject synchronously, returning the result.
+bool SynchronousDADiskEject(DADiskRef disk, DADiskEjectOptions options) {
+ SynchronousDACallbackData callback_data;
+ DADiskEject(disk, options, SynchronousDACallbackAdapter, &callback_data);
+ return SynchronousDAOperation("DADiskEject", &callback_data);
+}
+
+} // namespace
+
+void EjectAndTrashDiskImage(const std::string& dmg_bsd_device_name) {
+ base::mac::ScopedCFTypeRef<DASessionRef> session(DASessionCreate(NULL));
+ if (!session.get()) {
+ LOG(ERROR) << "DASessionCreate";
+ return;
+ }
+
+ base::mac::ScopedCFTypeRef<DADiskRef> disk(
+ DADiskCreateFromBSDName(NULL, session, dmg_bsd_device_name.c_str()));
+ if (!disk.get()) {
+ LOG(ERROR) << "DADiskCreateFromBSDName";
+ return;
+ }
+
+ // dmg_bsd_device_name may only refer to part of the disk: it may be a
+ // single filesystem on a larger disk. Use the "whole disk" object to
+ // be able to unmount all mounted filesystems from the disk image, and eject
+ // the image. This is harmless if dmg_bsd_device_name already referred to a
+ // "whole disk."
+ disk.reset(DADiskCopyWholeDisk(disk));
+ if (!disk.get()) {
+ LOG(ERROR) << "DADiskCopyWholeDisk";
+ return;
+ }
+
+ ScopedIOObject<io_service_t> media(DADiskCopyIOMedia(disk));
+ if (!media.get()) {
+ LOG(ERROR) << "DADiskCopyIOMedia";
+ return;
+ }
+
+ // Make sure the device is a disk image, and get the path to its disk image
+ // file.
+ std::string disk_image_path;
+ if (!MediaResidesOnDiskImage(media, &disk_image_path)) {
+ LOG(ERROR) << "MediaResidesOnDiskImage";
+ return;
+ }
+
+ // SynchronousDADiskUnmount and SynchronousDADiskEject require that the
+ // session be scheduled with the current run loop.
+ ScopedDASessionScheduleWithRunLoop session_run_loop(session,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopCommonModes);
+
+ if (!SynchronousDADiskUnmount(disk, kDADiskUnmountOptionWhole)) {
+ LOG(ERROR) << "SynchronousDADiskUnmount";
+ return;
+ }
+
+ if (!SynchronousDADiskEject(disk, kDADiskEjectOptionDefault)) {
+ LOG(ERROR) << "SynchronousDADiskEject";
+ return;
+ }
+
+ char* disk_image_path_in_trash_c;
+ OSStatus status = FSPathMoveObjectToTrashSync(disk_image_path.c_str(),
+ &disk_image_path_in_trash_c,
+ kFSFileOperationDefaultOptions);
+ if (status != noErr) {
+ LOG(ERROR) << "FSPathMoveObjectToTrashSync: " << status;
+ return;
+ }
+
+ // FSPathMoveObjectToTrashSync alone doesn't result in the Trash icon in the
+ // Dock indicating that any garbage has been placed within it. Using the
+ // trash path that FSPathMoveObjectToTrashSync claims to have used, call
+ // FNNotifyByPath to fatten up the icon.
+ FilePath disk_image_path_in_trash(disk_image_path_in_trash_c);
+ free(disk_image_path_in_trash_c);
+
+ FilePath trash_path = disk_image_path_in_trash.DirName();
+ const UInt8* trash_path_u8 = reinterpret_cast<const UInt8*>(
+ trash_path.value().c_str());
+ status = FNNotifyByPath(trash_path_u8,
+ kFNDirectoryModifiedMessage,
+ kNilOptions);
+ if (status != noErr) {
+ LOG(ERROR) << "FNNotifyByPath: " << status;
+ return;
+ }
+}
« no previous file with comments | « chrome/browser/mac/install_from_dmg.h ('k') | chrome/browser/mac/relauncher.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698