OLD | NEW |
| (Empty) |
1 // Copyright 2004-2009 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 // | |
16 // Disk functions | |
17 | |
18 #include "omaha/base/disk.h" | |
19 | |
20 #include <winioctl.h> | |
21 #include "omaha/base/commontypes.h" | |
22 #include "omaha/base/const_config.h" | |
23 #include "omaha/base/debug.h" | |
24 #include "omaha/base/error.h" | |
25 #include "omaha/base/file.h" | |
26 #include "omaha/base/localization.h" | |
27 #include "omaha/base/logging.h" | |
28 #include "omaha/base/shell.h" | |
29 #include "omaha/base/string.h" | |
30 #include "omaha/base/synchronized.h" | |
31 #include "omaha/base/system.h" | |
32 #include "omaha/base/timer.h" | |
33 #include "omaha/base/utils.h" | |
34 | |
35 namespace omaha { | |
36 | |
37 #define kNetdiskVendorId "netdisk" | |
38 | |
39 // see also: http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q264203 | |
40 | |
41 #if _MSC_VER < 1400 | |
42 // Not defined in the headers we have; from MSDN: | |
43 #define IOCTL_STORAGE_QUERY_PROPERTY \ | |
44 CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS) | |
45 | |
46 typedef struct _STORAGE_DEVICE_DESCRIPTOR { | |
47 ULONG Version; | |
48 ULONG Size; | |
49 UCHAR DeviceType; | |
50 UCHAR DeviceTypeModifier; | |
51 BOOLEAN RemovableMedia; | |
52 BOOLEAN CommandQueueing; | |
53 ULONG VendorIdOffset; | |
54 ULONG ProductIdOffset; | |
55 ULONG ProductRevisionOffset; | |
56 ULONG SerialNumberOffset; | |
57 STORAGE_BUS_TYPE BusType; | |
58 ULONG RawPropertiesLength; | |
59 UCHAR RawDeviceProperties[1]; | |
60 } STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR; | |
61 | |
62 typedef enum _STORAGE_QUERY_TYPE { | |
63 PropertyStandardQuery = 0, | |
64 PropertyExistsQuery, | |
65 PropertyMaskQuery, | |
66 PropertyQueryMaxDefined | |
67 } STORAGE_QUERY_TYPE, *PSTORAGE_QUERY_TYPE; | |
68 | |
69 typedef enum _STORAGE_PROPERTY_ID { | |
70 StorageDeviceProperty = 0, | |
71 StorageAdapterProperty, | |
72 StorageDeviceIdProperty | |
73 } STORAGE_PROPERTY_ID, *PSTORAGE_PROPERTY_ID; | |
74 | |
75 typedef struct _STORAGE_PROPERTY_QUERY { | |
76 STORAGE_PROPERTY_ID PropertyId; | |
77 STORAGE_QUERY_TYPE QueryType; | |
78 UCHAR AdditionalParameters[1]; | |
79 } STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY; | |
80 | |
81 // ------- | |
82 #endif | |
83 | |
84 #define kIoctlBufferSize 1024 | |
85 | |
86 #define kMaxDrivesCached 3 | |
87 #define kMaxDriveLen 20 | |
88 static TCHAR g_cache_drive[kMaxDrivesCached][kMaxDriveLen+1]; | |
89 static bool g_cache_external[kMaxDrivesCached]; | |
90 static int g_cache_pos; | |
91 static LLock g_cache_lock; | |
92 | |
93 bool IsDiskExternal(const TCHAR *drive) { | |
94 ASSERT(drive, (L"")); | |
95 ASSERT(lstrlen(drive) < kMaxDriveLen, (L"")); | |
96 | |
97 DisableThreadErrorUI disable_error_dialog_box; | |
98 | |
99 { | |
100 __mutexScope(g_cache_lock); | |
101 for (int i = 0; i < kMaxDrivesCached; i++) | |
102 if (!lstrcmp(drive, g_cache_drive[i])) { | |
103 UTIL_LOG(L1, (L"cached disk ext %s %d", drive, g_cache_external[i])); | |
104 return g_cache_external[i]; | |
105 } | |
106 } | |
107 | |
108 #ifdef _DEBUG | |
109 Timer timer(true); | |
110 #endif | |
111 | |
112 byte buffer[kIoctlBufferSize+1]; | |
113 | |
114 bool external = false; | |
115 HANDLE device = ::CreateFile(drive, | |
116 GENERIC_READ, | |
117 FILE_SHARE_READ | FILE_SHARE_WRITE, | |
118 NULL, | |
119 OPEN_EXISTING, | |
120 NULL, | |
121 NULL); | |
122 if (device == INVALID_HANDLE_VALUE) { | |
123 UTIL_LOG(L1, (L"disk external could not open drive %s", drive)); | |
124 goto done; | |
125 } | |
126 STORAGE_DEVICE_DESCRIPTOR *device_desc; | |
127 STORAGE_PROPERTY_QUERY query; | |
128 DWORD out_bytes; | |
129 query.PropertyId = StorageDeviceProperty; | |
130 query.QueryType = PropertyStandardQuery; | |
131 *(query.AdditionalParameters) = 0; | |
132 | |
133 device_desc = reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(buffer); | |
134 // should not be needed, but just to be safer | |
135 ZeroMemory(buffer, kIoctlBufferSize); | |
136 | |
137 BOOL ok = ::DeviceIoControl(device, | |
138 IOCTL_STORAGE_QUERY_PROPERTY, | |
139 &query, | |
140 sizeof(STORAGE_PROPERTY_QUERY), | |
141 buffer, | |
142 kIoctlBufferSize, | |
143 &out_bytes, | |
144 (LPOVERLAPPED)NULL); | |
145 | |
146 if (ok && | |
147 device_desc->VendorIdOffset && | |
148 stristr(reinterpret_cast<char*>(buffer + device_desc->VendorIdOffset), | |
149 kNetdiskVendorId)) { | |
150 external = true; | |
151 UTIL_LOG(L1, (L"ximeta netdisk %s", drive)); | |
152 } | |
153 | |
154 if (ok && | |
155 (device_desc->BusType == BusTypeUsb || | |
156 device_desc->BusType == BusType1394)) { | |
157 external = true; | |
158 } | |
159 if (!ok) { | |
160 UTIL_LOG(L1, (L"disk external ioctl failed %s", drive)); | |
161 } | |
162 CloseHandle(device); | |
163 done: | |
164 UTIL_LOG(L1, (L"disk external %s %d time %s", | |
165 drive, external, String_DoubleToString(timer.GetMilliseconds(), 3))); | |
166 | |
167 { | |
168 __mutexScope(g_cache_lock); | |
169 lstrcpyn(g_cache_drive[g_cache_pos], drive, kMaxDriveLen+1); | |
170 g_cache_external[g_cache_pos] = external; | |
171 if (++g_cache_pos >= kMaxDrivesCached) g_cache_pos = 0; | |
172 } | |
173 | |
174 return external; | |
175 } | |
176 | |
177 // find the first fixed local disk with at least the space requested | |
178 // confirms that we can create a directory on the drive | |
179 // returns the drive in the drive parameter | |
180 // returns E_FAIL if no drive with enough space could be found | |
181 HRESULT FindFirstLocalDriveWithEnoughSpace(const uint64 space_required, | |
182 CString *drive) { | |
183 ASSERT1(drive); | |
184 | |
185 DisableThreadErrorUI disable_error_dialog_box; | |
186 | |
187 const int kMaxNumDrives = 26; | |
188 static const size_t kBufLen = (STR_SIZE("c:\\\0") * kMaxNumDrives) + 1; | |
189 | |
190 // obtain the fixed system drives | |
191 TCHAR buf[kBufLen]; | |
192 DWORD str_len = ::GetLogicalDriveStrings(kBufLen, buf); | |
193 if (str_len > 0 && str_len < kBufLen) { | |
194 for (TCHAR* ptr = buf; *ptr != L'\0'; ptr += (lstrlen(ptr) + 1)) { | |
195 UINT drive_type = GetDriveType(ptr); | |
196 if (drive_type == DRIVE_FIXED) { | |
197 CString test_drive(ptr); | |
198 if (!IsDiskExternal(CString(L"\\\\?\\") + test_drive.Left(2))) { | |
199 uint64 free_disk_space = 0; | |
200 HRESULT hr = GetFreeDiskSpace(test_drive, &free_disk_space); | |
201 | |
202 if (SUCCEEDED(hr) && space_required <= free_disk_space) { | |
203 CString temp_dir; | |
204 // confirm that we can create a directory on this drive | |
205 bool found = false; | |
206 while (!found) { | |
207 temp_dir = test_drive + | |
208 NOTRANSL(L"test") + | |
209 itostr(static_cast<uint32>(::GetTickCount())); | |
210 if (!File::Exists (temp_dir)) found = true; | |
211 } | |
212 | |
213 if (SUCCEEDED(CreateDir(temp_dir, NULL))) { | |
214 VERIFY1(SUCCEEDED(DeleteDirectory(temp_dir))); | |
215 *drive = test_drive; | |
216 UTIL_LOG(L1, (L"drive %s enough space %d", test_drive.GetString(), | |
217 free_disk_space)); | |
218 return S_OK; | |
219 } | |
220 } | |
221 } | |
222 } | |
223 } | |
224 } | |
225 | |
226 return E_FAIL; | |
227 } | |
228 | |
229 // Get free disk space of a drive containing the specified folder | |
230 HRESULT GetFreeDiskSpace(uint32 csidl, uint64* free_disk_space) { | |
231 ASSERT1(free_disk_space); | |
232 | |
233 CString path; | |
234 RET_IF_FAILED(Shell::GetSpecialFolder(csidl, false, &path)); | |
235 | |
236 return GetFreeDiskSpace(path, free_disk_space); | |
237 } | |
238 | |
239 // Get free disk space of a drive containing the specified folder | |
240 HRESULT GetFreeDiskSpace(const TCHAR* folder, uint64* free_disk_space) { | |
241 ASSERT1(folder && *folder); | |
242 ASSERT1(free_disk_space); | |
243 | |
244 DisableThreadErrorUI disable_error_dialog_box; | |
245 | |
246 CString drive(folder); | |
247 | |
248 // (Stupid API used by System::GetDiskStatistics will work with any folder - | |
249 // as long as it EXISTS. Since the data storage folder might not exist yet | |
250 // (e.g., on a clean install) we'll just truncate it down to a drive letter.) | |
251 drive = drive.Left(3); // "X:\" | |
252 ASSERT1(String_EndsWith(drive, _T(":\\"), false)); | |
253 | |
254 // Get the free disk space available to this user on this drive | |
255 uint64 free_bytes_current_user = 0LL; | |
256 uint64 total_bytes_current_user = 0LL; | |
257 uint64 free_bytes_all_users = 0LL; | |
258 RET_IF_FAILED(System::GetDiskStatistics(drive, | |
259 &free_bytes_current_user, | |
260 &total_bytes_current_user, | |
261 &free_bytes_all_users)); | |
262 | |
263 *free_disk_space = std::min(free_bytes_current_user, free_bytes_all_users); | |
264 | |
265 return S_OK; | |
266 } | |
267 | |
268 // Has enough free disk space on a drive containing the specified folder | |
269 HRESULT HasEnoughFreeDiskSpace(uint32 csidl, uint64 disk_space_needed) { | |
270 uint64 free_disk_space = 0; | |
271 if (SUCCEEDED(GetFreeDiskSpace(csidl, &free_disk_space))) { | |
272 return (disk_space_needed <= free_disk_space) ? | |
273 S_OK : CI_E_NOT_ENOUGH_DISK_SPACE; | |
274 } | |
275 return S_OK; | |
276 } | |
277 | |
278 // Has enough free disk space on a drive containing the specified folder | |
279 HRESULT HasEnoughFreeDiskSpace(const TCHAR* folder, uint64 disk_space_needed) { | |
280 uint64 free_disk_space = 0; | |
281 if (SUCCEEDED(GetFreeDiskSpace(folder, &free_disk_space))) { | |
282 return (disk_space_needed <= free_disk_space) ? | |
283 S_OK : CI_E_NOT_ENOUGH_DISK_SPACE; | |
284 } | |
285 return S_OK; | |
286 } | |
287 | |
288 // The ::CreateFile() call will fail for mapped local drives (subst). | |
289 bool IsHotPluggable(const TCHAR* drive) { | |
290 ASSERT(drive, (L"")); | |
291 | |
292 // Disable potential error dialogs during this check | |
293 DisableThreadErrorUI disable_error_dialog_box; | |
294 | |
295 // | |
296 // We set the default return value to true so that | |
297 // we treat the disk as hot-pluggable in case we | |
298 // don't know. | |
299 // | |
300 bool ret = true; | |
301 | |
302 if (drive && lstrlen(drive) >= 2) { | |
303 CString volume_path(_T("\\\\.\\")); | |
304 // We don't want the trailing backslash. | |
305 volume_path.Append(drive, 2); | |
306 | |
307 CHandle volume(::CreateFile(volume_path, GENERIC_READ, | |
308 FILE_SHARE_READ | FILE_SHARE_WRITE, | |
309 NULL, OPEN_EXISTING, 0, NULL)); | |
310 | |
311 if (volume != INVALID_HANDLE_VALUE) { | |
312 STORAGE_HOTPLUG_INFO shi = {0}; | |
313 shi.Size = sizeof(shi); | |
314 DWORD bytes_returned = 0; | |
315 if (::DeviceIoControl(volume, IOCTL_STORAGE_GET_HOTPLUG_INFO, NULL, 0, | |
316 &shi, sizeof(STORAGE_HOTPLUG_INFO), &bytes_returned, | |
317 NULL)) { | |
318 ret = (shi.DeviceHotplug != false); | |
319 } else { | |
320 UTIL_LOG(LW, (_T("[::DeviceIoControl failed][%u]"), ::GetLastError())); | |
321 } | |
322 } else { | |
323 UTIL_LOG(LW, (_T("[::CreateFile failed][%u]"), ::GetLastError())); | |
324 } | |
325 } else { | |
326 ASSERT(false, (L"Invalid path")); | |
327 } | |
328 | |
329 return ret; | |
330 } | |
331 | |
332 bool IsLargeDrive(const TCHAR* drive) { | |
333 ASSERT1(drive && *drive); | |
334 | |
335 DisableThreadErrorUI disable_error_dialog_box; | |
336 | |
337 ULARGE_INTEGER caller_free_bytes = {0}; | |
338 ULARGE_INTEGER total_bytes = {0}; | |
339 ULARGE_INTEGER total_free_bytes = {0}; | |
340 | |
341 if (!::GetDiskFreeSpaceEx(drive, | |
342 &caller_free_bytes, | |
343 &total_bytes, | |
344 &total_free_bytes)) { | |
345 HRESULT hr = HRESULTFromLastError(); | |
346 UTIL_LOG(LEVEL_ERROR, | |
347 (_T("[IsLargeDrive - failed to GetDiskFreeSpaceEx][0x%x]"), hr)); | |
348 return false; | |
349 } | |
350 | |
351 return (total_bytes.QuadPart > kLargeDriveSize); | |
352 } | |
353 | |
354 HRESULT DevicePathToDosPath(const TCHAR* device_path, CString* dos_path) { | |
355 ASSERT1(device_path); | |
356 ASSERT1(dos_path); | |
357 UTIL_LOG(L4, (_T("[DevicePathToDosPath][device_path=%s]"), device_path)); | |
358 | |
359 dos_path->Empty(); | |
360 | |
361 TCHAR drive_strings[MAX_PATH] = _T(""); | |
362 if (!::GetLogicalDriveStrings(arraysize(drive_strings), drive_strings)) { | |
363 UTIL_LOG(L4, (_T("[DevicePathToDosPath-GetLogicalDriveStrings fail][0x%x]"), | |
364 HRESULTFromLastError())); | |
365 return HRESULTFromLastError(); | |
366 } | |
367 | |
368 // Drive strings are stored as a set of null terminated strings, with an | |
369 // extra null after the last string. Each drive string is of the form "C:\". | |
370 // We convert it to the form "C:", which is the format expected by | |
371 // ::QueryDosDevice(). | |
372 TCHAR drive_colon[3] = _T(" :"); | |
373 for (const TCHAR* next_drive_letter = drive_strings; | |
374 *next_drive_letter; | |
375 next_drive_letter += _tcslen(next_drive_letter) + 1) { | |
376 // Dos device of the form "C:". | |
377 *drive_colon = *next_drive_letter; | |
378 TCHAR device_name[MAX_PATH] = _T(""); | |
379 if (!::QueryDosDevice(drive_colon, device_name, arraysize(device_name))) { | |
380 UTIL_LOG(LEVEL_ERROR, (_T("[QueryDosDevice failed][0x%x]"), | |
381 HRESULTFromLastError())); | |
382 continue; | |
383 } | |
384 | |
385 UTIL_LOG(L4, (_T("[DevicePathToDosPath found drive]") | |
386 _T("[logical drive %s][device name %s]"), | |
387 drive_colon, device_name)); | |
388 | |
389 size_t name_length = _tcslen(device_name); | |
390 if (_tcsnicmp(device_path, device_name, name_length) == 0) { | |
391 // Construct DOS path. | |
392 dos_path->Format(_T("%s%s"), drive_colon, device_path + name_length); | |
393 UTIL_LOG(L4, (_T("[DevicePathToDosPath][dos_path=%s]"), *dos_path)); | |
394 return S_OK; | |
395 } | |
396 } | |
397 | |
398 return HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE); | |
399 } | |
400 | |
401 } // namespace omaha | |
OLD | NEW |