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