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