Add a function to create a bookmark app from a WebApplicationInfo.
[chromium-blink-merge.git] / components / storage_monitor / volume_mount_watcher_win.cc
blob5dc2013782e9b41f625b0d5b73f2fee6d2006487
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.
5 #include "components/storage_monitor/volume_mount_watcher_win.h"
7 #include <windows.h>
9 #include <dbt.h>
10 #include <fileapi.h>
11 #include <winioctl.h>
13 #include "base/bind_helpers.h"
14 #include "base/metrics/histogram.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/task_runner_util.h"
21 #include "base/time/time.h"
22 #include "base/win/scoped_handle.h"
23 #include "components/storage_monitor/media_storage_util.h"
24 #include "components/storage_monitor/storage_info.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/user_metrics.h"
28 using content::BrowserThread;
30 namespace storage_monitor {
32 namespace {
34 const DWORD kMaxPathBufLen = MAX_PATH + 1;
36 enum DeviceType {
37 FLOPPY,
38 REMOVABLE,
39 FIXED,
42 // Histogram values for recording frequencies of eject attempts and
43 // outcomes.
44 enum EjectWinLockOutcomes {
45 LOCK_ATTEMPT,
46 LOCK_TIMEOUT,
47 LOCK_TIMEOUT2,
48 NUM_LOCK_OUTCOMES,
51 // We are trying to figure out whether the drive is a fixed volume,
52 // a removable storage, or a floppy. A "floppy" here means "a volume we
53 // want to basically ignore because it won't fit media and will spin
54 // if we touch it to get volume metadata." GetDriveType returns DRIVE_REMOVABLE
55 // on either floppy or removable volumes. The DRIVE_CDROM type is handled
56 // as a floppy, as are DRIVE_UNKNOWN and DRIVE_NO_ROOT_DIR, as there are
57 // reports that some floppy drives don't report as DRIVE_REMOVABLE.
58 DeviceType GetDeviceType(const base::string16& mount_point) {
59 UINT drive_type = GetDriveType(mount_point.c_str());
60 if (drive_type == DRIVE_FIXED || drive_type == DRIVE_REMOTE ||
61 drive_type == DRIVE_RAMDISK) {
62 return FIXED;
64 if (drive_type != DRIVE_REMOVABLE)
65 return FLOPPY;
67 // Check device strings of the form "X:" and "\\.\X:"
68 // For floppy drives, these will return strings like "/Device/Floppy0"
69 base::string16 device = mount_point;
70 if (EndsWith(mount_point, L"\\", false))
71 device = mount_point.substr(0, mount_point.length() - 1);
72 base::string16 device_path;
73 base::string16 device_path_slash;
74 DWORD dos_device = QueryDosDevice(
75 device.c_str(), WriteInto(&device_path, kMaxPathBufLen), kMaxPathBufLen);
76 base::string16 device_slash = base::string16(L"\\\\.\\");
77 device_slash += device;
78 DWORD dos_device_slash = QueryDosDevice(
79 device_slash.c_str(), WriteInto(&device_path_slash, kMaxPathBufLen),
80 kMaxPathBufLen);
81 if (dos_device == 0 && dos_device_slash == 0)
82 return FLOPPY;
83 if (device_path.find(L"Floppy") != base::string16::npos ||
84 device_path_slash.find(L"Floppy") != base::string16::npos) {
85 return FLOPPY;
88 return REMOVABLE;
91 // Returns 0 if the devicetype is not volume.
92 uint32 GetVolumeBitMaskFromBroadcastHeader(LPARAM data) {
93 DEV_BROADCAST_VOLUME* dev_broadcast_volume =
94 reinterpret_cast<DEV_BROADCAST_VOLUME*>(data);
95 if (dev_broadcast_volume->dbcv_devicetype == DBT_DEVTYP_VOLUME)
96 return dev_broadcast_volume->dbcv_unitmask;
97 return 0;
100 // Returns true if |data| represents a logical volume structure.
101 bool IsLogicalVolumeStructure(LPARAM data) {
102 DEV_BROADCAST_HDR* broadcast_hdr =
103 reinterpret_cast<DEV_BROADCAST_HDR*>(data);
104 return broadcast_hdr != NULL &&
105 broadcast_hdr->dbch_devicetype == DBT_DEVTYP_VOLUME;
108 // Gets the total volume of the |mount_point| in bytes.
109 uint64 GetVolumeSize(const base::string16& mount_point) {
110 ULARGE_INTEGER total;
111 if (!GetDiskFreeSpaceExW(mount_point.c_str(), NULL, &total, NULL))
112 return 0;
113 return total.QuadPart;
116 // Gets mass storage device information given a |device_path|. On success,
117 // returns true and fills in |info|.
118 // The following msdn blog entry is helpful for understanding disk volumes
119 // and how they are treated in Windows:
120 // http://blogs.msdn.com/b/adioltean/archive/2005/04/16/408947.aspx.
121 bool GetDeviceDetails(const base::FilePath& device_path, StorageInfo* info) {
122 DCHECK(info);
124 base::string16 mount_point;
125 if (!GetVolumePathName(device_path.value().c_str(),
126 WriteInto(&mount_point, kMaxPathBufLen),
127 kMaxPathBufLen)) {
128 return false;
130 mount_point.resize(wcslen(mount_point.c_str()));
132 // Note: experimentally this code does not spin a floppy drive. It
133 // returns a GUID associated with the device, not the volume.
134 base::string16 guid;
135 if (!GetVolumeNameForVolumeMountPoint(mount_point.c_str(),
136 WriteInto(&guid, kMaxPathBufLen),
137 kMaxPathBufLen)) {
138 return false;
140 // In case it has two GUID's (see above mentioned blog), do it again.
141 if (!GetVolumeNameForVolumeMountPoint(guid.c_str(),
142 WriteInto(&guid, kMaxPathBufLen),
143 kMaxPathBufLen)) {
144 return false;
147 // If we're adding a floppy drive, return without querying any more
148 // drive metadata -- it will cause the floppy drive to seek.
149 // Note: treats FLOPPY as FIXED_MASS_STORAGE. This is intentional.
150 DeviceType device_type = GetDeviceType(mount_point);
151 if (device_type == FLOPPY) {
152 info->set_device_id(StorageInfo::MakeDeviceId(
153 StorageInfo::FIXED_MASS_STORAGE, base::UTF16ToUTF8(guid)));
154 return true;
157 StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
158 if (device_type == REMOVABLE) {
159 type = StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
160 if (MediaStorageUtil::HasDcim(base::FilePath(mount_point)))
161 type = StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
164 // NOTE: experimentally, this function returns false if there is no volume
165 // name set.
166 base::string16 volume_label;
167 GetVolumeInformationW(device_path.value().c_str(),
168 WriteInto(&volume_label, kMaxPathBufLen),
169 kMaxPathBufLen, NULL, NULL, NULL, NULL, 0);
171 uint64 total_size_in_bytes = GetVolumeSize(mount_point);
172 std::string device_id =
173 StorageInfo::MakeDeviceId(type, base::UTF16ToUTF8(guid));
175 // TODO(gbillock): if volume_label.empty(), get the vendor/model information
176 // for the volume.
177 *info = StorageInfo(device_id, mount_point, volume_label, base::string16(),
178 base::string16(), total_size_in_bytes);
179 return true;
182 // Returns a vector of all the removable mass storage devices that are
183 // connected.
184 std::vector<base::FilePath> GetAttachedDevices() {
185 std::vector<base::FilePath> result;
186 base::string16 volume_name;
187 HANDLE find_handle = FindFirstVolume(WriteInto(&volume_name, kMaxPathBufLen),
188 kMaxPathBufLen);
189 if (find_handle == INVALID_HANDLE_VALUE)
190 return result;
192 while (true) {
193 base::string16 volume_path;
194 DWORD return_count;
195 if (GetVolumePathNamesForVolumeName(volume_name.c_str(),
196 WriteInto(&volume_path, kMaxPathBufLen),
197 kMaxPathBufLen, &return_count)) {
198 result.push_back(base::FilePath(volume_path));
200 if (!FindNextVolume(find_handle, WriteInto(&volume_name, kMaxPathBufLen),
201 kMaxPathBufLen)) {
202 if (GetLastError() != ERROR_NO_MORE_FILES)
203 DPLOG(ERROR);
204 break;
208 FindVolumeClose(find_handle);
209 return result;
212 // Eject a removable volume at the specified |device| path. This works by
213 // 1) locking the volume,
214 // 2) unmounting the volume,
215 // 3) ejecting the volume.
216 // If the lock fails, it will re-schedule itself.
217 // See http://support.microsoft.com/kb/165721
218 void EjectDeviceInThreadPool(
219 const base::FilePath& device,
220 base::Callback<void(StorageMonitor::EjectStatus)> callback,
221 scoped_refptr<base::SequencedTaskRunner> task_runner,
222 int iteration) {
223 base::FilePath::StringType volume_name;
224 base::FilePath::CharType drive_letter = device.value()[0];
225 // Don't try to eject if the path isn't a simple one -- we're not
226 // sure how to do that yet. Need to figure out how to eject volumes mounted
227 // at not-just-drive-letter paths.
228 if (drive_letter < L'A' || drive_letter > L'Z' ||
229 device != device.DirName()) {
230 BrowserThread::PostTask(
231 BrowserThread::UI, FROM_HERE,
232 base::Bind(callback, StorageMonitor::EJECT_FAILURE));
233 return;
235 base::SStringPrintf(&volume_name, L"\\\\.\\%lc:", drive_letter);
237 base::win::ScopedHandle volume_handle(CreateFile(
238 volume_name.c_str(),
239 GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
240 NULL, OPEN_EXISTING, 0, NULL));
242 if (!volume_handle.IsValid()) {
243 BrowserThread::PostTask(
244 BrowserThread::UI, FROM_HERE,
245 base::Bind(callback, StorageMonitor::EJECT_FAILURE));
246 return;
249 DWORD bytes_returned = 0; // Unused, but necessary for ioctl's.
251 // Lock the drive to be ejected (so that other processes can't open
252 // files on it). If this fails, it means some other process has files
253 // open on the device. Note that the lock is released when the volume
254 // handle is closed, and this is done by the ScopedHandle above.
255 BOOL locked = DeviceIoControl(volume_handle, FSCTL_LOCK_VOLUME,
256 NULL, 0, NULL, 0, &bytes_returned, NULL);
257 UMA_HISTOGRAM_ENUMERATION("StorageMonitor.EjectWinLock",
258 LOCK_ATTEMPT, NUM_LOCK_OUTCOMES);
259 if (!locked) {
260 UMA_HISTOGRAM_ENUMERATION("StorageMonitor.EjectWinLock",
261 iteration == 0 ? LOCK_TIMEOUT : LOCK_TIMEOUT2,
262 NUM_LOCK_OUTCOMES);
263 const int kNumLockRetries = 1;
264 const base::TimeDelta kLockRetryInterval =
265 base::TimeDelta::FromMilliseconds(500);
266 if (iteration < kNumLockRetries) {
267 // Try again -- the lock may have been a transient one. This happens on
268 // things like AV disk lock for some reason, or another process
269 // transient disk lock.
270 task_runner->PostDelayedTask(
271 FROM_HERE,
272 base::Bind(&EjectDeviceInThreadPool,
273 device, callback, task_runner, iteration + 1),
274 kLockRetryInterval);
275 return;
278 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
279 base::Bind(callback, StorageMonitor::EJECT_IN_USE));
280 return;
283 // Unmount the device from the filesystem -- this will remove it from
284 // the file picker, drive enumerations, etc.
285 BOOL dismounted = DeviceIoControl(volume_handle, FSCTL_DISMOUNT_VOLUME,
286 NULL, 0, NULL, 0, &bytes_returned, NULL);
288 // Reached if we acquired a lock, but could not dismount. This might
289 // occur if another process unmounted without locking. Call this OK,
290 // since the volume is now unreachable.
291 if (!dismounted) {
292 DeviceIoControl(volume_handle, FSCTL_UNLOCK_VOLUME,
293 NULL, 0, NULL, 0, &bytes_returned, NULL);
294 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
295 base::Bind(callback, StorageMonitor::EJECT_OK));
296 return;
299 PREVENT_MEDIA_REMOVAL pmr_buffer;
300 pmr_buffer.PreventMediaRemoval = FALSE;
301 // Mark the device as safe to remove.
302 if (!DeviceIoControl(volume_handle, IOCTL_STORAGE_MEDIA_REMOVAL,
303 &pmr_buffer, sizeof(PREVENT_MEDIA_REMOVAL),
304 NULL, 0, &bytes_returned, NULL)) {
305 BrowserThread::PostTask(
306 BrowserThread::UI, FROM_HERE,
307 base::Bind(callback, StorageMonitor::EJECT_FAILURE));
308 return;
311 // Physically eject or soft-eject the device.
312 if (!DeviceIoControl(volume_handle, IOCTL_STORAGE_EJECT_MEDIA,
313 NULL, 0, NULL, 0, &bytes_returned, NULL)) {
314 BrowserThread::PostTask(
315 BrowserThread::UI, FROM_HERE,
316 base::Bind(callback, StorageMonitor::EJECT_FAILURE));
317 return;
320 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
321 base::Bind(callback, StorageMonitor::EJECT_OK));
324 } // namespace
326 const int kWorkerPoolNumThreads = 3;
327 const char* kWorkerPoolNamePrefix = "DeviceInfoPool";
329 VolumeMountWatcherWin::VolumeMountWatcherWin()
330 : device_info_worker_pool_(new base::SequencedWorkerPool(
331 kWorkerPoolNumThreads, kWorkerPoolNamePrefix)),
332 weak_factory_(this),
333 notifications_(NULL) {
334 task_runner_ =
335 device_info_worker_pool_->GetSequencedTaskRunnerWithShutdownBehavior(
336 device_info_worker_pool_->GetSequenceToken(),
337 base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
340 // static
341 base::FilePath VolumeMountWatcherWin::DriveNumberToFilePath(int drive_number) {
342 if (drive_number < 0 || drive_number > 25)
343 return base::FilePath();
344 base::string16 path(L"_:\\");
345 path[0] = L'A' + drive_number;
346 return base::FilePath(path);
349 // In order to get all the weak pointers created on the UI thread, and doing
350 // synchronous Windows calls in the worker pool, this kicks off a chain of
351 // events which will
352 // a) Enumerate attached devices
353 // b) Create weak pointers for which to send completion signals from
354 // c) Retrieve metadata on the volumes and then
355 // d) Notify that metadata to listeners.
356 void VolumeMountWatcherWin::Init() {
357 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
359 // When VolumeMountWatcherWin is created, the message pumps are not running
360 // so a posted task from the constructor would never run. Therefore, do all
361 // the initializations here.
362 base::PostTaskAndReplyWithResult(task_runner_, FROM_HERE,
363 GetAttachedDevicesCallback(),
364 base::Bind(&VolumeMountWatcherWin::AddDevicesOnUIThread,
365 weak_factory_.GetWeakPtr()));
368 void VolumeMountWatcherWin::AddDevicesOnUIThread(
369 std::vector<base::FilePath> removable_devices) {
370 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
372 for (size_t i = 0; i < removable_devices.size(); i++) {
373 if (ContainsKey(pending_device_checks_, removable_devices[i]))
374 continue;
375 pending_device_checks_.insert(removable_devices[i]);
376 task_runner_->PostTask(
377 FROM_HERE,
378 base::Bind(&VolumeMountWatcherWin::RetrieveInfoForDeviceAndAdd,
379 removable_devices[i], GetDeviceDetailsCallback(),
380 weak_factory_.GetWeakPtr()));
384 // static
385 void VolumeMountWatcherWin::RetrieveInfoForDeviceAndAdd(
386 const base::FilePath& device_path,
387 const GetDeviceDetailsCallbackType& get_device_details_callback,
388 base::WeakPtr<VolumeMountWatcherWin> volume_watcher) {
389 StorageInfo info;
390 if (!get_device_details_callback.Run(device_path, &info)) {
391 BrowserThread::PostTask(
392 BrowserThread::UI, FROM_HERE,
393 base::Bind(&VolumeMountWatcherWin::DeviceCheckComplete,
394 volume_watcher, device_path));
395 return;
398 BrowserThread::PostTask(
399 BrowserThread::UI, FROM_HERE,
400 base::Bind(&VolumeMountWatcherWin::HandleDeviceAttachEventOnUIThread,
401 volume_watcher, device_path, info));
404 void VolumeMountWatcherWin::DeviceCheckComplete(
405 const base::FilePath& device_path) {
406 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
407 pending_device_checks_.erase(device_path);
409 if (pending_device_checks_.size() == 0) {
410 if (notifications_)
411 notifications_->MarkInitialized();
415 VolumeMountWatcherWin::GetAttachedDevicesCallbackType
416 VolumeMountWatcherWin::GetAttachedDevicesCallback() const {
417 return base::Bind(&GetAttachedDevices);
420 VolumeMountWatcherWin::GetDeviceDetailsCallbackType
421 VolumeMountWatcherWin::GetDeviceDetailsCallback() const {
422 return base::Bind(&GetDeviceDetails);
425 bool VolumeMountWatcherWin::GetDeviceInfo(const base::FilePath& device_path,
426 StorageInfo* info) const {
427 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
428 DCHECK(info);
429 base::FilePath path(device_path);
430 MountPointDeviceMetadataMap::const_iterator iter =
431 device_metadata_.find(path);
432 while (iter == device_metadata_.end() && path.DirName() != path) {
433 path = path.DirName();
434 iter = device_metadata_.find(path);
437 if (iter == device_metadata_.end())
438 return false;
440 *info = iter->second;
441 return true;
444 void VolumeMountWatcherWin::OnWindowMessage(UINT event_type, LPARAM data) {
445 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
446 switch (event_type) {
447 case DBT_DEVICEARRIVAL: {
448 if (IsLogicalVolumeStructure(data)) {
449 DWORD unitmask = GetVolumeBitMaskFromBroadcastHeader(data);
450 std::vector<base::FilePath> paths;
451 for (int i = 0; unitmask; ++i, unitmask >>= 1) {
452 if (!(unitmask & 0x01))
453 continue;
454 paths.push_back(DriveNumberToFilePath(i));
456 AddDevicesOnUIThread(paths);
458 break;
460 case DBT_DEVICEREMOVECOMPLETE: {
461 if (IsLogicalVolumeStructure(data)) {
462 DWORD unitmask = GetVolumeBitMaskFromBroadcastHeader(data);
463 for (int i = 0; unitmask; ++i, unitmask >>= 1) {
464 if (!(unitmask & 0x01))
465 continue;
466 HandleDeviceDetachEventOnUIThread(DriveNumberToFilePath(i).value());
469 break;
474 void VolumeMountWatcherWin::SetNotifications(
475 StorageMonitor::Receiver* notifications) {
476 notifications_ = notifications;
479 VolumeMountWatcherWin::~VolumeMountWatcherWin() {
480 weak_factory_.InvalidateWeakPtrs();
481 device_info_worker_pool_->Shutdown();
484 void VolumeMountWatcherWin::HandleDeviceAttachEventOnUIThread(
485 const base::FilePath& device_path,
486 const StorageInfo& info) {
487 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
489 device_metadata_[device_path] = info;
491 if (notifications_)
492 notifications_->ProcessAttach(info);
494 DeviceCheckComplete(device_path);
497 void VolumeMountWatcherWin::HandleDeviceDetachEventOnUIThread(
498 const base::string16& device_location) {
499 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
501 MountPointDeviceMetadataMap::const_iterator device_info =
502 device_metadata_.find(base::FilePath(device_location));
503 // If the device isn't type removable (like a CD), it won't be there.
504 if (device_info == device_metadata_.end())
505 return;
507 if (notifications_)
508 notifications_->ProcessDetach(device_info->second.device_id());
509 device_metadata_.erase(device_info);
512 void VolumeMountWatcherWin::EjectDevice(
513 const std::string& device_id,
514 base::Callback<void(StorageMonitor::EjectStatus)> callback) {
515 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
516 base::FilePath device = MediaStorageUtil::FindDevicePathById(device_id);
517 if (device.empty()) {
518 callback.Run(StorageMonitor::EJECT_FAILURE);
519 return;
521 if (device_metadata_.erase(device) == 0) {
522 callback.Run(StorageMonitor::EJECT_FAILURE);
523 return;
526 task_runner_->PostTask(
527 FROM_HERE,
528 base::Bind(&EjectDeviceInThreadPool, device, callback, task_runner_, 0));
531 } // namespace storage_monitor