Home | History | Annotate | Download | only in storage_monitor
      1 // Copyright 2014 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 "components/storage_monitor/volume_mount_watcher_win.h"
      6 
      7 #include <windows.h>
      8 
      9 #include <dbt.h>
     10 #include <fileapi.h>
     11 #include <shlobj.h>
     12 #include <winioctl.h>
     13 
     14 #include "base/bind_helpers.h"
     15 #include "base/metrics/histogram.h"
     16 #include "base/stl_util.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/strings/string_util.h"
     19 #include "base/strings/stringprintf.h"
     20 #include "base/strings/utf_string_conversions.h"
     21 #include "base/task_runner_util.h"
     22 #include "base/time/time.h"
     23 #include "base/win/scoped_handle.h"
     24 #include "components/storage_monitor/media_storage_util.h"
     25 #include "components/storage_monitor/storage_info.h"
     26 #include "content/public/browser/browser_thread.h"
     27 #include "content/public/browser/user_metrics.h"
     28 
     29 using content::BrowserThread;
     30 
     31 namespace storage_monitor {
     32 
     33 namespace {
     34 
     35 const DWORD kMaxPathBufLen = MAX_PATH + 1;
     36 
     37 enum DeviceType {
     38   FLOPPY,
     39   REMOVABLE,
     40   FIXED,
     41 };
     42 
     43 // Histogram values for recording frequencies of eject attempts and
     44 // outcomes.
     45 enum EjectWinLockOutcomes {
     46   LOCK_ATTEMPT,
     47   LOCK_TIMEOUT,
     48   LOCK_TIMEOUT2,
     49   NUM_LOCK_OUTCOMES,
     50 };
     51 
     52 // We are trying to figure out whether the drive is a fixed volume,
     53 // a removable storage, or a floppy. A "floppy" here means "a volume we
     54 // want to basically ignore because it won't fit media and will spin
     55 // if we touch it to get volume metadata." GetDriveType returns DRIVE_REMOVABLE
     56 // on either floppy or removable volumes. The DRIVE_CDROM type is handled
     57 // as a floppy, as are DRIVE_UNKNOWN and DRIVE_NO_ROOT_DIR, as there are
     58 // reports that some floppy drives don't report as DRIVE_REMOVABLE.
     59 DeviceType GetDeviceType(const base::string16& mount_point) {
     60   UINT drive_type = GetDriveType(mount_point.c_str());
     61   if (drive_type == DRIVE_FIXED || drive_type == DRIVE_REMOTE ||
     62       drive_type == DRIVE_RAMDISK) {
     63     return FIXED;
     64   }
     65   if (drive_type != DRIVE_REMOVABLE)
     66     return FLOPPY;
     67 
     68   // Check device strings of the form "X:" and "\\.\X:"
     69   // For floppy drives, these will return strings like "/Device/Floppy0"
     70   base::string16 device = mount_point;
     71   if (EndsWith(mount_point, L"\\", false))
     72     device = mount_point.substr(0, mount_point.length() - 1);
     73   base::string16 device_path;
     74   base::string16 device_path_slash;
     75   DWORD dos_device = QueryDosDevice(
     76       device.c_str(), WriteInto(&device_path, kMaxPathBufLen), kMaxPathBufLen);
     77   base::string16 device_slash = base::string16(L"\\\\.\\");
     78   device_slash += device;
     79   DWORD dos_device_slash = QueryDosDevice(
     80       device_slash.c_str(), WriteInto(&device_path_slash, kMaxPathBufLen),
     81       kMaxPathBufLen);
     82   if (dos_device == 0 && dos_device_slash == 0)
     83     return FLOPPY;
     84   if (device_path.find(L"Floppy") != base::string16::npos ||
     85       device_path_slash.find(L"Floppy") != base::string16::npos) {
     86     return FLOPPY;
     87   }
     88 
     89   return REMOVABLE;
     90 }
     91 
     92 // Returns 0 if the devicetype is not volume.
     93 uint32 GetVolumeBitMaskFromBroadcastHeader(LPARAM data) {
     94   DEV_BROADCAST_VOLUME* dev_broadcast_volume =
     95       reinterpret_cast<DEV_BROADCAST_VOLUME*>(data);
     96   if (dev_broadcast_volume->dbcv_devicetype == DBT_DEVTYP_VOLUME)
     97     return dev_broadcast_volume->dbcv_unitmask;
     98   return 0;
     99 }
    100 
    101 // Returns true if |data| represents a logical volume structure.
    102 bool IsLogicalVolumeStructure(LPARAM data) {
    103   DEV_BROADCAST_HDR* broadcast_hdr =
    104       reinterpret_cast<DEV_BROADCAST_HDR*>(data);
    105   return broadcast_hdr != NULL &&
    106          broadcast_hdr->dbch_devicetype == DBT_DEVTYP_VOLUME;
    107 }
    108 
    109 // Gets the total volume of the |mount_point| in bytes.
    110 uint64 GetVolumeSize(const base::string16& mount_point) {
    111   ULARGE_INTEGER total;
    112   if (!GetDiskFreeSpaceExW(mount_point.c_str(), NULL, &total, NULL))
    113     return 0;
    114   return total.QuadPart;
    115 }
    116 
    117 // Gets mass storage device information given a |device_path|. On success,
    118 // returns true and fills in |info|.
    119 // The following msdn blog entry is helpful for understanding disk volumes
    120 // and how they are treated in Windows:
    121 // http://blogs.msdn.com/b/adioltean/archive/2005/04/16/408947.aspx.
    122 bool GetDeviceDetails(const base::FilePath& device_path, StorageInfo* info) {
    123   DCHECK(info);
    124 
    125   base::string16 mount_point;
    126   if (!GetVolumePathName(device_path.value().c_str(),
    127                          WriteInto(&mount_point, kMaxPathBufLen),
    128                          kMaxPathBufLen)) {
    129     return false;
    130   }
    131   mount_point.resize(wcslen(mount_point.c_str()));
    132 
    133   // Note: experimentally this code does not spin a floppy drive. It
    134   // returns a GUID associated with the device, not the volume.
    135   base::string16 guid;
    136   if (!GetVolumeNameForVolumeMountPoint(mount_point.c_str(),
    137                                         WriteInto(&guid, kMaxPathBufLen),
    138                                         kMaxPathBufLen)) {
    139     return false;
    140   }
    141   // In case it has two GUID's (see above mentioned blog), do it again.
    142   if (!GetVolumeNameForVolumeMountPoint(guid.c_str(),
    143                                         WriteInto(&guid, kMaxPathBufLen),
    144                                         kMaxPathBufLen)) {
    145     return false;
    146   }
    147 
    148   // If we're adding a floppy drive, return without querying any more
    149   // drive metadata -- it will cause the floppy drive to seek.
    150   // Note: treats FLOPPY as FIXED_MASS_STORAGE. This is intentional.
    151   DeviceType device_type = GetDeviceType(mount_point);
    152   if (device_type == FLOPPY) {
    153     info->set_device_id(StorageInfo::MakeDeviceId(
    154         StorageInfo::FIXED_MASS_STORAGE, base::UTF16ToUTF8(guid)));
    155     return true;
    156   }
    157 
    158   StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
    159   if (device_type == REMOVABLE) {
    160     type = StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
    161     if (MediaStorageUtil::HasDcim(base::FilePath(mount_point)))
    162       type = StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
    163   }
    164 
    165   // NOTE: experimentally, this function returns false if there is no volume
    166   // name set.
    167   base::string16 volume_label;
    168   GetVolumeInformationW(device_path.value().c_str(),
    169                         WriteInto(&volume_label, kMaxPathBufLen),
    170                         kMaxPathBufLen, NULL, NULL, NULL, NULL, 0);
    171 
    172   uint64 total_size_in_bytes = GetVolumeSize(mount_point);
    173   std::string device_id =
    174       StorageInfo::MakeDeviceId(type, base::UTF16ToUTF8(guid));
    175 
    176   // TODO(gbillock): if volume_label.empty(), get the vendor/model information
    177   // for the volume.
    178   *info = StorageInfo(device_id, mount_point, volume_label, base::string16(),
    179                       base::string16(), total_size_in_bytes);
    180   return true;
    181 }
    182 
    183 // Returns a vector of all the removable mass storage devices that are
    184 // connected.
    185 std::vector<base::FilePath> GetAttachedDevices() {
    186   std::vector<base::FilePath> result;
    187   base::string16 volume_name;
    188   HANDLE find_handle = FindFirstVolume(WriteInto(&volume_name, kMaxPathBufLen),
    189                                        kMaxPathBufLen);
    190   if (find_handle == INVALID_HANDLE_VALUE)
    191     return result;
    192 
    193   while (true) {
    194     base::string16 volume_path;
    195     DWORD return_count;
    196     if (GetVolumePathNamesForVolumeName(volume_name.c_str(),
    197                                         WriteInto(&volume_path, kMaxPathBufLen),
    198                                         kMaxPathBufLen, &return_count)) {
    199       result.push_back(base::FilePath(volume_path));
    200     }
    201     if (!FindNextVolume(find_handle, WriteInto(&volume_name, kMaxPathBufLen),
    202                         kMaxPathBufLen)) {
    203       if (GetLastError() != ERROR_NO_MORE_FILES)
    204         DPLOG(ERROR);
    205       break;
    206     }
    207   }
    208 
    209   FindVolumeClose(find_handle);
    210   return result;
    211 }
    212 
    213 // Eject a removable volume at the specified |device| path. This works by
    214 // 1) locking the volume,
    215 // 2) unmounting the volume,
    216 // 3) ejecting the volume.
    217 // If the lock fails, it will re-schedule itself.
    218 // See http://support.microsoft.com/kb/165721
    219 void EjectDeviceInThreadPool(
    220     const base::FilePath& device,
    221     base::Callback<void(StorageMonitor::EjectStatus)> callback,
    222     scoped_refptr<base::SequencedTaskRunner> task_runner,
    223     int iteration) {
    224   base::FilePath::StringType volume_name;
    225   base::FilePath::CharType drive_letter = device.value()[0];
    226   // Don't try to eject if the path isn't a simple one -- we're not
    227   // sure how to do that yet. Need to figure out how to eject volumes mounted
    228   // at not-just-drive-letter paths.
    229   if (drive_letter < L'A' || drive_letter > L'Z' ||
    230       device != device.DirName()) {
    231     BrowserThread::PostTask(
    232         BrowserThread::UI, FROM_HERE,
    233         base::Bind(callback, StorageMonitor::EJECT_FAILURE));
    234     return;
    235   }
    236   base::SStringPrintf(&volume_name, L"\\\\.\\%lc:", drive_letter);
    237 
    238   base::win::ScopedHandle volume_handle(CreateFile(
    239       volume_name.c_str(),
    240       GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
    241       NULL, OPEN_EXISTING, 0, NULL));
    242 
    243   if (!volume_handle.IsValid()) {
    244     BrowserThread::PostTask(
    245         BrowserThread::UI, FROM_HERE,
    246         base::Bind(callback, StorageMonitor::EJECT_FAILURE));
    247     return;
    248   }
    249 
    250   DWORD bytes_returned = 0;  // Unused, but necessary for ioctl's.
    251 
    252   // Lock the drive to be ejected (so that other processes can't open
    253   // files on it). If this fails, it means some other process has files
    254   // open on the device. Note that the lock is released when the volume
    255   // handle is closed, and this is done by the ScopedHandle above.
    256   BOOL locked = DeviceIoControl(volume_handle, FSCTL_LOCK_VOLUME,
    257                                 NULL, 0, NULL, 0, &bytes_returned, NULL);
    258   UMA_HISTOGRAM_ENUMERATION("StorageMonitor.EjectWinLock",
    259                             LOCK_ATTEMPT, NUM_LOCK_OUTCOMES);
    260   if (!locked) {
    261     UMA_HISTOGRAM_ENUMERATION("StorageMonitor.EjectWinLock",
    262                               iteration == 0 ? LOCK_TIMEOUT : LOCK_TIMEOUT2,
    263                               NUM_LOCK_OUTCOMES);
    264     const int kNumLockRetries = 1;
    265     const base::TimeDelta kLockRetryInterval =
    266         base::TimeDelta::FromMilliseconds(500);
    267     if (iteration < kNumLockRetries) {
    268       // Try again -- the lock may have been a transient one. This happens on
    269       // things like AV disk lock for some reason, or another process
    270       // transient disk lock.
    271       task_runner->PostDelayedTask(
    272           FROM_HERE,
    273           base::Bind(&EjectDeviceInThreadPool,
    274                      device, callback, task_runner, iteration + 1),
    275           kLockRetryInterval);
    276       return;
    277     }
    278 
    279     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    280                             base::Bind(callback, StorageMonitor::EJECT_IN_USE));
    281     return;
    282   }
    283 
    284   // Unmount the device from the filesystem -- this will remove it from
    285   // the file picker, drive enumerations, etc.
    286   BOOL dismounted = DeviceIoControl(volume_handle, FSCTL_DISMOUNT_VOLUME,
    287                                     NULL, 0, NULL, 0, &bytes_returned, NULL);
    288 
    289   // Reached if we acquired a lock, but could not dismount. This might
    290   // occur if another process unmounted without locking. Call this OK,
    291   // since the volume is now unreachable.
    292   if (!dismounted) {
    293     DeviceIoControl(volume_handle, FSCTL_UNLOCK_VOLUME,
    294                     NULL, 0, NULL, 0, &bytes_returned, NULL);
    295     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    296                             base::Bind(callback, StorageMonitor::EJECT_OK));
    297     return;
    298   }
    299 
    300   PREVENT_MEDIA_REMOVAL pmr_buffer;
    301   pmr_buffer.PreventMediaRemoval = FALSE;
    302   // Mark the device as safe to remove.
    303   if (!DeviceIoControl(volume_handle, IOCTL_STORAGE_MEDIA_REMOVAL,
    304                        &pmr_buffer, sizeof(PREVENT_MEDIA_REMOVAL),
    305                        NULL, 0, &bytes_returned, NULL)) {
    306     BrowserThread::PostTask(
    307         BrowserThread::UI, FROM_HERE,
    308         base::Bind(callback, StorageMonitor::EJECT_FAILURE));
    309     return;
    310   }
    311 
    312   // Physically eject or soft-eject the device.
    313   if (!DeviceIoControl(volume_handle, IOCTL_STORAGE_EJECT_MEDIA,
    314                        NULL, 0, NULL, 0, &bytes_returned, NULL)) {
    315     BrowserThread::PostTask(
    316         BrowserThread::UI, FROM_HERE,
    317         base::Bind(callback, StorageMonitor::EJECT_FAILURE));
    318     return;
    319   }
    320 
    321   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    322                           base::Bind(callback, StorageMonitor::EJECT_OK));
    323 }
    324 
    325 }  // namespace
    326 
    327 const int kWorkerPoolNumThreads = 3;
    328 const char* kWorkerPoolNamePrefix = "DeviceInfoPool";
    329 
    330 VolumeMountWatcherWin::VolumeMountWatcherWin()
    331     : device_info_worker_pool_(new base::SequencedWorkerPool(
    332           kWorkerPoolNumThreads, kWorkerPoolNamePrefix)),
    333       weak_factory_(this),
    334       notifications_(NULL) {
    335   task_runner_ =
    336       device_info_worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior(
    337           device_info_worker_pool_->GetSequenceToken(),
    338           base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
    339 }
    340 
    341 // static
    342 base::FilePath VolumeMountWatcherWin::DriveNumberToFilePath(int drive_number) {
    343   if (drive_number < 0 || drive_number > 25)
    344     return base::FilePath();
    345   base::string16 path(L"_:\\");
    346   path[0] = L'A' + drive_number;
    347   return base::FilePath(path);
    348 }
    349 
    350 // In order to get all the weak pointers created on the UI thread, and doing
    351 // synchronous Windows calls in the worker pool, this kicks off a chain of
    352 // events which will
    353 // a) Enumerate attached devices
    354 // b) Create weak pointers for which to send completion signals from
    355 // c) Retrieve metadata on the volumes and then
    356 // d) Notify that metadata to listeners.
    357 void VolumeMountWatcherWin::Init() {
    358   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    359 
    360   // When VolumeMountWatcherWin is created, the message pumps are not running
    361   // so a posted task from the constructor would never run. Therefore, do all
    362   // the initializations here.
    363   base::PostTaskAndReplyWithResult(task_runner_, FROM_HERE,
    364       GetAttachedDevicesCallback(),
    365       base::Bind(&VolumeMountWatcherWin::AddDevicesOnUIThread,
    366                  weak_factory_.GetWeakPtr()));
    367 }
    368 
    369 void VolumeMountWatcherWin::AddDevicesOnUIThread(
    370     std::vector<base::FilePath> removable_devices) {
    371   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    372 
    373   for (size_t i = 0; i < removable_devices.size(); i++) {
    374     if (ContainsKey(pending_device_checks_, removable_devices[i]))
    375       continue;
    376     pending_device_checks_.insert(removable_devices[i]);
    377     task_runner_->PostTask(
    378         FROM_HERE,
    379         base::Bind(&VolumeMountWatcherWin::RetrieveInfoForDeviceAndAdd,
    380                    removable_devices[i], GetDeviceDetailsCallback(),
    381                    weak_factory_.GetWeakPtr()));
    382   }
    383 }
    384 
    385 // static
    386 void VolumeMountWatcherWin::RetrieveInfoForDeviceAndAdd(
    387     const base::FilePath& device_path,
    388     const GetDeviceDetailsCallbackType& get_device_details_callback,
    389     base::WeakPtr<VolumeMountWatcherWin> volume_watcher) {
    390   StorageInfo info;
    391   if (!get_device_details_callback.Run(device_path, &info)) {
    392     BrowserThread::PostTask(
    393         BrowserThread::UI, FROM_HERE,
    394         base::Bind(&VolumeMountWatcherWin::DeviceCheckComplete,
    395                    volume_watcher, device_path));
    396     return;
    397   }
    398 
    399   BrowserThread::PostTask(
    400       BrowserThread::UI, FROM_HERE,
    401       base::Bind(&VolumeMountWatcherWin::HandleDeviceAttachEventOnUIThread,
    402                  volume_watcher, device_path, info));
    403 }
    404 
    405 void VolumeMountWatcherWin::DeviceCheckComplete(
    406     const base::FilePath& device_path) {
    407   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    408   pending_device_checks_.erase(device_path);
    409 
    410   if (pending_device_checks_.size() == 0) {
    411     if (notifications_)
    412       notifications_->MarkInitialized();
    413   }
    414 }
    415 
    416 VolumeMountWatcherWin::GetAttachedDevicesCallbackType
    417     VolumeMountWatcherWin::GetAttachedDevicesCallback() const {
    418   return base::Bind(&GetAttachedDevices);
    419 }
    420 
    421 VolumeMountWatcherWin::GetDeviceDetailsCallbackType
    422     VolumeMountWatcherWin::GetDeviceDetailsCallback() const {
    423   return base::Bind(&GetDeviceDetails);
    424 }
    425 
    426 bool VolumeMountWatcherWin::GetDeviceInfo(const base::FilePath& device_path,
    427                                           StorageInfo* info) const {
    428   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    429   DCHECK(info);
    430   base::FilePath path(device_path);
    431   MountPointDeviceMetadataMap::const_iterator iter =
    432       device_metadata_.find(path);
    433   while (iter == device_metadata_.end() && path.DirName() != path) {
    434     path = path.DirName();
    435     iter = device_metadata_.find(path);
    436   }
    437 
    438   if (iter == device_metadata_.end())
    439     return false;
    440 
    441   *info = iter->second;
    442   return true;
    443 }
    444 
    445 void VolumeMountWatcherWin::OnWindowMessage(UINT event_type, LPARAM data) {
    446   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    447   switch (event_type) {
    448     case DBT_DEVICEARRIVAL: {
    449       if (IsLogicalVolumeStructure(data)) {
    450         DWORD unitmask = GetVolumeBitMaskFromBroadcastHeader(data);
    451         std::vector<base::FilePath> paths;
    452         for (int i = 0; unitmask; ++i, unitmask >>= 1) {
    453           if (!(unitmask & 0x01))
    454             continue;
    455           paths.push_back(DriveNumberToFilePath(i));
    456         }
    457         AddDevicesOnUIThread(paths);
    458       }
    459       break;
    460     }
    461     case DBT_DEVICEREMOVECOMPLETE: {
    462       if (IsLogicalVolumeStructure(data)) {
    463         DWORD unitmask = GetVolumeBitMaskFromBroadcastHeader(data);
    464         for (int i = 0; unitmask; ++i, unitmask >>= 1) {
    465           if (!(unitmask & 0x01))
    466             continue;
    467           HandleDeviceDetachEventOnUIThread(DriveNumberToFilePath(i).value());
    468         }
    469       }
    470       break;
    471     }
    472   }
    473 }
    474 
    475 void VolumeMountWatcherWin::OnMediaChange(WPARAM wparam, LPARAM lparam) {
    476   if (lparam == SHCNE_MEDIAINSERTED || lparam == SHCNE_MEDIAREMOVED) {
    477     struct _ITEMIDLIST* pidl = *reinterpret_cast<struct _ITEMIDLIST**>(
    478         wparam);
    479     wchar_t sPath[MAX_PATH];
    480     if (!SHGetPathFromIDList(pidl, sPath)) {
    481       DVLOG(1) << "MediaInserted: SHGetPathFromIDList failed";
    482       return;
    483     }
    484     switch (lparam) {
    485       case SHCNE_MEDIAINSERTED: {
    486         std::vector<base::FilePath> paths;
    487         paths.push_back(base::FilePath(sPath));
    488         AddDevicesOnUIThread(paths);
    489         break;
    490       }
    491       case SHCNE_MEDIAREMOVED: {
    492         HandleDeviceDetachEventOnUIThread(sPath);
    493         break;
    494       }
    495     }
    496   }
    497 }
    498 
    499 void VolumeMountWatcherWin::SetNotifications(
    500     StorageMonitor::Receiver* notifications) {
    501   notifications_ = notifications;
    502 }
    503 
    504 VolumeMountWatcherWin::~VolumeMountWatcherWin() {
    505   weak_factory_.InvalidateWeakPtrs();
    506   device_info_worker_pool_->Shutdown();
    507 }
    508 
    509 void VolumeMountWatcherWin::HandleDeviceAttachEventOnUIThread(
    510     const base::FilePath& device_path,
    511     const StorageInfo& info) {
    512   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    513 
    514   device_metadata_[device_path] = info;
    515 
    516   if (notifications_)
    517     notifications_->ProcessAttach(info);
    518 
    519   DeviceCheckComplete(device_path);
    520 }
    521 
    522 void VolumeMountWatcherWin::HandleDeviceDetachEventOnUIThread(
    523     const base::string16& device_location) {
    524   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    525 
    526   MountPointDeviceMetadataMap::const_iterator device_info =
    527       device_metadata_.find(base::FilePath(device_location));
    528   // If the device isn't type removable (like a CD), it won't be there.
    529   if (device_info == device_metadata_.end())
    530     return;
    531 
    532   if (notifications_)
    533     notifications_->ProcessDetach(device_info->second.device_id());
    534   device_metadata_.erase(device_info);
    535 }
    536 
    537 void VolumeMountWatcherWin::EjectDevice(
    538     const std::string& device_id,
    539     base::Callback<void(StorageMonitor::EjectStatus)> callback) {
    540   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    541   base::FilePath device = MediaStorageUtil::FindDevicePathById(device_id);
    542   if (device.empty()) {
    543     callback.Run(StorageMonitor::EJECT_FAILURE);
    544     return;
    545   }
    546   if (device_metadata_.erase(device) == 0) {
    547     callback.Run(StorageMonitor::EJECT_FAILURE);
    548     return;
    549   }
    550 
    551   task_runner_->PostTask(
    552       FROM_HERE,
    553       base::Bind(&EjectDeviceInThreadPool, device, callback, task_runner_, 0));
    554 }
    555 
    556 }  // namespace storage_monitor
    557