storage_monitor_linux.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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. // StorageMonitorLinux implementation.
  5. #include "components/storage_monitor/storage_monitor_linux.h"
  6. #include <mntent.h>
  7. #include <stdint.h>
  8. #include <stdio.h>
  9. #include <sys/stat.h>
  10. #include <limits>
  11. #include <list>
  12. #include <memory>
  13. #include <utility>
  14. #include <vector>
  15. #include "base/bind.h"
  16. #include "base/callback_helpers.h"
  17. #include "base/containers/contains.h"
  18. #include "base/metrics/histogram_macros.h"
  19. #include "base/process/kill.h"
  20. #include "base/process/launch.h"
  21. #include "base/process/process.h"
  22. #include "base/strings/string_number_conversions.h"
  23. #include "base/strings/string_util.h"
  24. #include "base/strings/utf_string_conversions.h"
  25. #include "base/task/task_runner_util.h"
  26. #include "base/task/thread_pool.h"
  27. #include "base/threading/scoped_blocking_call.h"
  28. #include "base/threading/sequenced_task_runner_handle.h"
  29. #include "components/storage_monitor/media_storage_util.h"
  30. #include "components/storage_monitor/removable_device_constants.h"
  31. #include "components/storage_monitor/storage_info.h"
  32. #include "components/storage_monitor/udev_util_linux.h"
  33. #include "device/udev_linux/scoped_udev.h"
  34. namespace storage_monitor {
  35. using MountPointDeviceMap = MtabWatcherLinux::MountPointDeviceMap;
  36. namespace {
  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. // Construct a device id using label or manufacturer (vendor and model) details.
  50. std::string MakeDeviceUniqueId(struct udev_device* device) {
  51. std::string uuid = device::UdevDeviceGetPropertyValue(device, kFsUUID);
  52. if (!uuid.empty())
  53. return kFSUniqueIdPrefix + uuid;
  54. // If one of the vendor, model, serial information is missing, its value
  55. // in the string is empty.
  56. // Format: VendorModelSerial:VendorInfo:ModelInfo:SerialShortInfo
  57. // E.g.: VendorModelSerial:Kn:DataTravel_12.10:8000000000006CB02CDB
  58. std::string vendor = device::UdevDeviceGetPropertyValue(device, kVendorID);
  59. std::string model = device::UdevDeviceGetPropertyValue(device, kModelID);
  60. std::string serial_short =
  61. device::UdevDeviceGetPropertyValue(device, kSerialShort);
  62. if (vendor.empty() && model.empty() && serial_short.empty())
  63. return std::string();
  64. return kVendorModelSerialPrefix + vendor + ":" + model + ":" + serial_short;
  65. }
  66. // Records GetDeviceInfo result on destruction, to see how often we fail to get
  67. // device details.
  68. class ScopedGetDeviceInfoResultRecorder {
  69. public:
  70. ScopedGetDeviceInfoResultRecorder() = default;
  71. ScopedGetDeviceInfoResultRecorder(const ScopedGetDeviceInfoResultRecorder&) =
  72. delete;
  73. ScopedGetDeviceInfoResultRecorder& operator=(
  74. const ScopedGetDeviceInfoResultRecorder&) = delete;
  75. ~ScopedGetDeviceInfoResultRecorder() {
  76. UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
  77. result_);
  78. }
  79. void set_result(bool result) {
  80. result_ = result;
  81. }
  82. private:
  83. bool result_ = false;
  84. };
  85. // Returns the storage partition size of the device specified by |device_path|.
  86. // If the requested information is unavailable, returns 0.
  87. uint64_t GetDeviceStorageSize(const base::FilePath& device_path,
  88. struct udev_device* device) {
  89. // sysfs provides the device size in units of 512-byte blocks.
  90. const std::string partition_size =
  91. device::UdevDeviceGetSysattrValue(device, kSizeSysAttr);
  92. uint64_t total_size_in_bytes = 0;
  93. if (!base::StringToUint64(partition_size, &total_size_in_bytes))
  94. return 0;
  95. return (total_size_in_bytes <= std::numeric_limits<uint64_t>::max() / 512)
  96. ? total_size_in_bytes * 512
  97. : 0;
  98. }
  99. // Gets the device information using udev library.
  100. std::unique_ptr<StorageInfo> GetDeviceInfo(const base::FilePath& device_path,
  101. const base::FilePath& mount_point) {
  102. base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
  103. base::BlockingType::MAY_BLOCK);
  104. DCHECK(!device_path.empty());
  105. std::unique_ptr<StorageInfo> storage_info;
  106. ScopedGetDeviceInfoResultRecorder results_recorder;
  107. device::ScopedUdevPtr udev_obj(device::udev_new());
  108. if (!udev_obj.get())
  109. return storage_info;
  110. struct stat device_stat;
  111. if (stat(device_path.value().c_str(), &device_stat) < 0)
  112. return storage_info;
  113. char device_type;
  114. if (S_ISCHR(device_stat.st_mode))
  115. device_type = 'c';
  116. else if (S_ISBLK(device_stat.st_mode))
  117. device_type = 'b';
  118. else
  119. return storage_info; // Not a supported type.
  120. device::ScopedUdevDevicePtr device(
  121. device::udev_device_new_from_devnum(udev_obj.get(), device_type,
  122. device_stat.st_rdev));
  123. if (!device.get())
  124. return storage_info;
  125. std::u16string volume_label = base::UTF8ToUTF16(
  126. device::UdevDeviceGetPropertyValue(device.get(), kLabel));
  127. std::u16string vendor_name = base::UTF8ToUTF16(
  128. device::UdevDeviceGetPropertyValue(device.get(), kVendor));
  129. std::u16string model_name = base::UTF8ToUTF16(
  130. device::UdevDeviceGetPropertyValue(device.get(), kModel));
  131. std::string unique_id = MakeDeviceUniqueId(device.get());
  132. const char* value =
  133. device::udev_device_get_sysattr_value(device.get(), kRemovableSysAttr);
  134. if (!value) {
  135. // |parent_device| is owned by |device| and does not need to be cleaned
  136. // up.
  137. struct udev_device* parent_device =
  138. device::udev_device_get_parent_with_subsystem_devtype(
  139. device.get(),
  140. kBlockSubsystemKey,
  141. kDiskDeviceTypeKey);
  142. value = device::udev_device_get_sysattr_value(parent_device,
  143. kRemovableSysAttr);
  144. }
  145. const bool is_removable = (value && atoi(value) == 1);
  146. StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
  147. if (is_removable) {
  148. type = MediaStorageUtil::HasDcim(mount_point)
  149. ? StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM
  150. : StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
  151. }
  152. results_recorder.set_result(true);
  153. storage_info = std::make_unique<StorageInfo>(
  154. StorageInfo::MakeDeviceId(type, unique_id), mount_point.value(),
  155. volume_label, vendor_name, model_name,
  156. GetDeviceStorageSize(device_path, device.get()));
  157. return storage_info;
  158. }
  159. // Runs |callback| with the |new_mtab| on |storage_monitor_task_runner|.
  160. void BounceMtabUpdateToStorageMonitorTaskRunner(
  161. scoped_refptr<base::SequencedTaskRunner> storage_monitor_task_runner,
  162. const MtabWatcherLinux::UpdateMtabCallback& callback,
  163. const MtabWatcherLinux::MountPointDeviceMap& new_mtab) {
  164. storage_monitor_task_runner->PostTask(FROM_HERE,
  165. base::BindOnce(callback, new_mtab));
  166. }
  167. std::unique_ptr<MtabWatcherLinux> CreateMtabWatcherLinuxOnMtabWatcherTaskRunner(
  168. const base::FilePath& mtab_path,
  169. scoped_refptr<base::SequencedTaskRunner> storage_monitor_task_runner,
  170. const MtabWatcherLinux::UpdateMtabCallback& callback) {
  171. return std::make_unique<MtabWatcherLinux>(
  172. mtab_path,
  173. base::BindRepeating(&BounceMtabUpdateToStorageMonitorTaskRunner,
  174. storage_monitor_task_runner, callback));
  175. }
  176. StorageMonitor::EjectStatus EjectPathOnBlockingTaskRunner(
  177. const base::FilePath& path,
  178. const base::FilePath& device) {
  179. base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
  180. base::BlockingType::MAY_BLOCK);
  181. // Note: Linux LSB says umount should exist in /bin.
  182. static const char kUmountBinary[] = "/bin/umount";
  183. std::vector<std::string> command{kUmountBinary, path.value()};
  184. base::LaunchOptions options;
  185. base::Process process = base::LaunchProcess(command, options);
  186. if (!process.IsValid())
  187. return StorageMonitor::EJECT_FAILURE;
  188. static constexpr auto kEjectTimeout = base::Seconds(3);
  189. int exit_code = -1;
  190. if (!process.WaitForExitWithTimeout(kEjectTimeout, &exit_code)) {
  191. process.Terminate(-1, false);
  192. base::EnsureProcessTerminated(std::move(process));
  193. return StorageMonitor::EJECT_FAILURE;
  194. }
  195. // TODO(gbillock): Make sure this is found in documentation
  196. // somewhere. Experimentally it seems to hold that exit code
  197. // 1 means device is in use.
  198. if (exit_code == 1)
  199. return StorageMonitor::EJECT_IN_USE;
  200. if (exit_code != 0)
  201. return StorageMonitor::EJECT_FAILURE;
  202. return StorageMonitor::EJECT_OK;
  203. }
  204. } // namespace
  205. StorageMonitorLinux::StorageMonitorLinux(const base::FilePath& path)
  206. : mtab_path_(path),
  207. get_device_info_callback_(base::BindRepeating(&GetDeviceInfo)),
  208. mtab_watcher_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
  209. {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {}
  210. StorageMonitorLinux::~StorageMonitorLinux() {
  211. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  212. mtab_watcher_task_runner_->DeleteSoon(FROM_HERE, mtab_watcher_.release());
  213. }
  214. void StorageMonitorLinux::Init() {
  215. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  216. DCHECK(!mtab_path_.empty());
  217. base::PostTaskAndReplyWithResult(
  218. mtab_watcher_task_runner_.get(), FROM_HERE,
  219. base::BindOnce(&CreateMtabWatcherLinuxOnMtabWatcherTaskRunner, mtab_path_,
  220. base::SequencedTaskRunnerHandle::Get(),
  221. base::BindRepeating(&StorageMonitorLinux::UpdateMtab,
  222. weak_ptr_factory_.GetWeakPtr())),
  223. base::BindOnce(&StorageMonitorLinux::OnMtabWatcherCreated,
  224. weak_ptr_factory_.GetWeakPtr()));
  225. }
  226. bool StorageMonitorLinux::GetStorageInfoForPath(
  227. const base::FilePath& path,
  228. StorageInfo* device_info) const {
  229. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  230. DCHECK(device_info);
  231. if (!path.IsAbsolute())
  232. return false;
  233. base::FilePath current = path;
  234. while (!base::Contains(mount_info_map_, current) &&
  235. current != current.DirName())
  236. current = current.DirName();
  237. auto mount_info = mount_info_map_.find(current);
  238. if (mount_info == mount_info_map_.end())
  239. return false;
  240. *device_info = mount_info->second.storage_info;
  241. return true;
  242. }
  243. void StorageMonitorLinux::SetGetDeviceInfoCallbackForTest(
  244. const GetDeviceInfoCallback& get_device_info_callback) {
  245. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  246. get_device_info_callback_ = get_device_info_callback;
  247. }
  248. void StorageMonitorLinux::EjectDevice(
  249. const std::string& device_id,
  250. base::OnceCallback<void(EjectStatus)> callback) {
  251. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  252. StorageInfo::Type type;
  253. if (!StorageInfo::CrackDeviceId(device_id, &type, nullptr)) {
  254. std::move(callback).Run(EJECT_FAILURE);
  255. return;
  256. }
  257. DCHECK_NE(type, StorageInfo::MTP_OR_PTP);
  258. // Find the mount point for the given device ID.
  259. base::FilePath path;
  260. base::FilePath device;
  261. for (auto mount_info = mount_info_map_.begin();
  262. mount_info != mount_info_map_.end(); ++mount_info) {
  263. if (mount_info->second.storage_info.device_id() == device_id) {
  264. path = mount_info->first;
  265. device = mount_info->second.mount_device;
  266. mount_info_map_.erase(mount_info);
  267. break;
  268. }
  269. }
  270. if (path.empty()) {
  271. std::move(callback).Run(EJECT_NO_SUCH_DEVICE);
  272. return;
  273. }
  274. receiver()->ProcessDetach(device_id);
  275. base::ThreadPool::PostTaskAndReplyWithResult(
  276. FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
  277. base::BindOnce(&EjectPathOnBlockingTaskRunner, path, device),
  278. std::move(callback));
  279. }
  280. void StorageMonitorLinux::OnMtabWatcherCreated(
  281. std::unique_ptr<MtabWatcherLinux> watcher) {
  282. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  283. mtab_watcher_ = std::move(watcher);
  284. }
  285. void StorageMonitorLinux::UpdateMtab(const MountPointDeviceMap& new_mtab) {
  286. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  287. // Check existing mtab entries for unaccounted mount points.
  288. // These mount points must have been removed in the new mtab.
  289. std::list<base::FilePath> mount_points_to_erase;
  290. std::list<base::FilePath> multiple_mounted_devices_needing_reattachment;
  291. for (MountMap::const_iterator old_iter = mount_info_map_.begin();
  292. old_iter != mount_info_map_.end(); ++old_iter) {
  293. const base::FilePath& mount_point = old_iter->first;
  294. const base::FilePath& mount_device = old_iter->second.mount_device;
  295. auto new_iter = new_mtab.find(mount_point);
  296. // |mount_point| not in |new_mtab| or |mount_device| is no longer mounted at
  297. // |mount_point|.
  298. if (new_iter == new_mtab.end() || (new_iter->second != mount_device)) {
  299. auto priority = mount_priority_map_.find(mount_device);
  300. DCHECK(priority != mount_priority_map_.end());
  301. ReferencedMountPoint::const_iterator has_priority =
  302. priority->second.find(mount_point);
  303. if (StorageInfo::IsRemovableDevice(
  304. old_iter->second.storage_info.device_id())) {
  305. DCHECK(has_priority != priority->second.end());
  306. if (has_priority->second) {
  307. receiver()->ProcessDetach(old_iter->second.storage_info.device_id());
  308. }
  309. if (priority->second.size() > 1)
  310. multiple_mounted_devices_needing_reattachment.push_back(mount_device);
  311. }
  312. priority->second.erase(mount_point);
  313. if (priority->second.empty())
  314. mount_priority_map_.erase(mount_device);
  315. mount_points_to_erase.push_back(mount_point);
  316. }
  317. }
  318. // Erase the |mount_info_map_| entries afterwards. Erasing in the loop above
  319. // using the iterator is slightly more efficient, but more tricky, since
  320. // calling std::map::erase() on an iterator invalidates it.
  321. for (std::list<base::FilePath>::const_iterator it =
  322. mount_points_to_erase.begin();
  323. it != mount_points_to_erase.end();
  324. ++it) {
  325. mount_info_map_.erase(*it);
  326. }
  327. // For any multiply mounted device where the mount that we had notified
  328. // got detached, send a notification of attachment for one of the other
  329. // mount points.
  330. for (std::list<base::FilePath>::const_iterator it =
  331. multiple_mounted_devices_needing_reattachment.begin();
  332. it != multiple_mounted_devices_needing_reattachment.end();
  333. ++it) {
  334. auto first_mount_point_info = mount_priority_map_.find(*it)->second.begin();
  335. const base::FilePath& mount_point = first_mount_point_info->first;
  336. first_mount_point_info->second = true;
  337. const StorageInfo& mount_info =
  338. mount_info_map_.find(mount_point)->second.storage_info;
  339. DCHECK(StorageInfo::IsRemovableDevice(mount_info.device_id()));
  340. receiver()->ProcessAttach(mount_info);
  341. }
  342. // Check new mtab entries against existing ones.
  343. scoped_refptr<base::SequencedTaskRunner> mounting_task_runner =
  344. base::ThreadPool::CreateSequencedTaskRunner(
  345. {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
  346. for (auto new_iter = new_mtab.begin(); new_iter != new_mtab.end();
  347. ++new_iter) {
  348. const base::FilePath& mount_point = new_iter->first;
  349. const base::FilePath& mount_device = new_iter->second;
  350. auto old_iter = mount_info_map_.find(mount_point);
  351. if (old_iter == mount_info_map_.end() ||
  352. old_iter->second.mount_device != mount_device) {
  353. // New mount point found or an existing mount point found with a new
  354. // device.
  355. if (IsDeviceAlreadyMounted(mount_device)) {
  356. HandleDeviceMountedMultipleTimes(mount_device, mount_point);
  357. } else {
  358. base::PostTaskAndReplyWithResult(
  359. mounting_task_runner.get(), FROM_HERE,
  360. base::BindOnce(get_device_info_callback_, mount_device,
  361. mount_point),
  362. base::BindOnce(&StorageMonitorLinux::AddNewMount,
  363. weak_ptr_factory_.GetWeakPtr(), mount_device));
  364. }
  365. }
  366. }
  367. // Note: Relies on scheduled tasks on the |mounting_task_runner| being
  368. // sequential. This block needs to follow the for loop, so that the DoNothing
  369. // call on the |mounting_task_runner| happens after the scheduled metadata
  370. // retrievals, meaning that the reply callback will then happen after all the
  371. // AddNewMount calls.
  372. if (!IsInitialized()) {
  373. mounting_task_runner->PostTaskAndReply(
  374. FROM_HERE, base::DoNothing(),
  375. base::BindOnce(&StorageMonitorLinux::MarkInitialized,
  376. weak_ptr_factory_.GetWeakPtr()));
  377. }
  378. }
  379. bool StorageMonitorLinux::IsDeviceAlreadyMounted(
  380. const base::FilePath& mount_device) const {
  381. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  382. return base::Contains(mount_priority_map_, mount_device);
  383. }
  384. void StorageMonitorLinux::HandleDeviceMountedMultipleTimes(
  385. const base::FilePath& mount_device,
  386. const base::FilePath& mount_point) {
  387. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  388. auto priority = mount_priority_map_.find(mount_device);
  389. DCHECK(priority != mount_priority_map_.end());
  390. const base::FilePath& other_mount_point = priority->second.begin()->first;
  391. priority->second[mount_point] = false;
  392. mount_info_map_[mount_point] =
  393. mount_info_map_.find(other_mount_point)->second;
  394. }
  395. void StorageMonitorLinux::AddNewMount(
  396. const base::FilePath& mount_device,
  397. std::unique_ptr<StorageInfo> storage_info) {
  398. DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  399. if (!storage_info)
  400. return;
  401. DCHECK(!storage_info->device_id().empty());
  402. bool removable = StorageInfo::IsRemovableDevice(storage_info->device_id());
  403. const base::FilePath mount_point(storage_info->location());
  404. MountPointInfo mount_point_info;
  405. mount_point_info.mount_device = mount_device;
  406. mount_point_info.storage_info = *storage_info;
  407. mount_info_map_[mount_point] = mount_point_info;
  408. mount_priority_map_[mount_device][mount_point] = removable;
  409. receiver()->ProcessAttach(*storage_info);
  410. }
  411. StorageMonitor* StorageMonitor::CreateInternal() {
  412. const base::FilePath kDefaultMtabPath("/etc/mtab");
  413. return new StorageMonitorLinux(kDefaultMtabPath);
  414. }
  415. } // namespace storage_monitor