[vhd] add write support for .vhdx and .ffu images

* Also move VHD mounting function calls from iso.c to vhd.c and remove unused VHD footer elements.
This commit is contained in:
Pete Batard 2023-07-06 19:47:26 +01:00
parent f411d526d6
commit 5bbcba8534
No known key found for this signature in database
GPG key ID: 38E0CF5E69EDD671
10 changed files with 226 additions and 223 deletions

215
src/vhd.c
View file

@ -58,7 +58,7 @@ typedef struct {
uint32_t wim_nb_files, wim_proc_files, wim_extra_files;
HANDLE wim_thread = NULL;
extern int default_thread_priority;
extern BOOL ignore_boot_marker;
extern BOOL ignore_boot_marker, has_ffu_support;
extern RUFUS_DRIVE rufus_drive[MAX_DRIVES];
extern HANDLE format_thread;
@ -67,7 +67,6 @@ static uint32_t progress_report_mask;
static uint64_t progress_offset = 0, progress_total = 100;
static wchar_t wmount_path[MAX_PATH] = { 0 }, wmount_track[MAX_PATH] = { 0 };
static char sevenzip_path[MAX_PATH];
static const char conectix_str[] = VHD_FOOTER_COOKIE;
static BOOL count_files;
static int progress_op = OP_FILE_COPY, progress_msg = MSG_267;
@ -83,7 +82,7 @@ static BOOL Get7ZipPath(void)
typedef struct {
const char* ext;
bled_compression_type type;
uint8_t type;
} comp_assoc;
static comp_assoc file_assoc[] = {
@ -94,33 +93,75 @@ static comp_assoc file_assoc[] = {
{ ".bz2", BLED_COMPRESSION_BZIP2 },
{ ".xz", BLED_COMPRESSION_XZ },
{ ".vtsi", BLED_COMPRESSION_VTSI },
{ ".ffu", BLED_COMPRESSION_MAX },
{ ".vhd", BLED_COMPRESSION_MAX + 1 },
{ ".vhdx", BLED_COMPRESSION_MAX + 2 },
};
// For now we consider that an image that matches a known extension is bootable
// Look for a boot marker in the MBR area of the image
static BOOL IsCompressedBootableImage(const char* path)
{
char *p;
char *ext = NULL, *physical_disk = NULL;
unsigned char *buf = NULL;
int i;
FILE* fd = NULL;
BOOL r = FALSE;
int64_t dc;
int64_t dc = 0;
img_report.compression_type = BLED_COMPRESSION_NONE;
for (p = (char*)&path[strlen(path)-1]; (*p != '.') && (p != path); p--);
if (safe_strlen(path) > 4)
for (ext = (char*)&path[safe_strlen(path) - 1]; (*ext != '.') && (ext != path); ext--);
if (p == path)
return FALSE;
for (i = 0; i<ARRAYSIZE(file_assoc); i++) {
if (strcmp(p, file_assoc[i].ext) == 0) {
for (i = 0; i < ARRAYSIZE(file_assoc); i++) {
if (safe_stricmp(ext, file_assoc[i].ext) == 0) {
img_report.compression_type = file_assoc[i].type;
buf = malloc(MBR_SIZE);
if (buf == NULL)
return FALSE;
FormatStatus = 0;
bled_init(0, uprintf, NULL, NULL, NULL, NULL, &FormatStatus);
dc = bled_uncompress_to_buffer(path, (char*)buf, MBR_SIZE, file_assoc[i].type);
bled_exit();
if (img_report.compression_type < BLED_COMPRESSION_MAX) {
bled_init(0, uprintf, NULL, NULL, NULL, NULL, &FormatStatus);
dc = bled_uncompress_to_buffer(path, (char*)buf, MBR_SIZE, file_assoc[i].type);
bled_exit();
} else if (img_report.compression_type == BLED_COMPRESSION_MAX) {
// Dism, through FfuProvider.dll, can mount a .ffu as a physicaldrive, which we
// could then use to poke the MBR as we do for VHD... Except Microsoft did design
// dism to FAIL AND EXIT, after mounting the ffu as a virtual drive, if it doesn't
// find something that looks like Windows at the specified image index... which it
// usually won't in our case. So, curse Microsoft and their incredible short-
// sightedness (or, most likely in this case, intentional malice, by BREACHING the
// OS contract to keep useful disk APIs for their usage, and their usage only).
// Then again, considering that .ffu's are GPT based, the marker should always be
// present, so just check for the FFU signature and pretend there's a marker then.
if (has_ffu_support) {
fd = fopenU(path, "rb");
if (fd != NULL) {
dc = fread(buf, 1, MBR_SIZE, fd);
fclose(fd);
// The signature may not be constant, but since the only game in town to
// create FFU is dism, and dism appears to use "SignedImage " always,.we
// might as well use this to our advantage.
if (strncmp(&buf[4], "SignedImage ", 12) == 0) {
// At this stage, the buffer is only used for marker validation.
buf[0x1FE] = 0x55;
buf[0x1FF] = 0xAA;
}
} else
uprintf("Could not open %s: %d", path, errno);
} else {
uprintf(" An FFU image was selected, but this system does not have FFU support!");
}
} else {
physical_disk = VhdMountImage(path);
if (physical_disk != NULL) {
fd = fopenU(physical_disk, "rb");
if (fd != NULL) {
dc = fread(buf, 1, MBR_SIZE, fd);
fclose(fd);
}
}
VhdUnmountImage();
}
if (dc != MBR_SIZE) {
free(buf);
return FALSE;
@ -139,10 +180,7 @@ int8_t IsBootableImage(const char* path)
{
HANDLE handle = INVALID_HANDLE_VALUE;
LARGE_INTEGER liImageSize;
vhd_footer* footer = NULL;
DWORD size;
size_t i;
uint32_t checksum, old_checksum;
uint64_t wim_magic = 0;
LARGE_INTEGER ptr = { 0 };
int8_t is_bootable_img;
@ -171,40 +209,7 @@ int8_t IsBootableImage(const char* path)
if (img_report.is_windows_img)
goto out;
size = sizeof(vhd_footer);
if ((img_report.compression_type == BLED_COMPRESSION_NONE) && (img_report.image_size >= (512 + size))) {
footer = (vhd_footer*)malloc(size);
ptr.QuadPart = img_report.image_size - size;
if ( (footer == NULL) || (!SetFilePointerEx(handle, ptr, NULL, FILE_BEGIN)) ||
(!ReadFile(handle, footer, size, &size, NULL)) || (size != sizeof(vhd_footer)) ) {
uprintf(" Could not read VHD footer");
is_bootable_img = -3;
goto out;
}
if (memcmp(footer->cookie, conectix_str, sizeof(footer->cookie)) == 0) {
img_report.image_size -= sizeof(vhd_footer);
if ( (bswap_uint32(footer->file_format_version) != VHD_FOOTER_FILE_FORMAT_V1_0)
|| (bswap_uint32(footer->disk_type) != VHD_FOOTER_TYPE_FIXED_HARD_DISK)) {
uprintf(" Unsupported type of VHD image");
is_bootable_img = 0;
goto out;
}
// Might as well validate the checksum while we're at it
old_checksum = bswap_uint32(footer->checksum);
footer->checksum = 0;
for (checksum = 0, i = 0; i < sizeof(vhd_footer); i++)
checksum += ((uint8_t*)footer)[i];
checksum = ~checksum;
if (checksum != old_checksum)
uprintf(" Warning: VHD footer seems corrupted (checksum: %04X, expected: %04X)", old_checksum, checksum);
// Need to remove the footer from our payload
uprintf(" Image is a Fixed Hard Disk VHD file");
img_report.is_vhd = TRUE;
}
}
out:
safe_free(footer);
safe_closehandle(handle);
return is_bootable_img;
}
@ -405,7 +410,7 @@ char* WimMountImage(const char* image, int index)
mp.index = index;
// Try to unmount an existing stale image, if there is any
mount_path = GetExistingMountPoint(image, index);
mount_path = WimGetExistingMountPoint(image, index);
if (mount_path != NULL) {
uprintf("WARNING: Found stale '%s [%d]' image mounted on '%s' - Attempting to unmount it...",
image, index, mount_path);
@ -499,7 +504,7 @@ BOOL WimUnmountImage(const char* image, int index, BOOL commit)
// This basically parses HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WIMMount\Mounted Images
// to see if an instance exists with the image/index passed as parameter and returns
// the mount point of this image if found, or NULL otherwise.
char* GetExistingMountPoint(const char* image, int index)
char* WimGetExistingMountPoint(const char* image, int index)
{
static char path[MAX_PATH];
char class[MAX_PATH] = "", guid[40], key_name[MAX_PATH];
@ -886,17 +891,102 @@ BOOL WimApplyImage(const char* image, int index, const char* dst)
}
// VirtDisk API Prototypes since we can't use delay-loading because of MinGW
// See https://github.com/pbatard/rufus/issues/2272#issuecomment-1615976013
PF_TYPE_DECL(WINAPI, DWORD, CreateVirtualDisk, (PVIRTUAL_STORAGE_TYPE, PCWSTR,
VIRTUAL_DISK_ACCESS_MASK, PSECURITY_DESCRIPTOR, CREATE_VIRTUAL_DISK_FLAG, ULONG,
PCREATE_VIRTUAL_DISK_PARAMETERS, LPOVERLAPPED, PHANDLE));
PF_TYPE_DECL(WINAPI, DWORD, OpenVirtualDisk, (PVIRTUAL_STORAGE_TYPE, PCWSTR,
VIRTUAL_DISK_ACCESS_MASK, OPEN_VIRTUAL_DISK_FLAG, POPEN_VIRTUAL_DISK_PARAMETERS, PHANDLE));
PF_TYPE_DECL(WINAPI, DWORD, AttachVirtualDisk, (HANDLE, PSECURITY_DESCRIPTOR,
ATTACH_VIRTUAL_DISK_FLAG, ULONG, PATTACH_VIRTUAL_DISK_PARAMETERS, LPOVERLAPPED));
PF_TYPE_DECL(WINAPI, DWORD, DetachVirtualDisk, (HANDLE, DETACH_VIRTUAL_DISK_FLAG, ULONG));
PF_TYPE_DECL(WINAPI, DWORD, GetVirtualDiskPhysicalPath, (HANDLE, PULONG, PWSTR));
PF_TYPE_DECL(WINAPI, DWORD, GetVirtualDiskOperationProgress, (HANDLE, LPOVERLAPPED,
PVIRTUAL_DISK_PROGRESS));
static char physical_path[128] = "";
static HANDLE mounted_handle = INVALID_HANDLE_VALUE;
// Mount an ISO or a VHD/VHDX image
// Returns the physical path of the mounted image or NULL on error.
char* VhdMountImage(const char* path)
{
VIRTUAL_STORAGE_TYPE vtype = { VIRTUAL_STORAGE_TYPE_DEVICE_ISO, VIRTUAL_STORAGE_TYPE_VENDOR_MICROSOFT };
ATTACH_VIRTUAL_DISK_PARAMETERS vparams = { 0 };
DWORD r;
wchar_t wtmp[128];
ULONG size = ARRAYSIZE(wtmp);
wconvert(path);
char *ret = NULL, *ext = NULL;
PF_INIT_OR_OUT(OpenVirtualDisk, VirtDisk);
PF_INIT_OR_OUT(AttachVirtualDisk, VirtDisk);
PF_INIT_OR_OUT(GetVirtualDiskPhysicalPath, VirtDisk);
if (wpath == NULL)
return NULL;
if ((mounted_handle != NULL) && (mounted_handle != INVALID_HANDLE_VALUE))
VhdUnmountImage();
if (safe_strlen(path) > 4)
for (ext = (char*)&path[safe_strlen(path) - 1]; (*ext != '.') && (ext != path); ext--);
if (safe_stricmp(ext, ".vhdx") == 0)
vtype.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHDX;
else if (safe_stricmp(ext, ".vhdx") == 0)
vtype.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHD;
r = pfOpenVirtualDisk(&vtype, wpath, VIRTUAL_DISK_ACCESS_READ | VIRTUAL_DISK_ACCESS_GET_INFO,
OPEN_VIRTUAL_DISK_FLAG_NONE, NULL, &mounted_handle);
if (r != ERROR_SUCCESS) {
SetLastError(r);
uprintf("Could not open image '%s': %s", path, WindowsErrorString());
goto out;
}
vparams.Version = ATTACH_VIRTUAL_DISK_VERSION_1;
r = pfAttachVirtualDisk(mounted_handle, NULL, ATTACH_VIRTUAL_DISK_FLAG_READ_ONLY |
ATTACH_VIRTUAL_DISK_FLAG_NO_DRIVE_LETTER, 0, &vparams, NULL);
if (r != ERROR_SUCCESS) {
SetLastError(r);
uprintf("Could not mount image '%s': %s", path, WindowsErrorString());
goto out;
}
r = pfGetVirtualDiskPhysicalPath(mounted_handle, &size, wtmp);
if (r != ERROR_SUCCESS) {
SetLastError(r);
uprintf("Could not obtain physical path for mounted image '%s': %s", path, WindowsErrorString());
goto out;
}
wchar_to_utf8_no_alloc(wtmp, physical_path, sizeof(physical_path));
ret = physical_path;
out:
if (ret == NULL)
VhdUnmountImage();
wfree(path);
return ret;
}
void VhdUnmountImage(void)
{
PF_INIT_OR_OUT(DetachVirtualDisk, VirtDisk);
if ((mounted_handle == NULL) || (mounted_handle == INVALID_HANDLE_VALUE))
goto out;
pfDetachVirtualDisk(mounted_handle, DETACH_VIRTUAL_DISK_FLAG_NONE, 0);
safe_closehandle(mounted_handle);
out:
physical_path[0] = 0;
}
// Since we no longer have to deal with Windows 7, we can call on CreateVirtualDisk()
// to backup a physical disk to VHD/VHDX. Now if this could also be used to create an
// ISO from optical media that would be swell, but no matter what I tried, it didn't
// seem possible...
static DWORD WINAPI SaveVHDThread(void* param)
static DWORD WINAPI VhdSaveImageThread(void* param)
{
IMG_SAVE* img_save = (IMG_SAVE*)param;
HANDLE handle = INVALID_HANDLE_VALUE;
@ -984,21 +1074,21 @@ out:
// calls, as well as how to properly hook into the DLL for every arch/every release
// of Windows, would be a massive timesink, we just take a shortcut by calling dism
// directly, as imperfect as such a solution might be...
static DWORD WINAPI SaveFFUThread(void* param)
static DWORD WINAPI FfuSaveImageThread(void* param)
{
DWORD r;
IMG_SAVE* img_save = (IMG_SAVE*)param;
char cmd[MAX_PATH + 128], *letter = "", *label;
GetDriveLabel(SelectedDrive.DeviceNumber, letter, &label, TRUE);
static_sprintf(cmd, "dism /capture-ffu /capturedrive=%s /imagefile=\"%s\" "
"/name:\"%s\" /description:\"Created by %s (%s)\"",
static_sprintf(cmd, "dism /Capture-Ffu /CaptureDrive:%s /ImageFile:\"%s\" "
"/Name:\"%s\" /Description:\"Created by %s (%s)\"",
img_save->DevicePath, img_save->ImagePath, label, APPLICATION_NAME, RUFUS_URL);
uprintf("Running command: '%s", cmd);
r = RunCommandWithProgress(cmd, sysnative_dir, TRUE, MSG_261);
if (r != 0 && !IS_ERROR(FormatStatus)) {
SetLastError(r);
uprintf("Failed to create FFU image: %s", WindowsErrorString());
uprintf("Failed to capture FFU image: %s", WindowsErrorString());
FormatStatus = ERROR_SEVERITY_ERROR | FAC(FACILITY_WINDOWS) | SCODE_CODE(r);
}
safe_free(img_save->DevicePath);
@ -1007,7 +1097,7 @@ static DWORD WINAPI SaveFFUThread(void* param)
ExitThread(r);
}
void SaveVHD(void)
void VhdSaveImage(void)
{
static IMG_SAVE img_save = { 0 };
char filename[128];
@ -1023,9 +1113,8 @@ void SaveVHD(void)
static_sprintf(filename, "%s.vhdx", rufus_drive[DriveIndex].label);
img_save.DeviceNum = (DWORD)ComboBox_GetItemData(hDeviceList, DriveIndex);
img_save.DevicePath = GetPhysicalName(img_save.DeviceNum);
// FFU support started with Windows 10 1709 (through FfuProvider.dll) and requires GPT
static_sprintf(path, "%s\\dism\\FfuProvider.dll", sysnative_dir);
if ((_accessU(path, 0) != 0) || SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT)
// FFU support requires GPT
if (!has_ffu_support || SelectedDrive.PartitionStyle != PARTITION_STYLE_GPT)
img_ext.count = 2;
img_save.ImagePath = FileDialog(TRUE, NULL, &img_ext, 0);
img_save.Type = VIRTUAL_STORAGE_TYPE_DEVICE_VHD;
@ -1048,7 +1137,7 @@ void SaveVHD(void)
FormatStatus = 0;
InitProgress(TRUE);
format_thread = CreateThread(NULL, 0, img_save.Type == VIRTUAL_STORAGE_TYPE_DEVICE_FFU ?
SaveFFUThread : SaveVHDThread, &img_save, 0, NULL);
FfuSaveImageThread : VhdSaveImageThread, &img_save, 0, NULL);
if (format_thread != NULL) {
uprintf("\r\nSave to VHD operation started");
PrintInfo(0, -1);