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

Side by Side 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, 5 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/mac/install_from_dmg.h ('k') | chrome/browser/mac/relauncher.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/mac/install_from_dmg.h" 5 #include "chrome/browser/mac/install_from_dmg.h"
6 6
7 #include <ApplicationServices/ApplicationServices.h> 7 #include <ApplicationServices/ApplicationServices.h>
8 #import <AppKit/AppKit.h> 8 #import <AppKit/AppKit.h>
9 #include <CoreFoundation/CoreFoundation.h> 9 #include <CoreFoundation/CoreFoundation.h>
10 #include <CoreServices/CoreServices.h> 10 #include <CoreServices/CoreServices.h>
11 #include <DiskArbitration/DiskArbitration.h>
11 #include <IOKit/IOKitLib.h> 12 #include <IOKit/IOKitLib.h>
13 #include <stdlib.h>
12 #include <string.h> 14 #include <string.h>
13 #include <sys/param.h> 15 #include <sys/param.h>
14 #include <sys/mount.h> 16 #include <sys/mount.h>
15 17
18 #include "base/auto_reset.h"
16 #include "base/basictypes.h" 19 #include "base/basictypes.h"
17 #include "base/command_line.h" 20 #include "base/command_line.h"
18 #include "base/file_path.h" 21 #include "base/file_path.h"
19 #include "base/logging.h" 22 #include "base/logging.h"
20 #import "base/mac/mac_util.h" 23 #import "base/mac/mac_util.h"
24 #include "base/mac/scoped_cftyperef.h"
21 #include "base/mac/scoped_nsautorelease_pool.h" 25 #include "base/mac/scoped_nsautorelease_pool.h"
26 #include "base/string_util.h"
27 #include "base/sys_string_conversions.h"
22 #include "chrome/browser/mac/authorization_util.h" 28 #include "chrome/browser/mac/authorization_util.h"
23 #include "chrome/browser/mac/scoped_authorizationref.h" 29 #include "chrome/browser/mac/scoped_authorizationref.h"
24 #include "chrome/browser/mac/scoped_ioobject.h" 30 #include "chrome/browser/mac/scoped_ioobject.h"
25 #import "chrome/browser/mac/keystone_glue.h" 31 #import "chrome/browser/mac/keystone_glue.h"
26 #include "chrome/browser/mac/relauncher.h" 32 #include "chrome/browser/mac/relauncher.h"
27 #include "chrome/common/chrome_constants.h" 33 #include "chrome/common/chrome_constants.h"
28 #include "grit/chromium_strings.h" 34 #include "grit/chromium_strings.h"
29 #include "grit/generated_resources.h" 35 #include "grit/generated_resources.h"
30 #include "ui/base/l10n/l10n_util.h" 36 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/base/l10n/l10n_util_mac.h" 37 #include "ui/base/l10n/l10n_util_mac.h"
32 38
33 // When C++ exceptions are disabled, the C++ library defines |try| and 39 // When C++ exceptions are disabled, the C++ library defines |try| and
34 // |catch| so as to allow exception-expecting C++ code to build properly when 40 // |catch| so as to allow exception-expecting C++ code to build properly when
35 // language support for exceptions is not present. These macros interfere 41 // language support for exceptions is not present. These macros interfere
36 // with the use of |@try| and |@catch| in Objective-C files such as this one. 42 // with the use of |@try| and |@catch| in Objective-C files such as this one.
37 // Undefine these macros here, after everything has been #included, since 43 // Undefine these macros here, after everything has been #included, since
38 // there will be no C++ uses and only Objective-C uses from this point on. 44 // there will be no C++ uses and only Objective-C uses from this point on.
39 #undef try 45 #undef try
40 #undef catch 46 #undef catch
41 47
42 namespace { 48 namespace {
43 49
50 // Given an io_service_t (expected to be of class IOMedia), walks the ancestor
51 // chain, returning the closest ancestor that implements class IOHDIXHDDrive,
52 // if any. If no such ancestor is found, returns NULL. Following the "copy"
53 // rule, the caller assumes ownership of the returned value.
54 //
55 // Note that this looks for a class that inherits from IOHDIXHDDrive, but it
56 // will not likely find a concrete IOHDIXHDDrive. It will be
57 // IOHDIXHDDriveOutKernel for disk images mounted "out-of-kernel" or
58 // IOHDIXHDDriveInKernel for disk images mounted "in-kernel." Out-of-kernel is
59 // the default as of Mac OS X 10.5. See the documentation for "hdiutil attach
60 // -kernel" for more information.
61 io_service_t CopyHDIXDriveServiceForMedia(io_service_t media) {
62 const char disk_image_class[] = "IOHDIXHDDrive";
63
64 // This is highly unlikely. media as passed in is expected to be of class
65 // IOMedia. Since the media service's entire ancestor chain will be checked,
66 // though, check it as well.
67 if (IOObjectConformsTo(media, disk_image_class)) {
68 IOObjectRetain(media);
69 return media;
70 }
71
72 io_iterator_t iterator_ref;
73 kern_return_t kr =
74 IORegistryEntryCreateIterator(media,
75 kIOServicePlane,
76 kIORegistryIterateRecursively |
77 kIORegistryIterateParents,
78 &iterator_ref);
79 if (kr != KERN_SUCCESS) {
80 LOG(ERROR) << "IORegistryEntryCreateIterator: " << kr;
81 return NULL;
82 }
83 ScopedIOObject<io_iterator_t> iterator(iterator_ref);
84 iterator_ref = NULL;
85
86 // Look at each of the ancestor services, beginning with the parent,
87 // iterating all the way up to the device tree's root. If any ancestor
88 // service matches the class used for disk images, the media resides on a
89 // disk image, and the disk image file's path can be determined by examining
90 // the image-path property.
91 for (ScopedIOObject<io_service_t> ancestor(IOIteratorNext(iterator));
92 ancestor;
93 ancestor.reset(IOIteratorNext(iterator))) {
94 if (IOObjectConformsTo(ancestor, disk_image_class)) {
95 return ancestor.release();
96 }
97 }
98
99 // The media does not reside on a disk image.
100 return NULL;
101 }
102
103 // Given an io_service_t (expected to be of class IOMedia), determines whether
104 // that service is on a disk image. If it is, returns true. If image_path is
105 // present, it will be set to the pathname of the disk image file, encoded in
106 // filesystem encoding.
107 bool MediaResidesOnDiskImage(io_service_t media, std::string* image_path) {
108 if (image_path) {
109 image_path->clear();
110 }
111
112 ScopedIOObject<io_service_t> hdix_drive(CopyHDIXDriveServiceForMedia(media));
113 if (!hdix_drive) {
114 return false;
115 }
116
117 if (image_path) {
118 base::mac::ScopedCFTypeRef<CFTypeRef> image_path_cftyperef(
119 IORegistryEntryCreateCFProperty(hdix_drive,
120 CFSTR("image-path"),
121 NULL,
122 0));
123 if (!image_path_cftyperef) {
124 LOG(ERROR) << "IORegistryEntryCreateCFProperty";
125 return true;
126 }
127 if (CFGetTypeID(image_path_cftyperef) != CFDataGetTypeID()) {
128 base::mac::ScopedCFTypeRef<CFStringRef> observed_type_cf(
129 CFCopyTypeIDDescription(CFGetTypeID(image_path_cftyperef)));
130 std::string observed_type;
131 if (observed_type_cf) {
132 observed_type.assign(", observed ");
133 observed_type.append(base::SysCFStringRefToUTF8(observed_type_cf));
134 }
135 LOG(ERROR) << "image-path: expected CFData" << observed_type;
136 return true;
137 }
138
139 CFDataRef image_path_data = static_cast<CFDataRef>(
140 image_path_cftyperef.get());
141 CFIndex length = CFDataGetLength(image_path_data);
142 char* image_path_c = WriteInto(image_path, length + 1);
143 CFDataGetBytes(image_path_data,
144 CFRangeMake(0, length),
145 reinterpret_cast<UInt8*>(image_path_c));
146 }
147
148 return true;
149 }
150
44 // Returns true if |path| is located on a read-only filesystem of a disk 151 // Returns true if |path| is located on a read-only filesystem of a disk
45 // image. Returns false if not, or in the event of an error. 152 // image. Returns false if not, or in the event of an error. If
46 bool IsPathOnReadOnlyDiskImage(const char path[]) { 153 // out_dmg_bsd_device_name is present, it will be set to the BSD device name
154 // for the disk image's device, in "diskNsM" form.
155 bool IsPathOnReadOnlyDiskImage(const char path[],
156 std::string* out_dmg_bsd_device_name) {
157 if (out_dmg_bsd_device_name) {
158 out_dmg_bsd_device_name->clear();
159 }
160
47 struct statfs statfs_buf; 161 struct statfs statfs_buf;
48 if (statfs(path, &statfs_buf) != 0) { 162 if (statfs(path, &statfs_buf) != 0) {
49 PLOG(ERROR) << "statfs " << path; 163 PLOG(ERROR) << "statfs " << path;
50 return false; 164 return false;
51 } 165 }
52 166
53 if (!(statfs_buf.f_flags & MNT_RDONLY)) { 167 if (!(statfs_buf.f_flags & MNT_RDONLY)) {
54 // Not on a read-only filesystem. 168 // Not on a read-only filesystem.
55 return false; 169 return false;
56 } 170 }
57 171
58 const char dev_root[] = "/dev/"; 172 const char dev_root[] = "/dev/";
59 const int dev_root_length = arraysize(dev_root) - 1; 173 const int dev_root_length = arraysize(dev_root) - 1;
60 if (strncmp(statfs_buf.f_mntfromname, dev_root, dev_root_length) != 0) { 174 if (strncmp(statfs_buf.f_mntfromname, dev_root, dev_root_length) != 0) {
61 // Not rooted at dev_root, no BSD name to search on. 175 // Not rooted at dev_root, no BSD name to search on.
62 return false; 176 return false;
63 } 177 }
64 178
65 // BSD names in IOKit don't include dev_root. 179 // BSD names in IOKit don't include dev_root.
66 const char* bsd_device_name = statfs_buf.f_mntfromname + dev_root_length; 180 const char* dmg_bsd_device_name = statfs_buf.f_mntfromname + dev_root_length;
181 if (out_dmg_bsd_device_name) {
182 out_dmg_bsd_device_name->assign(dmg_bsd_device_name);
183 }
67 184
68 const mach_port_t master_port = kIOMasterPortDefault; 185 const mach_port_t master_port = kIOMasterPortDefault;
69 186
70 // IOBSDNameMatching gives ownership of match_dict to the caller, but 187 // IOBSDNameMatching gives ownership of match_dict to the caller, but
71 // IOServiceGetMatchingServices will assume that reference. 188 // IOServiceGetMatchingServices will assume that reference.
72 CFMutableDictionaryRef match_dict = IOBSDNameMatching(master_port, 189 CFMutableDictionaryRef match_dict = IOBSDNameMatching(master_port,
73 0, 190 0,
74 bsd_device_name); 191 dmg_bsd_device_name);
75 if (!match_dict) { 192 if (!match_dict) {
76 LOG(ERROR) << "IOBSDNameMatching " << bsd_device_name; 193 LOG(ERROR) << "IOBSDNameMatching " << dmg_bsd_device_name;
77 return false; 194 return false;
78 } 195 }
79 196
80 io_iterator_t iterator_ref; 197 io_iterator_t iterator_ref;
81 kern_return_t kr = IOServiceGetMatchingServices(master_port, 198 kern_return_t kr = IOServiceGetMatchingServices(master_port,
82 match_dict, 199 match_dict,
83 &iterator_ref); 200 &iterator_ref);
84 if (kr != KERN_SUCCESS) { 201 if (kr != KERN_SUCCESS) {
85 LOG(ERROR) << "IOServiceGetMatchingServices " << bsd_device_name 202 LOG(ERROR) << "IOServiceGetMatchingServices: " << kr;
86 << ": kernel error " << kr;
87 return false; 203 return false;
88 } 204 }
89 ScopedIOObject<io_iterator_t> iterator(iterator_ref); 205 ScopedIOObject<io_iterator_t> iterator(iterator_ref);
90 iterator_ref = NULL; 206 iterator_ref = NULL;
91 207
92 // There needs to be exactly one matching service. 208 // There needs to be exactly one matching service.
93 ScopedIOObject<io_service_t> filesystem_service(IOIteratorNext(iterator)); 209 ScopedIOObject<io_service_t> media(IOIteratorNext(iterator));
94 if (!filesystem_service) { 210 if (!media) {
95 LOG(ERROR) << "IOIteratorNext " << bsd_device_name << ": no service"; 211 LOG(ERROR) << "IOIteratorNext: no service";
96 return false; 212 return false;
97 } 213 }
98 ScopedIOObject<io_service_t> unexpected_service(IOIteratorNext(iterator)); 214 ScopedIOObject<io_service_t> unexpected_service(IOIteratorNext(iterator));
99 if (unexpected_service) { 215 if (unexpected_service) {
100 LOG(ERROR) << "IOIteratorNext " << bsd_device_name << ": too many services"; 216 LOG(ERROR) << "IOIteratorNext: too many services";
101 return false; 217 return false;
102 } 218 }
103 219
104 iterator.reset(); 220 iterator.reset();
105 221
106 const char disk_image_class[] = "IOHDIXController"; 222 return MediaResidesOnDiskImage(media, NULL);
107
108 // This is highly unlikely. The filesystem service is expected to be of
109 // class IOMedia. Since the filesystem service's entire ancestor chain
110 // will be checked, though, check the filesystem service's class itself.
111 if (IOObjectConformsTo(filesystem_service, disk_image_class)) {
112 return true;
113 }
114
115 kr = IORegistryEntryCreateIterator(filesystem_service,
116 kIOServicePlane,
117 kIORegistryIterateRecursively |
118 kIORegistryIterateParents,
119 &iterator_ref);
120 if (kr != KERN_SUCCESS) {
121 LOG(ERROR) << "IORegistryEntryCreateIterator " << bsd_device_name
122 << ": kernel error " << kr;
123 return false;
124 }
125 iterator.reset(iterator_ref);
126 iterator_ref = NULL;
127
128 // Look at each of the filesystem service's ancestor services, beginning
129 // with the parent, iterating all the way up to the device tree's root. If
130 // any ancestor service matches the class used for disk images, the
131 // filesystem resides on a disk image.
132 for (ScopedIOObject<io_service_t> ancestor_service(IOIteratorNext(iterator));
133 ancestor_service;
134 ancestor_service.reset(IOIteratorNext(iterator))) {
135 if (IOObjectConformsTo(ancestor_service, disk_image_class)) {
136 return true;
137 }
138 }
139
140 // The filesystem does not reside on a disk image.
141 return false;
142 } 223 }
143 224
144 // Returns true if the application is located on a read-only filesystem of a 225 // Returns true if the application is located on a read-only filesystem of a
145 // disk image. Returns false if not, or in the event of an error. 226 // disk image. Returns false if not, or in the event of an error. If
146 bool IsAppRunningFromReadOnlyDiskImage() { 227 // dmg_bsd_device_name is present, it will be set to the BSD device name for
228 // the disk image's device, in "diskNsM" form.
229 bool IsAppRunningFromReadOnlyDiskImage(std::string* dmg_bsd_device_name) {
147 return IsPathOnReadOnlyDiskImage( 230 return IsPathOnReadOnlyDiskImage(
148 [[[NSBundle mainBundle] bundlePath] fileSystemRepresentation]); 231 [[[NSBundle mainBundle] bundlePath] fileSystemRepresentation],
232 dmg_bsd_device_name);
149 } 233 }
150 234
151 // Shows a dialog asking the user whether or not to install from the disk 235 // Shows a dialog asking the user whether or not to install from the disk
152 // image. Returns true if the user approves installation. 236 // image. Returns true if the user approves installation.
153 bool ShouldInstallDialog() { 237 bool ShouldInstallDialog() {
154 NSString* title = l10n_util::GetNSStringFWithFixup( 238 NSString* title = l10n_util::GetNSStringFWithFixup(
155 IDS_INSTALL_FROM_DMG_TITLE, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); 239 IDS_INSTALL_FROM_DMG_TITLE, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
156 NSString* prompt = l10n_util::GetNSStringFWithFixup( 240 NSString* prompt = l10n_util::GetNSStringFWithFixup(
157 IDS_INSTALL_FROM_DMG_PROMPT, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); 241 IDS_INSTALL_FROM_DMG_PROMPT, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
158 NSString* yes = l10n_util::GetNSStringWithFixup(IDS_INSTALL_FROM_DMG_YES); 242 NSString* yes = l10n_util::GetNSStringWithFixup(IDS_INSTALL_FROM_DMG_YES);
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
252 [keystone_glue promoteTicketWithAuthorization:authorization.release() 336 [keystone_glue promoteTicketWithAuthorization:authorization.release()
253 synchronous:YES]; 337 synchronous:YES];
254 } 338 }
255 339
256 return true; 340 return true;
257 } 341 }
258 342
259 // Launches the application at installed_path. The helper application 343 // Launches the application at installed_path. The helper application
260 // contained within install_path will be used for the relauncher process. This 344 // contained within install_path will be used for the relauncher process. This
261 // keeps Launch Services from ever having to see or think about the helper 345 // keeps Launch Services from ever having to see or think about the helper
262 // application on the disk image. 346 // application on the disk image. The relauncher process will be asked to
263 bool LaunchInstalledApp(NSString* installed_path) { 347 // call EjectAndTrashDiskImage on dmg_bsd_device_name.
348 bool LaunchInstalledApp(NSString* installed_path,
349 const std::string& dmg_bsd_device_name) {
264 FilePath browser_path([installed_path fileSystemRepresentation]); 350 FilePath browser_path([installed_path fileSystemRepresentation]);
265 351
266 FilePath helper_path = browser_path.Append("Contents/Versions"); 352 FilePath helper_path = browser_path.Append("Contents/Versions");
267 helper_path = helper_path.Append(chrome::kChromeVersion); 353 helper_path = helper_path.Append(chrome::kChromeVersion);
268 helper_path = helper_path.Append(chrome::kHelperProcessExecutablePath); 354 helper_path = helper_path.Append(chrome::kHelperProcessExecutablePath);
269 355
270 std::vector<std::string> args = 356 std::vector<std::string> args =
271 CommandLine::ForCurrentProcess()->argv(); 357 CommandLine::ForCurrentProcess()->argv();
272 args[0] = browser_path.value(); 358 args[0] = browser_path.value();
273 359
274 return mac_relauncher::RelaunchAppWithHelper(helper_path.value(), args); 360 std::vector<std::string> relauncher_args;
361 if (!dmg_bsd_device_name.empty()) {
362 std::string dmg_arg(mac_relauncher::kRelauncherDMGDeviceArg);
363 dmg_arg.append(dmg_bsd_device_name);
364 relauncher_args.push_back(dmg_arg);
365 }
366
367 return mac_relauncher::RelaunchAppWithHelper(helper_path.value(),
368 relauncher_args,
369 args);
275 } 370 }
276 371
277 void ShowErrorDialog() { 372 void ShowErrorDialog() {
278 NSString* title = l10n_util::GetNSStringWithFixup( 373 NSString* title = l10n_util::GetNSStringWithFixup(
279 IDS_INSTALL_FROM_DMG_ERROR_TITLE); 374 IDS_INSTALL_FROM_DMG_ERROR_TITLE);
280 NSString* error = l10n_util::GetNSStringFWithFixup( 375 NSString* error = l10n_util::GetNSStringFWithFixup(
281 IDS_INSTALL_FROM_DMG_ERROR, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); 376 IDS_INSTALL_FROM_DMG_ERROR, l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
282 NSString* ok = l10n_util::GetNSStringWithFixup(IDS_OK); 377 NSString* ok = l10n_util::GetNSStringWithFixup(IDS_OK);
283 378
284 NSAlert* alert = [[[NSAlert alloc] init] autorelease]; 379 NSAlert* alert = [[[NSAlert alloc] init] autorelease];
285 380
286 [alert setAlertStyle:NSWarningAlertStyle]; 381 [alert setAlertStyle:NSWarningAlertStyle];
287 [alert setMessageText:title]; 382 [alert setMessageText:title];
288 [alert setInformativeText:error]; 383 [alert setInformativeText:error];
289 [alert addButtonWithTitle:ok]; 384 [alert addButtonWithTitle:ok];
290 385
291 [alert runModal]; 386 [alert runModal];
292 } 387 }
293 388
294 } // namespace 389 } // namespace
295 390
296 bool MaybeInstallFromDiskImage() { 391 bool MaybeInstallFromDiskImage() {
297 base::mac::ScopedNSAutoreleasePool autorelease_pool; 392 base::mac::ScopedNSAutoreleasePool autorelease_pool;
298 393
299 if (!IsAppRunningFromReadOnlyDiskImage()) { 394 std::string dmg_bsd_device_name;
395 if (!IsAppRunningFromReadOnlyDiskImage(&dmg_bsd_device_name)) {
300 return false; 396 return false;
301 } 397 }
302 398
303 NSArray* application_directories = 399 NSArray* application_directories =
304 NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, 400 NSSearchPathForDirectoriesInDomains(NSApplicationDirectory,
305 NSLocalDomainMask, 401 NSLocalDomainMask,
306 YES); 402 YES);
307 if ([application_directories count] == 0) { 403 if ([application_directories count] == 0) {
308 LOG(ERROR) << "NSSearchPathForDirectoriesInDomains: " 404 LOG(ERROR) << "NSSearchPathForDirectoriesInDomains: "
309 << "no local application directories"; 405 << "no local application directories";
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
346 ScopedAuthorizationRef authorization( 442 ScopedAuthorizationRef authorization(
347 MaybeShowAuthorizationDialog(application_directory)); 443 MaybeShowAuthorizationDialog(application_directory));
348 // authorization will be NULL if it's deemed unnecessary or if 444 // authorization will be NULL if it's deemed unnecessary or if
349 // authentication fails. In either case, try to install without privilege 445 // authentication fails. In either case, try to install without privilege
350 // escalation. 446 // escalation.
351 447
352 if (!InstallFromDiskImage(authorization.release(), 448 if (!InstallFromDiskImage(authorization.release(),
353 installer_path, 449 installer_path,
354 source_path, 450 source_path,
355 target_path) || 451 target_path) ||
356 !LaunchInstalledApp(target_path)) { 452 !LaunchInstalledApp(target_path, dmg_bsd_device_name)) {
357 ShowErrorDialog(); 453 ShowErrorDialog();
358 return false; 454 return false;
359 } 455 }
360 456
361 return true; 457 return true;
362 } 458 }
459
460 namespace {
461
462 // A simple scoper that calls DASessionScheduleWithRunLoop when created and
463 // DASessionUnscheduleFromRunLoop when destroyed.
464 class ScopedDASessionScheduleWithRunLoop {
465 public:
466 ScopedDASessionScheduleWithRunLoop(DASessionRef session,
467 CFRunLoopRef run_loop,
468 CFStringRef run_loop_mode)
469 : session_(session),
470 run_loop_(run_loop),
471 run_loop_mode_(run_loop_mode) {
472 DASessionScheduleWithRunLoop(session_, run_loop_, run_loop_mode_);
473 }
474
475 ~ScopedDASessionScheduleWithRunLoop() {
476 DASessionUnscheduleFromRunLoop(session_, run_loop_, run_loop_mode_);
477 }
478
479 private:
480 DASessionRef session_;
481 CFRunLoopRef run_loop_;
482 CFStringRef run_loop_mode_;
483
484 DISALLOW_COPY_AND_ASSIGN(ScopedDASessionScheduleWithRunLoop);
485 };
486
487 // A small structure used to ferry data between SynchronousDAOperation and
488 // SynchronousDACallbackAdapter.
489 struct SynchronousDACallbackData {
490 public:
491 SynchronousDACallbackData()
492 : callback_called(false),
493 run_loop_running(false) {
494 }
495
496 base::mac::ScopedCFTypeRef<DADissenterRef> dissenter;
497 bool callback_called;
498 bool run_loop_running;
499
500 private:
501 DISALLOW_COPY_AND_ASSIGN(SynchronousDACallbackData);
502 };
503
504 // The callback target for SynchronousDAOperation. Set the fields in
505 // SynchronousDACallbackData properly and then stops the run loop so that
506 // SynchronousDAOperation may proceed.
507 void SynchronousDACallbackAdapter(DADiskRef disk,
508 DADissenterRef dissenter,
509 void* context) {
510 SynchronousDACallbackData* callback_data =
511 static_cast<SynchronousDACallbackData*>(context);
512 callback_data->callback_called = true;
513
514 if (dissenter) {
515 CFRetain(dissenter);
516 callback_data->dissenter.reset(dissenter);
517 }
518
519 // Only stop the run loop if SynchronousDAOperation started it. Don't stop
520 // anything if this callback was reached synchronously from DADiskUnmount or
521 // DADiskEject.
522 if (callback_data->run_loop_running) {
523 CFRunLoopStop(CFRunLoopGetCurrent());
524 }
525 }
526
527 // Performs a DiskArbitration operation synchronously. After the operation is
528 // requested by SynchronousDADiskUnmount or SynchronousDADiskEject, those
529 // functions will call this one to run a run loop for a period of time,
530 // waiting for the callback to be called. When the callback is called, the
531 // run loop will be stopped, and this function will examine the result. If
532 // a dissenter prevented the operation from completing, or if the run loop
533 // timed out without the callback being called, this function will return
534 // false. When the callback completes successfully with no dissenters within
535 // the time allotted, this function returns true. This function requires that
536 // the DASession being used for the operation being performed has been added
537 // to the current run loop with DASessionScheduleWithRunLoop.
538 bool SynchronousDAOperation(const char* name,
539 SynchronousDACallbackData* callback_data) {
540 // The callback may already have been called synchronously. In that case,
541 // avoid spinning the run loop at all.
542 if (!callback_data->callback_called) {
543 const CFTimeInterval kOperationTimeoutSeconds = 15;
544 AutoReset<bool> running_reset(&callback_data->run_loop_running, true);
545 CFRunLoopRunInMode(kCFRunLoopDefaultMode, kOperationTimeoutSeconds, FALSE);
546 }
547
548 if (!callback_data->callback_called) {
549 LOG(ERROR) << name << ": timed out";
550 return false;
551 } else if (callback_data->dissenter) {
552 CFStringRef status_string_cf =
553 DADissenterGetStatusString(callback_data->dissenter);
554 std::string status_string;
555 if (status_string_cf) {
556 status_string.assign(" ");
557 status_string.append(base::SysCFStringRefToUTF8(status_string_cf));
558 }
559 LOG(ERROR) << name << ": dissenter: "
560 << DADissenterGetStatus(callback_data->dissenter)
561 << status_string;
562 return false;
563 }
564
565 return true;
566 }
567
568 // Calls DADiskUnmount synchronously, returning the result.
569 bool SynchronousDADiskUnmount(DADiskRef disk, DADiskUnmountOptions options) {
570 SynchronousDACallbackData callback_data;
571 DADiskUnmount(disk, options, SynchronousDACallbackAdapter, &callback_data);
572 return SynchronousDAOperation("DADiskUnmount", &callback_data);
573 }
574
575 // Calls DADiskEject synchronously, returning the result.
576 bool SynchronousDADiskEject(DADiskRef disk, DADiskEjectOptions options) {
577 SynchronousDACallbackData callback_data;
578 DADiskEject(disk, options, SynchronousDACallbackAdapter, &callback_data);
579 return SynchronousDAOperation("DADiskEject", &callback_data);
580 }
581
582 } // namespace
583
584 void EjectAndTrashDiskImage(const std::string& dmg_bsd_device_name) {
585 base::mac::ScopedCFTypeRef<DASessionRef> session(DASessionCreate(NULL));
586 if (!session.get()) {
587 LOG(ERROR) << "DASessionCreate";
588 return;
589 }
590
591 base::mac::ScopedCFTypeRef<DADiskRef> disk(
592 DADiskCreateFromBSDName(NULL, session, dmg_bsd_device_name.c_str()));
593 if (!disk.get()) {
594 LOG(ERROR) << "DADiskCreateFromBSDName";
595 return;
596 }
597
598 // dmg_bsd_device_name may only refer to part of the disk: it may be a
599 // single filesystem on a larger disk. Use the "whole disk" object to
600 // be able to unmount all mounted filesystems from the disk image, and eject
601 // the image. This is harmless if dmg_bsd_device_name already referred to a
602 // "whole disk."
603 disk.reset(DADiskCopyWholeDisk(disk));
604 if (!disk.get()) {
605 LOG(ERROR) << "DADiskCopyWholeDisk";
606 return;
607 }
608
609 ScopedIOObject<io_service_t> media(DADiskCopyIOMedia(disk));
610 if (!media.get()) {
611 LOG(ERROR) << "DADiskCopyIOMedia";
612 return;
613 }
614
615 // Make sure the device is a disk image, and get the path to its disk image
616 // file.
617 std::string disk_image_path;
618 if (!MediaResidesOnDiskImage(media, &disk_image_path)) {
619 LOG(ERROR) << "MediaResidesOnDiskImage";
620 return;
621 }
622
623 // SynchronousDADiskUnmount and SynchronousDADiskEject require that the
624 // session be scheduled with the current run loop.
625 ScopedDASessionScheduleWithRunLoop session_run_loop(session,
626 CFRunLoopGetCurrent(),
627 kCFRunLoopCommonModes);
628
629 if (!SynchronousDADiskUnmount(disk, kDADiskUnmountOptionWhole)) {
630 LOG(ERROR) << "SynchronousDADiskUnmount";
631 return;
632 }
633
634 if (!SynchronousDADiskEject(disk, kDADiskEjectOptionDefault)) {
635 LOG(ERROR) << "SynchronousDADiskEject";
636 return;
637 }
638
639 char* disk_image_path_in_trash_c;
640 OSStatus status = FSPathMoveObjectToTrashSync(disk_image_path.c_str(),
641 &disk_image_path_in_trash_c,
642 kFSFileOperationDefaultOptions);
643 if (status != noErr) {
644 LOG(ERROR) << "FSPathMoveObjectToTrashSync: " << status;
645 return;
646 }
647
648 // FSPathMoveObjectToTrashSync alone doesn't result in the Trash icon in the
649 // Dock indicating that any garbage has been placed within it. Using the
650 // trash path that FSPathMoveObjectToTrashSync claims to have used, call
651 // FNNotifyByPath to fatten up the icon.
652 FilePath disk_image_path_in_trash(disk_image_path_in_trash_c);
653 free(disk_image_path_in_trash_c);
654
655 FilePath trash_path = disk_image_path_in_trash.DirName();
656 const UInt8* trash_path_u8 = reinterpret_cast<const UInt8*>(
657 trash_path.value().c_str());
658 status = FNNotifyByPath(trash_path_u8,
659 kFNDirectoryModifiedMessage,
660 kNilOptions);
661 if (status != noErr) {
662 LOG(ERROR) << "FNNotifyByPath: " << status;
663 return;
664 }
665 }
OLDNEW
« 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