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

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

Powered by Google App Engine
This is Rietveld 408576698