| 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 |