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