OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 } | |
OLD | NEW |