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