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