"Fossies" - the Fresh Open Source Software Archive 
Member "rufus-3.13/src/vhd.c" (20 Nov 2020, 28157 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 "vhd.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 * Virtual Disk Handling functions
4 * Copyright © 2013-2016 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
20 #include <windows.h>
21 #include <stdlib.h>
22 #include <io.h>
23 #include <rpc.h>
24 #include <time.h>
25
26 #include "rufus.h"
27 #include "missing.h"
28 #include "resource.h"
29 #include "msapi_utf8.h"
30
31 #include "drive.h"
32 #include "registry.h"
33 #include "bled/bled.h"
34
35 #define VHD_FOOTER_COOKIE { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x' }
36
37 #define VHD_FOOTER_FEATURES_NONE 0x00000000
38 #define VHD_FOOTER_FEATURES_TEMPORARY 0x00000001
39 #define VHD_FOOTER_FEATURES_RESERVED 0x00000002
40
41 #define VHD_FOOTER_FILE_FORMAT_V1_0 0x00010000
42
43 #define VHD_FOOTER_DATA_OFFSET_FIXED_DISK 0xFFFFFFFFFFFFFFFFULL
44
45 #define VHD_FOOTER_CREATOR_HOST_OS_WINDOWS { 'W', 'i', '2', 'k' }
46 #define VHD_FOOTER_CREATOR_HOST_OS_MAC { 'M', 'a', 'c', ' ' }
47
48 #define VHD_FOOTER_TYPE_FIXED_HARD_DISK 0x00000002
49 #define VHD_FOOTER_TYPE_DYNAMIC_HARD_DISK 0x00000003
50 #define VHD_FOOTER_TYPE_DIFFER_HARD_DISK 0x00000004
51
52 #define WIM_MAGIC 0x0000004D4957534DULL // "MSWIM\0\0\0"
53 #define WIM_HAS_API_EXTRACT 1
54 #define WIM_HAS_7Z_EXTRACT 2
55 #define WIM_HAS_API_APPLY 4
56 #define WIM_HAS_EXTRACT(r) (r & (WIM_HAS_API_EXTRACT|WIM_HAS_7Z_EXTRACT))
57
58 #define SECONDS_SINCE_JAN_1ST_2000 946684800
59
60 /*
61 * VHD Fixed HD footer (Big Endian)
62 * http://download.microsoft.com/download/f/f/e/ffef50a5-07dd-4cf8-aaa3-442c0673a029/Virtual%20Hard%20Disk%20Format%20Spec_10_18_06.doc
63 * NB: If a dymamic implementation is needed, check the GPL v3 compatible C++ implementation from:
64 * https://sourceforge.net/p/urbackup/backend/ci/master/tree/fsimageplugin/
65 */
66 #pragma pack(push, 1)
67 typedef struct vhd_footer {
68 char cookie[8];
69 uint32_t features;
70 uint32_t file_format_version;
71 uint64_t data_offset;
72 uint32_t timestamp;
73 char creator_app[4];
74 uint32_t creator_version;
75 char creator_host_os[4];
76 uint64_t original_size;
77 uint64_t current_size;
78 union {
79 uint32_t geometry;
80 struct {
81 uint16_t cylinders;
82 uint8_t heads;
83 uint8_t sectors;
84 } chs;
85 } disk_geometry;
86 uint32_t disk_type;
87 uint32_t checksum;
88 uuid_t unique_id;
89 uint8_t saved_state;
90 uint8_t reserved[427];
91 } vhd_footer;
92 #pragma pack(pop)
93
94 // WIM API Prototypes
95 #define WIM_GENERIC_READ GENERIC_READ
96 #define WIM_OPEN_EXISTING OPEN_EXISTING
97 #define WIM_UNDOCUMENTED_BULLSHIT 0x20000000
98 PF_TYPE_DECL(WINAPI, HANDLE, WIMCreateFile, (PWSTR, DWORD, DWORD, DWORD, DWORD, PDWORD));
99 PF_TYPE_DECL(WINAPI, BOOL, WIMSetTemporaryPath, (HANDLE, PWSTR));
100 PF_TYPE_DECL(WINAPI, HANDLE, WIMLoadImage, (HANDLE, DWORD));
101 PF_TYPE_DECL(WINAPI, BOOL, WIMMountImage, (PCWSTR, PCWSTR, DWORD, PCWSTR));
102 PF_TYPE_DECL(WINAPI, BOOL, WIMUnmountImage, (PCWSTR, PCWSTR, DWORD, BOOL));
103 PF_TYPE_DECL(WINAPI, BOOL, WIMApplyImage, (HANDLE, PCWSTR, DWORD));
104 PF_TYPE_DECL(WINAPI, BOOL, WIMExtractImagePath, (HANDLE, PWSTR, PWSTR, DWORD));
105 PF_TYPE_DECL(WINAPI, BOOL, WIMGetImageInformation, (HANDLE, PVOID, PDWORD));
106 PF_TYPE_DECL(WINAPI, BOOL, WIMCloseHandle, (HANDLE));
107 PF_TYPE_DECL(WINAPI, DWORD, WIMRegisterMessageCallback, (HANDLE, FARPROC, PVOID));
108 PF_TYPE_DECL(WINAPI, DWORD, WIMUnregisterMessageCallback, (HANDLE, FARPROC));
109 PF_TYPE_DECL(RPC_ENTRY, RPC_STATUS, UuidCreate, (UUID __RPC_FAR*));
110
111 uint32_t wim_nb_files, wim_proc_files, wim_extra_files;
112 HANDLE apply_wim_thread = NULL;
113 extern int default_thread_priority;
114 extern BOOL ignore_boot_marker;
115
116 static uint8_t wim_flags = 0;
117 static wchar_t wmount_path[MAX_PATH] = { 0 };
118 static char sevenzip_path[MAX_PATH];
119 static const char conectix_str[] = VHD_FOOTER_COOKIE;
120 static BOOL count_files;
121
122 static BOOL Get7ZipPath(void)
123 {
124 if ( (GetRegistryKeyStr(REGKEY_HKCU, "7-Zip\\Path", sevenzip_path, sizeof(sevenzip_path)))
125 || (GetRegistryKeyStr(REGKEY_HKLM, "7-Zip\\Path", sevenzip_path, sizeof(sevenzip_path))) ) {
126 static_strcat(sevenzip_path, "\\7z.exe");
127 return (_accessU(sevenzip_path, 0) != -1);
128 }
129 return FALSE;
130 }
131
132 BOOL AppendVHDFooter(const char* vhd_path)
133 {
134 const char creator_os[4] = VHD_FOOTER_CREATOR_HOST_OS_WINDOWS;
135 const char creator_app[4] = { 'r', 'u', 'f', 's' };
136 BOOL r = FALSE;
137 DWORD size;
138 LARGE_INTEGER li;
139 HANDLE handle = INVALID_HANDLE_VALUE;
140 vhd_footer* footer = NULL;
141 uint64_t totalSectors;
142 uint16_t cylinders = 0;
143 uint8_t heads, sectorsPerTrack;
144 uint32_t cylinderTimesHeads;
145 uint32_t checksum;
146 size_t i;
147
148 PF_INIT(UuidCreate, Rpcrt4);
149 handle = CreateFileU(vhd_path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL,
150 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
151 li.QuadPart = 0;
152 if ((handle == INVALID_HANDLE_VALUE) || (!SetFilePointerEx(handle, li, &li, FILE_END))) {
153 uprintf("Could not open image '%s': %s", vhd_path, WindowsErrorString());
154 goto out;
155 }
156 footer = (vhd_footer*)calloc(1, sizeof(vhd_footer));
157 if (footer == NULL) {
158 uprintf("Could not allocate VHD footer");
159 goto out;
160 }
161
162 memcpy(footer->cookie, conectix_str, sizeof(footer->cookie));
163 footer->features = bswap_uint32(VHD_FOOTER_FEATURES_RESERVED);
164 footer->file_format_version = bswap_uint32(VHD_FOOTER_FILE_FORMAT_V1_0);
165 footer->data_offset = bswap_uint64(VHD_FOOTER_DATA_OFFSET_FIXED_DISK);
166 footer->timestamp = bswap_uint32((uint32_t)(_time64(NULL) - SECONDS_SINCE_JAN_1ST_2000));
167 memcpy(footer->creator_app, creator_app, sizeof(creator_app));
168 footer->creator_version = bswap_uint32((rufus_version[0]<<16)|rufus_version[1]);
169 memcpy(footer->creator_host_os, creator_os, sizeof(creator_os));
170 footer->original_size = bswap_uint64(li.QuadPart);
171 footer->current_size = footer->original_size;
172 footer->disk_type = bswap_uint32(VHD_FOOTER_TYPE_FIXED_HARD_DISK);
173 if ((pfUuidCreate == NULL) || (pfUuidCreate(&footer->unique_id) != RPC_S_OK))
174 uprintf("Warning: could not set VHD UUID");
175
176 // Compute CHS, as per the VHD specs
177 totalSectors = li.QuadPart / 512;
178 if (totalSectors > 65535 * 16 * 255) {
179 totalSectors = 65535 * 16 * 255;
180 }
181
182 if (totalSectors >= 65535 * 16 * 63) {
183 sectorsPerTrack = 255;
184 heads = 16;
185 cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
186 } else {
187 sectorsPerTrack = 17;
188 cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
189
190 heads = (cylinderTimesHeads + 1023) / 1024;
191
192 if (heads < 4) {
193 heads = 4;
194 }
195 if (cylinderTimesHeads >= ((uint32_t)heads * 1024) || heads > 16) {
196 sectorsPerTrack = 31;
197 heads = 16;
198 cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
199 }
200 if (cylinderTimesHeads >= ((uint32_t)heads * 1024)) {
201 sectorsPerTrack = 63;
202 heads = 16;
203 cylinderTimesHeads = (uint32_t)(totalSectors / sectorsPerTrack);
204 }
205 }
206 cylinders = cylinderTimesHeads / heads;
207 footer->disk_geometry.chs.cylinders = bswap_uint16(cylinders);
208 footer->disk_geometry.chs.heads = heads;
209 footer->disk_geometry.chs.sectors = sectorsPerTrack;
210
211 // Compute the VHD footer checksum
212 for (checksum=0, i=0; i<sizeof(vhd_footer); i++)
213 checksum += ((uint8_t*)footer)[i];
214 footer->checksum = bswap_uint32(~checksum);
215
216 if (!WriteFileWithRetry(handle, footer, sizeof(vhd_footer), &size, WRITE_RETRIES)) {
217 uprintf("Could not write VHD footer: %s", WindowsErrorString());
218 goto out;
219 }
220 r = TRUE;
221
222 out:
223 safe_free(footer);
224 safe_closehandle(handle);
225 return r;
226 }
227
228 typedef struct {
229 const char* ext;
230 bled_compression_type type;
231 } comp_assoc;
232
233 static comp_assoc file_assoc[] = {
234 { ".zip", BLED_COMPRESSION_ZIP },
235 { ".Z", BLED_COMPRESSION_LZW },
236 { ".gz", BLED_COMPRESSION_GZIP },
237 { ".lzma", BLED_COMPRESSION_LZMA },
238 { ".bz2", BLED_COMPRESSION_BZIP2 },
239 { ".xz", BLED_COMPRESSION_XZ },
240 };
241
242 // For now we consider that an image that matches a known extension is bootable
243 #define MBR_SIZE 512 // Might need to review this once we see bootable 4k systems
244 BOOL IsCompressedBootableImage(const char* path)
245 {
246 char *p;
247 unsigned char *buf = NULL;
248 int i;
249 BOOL r = FALSE;
250 int64_t dc;
251
252 img_report.compression_type = BLED_COMPRESSION_NONE;
253 for (p = (char*)&path[strlen(path)-1]; (*p != '.') && (p != path); p--);
254
255 if (p == path)
256 return FALSE;
257
258 for (i = 0; i<ARRAYSIZE(file_assoc); i++) {
259 if (strcmp(p, file_assoc[i].ext) == 0) {
260 img_report.compression_type = file_assoc[i].type;
261 buf = malloc(MBR_SIZE);
262 if (buf == NULL)
263 return FALSE;
264 FormatStatus = 0;
265 bled_init(_uprintf, NULL, NULL, NULL, NULL, &FormatStatus);
266 dc = bled_uncompress_to_buffer(path, (char*)buf, MBR_SIZE, file_assoc[i].type);
267 bled_exit();
268 if (dc != MBR_SIZE) {
269 free(buf);
270 return FALSE;
271 }
272 r = (buf[0x1FE] == 0x55) && (buf[0x1FF] == 0xAA);
273 free(buf);
274 return r;
275 }
276 }
277
278 return FALSE;
279 }
280
281 // 0: non-bootable, 1: bootable, 2: forced bootable
282 uint8_t IsBootableImage(const char* path)
283 {
284 HANDLE handle = INVALID_HANDLE_VALUE;
285 LARGE_INTEGER liImageSize;
286 vhd_footer* footer = NULL;
287 DWORD size;
288 size_t i;
289 uint32_t checksum, old_checksum;
290 uint64_t wim_magic = 0;
291 LARGE_INTEGER ptr = { 0 };
292 uint8_t is_bootable_img = 0;
293
294 uprintf("Disk image analysis:");
295 handle = CreateFileU(path, GENERIC_READ, FILE_SHARE_READ, NULL,
296 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
297 if (handle == INVALID_HANDLE_VALUE) {
298 uprintf(" Could not open image '%s'", path);
299 goto out;
300 }
301
302 is_bootable_img = IsCompressedBootableImage(path) ? 1 : 0;
303 if (img_report.compression_type == BLED_COMPRESSION_NONE)
304 is_bootable_img = AnalyzeMBR(handle, " Image", FALSE) ? 1 : (ignore_boot_marker ? 2 : 0);
305
306 if (!GetFileSizeEx(handle, &liImageSize)) {
307 uprintf(" Could not get image size: %s", WindowsErrorString());
308 goto out;
309 }
310 img_report.image_size = (uint64_t)liImageSize.QuadPart;
311 size = sizeof(wim_magic);
312 IGNORE_RETVAL(SetFilePointerEx(handle, ptr, NULL, FILE_BEGIN));
313 img_report.is_windows_img = ReadFile(handle, &wim_magic, size, &size, NULL) && (wim_magic == WIM_MAGIC);
314 if (img_report.is_windows_img)
315 goto out;
316
317 size = sizeof(vhd_footer);
318 if ((img_report.compression_type == BLED_COMPRESSION_NONE) && (img_report.image_size >= (512 + size))) {
319 footer = (vhd_footer*)malloc(size);
320 ptr.QuadPart = img_report.image_size - size;
321 if ( (footer == NULL) || (!SetFilePointerEx(handle, ptr, NULL, FILE_BEGIN)) ||
322 (!ReadFile(handle, footer, size, &size, NULL)) || (size != sizeof(vhd_footer)) ) {
323 uprintf(" Could not read VHD footer");
324 goto out;
325 }
326 if (memcmp(footer->cookie, conectix_str, sizeof(footer->cookie)) == 0) {
327 img_report.image_size -= sizeof(vhd_footer);
328 if ( (bswap_uint32(footer->file_format_version) != VHD_FOOTER_FILE_FORMAT_V1_0)
329 || (bswap_uint32(footer->disk_type) != VHD_FOOTER_TYPE_FIXED_HARD_DISK)) {
330 uprintf(" Unsupported type of VHD image");
331 is_bootable_img = 0;
332 goto out;
333 }
334 // Might as well validate the checksum while we're at it
335 old_checksum = bswap_uint32(footer->checksum);
336 footer->checksum = 0;
337 for (checksum=0, i=0; i<sizeof(vhd_footer); i++)
338 checksum += ((uint8_t*)footer)[i];
339 checksum = ~checksum;
340 if (checksum != old_checksum)
341 uprintf(" Warning: VHD footer seems corrupted (checksum: %04X, expected: %04X)", old_checksum, checksum);
342 // Need to remove the footer from our payload
343 uprintf(" Image is a Fixed Hard Disk VHD file");
344 img_report.is_vhd = TRUE;
345 }
346 }
347
348 out:
349 safe_free(footer);
350 safe_closehandle(handle);
351 return is_bootable_img;
352 }
353
354 // Find out if we have any way to extract/apply WIM files on this platform
355 // Returns a bitfield of the methods we can use (1 = Extract using wimgapi, 2 = Extract using 7-Zip, 4 = Apply using wimgapi)
356 uint8_t WimExtractCheck(BOOL bSilent)
357 {
358 PF_INIT(WIMCreateFile, Wimgapi);
359 PF_INIT(WIMSetTemporaryPath, Wimgapi);
360 PF_INIT(WIMLoadImage, Wimgapi);
361 PF_INIT(WIMApplyImage, Wimgapi);
362 PF_INIT(WIMExtractImagePath, Wimgapi);
363 PF_INIT(WIMGetImageInformation, Wimgapi);
364 PF_INIT(WIMRegisterMessageCallback, Wimgapi);
365 PF_INIT(WIMUnregisterMessageCallback, Wimgapi);
366 PF_INIT(WIMCloseHandle, Wimgapi);
367
368 if (pfWIMCreateFile && pfWIMSetTemporaryPath && pfWIMLoadImage && pfWIMExtractImagePath && pfWIMCloseHandle)
369 wim_flags |= WIM_HAS_API_EXTRACT;
370 if (Get7ZipPath())
371 wim_flags |= WIM_HAS_7Z_EXTRACT;
372 if ((wim_flags & WIM_HAS_API_EXTRACT) && pfWIMApplyImage && pfWIMRegisterMessageCallback && pfWIMUnregisterMessageCallback)
373 wim_flags |= WIM_HAS_API_APPLY;
374
375 suprintf("WIM extraction method(s) supported: %s%s%s", (wim_flags & WIM_HAS_7Z_EXTRACT)?"7-Zip":
376 ((wim_flags & WIM_HAS_API_EXTRACT)?"":"NONE"),
377 (WIM_HAS_EXTRACT(wim_flags) == (WIM_HAS_API_EXTRACT|WIM_HAS_7Z_EXTRACT))?", ":
378 "", (wim_flags & WIM_HAS_API_EXTRACT)?"wimgapi.dll":"");
379 suprintf("WIM apply method supported: %s", (wim_flags & WIM_HAS_API_APPLY)?"wimgapi.dll":"NONE");
380 return wim_flags;
381 }
382
383 // Looks like Microsoft's idea of "mount" for WIM images is to just *extract* all
384 // files to a mounpoint and pretend it is "mounted", even if you do specify that
385 // you're not planning to change the content. So, yeah, this is both super slow
386 // and super wasteful of space... These calls are a complete waste of time.
387 BOOL WimMountImage(char* pszWimFileName, DWORD dwImageIndex)
388 {
389 BOOL r = FALSE;
390 wconvert(temp_dir);
391 wconvert(pszWimFileName);
392 PF_INIT_OR_OUT(WIMMountImage, Wimgapi);
393
394 if (wmount_path[0] != 0) {
395 uprintf("WimMountImage: An image is already mounted");
396 goto out;
397 }
398 if (GetTempFileNameW(wtemp_dir, L"Rufus", 0, wmount_path) == 0) {
399 uprintf("WimMountImage: Can not create mount directory");
400 goto out;
401 }
402 DeleteFileW(wmount_path);
403 if (!CreateDirectoryW(wmount_path, 0)) {
404 uprintf("WimMountImage: Can not create mount directory");
405 goto out;
406 }
407
408 r = pfWIMMountImage(wmount_path, wpszWimFileName, dwImageIndex, NULL);
409 if (!r)
410 uprintf("Could not mount %S on %S: %s", wpszWimFileName, wmount_path, WindowsErrorString());
411
412 out:
413 wfree(temp_dir);
414 wfree(pszWimFileName);
415 return r;
416 }
417
418 BOOL WimUnmountImage(void)
419 {
420 BOOL r = FALSE;
421 PF_INIT_OR_OUT(WIMUnmountImage, Wimgapi);
422 if (wmount_path[0] == 0) {
423 uprintf("WimUnmountImage: No image is mounted");
424 goto out;
425 }
426 r = pfWIMUnmountImage(wmount_path, NULL, 0, FALSE);
427 if (!r)
428 uprintf("Could not unmount %S: %s", wmount_path, WindowsErrorString());
429 wmount_path[0] = 0;
430 out:
431 return r;
432 }
433
434 // Extract a file from a WIM image using wimgapi.dll (Windows 7 or later)
435 // NB: if you want progress from a WIM callback, you must run the WIM API call in its own thread
436 // (which we don't do here) as it won't work otherwise. Thanks go to Erwan for figuring this out!
437 BOOL WimExtractFile_API(const char* image, int index, const char* src, const char* dst, BOOL bSilent)
438 {
439 static char* index_name = "[1].xml";
440 BOOL r = FALSE;
441 DWORD dw = 0;
442 HANDLE hWim = NULL;
443 HANDLE hImage = NULL;
444 HANDLE hFile = NULL;
445 wchar_t wtemp[MAX_PATH] = {0};
446 wchar_t* wimage = utf8_to_wchar(image);
447 wchar_t* wsrc = utf8_to_wchar(src);
448 wchar_t* wdst = utf8_to_wchar(dst);
449 char* wim_info;
450
451 PF_INIT_OR_OUT(WIMCreateFile, Wimgapi);
452 PF_INIT_OR_OUT(WIMSetTemporaryPath, Wimgapi);
453 PF_INIT_OR_OUT(WIMLoadImage, Wimgapi);
454 PF_INIT_OR_OUT(WIMExtractImagePath, Wimgapi);
455 PF_INIT_OR_OUT(WIMCloseHandle, Wimgapi);
456
457 suprintf("Opening: %s:[%d] (API)", image, index);
458 if (GetTempPathW(ARRAYSIZE(wtemp), wtemp) == 0) {
459 uprintf(" Could not fetch temp path: %s", WindowsErrorString());
460 goto out;
461 }
462
463 // Thanks to dism++ for figuring out that you can use UNDOCUMENTED FLAG 0x20000000
464 // to open newer install.wim/install.esd images, without running into obnoxious error:
465 // [0x0000000B] An attempt was made to load a program with an incorrect format.
466 // No thanks to Microsoft for NOT DOCUMENTING THEIR UTTER BULLSHIT with the WIM API!
467 hWim = pfWIMCreateFile(wimage, WIM_GENERIC_READ, WIM_OPEN_EXISTING,
468 (img_report.wininst_version >= SPECIAL_WIM_VERSION) ? WIM_UNDOCUMENTED_BULLSHIT : 0, 0, NULL);
469 if (hWim == NULL) {
470 uprintf(" Could not access image: %s", WindowsErrorString());
471 goto out;
472 }
473
474 if (!pfWIMSetTemporaryPath(hWim, wtemp)) {
475 uprintf(" Could not set temp path: %s", WindowsErrorString());
476 goto out;
477 }
478
479 suprintf("Extracting: %s (From %s)", dst, src);
480 if (safe_strcmp(src, index_name) == 0) {
481 if (!pfWIMGetImageInformation(hWim, &wim_info, &dw)) {
482 uprintf(" Could not access WIM info: %s", WindowsErrorString());
483 goto out;
484 }
485 hFile = CreateFileW(wdst, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
486 NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
487 if ((hFile == INVALID_HANDLE_VALUE) || (!WriteFile(hFile, wim_info, dw, &dw, NULL))) {
488 suprintf(" Could not extract file: %s", WindowsErrorString());
489 goto out;
490 }
491 } else {
492 hImage = pfWIMLoadImage(hWim, (DWORD)index);
493 if (hImage == NULL) {
494 uprintf(" Could not set index: %s", WindowsErrorString());
495 goto out;
496 }
497 if (!pfWIMExtractImagePath(hImage, wsrc, wdst, 0)) {
498 suprintf(" Could not extract file: %s", WindowsErrorString());
499 goto out;
500 }
501 }
502 r = TRUE;
503
504 out:
505 if ((hImage != NULL) || (hWim != NULL)) {
506 suprintf("Closing: %s", image);
507 if (hImage != NULL) pfWIMCloseHandle(hImage);
508 if (hWim != NULL) pfWIMCloseHandle(hWim);
509 }
510 safe_closehandle(hFile);
511 safe_free(wimage);
512 safe_free(wsrc);
513 safe_free(wdst);
514 return r;
515 }
516
517 // Extract a file from a WIM image using 7-Zip
518 BOOL WimExtractFile_7z(const char* image, int index, const char* src, const char* dst, BOOL bSilent)
519 {
520 int n;
521 size_t i;
522 char cmdline[MAX_PATH];
523 char tmpdst[MAX_PATH];
524 char index_prefix[] = "#\\";
525
526 suprintf("Opening: %s:[%d] (7-Zip)", image, index);
527
528 if ((image == NULL) || (src == NULL) || (dst == NULL))
529 return FALSE;
530
531 // If you shove more than 9 images in a WIM, don't come complaining
532 // that this breaks!
533 index_prefix[0] = '0' + index;
534
535 suprintf("Extracting: %s (From %s)", dst, src);
536
537 // 7z has a quirk where the image index MUST be specified if a
538 // WIM has multiple indexes, but it MUST be removed if there is
539 // only one image. Because of this (and because 7z will not
540 // return an error code if it can't extract the file), we need
541 // to issue 2 passes. See github issue #680.
542 for (n = 0; n < 2; n++) {
543 static_strcpy(tmpdst, dst);
544 for (i = strlen(tmpdst) - 1; (i > 0) && (tmpdst[i] != '\\') && (tmpdst[i] != '/'); i--);
545 tmpdst[i] = 0;
546
547 static_sprintf(cmdline, "\"%s\" -y e \"%s\" %s%s", sevenzip_path,
548 image, (n == 0) ? index_prefix : "", src);
549 if (RunCommand(cmdline, tmpdst, FALSE) != 0) {
550 uprintf(" Could not launch 7z.exe: %s", WindowsErrorString());
551 return FALSE;
552 }
553
554 for (i = safe_strlen(src); (i > 0) && (src[i] != '\\') && (src[i] != '/'); i--);
555 if (i == 0)
556 static_strcat(tmpdst, "\\");
557 static_strcat(tmpdst, &src[i]);
558 if (_access(tmpdst, 0) == 0)
559 // File was extracted => move on
560 break;
561 }
562
563 if (n >= 2) {
564 suprintf(" 7z.exe did not extract %s", tmpdst);
565 return FALSE;
566 }
567
568 // coverity[toctou]
569 if (!MoveFileExU(tmpdst, dst, MOVEFILE_REPLACE_EXISTING)) {
570 uprintf(" Could not rename %s to %s: %s", tmpdst, dst, WindowsErrorString());
571 return FALSE;
572 }
573
574 return TRUE;
575 }
576
577 // Extract a file from a WIM image
578 BOOL WimExtractFile(const char* image, int index, const char* src, const char* dst, BOOL bSilent)
579 {
580 if ((wim_flags == 0) && (!WIM_HAS_EXTRACT(WimExtractCheck(TRUE))))
581 return FALSE;
582 if ((image == NULL) || (src == NULL) || (dst == NULL))
583 return FALSE;
584
585 // Prefer 7-Zip as, unsurprisingly, it's faster than the Microsoft way,
586 // but allow fallback if 7-Zip doesn't succeed
587 return ( ((wim_flags & WIM_HAS_7Z_EXTRACT) && WimExtractFile_7z(image, index, src, dst, bSilent))
588 || ((wim_flags & WIM_HAS_API_EXTRACT) && WimExtractFile_API(image, index, src, dst, bSilent)) );
589 }
590
591 // Apply image functionality
592 static const char *_image, *_dst;
593 static int _index;
594
595 // From http://msdn.microsoft.com/en-us/library/windows/desktop/dd834960.aspx
596 // as well as http://www.msfn.org/board/topic/150700-wimgapi-wimmountimage-progressbar/
597 enum WIMMessage {
598 WIM_MSG = WM_APP + 0x1476,
599 WIM_MSG_TEXT,
600 WIM_MSG_PROGRESS, // Indicates an update in the progress of an image application.
601 WIM_MSG_PROCESS, // Enables the caller to prevent a file or a directory from being captured or applied.
602 WIM_MSG_SCANNING, // Indicates that volume information is being gathered during an image capture.
603 WIM_MSG_SETRANGE, // Indicates the number of files that will be captured or applied.
604 WIM_MSG_SETPOS, // Indicates the number of files that have been captured or applied.
605 WIM_MSG_STEPIT, // Indicates that a file has been either captured or applied.
606 WIM_MSG_COMPRESS, // Enables the caller to prevent a file resource from being compressed during a capture.
607 WIM_MSG_ERROR, // Alerts the caller that an error has occurred while capturing or applying an image.
608 WIM_MSG_ALIGNMENT, // Enables the caller to align a file resource on a particular alignment boundary.
609 WIM_MSG_RETRY, // Sent when the file is being reapplied because of a network timeout.
610 WIM_MSG_SPLIT, // Enables the caller to align a file resource on a particular alignment boundary.
611 WIM_MSG_FILEINFO, // Used in conjunction with WimApplyImages()'s WIM_FLAG_FILEINFO flag to provide detailed file info.
612 WIM_MSG_INFO, // Sent when an info message is available.
613 WIM_MSG_WARNING, // Sent when a warning message is available.
614 WIM_MSG_CHK_PROCESS,
615 WIM_MSG_SUCCESS = 0x00000000,
616 WIM_MSG_ABORT_IMAGE = -1
617 };
618
619 #define INVALID_CALLBACK_VALUE 0xFFFFFFFF
620
621 #define WIM_FLAG_RESERVED 0x00000001
622 #define WIM_FLAG_VERIFY 0x00000002
623 #define WIM_FLAG_INDEX 0x00000004
624 #define WIM_FLAG_NO_APPLY 0x00000008
625 #define WIM_FLAG_NO_DIRACL 0x00000010
626 #define WIM_FLAG_NO_FILEACL 0x00000020
627 #define WIM_FLAG_SHARE_WRITE 0x00000040
628 #define WIM_FLAG_FILEINFO 0x00000080
629 #define WIM_FLAG_NO_RP_FIX 0x00000100
630
631 // Progress callback
632 DWORD WINAPI WimProgressCallback(DWORD dwMsgId, WPARAM wParam, LPARAM lParam, PVOID pvIgnored)
633 {
634 PBOOL pbCancel = NULL;
635 PWIN32_FIND_DATA pFileData;
636 const char* level = NULL;
637 uint64_t size;
638
639 switch (dwMsgId) {
640 case WIM_MSG_PROGRESS:
641 // The default WIM progress is useless (freezes at 95%, which is usually when only half
642 // the files have been processed), so we don't use it
643 #if 0
644 PrintInfo(0, MSG_267, (DWORD)wParam);
645 UpdateProgress(OP_FILE_COPY, 0.98f*(DWORD)wParam);
646 #endif
647 break;
648 case WIM_MSG_PROCESS:
649 // The amount of files processed is overwhelming (16k+ for a typical image),
650 // and trying to display it *WILL* slow us down, so we don't.
651 #if 0
652 uprintf("%S", (PWSTR)wParam);
653 PrintStatus(0, MSG_000, str); // MSG_000 is "%s"
654 #endif
655 if (count_files) {
656 wim_nb_files++;
657 } else {
658 // At the end of an actual apply, the WIM API re-lists a bunch of directories it already processed,
659 // so, even as we try to compensate, we might end up with more entries than counted - ignore those.
660 if (wim_proc_files < wim_nb_files)
661 wim_proc_files++;
662 else
663 wim_extra_files++;
664 UpdateProgressWithInfo(OP_FILE_COPY, MSG_267, wim_proc_files, wim_nb_files);
665 }
666 // Halt on error
667 if (IS_ERROR(FormatStatus)) {
668 pbCancel = (PBOOL)lParam;
669 *pbCancel = TRUE;
670 break;
671 }
672 break;
673 case WIM_MSG_FILEINFO:
674 pFileData = (PWIN32_FIND_DATA)lParam;
675 if (pFileData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
676 uprintf("Creating: %S", (PWSTR)wParam);
677 } else {
678 size = (((uint64_t)pFileData->nFileSizeHigh) << 32) + pFileData->nFileSizeLow;
679 uprintf("Extracting: %S (%s)", (PWSTR)wParam, SizeToHumanReadable(size, FALSE, FALSE));
680 }
681 break;
682 case WIM_MSG_RETRY:
683 level = "retry";
684 // fall through
685 case WIM_MSG_INFO:
686 if (level == NULL) level = "info";
687 // fall through
688 case WIM_MSG_WARNING:
689 if (level == NULL) level = "warning";
690 // fall through
691 case WIM_MSG_ERROR:
692 if (level == NULL) level = "error";
693 SetLastError((DWORD)lParam);
694 uprintf("Apply %s: %S [err = %d]\n", level, (PWSTR)wParam, WindowsErrorString());
695 break;
696 }
697
698 return IS_ERROR(FormatStatus)?WIM_MSG_ABORT_IMAGE:WIM_MSG_SUCCESS;
699 }
700
701 // Apply a WIM image using wimgapi.dll (Windows 7 or later)
702 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd851944.aspx
703 // To get progress, we must run this call within its own thread
704 static DWORD WINAPI WimApplyImageThread(LPVOID param)
705 {
706 BOOL r = FALSE;
707 HANDLE hWim = NULL;
708 HANDLE hImage = NULL;
709 wchar_t wtemp[MAX_PATH] = {0};
710 wchar_t* wimage = utf8_to_wchar(_image);
711 wchar_t* wdst = utf8_to_wchar(_dst);
712
713 PF_INIT_OR_OUT(WIMRegisterMessageCallback, Wimgapi);
714 PF_INIT_OR_OUT(WIMCreateFile, Wimgapi);
715 PF_INIT_OR_OUT(WIMSetTemporaryPath, Wimgapi);
716 PF_INIT_OR_OUT(WIMLoadImage, Wimgapi);
717 PF_INIT_OR_OUT(WIMApplyImage, Wimgapi);
718 PF_INIT_OR_OUT(WIMCloseHandle, Wimgapi);
719 PF_INIT_OR_OUT(WIMUnregisterMessageCallback, Wimgapi);
720
721 uprintf("Opening: %s:[%d]", _image, _index);
722
723 if (pfWIMRegisterMessageCallback(NULL, (FARPROC)WimProgressCallback, NULL) == INVALID_CALLBACK_VALUE) {
724 uprintf(" Could not set progress callback: %s", WindowsErrorString());
725 goto out;
726 }
727
728 if (GetTempPathW(ARRAYSIZE(wtemp), wtemp) == 0) {
729 uprintf(" Could not fetch temp path: %s", WindowsErrorString());
730 goto out;
731 }
732
733 hWim = pfWIMCreateFile(wimage, WIM_GENERIC_READ, WIM_OPEN_EXISTING,
734 (img_report.wininst_version >= SPECIAL_WIM_VERSION) ? WIM_UNDOCUMENTED_BULLSHIT : 0, 0, NULL);
735 if (hWim == NULL) {
736 uprintf(" Could not access image: %s", WindowsErrorString());
737 goto out;
738 }
739
740 if (!pfWIMSetTemporaryPath(hWim, wtemp)) {
741 uprintf(" Could not set temp path: %s", WindowsErrorString());
742 goto out;
743 }
744
745 hImage = pfWIMLoadImage(hWim, (DWORD)_index);
746 if (hImage == NULL) {
747 uprintf(" Could not set index: %s", WindowsErrorString());
748 goto out;
749 }
750
751 uprintf("Applying Windows image...");
752 UpdateProgressWithInfoInit(NULL, TRUE);
753 // Run a first pass using WIM_FLAG_NO_APPLY to count the files
754 wim_nb_files = 0;
755 wim_proc_files = 0;
756 wim_extra_files = 0;
757 count_files = TRUE;
758 if (!pfWIMApplyImage(hImage, wdst, WIM_FLAG_NO_APPLY)) {
759 uprintf(" Could not count the files to apply: %s", WindowsErrorString());
760 goto out;
761 }
762 // The latest Windows 10 ISOs have a ~17.5% discrepancy between the number of
763 // files and directories actually applied vs. the ones counted when not applying.
764 // Therefore, we add a 'safe' 20% to our counted files to compensate for yet
765 // another dismal Microsoft progress reporting API...
766 wim_nb_files += wim_nb_files / 5;
767 count_files = FALSE;
768 // Actual apply
769 if (!pfWIMApplyImage(hImage, wdst, WIM_FLAG_FILEINFO)) {
770 uprintf(" Could not apply image: %s", WindowsErrorString());
771 goto out;
772 }
773 // Ensure that we'll pick if need to readjust our 20% above from user reports
774 if (wim_extra_files > 0)
775 uprintf("Notice: An extra %d files and directories were applied, from the %d expected",
776 wim_extra_files, wim_nb_files);
777 // Re-use extra files as the final progress step
778 wim_extra_files = (wim_nb_files - wim_proc_files) / 3;
779 UpdateProgressWithInfo(OP_FILE_COPY, MSG_267, wim_proc_files + wim_extra_files, wim_nb_files);
780 r = TRUE;
781
782 out:
783 if ((hImage != NULL) || (hWim != NULL)) {
784 uprintf("Closing: %s", _image);
785 if (hImage != NULL) pfWIMCloseHandle(hImage);
786 if (hWim != NULL) pfWIMCloseHandle(hWim);
787 }
788 if (pfWIMUnregisterMessageCallback != NULL)
789 pfWIMUnregisterMessageCallback(NULL, (FARPROC)WimProgressCallback);
790 safe_free(wimage);
791 safe_free(wdst);
792 ExitThread((DWORD)r);
793 }
794
795 BOOL WimApplyImage(const char* image, int index, const char* dst)
796 {
797 DWORD dw = 0;
798 _image = image;
799 _index = index;
800 _dst = dst;
801
802 apply_wim_thread = CreateThread(NULL, 0, WimApplyImageThread, NULL, 0, NULL);
803 if (apply_wim_thread == NULL) {
804 uprintf("Unable to start apply-image thread");
805 return FALSE;
806 }
807 SetThreadPriority(apply_wim_thread, default_thread_priority);
808 WaitForSingleObject(apply_wim_thread, INFINITE);
809 if (!GetExitCodeThread(apply_wim_thread, &dw))
810 dw = 0;
811 apply_wim_thread = NULL;
812 return dw;
813 }