• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
MakeDeviceUniqueId(struct udev_device * device)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:
ScopedGetDeviceInfoResultRecorder()81   ScopedGetDeviceInfoResultRecorder() : result_(false) {}
~ScopedGetDeviceInfoResultRecorder()82   ~ScopedGetDeviceInfoResultRecorder() {
83     UMA_HISTOGRAM_BOOLEAN("MediaDeviceNotification.UdevRequestSuccess",
84                           result_);
85   }
86 
set_result(bool result)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.
GetDeviceStorageSize(const base::FilePath & device_path,struct udev_device * device)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.
GetDeviceInfo(const base::FilePath & device_path,const base::FilePath & mount_point)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 
CreateMtabWatcherLinuxOnFileThread(const base::FilePath & mtab_path,base::WeakPtr<MtabWatcherLinux::Delegate> delegate)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 
EjectPathOnFileThread(const base::FilePath & path,const base::FilePath & device)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 
StorageMonitorLinux(const base::FilePath & path)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 
~StorageMonitorLinux()247 StorageMonitorLinux::~StorageMonitorLinux() {
248   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
249 }
250 
Init()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 
GetStorageInfoForPath(const base::FilePath & path,StorageInfo * device_info) const274 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*
media_transfer_protocol_manager()303 StorageMonitorLinux::media_transfer_protocol_manager() {
304   return media_transfer_protocol_manager_.get();
305 }
306 
SetGetDeviceInfoCallbackForTest(const GetDeviceInfoCallback & get_device_info_callback)307 void StorageMonitorLinux::SetGetDeviceInfoCallbackForTest(
308     const GetDeviceInfoCallback& get_device_info_callback) {
309   get_device_info_callback_ = get_device_info_callback;
310 }
311 
SetMediaTransferProtocolManagerForTest(device::MediaTransferProtocolManager * test_manager)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 
EjectDevice(const std::string & device_id,base::Callback<void (EjectStatus)> callback)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 
OnMtabWatcherCreated(MtabWatcherLinux * watcher)358 void StorageMonitorLinux::OnMtabWatcherCreated(MtabWatcherLinux* watcher) {
359   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
360   mtab_watcher_.reset(watcher);
361 }
362 
UpdateMtab(const MountPointDeviceMap & new_mtab)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 
IsDeviceAlreadyMounted(const base::FilePath & mount_device) const463 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 
HandleDeviceMountedMultipleTimes(const base::FilePath & mount_device,const base::FilePath & mount_point)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 
AddNewMount(const base::FilePath & mount_device,scoped_ptr<StorageInfo> storage_info)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 
CreateInternal()502 StorageMonitor* StorageMonitor::CreateInternal() {
503   const base::FilePath kDefaultMtabPath("/etc/mtab");
504   return new StorageMonitorLinux(kDefaultMtabPath);
505 }
506 
507 }  // namespace storage_monitor
508