Upstream version 5.34.98.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / storage_monitor / storage_monitor_linux.cc
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       base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kLabel));
148   base::string16 vendor_name =
149       base::UTF8ToUTF16(GetUdevDevicePropertyValue(device.get(), kVendor));
150   base::string16 model_name =
151       base::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 }