Home | History | Annotate | Download | only in storage_monitor
      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 // StorageMonitorLinux implementation.
      6 
      7 #include "chrome/browser/storage_monitor/storage_monitor_linux.h"
      8 
      9 #include <mntent.h>
     10 #include <stdio.h>
     11 
     12 #include <list>
     13 
     14 #include "base/basictypes.h"
     15 #include "base/bind.h"
     16 #include "base/metrics/histogram.h"
     17 #include "base/process/kill.h"
     18 #include "base/process/launch.h"
     19 #include "base/stl_util.h"
     20 #include "base/strings/string_number_conversions.h"
     21 #include "base/strings/string_util.h"
     22 #include "base/strings/utf_string_conversions.h"
     23 #include "chrome/browser/storage_monitor/media_storage_util.h"
     24 #include "chrome/browser/storage_monitor/media_transfer_protocol_device_observer_linux.h"
     25 #include "chrome/browser/storage_monitor/removable_device_constants.h"
     26 #include "chrome/browser/storage_monitor/storage_info.h"
     27 #include "chrome/browser/storage_monitor/udev_util_linux.h"
     28 #include "device/media_transfer_protocol/media_transfer_protocol_manager.h"
     29 
     30 using content::BrowserThread;
     31 typedef MtabWatcherLinux::MountPointDeviceMap MountPointDeviceMap;
     32 
     33 namespace {
     34 
     35 // udev device property constants.
     36 const char kBlockSubsystemKey[] = "block";
     37 const char kDiskDeviceTypeKey[] = "disk";
     38 const char kFsUUID[] = "ID_FS_UUID";
     39 const char kLabel[] = "ID_FS_LABEL";
     40 const char kModel[] = "ID_MODEL";
     41 const char kModelID[] = "ID_MODEL_ID";
     42 const char kRemovableSysAttr[] = "removable";
     43 const char kSerialShort[] = "ID_SERIAL_SHORT";
     44 const char kSizeSysAttr[] = "size";
     45 const char kVendor[] = "ID_VENDOR";
     46 const char kVendorID[] = "ID_VENDOR_ID";
     47 
     48 // Construct a device id using label or manufacturer (vendor and model) details.
     49 std::string MakeDeviceUniqueId(struct udev_device* device) {
     50   std::string uuid = GetUdevDevicePropertyValue(device, kFsUUID);
     51   // Keep track of device uuid, to see how often we receive empty uuid values.
     52   UMA_HISTOGRAM_BOOLEAN(
     53       "RemovableDeviceNotificationsLinux.device_file_system_uuid_available",
     54       !uuid.empty());
     55 
     56   if (!uuid.empty())
     57     return kFSUniqueIdPrefix + uuid;
     58 
     59   // If one of the vendor, model, serial information is missing, its value
     60   // in the string is empty.
     61   // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
     62   // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
     63   std::string vendor = GetUdevDevicePropertyValue(device, kVendorID);
     64   std::string model = GetUdevDevicePropertyValue(device, kModelID);
     65   std::string serial_short = GetUdevDevicePropertyValue(device,
     66                                                         kSerialShort);
     67   if (vendor.empty() && model.empty() && serial_short.empty())
     68     return std::string();
     69 
     70   return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
     71 }
     72 
     73 // Records GetDeviceInfo result on destruction, to see how often we fail to get
     74 // device details.
     75 class ScopedGetDeviceInfoResultRecorder {
     76  public:
     77   ScopedGetDeviceInfoResultRecorder() : result_(false) {}
     78   ~ScopedGetDeviceInfoResultRecorder() {
     79     UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
     80                           result_);
     81   }
     82 
     83   void set_result(bool result) {
     84     result_ = result;
     85   }
     86 
     87  private:
     88   bool result_;
     89 
     90   DISALLOW_COPY_AND_ASSIGN(ScopedGetDeviceInfoResultRecorder);
     91 };
     92 
     93 // Returns the storage partition size of the device specified by |device_path|.
     94 // If the requested information is unavailable, returns 0.
     95 uint64 GetDeviceStorageSize(const base::FilePath& device_path,
     96                             struct udev_device* device) {
     97   // sysfs provides the device size in units of 512-byte blocks.
     98   const std::string partition_size = udev_device_get_sysattr_value(
     99       device, kSizeSysAttr);
    100 
    101   // Keep track of device size, to see how often this information is
    102   // unavailable.
    103   UMA_HISTOGRAM_BOOLEAN(
    104       "RemovableDeviceNotificationsLinux.device_partition_size_available",
    105       !partition_size.empty());
    106 
    107   uint64 total_size_in_bytes = 0;
    108   if (!base::StringToUint64(partition_size, &total_size_in_bytes))
    109     return 0;
    110   return (total_size_in_bytes <= kuint64max / 512) ?
    111       total_size_in_bytes * 512 : 0;
    112 }
    113 
    114 // Gets the device information using udev library.
    115 scoped_ptr<StorageInfo> GetDeviceInfo(const base::FilePath& device_path,
    116                                       const base::FilePath& mount_point) {
    117   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    118   DCHECK(!device_path.empty());
    119 
    120   scoped_ptr<StorageInfo> storage_info;
    121 
    122   ScopedGetDeviceInfoResultRecorder results_recorder;
    123 
    124   ScopedUdevObject udev_obj(udev_new());
    125   if (!udev_obj.get())
    126     return storage_info.Pass();
    127 
    128   struct stat device_stat;
    129   if (stat(device_path.value().c_str(), &device_stat) < 0)
    130     return storage_info.Pass();
    131 
    132   char device_type;
    133   if (S_ISCHR(device_stat.st_mode))
    134     device_type = 'c';
    135   else if (S_ISBLK(device_stat.st_mode))
    136     device_type = 'b';
    137   else
    138     return storage_info.Pass();  // Not a supported type.
    139 
    140   ScopedUdevDeviceObject device(
    141       udev_device_new_from_devnum(udev_obj.get(), device_type,
    142                                   device_stat.st_rdev));
    143   if (!device.get())
    144     return storage_info.Pass();
    145 
    146   base::string16 volume_label =
    147       UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kLabel));
    148   base::string16 vendor_name =
    149       UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kVendor));
    150   base::string16 model_name =
    151       UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kModel));
    152 
    153   std::string unique_id = MakeDeviceUniqueId(device.get());
    154 
    155   // Keep track of device info details to see how often we get invalid values.
    156   MediaStorageUtil::RecordDeviceInfoHistogram(true, unique_id, volume_label);
    157 
    158   const char* value =
    159       udev_device_get_sysattr_value(device.get(), kRemovableSysAttr);
    160   if (!value) {
    161     // |parent_device| is owned by |device| and does not need to be cleaned
    162     // up.
    163     struct udev_device* parent_device =
    164         udev_device_get_parent_with_subsystem_devtype(device.get(),
    165                                                       kBlockSubsystemKey,
    166                                                       kDiskDeviceTypeKey);
    167     value = udev_device_get_sysattr_value(parent_device, kRemovableSysAttr);
    168   }
    169   const bool is_removable = (value && atoi(value) == 1);
    170 
    171   StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
    172   if (is_removable) {
    173     if (MediaStorageUtil::HasDcim(mount_point))
    174       type = StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
    175     else
    176       type = StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
    177   }
    178 
    179   results_recorder.set_result(true);
    180 
    181   storage_info.reset(new StorageInfo(
    182       StorageInfo::MakeDeviceId(type, unique_id),
    183       base::string16(),
    184       mount_point.value(),
    185       volume_label,
    186       vendor_name,
    187       model_name,
    188       GetDeviceStorageSize(device_path, device.get())));
    189   return storage_info.Pass();
    190 }
    191 
    192 MtabWatcherLinux* CreateMtabWatcherLinuxOnFileThread(
    193     const base::FilePath& mtab_path,
    194     base::WeakPtr<MtabWatcherLinux::Delegate> delegate) {
    195   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    196   // Owned by caller.
    197   return new MtabWatcherLinux(mtab_path, delegate);
    198 }
    199 
    200 StorageMonitor::EjectStatus EjectPathOnFileThread(
    201     const base::FilePath& path,
    202     const base::FilePath& device) {
    203   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    204 
    205   // Note: Linux LSB says umount should exist in /bin.
    206   static const char kUmountBinary[] = "/bin/umount";
    207   std::vector<std::string> command;
    208   command.push_back(kUmountBinary);
    209   command.push_back(path.value());
    210 
    211   base::LaunchOptions options;
    212   base::ProcessHandle handle;
    213   if (!base::LaunchProcess(command, options, &handle))
    214     return StorageMonitor::EJECT_FAILURE;
    215 
    216   int exit_code = -1;
    217   if (!base::WaitForExitCodeWithTimeout(handle, &exit_code,
    218       base::TimeDelta::FromMilliseconds(3000))) {
    219     base::KillProcess(handle, -1, false);
    220     base::EnsureProcessTerminated(handle);
    221     return StorageMonitor::EJECT_FAILURE;
    222   }
    223 
    224   // TODO(gbillock): Make sure this is found in documentation
    225   // somewhere. Experimentally it seems to hold that exit code
    226   // 1 means device is in use.
    227   if (exit_code == 1)
    228     return StorageMonitor::EJECT_IN_USE;
    229   if (exit_code != 0)
    230     return StorageMonitor::EJECT_FAILURE;
    231 
    232   return StorageMonitor::EJECT_OK;
    233 }
    234 
    235 }  // namespace
    236 
    237 StorageMonitorLinux::StorageMonitorLinux(const base::FilePath& path)
    238     : mtab_path_(path),
    239       get_device_info_callback_(base::Bind(&GetDeviceInfo)),
    240       weak_ptr_factory_(this) {
    241   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    242 }
    243 
    244 StorageMonitorLinux::~StorageMonitorLinux() {
    245   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    246 }
    247 
    248 void StorageMonitorLinux::Init() {
    249   DCHECK(!mtab_path_.empty());
    250 
    251   BrowserThread::PostTaskAndReplyWithResult(
    252       BrowserThread::FILE, FROM_HERE,
    253       base::Bind(&CreateMtabWatcherLinuxOnFileThread,
    254                  mtab_path_,
    255                  weak_ptr_factory_.GetWeakPtr()),
    256       base::Bind(&StorageMonitorLinux::OnMtabWatcherCreated,
    257                  weak_ptr_factory_.GetWeakPtr()));
    258 
    259   if (!media_transfer_protocol_manager_) {
    260     scoped_refptr<base::MessageLoopProxy> loop_proxy =
    261         BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE);
    262     media_transfer_protocol_manager_.reset(
    263         device::MediaTransferProtocolManager::Initialize(loop_proxy));
    264   }
    265 
    266   media_transfer_protocol_device_observer_.reset(
    267       new MediaTransferProtocolDeviceObserverLinux(
    268           receiver(), media_transfer_protocol_manager_.get()));
    269 }
    270 
    271 bool StorageMonitorLinux::GetStorageInfoForPath(
    272     const base::FilePath& path,
    273     StorageInfo* device_info) const {
    274   DCHECK(device_info);
    275   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    276 
    277   // TODO(thestig) |media_transfer_protocol_device_observer_| should always be
    278   // valid.
    279   if (media_transfer_protocol_device_observer_ &&
    280       media_transfer_protocol_device_observer_->GetStorageInfoForPath(
    281           path, device_info)) {
    282     return true;
    283   }
    284 
    285   if (!path.IsAbsolute())
    286     return false;
    287 
    288   base::FilePath current = path;
    289   while (!ContainsKey(mount_info_map_, current) && current != current.DirName())
    290     current = current.DirName();
    291 
    292   MountMap::const_iterator mount_info = mount_info_map_.find(current);
    293   if (mount_info == mount_info_map_.end())
    294     return false;
    295   *device_info = mount_info->second.storage_info;
    296   return true;
    297 }
    298 
    299 device::MediaTransferProtocolManager*
    300 StorageMonitorLinux::media_transfer_protocol_manager() {
    301   return media_transfer_protocol_manager_.get();
    302 }
    303 
    304 void StorageMonitorLinux::SetGetDeviceInfoCallbackForTest(
    305     const GetDeviceInfoCallback& get_device_info_callback) {
    306   get_device_info_callback_ = get_device_info_callback;
    307 }
    308 
    309 void StorageMonitorLinux::SetMediaTransferProtocolManagerForTest(
    310     device::MediaTransferProtocolManager* test_manager) {
    311   DCHECK(!media_transfer_protocol_manager_);
    312   media_transfer_protocol_manager_.reset(test_manager);
    313 }
    314 
    315 void StorageMonitorLinux::EjectDevice(
    316     const std::string& device_id,
    317     base::Callback<void(EjectStatus)> callback) {
    318   StorageInfo::Type type;
    319   if (!StorageInfo::CrackDeviceId(device_id, &type, NULL)) {
    320     callback.Run(EJECT_FAILURE);
    321     return;
    322   }
    323 
    324   if (type == StorageInfo::MTP_OR_PTP) {
    325     media_transfer_protocol_device_observer_->EjectDevice(device_id, callback);
    326     return;
    327   }
    328 
    329   // Find the mount point for the given device ID.
    330   base::FilePath path;
    331   base::FilePath device;
    332   for (MountMap::iterator mount_info = mount_info_map_.begin();
    333        mount_info != mount_info_map_.end(); ++mount_info) {
    334     if (mount_info->second.storage_info.device_id() == device_id) {
    335       path = mount_info->first;
    336       device = mount_info->second.mount_device;
    337       mount_info_map_.erase(mount_info);
    338       break;
    339     }
    340   }
    341 
    342   if (path.empty()) {
    343     callback.Run(EJECT_NO_SUCH_DEVICE);
    344     return;
    345   }
    346 
    347   receiver()->ProcessDetach(device_id);
    348 
    349   BrowserThread::PostTaskAndReplyWithResult(
    350       BrowserThread::FILE, FROM_HERE,
    351       base::Bind(&EjectPathOnFileThread, path, device),
    352       callback);
    353 }
    354 
    355 void StorageMonitorLinux::OnMtabWatcherCreated(MtabWatcherLinux* watcher) {
    356   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    357   mtab_watcher_.reset(watcher);
    358 }
    359 
    360 void StorageMonitorLinux::UpdateMtab(const MountPointDeviceMap& new_mtab) {
    361   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    362 
    363   // Check existing mtab entries for unaccounted mount points.
    364   // These mount points must have been removed in the new mtab.
    365   std::list<base::FilePath> mount_points_to_erase;
    366   std::list<base::FilePath> multiple_mounted_devices_needing_reattachment;
    367   for (MountMap::const_iterator old_iter = mount_info_map_.begin();
    368        old_iter != mount_info_map_.end(); ++old_iter) {
    369     const base::FilePath& mount_point = old_iter->first;
    370     const base::FilePath& mount_device = old_iter->second.mount_device;
    371     MountPointDeviceMap::const_iterator new_iter = new_mtab.find(mount_point);
    372     // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
    373     // |mount_point|.
    374     if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
    375       MountPriorityMap::iterator priority =
    376           mount_priority_map_.find(mount_device);
    377       DCHECK(priority != mount_priority_map_.end());
    378       ReferencedMountPoint::const_iterator has_priority =
    379           priority->second.find(mount_point);
    380       if (StorageInfo::IsRemovableDevice(
    381               old_iter->second.storage_info.device_id())) {
    382         DCHECK(has_priority != priority->second.end());
    383         if (has_priority->second) {
    384           receiver()->ProcessDetach(old_iter->second.storage_info.device_id());
    385         }
    386         if (priority->second.size() > 1)
    387           multiple_mounted_devices_needing_reattachment.push_back(mount_device);
    388       }
    389       priority->second.erase(mount_point);
    390       if (priority->second.empty())
    391         mount_priority_map_.erase(mount_device);
    392       mount_points_to_erase.push_back(mount_point);
    393     }
    394   }
    395 
    396   // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
    397   // using the iterator is slightly more efficient, but more tricky, since
    398   // calling std::map::erase() on an iterator invalidates it.
    399   for (std::list<base::FilePath>::const_iterator it =
    400            mount_points_to_erase.begin();
    401        it != mount_points_to_erase.end();
    402        ++it) {
    403     mount_info_map_.erase(*it);
    404   }
    405 
    406   // For any multiply mounted device where the mount that we had notified
    407   // got detached, send a notification of attachment for one of the other
    408   // mount points.
    409   for (std::list<base::FilePath>::const_iterator it =
    410            multiple_mounted_devices_needing_reattachment.begin();
    411        it != multiple_mounted_devices_needing_reattachment.end();
    412        ++it) {
    413     ReferencedMountPoint::iterator first_mount_point_info =
    414         mount_priority_map_.find(*it)->second.begin();
    415     const base::FilePath& mount_point = first_mount_point_info->first;
    416     first_mount_point_info->second = true;
    417 
    418     const StorageInfo& mount_info =
    419         mount_info_map_.find(mount_point)->second.storage_info;
    420     DCHECK(StorageInfo::IsRemovableDevice(mount_info.device_id()));
    421     receiver()->ProcessAttach(mount_info);
    422   }
    423 
    424   // Check new mtab entries against existing ones.
    425   for (MountPointDeviceMap::const_iterator new_iter = new_mtab.begin();
    426        new_iter != new_mtab.end(); ++new_iter) {
    427     const base::FilePath& mount_point = new_iter->first;
    428     const base::FilePath& mount_device = new_iter->second;
    429     MountMap::iterator old_iter = mount_info_map_.find(mount_point);
    430     if (old_iter == mount_info_map_.end() ||
    431         old_iter->second.mount_device != mount_device) {
    432       // New mount point found or an existing mount point found with a new
    433       // device.
    434       if (IsDeviceAlreadyMounted(mount_device)) {
    435         HandleDeviceMountedMultipleTimes(mount_device, mount_point);
    436       } else {
    437         BrowserThread::PostTaskAndReplyWithResult(
    438             BrowserThread::FILE, FROM_HERE,
    439             base::Bind(get_device_info_callback_, mount_device, mount_point),
    440             base::Bind(&StorageMonitorLinux::AddNewMount,
    441                        weak_ptr_factory_.GetWeakPtr(),
    442                        mount_device));
    443       }
    444     }
    445   }
    446 
    447   // Note: relies on scheduled tasks on the file thread being sequential. This
    448   // block needs to follow the for loop, so that the DoNothing call on the FILE
    449   // thread happens after the scheduled metadata retrievals, meaning that the
    450   // reply callback will then happen after all the AddNewMount calls.
    451   if (!IsInitialized()) {
    452     BrowserThread::PostTaskAndReply(
    453         BrowserThread::FILE, FROM_HERE,
    454         base::Bind(&base::DoNothing),
    455         base::Bind(&StorageMonitorLinux::MarkInitialized,
    456                    weak_ptr_factory_.GetWeakPtr()));
    457   }
    458 }
    459 
    460 bool StorageMonitorLinux::IsDeviceAlreadyMounted(
    461     const base::FilePath& mount_device) const {
    462   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    463   return ContainsKey(mount_priority_map_, mount_device);
    464 }
    465 
    466 void StorageMonitorLinux::HandleDeviceMountedMultipleTimes(
    467     const base::FilePath& mount_device,
    468     const base::FilePath& mount_point) {
    469   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    470 
    471   MountPriorityMap::iterator priority = mount_priority_map_.find(mount_device);
    472   DCHECK(priority != mount_priority_map_.end());
    473   const base::FilePath& other_mount_point = priority->second.begin()->first;
    474   priority->second[mount_point] = false;
    475   mount_info_map_[mount_point] =
    476       mount_info_map_.find(other_mount_point)->second;
    477 }
    478 
    479 void StorageMonitorLinux::AddNewMount(const base::FilePath& mount_device,
    480                                       scoped_ptr<StorageInfo> storage_info) {
    481   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    482 
    483   if (!storage_info)
    484     return;
    485 
    486   DCHECK(!storage_info->device_id().empty());
    487 
    488   bool removable = StorageInfo::IsRemovableDevice(storage_info->device_id());
    489   const base::FilePath mount_point(storage_info->location());
    490 
    491   MountPointInfo mount_point_info;
    492   mount_point_info.mount_device = mount_device;
    493   mount_point_info.storage_info = *storage_info;
    494   mount_info_map_[mount_point] = mount_point_info;
    495   mount_priority_map_[mount_device][mount_point] = removable;
    496   receiver()->ProcessAttach(*storage_info);
    497 }
    498 
    499 StorageMonitor* StorageMonitor::Create() {
    500   const base::FilePath kDefaultMtabPath("/etc/mtab");
    501   return new StorageMonitorLinux(kDefaultMtabPath);
    502 }
    503