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