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/storage_monitor_chromeos.h"
      6 
      7 #include "base/files/file_path.h"
      8 #include "base/logging.h"
      9 #include "base/stl_util.h"
     10 #include "base/strings/string16.h"
     11 #include "base/strings/string_number_conversions.h"
     12 #include "base/strings/string_util.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "components/storage_monitor/media_storage_util.h"
     15 #include "components/storage_monitor/media_transfer_protocol_device_observer_linux.h"
     16 #include "components/storage_monitor/removable_device_constants.h"
     17 #include "content/public/browser/browser_thread.h"
     18 #include "device/media_transfer_protocol/media_transfer_protocol_manager.h"
     19 
     20 using chromeos::disks::DiskMountManager;
     21 
     22 namespace storage_monitor {
     23 
     24 namespace {
     25 
     26 // Constructs a device id using uuid or manufacturer (vendor and product) id
     27 // details.
     28 std::string MakeDeviceUniqueId(const DiskMountManager::Disk& disk) {
     29   std::string uuid = disk.fs_uuid();
     30   if (!uuid.empty())
     31     return kFSUniqueIdPrefix + uuid;
     32 
     33   // If one of the vendor or product information is missing, its value in the
     34   // string is empty.
     35   // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialInfo
     36   // TODO(kmadhusu) Extract serial information for the disks and append it to
     37   // the device unique id.
     38   const std::string& vendor = disk.vendor_id();
     39   const std::string& product = disk.product_id();
     40   if (vendor.empty() && product.empty())
     41     return std::string();
     42   return kVendorModelSerialPrefix + vendor + ":" + product + ":";
     43 }
     44 
     45 // Returns true if the requested device is valid, else false. On success, fills
     46 // in |info|.
     47 bool GetDeviceInfo(const DiskMountManager::MountPointInfo& mount_info,
     48                    bool has_dcim,
     49                    StorageInfo* info) {
     50   DCHECK(info);
     51   std::string source_path = mount_info.source_path;
     52 
     53   const DiskMountManager::Disk* disk =
     54       DiskMountManager::GetInstance()->FindDiskBySourcePath(source_path);
     55   if (!disk || disk->device_type() == chromeos::DEVICE_TYPE_UNKNOWN)
     56     return false;
     57 
     58   std::string unique_id = MakeDeviceUniqueId(*disk);
     59   // Keep track of device uuid and label, to see how often we receive empty
     60   // values.
     61   base::string16 device_label = base::UTF8ToUTF16(disk->device_label());
     62   MediaStorageUtil::RecordDeviceInfoHistogram(true, unique_id, device_label);
     63   if (unique_id.empty())
     64     return false;
     65 
     66   StorageInfo::Type type = has_dcim ?
     67       StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM :
     68       StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
     69 
     70   *info = StorageInfo(StorageInfo::MakeDeviceId(type, unique_id),
     71                       mount_info.mount_path,
     72                       device_label,
     73                       base::UTF8ToUTF16(disk->vendor_name()),
     74                       base::UTF8ToUTF16(disk->product_name()),
     75                       disk->total_size_in_bytes());
     76   return true;
     77 }
     78 
     79 // Returns whether the mount point in |mount_info| is a media device or not.
     80 bool CheckMountedPathOnFileThread(
     81     const DiskMountManager::MountPointInfo& mount_info) {
     82   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
     83   return MediaStorageUtil::HasDcim(base::FilePath(mount_info.mount_path));
     84 }
     85 
     86 }  // namespace
     87 
     88 using content::BrowserThread;
     89 
     90 StorageMonitorCros::StorageMonitorCros()
     91     : weak_ptr_factory_(this) {
     92 }
     93 
     94 StorageMonitorCros::~StorageMonitorCros() {
     95   DiskMountManager* manager = DiskMountManager::GetInstance();
     96   if (manager) {
     97     manager->RemoveObserver(this);
     98   }
     99 }
    100 
    101 void StorageMonitorCros::Init() {
    102   DCHECK(DiskMountManager::GetInstance());
    103   DiskMountManager::GetInstance()->AddObserver(this);
    104   CheckExistingMountPoints();
    105 
    106   if (!media_transfer_protocol_manager_) {
    107     scoped_refptr<base::MessageLoopProxy> loop_proxy;
    108     media_transfer_protocol_manager_.reset(
    109         device::MediaTransferProtocolManager::Initialize(loop_proxy));
    110   }
    111 
    112   media_transfer_protocol_device_observer_.reset(
    113       new MediaTransferProtocolDeviceObserverLinux(
    114           receiver(), media_transfer_protocol_manager_.get()));
    115 }
    116 
    117 void StorageMonitorCros::CheckExistingMountPoints() {
    118   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    119   const DiskMountManager::MountPointMap& mount_point_map =
    120       DiskMountManager::GetInstance()->mount_points();
    121   for (DiskMountManager::MountPointMap::const_iterator it =
    122       mount_point_map.begin(); it != mount_point_map.end(); ++it) {
    123     BrowserThread::PostTaskAndReplyWithResult(
    124         BrowserThread::FILE, FROM_HERE,
    125         base::Bind(&CheckMountedPathOnFileThread, it->second),
    126         base::Bind(&StorageMonitorCros::AddMountedPath,
    127                    weak_ptr_factory_.GetWeakPtr(), it->second));
    128   }
    129 
    130   // Note: relies on scheduled tasks on the file thread being sequential. This
    131   // block needs to follow the for loop, so that the DoNothing call on the FILE
    132   // thread happens after the scheduled metadata retrievals, meaning that the
    133   // reply callback will then happen after all the AddNewMount calls.
    134   BrowserThread::PostTaskAndReply(
    135       BrowserThread::FILE, FROM_HERE,
    136       base::Bind(&base::DoNothing),
    137       base::Bind(&StorageMonitorCros::MarkInitialized,
    138                  weak_ptr_factory_.GetWeakPtr()));
    139 }
    140 
    141 void StorageMonitorCros::OnDiskEvent(DiskMountManager::DiskEvent event,
    142                                      const DiskMountManager::Disk* disk) {}
    143 
    144 void StorageMonitorCros::OnDeviceEvent(DiskMountManager::DeviceEvent event,
    145                                        const std::string& device_path) {}
    146 
    147 void StorageMonitorCros::OnMountEvent(
    148     DiskMountManager::MountEvent event,
    149     chromeos::MountError error_code,
    150     const DiskMountManager::MountPointInfo& mount_info) {
    151   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    152 
    153   // Ignore mount points that are not devices.
    154   if (mount_info.mount_type != chromeos::MOUNT_TYPE_DEVICE)
    155     return;
    156   // Ignore errors.
    157   if (error_code != chromeos::MOUNT_ERROR_NONE)
    158     return;
    159   if (mount_info.mount_condition != chromeos::disks::MOUNT_CONDITION_NONE)
    160     return;
    161 
    162   switch (event) {
    163     case DiskMountManager::MOUNTING: {
    164       if (ContainsKey(mount_map_, mount_info.mount_path)) {
    165         NOTREACHED();
    166         return;
    167       }
    168 
    169       BrowserThread::PostTaskAndReplyWithResult(
    170           BrowserThread::FILE, FROM_HERE,
    171           base::Bind(&CheckMountedPathOnFileThread, mount_info),
    172           base::Bind(&StorageMonitorCros::AddMountedPath,
    173                      weak_ptr_factory_.GetWeakPtr(), mount_info));
    174       break;
    175     }
    176     case DiskMountManager::UNMOUNTING: {
    177       MountMap::iterator it = mount_map_.find(mount_info.mount_path);
    178       if (it == mount_map_.end())
    179         return;
    180       receiver()->ProcessDetach(it->second.device_id());
    181       mount_map_.erase(it);
    182       break;
    183     }
    184   }
    185 }
    186 
    187 void StorageMonitorCros::OnFormatEvent(DiskMountManager::FormatEvent event,
    188                                        chromeos::FormatError error_code,
    189                                        const std::string& device_path) {}
    190 
    191 void StorageMonitorCros::SetMediaTransferProtocolManagerForTest(
    192     device::MediaTransferProtocolManager* test_manager) {
    193   DCHECK(!media_transfer_protocol_manager_);
    194   media_transfer_protocol_manager_.reset(test_manager);
    195 }
    196 
    197 
    198 bool StorageMonitorCros::GetStorageInfoForPath(
    199     const base::FilePath& path,
    200     StorageInfo* device_info) const {
    201   DCHECK(device_info);
    202 
    203   if (media_transfer_protocol_device_observer_->GetStorageInfoForPath(
    204           path, device_info)) {
    205     return true;
    206   }
    207 
    208   if (!path.IsAbsolute())
    209     return false;
    210 
    211   base::FilePath current = path;
    212   while (!ContainsKey(mount_map_, current.value()) &&
    213          current != current.DirName()) {
    214     current = current.DirName();
    215   }
    216 
    217   MountMap::const_iterator info_it = mount_map_.find(current.value());
    218   if (info_it == mount_map_.end())
    219     return false;
    220 
    221   *device_info = info_it->second;
    222   return true;
    223 }
    224 
    225 // Callback executed when the unmount call is run by DiskMountManager.
    226 // Forwards result to |EjectDevice| caller.
    227 void NotifyUnmountResult(
    228     base::Callback<void(StorageMonitor::EjectStatus)> callback,
    229     chromeos::MountError error_code) {
    230   if (error_code == chromeos::MOUNT_ERROR_NONE)
    231     callback.Run(StorageMonitor::EJECT_OK);
    232   else
    233     callback.Run(StorageMonitor::EJECT_FAILURE);
    234 }
    235 
    236 void StorageMonitorCros::EjectDevice(
    237     const std::string& device_id,
    238     base::Callback<void(EjectStatus)> callback) {
    239   StorageInfo::Type type;
    240   if (!StorageInfo::CrackDeviceId(device_id, &type, NULL)) {
    241     callback.Run(EJECT_FAILURE);
    242     return;
    243   }
    244 
    245   if (type == StorageInfo::MTP_OR_PTP) {
    246     media_transfer_protocol_device_observer_->EjectDevice(device_id, callback);
    247     return;
    248   }
    249 
    250   std::string mount_path;
    251   for (MountMap::const_iterator info_it = mount_map_.begin();
    252        info_it != mount_map_.end(); ++info_it) {
    253     if (info_it->second.device_id() == device_id)
    254       mount_path = info_it->first;
    255   }
    256 
    257   if (mount_path.empty()) {
    258     callback.Run(EJECT_NO_SUCH_DEVICE);
    259     return;
    260   }
    261 
    262   DiskMountManager* manager = DiskMountManager::GetInstance();
    263   if (!manager) {
    264     callback.Run(EJECT_FAILURE);
    265     return;
    266   }
    267 
    268   manager->UnmountPath(mount_path, chromeos::UNMOUNT_OPTIONS_NONE,
    269                        base::Bind(NotifyUnmountResult, callback));
    270 }
    271 
    272 device::MediaTransferProtocolManager*
    273 StorageMonitorCros::media_transfer_protocol_manager() {
    274   return media_transfer_protocol_manager_.get();
    275 }
    276 
    277 void StorageMonitorCros::AddMountedPath(
    278     const DiskMountManager::MountPointInfo& mount_info,
    279     bool has_dcim) {
    280   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    281 
    282   if (ContainsKey(mount_map_, mount_info.mount_path)) {
    283     // CheckExistingMountPointsOnUIThread() added the mount point information
    284     // in the map before the device attached handler is called. Therefore, an
    285     // entry for the device already exists in the map.
    286     return;
    287   }
    288 
    289   // Get the media device uuid and label if exists.
    290   StorageInfo info;
    291   if (!GetDeviceInfo(mount_info, has_dcim, &info))
    292     return;
    293 
    294   if (info.device_id().empty())
    295     return;
    296 
    297   mount_map_.insert(std::make_pair(mount_info.mount_path, info));
    298 
    299   receiver()->ProcessAttach(info);
    300 }
    301 
    302 StorageMonitor* StorageMonitor::CreateInternal() {
    303   return new StorageMonitorCros();
    304 }
    305 
    306 }  // namespace storage_monitor
    307