Chromium Code Reviews| 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 |