[M120 Migration][VD] Enable direct rendering for TVPlus
[platform/framework/web/chromium-efl.git] / components / storage_monitor / volume_mount_watcher_win.cc
1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/storage_monitor/volume_mount_watcher_win.h"
6
7 #include <windows.h>
8 #include <stddef.h>
9 #include <stdint.h>
10
11 #include <dbt.h>
12 #include <fileapi.h>
13 #include <shlobj.h>
14 #include <winioctl.h>
15
16 #include <algorithm>
17 #include <string>
18
19 #include "base/containers/contains.h"
20 #include "base/functional/bind.h"
21 #include "base/functional/callback_helpers.h"
22 #include "base/logging.h"
23 #include "base/strings/strcat_win.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "base/system/sys_info.h"
28 #include "base/task/sequenced_task_runner.h"
29 #include "base/task/thread_pool.h"
30 #include "base/time/time.h"
31 #include "base/win/scoped_handle.h"
32 #include "components/storage_monitor/media_storage_util.h"
33 #include "components/storage_monitor/storage_info.h"
34 #include "content/public/browser/browser_task_traits.h"
35 #include "content/public/browser/browser_thread.h"
36
37 using content::BrowserThread;
38
39 namespace storage_monitor {
40
41 namespace {
42
43 const DWORD kMaxPathBufLen = MAX_PATH + 1;
44
45 enum DeviceType {
46   FLOPPY,
47   REMOVABLE,
48   FIXED,
49 };
50
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 std::wstring& 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;
63   }
64   if (drive_type != DRIVE_REMOVABLE)
65     return FLOPPY;
66
67   // Check device strings of the form "X:" and "\\.\X:"
68   // For floppy drives, these will return strings like "/Device/Floppy0"
69   std::wstring device = mount_point;
70   if (base::EndsWith(mount_point, L"\\", base::CompareCase::INSENSITIVE_ASCII))
71     device = mount_point.substr(0, mount_point.length() - 1);
72   std::wstring device_path;
73   std::wstring device_path_slash;
74   DWORD dos_device = QueryDosDevice(
75       device.c_str(), base::WriteInto(&device_path, kMaxPathBufLen),
76       kMaxPathBufLen);
77   std::wstring device_slash = std::wstring(L"\\\\.\\");
78   device_slash += device;
79   DWORD dos_device_slash = QueryDosDevice(
80       device_slash.c_str(), base::WriteInto(&device_path_slash, kMaxPathBufLen),
81       kMaxPathBufLen);
82   if (dos_device == 0 && dos_device_slash == 0)
83     return FLOPPY;
84   if (device_path.find(L"Floppy") != std::wstring::npos ||
85       device_path_slash.find(L"Floppy") != std::wstring::npos) {
86     return FLOPPY;
87   }
88
89   return REMOVABLE;
90 }
91
92 // Returns 0 if the devicetype is not volume.
93 uint32_t GetVolumeBitMaskFromBroadcastHeader(LPARAM data) {
94   DEV_BROADCAST_VOLUME* dev_broadcast_volume =
95       reinterpret_cast<DEV_BROADCAST_VOLUME*>(data);
96   if (dev_broadcast_volume->dbcv_devicetype == DBT_DEVTYP_VOLUME)
97     return dev_broadcast_volume->dbcv_unitmask;
98   return 0;
99 }
100
101 // Returns true if |data| represents a logical volume structure.
102 bool IsLogicalVolumeStructure(LPARAM data) {
103   DEV_BROADCAST_HDR* broadcast_hdr =
104       reinterpret_cast<DEV_BROADCAST_HDR*>(data);
105   return broadcast_hdr && broadcast_hdr->dbch_devicetype == DBT_DEVTYP_VOLUME;
106 }
107
108 // Gets the total volume of the |mount_point| in bytes.
109 uint64_t GetVolumeSize(const base::FilePath& mount_point) {
110   int64_t size = base::SysInfo::AmountOfTotalDiskSpace(mount_point);
111   return std::max(size, static_cast<int64_t>(0));
112 }
113
114 // Gets mass storage device information given a |device_path|. On success,
115 // returns true and fills in |info|.
116 // The following msdn blog entry is helpful for understanding disk volumes
117 // and how they are treated in Windows:
118 // http://blogs.msdn.com/b/adioltean/archive/2005/04/16/408947.aspx.
119 bool GetDeviceDetails(const base::FilePath& device_path, StorageInfo* info) {
120   DCHECK(info);
121
122   std::wstring mount_point;
123   if (!GetVolumePathName(device_path.value().c_str(),
124                          base::WriteInto(&mount_point, kMaxPathBufLen),
125                          kMaxPathBufLen)) {
126     return false;
127   }
128   mount_point.resize(wcslen(mount_point.c_str()));
129
130   // Note: experimentally this code does not spin a floppy drive. It
131   // returns a GUID associated with the device, not the volume.
132   std::wstring guid;
133   if (!GetVolumeNameForVolumeMountPoint(mount_point.c_str(),
134                                         base::WriteInto(&guid, kMaxPathBufLen),
135                                         kMaxPathBufLen)) {
136     return false;
137   }
138   // In case it has two GUID's (see above mentioned blog), do it again.
139   if (!GetVolumeNameForVolumeMountPoint(guid.c_str(),
140                                         base::WriteInto(&guid, kMaxPathBufLen),
141                                         kMaxPathBufLen)) {
142     return false;
143   }
144
145   // If we're adding a floppy drive, return without querying any more
146   // drive metadata -- it will cause the floppy drive to seek.
147   // Note: treats FLOPPY as FIXED_MASS_STORAGE. This is intentional.
148   DeviceType device_type = GetDeviceType(mount_point);
149   if (device_type == FLOPPY) {
150     info->set_device_id(StorageInfo::MakeDeviceId(
151         StorageInfo::FIXED_MASS_STORAGE, base::WideToUTF8(guid)));
152     return true;
153   }
154
155   base::FilePath mount_path(mount_point);
156   StorageInfo::Type type = StorageInfo::FIXED_MASS_STORAGE;
157   if (device_type == REMOVABLE) {
158     type = StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM;
159     if (MediaStorageUtil::HasDcim(mount_path))
160       type = StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM;
161   }
162
163   // NOTE: experimentally, this function returns false if there is no volume
164   // name set.
165   std::wstring volume_label;
166   GetVolumeInformationW(device_path.value().c_str(),
167                         base::WriteInto(&volume_label, kMaxPathBufLen),
168                         kMaxPathBufLen, nullptr, nullptr, nullptr, nullptr, 0);
169
170   uint64_t total_size_in_bytes = GetVolumeSize(mount_path);
171   std::string device_id =
172       StorageInfo::MakeDeviceId(type, base::WideToUTF8(guid));
173
174   // TODO(gbillock): if volume_label.empty(), get the vendor/model information
175   // for the volume.
176   *info = StorageInfo(device_id, mount_point, base::WideToUTF16(volume_label),
177                       std::u16string(), std::u16string(), total_size_in_bytes);
178   return true;
179 }
180
181 // Returns a vector of all the removable mass storage devices that are
182 // connected.
183 std::vector<base::FilePath> GetAttachedDevices() {
184   std::vector<base::FilePath> result;
185   std::wstring volume_name;
186   HANDLE find_handle = FindFirstVolume(
187       base::WriteInto(&volume_name, kMaxPathBufLen), kMaxPathBufLen);
188   if (find_handle == INVALID_HANDLE_VALUE)
189     return result;
190
191   while (true) {
192     std::wstring volume_path;
193     DWORD return_count;
194     if (GetVolumePathNamesForVolumeName(
195             volume_name.c_str(), base::WriteInto(&volume_path, kMaxPathBufLen),
196             kMaxPathBufLen, &return_count)) {
197       result.push_back(base::FilePath(volume_path));
198     }
199     if (!FindNextVolume(find_handle,
200                         base::WriteInto(&volume_name, kMaxPathBufLen),
201                         kMaxPathBufLen)) {
202       if (GetLastError() != ERROR_NO_MORE_FILES)
203         DPLOG(ERROR);
204       break;
205     }
206   }
207
208   FindVolumeClose(find_handle);
209   return result;
210 }
211
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::OnceCallback<void(StorageMonitor::EjectStatus)> callback,
221     scoped_refptr<base::SequencedTaskRunner> task_runner,
222     int iteration) {
223   base::FilePath::CharType drive_letter = device.value()[0];
224   // Don't try to eject if the path isn't a simple one -- we're not
225   // sure how to do that yet. Need to figure out how to eject volumes mounted
226   // at not-just-drive-letter paths.
227   if (drive_letter < L'A' || drive_letter > L'Z' ||
228       device != device.DirName()) {
229     content::GetUIThreadTaskRunner({})->PostTask(
230         FROM_HERE,
231         base::BindOnce(std::move(callback), StorageMonitor::EJECT_FAILURE));
232     return;
233   }
234   std::wstring volume_name =
235       base::StrCat({L"\\\\.\\", std::wstring(1, drive_letter), L":"});
236
237   base::win::ScopedHandle volume_handle(CreateFile(
238       volume_name.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
239       nullptr, OPEN_EXISTING, 0, nullptr));
240
241   if (!volume_handle.IsValid()) {
242     content::GetUIThreadTaskRunner({})->PostTask(
243         FROM_HERE,
244         base::BindOnce(std::move(callback), StorageMonitor::EJECT_FAILURE));
245     return;
246   }
247
248   DWORD bytes_returned = 0;  // Unused, but necessary for ioctl's.
249
250   // Lock the drive to be ejected (so that other processes can't open
251   // files on it). If this fails, it means some other process has files
252   // open on the device. Note that the lock is released when the volume
253   // handle is closed, and this is done by the ScopedHandle above.
254   BOOL locked = DeviceIoControl(volume_handle.Get(), FSCTL_LOCK_VOLUME, nullptr,
255                                 0, nullptr, 0, &bytes_returned, nullptr);
256   if (!locked) {
257     const int kNumLockRetries = 1;
258     const base::TimeDelta kLockRetryInterval = base::Milliseconds(500);
259     if (iteration < kNumLockRetries) {
260       // Try again -- the lock may have been a transient one. This happens on
261       // things like AV disk lock for some reason, or another process
262       // transient disk lock.
263       task_runner->PostDelayedTask(
264           FROM_HERE,
265           base::BindOnce(&EjectDeviceInThreadPool, device, std::move(callback),
266                          task_runner, iteration + 1),
267           kLockRetryInterval);
268       return;
269     }
270
271     content::GetUIThreadTaskRunner({})->PostTask(
272         FROM_HERE,
273         base::BindOnce(std::move(callback), StorageMonitor::EJECT_IN_USE));
274     return;
275   }
276
277   // Unmount the device from the filesystem -- this will remove it from
278   // the file picker, drive enumerations, etc.
279   BOOL dismounted =
280       DeviceIoControl(volume_handle.Get(), FSCTL_DISMOUNT_VOLUME, nullptr, 0,
281                       nullptr, 0, &bytes_returned, nullptr);
282
283   // Reached if we acquired a lock, but could not dismount. This might
284   // occur if another process unmounted without locking. Call this OK,
285   // since the volume is now unreachable.
286   if (!dismounted) {
287     DeviceIoControl(volume_handle.Get(), FSCTL_UNLOCK_VOLUME, nullptr, 0,
288                     nullptr, 0, &bytes_returned, nullptr);
289     content::GetUIThreadTaskRunner({})->PostTask(
290         FROM_HERE,
291         base::BindOnce(std::move(callback), StorageMonitor::EJECT_OK));
292     return;
293   }
294
295   PREVENT_MEDIA_REMOVAL pmr_buffer;
296   pmr_buffer.PreventMediaRemoval = FALSE;
297   // Mark the device as safe to remove.
298   if (!DeviceIoControl(volume_handle.Get(), IOCTL_STORAGE_MEDIA_REMOVAL,
299                        &pmr_buffer, sizeof(PREVENT_MEDIA_REMOVAL), nullptr, 0,
300                        &bytes_returned, nullptr)) {
301     content::GetUIThreadTaskRunner({})->PostTask(
302         FROM_HERE,
303         base::BindOnce(std::move(callback), StorageMonitor::EJECT_FAILURE));
304     return;
305   }
306
307   // Physically eject or soft-eject the device.
308   if (!DeviceIoControl(volume_handle.Get(), IOCTL_STORAGE_EJECT_MEDIA, nullptr,
309                        0, nullptr, 0, &bytes_returned, nullptr)) {
310     content::GetUIThreadTaskRunner({})->PostTask(
311         FROM_HERE,
312         base::BindOnce(std::move(callback), StorageMonitor::EJECT_FAILURE));
313     return;
314   }
315
316   content::GetUIThreadTaskRunner({})->PostTask(
317       FROM_HERE, base::BindOnce(std::move(callback), StorageMonitor::EJECT_OK));
318 }
319
320 }  // namespace
321
322 VolumeMountWatcherWin::VolumeMountWatcherWin()
323     : device_info_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
324           {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
325            base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
326       notifications_(nullptr) {}
327
328 // static
329 base::FilePath VolumeMountWatcherWin::DriveNumberToFilePath(int drive_number) {
330   if (drive_number < 0 || drive_number > 25)
331     return base::FilePath();
332   std::wstring path(L"A:\\");
333   path[0] += drive_number;
334   return base::FilePath(path);
335 }
336
337 // In order to get all the weak pointers created on the UI thread, and doing
338 // synchronous Windows calls in the worker pool, this kicks off a chain of
339 // events which will
340 // a) Enumerate attached devices
341 // b) Create weak pointers for which to send completion signals from
342 // c) Retrieve metadata on the volumes and then
343 // d) Notify that metadata to listeners.
344 void VolumeMountWatcherWin::Init() {
345   DCHECK_CURRENTLY_ON(BrowserThread::UI);
346
347   // When VolumeMountWatcherWin is created, the message pumps are not running
348   // so a posted task from the constructor would never run. Therefore, do all
349   // the initializations here.
350   device_info_task_runner_->PostTaskAndReplyWithResult(
351       FROM_HERE, GetAttachedDevicesCallback(),
352       base::BindOnce(&VolumeMountWatcherWin::AddDevicesOnUIThread,
353                      weak_factory_.GetWeakPtr()));
354 }
355
356 void VolumeMountWatcherWin::AddDevicesOnUIThread(
357     std::vector<base::FilePath> removable_devices) {
358   DCHECK_CURRENTLY_ON(BrowserThread::UI);
359
360   for (size_t i = 0; i < removable_devices.size(); i++) {
361     if (base::Contains(pending_device_checks_, removable_devices[i]))
362       continue;
363     pending_device_checks_.insert(removable_devices[i]);
364     device_info_task_runner_->PostTask(
365         FROM_HERE,
366         base::BindOnce(&VolumeMountWatcherWin::RetrieveInfoForDeviceAndAdd,
367                        removable_devices[i], GetDeviceDetailsCallback(),
368                        weak_factory_.GetWeakPtr()));
369   }
370 }
371
372 // static
373 void VolumeMountWatcherWin::RetrieveInfoForDeviceAndAdd(
374     const base::FilePath& device_path,
375     GetDeviceDetailsCallbackType get_device_details_callback,
376     base::WeakPtr<VolumeMountWatcherWin> volume_watcher) {
377   StorageInfo info;
378   if (!std::move(get_device_details_callback).Run(device_path, &info)) {
379     content::GetUIThreadTaskRunner({})->PostTask(
380         FROM_HERE, base::BindOnce(&VolumeMountWatcherWin::DeviceCheckComplete,
381                                   volume_watcher, device_path));
382     return;
383   }
384
385   content::GetUIThreadTaskRunner({})->PostTask(
386       FROM_HERE,
387       base::BindOnce(&VolumeMountWatcherWin::HandleDeviceAttachEventOnUIThread,
388                      volume_watcher, device_path, info));
389 }
390
391 void VolumeMountWatcherWin::DeviceCheckComplete(
392     const base::FilePath& device_path) {
393   DCHECK_CURRENTLY_ON(BrowserThread::UI);
394   pending_device_checks_.erase(device_path);
395
396   if (pending_device_checks_.empty()) {
397     if (notifications_)
398       notifications_->MarkInitialized();
399   }
400 }
401
402 VolumeMountWatcherWin::GetAttachedDevicesCallbackType
403     VolumeMountWatcherWin::GetAttachedDevicesCallback() const {
404   return base::BindOnce(&GetAttachedDevices);
405 }
406
407 VolumeMountWatcherWin::GetDeviceDetailsCallbackType
408     VolumeMountWatcherWin::GetDeviceDetailsCallback() const {
409   return base::BindOnce(&GetDeviceDetails);
410 }
411
412 bool VolumeMountWatcherWin::GetDeviceInfo(const base::FilePath& device_path,
413                                           StorageInfo* info) const {
414   DCHECK_CURRENTLY_ON(BrowserThread::UI);
415   DCHECK(info);
416   base::FilePath path(device_path);
417   MountPointDeviceMetadataMap::const_iterator iter =
418       device_metadata_.find(path);
419   while (iter == device_metadata_.end() && path.DirName() != path) {
420     path = path.DirName();
421     iter = device_metadata_.find(path);
422   }
423
424   if (iter == device_metadata_.end())
425     return false;
426
427   *info = iter->second;
428   return true;
429 }
430
431 void VolumeMountWatcherWin::OnWindowMessage(UINT event_type, LPARAM data) {
432   DCHECK_CURRENTLY_ON(BrowserThread::UI);
433   switch (event_type) {
434     case DBT_DEVICEARRIVAL: {
435       if (IsLogicalVolumeStructure(data)) {
436         DWORD unitmask = GetVolumeBitMaskFromBroadcastHeader(data);
437         std::vector<base::FilePath> paths;
438         for (int i = 0; unitmask; ++i, unitmask >>= 1) {
439           if (!(unitmask & 0x01))
440             continue;
441           paths.push_back(DriveNumberToFilePath(i));
442         }
443         AddDevicesOnUIThread(paths);
444       }
445       break;
446     }
447     case DBT_DEVICEREMOVECOMPLETE: {
448       if (IsLogicalVolumeStructure(data)) {
449         DWORD unitmask = GetVolumeBitMaskFromBroadcastHeader(data);
450         for (int i = 0; unitmask; ++i, unitmask >>= 1) {
451           if (!(unitmask & 0x01))
452             continue;
453           HandleDeviceDetachEventOnUIThread(DriveNumberToFilePath(i).value());
454         }
455       }
456       break;
457     }
458   }
459 }
460
461 void VolumeMountWatcherWin::OnMediaChange(WPARAM wparam, LPARAM lparam) {
462   if (lparam == SHCNE_MEDIAINSERTED || lparam == SHCNE_MEDIAREMOVED) {
463     struct _ITEMIDLIST* pidl = *reinterpret_cast<struct _ITEMIDLIST**>(
464         wparam);
465     wchar_t sPath[MAX_PATH];
466     if (!SHGetPathFromIDList(pidl, sPath)) {
467       DVLOG(1) << "MediaInserted: SHGetPathFromIDList failed";
468       return;
469     }
470     switch (lparam) {
471       case SHCNE_MEDIAINSERTED: {
472         std::vector<base::FilePath> paths;
473         paths.push_back(base::FilePath(sPath));
474         AddDevicesOnUIThread(paths);
475         break;
476       }
477       case SHCNE_MEDIAREMOVED: {
478         HandleDeviceDetachEventOnUIThread(sPath);
479         break;
480       }
481     }
482   }
483 }
484
485 void VolumeMountWatcherWin::SetNotifications(
486     StorageMonitor::Receiver* notifications) {
487   notifications_ = notifications;
488 }
489
490 VolumeMountWatcherWin::~VolumeMountWatcherWin() {
491   weak_factory_.InvalidateWeakPtrs();
492 }
493
494 void VolumeMountWatcherWin::HandleDeviceAttachEventOnUIThread(
495     const base::FilePath& device_path,
496     const StorageInfo& info) {
497   DCHECK_CURRENTLY_ON(BrowserThread::UI);
498
499   device_metadata_[device_path] = info;
500
501   if (notifications_)
502     notifications_->ProcessAttach(info);
503
504   DeviceCheckComplete(device_path);
505 }
506
507 void VolumeMountWatcherWin::HandleDeviceDetachEventOnUIThread(
508     const std::wstring& device_location) {
509   DCHECK_CURRENTLY_ON(BrowserThread::UI);
510
511   MountPointDeviceMetadataMap::const_iterator device_info =
512       device_metadata_.find(base::FilePath(device_location));
513   // If the device isn't type removable (like a CD), it won't be there.
514   if (device_info == device_metadata_.end())
515     return;
516
517   if (notifications_)
518     notifications_->ProcessDetach(device_info->second.device_id());
519   device_metadata_.erase(device_info);
520 }
521
522 void VolumeMountWatcherWin::EjectDevice(
523     const std::string& device_id,
524     base::OnceCallback<void(StorageMonitor::EjectStatus)> callback) {
525   DCHECK_CURRENTLY_ON(BrowserThread::UI);
526   base::FilePath device = MediaStorageUtil::FindDevicePathById(device_id);
527   if (device.empty()) {
528     std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
529     return;
530   }
531   if (device_metadata_.erase(device) == 0) {
532     std::move(callback).Run(StorageMonitor::EJECT_FAILURE);
533     return;
534   }
535
536   device_info_task_runner_->PostTask(
537       FROM_HERE,
538       base::BindOnce(&EjectDeviceInThreadPool, device, std::move(callback),
539                      device_info_task_runner_, 0));
540 }
541
542 }  // namespace storage_monitor