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