"Fossies" - the Fresh Open Source Software Archive 
Member "rufus-3.13/src/drive.c" (20 Nov 2020, 86100 Bytes) of package /linux/misc/rufus-3.13.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "drive.c" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
3.12_vs_3.13.
1 /*
2 * Rufus: The Reliable USB Formatting Utility
3 * Drive access function calls
4 * Copyright © 2011-2020 Pete Batard <pete@akeo.ie>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #ifdef _CRTDBG_MAP_ALLOC
20 #include <stdlib.h>
21 #include <crtdbg.h>
22 #endif
23
24 #include <windows.h>
25 #include <windowsx.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <assert.h>
30 #if !defined(__MINGW32__)
31 #include <initguid.h>
32 #endif
33 #include <vds.h>
34
35 #include "rufus.h"
36 #include "missing.h"
37 #include "resource.h"
38 #include "settings.h"
39 #include "msapi_utf8.h"
40 #include "localization.h"
41
42 #include "file.h"
43 #include "drive.h"
44 #include "mbr_types.h"
45 #include "gpt_types.h"
46 #include "br.h"
47 #include "fat16.h"
48 #include "fat32.h"
49 #include "ntfs.h"
50
51 #define GLOBALROOT_NAME "\\\\?\\GLOBALROOT"
52 const char* sfd_name = "Super Floppy Disk";
53 const char* groot_name = GLOBALROOT_NAME;
54 const size_t groot_len = sizeof(GLOBALROOT_NAME) - 1;
55
56
57 #if defined(__MINGW32__)
58 const IID CLSID_VdsLoader = { 0x9c38ed61, 0xd565, 0x4728, { 0xae, 0xee, 0xc8, 0x09, 0x52, 0xf0, 0xec, 0xde } };
59 const IID IID_IVdsServiceLoader = { 0xe0393303, 0x90d4, 0x4a97, { 0xab, 0x71, 0xe9, 0xb6, 0x71, 0xee, 0x27, 0x29 } };
60 const IID IID_IVdsProvider = { 0x10c5e575, 0x7984, 0x4e81, { 0xa5, 0x6b, 0x43, 0x1f, 0x5f, 0x92, 0xae, 0x42 } };
61 const IID IID_IVdsSwProvider = { 0x9aa58360, 0xce33, 0x4f92, { 0xb6, 0x58, 0xed, 0x24, 0xb1, 0x44, 0x25, 0xb8 } };
62 const IID IID_IVdsPack = { 0x3b69d7f5, 0x9d94, 0x4648, { 0x91, 0xca, 0x79, 0x93, 0x9b, 0xa2, 0x63, 0xbf } };
63 const IID IID_IVdsDisk = { 0x07e5c822, 0xf00c, 0x47a1, { 0x8f, 0xce, 0xb2, 0x44, 0xda, 0x56, 0xfd, 0x06 } };
64 const IID IID_IVdsAdvancedDisk = { 0x6e6f6b40, 0x977c, 0x4069, { 0xbd, 0xdd, 0xac, 0x71, 0x00, 0x59, 0xf8, 0xc0 } };
65 const IID IID_IVdsVolume = { 0x88306BB2, 0xE71F, 0x478C, { 0x86, 0xA2, 0x79, 0xDA, 0x20, 0x0A, 0x0F, 0x11} };
66 const IID IID_IVdsVolumeMF3 = { 0x6788FAF9, 0x214E, 0x4B85, { 0xBA, 0x59, 0x26, 0x69, 0x53, 0x61, 0x6E, 0x09 } };
67 #endif
68
69 PF_TYPE_DECL(NTAPI, NTSTATUS, NtQueryVolumeInformationFile, (HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FS_INFORMATION_CLASS));
70
71 /*
72 * Globals
73 */
74 RUFUS_DRIVE_INFO SelectedDrive;
75 extern BOOL installed_uefi_ntfs, write_as_esp;
76 extern int nWindowsVersion, nWindowsBuildNumber;
77 uint64_t partition_offset[PI_MAX];
78 uint64_t persistence_size = 0;
79
80 /*
81 * The following methods get or set the AutoMount setting (which is different from AutoRun)
82 * Rufus needs AutoMount to be set as the format process may fail for fixed drives otherwise.
83 * See https://github.com/pbatard/rufus/issues/386.
84 *
85 * Reverse engineering diskpart and mountvol indicates that the former uses the IVdsService
86 * ClearFlags()/SetFlags() to set VDS_SVF_AUTO_MOUNT_OFF whereas mountvol on uses
87 * IOCTL_MOUNTMGR_SET_AUTO_MOUNT on "\\\\.\\MountPointManager".
88 * As the latter is MUCH simpler this is what we'll use too
89 */
90 BOOL SetAutoMount(BOOL enable)
91 {
92 HANDLE hMountMgr;
93 DWORD size;
94 BOOL ret = FALSE;
95
96 hMountMgr = CreateFileA(MOUNTMGR_DOS_DEVICE_NAME, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
97 if (hMountMgr == INVALID_HANDLE_VALUE)
98 return FALSE;
99 ret = DeviceIoControl(hMountMgr, IOCTL_MOUNTMGR_SET_AUTO_MOUNT, &enable, sizeof(enable), NULL, 0, &size, NULL);
100 CloseHandle(hMountMgr);
101 return ret;
102 }
103
104 BOOL GetAutoMount(BOOL* enabled)
105 {
106 HANDLE hMountMgr;
107 DWORD size;
108 BOOL ret = FALSE;
109
110 if (enabled == NULL)
111 return FALSE;
112 hMountMgr = CreateFileA(MOUNTMGR_DOS_DEVICE_NAME, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
113 if (hMountMgr == INVALID_HANDLE_VALUE)
114 return FALSE;
115 ret = DeviceIoControl(hMountMgr, IOCTL_MOUNTMGR_QUERY_AUTO_MOUNT, NULL, 0, enabled, sizeof(*enabled), &size, NULL);
116 CloseHandle(hMountMgr);
117 return ret;
118 }
119
120 /*
121 * Working with drive indexes quite risky (left unchecked,inadvertently passing 0 as
122 * index would return a handle to C:, which we might then proceed to unknowingly
123 * clear the MBR of!), so we mitigate the risk by forcing our indexes to belong to
124 * the specific range [DRIVE_INDEX_MIN; DRIVE_INDEX_MAX].
125 */
126 #define CheckDriveIndex(DriveIndex) do { \
127 if ((int)DriveIndex < 0) goto out; \
128 assert((DriveIndex >= DRIVE_INDEX_MIN) && (DriveIndex <= DRIVE_INDEX_MAX)); \
129 if ((DriveIndex < DRIVE_INDEX_MIN) || (DriveIndex > DRIVE_INDEX_MAX)) goto out; \
130 DriveIndex -= DRIVE_INDEX_MIN; } while (0)
131
132 /*
133 * Open a drive or volume with optional write and lock access
134 * Return INVALID_HANDLE_VALUE (/!\ which is DIFFERENT from NULL /!\) on failure.
135 */
136 static HANDLE GetHandle(char* Path, BOOL bLockDrive, BOOL bWriteAccess, BOOL bWriteShare)
137 {
138 int i;
139 BYTE access_mask = 0;
140 DWORD size;
141 uint64_t EndTime;
142 HANDLE hDrive = INVALID_HANDLE_VALUE;
143 char DevPath[MAX_PATH];
144
145 if ((safe_strlen(Path) < 5) || (Path[0] != '\\') || (Path[1] != '\\') || (Path[3] != '\\'))
146 goto out;
147
148 // Resolve a device path, so that we can look for that handle in case of access issues.
149 if (safe_strncmp(Path, groot_name, groot_len) == 0)
150 static_strcpy(DevPath, &Path[groot_len]);
151 else if (QueryDosDeviceA(&Path[4], DevPath, sizeof(DevPath)) == 0)
152 strcpy(DevPath, "???");
153
154 for (i = 0; i < DRIVE_ACCESS_RETRIES; i++) {
155 // Try without FILE_SHARE_WRITE (unless specifically requested) so that
156 // we won't be bothered by the OS or other apps when we set up our data.
157 // However this means we might have to wait for an access gap...
158 // We keep FILE_SHARE_READ though, as this shouldn't hurt us any, and is
159 // required for enumeration.
160 hDrive = CreateFileA(Path, GENERIC_READ|(bWriteAccess?GENERIC_WRITE:0),
161 FILE_SHARE_READ|(bWriteShare?FILE_SHARE_WRITE:0),
162 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
163 if (hDrive != INVALID_HANDLE_VALUE)
164 break;
165 if ((GetLastError() != ERROR_SHARING_VIOLATION) && (GetLastError() != ERROR_ACCESS_DENIED))
166 break;
167 if (i == 0) {
168 uprintf("Notice: Volume Device Path is %s", DevPath);
169 uprintf("Waiting for access on %s...", Path);
170 } else if (!bWriteShare && (i > DRIVE_ACCESS_RETRIES/3)) {
171 // If we can't seem to get a hold of the drive for some time, try to enable FILE_SHARE_WRITE...
172 uprintf("Warning: Could not obtain exclusive rights. Retrying with write sharing enabled...");
173 bWriteShare = TRUE;
174 // Try to report the process that is locking the drive
175 // We also use bit 6 as a flag to indicate that SearchProcess was called.
176 access_mask = SearchProcess(DevPath, SEARCH_PROCESS_TIMEOUT, TRUE, TRUE, FALSE) | 0x40;
177 }
178 Sleep(DRIVE_ACCESS_TIMEOUT / DRIVE_ACCESS_RETRIES);
179 }
180 if (hDrive == INVALID_HANDLE_VALUE) {
181 uprintf("Could not open %s: %s", Path, WindowsErrorString());
182 goto out;
183 }
184
185 if (bWriteAccess) {
186 uprintf("Opened %s for %s write access", Path, bWriteShare?"shared":"exclusive");
187 }
188
189 if (bLockDrive) {
190 if (DeviceIoControl(hDrive, FSCTL_ALLOW_EXTENDED_DASD_IO, NULL, 0, NULL, 0, &size, NULL)) {
191 uprintf("I/O boundary checks disabled");
192 }
193
194 EndTime = GetTickCount64() + DRIVE_ACCESS_TIMEOUT;
195 do {
196 if (DeviceIoControl(hDrive, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &size, NULL))
197 goto out;
198 if (IS_ERROR(FormatStatus)) // User cancel
199 break;
200 Sleep(DRIVE_ACCESS_TIMEOUT / DRIVE_ACCESS_RETRIES);
201 } while (GetTickCount64() < EndTime);
202 // If we reached this section, either we didn't manage to get a lock or the user cancelled
203 uprintf("Could not lock access to %s: %s", Path, WindowsErrorString());
204 // See if we can report the processes are accessing the drive
205 if (!IS_ERROR(FormatStatus) && (access_mask == 0))
206 access_mask = SearchProcess(DevPath, SEARCH_PROCESS_TIMEOUT, TRUE, TRUE, FALSE);
207 // Try to continue if the only access rights we saw were for read-only
208 if ((access_mask & 0x07) != 0x01)
209 safe_closehandle(hDrive);
210 }
211
212 out:
213 return hDrive;
214 }
215
216 /*
217 * Return the path to access the physical drive, or NULL on error.
218 * The string is allocated and must be freed (to ensure concurrent access)
219 */
220 char* GetPhysicalName(DWORD DriveIndex)
221 {
222 BOOL success = FALSE;
223 char physical_name[24];
224
225 CheckDriveIndex(DriveIndex);
226 static_sprintf(physical_name, "\\\\.\\PhysicalDrive%lu", DriveIndex);
227 success = TRUE;
228 out:
229 return (success)?safe_strdup(physical_name):NULL;
230 }
231
232 /*
233 * Return a handle to the physical drive identified by DriveIndex
234 */
235 HANDLE GetPhysicalHandle(DWORD DriveIndex, BOOL bLockDrive, BOOL bWriteAccess, BOOL bWriteShare)
236 {
237 HANDLE hPhysical = INVALID_HANDLE_VALUE;
238 char* PhysicalPath = GetPhysicalName(DriveIndex);
239 hPhysical = GetHandle(PhysicalPath, bLockDrive, bWriteAccess, bWriteShare);
240 safe_free(PhysicalPath);
241 return hPhysical;
242 }
243
244 /*
245 * Return the GUID volume name for the disk and partition specified, or NULL if not found.
246 * See http://msdn.microsoft.com/en-us/library/cc542456.aspx
247 * If PartitionOffset is 0, the offset is ignored and the first partition found is returned.
248 * The returned string is allocated and must be freed.
249 */
250 char* GetLogicalName(DWORD DriveIndex, uint64_t PartitionOffset, BOOL bKeepTrailingBackslash, BOOL bSilent)
251 {
252 static const char* ignore_device[] = { "\\Device\\CdRom", "\\Device\\Floppy" };
253 static const char* volume_start = "\\\\?\\";
254 char *ret = NULL, volume_name[MAX_PATH], path[MAX_PATH];
255 BOOL bPrintHeader = TRUE;
256 HANDLE hDrive = INVALID_HANDLE_VALUE, hVolume = INVALID_HANDLE_VALUE;
257 VOLUME_DISK_EXTENTS_REDEF DiskExtents;
258 DWORD size;
259 UINT drive_type;
260 StrArray found_name;
261 uint64_t found_offset[MAX_PARTITIONS] = { 0 };
262 uint32_t i, j;
263 size_t len;
264
265 StrArrayCreate(&found_name, MAX_PARTITIONS);
266 CheckDriveIndex(DriveIndex);
267
268 for (i = 0; hDrive == INVALID_HANDLE_VALUE; i++) {
269 if (i == 0) {
270 hVolume = FindFirstVolumeA(volume_name, sizeof(volume_name));
271 if (hVolume == INVALID_HANDLE_VALUE) {
272 suprintf("Could not access first GUID volume: %s", WindowsErrorString());
273 goto out;
274 }
275 } else {
276 if (!FindNextVolumeA(hVolume, volume_name, sizeof(volume_name))) {
277 if (GetLastError() != ERROR_NO_MORE_FILES) {
278 suprintf("Could not access next GUID volume: %s", WindowsErrorString());
279 }
280 break;
281 }
282 }
283
284 // Sanity checks
285 len = safe_strlen(volume_name);
286 assert(len > 4);
287 assert(safe_strnicmp(volume_name, volume_start, 4) == 0);
288 assert(volume_name[len - 1] == '\\');
289
290 drive_type = GetDriveTypeA(volume_name);
291 if ((drive_type != DRIVE_REMOVABLE) && (drive_type != DRIVE_FIXED))
292 continue;
293
294 volume_name[len-1] = 0;
295
296 if (QueryDosDeviceA(&volume_name[4], path, sizeof(path)) == 0) {
297 suprintf("Failed to get device path for GUID volume '%s': %s", volume_name, WindowsErrorString());
298 continue;
299 }
300
301 for (j=0; (j<ARRAYSIZE(ignore_device)) &&
302 (_strnicmp(path, ignore_device[j], safe_strlen(ignore_device[j])) != 0); j++);
303 if (j < ARRAYSIZE(ignore_device)) {
304 suprintf("Skipping GUID volume for '%s'", path);
305 continue;
306 }
307
308 hDrive = CreateFileA(volume_name, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
309 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
310 if (hDrive == INVALID_HANDLE_VALUE) {
311 suprintf("Could not open GUID volume '%s': %s", volume_name, WindowsErrorString());
312 continue;
313 }
314
315 if ((!DeviceIoControl(hDrive, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0,
316 &DiskExtents, sizeof(DiskExtents), &size, NULL)) || (size <= 0)) {
317 suprintf("Could not get Disk Extents: %s", WindowsErrorString());
318 safe_closehandle(hDrive);
319 continue;
320 }
321 safe_closehandle(hDrive);
322 if (DiskExtents.NumberOfDiskExtents == 0) {
323 suprintf("Ignoring volume '%s' because it has no extents...", volume_name);
324 continue;
325 }
326 if (DiskExtents.NumberOfDiskExtents != 1) {
327 // If we have more than one extent for a volume, it means that someone
328 // is using RAID-1 or something => Stay well away from such a volume!
329 suprintf("Ignoring volume '%s' because it has more than one extent (RAID?)...", volume_name);
330 continue;
331 }
332 if (DiskExtents.Extents[0].DiskNumber != DriveIndex)
333 // Not on our disk
334 continue;
335
336 if (found_name.Index == MAX_PARTITIONS) {
337 uprintf("Error: Trying to process a disk with more than %d partitions!", MAX_PARTITIONS);
338 goto out;
339 }
340
341 if (bKeepTrailingBackslash)
342 volume_name[len - 1] = '\\';
343 found_offset[found_name.Index] = DiskExtents.Extents[0].StartingOffset.QuadPart;
344 StrArrayAdd(&found_name, volume_name, TRUE);
345 if (!bSilent) {
346 if (bPrintHeader) {
347 bPrintHeader = FALSE;
348 uuprintf("Windows volumes from this device:");
349 }
350 uuprintf("● %s @%lld", volume_name, DiskExtents.Extents[0].StartingOffset.QuadPart);
351 }
352 }
353
354 if (found_name.Index == 0)
355 goto out;
356
357 // Now process all the volumes we found, and try to match one with our partition offset
358 for (i = 0; (i < found_name.Index) && (PartitionOffset != 0) && (PartitionOffset != found_offset[i]); i++);
359
360 if (i < found_name.Index) {
361 ret = safe_strdup(found_name.String[i]);
362 } else {
363 // NB: We need to re-add DRIVE_INDEX_MIN for this call since CheckDriveIndex() substracted it
364 ret = AltGetLogicalName(DriveIndex + DRIVE_INDEX_MIN, PartitionOffset, bKeepTrailingBackslash, bSilent);
365 if ((ret != NULL) && (strchr(ret, ' ') != NULL))
366 uprintf("Warning: Using physical device to access partition data");
367 }
368
369 out:
370 if (hVolume != INVALID_HANDLE_VALUE)
371 FindVolumeClose(hVolume);
372 StrArrayDestroy(&found_name);
373 return ret;
374 }
375
376 /*
377 * Alternative version of the above, needed because some volumes, such as ESPs, are not listed
378 * by Windows, be it with VDS or other APIs.
379 * For these, we return the "\\?\GLOBALROOT\Device\HarddiskVolume#" identifier that matches
380 * our "Harddisk#Partition#", as reported by QueryDosDevice().
381 * The returned string is allocated and must be freed.
382 */
383 char* AltGetLogicalName(DWORD DriveIndex, uint64_t PartitionOffset, BOOL bKeepTrailingBackslash, BOOL bSilent)
384 {
385 BOOL matching_drive = (DriveIndex == SelectedDrive.DeviceNumber);
386 DWORD i;
387 char *ret = NULL, volume_name[MAX_PATH], path[64];
388
389 CheckDriveIndex(DriveIndex);
390
391 // Match the offset to a partition index
392 if (PartitionOffset == 0) {
393 i = 0;
394 } else if (matching_drive) {
395 for (i = 0; (i < MAX_PARTITIONS) && (PartitionOffset != SelectedDrive.PartitionOffset[i]); i++);
396 if (i >= MAX_PARTITIONS) {
397 suprintf("Error: Could not find a partition at offset %lld on this disk", PartitionOffset);
398 goto out;
399 }
400 } else {
401 suprintf("Error: Searching for a partition on a non matching disk");
402 goto out;
403 }
404 static_sprintf(path, "Harddisk%luPartition%lu", DriveIndex, i + 1);
405 static_strcpy(volume_name, groot_name);
406 if (!QueryDosDeviceA(path, &volume_name[groot_len], (DWORD)(MAX_PATH - groot_len)) || (strlen(volume_name) < 20)) {
407 suprintf("Could not find a DOS volume name for '%s': %s", path, WindowsErrorString());
408 goto out;
409 } else if (bKeepTrailingBackslash) {
410 static_strcat(volume_name, "\\");
411 }
412 ret = safe_strdup(volume_name);
413
414 out:
415 return ret;
416 }
417
418 /*
419 * Custom volume name for extfs formatting (that includes partition offset and partition size)
420 * so that these can be created and accessed on pre 1703 versions of Windows.
421 */
422 char* GetExtPartitionName(DWORD DriveIndex, uint64_t PartitionOffset)
423 {
424 DWORD i;
425 char* ret = NULL, volume_name[MAX_PATH];
426
427 // Can't operate if we're not on the selected drive
428 if (DriveIndex != SelectedDrive.DeviceNumber)
429 goto out;
430 CheckDriveIndex(DriveIndex);
431 for (i = 0; (i < MAX_PARTITIONS) && (PartitionOffset != SelectedDrive.PartitionOffset[i]); i++);
432 if (i >= MAX_PARTITIONS)
433 goto out;
434 static_sprintf(volume_name, "\\\\.\\PhysicalDrive%lu %I64u %I64u", DriveIndex,
435 SelectedDrive.PartitionOffset[i], SelectedDrive.PartitionSize[i]);
436 ret = safe_strdup(volume_name);
437 out:
438 return ret;
439 }
440
441 static const char* VdsErrorString(HRESULT hr) {
442 SetLastError(hr);
443 return WindowsErrorString();
444 }
445
446 /*
447 * Call on VDS to refresh the drive layout
448 */
449 BOOL RefreshLayout(DWORD DriveIndex)
450 {
451 HRESULT hr = S_FALSE;
452 wchar_t wPhysicalName[24];
453 IVdsServiceLoader* pLoader = NULL;
454 IVdsService* pService = NULL;
455 IEnumVdsObject *pEnum;
456
457 CheckDriveIndex(DriveIndex);
458 wnsprintf(wPhysicalName, ARRAYSIZE(wPhysicalName), L"\\\\?\\PhysicalDrive%lu", DriveIndex);
459
460 // Initialize COM
461 IGNORE_RETVAL(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
462 IGNORE_RETVAL(CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT,
463 RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL));
464
465 // Create a VDS Loader Instance
466 hr = CoCreateInstance(&CLSID_VdsLoader, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER,
467 &IID_IVdsServiceLoader, (void **)&pLoader);
468 if (hr != S_OK) {
469 uprintf("Could not create VDS Loader Instance: %s", VdsErrorString(hr));
470 goto out;
471 }
472
473 // Load the VDS Service
474 hr = IVdsServiceLoader_LoadService(pLoader, L"", &pService);
475 if (hr != S_OK) {
476 uprintf("Could not load VDS Service: %s", VdsErrorString(hr));
477 goto out;
478 }
479
480 // Wait for the Service to become ready if needed
481 hr = IVdsService_WaitForServiceReady(pService);
482 if (hr != S_OK) {
483 uprintf("VDS Service is not ready: %s", VdsErrorString(hr));
484 goto out;
485 }
486
487 // Query the VDS Service Providers
488 hr = IVdsService_QueryProviders(pService, VDS_QUERY_SOFTWARE_PROVIDERS, &pEnum);
489 if (hr != S_OK) {
490 uprintf("Could not query VDS Service Providers: %s", VdsErrorString(hr));
491 goto out;
492 }
493
494 // Remove mountpoints
495 hr = IVdsService_CleanupObsoleteMountPoints(pService);
496 if (hr != S_OK) {
497 uprintf("Could not clean up VDS mountpoints: %s", VdsErrorString(hr));
498 goto out;
499 }
500
501 // Invoke layout refresh
502 hr = IVdsService_Refresh(pService);
503 if (hr != S_OK) {
504 uprintf("Could not refresh VDS layout: %s", VdsErrorString(hr));
505 goto out;
506 }
507
508 // Force re-enum
509 hr = IVdsService_Reenumerate(pService);
510 if (hr != S_OK) {
511 uprintf("Could not refresh VDS layout: %s", VdsErrorString(hr));
512 goto out;
513 }
514
515 out:
516 if (pService != NULL)
517 IVdsService_Release(pService);
518 if (pLoader != NULL)
519 IVdsServiceLoader_Release(pLoader);
520 VDS_SET_ERROR(hr);
521 return (hr == S_OK);
522 }
523
524 /*
525 * Generic call to instantiate a VDS Disk Interface. Mostly copied from:
526 * https://social.msdn.microsoft.com/Forums/vstudio/en-US/b90482ae-4e44-4b08-8731-81915030b32a/createpartition-using-vds-interface-throw-error-enointerface-dcom?forum=vcgeneral
527 * See also: https://docs.microsoft.com/en-us/windows/win32/vds/working-with-enumeration-objects
528 */
529 static BOOL GetVdsDiskInterface(DWORD DriveIndex, const IID* InterfaceIID, void** pInterfaceInstance, BOOL bSilent)
530 {
531 HRESULT hr = S_FALSE;
532 ULONG ulFetched;
533 wchar_t wPhysicalName[24];
534 IVdsServiceLoader* pLoader;
535 IVdsService* pService;
536 IEnumVdsObject* pEnum;
537 IUnknown* pUnk;
538
539 *pInterfaceInstance = NULL;
540 CheckDriveIndex(DriveIndex);
541 wnsprintf(wPhysicalName, ARRAYSIZE(wPhysicalName), L"\\\\?\\PhysicalDrive%lu", DriveIndex);
542
543 // Initialize COM
544 IGNORE_RETVAL(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
545 IGNORE_RETVAL(CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT,
546 RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL));
547
548 // Create a VDS Loader Instance
549 hr = CoCreateInstance(&CLSID_VdsLoader, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER,
550 &IID_IVdsServiceLoader, (void**)&pLoader);
551 if (hr != S_OK) {
552 suprintf("Could not create VDS Loader Instance: %s", VdsErrorString(hr));
553 goto out;
554 }
555
556 // Load the VDS Service
557 hr = IVdsServiceLoader_LoadService(pLoader, L"", &pService);
558 IVdsServiceLoader_Release(pLoader);
559 if (hr != S_OK) {
560 suprintf("Could not load VDS Service: %s", VdsErrorString(hr));
561 goto out;
562 }
563
564 // Wait for the Service to become ready if needed
565 hr = IVdsService_WaitForServiceReady(pService);
566 if (hr != S_OK) {
567 suprintf("VDS Service is not ready: %s", VdsErrorString(hr));
568 goto out;
569 }
570
571 // Query the VDS Service Providers
572 hr = IVdsService_QueryProviders(pService, VDS_QUERY_SOFTWARE_PROVIDERS, &pEnum);
573 IVdsService_Release(pService);
574 if (hr != S_OK) {
575 suprintf("Could not query VDS Service Providers: %s", VdsErrorString(hr));
576 goto out;
577 }
578
579 while (IEnumVdsObject_Next(pEnum, 1, &pUnk, &ulFetched) == S_OK) {
580 IVdsProvider* pProvider;
581 IVdsSwProvider* pSwProvider;
582 IEnumVdsObject* pEnumPack;
583 IUnknown* pPackUnk;
584
585 // Get VDS Provider
586 hr = IUnknown_QueryInterface(pUnk, &IID_IVdsProvider, (void**)&pProvider);
587 IUnknown_Release(pUnk);
588 if (hr != S_OK) {
589 suprintf("Could not get VDS Provider: %s", VdsErrorString(hr));
590 break;
591 }
592
593 // Get VDS Software Provider
594 hr = IVdsSwProvider_QueryInterface(pProvider, &IID_IVdsSwProvider, (void**)&pSwProvider);
595 IVdsProvider_Release(pProvider);
596 if (hr != S_OK) {
597 suprintf("Could not get VDS Software Provider: %s", VdsErrorString(hr));
598 break;
599 }
600
601 // Get VDS Software Provider Packs
602 hr = IVdsSwProvider_QueryPacks(pSwProvider, &pEnumPack);
603 IVdsSwProvider_Release(pSwProvider);
604 if (hr != S_OK) {
605 suprintf("Could not get VDS Software Provider Packs: %s", VdsErrorString(hr));
606 break;
607 }
608
609 // Enumerate Provider Packs
610 while (IEnumVdsObject_Next(pEnumPack, 1, &pPackUnk, &ulFetched) == S_OK) {
611 IVdsPack* pPack;
612 IEnumVdsObject* pEnumDisk;
613 IUnknown* pDiskUnk;
614
615 hr = IUnknown_QueryInterface(pPackUnk, &IID_IVdsPack, (void**)&pPack);
616 IUnknown_Release(pPackUnk);
617 if (hr != S_OK) {
618 suprintf("Could not query VDS Software Provider Pack: %s", VdsErrorString(hr));
619 break;
620 }
621
622 // Use the pack interface to access the disks
623 hr = IVdsPack_QueryDisks(pPack, &pEnumDisk);
624 IVdsPack_Release(pPack);
625 if (hr != S_OK) {
626 suprintf("Could not query VDS disks: %s", VdsErrorString(hr));
627 break;
628 }
629
630 // List disks
631 while (IEnumVdsObject_Next(pEnumDisk, 1, &pDiskUnk, &ulFetched) == S_OK) {
632 VDS_DISK_PROP prop;
633 IVdsDisk* pDisk;
634
635 // Get the disk interface.
636 hr = IUnknown_QueryInterface(pDiskUnk, &IID_IVdsDisk, (void**)&pDisk);
637 IUnknown_Release(pDiskUnk);
638 if (hr != S_OK) {
639 suprintf("Could not query VDS Disk Interface: %s", VdsErrorString(hr));
640 break;
641 }
642
643 // Get the disk properties
644 hr = IVdsDisk_GetProperties(pDisk, &prop);
645 if ((hr != S_OK) && (hr != VDS_S_PROPERTIES_INCOMPLETE)) {
646 IVdsDisk_Release(pDisk);
647 suprintf("Could not query VDS Disk Properties: %s", VdsErrorString(hr));
648 break;
649 }
650
651 // Check if we are on the target disk
652 hr = (HRESULT)_wcsicmp(wPhysicalName, prop.pwszName);
653 CoTaskMemFree(prop.pwszName);
654 if (hr != S_OK) {
655 hr = S_OK;
656 continue;
657 }
658
659 // Instantiate the requested VDS disk interface
660 hr = IVdsDisk_QueryInterface(pDisk, InterfaceIID, pInterfaceInstance);
661 IVdsDisk_Release(pDisk);
662 if (hr != S_OK)
663 suprintf("Could not access the requested Disk interface: %s", VdsErrorString(hr));
664
665 // With the interface found, we should be able to return
666 break;
667 }
668 IEnumVdsObject_Release(pEnumDisk);
669 }
670 IEnumVdsObject_Release(pEnumPack);
671 }
672 IEnumVdsObject_Release(pEnum);
673
674 out:
675 VDS_SET_ERROR(hr);
676 return (hr == S_OK);
677 }
678
679 /*
680 * Delete one partition at offset PartitionOffset, or all partitions if the offset is 0.
681 */
682 BOOL DeletePartition(DWORD DriveIndex, ULONGLONG PartitionOffset, BOOL bSilent)
683 {
684 HRESULT hr = S_FALSE;
685 VDS_PARTITION_PROP* prop_array;
686 LONG i, prop_array_size;
687 IVdsAdvancedDisk *pAdvancedDisk;
688
689 if (!GetVdsDiskInterface(DriveIndex, &IID_IVdsAdvancedDisk, (void**)&pAdvancedDisk, bSilent))
690 return FALSE;
691 if (pAdvancedDisk == NULL) {
692 suprintf("No partition to delete on disk");
693 return TRUE;
694 }
695
696 // Query the partition data, so we can get the start offset, which we need for deletion
697 hr = IVdsAdvancedDisk_QueryPartitions(pAdvancedDisk, &prop_array, &prop_array_size);
698 if (hr == S_OK) {
699 suprintf("Deleting partition%s:", (PartitionOffset == 0) ? "s" : "");
700 // Now go through each partition
701 for (i = 0; i < prop_array_size; i++) {
702 if ((PartitionOffset != 0) && (prop_array[i].ullOffset != PartitionOffset))
703 continue;
704 suprintf("● Partition %d (offset: %lld, size: %s)", prop_array[i].ulPartitionNumber,
705 prop_array[i].ullOffset, SizeToHumanReadable(prop_array[i].ullSize, FALSE, FALSE));
706 hr = IVdsAdvancedDisk_DeletePartition(pAdvancedDisk, prop_array[i].ullOffset, TRUE, TRUE);
707 if (hr != S_OK)
708 suprintf("Could not delete partition: %s", VdsErrorString(hr));
709 }
710 } else {
711 suprintf("No partition to delete on disk");
712 hr = S_OK;
713 }
714 CoTaskMemFree(prop_array);
715 IVdsAdvancedDisk_Release(pAdvancedDisk);
716 VDS_SET_ERROR(hr);
717 return (hr == S_OK);
718 }
719
720 /*
721 * Count on Microsoft for *COMPLETELY CRIPPLING* an API when alledgedly upgrading it...
722 * As illustrated when you do so with diskpart (which uses VDS behind the scenes), VDS
723 * simply *DOES NOT* list all the volumes that the system can see, especially compared
724 * to what mountvol (which uses FindFirstVolume()/FindNextVolume()) and other APIs do.
725 * Also for reference, if you want to list volumes through WMI in PowerShell:
726 * Get-WmiObject win32_volume | Format-Table -Property DeviceID,Name,Label,Capacity
727 */
728 BOOL ListVdsVolumes(BOOL bSilent)
729 {
730 HRESULT hr = S_FALSE;
731 ULONG ulFetched;
732 IVdsServiceLoader* pLoader;
733 IVdsService* pService;
734 IEnumVdsObject* pEnum;
735 IUnknown* pUnk;
736
737 // Initialize COM
738 IGNORE_RETVAL(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED));
739 IGNORE_RETVAL(CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT,
740 RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL));
741
742 // Create a VDS Loader Instance
743 hr = CoCreateInstance(&CLSID_VdsLoader, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER,
744 &IID_IVdsServiceLoader, (void**)&pLoader);
745 if (hr != S_OK) {
746 suprintf("Could not create VDS Loader Instance: %s", VdsErrorString(hr));
747 goto out;
748 }
749
750 // Load the VDS Service
751 hr = IVdsServiceLoader_LoadService(pLoader, L"", &pService);
752 IVdsServiceLoader_Release(pLoader);
753 if (hr != S_OK) {
754 suprintf("Could not load VDS Service: %s", VdsErrorString(hr));
755 goto out;
756 }
757
758 // Wait for the Service to become ready if needed
759 hr = IVdsService_WaitForServiceReady(pService);
760 if (hr != S_OK) {
761 suprintf("VDS Service is not ready: %s", VdsErrorString(hr));
762 goto out;
763 }
764
765 // Query the VDS Service Providers
766 hr = IVdsService_QueryProviders(pService, VDS_QUERY_SOFTWARE_PROVIDERS, &pEnum);
767 IVdsService_Release(pService);
768 if (hr != S_OK) {
769 suprintf("Could not query VDS Service Providers: %s", VdsErrorString(hr));
770 goto out;
771 }
772
773 while (IEnumVdsObject_Next(pEnum, 1, &pUnk, &ulFetched) == S_OK) {
774 IVdsProvider* pProvider;
775 IVdsSwProvider* pSwProvider;
776 IEnumVdsObject* pEnumPack;
777 IUnknown* pPackUnk;
778
779 // Get VDS Provider
780 hr = IUnknown_QueryInterface(pUnk, &IID_IVdsProvider, (void**)&pProvider);
781 IUnknown_Release(pUnk);
782 if (hr != S_OK) {
783 suprintf("Could not get VDS Provider: %s", VdsErrorString(hr));
784 break;
785 }
786
787 // Get VDS Software Provider
788 hr = IVdsSwProvider_QueryInterface(pProvider, &IID_IVdsSwProvider, (void**)&pSwProvider);
789 IVdsProvider_Release(pProvider);
790 if (hr != S_OK) {
791 suprintf("Could not get VDS Software Provider: %s", VdsErrorString(hr));
792 break;
793 }
794
795 // Get VDS Software Provider Packs
796 hr = IVdsSwProvider_QueryPacks(pSwProvider, &pEnumPack);
797 IVdsSwProvider_Release(pSwProvider);
798 if (hr != S_OK) {
799 suprintf("Could not get VDS Software Provider Packs: %s", VdsErrorString(hr));
800 break;
801 }
802
803 // Enumerate Provider Packs
804 while (IEnumVdsObject_Next(pEnumPack, 1, &pPackUnk, &ulFetched) == S_OK) {
805 IVdsPack* pPack;
806 IEnumVdsObject* pEnumVolume;
807 IUnknown* pVolumeUnk;
808
809 hr = IUnknown_QueryInterface(pPackUnk, &IID_IVdsPack, (void**)&pPack);
810 IUnknown_Release(pPackUnk);
811 if (hr != S_OK) {
812 suprintf("Could not query VDS Software Provider Pack: %s", VdsErrorString(hr));
813 break;
814 }
815
816 // Use the pack interface to access the disks
817 hr = IVdsPack_QueryVolumes(pPack, &pEnumVolume);
818 if (hr != S_OK) {
819 suprintf("Could not query VDS volumes: %s", VdsErrorString(hr));
820 break;
821 }
822
823 // List volumes
824 while (IEnumVdsObject_Next(pEnumVolume, 1, &pVolumeUnk, &ulFetched) == S_OK) {
825 IVdsVolume* pVolume;
826 IVdsVolumeMF3* pVolumeMF3;
827 VDS_VOLUME_PROP prop;
828 LPWSTR* wszPathArray;
829 ULONG i, ulNumberOfPaths;
830
831 // Get the volume interface.
832 hr = IUnknown_QueryInterface(pVolumeUnk, &IID_IVdsVolume, (void**)&pVolume);
833 if (hr != S_OK) {
834 suprintf("Could not query VDS Volume Interface: %s", VdsErrorString(hr));
835 break;
836 }
837
838 // Get the volume properties
839 hr = IVdsVolume_GetProperties(pVolume, &prop);
840 if ((hr != S_OK) && (hr != VDS_S_PROPERTIES_INCOMPLETE)) {
841 suprintf("Could not query VDS Volume Properties: %s", VdsErrorString(hr));
842 break;
843 }
844
845 uprintf("FOUND VOLUME: '%S'", prop.pwszName);
846 CoTaskMemFree(prop.pwszName);
847 IVdsVolume_Release(pVolume);
848
849 // Get the volume MF3 interface.
850 hr = IUnknown_QueryInterface(pVolumeUnk, &IID_IVdsVolumeMF3, (void**)&pVolumeMF3);
851 if (hr != S_OK) {
852 suprintf("Could not query VDS VolumeMF3 Interface: %s", VdsErrorString(hr));
853 break;
854 }
855
856 // Get the volume properties
857 hr = IVdsVolumeMF3_QueryVolumeGuidPathnames(pVolumeMF3, &wszPathArray, &ulNumberOfPaths);
858 if ((hr != S_OK) && (hr != VDS_S_PROPERTIES_INCOMPLETE)) {
859 suprintf("Could not query VDS VolumeMF3 GUID PathNames: %s", VdsErrorString(hr));
860 break;
861 }
862 hr = S_OK;
863
864 for (i = 0; i < ulNumberOfPaths; i++)
865 uprintf(" VOL GUID: '%S'", wszPathArray[i]);
866 CoTaskMemFree(wszPathArray);
867 IVdsVolume_Release(pVolumeMF3);
868 IUnknown_Release(pVolumeUnk);
869 }
870 IEnumVdsObject_Release(pEnumVolume);
871 }
872 IEnumVdsObject_Release(pEnumPack);
873 }
874 IEnumVdsObject_Release(pEnum);
875
876 out:
877 VDS_SET_ERROR(hr);
878 return (hr == S_OK);
879 }
880
881 /* Wait for a logical drive to reappear - Used when a drive has just been repartitioned */
882 BOOL WaitForLogical(DWORD DriveIndex, uint64_t PartitionOffset)
883 {
884 uint64_t EndTime;
885 char* LogicalPath = NULL;
886
887 // GetLogicalName() calls may be slow, so use the system time to
888 // make sure we don't spend more than DRIVE_ACCESS_TIMEOUT in wait.
889 EndTime = GetTickCount64() + DRIVE_ACCESS_TIMEOUT;
890 do {
891 LogicalPath = GetLogicalName(DriveIndex, PartitionOffset, FALSE, TRUE);
892 // Need to filter out GlobalRoot devices as we don't want to wait on those
893 if ((LogicalPath != NULL) && (strncmp(LogicalPath, groot_name, groot_len) != 0)) {
894 free(LogicalPath);
895 return TRUE;
896 }
897 free(LogicalPath);
898 if (IS_ERROR(FormatStatus)) // User cancel
899 return FALSE;
900 Sleep(DRIVE_ACCESS_TIMEOUT / DRIVE_ACCESS_RETRIES);
901 } while (GetTickCount64() < EndTime);
902 uprintf("Timeout while waiting for logical drive");
903 return FALSE;
904 }
905
906 /*
907 * Obtain a handle to the volume identified by DriveIndex + PartitionIndex
908 * Returns INVALID_HANDLE_VALUE on error or NULL if no logical path exists (typical
909 * of unpartitioned drives)
910 */
911 HANDLE GetLogicalHandle(DWORD DriveIndex, uint64_t PartitionOffset, BOOL bLockDrive, BOOL bWriteAccess, BOOL bWriteShare)
912 {
913 HANDLE hLogical = INVALID_HANDLE_VALUE;
914 char* LogicalPath = GetLogicalName(DriveIndex, PartitionOffset, FALSE, FALSE);
915
916 if (LogicalPath == NULL) {
917 uprintf("No logical drive found (unpartitioned?)");
918 return NULL;
919 }
920
921 hLogical = GetHandle(LogicalPath, bLockDrive, bWriteAccess, bWriteShare);
922 free(LogicalPath);
923 return hLogical;
924 }
925
926 /*
927 * Who would have thought that Microsoft would make it so unbelievably hard to
928 * get the frickin' device number for a drive? You have to use TWO different
929 * methods to have a chance to get it!
930 */
931 int GetDriveNumber(HANDLE hDrive, char* path)
932 {
933 STORAGE_DEVICE_NUMBER_REDEF DeviceNumber;
934 VOLUME_DISK_EXTENTS_REDEF DiskExtents;
935 DWORD size;
936 int r = -1;
937
938 if (!DeviceIoControl(hDrive, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0,
939 &DiskExtents, sizeof(DiskExtents), &size, NULL) || (size <= 0) || (DiskExtents.NumberOfDiskExtents < 1) ) {
940 // DiskExtents are NO_GO (which is the case for external USB HDDs...)
941 if(!DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,
942 &DeviceNumber, sizeof(DeviceNumber), &size, NULL ) || (size <= 0)) {
943 uprintf("Could not get device number for device %s: %s", path, WindowsErrorString());
944 return -1;
945 }
946 r = (int)DeviceNumber.DeviceNumber;
947 } else if (DiskExtents.NumberOfDiskExtents >= 2) {
948 uprintf("Ignoring drive '%s' as it spans multiple disks (RAID?)", path);
949 return -1;
950 } else {
951 r = (int)DiskExtents.Extents[0].DiskNumber;
952 }
953 if (r >= MAX_DRIVES) {
954 uprintf("Device Number for device %s is too big (%d) - ignoring device", path, r);
955 return -1;
956 }
957 return r;
958 }
959
960 /*
961 * Returns the drive letters for all volumes located on the drive identified by DriveIndex,
962 * as well as the drive type. This is used as base for the 2 function calls that follow.
963 */
964 static BOOL _GetDriveLettersAndType(DWORD DriveIndex, char* drive_letters, UINT* drive_type)
965 {
966 DWORD size;
967 BOOL r = FALSE;
968 HANDLE hDrive = INVALID_HANDLE_VALUE, hPhysical = INVALID_HANDLE_VALUE;
969 UINT _drive_type;
970 IO_STATUS_BLOCK io_status_block;
971 FILE_FS_DEVICE_INFORMATION file_fs_device_info;
972 BYTE geometry[256] = { 0 };
973 PDISK_GEOMETRY_EX DiskGeometry = (PDISK_GEOMETRY_EX)(void*)geometry;
974 int i = 0, drives_found = 0, drive_number;
975 char *drive, drives[26*4 + 1]; /* "D:\", "E:\", etc., plus one NUL */
976 char logical_drive[] = "\\\\.\\#:";
977
978 PF_INIT(NtQueryVolumeInformationFile, Ntdll);
979
980 if (drive_letters != NULL)
981 drive_letters[0] = 0;
982 if (drive_type != NULL)
983 *drive_type = DRIVE_UNKNOWN;
984 CheckDriveIndex(DriveIndex);
985
986 // This call is weird... The buffer needs to have an extra NUL, but you're
987 // supposed to provide the size without the extra NUL. And the returned size
988 // does not include the NUL either *EXCEPT* if your buffer is too small...
989 // But then again, this doesn't hold true if you have a 105 byte buffer and
990 // pass a 4*26=104 size, as the the call will return 105 (i.e. *FAILURE*)
991 // instead of 104 as it should => screw Microsoft: We'll include the NUL
992 // always, as each drive string is at least 4 chars long anyway.
993 size = GetLogicalDriveStringsA(sizeof(drives), drives);
994 if (size == 0) {
995 uprintf("GetLogicalDriveStrings failed: %s", WindowsErrorString());
996 goto out;
997 }
998 if (size > sizeof(drives)) {
999 uprintf("GetLogicalDriveStrings: Buffer too small (required %d vs. %d)", size, sizeof(drives));
1000 goto out;
1001 }
1002
1003 r = TRUE; // Required to detect drives that don't have volumes assigned
1004 for (drive = drives ;*drive; drive += safe_strlen(drive) + 1) {
1005 if (!isalpha(*drive))
1006 continue;
1007 *drive = (char)toupper((int)*drive);
1008
1009 // IOCTL_STORAGE_GET_DEVICE_NUMBER's STORAGE_DEVICE_NUMBER.DeviceNumber is
1010 // not unique! An HDD, a DVD and probably other drives can have the same
1011 // value there => Use GetDriveType() to filter out unwanted devices.
1012 // See https://github.com/pbatard/rufus/issues/32#issuecomment-3785956
1013 _drive_type = GetDriveTypeA(drive);
1014
1015 if ((_drive_type != DRIVE_REMOVABLE) && (_drive_type != DRIVE_FIXED))
1016 continue;
1017
1018 static_sprintf(logical_drive, "\\\\.\\%c:", drive[0]);
1019 hDrive = CreateFileA(logical_drive, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
1020 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1021 if (hDrive == INVALID_HANDLE_VALUE) {
1022 // uprintf("Warning: could not open drive %c: %s", drive[0], WindowsErrorString());
1023 continue;
1024 }
1025
1026 // Eliminate floppy drives
1027 if ((pfNtQueryVolumeInformationFile != NULL) &&
1028 (pfNtQueryVolumeInformationFile(hDrive, &io_status_block, &file_fs_device_info,
1029 sizeof(file_fs_device_info), FileFsDeviceInformation) == NO_ERROR) &&
1030 (file_fs_device_info.Characteristics & FILE_FLOPPY_DISKETTE) ) {
1031 continue;
1032 }
1033
1034 drive_number = GetDriveNumber(hDrive, logical_drive);
1035 safe_closehandle(hDrive);
1036 if (drive_number == DriveIndex) {
1037 r = TRUE;
1038 drives_found++;
1039 if (drive_letters != NULL)
1040 drive_letters[i++] = *drive;
1041 // The drive type should be the same for all volumes, so we can overwrite
1042 if (drive_type != NULL)
1043 *drive_type = _drive_type;
1044 }
1045 }
1046
1047 // Devices that don't have mounted partitions require special
1048 // handling to determine if they are fixed or removable.
1049 if ((drives_found == 0) && (drive_type != NULL)) {
1050 hPhysical = GetPhysicalHandle(DriveIndex + DRIVE_INDEX_MIN, FALSE, FALSE, FALSE);
1051 r = DeviceIoControl(hPhysical, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
1052 NULL, 0, geometry, sizeof(geometry), &size, NULL);
1053 safe_closehandle(hPhysical);
1054 if (r && size > 0) {
1055 if (DiskGeometry->Geometry.MediaType == FixedMedia)
1056 *drive_type = DRIVE_FIXED;
1057 else if (DiskGeometry->Geometry.MediaType == RemovableMedia)
1058 *drive_type = DRIVE_REMOVABLE;
1059 }
1060 }
1061
1062 out:
1063 if (drive_letters != NULL)
1064 drive_letters[i] = 0;
1065 return r;
1066 }
1067
1068 // Could have used a #define, but this is clearer
1069 BOOL GetDriveLetters(DWORD DriveIndex, char* drive_letters)
1070 {
1071 return _GetDriveLettersAndType(DriveIndex, drive_letters, NULL);
1072 }
1073
1074 // There's already a GetDriveType in the Windows API
1075 UINT GetDriveTypeFromIndex(DWORD DriveIndex)
1076 {
1077 UINT drive_type;
1078 _GetDriveLettersAndType(DriveIndex, NULL, &drive_type);
1079 return drive_type;
1080 }
1081
1082 // Removes all drive letters associated with the specific drive, and return
1083 // either the first or last letter that was removed, according to bReturnLast.
1084 char RemoveDriveLetters(DWORD DriveIndex, BOOL bReturnLast, BOOL bSilent)
1085 {
1086 int i, len;
1087 char drive_letters[27] = { 0 }, drive_name[4] = "#:\\";
1088
1089 if (!GetDriveLetters(DriveIndex, drive_letters)) {
1090 suprintf("Failed to get a drive letter");
1091 return 0;
1092 }
1093 if (drive_letters[0] == 0) {
1094 suprintf("No drive letter was assigned...");
1095 return GetUnusedDriveLetter();
1096 }
1097 len = (int)strlen(drive_letters);
1098 if (len == 0)
1099 return 0;
1100
1101 // Unmount all mounted volumes that belong to this drive
1102 for (i = 0; i < len; i++) {
1103 // Check that the current image isn't located on a drive we are trying to dismount
1104 if ((boot_type == BT_IMAGE) && (drive_letters[i] == (PathGetDriveNumberU(image_path) + 'A'))) {
1105 if ((PathGetDriveNumberU(image_path) + 'A') == drive_letters[i]) {
1106 suprintf("ABORTED: Cannot use an image that is located on the target drive!");
1107 return 0;
1108 }
1109 }
1110 drive_name[0] = drive_letters[i];
1111 // DefineDosDevice() cannot have a trailing backslash...
1112 drive_name[2] = 0;
1113 DefineDosDeviceA(DDD_REMOVE_DEFINITION, drive_name, NULL);
1114 // ... but DeleteVolumeMountPoint() requires one. Go figure...
1115 drive_name[2] = '\\';
1116 if (!DeleteVolumeMountPointA(drive_name))
1117 suprintf("Failed to delete mountpoint %s: %s", drive_name, WindowsErrorString());
1118 }
1119 return drive_letters[bReturnLast ? (len - 1) : 0];
1120 }
1121
1122 /*
1123 * Return the next unused drive letter from the system or NUL on error.
1124 */
1125 char GetUnusedDriveLetter(void)
1126 {
1127 DWORD size;
1128 char drive_letter, *drive, drives[26*4 + 1]; /* "D:\", "E:\", etc., plus one NUL */
1129
1130 size = GetLogicalDriveStringsA(sizeof(drives), drives);
1131 if (size == 0) {
1132 uprintf("GetLogicalDriveStrings failed: %s", WindowsErrorString());
1133 return 0;
1134 }
1135 if (size > sizeof(drives)) {
1136 uprintf("GetLogicalDriveStrings: Buffer too small (required %d vs. %d)", size, sizeof(drives));
1137 return 0;
1138 }
1139
1140 for (drive_letter = 'C'; drive_letter <= 'Z'; drive_letter++) {
1141 for (drive = drives ; *drive; drive += safe_strlen(drive) + 1) {
1142 if (!isalpha(*drive))
1143 continue;
1144 if (drive_letter == (char)toupper((int)*drive))
1145 break;
1146 }
1147 if (!*drive)
1148 break;
1149 }
1150
1151 return (drive_letter > 'Z') ? 0 : drive_letter;
1152 }
1153
1154 BOOL IsDriveLetterInUse(const char drive_letter)
1155 {
1156 DWORD size;
1157 char *drive, drives[26 * 4 + 1];
1158
1159 size = GetLogicalDriveStringsA(sizeof(drives), drives);
1160 if (size == 0) {
1161 uprintf("GetLogicalDriveStrings failed: %s", WindowsErrorString());
1162 return TRUE;
1163 }
1164 if (size > sizeof(drives)) {
1165 uprintf("GetLogicalDriveStrings: Buffer too small (required %d vs. %d)", size, sizeof(drives));
1166 return TRUE;
1167 }
1168
1169 for (drive = drives; *drive; drive += safe_strlen(drive) + 1) {
1170 if (drive_letter == (char)toupper((int)*drive))
1171 return TRUE;
1172 }
1173
1174 return FALSE;
1175 }
1176
1177 /*
1178 * Return the drive letter and volume label
1179 * If the drive doesn't have a volume assigned, space is returned for the letter
1180 */
1181 BOOL GetDriveLabel(DWORD DriveIndex, char* letters, char** label)
1182 {
1183 HANDLE hPhysical;
1184 DWORD size, error;
1185 static char VolumeLabel[MAX_PATH + 1] = { 0 };
1186 char DrivePath[] = "#:\\", AutorunPath[] = "#:\\autorun.inf", *AutorunLabel = NULL;
1187 WCHAR VolumeName[MAX_PATH + 1] = { 0 }, FileSystemName[64];
1188 DWORD VolumeSerialNumber, MaximumComponentLength, FileSystemFlags;
1189
1190 *label = STR_NO_LABEL;
1191
1192 if (!GetDriveLetters(DriveIndex, letters))
1193 return FALSE;
1194 if (letters[0] == 0) {
1195 // Even if we don't have a letter, try to obtain the label of the first partition
1196 HANDLE h = GetLogicalHandle(DriveIndex, 0, FALSE, FALSE, FALSE);
1197 if (GetVolumeInformationByHandleW(h, VolumeName, 64, &VolumeSerialNumber,
1198 &MaximumComponentLength, &FileSystemFlags, FileSystemName, 64)) {
1199 wchar_to_utf8_no_alloc(VolumeName, VolumeLabel, sizeof(VolumeLabel));
1200 *label = (VolumeLabel[0] != 0) ? VolumeLabel : STR_NO_LABEL;
1201 }
1202 safe_closehandle(h);
1203 // Drive without volume assigned - always enabled
1204 return TRUE;
1205 }
1206 // We only care about an autorun.inf if we have a single volume
1207 AutorunPath[0] = letters[0];
1208 DrivePath[0] = letters[0];
1209
1210 // Try to read an extended label from autorun first. Fallback to regular label if not found.
1211 // In the case of card readers with no card, users can get an annoying popup asking them
1212 // to insert media. Use IOCTL_STORAGE_CHECK_VERIFY to prevent this
1213 hPhysical = GetPhysicalHandle(DriveIndex, FALSE, FALSE, TRUE);
1214 if (DeviceIoControl(hPhysical, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, &size, NULL))
1215 AutorunLabel = get_token_data_file("label", AutorunPath);
1216 else if (GetLastError() == ERROR_NOT_READY)
1217 uprintf("Ignoring autorun.inf label for drive %c: %s", letters[0],
1218 (HRESULT_CODE(GetLastError()) == ERROR_NOT_READY)?"No media":WindowsErrorString());
1219 safe_closehandle(hPhysical);
1220 if (AutorunLabel != NULL) {
1221 uprintf("Using autorun.inf label for drive %c: '%s'", letters[0], AutorunLabel);
1222 static_strcpy(VolumeLabel, AutorunLabel);
1223 safe_free(AutorunLabel);
1224 *label = VolumeLabel;
1225 } else if (GetVolumeInformationU(DrivePath, VolumeLabel, ARRAYSIZE(VolumeLabel),
1226 NULL, NULL, NULL, NULL, 0) && (VolumeLabel[0] != 0)) {
1227 *label = VolumeLabel;
1228 } else {
1229 // Might be an extfs label
1230 error = GetLastError();
1231 *label = (char*)GetExtFsLabel(DriveIndex, 0);
1232 if (*label == NULL) {
1233 SetLastError(error);
1234 if (error != ERROR_UNRECOGNIZED_VOLUME)
1235 duprintf("Failed to read label: %s", WindowsErrorString());
1236 *label = STR_NO_LABEL;
1237 }
1238 }
1239 return TRUE;
1240 }
1241
1242 /*
1243 * Return the drive size
1244 */
1245 uint64_t GetDriveSize(DWORD DriveIndex)
1246 {
1247 BOOL r;
1248 HANDLE hPhysical;
1249 DWORD size;
1250 BYTE geometry[256];
1251 PDISK_GEOMETRY_EX DiskGeometry = (PDISK_GEOMETRY_EX)(void*)geometry;
1252
1253 hPhysical = GetPhysicalHandle(DriveIndex, FALSE, FALSE, TRUE);
1254 if (hPhysical == INVALID_HANDLE_VALUE)
1255 return FALSE;
1256
1257 r = DeviceIoControl(hPhysical, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
1258 NULL, 0, geometry, sizeof(geometry), &size, NULL);
1259 safe_closehandle(hPhysical);
1260 if (!r || size <= 0)
1261 return 0;
1262 return DiskGeometry->DiskSize.QuadPart;
1263 }
1264
1265 /*
1266 * GET_DRIVE_GEOMETRY is used to tell if there is an actual media
1267 */
1268 BOOL IsMediaPresent(DWORD DriveIndex)
1269 {
1270 BOOL r;
1271 HANDLE hPhysical;
1272 DWORD size;
1273 BYTE geometry[128];
1274
1275 hPhysical = GetPhysicalHandle(DriveIndex, FALSE, FALSE, TRUE);
1276 r = DeviceIoControl(hPhysical, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
1277 NULL, 0, geometry, sizeof(geometry), &size, NULL) && (size > 0);
1278 safe_closehandle(hPhysical);
1279 return r;
1280 }
1281
1282 const struct {int (*fn)(FILE *fp); char* str;} known_mbr[] = {
1283 { is_dos_mbr, "DOS/NT/95A" },
1284 { is_dos_f2_mbr, "DOS/NT/95A (F2)" },
1285 { is_95b_mbr, "Windows 95B/98/98SE/ME" },
1286 { is_2000_mbr, "Windows 2000/XP/2003" },
1287 { is_vista_mbr, "Windows Vista" },
1288 { is_win7_mbr, "Windows 7" },
1289 { is_rufus_mbr, "Rufus" },
1290 { is_syslinux_mbr, "Syslinux" },
1291 { is_reactos_mbr, "ReactOS" },
1292 { is_kolibrios_mbr, "KolibriOS" },
1293 { is_grub4dos_mbr, "Grub4DOS" },
1294 { is_grub2_mbr, "Grub 2.0" },
1295 { is_zero_mbr_not_including_disk_signature_or_copy_protect, "Zeroed" },
1296 };
1297
1298 // Returns TRUE if the drive seems bootable, FALSE otherwise
1299 BOOL AnalyzeMBR(HANDLE hPhysicalDrive, const char* TargetName, BOOL bSilent)
1300 {
1301 FAKE_FD fake_fd = { 0 };
1302 FILE* fp = (FILE*)&fake_fd;
1303 int i;
1304
1305 fake_fd._handle = (char*)hPhysicalDrive;
1306 set_bytes_per_sector(SelectedDrive.SectorSize);
1307
1308 if (!is_br(fp)) {
1309 suprintf("%s does not have a Boot Marker", TargetName);
1310 return FALSE;
1311 }
1312 for (i=0; i<ARRAYSIZE(known_mbr); i++) {
1313 if (known_mbr[i].fn(fp)) {
1314 suprintf("%s has a %s Master Boot Record", TargetName, known_mbr[i].str);
1315 return TRUE;
1316 }
1317 }
1318
1319 suprintf("%s has an unknown Master Boot Record", TargetName);
1320 return TRUE;
1321 }
1322
1323 const struct {int (*fn)(FILE *fp); char* str;} known_pbr[] = {
1324 { entire_fat_16_br_matches, "FAT16 DOS" },
1325 { entire_fat_16_fd_br_matches, "FAT16 FreeDOS" },
1326 { entire_fat_16_ros_br_matches, "FAT16 ReactOS" },
1327 { entire_fat_32_br_matches, "FAT32 DOS" },
1328 { entire_fat_32_nt_br_matches, "FAT32 NT" },
1329 { entire_fat_32_fd_br_matches, "FAT32 FreeDOS" },
1330 { entire_fat_32_ros_br_matches, "FAT32 ReactOS" },
1331 { entire_fat_32_kos_br_matches, "FAT32 KolibriOS" },
1332 };
1333
1334 BOOL AnalyzePBR(HANDLE hLogicalVolume)
1335 {
1336 const char* pbr_name = "Partition Boot Record";
1337 FAKE_FD fake_fd = { 0 };
1338 FILE* fp = (FILE*)&fake_fd;
1339 int i;
1340
1341 fake_fd._handle = (char*)hLogicalVolume;
1342 set_bytes_per_sector(SelectedDrive.SectorSize);
1343
1344 if (!is_br(fp)) {
1345 uprintf("Volume does not have an x86 %s", pbr_name);
1346 return FALSE;
1347 }
1348
1349 if (is_fat_16_br(fp) || is_fat_32_br(fp)) {
1350 for (i=0; i<ARRAYSIZE(known_pbr); i++) {
1351 if (known_pbr[i].fn(fp)) {
1352 uprintf("Drive has a %s %s", known_pbr[i].str, pbr_name);
1353 return TRUE;
1354 }
1355 }
1356 uprintf("Volume has an unknown FAT16 or FAT32 %s", pbr_name);
1357 } else {
1358 uprintf("Volume has an unknown %s", pbr_name);
1359 }
1360 return TRUE;
1361 }
1362
1363 static BOOL StoreEspInfo(GUID* guid)
1364 {
1365 uint8_t j;
1366 char key_name[2][16], *str;
1367 // Look for an empty slot and use that if available
1368 for (j = 1; j <= MAX_ESP_TOGGLE; j++) {
1369 static_sprintf(key_name[0], "ToggleEsp%02u", j);
1370 str = ReadSettingStr(key_name[0]);
1371 if ((str == NULL) || (str[0] == 0))
1372 return WriteSettingStr(key_name[0], GuidToString(guid));
1373 }
1374 // All slots are used => Move every key down and add to last slot
1375 // NB: No, we don't care that the slot we remove may not be the oldest.
1376 for (j = 1; j < MAX_ESP_TOGGLE; j++) {
1377 static_sprintf(key_name[0], "ToggleEsp%02u", j);
1378 static_sprintf(key_name[1], "ToggleEsp%02u", j + 1);
1379 WriteSettingStr(key_name[0], ReadSettingStr(key_name[1]));
1380 }
1381 return WriteSettingStr(key_name[1], GuidToString(guid));
1382 }
1383
1384 static GUID* GetEspGuid(uint8_t index)
1385 {
1386 char key_name[16];
1387
1388 static_sprintf(key_name, "ToggleEsp%02u", index);
1389 return StringToGuid(ReadSettingStr(key_name));
1390 }
1391
1392 static BOOL ClearEspInfo(uint8_t index)
1393 {
1394 char key_name[16];
1395 static_sprintf(key_name, "ToggleEsp%02u", index);
1396 return WriteSettingStr(key_name, "");
1397 }
1398
1399 /*
1400 * This calls changes the type of a GPT ESP back and forth to Basic Data.
1401 * Needed because Windows 10 doesn't mount ESPs by default, and also
1402 * doesn't let usermode apps (such as File Explorer) access mounted ESPs.
1403 */
1404 BOOL ToggleEsp(DWORD DriveIndex, uint64_t PartitionOffset)
1405 {
1406 char *volume_name, mount_point[] = DEFAULT_ESP_MOUNT_POINT;
1407 BOOL r, ret = FALSE, found = FALSE;
1408 HANDLE hPhysical;
1409 DWORD size, i, j, esp_index = 0;
1410 BYTE layout[4096] = { 0 };
1411 GUID* guid;
1412 PDRIVE_LAYOUT_INFORMATION_EX DriveLayout = (PDRIVE_LAYOUT_INFORMATION_EX)(void*)layout;
1413
1414 if ((PartitionOffset == 0) && (nWindowsVersion < WINDOWS_10)) {
1415 uprintf("ESP toggling is only available for Windows 10 or later");
1416 return FALSE;
1417 }
1418
1419 hPhysical = GetPhysicalHandle(DriveIndex, FALSE, TRUE, TRUE);
1420 if (hPhysical == INVALID_HANDLE_VALUE)
1421 return FALSE;
1422
1423 r = DeviceIoControl(hPhysical, IOCTL_DISK_GET_DRIVE_LAYOUT_EX,
1424 NULL, 0, layout, sizeof(layout), &size, NULL);
1425 if (!r || size <= 0) {
1426 uprintf("Could not get layout for drive 0x%02x: %s", DriveIndex, WindowsErrorString());
1427 goto out;
1428 }
1429 // TODO: Handle MBR
1430 if (DriveLayout->PartitionStyle != PARTITION_STYLE_GPT) {
1431 uprintf("ESP toggling is only available for GPT drives");
1432 goto out;
1433 }
1434
1435 if (PartitionOffset == 0) {
1436 // See if the current drive contains an ESP
1437 for (i = 0, j = 0; i < DriveLayout->PartitionCount; i++) {
1438 if (CompareGUID(&DriveLayout->PartitionEntry[i].Gpt.PartitionType, &PARTITION_GENERIC_ESP)) {
1439 esp_index = i;
1440 j++;
1441 }
1442 }
1443
1444 if (j > 1) {
1445 uprintf("ESP toggling is not available for drives with more than one ESP");
1446 goto out;
1447 }
1448 if (j == 1) {
1449 // ESP -> Basic Data
1450 i = esp_index;
1451 uprintf("ESP name: '%S'", DriveLayout->PartitionEntry[i].Gpt.Name);
1452 if (!StoreEspInfo(&DriveLayout->PartitionEntry[i].Gpt.PartitionId)) {
1453 uprintf("ESP toggling data could not be stored");
1454 goto out;
1455 }
1456 DriveLayout->PartitionEntry[i].Gpt.PartitionType = PARTITION_MICROSOFT_DATA;
1457 } else {
1458 // Basic Data -> ESP
1459 for (j = 1; j <= MAX_ESP_TOGGLE; j++) {
1460 guid = GetEspGuid((uint8_t)j);
1461 if (guid != NULL) {
1462 for (i = 0; i < DriveLayout->PartitionCount; i++) {
1463 if (CompareGUID(guid, &DriveLayout->PartitionEntry[i].Gpt.PartitionId)) {
1464 found = TRUE;
1465 break;
1466 }
1467 }
1468 if (found)
1469 break;
1470 }
1471 }
1472 if (j > MAX_ESP_TOGGLE)
1473 goto out;
1474 DriveLayout->PartitionEntry[i].Gpt.PartitionType = PARTITION_GENERIC_ESP;
1475 }
1476 } else {
1477 for (i = 0, j = 0; i < DriveLayout->PartitionCount; i++) {
1478 if (DriveLayout->PartitionEntry[i].StartingOffset.QuadPart == PartitionOffset) {
1479 DriveLayout->PartitionEntry[i].Gpt.PartitionType = PARTITION_GENERIC_ESP;
1480 break;
1481 }
1482 }
1483 }
1484 if (i >= DriveLayout->PartitionCount) {
1485 uprintf("No partition to toggle");
1486 goto out;
1487 }
1488
1489 DriveLayout->PartitionEntry[i].RewritePartition = TRUE; // Just in case
1490 r = DeviceIoControl(hPhysical, IOCTL_DISK_SET_DRIVE_LAYOUT_EX, (BYTE*)DriveLayout, size, NULL, 0, &size, NULL);
1491 if (!r) {
1492 uprintf("Could not set drive layout: %s", WindowsErrorString());
1493 goto out;
1494 }
1495 RefreshDriveLayout(hPhysical);
1496 if (PartitionOffset == 0) {
1497 if (CompareGUID(&DriveLayout->PartitionEntry[i].Gpt.PartitionType, &PARTITION_GENERIC_ESP)) {
1498 // We successfully reverted ESP from Basic Data -> Delete stored ESP info
1499 ClearEspInfo((uint8_t)j);
1500 } else if (!IsDriveLetterInUse(*mount_point)) {
1501 // We succesfully switched ESP to Basic Data -> Try to mount it
1502 volume_name = GetLogicalName(DriveIndex, DriveLayout->PartitionEntry[i].StartingOffset.QuadPart, TRUE, FALSE);
1503 IGNORE_RETVAL(MountVolume(mount_point, volume_name));
1504 free(volume_name);
1505 }
1506 }
1507 ret = TRUE;
1508
1509 out:
1510 safe_closehandle(hPhysical);
1511 return ret;
1512 }
1513
1514 /*
1515 * Fill the drive properties (size, FS, etc)
1516 * Returns TRUE if the drive has a partition that can be mounted in Windows, FALSE otherwise
1517 */
1518 BOOL GetDrivePartitionData(DWORD DriveIndex, char* FileSystemName, DWORD FileSystemNameSize, BOOL bSilent)
1519 {
1520 // MBR partition types that can be mounted in Windows
1521 const uint8_t mbr_mountable[] = { 0x01, 0x04, 0x06, 0x07, 0x0b, 0x0c, 0x0e, 0xef };
1522 BOOL r, ret = FALSE, isUefiNtfs;
1523 HANDLE hPhysical;
1524 DWORD size, i, j, super_floppy_disk = FALSE;
1525 BYTE geometry[256] = {0}, layout[4096] = {0}, part_type;
1526 PDISK_GEOMETRY_EX DiskGeometry = (PDISK_GEOMETRY_EX)(void*)geometry;
1527 PDRIVE_LAYOUT_INFORMATION_EX DriveLayout = (PDRIVE_LAYOUT_INFORMATION_EX)(void*)layout;
1528 char *volume_name, *buf;
1529
1530 if (FileSystemName == NULL)
1531 return FALSE;
1532
1533 SelectedDrive.nPartitions = 0;
1534 memset(SelectedDrive.PartitionOffset, 0, sizeof(SelectedDrive.PartitionOffset));
1535 memset(SelectedDrive.PartitionSize, 0, sizeof(SelectedDrive.PartitionSize));
1536 // Populate the filesystem data
1537 FileSystemName[0] = 0;
1538 volume_name = GetLogicalName(DriveIndex, 0, TRUE, FALSE);
1539 if ((volume_name == NULL) || (!GetVolumeInformationA(volume_name, NULL, 0, NULL, NULL, NULL, FileSystemName, FileSystemNameSize))) {
1540 suprintf("No volume information for drive 0x%02x", DriveIndex);
1541 }
1542 safe_free(volume_name);
1543
1544 hPhysical = GetPhysicalHandle(DriveIndex, FALSE, FALSE, TRUE);
1545 if (hPhysical == INVALID_HANDLE_VALUE)
1546 return FALSE;
1547
1548 r = DeviceIoControl(hPhysical, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
1549 NULL, 0, geometry, sizeof(geometry), &size, NULL);
1550 if (!r || size <= 0) {
1551 suprintf("Could not get geometry for drive 0x%02x: %s", DriveIndex, WindowsErrorString());
1552 safe_closehandle(hPhysical);
1553 return FALSE;
1554 }
1555 SelectedDrive.DiskSize = DiskGeometry->DiskSize.QuadPart;
1556 SelectedDrive.SectorSize = DiskGeometry->Geometry.BytesPerSector;
1557 SelectedDrive.FirstDataSector = MAXDWORD;
1558 if (SelectedDrive.SectorSize < 512) {
1559 suprintf("Warning: Drive 0x%02x reports a sector size of %d - Correcting to 512 bytes.",
1560 DriveIndex, SelectedDrive.SectorSize);
1561 SelectedDrive.SectorSize = 512;
1562 }
1563 SelectedDrive.SectorsPerTrack = DiskGeometry->Geometry.SectorsPerTrack;
1564 SelectedDrive.MediaType = DiskGeometry->Geometry.MediaType;
1565
1566 suprintf("Disk type: %s, Disk size: %s, Sector size: %d bytes", (SelectedDrive.MediaType == FixedMedia)?"FIXED":"Removable",
1567 SizeToHumanReadable(SelectedDrive.DiskSize, FALSE, TRUE), SelectedDrive.SectorSize);
1568 suprintf("Cylinders: %" PRIi64 ", Tracks per cylinder: %d, Sectors per track: %d",
1569 DiskGeometry->Geometry.Cylinders, DiskGeometry->Geometry.TracksPerCylinder, DiskGeometry->Geometry.SectorsPerTrack);
1570
1571 r = DeviceIoControl(hPhysical, IOCTL_DISK_GET_DRIVE_LAYOUT_EX,
1572 NULL, 0, layout, sizeof(layout), &size, NULL );
1573 if (!r || size <= 0) {
1574 suprintf("Could not get layout for drive 0x%02x: %s", DriveIndex, WindowsErrorString());
1575 safe_closehandle(hPhysical);
1576 return FALSE;
1577 }
1578
1579 switch (DriveLayout->PartitionStyle) {
1580 case PARTITION_STYLE_MBR:
1581 SelectedDrive.PartitionStyle = PARTITION_STYLE_MBR;
1582 for (i = 0; i < DriveLayout->PartitionCount; i++) {
1583 if (DriveLayout->PartitionEntry[i].Mbr.PartitionType != PARTITION_ENTRY_UNUSED) {
1584 SelectedDrive.nPartitions++;
1585 }
1586 }
1587 // Detect drives that are using the whole disk as a single partition
1588 if ((DriveLayout->PartitionEntry[0].Mbr.PartitionType != PARTITION_ENTRY_UNUSED) &&
1589 (DriveLayout->PartitionEntry[0].StartingOffset.QuadPart == 0LL)) {
1590 suprintf("Partition type: SFD (%s) or unpartitioned", sfd_name);
1591 super_floppy_disk = TRUE;
1592 } else {
1593 suprintf("Partition type: MBR, NB Partitions: %d", SelectedDrive.nPartitions);
1594 SelectedDrive.has_mbr_uefi_marker = (DriveLayout->Mbr.Signature == MBR_UEFI_MARKER);
1595 suprintf("Disk ID: 0x%08X %s", DriveLayout->Mbr.Signature, SelectedDrive.has_mbr_uefi_marker?"(UEFI target)":"");
1596 AnalyzeMBR(hPhysical, "Drive", bSilent);
1597 }
1598 for (i = 0; i < DriveLayout->PartitionCount; i++) {
1599 isUefiNtfs = FALSE;
1600 if (DriveLayout->PartitionEntry[i].Mbr.PartitionType != PARTITION_ENTRY_UNUSED) {
1601 part_type = DriveLayout->PartitionEntry[i].Mbr.PartitionType;
1602 // Microsoft will have to explain why they completely ignore the actual MBR partition
1603 // type for zeroed drive (which *IS* 0x00) and fill in Small FAT16 instead (0x04).
1604 // This means that if we detect a Small FAT16 "partition", that "starts" at offset 0
1605 // and that is larger than 16 MB, our drive is actually unpartitioned.
1606 if (part_type == 0x04 && super_floppy_disk && SelectedDrive.DiskSize > 16 * MB)
1607 break;
1608 if (part_type == 0xef) {
1609 // Check the FAT label to see if we're dealing with an UEFI_NTFS partition
1610 buf = calloc(SelectedDrive.SectorSize, 1);
1611 if (buf != NULL) {
1612 if (SetFilePointerEx(hPhysical, DriveLayout->PartitionEntry[i].StartingOffset, NULL, FILE_BEGIN) &&
1613 ReadFile(hPhysical, buf, SelectedDrive.SectorSize, &size, NULL)) {
1614 isUefiNtfs = (strncmp(&buf[0x2B], "UEFI_NTFS", 9) == 0);
1615 }
1616 free(buf);
1617 }
1618 }
1619 suprintf("Partition %d%s:", i + (super_floppy_disk ? 0 : 1), isUefiNtfs ? " (UEFI:NTFS)" : "");
1620 for (j = 0; j < ARRAYSIZE(mbr_mountable); j++) {
1621 if (part_type == mbr_mountable[j]) {
1622 ret = TRUE;
1623 break;
1624 }
1625 }
1626 if (i < MAX_PARTITIONS) {
1627 SelectedDrive.PartitionOffset[i] = DriveLayout->PartitionEntry[i].StartingOffset.QuadPart;
1628 SelectedDrive.PartitionSize[i] = DriveLayout->PartitionEntry[i].PartitionLength.QuadPart;
1629 }
1630 // NB: MinGW's gcc 4.9.2 broke "%lld" printout on XP so we use the inttypes.h "PRI##" qualifiers
1631 suprintf(" Type: %s (0x%02x)\r\n Size: %s (%" PRIi64 " bytes)\r\n Start Sector: %" PRIi64 ", Boot: %s",
1632 ((part_type == 0x07 || super_floppy_disk) && (FileSystemName[0] != 0)) ?
1633 FileSystemName : GetMBRPartitionType(part_type), super_floppy_disk ? 0: part_type,
1634 SizeToHumanReadable(DriveLayout->PartitionEntry[i].PartitionLength.QuadPart, TRUE, FALSE),
1635 DriveLayout->PartitionEntry[i].PartitionLength.QuadPart,
1636 DriveLayout->PartitionEntry[i].StartingOffset.QuadPart / SelectedDrive.SectorSize,
1637 DriveLayout->PartitionEntry[i].Mbr.BootIndicator?"Yes":"No");
1638 // suprintf(" GUID: %s", GuidToString(&DriveLayout->PartitionEntry[i].Mbr.PartitionId));
1639 SelectedDrive.FirstDataSector = min(SelectedDrive.FirstDataSector,
1640 (DWORD)(DriveLayout->PartitionEntry[i].StartingOffset.QuadPart / SelectedDrive.SectorSize));
1641 if ((part_type == RUFUS_EXTRA_PARTITION_TYPE) || (isUefiNtfs))
1642 // This is a partition Rufus created => we can safely ignore it
1643 --SelectedDrive.nPartitions;
1644 if (part_type == 0xee) // Flag a protective MBR for non GPT platforms (XP)
1645 SelectedDrive.has_protective_mbr = TRUE;
1646 }
1647 }
1648 break;
1649 case PARTITION_STYLE_GPT:
1650 SelectedDrive.PartitionStyle = PARTITION_STYLE_GPT;
1651 suprintf("Partition type: GPT, NB Partitions: %d", DriveLayout->PartitionCount);
1652 suprintf("Disk GUID: %s", GuidToString(&DriveLayout->Gpt.DiskId));
1653 suprintf("Max parts: %d, Start Offset: %" PRIi64 ", Usable = %" PRIi64 " bytes",
1654 DriveLayout->Gpt.MaxPartitionCount, DriveLayout->Gpt.StartingUsableOffset.QuadPart, DriveLayout->Gpt.UsableLength.QuadPart);
1655 for (i = 0; i < DriveLayout->PartitionCount; i++) {
1656 if (i < MAX_PARTITIONS) {
1657 SelectedDrive.PartitionOffset[i] = DriveLayout->PartitionEntry[i].StartingOffset.QuadPart;
1658 SelectedDrive.PartitionSize[i] = DriveLayout->PartitionEntry[i].PartitionLength.QuadPart;
1659 }
1660 SelectedDrive.nPartitions++;
1661 isUefiNtfs = (wcscmp(DriveLayout->PartitionEntry[i].Gpt.Name, L"UEFI:NTFS") == 0);
1662 suprintf("Partition %d%s:\r\n Type: %s", i+1, isUefiNtfs ? " (UEFI:NTFS)" : "",
1663 GetGPTPartitionType(&DriveLayout->PartitionEntry[i].Gpt.PartitionType));
1664 if (DriveLayout->PartitionEntry[i].Gpt.Name[0] != 0)
1665 suprintf(" Name: '%S'", DriveLayout->PartitionEntry[i].Gpt.Name);
1666 suprintf(" ID: %s\r\n Size: %s (%" PRIi64 " bytes)\r\n Start Sector: %" PRIi64 ", Attributes: 0x%016" PRIX64,
1667 GuidToString(&DriveLayout->PartitionEntry[i].Gpt.PartitionId),
1668 SizeToHumanReadable(DriveLayout->PartitionEntry[i].PartitionLength.QuadPart, TRUE, FALSE),
1669 DriveLayout->PartitionEntry[i].PartitionLength,
1670 DriveLayout->PartitionEntry[i].StartingOffset.QuadPart / SelectedDrive.SectorSize,
1671 DriveLayout->PartitionEntry[i].Gpt.Attributes);
1672 SelectedDrive.FirstDataSector = min(SelectedDrive.FirstDataSector,
1673 (DWORD)(DriveLayout->PartitionEntry[i].StartingOffset.QuadPart / SelectedDrive.SectorSize));
1674 // Don't register the partitions that we don't care about destroying
1675 if ( isUefiNtfs ||
1676 (CompareGUID(&DriveLayout->PartitionEntry[i].Gpt.PartitionType, &PARTITION_MICROSOFT_RESERVED)) ||
1677 (CompareGUID(&DriveLayout->PartitionEntry[i].Gpt.PartitionType, &PARTITION_GENERIC_ESP)) )
1678 --SelectedDrive.nPartitions;
1679 if (CompareGUID(&DriveLayout->PartitionEntry[i].Gpt.PartitionType, &PARTITION_MICROSOFT_DATA))
1680 ret = TRUE;
1681 }
1682 break;
1683 default:
1684 SelectedDrive.PartitionStyle = PARTITION_STYLE_MBR;
1685 suprintf("Partition type: RAW");
1686 break;
1687 }
1688 #if defined(__GNUC__)
1689 #pragma GCC diagnostic warning "-Warray-bounds"
1690 #endif
1691 safe_closehandle(hPhysical);
1692
1693 return ret;
1694 }
1695
1696 /*
1697 * Flush file data
1698 */
1699 static BOOL FlushDrive(char drive_letter)
1700 {
1701 HANDLE hDrive = INVALID_HANDLE_VALUE;
1702 BOOL r = FALSE;
1703 char logical_drive[] = "\\\\.\\#:";
1704
1705 logical_drive[4] = drive_letter;
1706 hDrive = CreateFileA(logical_drive, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
1707 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1708 if (hDrive == INVALID_HANDLE_VALUE) {
1709 uprintf("Failed to open %c: for flushing: %s", drive_letter, WindowsErrorString());
1710 goto out;
1711 }
1712 r = FlushFileBuffers(hDrive);
1713 if (r == FALSE)
1714 uprintf("Failed to flush %c: %s", drive_letter, WindowsErrorString());
1715
1716 out:
1717 safe_closehandle(hDrive);
1718 return r;
1719 }
1720
1721 /*
1722 * Unmount of volume using the DISMOUNT_VOLUME ioctl
1723 */
1724 BOOL UnmountVolume(HANDLE hDrive)
1725 {
1726 DWORD size;
1727
1728 if (!DeviceIoControl(hDrive, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &size, NULL)) {
1729 uprintf("Could not unmount drive: %s", WindowsErrorString());
1730 return FALSE;
1731 }
1732 return TRUE;
1733 }
1734
1735 /*
1736 * Mount the volume identified by drive_guid to mountpoint drive_name.
1737 * If volume_name is already mounted, but with a different letter than the
1738 * one requested then drive_name is updated to use that letter.
1739 */
1740 BOOL MountVolume(char* drive_name, char *volume_name)
1741 {
1742 char mounted_guid[52], dos_name[] = "?:";
1743 #if defined(WINDOWS_IS_NOT_BUGGY)
1744 char mounted_letter[27] = { 0 };
1745 DWORD size;
1746 #endif
1747
1748 if ((drive_name == NULL) || (volume_name == NULL) || (drive_name[0] == '?')) {
1749 SetLastError(ERROR_INVALID_PARAMETER);
1750 return FALSE;
1751 }
1752
1753 // If we are working with a "\\?\GLOBALROOT" device, SetVolumeMountPoint()
1754 // is useless, so try with DefineDosDevice() instead.
1755 if (_strnicmp(volume_name, groot_name, groot_len) == 0) {
1756 dos_name[0] = drive_name[0];
1757 // Microsoft will also have to explain why "In no case is a trailing backslash allowed" [1] in
1758 // DefineDosDevice(), instead of just checking if the driver parameter is "X:\" and remove the
1759 // backslash from a copy of the parameter in the bloody API call. *THIS* really tells a lot
1760 // about the level of thought and foresight that actually goes into the Windows APIs...
1761 // [1] https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-definedosdevicew
1762 if (!DefineDosDeviceA(DDD_RAW_TARGET_PATH | DDD_NO_BROADCAST_SYSTEM, dos_name, &volume_name[14])) {
1763 uprintf("Could not mount %s as %C:", volume_name, drive_name[0]);
1764 return FALSE;
1765 }
1766 uprintf("%s was successfully mounted as %C:", volume_name, drive_name[0]);
1767 return TRUE;
1768 }
1769
1770 // Great: Windows has a *MAJOR BUG* whereas, in some circumstances, GetVolumePathNamesForVolumeName()
1771 // can return the *WRONG* drive letter. And yes, we validated that this is *NOT* an issue like stack
1772 // or buffer corruption and whatnot. It *IS* a Windows bug. So just drop the idea of updating the
1773 // drive letter if already mounted and use the passed target always.
1774 #if defined(WINDOWS_IS_NOT_BUGGY)
1775 // Windows may already have the volume mounted but under a different letter.
1776 // If that is the case, update drive_name to that letter.
1777 if ( (GetVolumePathNamesForVolumeNameA(volume_name, mounted_letter, sizeof(mounted_letter), &size))
1778 && (size > 1) && (mounted_letter[0] != drive_name[0]) ) {
1779 uprintf("%s is already mounted as %C: instead of %C: - Will now use this target instead...",
1780 volume_name, mounted_letter[0], drive_name[0]);
1781 drive_name[0] = mounted_letter[0];
1782 return TRUE;
1783 }
1784 #endif
1785
1786 if (!SetVolumeMountPointA(drive_name, volume_name)) {
1787 if (GetLastError() == ERROR_DIR_NOT_EMPTY) {
1788 if (!GetVolumeNameForVolumeMountPointA(drive_name, mounted_guid, sizeof(mounted_guid))) {
1789 uprintf("%s is already mounted, but volume GUID could not be checked: %s",
1790 drive_name, WindowsErrorString());
1791 } else if (safe_strcmp(volume_name, mounted_guid) != 0) {
1792 uprintf("%s is mounted, but volume GUID doesn't match:\r\n expected %s, got %s",
1793 drive_name, volume_name, mounted_guid);
1794 } else {
1795 duprintf("%s is already mounted as %C:", volume_name, drive_name[0]);
1796 return TRUE;
1797 }
1798 uprintf("Retrying after dismount...");
1799 if (!DeleteVolumeMountPointA(drive_name))
1800 uprintf("Warning: Could not delete volume mountpoint '%s': %s", drive_name, WindowsErrorString());
1801 if (SetVolumeMountPointA(drive_name, volume_name))
1802 return TRUE;
1803 if ((GetLastError() == ERROR_DIR_NOT_EMPTY) &&
1804 GetVolumeNameForVolumeMountPointA(drive_name, mounted_guid, sizeof(mounted_guid)) &&
1805 (safe_strcmp(volume_name, mounted_guid) == 0)) {
1806 uprintf("%s was remounted as %C: (second time lucky!)", volume_name, drive_name[0]);
1807 return TRUE;
1808 }
1809 }
1810 return FALSE;
1811 }
1812 return TRUE;
1813 }
1814
1815 /*
1816 * Alternate version of MountVolume required for ESP's, since Windows (including VDS) does
1817 * *NOT* provide any means of mounting these volume but through DefineDosDevice(). Also
1818 * note that bcdboot is very finicky about what it may or may not handle, even if the
1819 * mount was successful (e.g. '\Device\HarddiskVolume###' vs 'Device\HarddiskVolume###').
1820 * Returned string is static (no concurrency) and must not be freed.
1821 */
1822 char* AltMountVolume(DWORD DriveIndex, uint64_t PartitionOffset, BOOL bSilent)
1823 {
1824 char* ret = NULL, *volume_name = NULL;
1825 static char mounted_drive[] = "?:";
1826
1827 mounted_drive[0] = GetUnusedDriveLetter();
1828 if (mounted_drive[0] == 0) {
1829 suprintf("Could not find an unused drive letter");
1830 goto out;
1831 }
1832 // Can't use a regular volume GUID for ESPs...
1833 volume_name = AltGetLogicalName(DriveIndex, PartitionOffset, FALSE, FALSE);
1834 if ((volume_name == NULL) || (strncmp(volume_name, groot_name, groot_len) != 0)) {
1835 suprintf("Unexpected volume name: '%s'", volume_name);
1836 goto out;
1837 }
1838
1839 suprintf("Mounting '%s' as '%s'", &volume_name[14], mounted_drive);
1840 // bcdboot doesn't like it if you forget the starting '\'
1841 if (!DefineDosDeviceA(DDD_RAW_TARGET_PATH | DDD_NO_BROADCAST_SYSTEM, mounted_drive, &volume_name[14])) {
1842 suprintf("Mount operation failed: %s", WindowsErrorString());
1843 goto out;
1844 }
1845 ret = mounted_drive;
1846
1847 out:
1848 free(volume_name);
1849 return ret;
1850 }
1851
1852 /*
1853 * Unmount a volume that was mounted by AltmountVolume()
1854 */
1855 BOOL AltUnmountVolume(const char* drive_name, BOOL bSilent)
1856 {
1857 if (drive_name == NULL)
1858 return FALSE;
1859 if (!DefineDosDeviceA(DDD_REMOVE_DEFINITION | DDD_NO_BROADCAST_SYSTEM, drive_name, NULL)) {
1860 suprintf("Could not unmount '%s': %s", drive_name, WindowsErrorString());
1861 return FALSE;
1862 }
1863 suprintf("Successfully unmounted '%s'", drive_name);
1864 return TRUE;
1865 }
1866
1867 /*
1868 * Issue a complete remount of the volume.
1869 * Note that drive_name *may* be altered when the volume gets remounted.
1870 */
1871 BOOL RemountVolume(char* drive_name, BOOL bSilent)
1872 {
1873 char volume_name[51];
1874
1875 // UDF requires a sync/flush, and it's also a good idea for other FS's
1876 FlushDrive(drive_name[0]);
1877 if (GetVolumeNameForVolumeMountPointA(drive_name, volume_name, sizeof(volume_name))) {
1878 if (MountVolume(drive_name, volume_name)) {
1879 suprintf("Successfully remounted %s as %C:", volume_name, drive_name[0]);
1880 } else {
1881 suprintf("Could not remount %s as %C: %s", volume_name, drive_name[0], WindowsErrorString());
1882 // This will leave the drive inaccessible and must be flagged as an error
1883 FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|APPERR(ERROR_CANT_REMOUNT_VOLUME);
1884 return FALSE;
1885 }
1886 }
1887 return TRUE;
1888 }
1889
1890 /*
1891 * Zero the first 'size' bytes of a partition. This is needed because we haven't found a way to
1892 * properly reset Windows's cached view of a drive partitioning short of cycling the USB port
1893 * (especially IOCTL_DISK_UPDATE_PROPERTIES is *USELESS*), and therefore the OS will try to
1894 * read the file system data at an old location, even if the partition has just been deleted.
1895 */
1896 static BOOL ClearPartition(HANDLE hDrive, LARGE_INTEGER offset, DWORD size)
1897 {
1898 BOOL r = FALSE;
1899 uint8_t* buffer = calloc(size, 1);
1900
1901 if (buffer == NULL)
1902 return FALSE;
1903
1904 if (!SetFilePointerEx(hDrive, offset, NULL, FILE_BEGIN)) {
1905 free(buffer);
1906 return FALSE;
1907 }
1908
1909 r = WriteFileWithRetry(hDrive, buffer, size, &size, WRITE_RETRIES);
1910 free(buffer);
1911 return r;
1912 }
1913
1914 /*
1915 * Create a partition table
1916 * See http://technet.microsoft.com/en-us/library/cc739412.aspx for some background info
1917 * NB: if you modify the MBR outside of using the Windows API, Windows still uses the cached
1918 * copy it got from the last IOCTL, and ignores your changes until you replug the drive
1919 * or issue an IOCTL_DISK_UPDATE_PROPERTIES.
1920 */
1921 BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL mbr_uefi_marker, uint8_t extra_partitions)
1922 {
1923 const char* PartitionTypeName[] = { "MBR", "GPT", "SFD" };
1924 const wchar_t *extra_part_name = L"", *main_part_name = write_as_esp ? L"EFI System Partition" : L"Main Data Partition";
1925 const LONGLONG main_part_size = write_as_esp ? MAX_ISO_TO_ESP_SIZE * MB : SelectedDrive.DiskSize;
1926 const LONGLONG bytes_per_track = ((LONGLONG)SelectedDrive.SectorsPerTrack) * SelectedDrive.SectorSize;
1927 const DWORD size_to_clear = MAX_SECTORS_TO_CLEAR * SelectedDrive.SectorSize;
1928 uint8_t* buffer;
1929 size_t uefi_ntfs_size = 0;
1930 CREATE_DISK CreateDisk = {PARTITION_STYLE_RAW, {{0}}};
1931 DRIVE_LAYOUT_INFORMATION_EX4 DriveLayoutEx = {0};
1932 BOOL r;
1933 DWORD i, size, bufsize, pn = 0;
1934 LONGLONG main_part_size_in_sectors, extra_part_size_in_tracks = 0;
1935 // Go for a 260 MB sized ESP by default to keep everyone happy, including 4K sector users:
1936 // https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/configure-uefigpt-based-hard-drive-partitions
1937 // and folks using MacOS: https://github.com/pbatard/rufus/issues/979
1938 LONGLONG esp_size = 260 * MB;
1939
1940 PrintInfoDebug(0, MSG_238, PartitionTypeName[partition_style]);
1941
1942 if (partition_style == PARTITION_STYLE_SFD)
1943 // Nothing to do
1944 return TRUE;
1945
1946 if (extra_partitions & XP_UEFI_NTFS) {
1947 uefi_ntfs_size = GetResourceSize(hMainInstance, MAKEINTRESOURCEA(IDR_UEFI_NTFS), _RT_RCDATA, "uefi-ntfs.img");
1948 if (uefi_ntfs_size == 0)
1949 return FALSE;
1950 }
1951 memset(partition_offset, 0, sizeof(partition_offset));
1952 memset(SelectedDrive.PartitionOffset, 0, sizeof(SelectedDrive.PartitionOffset));
1953 memset(SelectedDrive.PartitionSize, 0, sizeof(SelectedDrive.PartitionSize));
1954
1955 // Compute the start offset of our first partition
1956 if ((partition_style == PARTITION_STYLE_GPT) || (!IsChecked(IDC_OLD_BIOS_FIXES))) {
1957 // Go with the MS 1 MB wastage at the beginning...
1958 DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = MB;
1959 } else {
1960 // Some folks appear to think that 'Fixes for old BIOSes' is some kind of magic
1961 // wand and are adamant to try to apply them when creating *MODERN* VHD drives.
1962 // This, however, wrecks havok on MS' internal format calls because, as opposed
1963 // to what is the case for regular drives, VHDs require each cluster block to
1964 // be aligned to the cluster size, and that may not be the case with the stupid
1965 // CHS sizes that IBM imparted upon us. Long story short, we now align to a
1966 // cylinder size that is itself aligned to the cluster size.
1967 // If this actually breaks old systems, please send your complaints to IBM.
1968 LONGLONG ClusterSize = (LONGLONG)ComboBox_GetCurItemData(hClusterSize);
1969 if (ClusterSize == 0)
1970 ClusterSize = 0x200;
1971 DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart =
1972 ((bytes_per_track + (ClusterSize - 1)) / ClusterSize) * ClusterSize;
1973 }
1974
1975 // Having the ESP up front may help (and is the Microsoft recommended way) but this
1976 // is only achievable if we can mount more than one partition at once, which means
1977 // either fixed drive or Windows 10 1703 or later.
1978 if (((SelectedDrive.MediaType == FixedMedia) || (nWindowsBuildNumber > 15000)) &&
1979 (extra_partitions & XP_ESP)) {
1980 assert(partition_style == PARTITION_STYLE_GPT);
1981 extra_part_name = L"EFI System Partition";
1982 DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart = esp_size;
1983 DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_GENERIC_ESP;
1984 uprintf("● Creating %S (offset: %lld, size: %s)", extra_part_name, DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart,
1985 SizeToHumanReadable(DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart, TRUE, FALSE));
1986 IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionId));
1987 wcsncpy(DriveLayoutEx.PartitionEntry[pn].Gpt.Name, extra_part_name, ARRAYSIZE(DriveLayoutEx.PartitionEntry[pn].Gpt.Name));
1988 // Zero the first sectors from this partition to avoid file system caching issues
1989 if (!ClearPartition(hDrive, DriveLayoutEx.PartitionEntry[pn].StartingOffset, size_to_clear))
1990 uprintf("Could not zero %S: %s", extra_part_name, WindowsErrorString());
1991 SelectedDrive.PartitionOffset[pn] = DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart;
1992 SelectedDrive.PartitionSize[pn] = DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart;
1993 partition_offset[PI_ESP] = SelectedDrive.PartitionOffset[pn];
1994 pn++;
1995 DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = DriveLayoutEx.PartitionEntry[pn - 1].StartingOffset.QuadPart +
1996 DriveLayoutEx.PartitionEntry[pn - 1].PartitionLength.QuadPart;
1997 // Clear the extra partition we processed
1998 extra_partitions &= ~(XP_ESP);
1999 }
2000
2001 // If required, set the MSR partition (GPT only - must be created before the data part)
2002 if (extra_partitions & XP_MSR) {
2003 assert(partition_style == PARTITION_STYLE_GPT);
2004 extra_part_name = L"Microsoft Reserved Partition";
2005 DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart = 128*MB;
2006 DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = PARTITION_MICROSOFT_RESERVED;
2007 uprintf("● Creating %S (offset: %lld, size: %s)", extra_part_name, DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart,
2008 SizeToHumanReadable(DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart, TRUE, FALSE));
2009 IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionId));
2010 wcsncpy(DriveLayoutEx.PartitionEntry[pn].Gpt.Name, extra_part_name, ARRAYSIZE(DriveLayoutEx.PartitionEntry[pn].Gpt.Name));
2011 // Zero the first sectors from this partition to avoid file system caching issues
2012 if (!ClearPartition(hDrive, DriveLayoutEx.PartitionEntry[pn].StartingOffset, size_to_clear))
2013 uprintf("Could not zero %S: %s", extra_part_name, WindowsErrorString());
2014 SelectedDrive.PartitionOffset[pn] = DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart;
2015 SelectedDrive.PartitionSize[pn] = DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart;
2016 pn++;
2017 DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = DriveLayoutEx.PartitionEntry[pn-1].StartingOffset.QuadPart +
2018 DriveLayoutEx.PartitionEntry[pn-1].PartitionLength.QuadPart;
2019 // Clear the extra partition we processed
2020 extra_partitions &= ~(XP_MSR);
2021 }
2022
2023 // Set our main data partition
2024 if (write_as_esp) {
2025 // Align ESP to 64 MB while leaving at least 32 MB free space
2026 esp_size = max(esp_size, ((((LONGLONG)img_report.projected_size / MB) + 96) / 64) * 64 * MB);
2027 main_part_size_in_sectors = (esp_size - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart) /
2028 SelectedDrive.SectorSize;
2029 } else {
2030 main_part_size_in_sectors = (main_part_size - DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart) /
2031 // Need 33 sectors at the end for secondary GPT
2032 SelectedDrive.SectorSize - ((partition_style == PARTITION_STYLE_GPT) ? 33 : 0);
2033 }
2034 if (extra_partitions) {
2035 // Adjust the size according to extra partitions (which we always align to a track)
2036 if (extra_partitions & XP_ESP) {
2037 extra_part_name = L"EFI System";
2038 extra_part_size_in_tracks = (esp_size + bytes_per_track - 1) / bytes_per_track;
2039 } else if (extra_partitions & XP_UEFI_NTFS) {
2040 extra_part_name = L"UEFI:NTFS";
2041 extra_part_size_in_tracks = (max(MIN_EXTRA_PART_SIZE, uefi_ntfs_size) + bytes_per_track - 1) / bytes_per_track;
2042 } else if ((extra_partitions & XP_CASPER)) {
2043 assert(persistence_size != 0);
2044 extra_part_name = L"Linux Persistence";
2045 extra_part_size_in_tracks = persistence_size / bytes_per_track;
2046 } else if (extra_partitions & XP_COMPAT) {
2047 extra_part_name = L"BIOS Compatibility";
2048 extra_part_size_in_tracks = 1; // One track for the extra partition
2049 } else {
2050 assert(FALSE);
2051 }
2052 // NB: Because we already subtracted the backup GPT size from the main partition size and
2053 // this extra partition is indexed on main size, it does not overflow into the backup GPT.
2054 main_part_size_in_sectors = ((main_part_size_in_sectors / SelectedDrive.SectorsPerTrack) -
2055 extra_part_size_in_tracks) * SelectedDrive.SectorsPerTrack;
2056 }
2057 if (main_part_size_in_sectors <= 0) {
2058 uprintf("Error: Invalid %S size", main_part_name);
2059 return FALSE;
2060 }
2061 uprintf("● Creating %S (offset: %lld, size: %s)", main_part_name, DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart,
2062 SizeToHumanReadable(main_part_size_in_sectors * SelectedDrive.SectorSize, TRUE, FALSE));
2063 // Zero the beginning of this partition to avoid conflicting leftovers
2064 if (!ClearPartition(hDrive, DriveLayoutEx.PartitionEntry[pn].StartingOffset, size_to_clear))
2065 uprintf("Could not zero %S: %s", main_part_name, WindowsErrorString());
2066
2067 DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart = main_part_size_in_sectors * SelectedDrive.SectorSize;
2068 if (partition_style == PARTITION_STYLE_MBR) {
2069 DriveLayoutEx.PartitionEntry[pn].Mbr.BootIndicator = (boot_type != BT_NON_BOOTABLE);
2070 switch (file_system) {
2071 case FS_FAT16:
2072 DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x0e; // FAT16 LBA
2073 break;
2074 case FS_NTFS:
2075 case FS_EXFAT:
2076 case FS_UDF:
2077 case FS_REFS:
2078 DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x07;
2079 break;
2080 case FS_EXT2:
2081 case FS_EXT3:
2082 case FS_EXT4:
2083 DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x83;
2084 break;
2085 case FS_FAT32:
2086 DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x0c; // FAT32 LBA
2087 break;
2088 default:
2089 uprintf("Unsupported file system");
2090 return FALSE;
2091 }
2092 } else {
2093 DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = write_as_esp ? PARTITION_GENERIC_ESP : PARTITION_MICROSOFT_DATA;
2094 IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionId));
2095 wcsncpy(DriveLayoutEx.PartitionEntry[pn].Gpt.Name, main_part_name, ARRAYSIZE(DriveLayoutEx.PartitionEntry[pn].Gpt.Name));
2096 }
2097 SelectedDrive.PartitionOffset[pn] = DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart;
2098 SelectedDrive.PartitionSize[pn] = DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart;
2099 partition_offset[PI_MAIN] = SelectedDrive.PartitionOffset[pn];
2100 pn++;
2101
2102 // Set the optional extra partition
2103 if (extra_partitions) {
2104 // Should end on a track boundary
2105 DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart = DriveLayoutEx.PartitionEntry[pn-1].StartingOffset.QuadPart +
2106 DriveLayoutEx.PartitionEntry[pn-1].PartitionLength.QuadPart;
2107 DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart = (extra_partitions & XP_UEFI_NTFS)?uefi_ntfs_size:
2108 extra_part_size_in_tracks * bytes_per_track;
2109 uprintf("● Creating %S Partition (offset: %lld, size: %s)", extra_part_name, DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart,
2110 SizeToHumanReadable(DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart, TRUE, FALSE));
2111 SelectedDrive.PartitionOffset[pn] = DriveLayoutEx.PartitionEntry[pn].StartingOffset.QuadPart;
2112 SelectedDrive.PartitionSize[pn] = DriveLayoutEx.PartitionEntry[pn].PartitionLength.QuadPart;
2113 if (extra_partitions & XP_CASPER)
2114 partition_offset[PI_CASPER] = SelectedDrive.PartitionOffset[pn];
2115 else if (extra_partitions & XP_ESP)
2116 partition_offset[PI_ESP] = SelectedDrive.PartitionOffset[pn];
2117
2118 if (partition_style == PARTITION_STYLE_GPT) {
2119 DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionType = (extra_partitions & XP_ESP) ? PARTITION_GENERIC_ESP : PARTITION_MICROSOFT_DATA;
2120 IGNORE_RETVAL(CoCreateGuid(&DriveLayoutEx.PartitionEntry[pn].Gpt.PartitionId));
2121 wcsncpy(DriveLayoutEx.PartitionEntry[pn].Gpt.Name, (extra_partitions & XP_ESP) ? L"EFI System Partition" : extra_part_name,
2122 ARRAYSIZE(DriveLayoutEx.PartitionEntry[pn].Gpt.Name));
2123 } else {
2124 if (extra_partitions & (XP_UEFI_NTFS | XP_ESP)) {
2125 DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0xef;
2126 } else if (extra_partitions & XP_CASPER) {
2127 DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = 0x83;
2128 } else if (extra_partitions & XP_COMPAT) {
2129 DriveLayoutEx.PartitionEntry[pn].Mbr.PartitionType = RUFUS_EXTRA_PARTITION_TYPE;
2130 // Set the one track compatibility partition to be all hidden sectors
2131 DriveLayoutEx.PartitionEntry[pn].Mbr.HiddenSectors = SelectedDrive.SectorsPerTrack;
2132 } else {
2133 assert(FALSE);
2134 }
2135 }
2136
2137 // We need to write the UEFI:NTFS partition before we refresh the disk
2138 if (extra_partitions & XP_UEFI_NTFS) {
2139 uprintf("Writing %S data...", extra_part_name);
2140 if (!SetFilePointerEx(hDrive, DriveLayoutEx.PartitionEntry[pn].StartingOffset, NULL, FILE_BEGIN)) {
2141 uprintf("Could not set position");
2142 return FALSE;
2143 }
2144 buffer = GetResource(hMainInstance, MAKEINTRESOURCEA(IDR_UEFI_NTFS), _RT_RCDATA, "uefi-ntfs.img", &bufsize, FALSE);
2145 if (buffer == NULL) {
2146 uprintf("Could not access source image");
2147 return FALSE;
2148 }
2149 if(!WriteFileWithRetry(hDrive, buffer, bufsize, &size, WRITE_RETRIES)) {
2150 uprintf("Write error: %s", WindowsErrorString());
2151 return FALSE;
2152 }
2153 installed_uefi_ntfs = TRUE;
2154 }
2155 pn++;
2156 }
2157
2158 // Initialize the remaining partition data
2159 for (i = 0; i < pn; i++) {
2160 DriveLayoutEx.PartitionEntry[i].PartitionNumber = i + 1;
2161 DriveLayoutEx.PartitionEntry[i].PartitionStyle = partition_style;
2162 DriveLayoutEx.PartitionEntry[i].RewritePartition = TRUE;
2163 }
2164
2165 switch (partition_style) {
2166 case PARTITION_STYLE_MBR:
2167 CreateDisk.PartitionStyle = PARTITION_STYLE_MBR;
2168 // If MBR+UEFI is selected, write an UEFI marker in lieu of the regular MBR signature.
2169 // This helps us reselect the partition scheme option that was used when creating the
2170 // drive in Rufus. As far as I can tell, Windows doesn't care much if this signature
2171 // isn't unique for USB drives.
2172 CreateDisk.Mbr.Signature = mbr_uefi_marker?MBR_UEFI_MARKER:(DWORD)GetTickCount64();
2173
2174 DriveLayoutEx.PartitionStyle = PARTITION_STYLE_MBR;
2175 DriveLayoutEx.PartitionCount = 4; // Must be multiple of 4 for MBR
2176 DriveLayoutEx.Type.Mbr.Signature = CreateDisk.Mbr.Signature;
2177 // TODO: CHS fixup (32 sectors/track) through a cheat mode, if requested
2178 // NB: disk geometry is computed by BIOS & co. by finding a match between LBA and CHS value of first partition
2179 // ms-sys's write_partition_number_of_heads() and write_partition_start_sector_number() can be used if needed
2180 break;
2181 case PARTITION_STYLE_GPT:
2182 // TODO: (HOW?!?!?) As per MSDN: "When specifying a GUID partition table (GPT) as the PARTITION_STYLE of the CREATE_DISK
2183 // structure, an application should wait for the MSR partition arrival before sending the IOCTL_DISK_SET_DRIVE_LAYOUT_EX
2184 // control code. For more information about device notification, see RegisterDeviceNotification."
2185
2186 CreateDisk.PartitionStyle = PARTITION_STYLE_GPT;
2187 IGNORE_RETVAL(CoCreateGuid(&CreateDisk.Gpt.DiskId));
2188 CreateDisk.Gpt.MaxPartitionCount = MAX_PARTITIONS;
2189
2190 DriveLayoutEx.PartitionStyle = PARTITION_STYLE_GPT;
2191 DriveLayoutEx.PartitionCount = pn;
2192 // At the very least, a GPT disk has 34 reserved sectors at the beginning and 33 at the end.
2193 DriveLayoutEx.Type.Gpt.StartingUsableOffset.QuadPart = 34 * SelectedDrive.SectorSize;
2194 DriveLayoutEx.Type.Gpt.UsableLength.QuadPart = SelectedDrive.DiskSize - (34+33) * SelectedDrive.SectorSize;
2195 DriveLayoutEx.Type.Gpt.MaxPartitionCount = MAX_PARTITIONS;
2196 DriveLayoutEx.Type.Gpt.DiskId = CreateDisk.Gpt.DiskId;
2197 break;
2198 }
2199
2200 // If you don't call IOCTL_DISK_CREATE_DISK, the IOCTL_DISK_SET_DRIVE_LAYOUT_EX call will fail
2201 size = sizeof(CreateDisk);
2202 r = DeviceIoControl(hDrive, IOCTL_DISK_CREATE_DISK, (BYTE*)&CreateDisk, size, NULL, 0, &size, NULL);
2203 if (!r) {
2204 uprintf("Could not reset disk: %s", WindowsErrorString());
2205 return FALSE;
2206 }
2207
2208 // "The goggles, they do nothing!"
2209 RefreshDriveLayout(hDrive);
2210
2211 size = sizeof(DriveLayoutEx) - ((partition_style == PARTITION_STYLE_GPT)?((4-pn)*sizeof(PARTITION_INFORMATION_EX)):0);
2212 r = DeviceIoControl(hDrive, IOCTL_DISK_SET_DRIVE_LAYOUT_EX, (BYTE*)&DriveLayoutEx, size, NULL, 0, &size, NULL);
2213 if (!r) {
2214 uprintf("Could not set drive layout: %s", WindowsErrorString());
2215 return FALSE;
2216 }
2217
2218 if (!RefreshDriveLayout(hDrive))
2219 return FALSE;
2220
2221 return TRUE;
2222 }
2223
2224 BOOL RefreshDriveLayout(HANDLE hDrive)
2225 {
2226 BOOL r;
2227 DWORD size;
2228
2229 // Diskpart does call the following IOCTL this after updating the partition table, so we do too
2230 r = DeviceIoControl(hDrive, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &size, NULL );
2231 if (!r)
2232 uprintf("Could not refresh drive layout: %s", WindowsErrorString());
2233 return r;
2234 }
2235
2236 /* Initialize disk for partitioning */
2237 BOOL InitializeDisk(HANDLE hDrive)
2238 {
2239 BOOL r;
2240 DWORD size;
2241 CREATE_DISK CreateDisk = {PARTITION_STYLE_RAW, {{0}}};
2242
2243 uprintf("Initializing disk...");
2244
2245 size = sizeof(CreateDisk);
2246 r = DeviceIoControl(hDrive, IOCTL_DISK_CREATE_DISK,
2247 (BYTE*)&CreateDisk, size, NULL, 0, &size, NULL );
2248 if (!r) {
2249 uprintf("Could not delete drive layout: %s", WindowsErrorString());
2250 safe_closehandle(hDrive);
2251 return FALSE;
2252 }
2253
2254 r = DeviceIoControl(hDrive, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &size, NULL );
2255 if (!r) {
2256 uprintf("Could not refresh drive layout: %s", WindowsErrorString());
2257 safe_closehandle(hDrive);
2258 return FALSE;
2259 }
2260
2261 return TRUE;
2262 }
2263
2264 /*
2265 * Convert MBR or GPT partition types to their human readable forms
2266 */
2267 const char* GetMBRPartitionType(const uint8_t type)
2268 {
2269 int i;
2270 for (i = 0; (i < ARRAYSIZE(mbr_type)) && (mbr_type[i].type != type); i++);
2271 return (i < ARRAYSIZE(mbr_type)) ? mbr_type[i].name : "Unknown";
2272 }
2273
2274 const char* GetGPTPartitionType(const GUID* guid)
2275 {
2276 int i;
2277 for (i = 0; (i < ARRAYSIZE(gpt_type)) && !CompareGUID(guid, gpt_type[i].guid); i++);
2278 return (i < ARRAYSIZE(gpt_type)) ? gpt_type[i].name : GuidToString(guid);
2279 }