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