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