]> wimlib.net Git - wimlib/blob - src/wimboot.c
cff73e8fb33000e68f6b63cd007c40aad8e79fb1
[wimlib] / src / wimboot.c
1 /*
2  * wimboot.c
3  *
4  * Support for creating WIMBoot pointer files.
5  *
6  * See http://technet.microsoft.com/en-us/library/dn594399.aspx for general
7  * information about WIMBoot.
8  *
9  * Note that WIMBoot pointer files are actually implemented on top of the
10  * Windows Overlay File System Filter (WOF).  See wof.h for more info.
11  */
12
13 /*
14  * Copyright (C) 2014 Eric Biggers
15  *
16  * This file is free software; you can redistribute it and/or modify it under
17  * the terms of the GNU Lesser General Public License as published by the Free
18  * Software Foundation; either version 3 of the License, or (at your option) any
19  * later version.
20  *
21  * This file is distributed in the hope that it will be useful, but WITHOUT
22  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
23  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
24  * details.
25  *
26  * You should have received a copy of the GNU Lesser General Public License
27  * along with this file; if not, see http://www.gnu.org/licenses/.
28  */
29
30 #ifdef __WIN32__
31
32 #ifdef HAVE_CONFIG_H
33 #  include "config.h"
34 #endif
35
36 #include "wimlib/win32_common.h"
37 #include "wimlib/win32.h"
38 #include "wimlib/assert.h"
39 #include "wimlib/error.h"
40 #include "wimlib/lookup_table.h"
41 #include "wimlib/util.h"
42 #include "wimlib/wimboot.h"
43 #include "wimlib/wof.h"
44
45 static HANDLE
46 open_file(const wchar_t *device_name, DWORD desiredAccess)
47 {
48         return CreateFile(device_name, desiredAccess,
49                           FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING,
50                           FILE_FLAG_BACKUP_SEMANTICS, NULL);
51 }
52
53 static BOOL
54 query_device(HANDLE h, DWORD code, void *out, DWORD out_size)
55 {
56         DWORD bytes_returned;
57         return DeviceIoControl(h, code, NULL, 0, out, out_size,
58                                &bytes_returned, NULL);
59 }
60
61 /*
62  * Gets partition and drive information for the specified path.
63  *
64  * @path
65  *      Absolute path which must begin with a drive letter.  For example, if the
66  *      path is D:\install.wim, this function will query information about the
67  *      D: volume.
68  * @part_info_ret
69  *      Partition info is returned here.
70  * @drive_info_ret
71  *      Drive info is returned here.  The contained partition info will not be
72  *      valid.
73  *
74  * Returns 0 on success, or a positive error code on failure.
75  */
76 static int
77 query_partition_and_disk_info(const wchar_t *path,
78                               PARTITION_INFORMATION_EX *part_info_ret,
79                               DRIVE_LAYOUT_INFORMATION_EX *drive_info_ret)
80 {
81         HANDLE h;
82         wchar_t vol_name[] = L"\\\\.\\X:";
83         wchar_t disk_name[] = L"\\\\?\\PhysicalDriveXXXXXXXXXX";
84
85         PARTITION_INFORMATION_EX part_info;
86         size_t extents_size = sizeof(VOLUME_DISK_EXTENTS) + 4 * sizeof(DISK_EXTENT);
87         VOLUME_DISK_EXTENTS *extents = alloca(extents_size);
88         size_t drive_info_size = sizeof(DRIVE_LAYOUT_INFORMATION_EX) +
89                                         8 * sizeof(PARTITION_INFORMATION_EX);
90         DRIVE_LAYOUT_INFORMATION_EX *drive_info = alloca(drive_info_size);
91
92         wimlib_assert(path[0] != L'\0' && path[1] == L':');
93
94         *(wcschr(vol_name, L'X')) = path[0];
95
96         h = open_file(vol_name, GENERIC_READ);
97         if (h == INVALID_HANDLE_VALUE) {
98                 set_errno_from_GetLastError();
99                 ERROR_WITH_ERRNO("\"%ls\": Can't open volume device", vol_name);
100                 return WIMLIB_ERR_OPEN;
101         }
102
103         if (!query_device(h, IOCTL_DISK_GET_PARTITION_INFO_EX,
104                           &part_info, sizeof(part_info)))
105         {
106                 ERROR("\"%ls\": Can't get partition info (err=0x%08"PRIx32")",
107                       vol_name, (u32)GetLastError());
108                 CloseHandle(h);
109                 return WIMLIB_ERR_READ;
110         }
111
112         if (!query_device(h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
113                           extents, extents_size))
114         {
115                 ERROR("\"%ls\": Can't get volume extent info (err=0x%08"PRIx32")",
116                       vol_name, (u32)GetLastError());
117                 CloseHandle(h);
118                 return WIMLIB_ERR_READ;
119         }
120
121         CloseHandle(h);
122
123         if (extents->NumberOfDiskExtents != 1) {
124                 ERROR("\"%ls\": This volume has %"PRIu32" disk extents, "
125                       "but this code is untested for more than 1",
126                       vol_name, (u32)extents->NumberOfDiskExtents);
127                 return WIMLIB_ERR_UNSUPPORTED;
128         }
129
130         wsprintf(wcschr(disk_name, L'X'), L"%"PRIu32,
131                  extents->Extents[0].DiskNumber);
132
133         h = open_file(disk_name, GENERIC_READ);
134         if (h == INVALID_HANDLE_VALUE) {
135                 set_errno_from_GetLastError();
136                 ERROR_WITH_ERRNO("\"%ls\": Can't open disk device", disk_name);
137                 return WIMLIB_ERR_OPEN;
138         }
139
140         if (!query_device(h, IOCTL_DISK_GET_DRIVE_LAYOUT_EX,
141                           drive_info, drive_info_size))
142         {
143                 ERROR("\"%ls\": Can't get disk info (err=0x%08"PRIx32")",
144                       disk_name, (u32)GetLastError());
145                 CloseHandle(h);
146                 return WIMLIB_ERR_READ;
147         }
148
149         CloseHandle(h);
150
151         if (drive_info->PartitionStyle != part_info.PartitionStyle) {
152                 ERROR("\"%ls\", \"%ls\": Inconsistent partition table type!",
153                       vol_name, disk_name);
154                 return WIMLIB_ERR_UNSUPPORTED;
155         }
156
157         if (part_info.PartitionStyle == PARTITION_STYLE_GPT) {
158                 BUILD_BUG_ON(sizeof(part_info.Gpt.PartitionId) !=
159                              sizeof(drive_info->Gpt.DiskId));
160                 if (!memcmp(&part_info.Gpt.PartitionId,
161                             &drive_info->Gpt.DiskId,
162                             sizeof(drive_info->Gpt.DiskId)))
163                 {
164                         ERROR("\"%ls\", \"%ls\": Partition GUID is the "
165                               "same as the disk GUID???", vol_name, disk_name);
166                         return WIMLIB_ERR_UNSUPPORTED;
167                 }
168         }
169
170         if (part_info.PartitionStyle != PARTITION_STYLE_MBR &&
171             part_info.PartitionStyle != PARTITION_STYLE_GPT)
172         {
173                 ERROR("\"%ls\": Unknown partition style 0x%08"PRIx32,
174                       vol_name, (u32)part_info.PartitionStyle);
175                 return WIMLIB_ERR_UNSUPPORTED;
176         }
177
178         *part_info_ret = part_info;
179         *drive_info_ret = *drive_info;
180         return 0;
181 }
182
183 /*
184  * Allocate a new WIM data source ID.
185  *
186  * @old_hdr
187  *      Previous WimOverlay.dat contents, or NULL if file did not exist.
188  *
189  * Returns the new data source ID.
190  */
191 static u64
192 alloc_new_data_source_id(const struct WimOverlay_dat_header *old_hdr)
193 {
194         if (!old_hdr)
195                 return 0;
196
197         for (u64 id = 0; ; id++) {
198                 for (u32 i = 0; i < old_hdr->num_entries_1; i++)
199                         if (id == old_hdr->entry_1s[i].data_source_id)
200                                 goto next;
201                 return id;
202         next:
203                 ;
204         }
205 }
206
207 /*
208  * Calculate the size of WimOverlay.dat with one entry added.
209  *
210  * @old_hdr
211  *      Previous WimOverlay.dat contents, or NULL if file did not exist.
212  * @new_entry_2_size
213  *      Size of entry_2 being added.
214  * @size_ret
215  *      Size will be returned here.
216  *
217  * Returns 0 on success, or WIMLIB_ERR_UNSUPPORTED if size overflows 32 bits.
218  */
219 static int
220 calculate_wimoverlay_dat_size(const struct WimOverlay_dat_header *old_hdr,
221                               u32 new_entry_2_size, u32 *size_ret)
222 {
223         u64 size_64;
224         u32 size;
225
226         size_64 = sizeof(struct WimOverlay_dat_header);
227         if (old_hdr) {
228                 for (u32 i = 0; i < old_hdr->num_entries_1; i++) {
229                         size_64 += sizeof(struct WimOverlay_dat_entry_1);
230                         size_64 += old_hdr->entry_1s[i].entry_2_length;
231                 }
232         }
233         size_64 += sizeof(struct WimOverlay_dat_entry_1);
234         size_64 += new_entry_2_size;
235
236         size = size_64;
237         if (size_64 != size)
238                 return WIMLIB_ERR_UNSUPPORTED;
239
240         *size_ret = size;
241         return 0;
242 }
243
244 /*
245  * Writes @size bytes of @contents to the named file @path.
246  *
247  * Returns 0 on success; WIMLIB_ERR_OPEN or WIMLIB_ERR_WRITE on failure.
248  */
249 static int
250 write_wimoverlay_dat(const wchar_t *path, const void *contents, u32 size)
251 {
252         HANDLE h;
253         DWORD bytes_written;
254
255         h = CreateFile(path, GENERIC_WRITE, 0, NULL,
256                        CREATE_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL);
257         if (h == INVALID_HANDLE_VALUE) {
258                 set_errno_from_GetLastError();
259                 ERROR_WITH_ERRNO("\"%ls\": Can't open file for writing", path);
260                 return WIMLIB_ERR_OPEN;
261         }
262
263         if (!WriteFile(h, contents, size, &bytes_written, NULL) ||
264             bytes_written != size)
265         {
266                 set_errno_from_GetLastError();
267                 ERROR_WITH_ERRNO("\"%ls\": Can't write file", path);
268                 CloseHandle(h);
269                 return WIMLIB_ERR_WRITE;
270         }
271
272         if (!CloseHandle(h)) {
273                 set_errno_from_GetLastError();
274                 ERROR_WITH_ERRNO("\"%ls\": Can't close handle", path);
275                 return WIMLIB_ERR_WRITE;
276         }
277
278         return 0;
279 }
280
281 /*
282  * Generates the contents of WimOverlay.dat in memory, with one entry added.
283  *
284  * @buf
285  *      Buffer large enough to hold the new contents.
286  * @old_hdr
287  *      Old contents of WimOverlay.dat, or NULL if it did not exist.
288  * @wim_path
289  *      Absolute path to the WIM file.  It must begin with a drive letter; for
290  *      example, D:\install.wim.
291  * @wim_guid
292  *      GUID of the WIM, from the WIM header.
293  * @image
294  *      Number of the image in the WIM to specify in the new entry.
295  * @new_data_source_id
296  *      Data source ID to use for the new entry.
297  * @part_info
298  *      Partition information for the WIM file.
299  * @disk_info
300  *      Disk information for the WIM file.
301  * @new_entry_2_size
302  *      Size, in bytes, of the new location information structure ('struct
303  *      WimOverlay_dat_entry_2').
304  *
305  * Returns a pointer one past the last byte of @buf filled in.
306  */
307 static u8 *
308 fill_in_wimoverlay_dat(u8 *buf,
309                        const struct WimOverlay_dat_header *old_hdr,
310                        const wchar_t *wim_path,
311                        const u8 wim_guid[WIM_GUID_LEN],
312                        int image,
313                        u64 new_data_source_id,
314                        const PARTITION_INFORMATION_EX *part_info,
315                        const DRIVE_LAYOUT_INFORMATION_EX *disk_info,
316                        u32 new_entry_2_size)
317 {
318         struct WimOverlay_dat_header *new_hdr;
319         struct WimOverlay_dat_entry_1 *new_entry_1;
320         struct WimOverlay_dat_entry_2 *new_entry_2;
321         u32 entry_2_offset;
322         u8 *p = buf;
323
324         new_hdr = (struct WimOverlay_dat_header *)p;
325
326         /* Fill in new header  */
327         new_hdr->magic = WIMOVERLAY_DAT_MAGIC;
328         new_hdr->wim_provider_version = WIM_PROVIDER_CURRENT_VERSION;
329         new_hdr->unknown_0x08 = 0x00000028;
330         new_hdr->num_entries_1 = (old_hdr ? old_hdr->num_entries_1 : 0) + 1;
331         new_hdr->num_entries_2 = (old_hdr ? old_hdr->num_entries_2 : 0) + 1;
332         new_hdr->unknown_0x14 = 0x00000000;
333
334         p += sizeof(struct WimOverlay_dat_header);
335
336         /* Copy WIM-specific information for old entries  */
337         entry_2_offset = sizeof(struct WimOverlay_dat_header) +
338                         (new_hdr->num_entries_1 * sizeof(struct WimOverlay_dat_entry_1));
339         if (old_hdr) {
340                 for (u32 i = 0; i < old_hdr->num_entries_1; i++) {
341                         new_entry_1 = (struct WimOverlay_dat_entry_1 *)p;
342
343                         p = mempcpy(p, &old_hdr->entry_1s[i],
344                                     sizeof(struct WimOverlay_dat_entry_1));
345
346                         new_entry_1->entry_2_offset = entry_2_offset;
347                         entry_2_offset += new_entry_1->entry_2_length;
348                 }
349         }
350
351         /* Generate WIM-specific information for new entry  */
352         new_entry_1 = (struct WimOverlay_dat_entry_1 *)p;
353
354         new_entry_1->data_source_id = new_data_source_id;
355         new_entry_1->entry_2_offset = entry_2_offset;
356         new_entry_1->entry_2_length = new_entry_2_size;
357         new_entry_1->wim_type = WIM_BOOT_NOT_OS_WIM;
358         new_entry_1->wim_index = image;
359         BUILD_BUG_ON(sizeof(new_entry_1->guid) != WIM_GUID_LEN);
360         memcpy(new_entry_1->guid, wim_guid, WIM_GUID_LEN);
361
362         p += sizeof(struct WimOverlay_dat_entry_1);
363
364         /* Copy WIM location information for old entries  */
365         if (old_hdr) {
366                 for (u32 i = 0; i < old_hdr->num_entries_1; i++) {
367                         wimlib_assert(new_hdr->entry_1s[i].entry_2_offset == p - buf);
368                         wimlib_assert(old_hdr->entry_1s[i].entry_2_length ==
369                                       new_hdr->entry_1s[i].entry_2_length);
370                         p = mempcpy(p,
371                                     ((const u8 *)old_hdr + old_hdr->entry_1s[i].entry_2_offset),
372                                     old_hdr->entry_1s[i].entry_2_length);
373                 }
374         }
375
376         /* Generate WIM location information for new entry  */
377         new_entry_2 = (struct WimOverlay_dat_entry_2 *)p;
378
379         new_entry_2->unknown_0x00 = 0x00000000;
380         new_entry_2->unknown_0x04 = 0x00000000;
381         new_entry_2->entry_2_length = new_entry_2_size;
382         new_entry_2->unknown_0x0C = 0x00000000;
383         new_entry_2->unknown_0x10 = 0x00000005;
384         new_entry_2->unknown_0x14 = 0x00000001;
385         new_entry_2->inner_struct_size = new_entry_2_size - 0x14;
386         new_entry_2->unknown_0x1C = 0x00000005;
387         new_entry_2->unknown_0x20 = 0x00000006;
388         new_entry_2->unknown_0x24 = 0x00000000;
389         new_entry_2->unknown_0x28 = 0x00000048;
390         new_entry_2->unknown_0x2C = 0x00000000;
391         new_entry_2->unknown_0x40 = 0x00000000;
392
393         if (part_info->PartitionStyle == PARTITION_STYLE_MBR) {
394                 new_entry_2->partition.mbr.part_start_offset = part_info->StartingOffset.QuadPart;
395                 new_entry_2->partition.mbr.padding = 0;
396                 new_entry_2->partition_table_type = WIMOVERLAY_PARTITION_TYPE_MBR;
397                 new_entry_2->disk.mbr.disk_id = disk_info->Mbr.Signature;
398                 new_entry_2->disk.mbr.padding[0] = 0x00000000;
399                 new_entry_2->disk.mbr.padding[1] = 0x00000000;
400                 new_entry_2->disk.mbr.padding[2] = 0x00000000;
401         } else {
402                 BUILD_BUG_ON(sizeof(new_entry_2->partition.gpt.part_unique_guid) !=
403                              sizeof(part_info->Gpt.PartitionId));
404                 memcpy(new_entry_2->partition.gpt.part_unique_guid,
405                        &part_info->Gpt.PartitionId,
406                        sizeof(part_info->Gpt.PartitionId));
407                 new_entry_2->partition_table_type = WIMOVERLAY_PARTITION_TYPE_GPT;
408
409                 BUILD_BUG_ON(sizeof(new_entry_2->disk.gpt.disk_guid) !=
410                              sizeof(disk_info->Gpt.DiskId));
411                 memcpy(new_entry_2->disk.gpt.disk_guid,
412                        &disk_info->Gpt.DiskId,
413                        sizeof(disk_info->Gpt.DiskId));
414
415                 BUILD_BUG_ON(sizeof(new_entry_2->disk.gpt.disk_guid) !=
416                              sizeof(new_entry_2->partition.gpt.part_unique_guid));
417         }
418         new_entry_2->unknown_0x58[0] = 0x00000000;
419         new_entry_2->unknown_0x58[1] = 0x00000000;
420         new_entry_2->unknown_0x58[2] = 0x00000000;
421         new_entry_2->unknown_0x58[3] = 0x00000000;
422
423         wimlib_assert(wim_path[2] == L'\\');
424         return mempcpy(new_entry_2->wim_file_name,
425                        wim_path + 2,
426                        new_entry_2_size - sizeof(struct WimOverlay_dat_entry_2));
427 }
428
429 /*
430  * Prepares the new contents of WimOverlay.dat in memory, with one entry added.
431  *
432  * @old_hdr
433  *      Old contents of WimOverlay.dat, or NULL if it did not exist.
434  * @wim_path
435  *      Absolute path to the WIM file.  It must begin with a drive letter; for
436  *      example, D:\install.wim.
437  * @wim_guid
438  *      GUID of the WIM, from the WIM header.
439  * @image
440  *      Number of the image in the WIM to specify in the new entry.
441  * @new_contents_ret
442  *      Location into which to return the new contents as a malloc()ed buffer on
443  *      success.
444  * @new_contents_size_ret
445  *      Location into which to return the size, in bytes, of the new contents on
446  *      success.
447  * @new_data_source_id_ret
448  *      Location into which to return the data source ID of the new entry on
449  *      success.
450  *
451  * Returns 0 on success, or a positive error code on failure.
452  */
453 static int
454 prepare_wimoverlay_dat(const struct WimOverlay_dat_header *old_hdr,
455                        const wchar_t *wim_path,
456                        const u8 wim_guid[WIM_GUID_LEN],
457                        int image,
458                        void **new_contents_ret,
459                        u32 *new_contents_size_ret,
460                        u64 *new_data_source_id_ret)
461 {
462         int ret;
463         PARTITION_INFORMATION_EX part_info;
464         DRIVE_LAYOUT_INFORMATION_EX disk_info;
465         u64 new_data_source_id;
466         u32 new_entry_2_size;
467         u32 new_contents_size;
468         u8 *buf;
469         u8 *end;
470
471         ret = query_partition_and_disk_info(wim_path, &part_info, &disk_info);
472         if (ret)
473                 return ret;
474
475         new_data_source_id = alloc_new_data_source_id(old_hdr);
476
477         new_entry_2_size = sizeof(struct WimOverlay_dat_entry_2) +
478                                 ((wcslen(wim_path) - 2 + 1) * sizeof(wchar_t));
479         ret = calculate_wimoverlay_dat_size(old_hdr, new_entry_2_size,
480                                             &new_contents_size);
481         if (ret)
482                 return ret;
483
484         buf = MALLOC(new_contents_size);
485         if (!buf)
486                 return WIMLIB_ERR_NOMEM;
487
488         end = fill_in_wimoverlay_dat(buf, old_hdr, wim_path, wim_guid, image,
489                                      new_data_source_id,
490                                      &part_info, &disk_info, new_entry_2_size);
491
492         wimlib_assert(end - buf == new_contents_size);
493
494         *new_contents_ret = buf;
495         *new_contents_size_ret = new_contents_size;
496         *new_data_source_id_ret = new_data_source_id;
497         return 0;
498 }
499
500 /*
501  * Reads and validates a WimOverlay.dat file.
502  *
503  * @path
504  *      Path to the WimOverlay.dat file, such as
505  *      C:\System Volume Information\WimOverlay.dat
506  * @contents_ret
507  *      Location into which to return the contents as a malloc()ed buffer on
508  *      success.  This can be cast to 'struct WimOverlay_dat_header', and its
509  *      contents are guaranteed to be valid.  Alternatively, if the file does
510  *      not exist, NULL will be returned here.
511  *
512  * Returns 0 on success, or a positive error code on failure.
513  */
514 static int
515 read_wimoverlay_dat(const wchar_t *path, void **contents_ret)
516 {
517         HANDLE h;
518         BY_HANDLE_FILE_INFORMATION info;
519         int ret;
520         void *contents;
521         const struct WimOverlay_dat_header *hdr;
522         DWORD bytes_read;
523         bool already_retried = false;
524
525 retry:
526         h = open_file(path, GENERIC_READ);
527         if (h == INVALID_HANDLE_VALUE) {
528                 DWORD err = GetLastError();
529                 if (err == ERROR_FILE_NOT_FOUND) {
530                         *contents_ret = NULL;
531                         return 0;
532                 }
533                 if (err == ERROR_PATH_NOT_FOUND &&
534                     func_RtlCreateSystemVolumeInformationFolder)
535                 {
536                         wchar_t volume_root_path[] = L"\\??\\X:\\";
537
538                         *(wcschr(volume_root_path, L'X')) = path[0];
539
540                         UNICODE_STRING str = {
541                                 .Length = sizeof(volume_root_path) - sizeof(wchar_t),
542                                 .MaximumLength = sizeof(volume_root_path),
543                                 .Buffer = volume_root_path,
544                         };
545                         NTSTATUS status;
546                         DWORD err2;
547
548                         status = (*func_RtlCreateSystemVolumeInformationFolder)(&str);
549
550                         err2 = (*func_RtlNtStatusToDosError)(status);
551                         if (err2 == ERROR_SUCCESS) {
552                                 if (!already_retried) {
553                                         already_retried = true;
554                                         goto retry;
555                                 }
556                         } else {
557                                 err = err2;
558                         }
559                 }
560                 set_errno_from_win32_error(err);
561                 ERROR_WITH_ERRNO("\"%ls\": Can't open for reading", path);
562                 return WIMLIB_ERR_OPEN;
563         }
564         if (!GetFileInformationByHandle(h, &info)) {
565                 set_errno_from_GetLastError();
566                 ERROR_WITH_ERRNO("\"%ls\": Can't query metadata", path);
567                 CloseHandle(h);
568                 return WIMLIB_ERR_STAT;
569         }
570
571         contents = NULL;
572         if (!info.nFileSizeHigh)
573                 contents = MALLOC(info.nFileSizeLow);
574         if (!contents) {
575                 ERROR("\"%ls\": File is too large to fit into memory", path);
576                 CloseHandle(h);
577                 return WIMLIB_ERR_NOMEM;
578         }
579
580         if (!ReadFile(h, contents, info.nFileSizeLow, &bytes_read, NULL) ||
581             bytes_read != info.nFileSizeLow)
582         {
583                 set_errno_from_GetLastError();
584                 ERROR_WITH_ERRNO("\"%ls\": Can't read data", path);
585                 CloseHandle(h);
586                 ret = WIMLIB_ERR_READ;
587                 goto out_free_contents;
588         }
589
590         CloseHandle(h);
591
592         if (info.nFileSizeLow < sizeof(struct WimOverlay_dat_header)) {
593                 ERROR("\"%ls\": File is unexpectedly small (only %"PRIu32" bytes)",
594                       path, (u32)info.nFileSizeLow);
595                 ret = WIMLIB_ERR_UNSUPPORTED;
596                 goto out_free_contents;
597         }
598
599         hdr = (const struct WimOverlay_dat_header *)contents;
600
601         if (hdr->magic != WIMOVERLAY_DAT_MAGIC ||
602             hdr->wim_provider_version != WIM_PROVIDER_CURRENT_VERSION ||
603             hdr->unknown_0x08 != 0x00000028 ||
604             (hdr->num_entries_1 != hdr->num_entries_2) ||
605             hdr->unknown_0x14 != 0x00000000)
606         {
607                 ERROR("\"%ls\": Header contains unexpected data:", path);
608                 if (wimlib_print_errors) {
609                         print_byte_field((const u8 *)hdr,
610                                          sizeof(struct WimOverlay_dat_header),
611                                          wimlib_error_file);
612                         fputc('\n', wimlib_error_file);
613                 }
614                 ret = WIMLIB_ERR_UNSUPPORTED;
615                 goto out_free_contents;
616         }
617
618         if ((u64)hdr->num_entries_1 * sizeof(struct WimOverlay_dat_entry_1) >
619             info.nFileSizeLow - sizeof(struct WimOverlay_dat_header))
620         {
621                 ERROR("\"%ls\": File is unexpectedly small "
622                       "(only %"PRIu32" bytes, but has %"PRIu32" entries)",
623                       path, (u32)info.nFileSizeLow, hdr->num_entries_1);
624                 ret = WIMLIB_ERR_UNSUPPORTED;
625                 goto out_free_contents;
626         }
627
628         for (u32 i = 0; i < hdr->num_entries_1; i++) {
629                 const struct WimOverlay_dat_entry_1 *entry_1;
630                 const struct WimOverlay_dat_entry_2 *entry_2;
631                 u32 wim_file_name_length;
632
633                 entry_1 = &hdr->entry_1s[i];
634
635                 if (((u64)entry_1->entry_2_offset +
636                      (u64)entry_1->entry_2_length) >
637                     info.nFileSizeLow)
638                 {
639                         ERROR("\"%ls\": entry %"PRIu32" (2) "
640                               "(data source ID 0x%016"PRIx64") "
641                               "overflows file",
642                               path, i, entry_1->data_source_id);
643                         ret = WIMLIB_ERR_UNSUPPORTED;
644                         goto out_free_contents;
645                 }
646                 if (entry_1->entry_2_length < sizeof(struct WimOverlay_dat_entry_2)) {
647                         ERROR("\"%ls\": entry %"PRIu32" (2) "
648                               "(data source ID 0x%016"PRIx64") "
649                               "is too short",
650                               path, i, entry_1->data_source_id);
651                         ret = WIMLIB_ERR_UNSUPPORTED;
652                         goto out_free_contents;
653                 }
654
655                 if (entry_1->entry_2_offset % 2 != 0) {
656                         ERROR("\"%ls\": entry %"PRIu32" (2) "
657                               "(data source ID 0x%016"PRIx64") "
658                               "is misaligned",
659                               path, i, entry_1->data_source_id);
660                         ret = WIMLIB_ERR_UNSUPPORTED;
661                         goto out_free_contents;
662                 }
663
664                 entry_2 = (const struct WimOverlay_dat_entry_2 *)
665                                 ((const u8 *)hdr + entry_1->entry_2_offset);
666
667                 wim_file_name_length = entry_1->entry_2_length -
668                                         sizeof(struct WimOverlay_dat_entry_2);
669                 if ((wim_file_name_length < 2 * sizeof(wchar_t)) ||
670                     (wim_file_name_length % sizeof(wchar_t) != 0) ||
671                     (wmemchr(entry_2->wim_file_name, L'\0',
672                              wim_file_name_length / sizeof(wchar_t))
673                      != &entry_2->wim_file_name[wim_file_name_length /
674                                                 sizeof(wchar_t) - 1]))
675                 {
676                         ERROR("\"%ls\": entry %"PRIu32" (2) "
677                               "(data source ID 0x%016"PRIx64") "
678                               "has invalid WIM file name",
679                               path, i, entry_1->data_source_id);
680                         if (wimlib_print_errors) {
681                                 print_byte_field((const u8 *)entry_2->wim_file_name,
682                                                  wim_file_name_length,
683                                                  wimlib_error_file);
684                                 fputc('\n', wimlib_error_file);
685                         }
686                         ret = WIMLIB_ERR_UNSUPPORTED;
687                         goto out_free_contents;
688                 }
689
690                 if (entry_2->unknown_0x00 != 0x00000000 ||
691                     entry_2->unknown_0x04 != 0x00000000 ||
692                     entry_2->unknown_0x0C != 0x00000000 ||
693                     entry_2->entry_2_length != entry_1->entry_2_length ||
694                     entry_2->unknown_0x10 != 0x00000005 ||
695                     entry_2->unknown_0x14 != 0x00000001 ||
696                     entry_2->inner_struct_size != entry_1->entry_2_length - 0x14 ||
697                     entry_2->unknown_0x1C != 0x00000005 ||
698                     entry_2->unknown_0x20 != 0x00000006 ||
699                     entry_2->unknown_0x24 != 0x00000000 ||
700                     entry_2->unknown_0x28 != 0x00000048 ||
701                     entry_2->unknown_0x2C != 0x00000000 ||
702                     entry_2->unknown_0x40 != 0x00000000 ||
703                     (entry_2->partition_table_type != WIMOVERLAY_PARTITION_TYPE_GPT &&
704                      entry_2->partition_table_type != WIMOVERLAY_PARTITION_TYPE_MBR) ||
705                     (entry_2->partition_table_type == WIMOVERLAY_PARTITION_TYPE_MBR &&
706                      entry_2->partition.mbr.padding != 0) ||
707                     (entry_2->partition_table_type == WIMOVERLAY_PARTITION_TYPE_GPT &&
708                      entry_2->partition.mbr.padding == 0) ||
709                     entry_2->unknown_0x58[0] != 0x00000000 ||
710                     entry_2->unknown_0x58[1] != 0x00000000 ||
711                     entry_2->unknown_0x58[2] != 0x00000000 ||
712                     entry_2->unknown_0x58[3] != 0x00000000)
713                 {
714                         ERROR("\"%ls\": entry %"PRIu32" (2) "
715                               "(data source ID 0x%016"PRIx64") "
716                               "contains unexpected data!",
717                               path, i, entry_1->data_source_id);
718                         if (wimlib_print_errors) {
719                                 print_byte_field((const u8 *)entry_2,
720                                                  entry_1->entry_2_length,
721                                                  wimlib_error_file);
722                                 fputc('\n', wimlib_error_file);
723                         }
724                         ret = WIMLIB_ERR_UNSUPPORTED;
725                         goto out_free_contents;
726                 }
727         }
728
729         *contents_ret = contents;
730         return 0;
731
732 out_free_contents:
733         FREE(contents);
734         return ret;
735 }
736
737 /*
738  * Update WimOverlay.dat manually in order to add a WIM data source to the
739  * target volume.
740  *
741  * THIS CODE IS EXPERIMENTAL AS I HAD TO REVERSE ENGINEER THE FILE FORMAT!
742  *
743  * @path
744  *      Target drive.  Must be a letter followed by a colon (e.g. D:).
745  * @wim_path
746  *      Absolute path to the WIM file.  It must begin with a drive letter; for
747  *      example, D:\install.wim.
748  * @wim_guid
749  *      GUID of the WIM, from the WIM header.
750  * @image
751  *      Number of the image in the WIM to specify in the new entry.
752  * @data_source_id_ret
753  *      On success, the allocated data source ID is returned here.
754  */
755 static int
756 update_wimoverlay_manually(const wchar_t *drive, const wchar_t *wim_path,
757                            const u8 wim_guid[WIM_GUID_LEN],
758                            int image, u64 *data_source_id_ret)
759 {
760         wchar_t path_main[] = L"A:\\System Volume Information\\WimOverlay.dat";
761         wchar_t path_backup[] = L"A:\\System Volume Information\\WimOverlay.backup";
762         wchar_t path_wimlib_backup[] = L"A:\\System Volume Information\\WimOverlay.wimlib_backup";
763         wchar_t path_new[] = L"A:\\System Volume Information\\WimOverlay.wimlib_new";
764         void *old_contents;
765         void *new_contents;
766         u32 new_contents_size;
767         u64 new_data_source_id;
768         int ret;
769
770         wimlib_assert(drive[0] != L'\0' &&
771                       drive[1] == L':' &&
772                       drive[2] == L'\0');
773
774         path_main[0]          = drive[0];
775         path_backup[0]        = drive[0];
776         path_wimlib_backup[0] = drive[0];
777         path_new[0]           = drive[0];
778
779         ret = read_wimoverlay_dat(path_main, &old_contents);
780         if (ret)
781                 goto out;
782
783         ret = prepare_wimoverlay_dat(old_contents, wim_path, wim_guid, image,
784                                      &new_contents, &new_contents_size,
785                                      &new_data_source_id);
786         FREE(old_contents);
787         if (ret)
788                 goto out;
789
790         /* Write WimOverlay.wimlib_new  */
791         ret = write_wimoverlay_dat(path_new,
792                                    new_contents, new_contents_size);
793         if (ret)
794                 goto out_free_new_contents;
795
796         /* Write WimOverlay.backup  */
797         ret = write_wimoverlay_dat(path_backup,
798                                    new_contents, new_contents_size);
799         if (ret)
800                 goto out_free_new_contents;
801
802         if (old_contents) {
803                 /* Rename WimOverlay.dat => WimOverlay.wimlib_backup  */
804                 ret = win32_rename_replacement(path_main, path_wimlib_backup);
805                 if (ret) {
806                         ERROR_WITH_ERRNO("Can't rename \"%ls\" => \"%ls\"",
807                                          path_main, path_wimlib_backup);
808                         ret = WIMLIB_ERR_RENAME;
809                         goto out_free_new_contents;
810                 }
811         }
812
813         /* Rename WimOverlay.wimlib_new => WimOverlay.dat  */
814         ret = win32_rename_replacement(path_new, path_main);
815         if (ret) {
816                 ERROR_WITH_ERRNO("Can't rename \"%ls\" => \"%ls\"",
817                                  path_new, path_main);
818                 ret = WIMLIB_ERR_RENAME;
819         }
820 out_free_new_contents:
821         FREE(new_contents);
822 out:
823         if (ret == WIMLIB_ERR_UNSUPPORTED) {
824                 ERROR("Please report to developer ("PACKAGE_BUGREPORT").\n"
825                       "        If possible send the file \"%ls\".\n\n", path_main);
826         }
827         if (ret == 0)
828                 *data_source_id_ret = new_data_source_id;
829         return ret;
830 }
831
832 /* Try to attach an instance of the Windows Overlay File System Filter Driver to
833  * the specified drive (such as C:)  */
834 static bool
835 try_to_attach_wof(const wchar_t *drive)
836 {
837         HMODULE fltlib;
838         bool retval = false;
839
840         /* Use FilterAttach() from Fltlib.dll.  */
841
842         fltlib = LoadLibrary(L"Fltlib.dll");
843
844         if (!fltlib) {
845                 WARNING("Failed to load Fltlib.dll");
846                 return retval;
847         }
848
849         HRESULT (WINAPI *func_FilterAttach)(LPCWSTR lpFilterName,
850                                             LPCWSTR lpVolumeName,
851                                             LPCWSTR lpInstanceName,
852                                             DWORD dwCreatedInstanceNameLength,
853                                             LPWSTR lpCreatedInstanceName);
854
855         func_FilterAttach = (void *)GetProcAddress(fltlib, "FilterAttach");
856
857         if (func_FilterAttach) {
858                 HRESULT res;
859
860                 res = (*func_FilterAttach)(L"WoF", drive, NULL, 0, NULL);
861
862                 if (res == S_OK)
863                         retval = true;
864         } else {
865                 WARNING("FilterAttach() does not exist in Fltlib.dll");
866         }
867
868         FreeLibrary(fltlib);
869
870         return retval;
871 }
872
873 /*
874  * Allocate a WOF data source ID for a WIM file.
875  *
876  * @wim_path
877  *      Absolute path to the WIM file.  This must include a drive letter and use
878  *      backslash path separators.
879  * @wim_guid
880  *      GUID of the WIM, from the WIM header.
881  * @image
882  *      Number of the image in the WIM being applied.
883  * @target
884  *      Path to the target directory.
885  * @data_source_id_ret
886  *      On success, an identifier for the backing WIM file will be returned
887  *      here.
888  *
889  * Returns 0 on success, or a positive error code on failure.
890  */
891 int
892 wimboot_alloc_data_source_id(const wchar_t *wim_path,
893                              const u8 wim_guid[WIM_GUID_LEN],
894                              int image, const wchar_t *target,
895                              u64 *data_source_id_ret, bool *wof_running_ret)
896 {
897         tchar drive_path[7];
898         size_t wim_path_nchars;
899         size_t wim_file_name_length;
900         void *in;
901         size_t insize;
902         struct wof_external_info *wof_info;
903         struct wim_provider_add_overlay_input *wim_info;
904         HANDLE h;
905         u64 data_source_id;
906         DWORD bytes_returned;
907         int ret;
908         const wchar_t *prefix = L"\\??\\";
909         const size_t prefix_nchars = 4;
910         bool tried_to_attach_wof = false;
911
912         ret = win32_get_drive_path(target, drive_path);
913         if (ret)
914                 return ret;
915
916         wimlib_assert(!wcschr(wim_path, L'/'));
917         wimlib_assert(wim_path[0] != L'\0' && wim_path[1] == L':');
918
919         wim_path_nchars = wcslen(wim_path);
920         wim_file_name_length = sizeof(wchar_t) *
921                                (wim_path_nchars + prefix_nchars);
922
923         insize = sizeof(struct wof_external_info) +
924                  sizeof(struct wim_provider_add_overlay_input) +
925                  wim_file_name_length;
926
927         in = MALLOC(insize);
928         if (!in) {
929                 ret = WIMLIB_ERR_NOMEM;
930                 goto out;
931         }
932
933         wof_info = (struct wof_external_info *)in;
934         wof_info->version = WOF_CURRENT_VERSION;
935         wof_info->provider = WOF_PROVIDER_WIM;
936
937         wim_info = (struct wim_provider_add_overlay_input *)(wof_info + 1);
938         wim_info->wim_type = WIM_BOOT_NOT_OS_WIM;
939         wim_info->wim_index = image;
940         wim_info->wim_file_name_offset = offsetof(struct wim_provider_add_overlay_input,
941                                                   wim_file_name);
942         wim_info->wim_file_name_length = wim_file_name_length;
943         wmemcpy(&wim_info->wim_file_name[0], prefix, prefix_nchars);
944         wmemcpy(&wim_info->wim_file_name[prefix_nchars], wim_path, wim_path_nchars);
945
946 retry_ioctl:
947         h = open_file(drive_path, GENERIC_WRITE);
948
949         if (h == INVALID_HANDLE_VALUE) {
950                 set_errno_from_GetLastError();
951                 ERROR_WITH_ERRNO("Failed to open \"%ls\"", drive_path + 4);
952                 ret = WIMLIB_ERR_OPEN;
953                 goto out_free_in;
954         }
955
956         if (!DeviceIoControl(h, FSCTL_ADD_OVERLAY,
957                              in, insize,
958                              &data_source_id, sizeof(data_source_id),
959                              &bytes_returned, NULL))
960         {
961                 DWORD err = GetLastError();
962                 if (err == ERROR_INVALID_FUNCTION) {
963                         if (!tried_to_attach_wof) {
964                                 CloseHandle(h);
965                                 h = INVALID_HANDLE_VALUE;
966                                 tried_to_attach_wof = true;
967                                 if (try_to_attach_wof(drive_path + 4))
968                                         goto retry_ioctl;
969                         }
970                         ret = WIMLIB_ERR_UNSUPPORTED;
971                         goto out_close_handle;
972                 } else {
973                         set_errno_from_win32_error(err);
974                         ERROR_WITH_ERRNO("Failed to add overlay source \"%ls\" "
975                                          "to volume \"%ls\" (err=0x%08"PRIx32")",
976                                          wim_path, drive_path + 4, (u32)err);
977                         ret = WIMLIB_ERR_WIMBOOT;
978                         goto out_close_handle;
979                 }
980         }
981
982         if (bytes_returned != sizeof(data_source_id)) {
983                 set_errno_from_win32_error(ERROR_INVALID_DATA);
984                 ret = WIMLIB_ERR_WIMBOOT;
985                 ERROR("Unexpected result size when adding "
986                       "overlay source \"%ls\" to volume \"%ls\"",
987                       wim_path, drive_path + 4);
988                 goto out_close_handle;
989         }
990
991         *wof_running_ret = true;
992         *data_source_id_ret = data_source_id;
993         ret = 0;
994
995 out_close_handle:
996         CloseHandle(h);
997 out_free_in:
998         FREE(in);
999 out:
1000         if (ret == WIMLIB_ERR_UNSUPPORTED) {
1001         #if 0
1002                 WARNING(
1003                 "The version of Windows you are running does not appear to support\n"
1004                 "        the Windows Overlay File System Filter Driver.  This is normally\n"
1005                 "        available on Windows 8.1 Update 1 or later.  Therefore, wimlib\n"
1006                 "        will attempt to update the WimOverlay.dat file directly.\n");
1007         #else
1008                 WARNING("WOF driver is not available; updating WimOverlay.dat directly.");
1009         #endif
1010                 ret = update_wimoverlay_manually(drive_path + 4, wim_path,
1011                                                  wim_guid, image,
1012                                                  data_source_id_ret);
1013                 *wof_running_ret = false;
1014         }
1015         return ret;
1016 }
1017
1018
1019 /*
1020  * Set WIMBoot information on the specified file.
1021  *
1022  * This turns it into a reparse point that redirects accesses to it, to the
1023  * corresponding resource in the WIM archive.
1024  *
1025  * @h
1026  *      Open handle to the file, with GENERIC_WRITE access.
1027  * @printable_name
1028  *      Printable representation of the path to the file.
1029  * @lte
1030  *      Unnamed data stream of the file.
1031  * @data_source_id
1032  *      Allocated identifier for the WIM data source on the destination volume.
1033  * @lookup_table_hash
1034  *      SHA-1 message digest of the WIM's lookup table.
1035  * @wof_running
1036  *      %true if the WOF driver appears to be available and working; %false if
1037  *      not.
1038  *
1039  * Returns 0 on success, or a positive error code on failure.
1040  */
1041 int
1042 wimboot_set_pointer(HANDLE h,
1043                     const wchar_t *printable_name,
1044                     const struct wim_lookup_table_entry *lte,
1045                     u64 data_source_id,
1046                     const u8 lookup_table_hash[SHA1_HASH_SIZE],
1047                     bool wof_running)
1048 {
1049         DWORD bytes_returned;
1050         DWORD err;
1051
1052         if (wof_running) {
1053                 /* The WOF driver is running.  We can create the reparse point
1054                  * using FSCTL_SET_EXTERNAL_BACKING.  */
1055
1056                 struct {
1057                         struct wof_external_info wof_info;
1058                         struct wim_provider_external_info wim_info;
1059                 } in;
1060
1061                 in.wof_info.version = WOF_CURRENT_VERSION;
1062                 in.wof_info.provider = WOF_PROVIDER_WIM;
1063
1064                 in.wim_info.version = WIM_PROVIDER_CURRENT_VERSION;
1065                 in.wim_info.flags = 0;
1066                 in.wim_info.data_source_id = data_source_id;
1067                 copy_hash(in.wim_info.resource_hash, lte->hash);
1068
1069                 /* lookup_table_hash is not necessary  */
1070
1071                 if (!DeviceIoControl(h, FSCTL_SET_EXTERNAL_BACKING,
1072                                      &in, sizeof(in), NULL, 0,
1073                                      &bytes_returned, NULL))
1074                         goto fail;
1075         } else {
1076
1077                 /* The WOF driver is running.  We need to create the reparse
1078                  * point manually.  */
1079
1080                 struct {
1081                         struct {
1082                                 le32 rptag;
1083                                 le16 rpdatalen;
1084                                 le16 rpreserved;
1085                         } hdr;
1086                         struct wof_external_info wof_info;
1087                         struct wim_provider_rpdata wim_info;
1088                 } in;
1089
1090                 BUILD_BUG_ON(sizeof(in) != 8 +
1091                              sizeof(struct wof_external_info) +
1092                              sizeof(struct wim_provider_rpdata));
1093
1094                 in.hdr.rptag = WIMLIB_REPARSE_TAG_WOF;
1095                 in.hdr.rpdatalen = sizeof(in) - sizeof(in.hdr);
1096                 in.hdr.rpreserved = 0;
1097
1098                 in.wof_info.version = WOF_CURRENT_VERSION;
1099                 in.wof_info.provider = WOF_PROVIDER_WIM;
1100
1101                 in.wim_info.version = 2;
1102                 in.wim_info.flags = 0;
1103                 in.wim_info.data_source_id = data_source_id;
1104                 copy_hash(in.wim_info.resource_hash, lte->hash);
1105                 copy_hash(in.wim_info.wim_lookup_table_hash, lookup_table_hash);
1106                 in.wim_info.stream_uncompressed_size = lte->size;
1107                 in.wim_info.stream_compressed_size = lte->rspec->size_in_wim;
1108                 in.wim_info.stream_offset_in_wim = lte->rspec->offset_in_wim;
1109
1110                 if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT,
1111                                      &in, sizeof(in), NULL, 0, &bytes_returned, NULL))
1112                         goto fail;
1113
1114                 /* We also need to create an unnamed data stream of the correct
1115                  * size.  Otherwise the file shows up as zero length.  It can be
1116                  * a sparse stream containing all zeroes; its contents
1117                  * are unimportant.  */
1118                 if (!DeviceIoControl(h, FSCTL_SET_SPARSE, NULL, 0, NULL, 0,
1119                                      &bytes_returned, NULL))
1120                         goto fail;
1121
1122                 if (!SetFilePointerEx(h,
1123                                       (LARGE_INTEGER){ .QuadPart = lte->size},
1124                                       NULL, FILE_BEGIN))
1125                         goto fail;
1126
1127                 if (!SetEndOfFile(h))
1128                         goto fail;
1129         }
1130
1131         return 0;
1132
1133 fail:
1134         err = GetLastError();
1135         set_errno_from_win32_error(err);
1136         ERROR_WITH_ERRNO("\"%ls\": Couldn't set WIMBoot pointer data "
1137                          "(err=%"PRIu32")", printable_name, (u32)err);
1138         return WIMLIB_ERR_WIMBOOT;
1139 }
1140
1141 #endif /* __WIN32__ */