Directly link with ntdll on Windows
[wimlib] / src / win32_common.c
1 /*
2  * win32_common.c - Windows code common to applying and capturing images.
3  */
4
5 /*
6  * Copyright (C) 2013-2016 Eric Biggers
7  *
8  * This file is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU Lesser General Public License as published by the Free
10  * Software Foundation; either version 3 of the License, or (at your option) any
11  * later version.
12  *
13  * This file is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this file; if not, see http://www.gnu.org/licenses/.
20  */
21
22 #ifdef __WIN32__
23
24 #ifdef HAVE_CONFIG_H
25 #  include "config.h"
26 #endif
27
28 #include "wimlib/win32_common.h"
29
30 #include "wimlib/assert.h"
31 #include "wimlib/error.h"
32 #include "wimlib/util.h"
33 #include "wimlib/win32_vss.h"
34
35 static bool
36 win32_modify_privilege(const wchar_t *privilege, bool enable)
37 {
38         HANDLE hToken;
39         LUID luid;
40         TOKEN_PRIVILEGES newState;
41         bool ret = FALSE;
42
43         if (!OpenProcessToken(GetCurrentProcess(),
44                               TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
45                               &hToken))
46                 goto out;
47
48         if (!LookupPrivilegeValue(NULL, privilege, &luid))
49                 goto out_close_handle;
50
51         newState.PrivilegeCount = 1;
52         newState.Privileges[0].Luid = luid;
53         newState.Privileges[0].Attributes = (enable ? SE_PRIVILEGE_ENABLED : 0);
54         SetLastError(ERROR_SUCCESS);
55         ret = AdjustTokenPrivileges(hToken, FALSE, &newState, 0, NULL, NULL);
56         if (ret && GetLastError() == ERROR_NOT_ALL_ASSIGNED)
57                 ret = FALSE;
58 out_close_handle:
59         CloseHandle(hToken);
60 out:
61         return ret;
62 }
63
64 static bool
65 win32_modify_capture_privileges(bool enable)
66 {
67         bool ok = true;
68         ok &= win32_modify_privilege(SE_BACKUP_NAME, enable);
69         ok &= win32_modify_privilege(SE_SECURITY_NAME, enable);
70         return ok;
71 }
72
73 static bool
74 win32_modify_apply_privileges(bool enable)
75 {
76         bool ok = true;
77         ok &= win32_modify_privilege(SE_RESTORE_NAME, enable);
78         ok &= win32_modify_privilege(SE_SECURITY_NAME, enable);
79         ok &= win32_modify_privilege(SE_TAKE_OWNERSHIP_NAME, enable);
80         ok &= win32_modify_privilege(SE_MANAGE_VOLUME_NAME, enable);
81         return ok;
82 }
83
84 static void
85 win32_release_capture_and_apply_privileges(void)
86 {
87         win32_modify_capture_privileges(false);
88         win32_modify_apply_privileges(false);
89 }
90
91 /* Pointers to dynamically loaded functions  */
92
93 /* ntdll.dll  */
94
95 NTSTATUS (WINAPI *func_RtlDosPathNameToNtPathName_U_WithStatus)
96                 (IN PCWSTR DosName,
97                  OUT PUNICODE_STRING NtName,
98                  OUT PCWSTR *PartName,
99                  OUT PRTL_RELATIVE_NAME_U RelativeName);
100
101 NTSTATUS (WINAPI *func_RtlCreateSystemVolumeInformationFolder)
102                 (PCUNICODE_STRING VolumeRootPath);
103
104 static bool acquired_privileges = false;
105
106 struct dll_sym {
107         void **func_ptr;
108         const char *name;
109         bool required;
110 };
111
112 #define DLL_SYM(name, required) { (void **)&func_##name, #name, required }
113
114 #define for_each_sym(sym, spec) \
115         for ((sym) = (spec)->syms; (sym)->name; (sym)++)
116
117 struct dll_spec {
118         const wchar_t *name;
119         HMODULE handle;
120         const struct dll_sym syms[];
121 };
122
123 struct dll_spec ntdll_spec = {
124         .name = L"ntdll.dll",
125         .syms = {
126                 DLL_SYM(RtlCreateSystemVolumeInformationFolder, false),
127                 DLL_SYM(RtlDosPathNameToNtPathName_U_WithStatus, false), /* Not present on XP  */
128                 {NULL, NULL},
129         },
130 };
131
132 static int
133 init_dll(struct dll_spec *spec)
134 {
135         const struct dll_sym *sym;
136         void *addr;
137
138         if (!spec->handle)
139                 spec->handle = LoadLibrary(spec->name);
140         if (!spec->handle) {
141                 for_each_sym(sym, spec) {
142                         if (sym->required) {
143                                 ERROR("%ls could not be loaded!", spec->name);
144                                 return WIMLIB_ERR_UNSUPPORTED;
145                         }
146                 }
147                 return 0;
148         }
149         for_each_sym(sym, spec) {
150                 addr = (void *)GetProcAddress(spec->handle, sym->name);
151                 if (addr) {
152                         *(sym->func_ptr) = addr;
153                 } else if (sym->required) {
154                         ERROR("Can't find %s in %ls", sym->name, spec->name);
155                         return WIMLIB_ERR_UNSUPPORTED;
156                 }
157         }
158         return 0;
159 }
160
161 static void
162 cleanup_dll(struct dll_spec *spec)
163 {
164         const struct dll_sym *sym;
165
166         if (spec->handle) {
167                 FreeLibrary(spec->handle);
168                 spec->handle = NULL;
169
170                 for_each_sym(sym, spec)
171                         *(sym->func_ptr) = NULL;
172         }
173 }
174
175 /* One-time initialization for Windows capture/apply code.  */
176 int
177 win32_global_init(int init_flags)
178 {
179         int ret;
180
181         /* Try to acquire useful privileges.  */
182         if (!(init_flags & WIMLIB_INIT_FLAG_DONT_ACQUIRE_PRIVILEGES)) {
183                 ret = WIMLIB_ERR_INSUFFICIENT_PRIVILEGES;
184                 if (!win32_modify_capture_privileges(true))
185                         if (init_flags & WIMLIB_INIT_FLAG_STRICT_CAPTURE_PRIVILEGES)
186                                 goto out_drop_privs;
187                 if (!win32_modify_apply_privileges(true))
188                         if (init_flags & WIMLIB_INIT_FLAG_STRICT_APPLY_PRIVILEGES)
189                                 goto out_drop_privs;
190                 acquired_privileges = true;
191         }
192
193         ret = init_dll(&ntdll_spec);
194         if (ret)
195                 goto out_drop_privs;
196
197         return 0;
198
199 out_drop_privs:
200         win32_release_capture_and_apply_privileges();
201         return ret;
202 }
203
204 void
205 win32_global_cleanup(void)
206 {
207         vss_global_cleanup();
208
209         if (acquired_privileges)
210                 win32_release_capture_and_apply_privileges();
211
212         cleanup_dll(&ntdll_spec);
213 }
214
215 /*
216  * Translates a Win32-namespace path into an NT-namespace path.
217  *
218  * On success, returns 0.  The NT-namespace path will be stored in the
219  * UNICODE_STRING structure pointed to by nt_path.  nt_path->Buffer will be set
220  * to a new buffer that must later be freed with HeapFree().  (Really
221  * RtlHeapFree(), but HeapFree() seems to be the same thing.)
222  *
223  * On failure, returns WIMLIB_ERR_NOMEM or WIMLIB_ERR_INVALID_PARAM.
224  */
225 int
226 win32_path_to_nt_path(const wchar_t *win32_path, UNICODE_STRING *nt_path)
227 {
228         NTSTATUS status;
229
230         if (func_RtlDosPathNameToNtPathName_U_WithStatus) {
231                 status = (*func_RtlDosPathNameToNtPathName_U_WithStatus)(win32_path,
232                                                                          nt_path,
233                                                                          NULL, NULL);
234         } else {
235                 if (RtlDosPathNameToNtPathName_U(win32_path, nt_path, NULL, NULL))
236                         status = STATUS_SUCCESS;
237                 else
238                         status = STATUS_NO_MEMORY;
239         }
240
241         if (likely(NT_SUCCESS(status)))
242                 return 0;
243
244         if (status == STATUS_NO_MEMORY)
245                 return WIMLIB_ERR_NOMEM;
246
247         winnt_error(status, L"\"%ls\": invalid path name", win32_path);
248         return WIMLIB_ERR_INVALID_PARAM;
249 }
250
251 int
252 win32_get_drive_path(const wchar_t *file_path, wchar_t drive_path[7])
253 {
254         tchar *file_abspath;
255
256         file_abspath = realpath(file_path, NULL);
257         if (!file_abspath)
258                 return WIMLIB_ERR_NOMEM;
259
260         if (file_abspath[0] == L'\0' || file_abspath[1] != L':') {
261                 ERROR("\"%ls\": Path format not recognized", file_abspath);
262                 FREE(file_abspath);
263                 return WIMLIB_ERR_UNSUPPORTED;
264         }
265
266         wsprintf(drive_path, L"\\\\.\\%lc:", file_abspath[0]);
267         FREE(file_abspath);
268         return 0;
269 }
270
271 /* Try to attach an instance of the Windows Overlay File System Filter Driver to
272  * the specified drive (such as C:)  */
273 bool
274 win32_try_to_attach_wof(const wchar_t *drive)
275 {
276         HMODULE fltlib;
277         bool retval = false;
278
279         /* Use FilterAttach() from Fltlib.dll.  */
280
281         fltlib = LoadLibrary(L"Fltlib.dll");
282
283         if (!fltlib) {
284                 WARNING("Failed to load Fltlib.dll");
285                 return retval;
286         }
287
288         HRESULT (WINAPI *func_FilterAttach)(LPCWSTR lpFilterName,
289                                             LPCWSTR lpVolumeName,
290                                             LPCWSTR lpInstanceName,
291                                             DWORD dwCreatedInstanceNameLength,
292                                             LPWSTR lpCreatedInstanceName);
293
294         func_FilterAttach = (void *)GetProcAddress(fltlib, "FilterAttach");
295
296         if (func_FilterAttach) {
297                 HRESULT res;
298
299                 res = (*func_FilterAttach)(L"wof", drive, NULL, 0, NULL);
300
301                 if (res != S_OK)
302                         res = (*func_FilterAttach)(L"wofadk", drive, NULL, 0, NULL);
303
304                 if (res == S_OK)
305                         retval = true;
306         } else {
307                 WARNING("FilterAttach() does not exist in Fltlib.dll");
308         }
309
310         FreeLibrary(fltlib);
311
312         return retval;
313 }
314
315
316 static void
317 windows_msg(u32 code, const wchar_t *format, va_list va,
318             bool is_ntstatus, bool is_error)
319 {
320         wchar_t _buf[STACK_MAX / 8];
321         wchar_t *buf = _buf;
322         size_t buflen = ARRAY_LEN(_buf);
323         size_t ret;
324         size_t n;
325
326 retry:
327         n = vsnwprintf(buf, buflen, format, va);
328
329         if (n >= buflen)
330                 goto realloc;
331
332         n += snwprintf(&buf[n], buflen - n,
333                        (is_ntstatus ?
334                         L" (status=%08"PRIx32"): " :
335                         L" (err=%"PRIu32"): "),
336                        code);
337
338         if (n >= buflen)
339                 goto realloc;
340
341         ret = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
342                                 FORMAT_MESSAGE_IGNORE_INSERTS |
343                                 (is_ntstatus ? FORMAT_MESSAGE_FROM_HMODULE : 0),
344                             (is_ntstatus ? ntdll_spec.handle : NULL),
345                             code,
346                             MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
347                             &buf[n],
348                             buflen - n,
349                             NULL);
350         n += ret;
351
352         if (n >= buflen || (ret == 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER))
353                 goto realloc;
354
355         if (buf[n - 1] == L'\n')
356                 buf[--n] = L'\0';
357         if (buf[n - 1] == L'\r')
358                 buf[--n] = L'\0';
359         if (buf[n - 1] == L'.')
360                 buf[--n] = L'\0';
361
362         if (is_error)
363                 ERROR("%ls", buf);
364         else
365                 WARNING("%ls", buf);
366         if (buf != _buf)
367                 FREE(buf);
368         return;
369
370 realloc:
371         if (buf != _buf)
372                 FREE(buf);
373         buflen *= 2;
374         buf = MALLOC(buflen * sizeof(buf[0]));
375         if (buf)
376                 goto retry;
377         ERROR("Ran out of memory while building error message!!!");
378 }
379
380 void
381 win32_warning(DWORD err, const wchar_t *format, ...)
382 {
383         va_list va;
384
385         va_start(va, format);
386         windows_msg(err, format, va, false, false);
387         va_end(va);
388 }
389
390 void
391 win32_error(DWORD err, const wchar_t *format, ...)
392 {
393         va_list va;
394
395         va_start(va, format);
396         windows_msg(err, format, va, false, true);
397         va_end(va);
398 }
399
400 void
401 winnt_warning(NTSTATUS status, const wchar_t *format, ...)
402 {
403         va_list va;
404
405         va_start(va, format);
406         windows_msg(status, format, va, true, false);
407         va_end(va);
408 }
409
410 void
411 winnt_error(NTSTATUS status, const wchar_t *format, ...)
412 {
413         va_list va;
414
415         va_start(va, format);
416         windows_msg(status, format, va, true, true);
417         va_end(va);
418 }
419
420 /*
421  * Synchronously execute a filesystem control method.  This is a wrapper around
422  * NtFsControlFile() that handles STATUS_PENDING.  Note that SYNCHRONIZE
423  * permission is, in general, required on the handle.
424  */
425 NTSTATUS
426 winnt_fsctl(HANDLE h, u32 code, const void *in, u32 in_size,
427             void *out, u32 out_size_avail, u32 *actual_out_size_ret)
428 {
429         IO_STATUS_BLOCK iosb;
430         NTSTATUS status;
431
432         status = NtFsControlFile(h, NULL, NULL, NULL, &iosb, code,
433                                  (void *)in, in_size, out, out_size_avail);
434         if (status == STATUS_PENDING) {
435                 /* Beware: this case is often encountered with remote
436                  * filesystems, but rarely with local filesystems.  */
437
438                 status = NtWaitForSingleObject(h, FALSE, NULL);
439                 if (NT_SUCCESS(status)) {
440                         status = iosb.Status;
441                 } else {
442                         /* We shouldn't be issuing ioctls on a handle to which
443                          * we don't have SYNCHRONIZE access.  Otherwise we have
444                          * no way to wait for them to complete.  */
445                         wimlib_assert(status != STATUS_ACCESS_DENIED);
446                 }
447         }
448
449         if (NT_SUCCESS(status) && actual_out_size_ret != NULL)
450                 *actual_out_size_ret = (u32)iosb.Information;
451
452         return status;
453 }
454
455 #endif /* __WIN32__ */