9270d41587d74c49b2af2c234bb4e32c4cea700b
[wimlib] / src / wimboot.c
1 /*
2  * wimboot.c
3  *
4  * Support for creating WIMBoot pointer files.
5  *
6  * See http://technet.microsoft.com/en-us/library/dn594399.aspx for general
7  * information about WIMBoot.
8  *
9  * Note that WIMBoot pointer files are actually implemented on top of the
10  * Windows Overlay File System Filter (WOF).  See wof.h for more info.
11  */
12
13 /*
14  * Copyright (C) 2014 Eric Biggers
15  *
16  * This file is part of wimlib, a library for working with WIM files.
17  *
18  * wimlib is free software; you can redistribute it and/or modify it under the
19  * terms of the GNU General Public License as published by the Free
20  * Software Foundation; either version 3 of the License, or (at your option)
21  * any later version.
22  *
23  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
24  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
25  * A PARTICULAR PURPOSE. See the GNU General Public License for more
26  * details.
27  *
28  * You should have received a copy of the GNU General Public License
29  * along with wimlib; if not, see http://www.gnu.org/licenses/.
30  */
31
32
33 #ifdef HAVE_CONFIG_H
34 #  include "config.h"
35 #endif
36
37 #ifdef __WIN32__
38
39 #include "wimlib/win32_common.h"
40 #include "wimlib/win32.h"
41 #include "wimlib/assert.h"
42 #include "wimlib/error.h"
43 #include "wimlib/util.h"
44 #include "wimlib/wimboot.h"
45 #include "wimlib/wof.h"
46
47 static int
48 win32_get_drive_path(const wchar_t *file_path, wchar_t drive_path[7])
49 {
50         tchar *file_abspath;
51
52         file_abspath = realpath(file_path, NULL);
53         if (!file_abspath)
54                 return WIMLIB_ERR_NOMEM;
55
56         if (file_abspath[0] == L'\0' || file_abspath[1] != L':') {
57                 ERROR("\"%ls\": Path format not recognized", file_abspath);
58                 FREE(file_abspath);
59                 return WIMLIB_ERR_UNSUPPORTED;
60         }
61
62         wsprintf(drive_path, L"\\\\.\\%lc:", file_abspath[0]);
63         FREE(file_abspath);
64         return 0;
65 }
66
67 /* Try to attach an instance of the Windows Overlay File System Filter Driver to
68  * the specified drive (such as C:)  */
69 static bool
70 try_to_attach_wof(const wchar_t *drive)
71 {
72         HMODULE fltlib;
73         bool retval = false;
74
75         /* Use FilterAttach() from Fltlib.dll.  */
76
77         fltlib = LoadLibrary(L"Fltlib.dll");
78
79         if (!fltlib) {
80                 WARNING("Failed to load Fltlib.dll");
81                 return retval;
82         }
83
84         HRESULT (WINAPI *func_FilterAttach)(LPCWSTR lpFilterName,
85                                             LPCWSTR lpVolumeName,
86                                             LPCWSTR lpInstanceName,
87                                             DWORD dwCreatedInstanceNameLength,
88                                             LPWSTR lpCreatedInstanceName);
89
90         func_FilterAttach = (void *)GetProcAddress(fltlib, "FilterAttach");
91
92         if (func_FilterAttach) {
93                 HRESULT res;
94
95                 res = (*func_FilterAttach)(L"WoF", drive, NULL, 0, NULL);
96
97                 if (res == S_OK)
98                         retval = true;
99         } else {
100                 WARNING("FilterAttach() does not exist in Fltlib.dll");
101         }
102
103         FreeLibrary(fltlib);
104
105         return retval;
106 }
107
108 /*
109  * Allocate a WOF data source ID for a WIM file.
110  *
111  * @wim_path
112  *      Absolute path to the WIM file.  This must include a drive letter and use
113  *      backslash path separators.
114  * @image
115  *      Index of the image in the WIM being applied.
116  * @target
117  *      Path to the target drive.
118  * @data_source_id_ret
119  *      On success, an identifier for the backing WIM file will be returned
120  *      here.
121  *
122  * Returns 0 on success, or a positive error code on failure.
123  */
124 int
125 wimboot_alloc_data_source_id(const wchar_t *wim_path, int image,
126                              const wchar_t *target, u64 *data_source_id_ret)
127 {
128         tchar drive_path[7];
129         size_t wim_path_nchars;
130         size_t wim_file_name_length;
131         void *in;
132         size_t insize;
133         struct wof_external_info *wof_info;
134         struct wim_provider_add_overlay_input *wim_info;
135         HANDLE h;
136         u64 data_source_id;
137         DWORD bytes_returned;
138         int ret;
139         const wchar_t *prefix = L"\\??\\";
140         const size_t prefix_nchars = 4;
141         bool tried_to_attach_wof = false;
142
143         ret = win32_get_drive_path(target, drive_path);
144         if (ret)
145                 return ret;
146
147         wimlib_assert(!wcschr(wim_path, L'/'));
148         wimlib_assert(wim_path[0] != L'\0' && wim_path[1] == L':');
149
150         wim_path_nchars = wcslen(wim_path);
151         wim_file_name_length = sizeof(wchar_t) *
152                                (wim_path_nchars + prefix_nchars);
153
154         insize = sizeof(struct wof_external_info) +
155                  sizeof(struct wim_provider_add_overlay_input) +
156                  wim_file_name_length;
157
158         in = MALLOC(insize);
159         if (!in) {
160                 ret = WIMLIB_ERR_NOMEM;
161                 goto out;
162         }
163
164         wof_info = (struct wof_external_info *)in;
165         wof_info->version = WOF_CURRENT_VERSION;
166         wof_info->provider = WOF_PROVIDER_WIM;
167
168         wim_info = (struct wim_provider_add_overlay_input *)(wof_info + 1);
169         wim_info->wim_type = WIM_BOOT_NOT_OS_WIM;
170         wim_info->wim_index = image;
171         wim_info->wim_file_name_offset = offsetof(struct wim_provider_add_overlay_input,
172                                                   wim_file_name);
173         wim_info->wim_file_name_length = wim_file_name_length;
174         wmemcpy(&wim_info->wim_file_name[0], prefix, prefix_nchars);
175         wmemcpy(&wim_info->wim_file_name[prefix_nchars], wim_path, wim_path_nchars);
176
177 retry_ioctl:
178         h = CreateFile(drive_path, GENERIC_WRITE,
179                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
180                        NULL, OPEN_EXISTING, 0, NULL);
181
182         if (h == INVALID_HANDLE_VALUE) {
183                 set_errno_from_GetLastError();
184                 ERROR_WITH_ERRNO("Failed to open \"%ls\"", drive_path + 4);
185                 ret = WIMLIB_ERR_OPEN;
186                 goto out_free_in;
187         }
188
189         if (!DeviceIoControl(h, FSCTL_ADD_OVERLAY,
190                              in, insize,
191                              &data_source_id, sizeof(data_source_id),
192                              &bytes_returned, NULL))
193         {
194                 DWORD err = GetLastError();
195                 if (err == ERROR_INVALID_FUNCTION) {
196                         if (!tried_to_attach_wof) {
197                                 CloseHandle(h);
198                                 h = INVALID_HANDLE_VALUE;
199                                 tried_to_attach_wof = true;
200                                 if (try_to_attach_wof(drive_path + 4))
201                                         goto retry_ioctl;
202                         }
203                         ERROR("The version of Windows you are running does not appear to support\n"
204                               "        the Windows Overlay File System Filter Driver.  Therefore, wimlib\n"
205                               "        cannot apply WIMBoot information.  Please run from Windows 8.1\n"
206                               "        Update 1 or later.");
207                         ret = WIMLIB_ERR_UNSUPPORTED;
208                         goto out_close_handle;
209                 } else {
210                         set_errno_from_win32_error(err);
211                         ERROR_WITH_ERRNO("Failed to add overlay source \"%ls\" "
212                                          "to volume \"%ls\" (err=0x%08"PRIx32")",
213                                          wim_path, drive_path + 4, (uint32_t)err);
214                         ret = WIMLIB_ERR_WIMBOOT;
215                         goto out_close_handle;
216                 }
217         }
218
219         if (bytes_returned != sizeof(data_source_id)) {
220                 set_errno_from_win32_error(ERROR_INVALID_DATA);
221                 ret = WIMLIB_ERR_WIMBOOT;
222                 ERROR("Unexpected result size when adding "
223                       "overlay source \"%ls\" to volume \"%ls\"",
224                       wim_path, drive_path + 4);
225                 goto out_close_handle;
226         }
227
228         *data_source_id_ret = data_source_id;
229         ret = 0;
230
231 out_close_handle:
232         CloseHandle(h);
233 out_free_in:
234         FREE(in);
235 out:
236         return ret;
237 }
238
239
240 /*
241  * Set WIMBoot information on the specified file.
242  *
243  * @path
244  *      Path to extracted file (already created).
245  * @data_source_id
246  *      Identifier for backing WIM file.
247  * @hash
248  *      SHA-1 message digest of the file's unnamed data stream.
249  *
250  * Returns 0 on success, or a positive error code on failure.
251  */
252 int
253 wimboot_set_pointer(const wchar_t *path, u64 data_source_id,
254                     const u8 hash[20])
255 {
256         struct {
257                 struct wof_external_info wof_info;
258                 struct wim_provider_external_info wim_info;
259         } in;
260         HANDLE h;
261         DWORD bytes_returned;
262         int ret;
263
264         in.wof_info.version = WOF_CURRENT_VERSION;
265         in.wof_info.provider = WOF_PROVIDER_WIM;
266
267         in.wim_info.version = WIM_PROVIDER_CURRENT_VERSION;
268         in.wim_info.flags = 0;
269         in.wim_info.data_source_id = data_source_id;
270         memcpy(in.wim_info.resource_hash, hash, 20);
271
272         h = win32_open_existing_file(path, GENERIC_WRITE);
273         if (h == INVALID_HANDLE_VALUE) {
274                 set_errno_from_GetLastError();
275                 ret = WIMLIB_ERR_OPEN;
276                 goto out;
277         }
278
279         if (!DeviceIoControl(h, FSCTL_SET_EXTERNAL_BACKING,
280                              &in, sizeof(in), NULL, 0, &bytes_returned, NULL))
281         {
282                 DWORD err = GetLastError();
283                 set_errno_from_win32_error(err);
284                 ERROR_WITH_ERRNO("\"%ls\": Couldn't set WIMBoot pointer data "
285                                  "(err=0x%08"PRIx32")", path, (uint32_t)err);
286                 ret = WIMLIB_ERR_WIMBOOT;
287                 goto out_close_handle;
288         }
289         ret = 0;
290 out_close_handle:
291         CloseHandle(h);
292 out:
293         return ret;
294 }
295
296 #endif /* __WIN32__ */