]> wimlib.net Git - wimlib/blob - src/win32.c
774222497627560bb8bb08a13023d60ac92552e3
[wimlib] / src / win32.c
1 /*
2  * win32.c
3  *
4  * All the library code specific to native Windows builds is in here.
5  */
6
7 /*
8  * Copyright (C) 2013 Eric Biggers
9  *
10  * This file is part of wimlib, a library for working with WIM files.
11  *
12  * wimlib is free software; you can redistribute it and/or modify it under the
13  * terms of the GNU General Public License as published by the Free
14  * Software Foundation; either version 3 of the License, or (at your option)
15  * any later version.
16  *
17  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
18  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
19  * A PARTICULAR PURPOSE. See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with wimlib; if not, see http://www.gnu.org/licenses/.
24  */
25
26 #ifdef __WIN32__
27
28 #include "config.h"
29 #include <windows.h>
30 #include <ntdef.h>
31 #include <wchar.h>
32 #include <shlwapi.h> /* shlwapi.h for PathMatchSpecW() */
33 #ifdef ERROR /* windows.h defines this */
34 #  undef ERROR
35 #endif
36
37 #include "win32.h"
38 #include "dentry.h"
39 #include "lookup_table.h"
40 #include "security.h"
41 #include "endianness.h"
42 #include <pthread.h>
43
44 #include <errno.h>
45
46 #define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1
47 #define MAX_GET_SACL_PRIV_NOTHELD_WARNINGS 1
48 struct win32_capture_state {
49         unsigned long num_get_sd_access_denied;
50         unsigned long num_get_sacl_priv_notheld;
51 };
52
53 #define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1
54 #define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1
55
56 #ifdef ENABLE_ERROR_MESSAGES
57 static void
58 win32_error(u32 err_code)
59 {
60         wchar_t *buffer;
61         DWORD nchars;
62         nchars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM |
63                                     FORMAT_MESSAGE_ALLOCATE_BUFFER,
64                                 NULL, err_code, 0,
65                                 (wchar_t*)&buffer, 0, NULL);
66         if (nchars == 0) {
67                 ERROR("Error printing error message! "
68                       "Computer will self-destruct in 3 seconds.");
69         } else {
70                 ERROR("Win32 error: %ls", buffer);
71                 LocalFree(buffer);
72         }
73 }
74 #else /* ENABLE_ERROR_MESSAGES */
75 #  define win32_error(err_code)
76 #endif /* !ENABLE_ERROR_MESSAGES */
77
78 /* Pointers to functions that are not available on all targetted versions of
79  * Windows (XP and later).  NOTE: The WINAPI annotations seem to be important; I
80  * assume it specifies a certain calling convention. */
81
82 /* Vista and later */
83 static HANDLE (WINAPI *win32func_FindFirstStreamW)(LPCWSTR lpFileName,
84                                             STREAM_INFO_LEVELS InfoLevel,
85                                             LPVOID lpFindStreamData,
86                                             DWORD dwFlags) = NULL;
87
88 /* Vista and later */
89 static BOOL (WINAPI *win32func_FindNextStreamW)(HANDLE hFindStream,
90                                          LPVOID lpFindStreamData) = NULL;
91
92 static HMODULE hKernel32 = NULL;
93
94 /* Try to dynamically load some functions */
95 void
96 win32_global_init()
97 {
98         DWORD err;
99
100         if (hKernel32 == NULL) {
101                 DEBUG("Loading Kernel32.dll");
102                 hKernel32 = LoadLibraryW(L"Kernel32.dll");
103                 if (hKernel32 == NULL) {
104                         err = GetLastError();
105                         WARNING("Can't load Kernel32.dll");
106                         win32_error(err);
107                         return;
108                 }
109         }
110
111         DEBUG("Looking for FindFirstStreamW");
112         win32func_FindFirstStreamW = (void*)GetProcAddress(hKernel32, "FindFirstStreamW");
113         if (!win32func_FindFirstStreamW) {
114                 WARNING("Could not find function FindFirstStreamW() in Kernel32.dll!");
115                 WARNING("Capturing alternate data streams will not be supported.");
116                 return;
117         }
118
119         DEBUG("Looking for FindNextStreamW");
120         win32func_FindNextStreamW = (void*)GetProcAddress(hKernel32, "FindNextStreamW");
121         if (!win32func_FindNextStreamW) {
122                 WARNING("Could not find function FindNextStreamW() in Kernel32.dll!");
123                 WARNING("Capturing alternate data streams will not be supported.");
124                 win32func_FindFirstStreamW = NULL;
125         }
126 }
127
128 void
129 win32_global_cleanup()
130 {
131         if (hKernel32 != NULL) {
132                 DEBUG("Closing Kernel32.dll");
133                 FreeLibrary(hKernel32);
134                 hKernel32 = NULL;
135         }
136 }
137
138 static const wchar_t *capture_access_denied_msg =
139 L"         If you are not running this program as the administrator, you may\n"
140  "         need to do so, so that all data and metadata can be backed up.\n"
141  "         Otherwise, there may be no way to access the desired data or\n"
142  "         metadata without taking ownership of the file or directory.\n"
143  ;
144
145 static const wchar_t *apply_access_denied_msg =
146 L"If you are not running this program as the administrator, you may\n"
147  "          need to do so, so that all data and metadata can be extracted\n"
148  "          exactly as the origignal copy.  However, if you do not care that\n"
149  "          the security descriptors are extracted correctly, you could run\n"
150  "          `wimlib-imagex apply' with the --no-acls flag instead.\n"
151  ;
152
153 static HANDLE
154 win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess)
155 {
156         return CreateFileW(path,
157                            dwDesiredAccess,
158                            FILE_SHARE_READ,
159                            NULL, /* lpSecurityAttributes */
160                            OPEN_EXISTING,
161                            FILE_FLAG_BACKUP_SEMANTICS |
162                                FILE_FLAG_OPEN_REPARSE_POINT,
163                            NULL /* hTemplateFile */);
164 }
165
166 HANDLE
167 win32_open_file_data_only(const wchar_t *path)
168 {
169         return win32_open_existing_file(path, FILE_READ_DATA);
170 }
171
172 int
173 read_win32_file_prefix(const struct wim_lookup_table_entry *lte,
174                        u64 size,
175                        consume_data_callback_t cb,
176                        void *ctx_or_buf,
177                        int _ignored_flags)
178 {
179         int ret = 0;
180         void *out_buf;
181         DWORD err;
182         u64 bytes_remaining;
183
184         HANDLE hFile = win32_open_file_data_only(lte->file_on_disk);
185         if (hFile == INVALID_HANDLE_VALUE) {
186                 err = GetLastError();
187                 ERROR("Failed to open \"%ls\"", lte->file_on_disk);
188                 win32_error(err);
189                 return WIMLIB_ERR_OPEN;
190         }
191
192         if (cb)
193                 out_buf = alloca(WIM_CHUNK_SIZE);
194         else
195                 out_buf = ctx_or_buf;
196
197         bytes_remaining = size;
198         while (bytes_remaining) {
199                 DWORD bytesToRead, bytesRead;
200
201                 bytesToRead = min(WIM_CHUNK_SIZE, bytes_remaining);
202                 if (!ReadFile(hFile, out_buf, bytesToRead, &bytesRead, NULL) ||
203                     bytesRead != bytesToRead)
204                 {
205                         err = GetLastError();
206                         ERROR("Failed to read data from \"%ls\"", lte->file_on_disk);
207                         win32_error(err);
208                         ret = WIMLIB_ERR_READ;
209                         break;
210                 }
211                 bytes_remaining -= bytesRead;
212                 if (cb) {
213                         ret = (*cb)(out_buf, bytesRead, ctx_or_buf);
214                         if (ret)
215                                 break;
216                 } else {
217                         out_buf += bytesRead;
218                 }
219         }
220         CloseHandle(hFile);
221         return ret;
222 }
223
224 struct win32_encrypted_read_ctx {
225         consume_data_callback_t read_prefix_cb;
226         void *read_prefix_ctx_or_buf;
227         int wimlib_err_code;
228         void *buf;
229         size_t buf_filled;
230         u64 bytes_remaining;
231 };
232
233 static DWORD WINAPI
234 win32_encrypted_export_cb(unsigned char *_data, void *_ctx, unsigned long len)
235 {
236         const void *data = _data;
237         struct win32_encrypted_read_ctx *ctx = _ctx;
238         int ret;
239
240         DEBUG("len = %lu", len);
241         if (ctx->read_prefix_cb) {
242                 /* The length of the buffer passed to the ReadEncryptedFileRaw()
243                  * export callback is undocumented, so we assume it may be of
244                  * arbitrary size. */
245                 size_t bytes_to_buffer = min(ctx->bytes_remaining - ctx->buf_filled,
246                                              len);
247                 while (bytes_to_buffer) {
248                         size_t bytes_to_copy_to_buf =
249                                 min(bytes_to_buffer, WIM_CHUNK_SIZE - ctx->buf_filled);
250
251                         memcpy(ctx->buf + ctx->buf_filled, data,
252                                bytes_to_copy_to_buf);
253                         ctx->buf_filled += bytes_to_copy_to_buf;
254                         data += bytes_to_copy_to_buf;
255                         bytes_to_buffer -= bytes_to_copy_to_buf;
256
257                         if (ctx->buf_filled == WIM_CHUNK_SIZE ||
258                             ctx->buf_filled == ctx->bytes_remaining)
259                         {
260                                 ret = (*ctx->read_prefix_cb)(ctx->buf,
261                                                              ctx->buf_filled,
262                                                              ctx->read_prefix_ctx_or_buf);
263                                 if (ret) {
264                                         ctx->wimlib_err_code = ret;
265                                         /* Shouldn't matter what error code is returned
266                                          * here, as long as it isn't ERROR_SUCCESS. */
267                                         return ERROR_READ_FAULT;
268                                 }
269                                 ctx->bytes_remaining -= ctx->buf_filled;
270                                 ctx->buf_filled = 0;
271                         }
272                 }
273         } else {
274                 size_t len_to_copy = min(len, ctx->bytes_remaining);
275                 memcpy(ctx->read_prefix_ctx_or_buf, data, len_to_copy);
276                 ctx->bytes_remaining -= len_to_copy;
277                 ctx->read_prefix_ctx_or_buf += len_to_copy;
278         }
279         return ERROR_SUCCESS;
280 }
281
282 int
283 read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte,
284                                  u64 size,
285                                  consume_data_callback_t cb,
286                                  void *ctx_or_buf,
287                                  int _ignored_flags)
288 {
289         struct win32_encrypted_read_ctx export_ctx;
290         DWORD err;
291         void *file_ctx;
292         int ret;
293
294         DEBUG("Reading %"PRIu64" bytes from encryted file \"%ls\"",
295               size, lte->file_on_disk);
296
297         export_ctx.read_prefix_cb = cb;
298         export_ctx.read_prefix_ctx_or_buf = ctx_or_buf;
299         export_ctx.wimlib_err_code = 0;
300         if (cb) {
301                 export_ctx.buf = MALLOC(WIM_CHUNK_SIZE);
302                 if (!export_ctx.buf)
303                         return WIMLIB_ERR_NOMEM;
304         } else {
305                 export_ctx.buf = NULL;
306         }
307         export_ctx.bytes_remaining = size;
308
309         err = OpenEncryptedFileRawW(lte->file_on_disk, 0, &file_ctx);
310         if (err != ERROR_SUCCESS) {
311                 ERROR("Failed to open encrypted file \"%ls\" for raw read",
312                       lte->file_on_disk);
313                 win32_error(err);
314                 ret = WIMLIB_ERR_OPEN;
315                 goto out_free_buf;
316         }
317         err = ReadEncryptedFileRaw(win32_encrypted_export_cb,
318                                    &export_ctx, file_ctx);
319         if (err != ERROR_SUCCESS) {
320                 ERROR("Failed to read encrypted file \"%ls\"",
321                       lte->file_on_disk);
322                 win32_error(err);
323                 ret = export_ctx.wimlib_err_code;
324                 if (ret == 0)
325                         ret = WIMLIB_ERR_READ;
326         } else if (export_ctx.bytes_remaining != 0) {
327                 ERROR("Only could read %"PRIu64" of %"PRIu64" bytes from "
328                       "encryted file \"%ls\"",
329                       size - export_ctx.bytes_remaining, size,
330                       lte->file_on_disk);
331                 ret = WIMLIB_ERR_READ;
332         } else {
333                 ret = 0;
334         }
335         CloseEncryptedFileRaw(file_ctx);
336 out_free_buf:
337         FREE(export_ctx.buf);
338         return ret;
339 }
340
341 /* Given a path, which may not yet exist, get a set of flags that describe the
342  * features of the volume the path is on. */
343 static int
344 win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret)
345 {
346         wchar_t *volume;
347         BOOL bret;
348         DWORD vol_flags;
349
350         if (path[0] != L'\0' && path[0] != L'\\' &&
351             path[0] != L'/' && path[1] == L':')
352         {
353                 /* Path starts with a drive letter; use it. */
354                 volume = alloca(4 * sizeof(wchar_t));
355                 volume[0] = path[0];
356                 volume[1] = path[1];
357                 volume[2] = L'\\';
358                 volume[3] = L'\0';
359         } else {
360                 /* Path does not start with a drive letter; use the volume of
361                  * the current working directory. */
362                 volume = NULL;
363         }
364         bret = GetVolumeInformationW(volume, /* lpRootPathName */
365                                      NULL,  /* lpVolumeNameBuffer */
366                                      0,     /* nVolumeNameSize */
367                                      NULL,  /* lpVolumeSerialNumber */
368                                      NULL,  /* lpMaximumComponentLength */
369                                      &vol_flags, /* lpFileSystemFlags */
370                                      NULL,  /* lpFileSystemNameBuffer */
371                                      0);    /* nFileSystemNameSize */
372         if (!bret) {
373                 DWORD err = GetLastError();
374                 WARNING("Failed to get volume information for path \"%ls\"", path);
375                 win32_error(err);
376                 vol_flags = 0xffffffff;
377         }
378
379         DEBUG("using vol_flags = %x", vol_flags);
380         *vol_flags_ret = vol_flags;
381         return 0;
382 }
383
384
385 static u64
386 FILETIME_to_u64(const FILETIME *ft)
387 {
388         return ((u64)ft->dwHighDateTime << 32) | (u64)ft->dwLowDateTime;
389 }
390
391 static int
392 win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path)
393 {
394         WIN32_FIND_DATAW dat;
395         if (FindFirstFileW(path, &dat) && dat.cAlternateFileName[0] != L'\0') {
396                 DEBUG("\"%ls\": short name \"%ls\"", path, dat.cAlternateFileName);
397                 size_t short_name_nbytes = wcslen(dat.cAlternateFileName) *
398                                            sizeof(wchar_t);
399                 size_t n = short_name_nbytes + sizeof(wchar_t);
400                 dentry->short_name = MALLOC(n);
401                 if (!dentry->short_name)
402                         return WIMLIB_ERR_NOMEM;
403                 memcpy(dentry->short_name, dat.cAlternateFileName, n);
404                 dentry->short_name_nbytes = short_name_nbytes;
405         }
406         /* If we can't read the short filename for some reason, we just ignore
407          * the error and assume the file has no short name.  I don't think this
408          * should be an issue, since the short names are essentially obsolete
409          * anyway. */
410         return 0;
411 }
412
413 static int
414 win32_get_security_descriptor(struct wim_dentry *dentry,
415                               struct sd_set *sd_set,
416                               const wchar_t *path,
417                               struct win32_capture_state *state,
418                               int add_image_flags)
419 {
420         SECURITY_INFORMATION requestedInformation;
421         DWORD lenNeeded = 0;
422         BOOL status;
423         DWORD err;
424         unsigned long n;
425
426         requestedInformation = DACL_SECURITY_INFORMATION |
427                                SACL_SECURITY_INFORMATION |
428                                OWNER_SECURITY_INFORMATION |
429                                GROUP_SECURITY_INFORMATION;
430 again:
431         /* Request length of security descriptor */
432         status = GetFileSecurityW(path, requestedInformation,
433                                   NULL, 0, &lenNeeded);
434         err = GetLastError();
435         if (!status && err == ERROR_INSUFFICIENT_BUFFER) {
436                 DWORD len = lenNeeded;
437                 char buf[len];
438                 if (GetFileSecurityW(path, requestedInformation,
439                                      (PSECURITY_DESCRIPTOR)buf, len, &lenNeeded))
440                 {
441                         int security_id = sd_set_add_sd(sd_set, buf, len);
442                         if (security_id < 0)
443                                 return WIMLIB_ERR_NOMEM;
444                         else {
445                                 dentry->d_inode->i_security_id = security_id;
446                                 return 0;
447                         }
448                 } else {
449                         err = GetLastError();
450                 }
451         }
452
453         if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS)
454                 goto fail;
455
456         switch (err) {
457         case ERROR_PRIVILEGE_NOT_HELD:
458                 if (requestedInformation & SACL_SECURITY_INFORMATION) {
459                         n = state->num_get_sacl_priv_notheld++;
460                         requestedInformation &= ~SACL_SECURITY_INFORMATION;
461                         if (n < MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) {
462                                 WARNING(
463 "We don't have enough privileges to read the full security\n"
464 "          descriptor of \"%ls\"!\n"
465 "          Re-trying with SACL omitted.\n", path);
466                         } else if (n == MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) {
467                                 WARNING(
468 "Suppressing further privileges not held error messages when reading\n"
469 "          security descriptors.");
470                         }
471                         goto again;
472                 }
473                 /* Fall through */
474         case ERROR_ACCESS_DENIED:
475                 n = state->num_get_sd_access_denied++;
476                 if (n < MAX_GET_SD_ACCESS_DENIED_WARNINGS) {
477                         WARNING("Failed to read security descriptor of \"%ls\": "
478                                 "Access denied!\n%ls", path, capture_access_denied_msg);
479                 } else if (n == MAX_GET_SD_ACCESS_DENIED_WARNINGS) {
480                         WARNING("Suppressing further access denied errors messages i"
481                                 "when reading security descriptors");
482                 }
483                 return 0;
484         default:
485 fail:
486                 ERROR("Failed to read security descriptor of \"%ls\"", path);
487                 win32_error(err);
488                 return WIMLIB_ERR_READ;
489         }
490 }
491
492 static int
493 win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
494                                   wchar_t *path,
495                                   size_t path_num_chars,
496                                   struct wim_lookup_table *lookup_table,
497                                   struct wim_inode_table *inode_table,
498                                   struct sd_set *sd_set,
499                                   const struct wimlib_capture_config *config,
500                                   int add_image_flags,
501                                   wimlib_progress_func_t progress_func,
502                                   struct win32_capture_state *state,
503                                   unsigned vol_flags);
504
505 /* Reads the directory entries of directory using a Win32 API and recursively
506  * calls win32_build_dentry_tree() on them. */
507 static int
508 win32_recurse_directory(struct wim_dentry *root,
509                         wchar_t *dir_path,
510                         size_t dir_path_num_chars,
511                         struct wim_lookup_table *lookup_table,
512                         struct wim_inode_table *inode_table,
513                         struct sd_set *sd_set,
514                         const struct wimlib_capture_config *config,
515                         int add_image_flags,
516                         wimlib_progress_func_t progress_func,
517                         struct win32_capture_state *state,
518                         unsigned vol_flags)
519 {
520         WIN32_FIND_DATAW dat;
521         HANDLE hFind;
522         DWORD err;
523         int ret;
524
525         DEBUG("Recurse to directory \"%ls\"", dir_path);
526
527         /* Begin reading the directory by calling FindFirstFileW.  Unlike UNIX
528          * opendir(), FindFirstFileW has file globbing built into it.  But this
529          * isn't what we actually want, so just add a dummy glob to get all
530          * entries. */
531         dir_path[dir_path_num_chars] = L'/';
532         dir_path[dir_path_num_chars + 1] = L'*';
533         dir_path[dir_path_num_chars + 2] = L'\0';
534         hFind = FindFirstFileW(dir_path, &dat);
535         dir_path[dir_path_num_chars] = L'\0';
536
537         if (hFind == INVALID_HANDLE_VALUE) {
538                 err = GetLastError();
539                 if (err == ERROR_FILE_NOT_FOUND) {
540                         return 0;
541                 } else {
542                         ERROR("Failed to read directory \"%ls\"", dir_path);
543                         win32_error(err);
544                         return WIMLIB_ERR_READ;
545                 }
546         }
547         ret = 0;
548         do {
549                 /* Skip . and .. entries */
550                 if (dat.cFileName[0] == L'.' &&
551                     (dat.cFileName[1] == L'\0' ||
552                      (dat.cFileName[1] == L'.' &&
553                       dat.cFileName[2] == L'\0')))
554                         continue;
555                 size_t filename_len = wcslen(dat.cFileName);
556
557                 dir_path[dir_path_num_chars] = L'/';
558                 wmemcpy(dir_path + dir_path_num_chars + 1,
559                         dat.cFileName,
560                         filename_len + 1);
561
562                 struct wim_dentry *child;
563                 size_t path_len = dir_path_num_chars + 1 + filename_len;
564                 ret = win32_build_dentry_tree_recursive(&child,
565                                                         dir_path,
566                                                         path_len,
567                                                         lookup_table,
568                                                         inode_table,
569                                                         sd_set,
570                                                         config,
571                                                         add_image_flags,
572                                                         progress_func,
573                                                         state,
574                                                         vol_flags);
575                 dir_path[dir_path_num_chars] = L'\0';
576                 if (ret)
577                         goto out_find_close;
578                 if (child)
579                         dentry_add_child(root, child);
580         } while (FindNextFileW(hFind, &dat));
581         err = GetLastError();
582         if (err != ERROR_NO_MORE_FILES) {
583                 ERROR("Failed to read directory \"%ls\"", dir_path);
584                 win32_error(err);
585                 if (ret == 0)
586                         ret = WIMLIB_ERR_READ;
587         }
588 out_find_close:
589         FindClose(hFind);
590         return ret;
591 }
592
593 /* Load a reparse point into a WIM inode.  It is just stored in memory.
594  *
595  * @hFile:  Open handle to a reparse point, with permission to read the reparse
596  *          data.
597  *
598  * @inode:  WIM inode for the reparse point.
599  *
600  * @lookup_table:  Stream lookup table for the WIM; an entry will be added to it
601  *                 for the reparse point unless an entry already exists for
602  *                 the exact same data stream.
603  *
604  * @path:  External path to the reparse point.  Used for error messages only.
605  *
606  * Returns 0 on success; nonzero on failure. */
607 static int
608 win32_capture_reparse_point(HANDLE hFile,
609                             struct wim_inode *inode,
610                             struct wim_lookup_table *lookup_table,
611                             const wchar_t *path)
612 {
613         DEBUG("Capturing reparse point \"%ls\"", path);
614
615         /* "Reparse point data, including the tag and optional GUID,
616          * cannot exceed 16 kilobytes." - MSDN  */
617         char reparse_point_buf[REPARSE_POINT_MAX_SIZE];
618         DWORD bytesReturned;
619
620         if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT,
621                              NULL, /* "Not used with this operation; set to NULL" */
622                              0, /* "Not used with this operation; set to 0" */
623                              reparse_point_buf, /* "A pointer to a buffer that
624                                                    receives the reparse point data */
625                              sizeof(reparse_point_buf), /* "The size of the output
626                                                            buffer, in bytes */
627                              &bytesReturned,
628                              NULL))
629         {
630                 DWORD err = GetLastError();
631                 ERROR("Failed to get reparse data of \"%ls\"", path);
632                 win32_error(err);
633                 return WIMLIB_ERR_READ;
634         }
635         if (bytesReturned < 8) {
636                 ERROR("Reparse data on \"%ls\" is invalid", path);
637                 return WIMLIB_ERR_READ;
638         }
639         inode->i_reparse_tag = le32_to_cpu(*(u32*)reparse_point_buf);
640         return inode_add_ads_with_data(inode, L"",
641                                        reparse_point_buf + 8,
642                                        bytesReturned - 8, lookup_table);
643 }
644
645 /* Scans an unnamed or named stream of a Win32 file (not a reparse point
646  * stream); calculates its SHA1 message digest and either creates a `struct
647  * wim_lookup_table_entry' in memory for it, or uses an existing 'struct
648  * wim_lookup_table_entry' for an identical stream.
649  *
650  * @path:               Path to the file (UTF-16LE).
651  *
652  * @path_num_chars:     Number of 2-byte characters in @path.
653  *
654  * @inode:              WIM inode to save the stream into.
655  *
656  * @lookup_table:       Stream lookup table for the WIM.
657  *
658  * @dat:                A `WIN32_FIND_STREAM_DATA' structure that specifies the
659  *                      stream name.
660  *
661  * Returns 0 on success; nonzero on failure.
662  */
663 static int
664 win32_capture_stream(const wchar_t *path,
665                      size_t path_num_chars,
666                      struct wim_inode *inode,
667                      struct wim_lookup_table *lookup_table,
668                      WIN32_FIND_STREAM_DATA *dat)
669 {
670         struct wim_ads_entry *ads_entry;
671         struct wim_lookup_table_entry *lte;
672         int ret;
673         wchar_t *stream_name, *colon;
674         size_t stream_name_nchars;
675         bool is_named_stream;
676         wchar_t *spath;
677         size_t spath_nchars;
678         size_t spath_buf_nbytes;
679         const wchar_t *relpath_prefix;
680         const wchar_t *colonchar;
681
682         DEBUG("Capture \"%ls\" stream \"%ls\"", path, dat->cStreamName);
683
684         /* The stream name should be returned as :NAME:TYPE */
685         stream_name = dat->cStreamName;
686         if (*stream_name != L':')
687                 goto out_invalid_stream_name;
688         stream_name += 1;
689         colon = wcschr(stream_name, L':');
690         if (colon == NULL)
691                 goto out_invalid_stream_name;
692
693         if (wcscmp(colon + 1, L"$DATA")) {
694                 /* Not a DATA stream */
695                 ret = 0;
696                 goto out;
697         }
698
699         *colon = '\0';
700
701         stream_name_nchars = colon - stream_name;
702         is_named_stream = (stream_name_nchars != 0);
703
704         if (is_named_stream) {
705                 /* Allocate an ADS entry for the named stream. */
706                 ads_entry = inode_add_ads_utf16le(inode, stream_name,
707                                                   stream_name_nchars * sizeof(wchar_t));
708                 if (!ads_entry) {
709                         ret = WIMLIB_ERR_NOMEM;
710                         goto out;
711                 }
712         }
713
714         /* Create a UTF-16LE string @spath that gives the filename, then a
715          * colon, then the stream name.  Or, if it's an unnamed stream, just the
716          * filename.  It is MALLOC()'ed so that it can be saved in the
717          * wim_lookup_table_entry if needed.
718          *
719          * As yet another special case, relative paths need to be changed to
720          * begin with an explicit "./" so that, for example, a file t:ads, where
721          * :ads is the part we added, is not interpreted as a file on the t:
722          * drive. */
723         spath_nchars = path_num_chars;
724         relpath_prefix = L"";
725         colonchar = L"";
726         if (is_named_stream) {
727                 spath_nchars += 1 + stream_name_nchars;
728                 colonchar = L":";
729                 if (path_num_chars == 1 &&
730                     path[0] != L'/' &&
731                     path[0] != L'\\')
732                 {
733                         spath_nchars += 2;
734                         relpath_prefix = L"./";
735                 }
736         }
737
738         spath_buf_nbytes = (spath_nchars + 1) * sizeof(wchar_t);
739         spath = MALLOC(spath_buf_nbytes);
740
741         swprintf(spath, L"%ls%ls%ls%ls",
742                  relpath_prefix, path, colonchar, stream_name);
743
744         /* Make a new wim_lookup_table_entry */
745         lte = new_lookup_table_entry();
746         if (!lte) {
747                 ret = WIMLIB_ERR_NOMEM;
748                 goto out_free_spath;
749         }
750         lte->file_on_disk = spath;
751         spath = NULL;
752         if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED && !is_named_stream)
753                 lte->resource_location = RESOURCE_WIN32_ENCRYPTED;
754         else
755                 lte->resource_location = RESOURCE_WIN32;
756         lte->resource_entry.original_size = (u64)dat->StreamSize.QuadPart;
757
758         u32 stream_id;
759         if (is_named_stream) {
760                 stream_id = ads_entry->stream_id;
761                 ads_entry->lte = lte;
762         } else {
763                 stream_id = 0;
764                 inode->i_lte = lte;
765         }
766         lookup_table_insert_unhashed(lookup_table, lte, inode, stream_id);
767         ret = 0;
768 out_free_spath:
769         FREE(spath);
770 out:
771         return ret;
772 out_invalid_stream_name:
773         ERROR("Invalid stream name: \"%ls:%ls\"", path, dat->cStreamName);
774         ret = WIMLIB_ERR_READ;
775         goto out;
776 }
777
778 /* Scans a Win32 file for unnamed and named data streams (not reparse point
779  * streams).
780  *
781  * @path:               Path to the file (UTF-16LE).
782  *
783  * @path_num_chars:     Number of 2-byte characters in @path.
784  *
785  * @inode:              WIM inode to save the stream into.
786  *
787  * @lookup_table:       Stream lookup table for the WIM.
788  *
789  * @file_size:          Size of unnamed data stream.  (Used only if alternate
790  *                      data streams API appears to be unavailable.)
791  *
792  * @vol_flags:          Flags that specify features of the volume being
793  *                      captured.
794  *
795  * Returns 0 on success; nonzero on failure.
796  */
797 static int
798 win32_capture_streams(const wchar_t *path,
799                       size_t path_num_chars,
800                       struct wim_inode *inode,
801                       struct wim_lookup_table *lookup_table,
802                       u64 file_size,
803                       unsigned vol_flags)
804 {
805         WIN32_FIND_STREAM_DATA dat;
806         int ret;
807         HANDLE hFind;
808         DWORD err;
809
810         DEBUG("Capturing streams from \"%ls\"", path);
811
812         if (win32func_FindFirstStreamW == NULL ||
813             !(vol_flags & FILE_NAMED_STREAMS))
814                 goto unnamed_only;
815
816         hFind = win32func_FindFirstStreamW(path, FindStreamInfoStandard, &dat, 0);
817         if (hFind == INVALID_HANDLE_VALUE) {
818                 err = GetLastError();
819                 if (err == ERROR_CALL_NOT_IMPLEMENTED)
820                         goto unnamed_only;
821
822                 /* Seems legal for this to return ERROR_HANDLE_EOF on reparse
823                  * points and directories */
824                 if ((inode->i_attributes &
825                     (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
826                     && err == ERROR_HANDLE_EOF)
827                 {
828                         DEBUG("ERROR_HANDLE_EOF (ok)");
829                         return 0;
830                 } else {
831                         if (err == ERROR_ACCESS_DENIED) {
832                                 ERROR("Failed to look up data streams "
833                                       "of \"%ls\": Access denied!\n%ls",
834                                       path, capture_access_denied_msg);
835                                 return WIMLIB_ERR_READ;
836                         } else {
837                                 ERROR("Failed to look up data streams "
838                                       "of \"%ls\"", path);
839                                 win32_error(err);
840                                 return WIMLIB_ERR_READ;
841                         }
842                 }
843         }
844         do {
845                 ret = win32_capture_stream(path,
846                                            path_num_chars,
847                                            inode, lookup_table,
848                                            &dat);
849                 if (ret)
850                         goto out_find_close;
851         } while (win32func_FindNextStreamW(hFind, &dat));
852         err = GetLastError();
853         if (err != ERROR_HANDLE_EOF) {
854                 ERROR("Win32 API: Error reading data streams from \"%ls\"", path);
855                 win32_error(err);
856                 ret = WIMLIB_ERR_READ;
857         }
858 out_find_close:
859         FindClose(hFind);
860         return ret;
861 unnamed_only:
862         /* FindFirstStreamW() API is not available, or the volume does not
863          * support named streams.  Only capture the unnamed data stream. */
864         DEBUG("Only capturing unnamed data stream");
865         if (inode->i_attributes &
866              (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
867         {
868                 ret = 0;
869         } else {
870                 /* Just create our own WIN32_FIND_STREAM_DATA for an unnamed
871                  * stream to reduce the code to a call to the
872                  * already-implemented win32_capture_stream() */
873                 wcscpy(dat.cStreamName, L"::$DATA");
874                 dat.StreamSize.QuadPart = file_size;
875                 ret = win32_capture_stream(path,
876                                            path_num_chars,
877                                            inode, lookup_table,
878                                            &dat);
879         }
880         return ret;
881 }
882
883 static int
884 win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
885                                   wchar_t *path,
886                                   size_t path_num_chars,
887                                   struct wim_lookup_table *lookup_table,
888                                   struct wim_inode_table *inode_table,
889                                   struct sd_set *sd_set,
890                                   const struct wimlib_capture_config *config,
891                                   int add_image_flags,
892                                   wimlib_progress_func_t progress_func,
893                                   struct win32_capture_state *state,
894                                   unsigned vol_flags)
895 {
896         struct wim_dentry *root = NULL;
897         struct wim_inode *inode;
898         DWORD err;
899         u64 file_size;
900         int ret = 0;
901
902         if (exclude_path(path, path_num_chars, config, true)) {
903                 if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_ROOT) {
904                         ERROR("Cannot exclude the root directory from capture");
905                         ret = WIMLIB_ERR_INVALID_CAPTURE_CONFIG;
906                         goto out;
907                 }
908                 if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE)
909                     && progress_func)
910                 {
911                         union wimlib_progress_info info;
912                         info.scan.cur_path = path;
913                         info.scan.excluded = true;
914                         progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info);
915                 }
916                 goto out;
917         }
918
919         if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_VERBOSE)
920             && progress_func)
921         {
922                 union wimlib_progress_info info;
923                 info.scan.cur_path = path;
924                 info.scan.excluded = false;
925                 progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info);
926         }
927
928         HANDLE hFile = win32_open_existing_file(path,
929                                                 FILE_READ_DATA | FILE_READ_ATTRIBUTES);
930         if (hFile == INVALID_HANDLE_VALUE) {
931                 err = GetLastError();
932                 ERROR("Win32 API: Failed to open \"%ls\"", path);
933                 win32_error(err);
934                 ret = WIMLIB_ERR_OPEN;
935                 goto out;
936         }
937
938         BY_HANDLE_FILE_INFORMATION file_info;
939         if (!GetFileInformationByHandle(hFile, &file_info)) {
940                 err = GetLastError();
941                 ERROR("Win32 API: Failed to get file information for \"%ls\"",
942                       path);
943                 win32_error(err);
944                 ret = WIMLIB_ERR_STAT;
945                 goto out_close_handle;
946         }
947
948         /* Create a WIM dentry with an associated inode, which may be shared */
949         ret = inode_table_new_dentry(inode_table,
950                                      path_basename_with_len(path, path_num_chars),
951                                      ((u64)file_info.nFileIndexHigh << 32) |
952                                          (u64)file_info.nFileIndexLow,
953                                      file_info.dwVolumeSerialNumber,
954                                      &root);
955         if (ret)
956                 goto out_close_handle;
957
958         ret = win32_get_short_name(root, path);
959         if (ret)
960                 goto out_close_handle;
961
962         inode = root->d_inode;
963
964         if (inode->i_nlink > 1) /* Shared inode; nothing more to do */
965                 goto out_close_handle;
966
967         inode->i_attributes = file_info.dwFileAttributes;
968         inode->i_creation_time = FILETIME_to_u64(&file_info.ftCreationTime);
969         inode->i_last_write_time = FILETIME_to_u64(&file_info.ftLastWriteTime);
970         inode->i_last_access_time = FILETIME_to_u64(&file_info.ftLastAccessTime);
971         inode->i_resolved = 1;
972
973         add_image_flags &= ~(WIMLIB_ADD_IMAGE_FLAG_ROOT | WIMLIB_ADD_IMAGE_FLAG_SOURCE);
974
975         if (!(add_image_flags & WIMLIB_ADD_IMAGE_FLAG_NO_ACLS)
976             && (vol_flags & FILE_PERSISTENT_ACLS))
977         {
978                 ret = win32_get_security_descriptor(root, sd_set, path, state,
979                                                     add_image_flags);
980                 if (ret)
981                         goto out_close_handle;
982         }
983
984         file_size = ((u64)file_info.nFileSizeHigh << 32) |
985                      (u64)file_info.nFileSizeLow;
986
987         if (inode_is_directory(inode)) {
988                 /* Directory (not a reparse point) --- recurse to children */
989
990                 /* But first... directories may have alternate data streams that
991                  * need to be captured. */
992                 ret = win32_capture_streams(path,
993                                             path_num_chars,
994                                             inode,
995                                             lookup_table,
996                                             file_size,
997                                             vol_flags);
998                 if (ret)
999                         goto out_close_handle;
1000                 ret = win32_recurse_directory(root,
1001                                               path,
1002                                               path_num_chars,
1003                                               lookup_table,
1004                                               inode_table,
1005                                               sd_set,
1006                                               config,
1007                                               add_image_flags,
1008                                               progress_func,
1009                                               state,
1010                                               vol_flags);
1011         } else if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
1012                 /* Reparse point: save the reparse tag and data.  Alternate data
1013                  * streams are not captured, if it's even possible for a reparse
1014                  * point to have alternate data streams... */
1015                 ret = win32_capture_reparse_point(hFile,
1016                                                   inode,
1017                                                   lookup_table,
1018                                                   path);
1019         } else {
1020                 /* Not a directory, not a reparse point; capture the default
1021                  * file contents and any alternate data streams. */
1022                 ret = win32_capture_streams(path,
1023                                             path_num_chars,
1024                                             inode,
1025                                             lookup_table,
1026                                             file_size,
1027                                             vol_flags);
1028         }
1029 out_close_handle:
1030         CloseHandle(hFile);
1031 out:
1032         if (ret == 0)
1033                 *root_ret = root;
1034         else
1035                 free_dentry_tree(root, lookup_table);
1036         return ret;
1037 }
1038
1039 static void
1040 win32_do_capture_warnings(const struct win32_capture_state *state,
1041                           int add_image_flags)
1042 {
1043         if (state->num_get_sacl_priv_notheld == 0 &&
1044             state->num_get_sd_access_denied == 0)
1045                 return;
1046
1047         WARNING("");
1048         WARNING("Built dentry tree successfully, but with the following problem(s):");
1049         if (state->num_get_sacl_priv_notheld != 0) {
1050                 WARNING("Could not capture SACL (System Access Control List)\n"
1051                         "          on %lu files or directories.",
1052                         state->num_get_sacl_priv_notheld);
1053         }
1054         if (state->num_get_sd_access_denied != 0) {
1055                 WARNING("Could not capture security descriptor at all\n"
1056                         "          on %lu files or directories.",
1057                         state->num_get_sd_access_denied);
1058         }
1059         WARNING(
1060           "Try running the program as the Administrator to make sure all the\n"
1061 "          desired metadata has been captured exactly.  However, if you\n"
1062 "          do not care about capturing security descriptors correctly, then\n"
1063 "          nothing more needs to be done%ls\n",
1064         (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_NO_ACLS) ? L"." :
1065          L", although you might consider\n"
1066 "          passing the --no-acls flag to `wimlib-imagex capture' or\n"
1067 "          `wimlib-imagex append' to explicitly capture no security\n"
1068 "          descriptors.\n");
1069 }
1070
1071 /* Win32 version of capturing a directory tree */
1072 int
1073 win32_build_dentry_tree(struct wim_dentry **root_ret,
1074                         const wchar_t *root_disk_path,
1075                         struct wim_lookup_table *lookup_table,
1076                         struct wim_inode_table *inode_table,
1077                         struct sd_set *sd_set,
1078                         const struct wimlib_capture_config *config,
1079                         int add_image_flags,
1080                         wimlib_progress_func_t progress_func,
1081                         void *extra_arg)
1082 {
1083         size_t path_nchars;
1084         wchar_t *path;
1085         int ret;
1086         struct win32_capture_state state;
1087         unsigned vol_flags;
1088
1089         path_nchars = wcslen(root_disk_path);
1090         if (path_nchars > 32767)
1091                 return WIMLIB_ERR_INVALID_PARAM;
1092
1093         win32_get_vol_flags(root_disk_path, &vol_flags);
1094
1095         /* There is no check for overflow later when this buffer is being used!
1096          * But the max path length on NTFS is 32767 characters, and paths need
1097          * to be written specially to even go past 260 characters, so we should
1098          * be okay with 32770 characters. */
1099         path = MALLOC(32770 * sizeof(wchar_t));
1100         if (!path)
1101                 return WIMLIB_ERR_NOMEM;
1102
1103         wmemcpy(path, root_disk_path, path_nchars + 1);
1104
1105         memset(&state, 0, sizeof(state));
1106         ret = win32_build_dentry_tree_recursive(root_ret,
1107                                                 path,
1108                                                 path_nchars,
1109                                                 lookup_table,
1110                                                 inode_table,
1111                                                 sd_set,
1112                                                 config,
1113                                                 add_image_flags,
1114                                                 progress_func,
1115                                                 &state,
1116                                                 vol_flags);
1117         FREE(path);
1118         if (ret == 0)
1119                 win32_do_capture_warnings(&state, add_image_flags);
1120         return ret;
1121 }
1122
1123 static int
1124 win32_set_reparse_data(HANDLE h,
1125                        u32 reparse_tag,
1126                        const struct wim_lookup_table_entry *lte,
1127                        const wchar_t *path)
1128 {
1129         int ret;
1130         u8 *buf;
1131         size_t len;
1132
1133         if (!lte) {
1134                 WARNING("\"%ls\" is marked as a reparse point but had no reparse data",
1135                         path);
1136                 return 0;
1137         }
1138         len = wim_resource_size(lte);
1139         if (len > 16 * 1024 - 8) {
1140                 WARNING("\"%ls\": reparse data too long!", path);
1141                 return 0;
1142         }
1143
1144         /* The WIM stream omits the ReparseTag and ReparseDataLength fields, so
1145          * leave 8 bytes of space for them at the beginning of the buffer, then
1146          * set them manually. */
1147         buf = alloca(len + 8);
1148         ret = read_full_resource_into_buf(lte, buf + 8, false);
1149         if (ret)
1150                 return ret;
1151         *(u32*)(buf + 0) = cpu_to_le32(reparse_tag);
1152         *(u16*)(buf + 4) = cpu_to_le16(len);
1153         *(u16*)(buf + 6) = 0;
1154
1155         /* Set the reparse data on the open file using the
1156          * FSCTL_SET_REPARSE_POINT ioctl.
1157          *
1158          * There are contradictions in Microsoft's documentation for this:
1159          *
1160          * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED,
1161          * lpOverlapped is ignored."
1162          *
1163          * --- So setting lpOverlapped to NULL is okay since it's ignored.
1164          *
1165          * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an
1166          * operation returns no output data and lpOutBuffer is NULL,
1167          * DeviceIoControl makes use of lpBytesReturned. After such an
1168          * operation, the value of lpBytesReturned is meaningless."
1169          *
1170          * --- So lpOverlapped not really ignored, as it affects another
1171          *  parameter.  This is the actual behavior: lpBytesReturned must be
1172          *  specified, even though lpBytesReturned is documented as:
1173          *
1174          *  "Not used with this operation; set to NULL."
1175          */
1176         DWORD bytesReturned;
1177         if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, buf, len + 8,
1178                              NULL, 0,
1179                              &bytesReturned /* lpBytesReturned */,
1180                              NULL /* lpOverlapped */))
1181         {
1182                 DWORD err = GetLastError();
1183                 ERROR("Failed to set reparse data on \"%ls\"", path);
1184                 win32_error(err);
1185                 return WIMLIB_ERR_WRITE;
1186         }
1187         return 0;
1188 }
1189
1190 static int
1191 win32_set_compressed(HANDLE hFile, const wchar_t *path)
1192 {
1193         USHORT format = COMPRESSION_FORMAT_DEFAULT;
1194         DWORD bytesReturned = 0;
1195         if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION,
1196                              &format, sizeof(USHORT),
1197                              NULL, 0,
1198                              &bytesReturned, NULL))
1199         {
1200                 /* Could be a warning only, but we only call this if the volume
1201                  * supports compression.  So I'm calling this an error. */
1202                 DWORD err = GetLastError();
1203                 ERROR("Failed to set compression flag on \"%ls\"", path);
1204                 win32_error(err);
1205                 return WIMLIB_ERR_WRITE;
1206         }
1207         return 0;
1208 }
1209
1210 static int
1211 win32_set_sparse(HANDLE hFile, const wchar_t *path)
1212 {
1213         DWORD bytesReturned = 0;
1214         if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE,
1215                              NULL, 0,
1216                              NULL, 0,
1217                              &bytesReturned, NULL))
1218         {
1219                 /* Could be a warning only, but we only call this if the volume
1220                  * supports sparse files.  So I'm calling this an error. */
1221                 DWORD err = GetLastError();
1222                 WARNING("Failed to set sparse flag on \"%ls\"", path);
1223                 win32_error(err);
1224                 return WIMLIB_ERR_WRITE;
1225         }
1226         return 0;
1227 }
1228
1229 /*
1230  * Sets the security descriptor on an extracted file.
1231  */
1232 static int
1233 win32_set_security_data(const struct wim_inode *inode,
1234                         const wchar_t *path,
1235                         struct apply_args *args)
1236 {
1237         PSECURITY_DESCRIPTOR descriptor;
1238         unsigned long n;
1239         DWORD err;
1240
1241         descriptor = wim_const_security_data(args->w)->descriptors[inode->i_security_id];
1242
1243         SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION |
1244                                                    SACL_SECURITY_INFORMATION |
1245                                                    OWNER_SECURITY_INFORMATION |
1246                                                    GROUP_SECURITY_INFORMATION;
1247 again:
1248         if (SetFileSecurityW(path, securityInformation, descriptor))
1249                 return 0;
1250         err = GetLastError();
1251         if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)
1252                 goto fail;
1253         switch (err) {
1254         case ERROR_PRIVILEGE_NOT_HELD:
1255                 if (securityInformation & SACL_SECURITY_INFORMATION) {
1256                         n = args->num_set_sacl_priv_notheld++;
1257                         securityInformation &= ~SACL_SECURITY_INFORMATION;
1258                         if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
1259                                 WARNING(
1260 "We don't have enough privileges to set the full security\n"
1261 "          descriptor on \"%ls\"!\n", path);
1262                                 if (args->num_set_sd_access_denied +
1263                                     args->num_set_sacl_priv_notheld == 1)
1264                                 {
1265                                         WARNING("%ls", apply_access_denied_msg);
1266                                 }
1267                                 WARNING("Re-trying with SACL omitted.\n", path);
1268                         } else if (n == MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) {
1269                                 WARNING(
1270 "Suppressing further 'privileges not held' error messages when setting\n"
1271 "          security descriptors.");
1272                         }
1273                         goto again;
1274                 }
1275                 /* Fall through */
1276         case ERROR_INVALID_OWNER:
1277         case ERROR_ACCESS_DENIED:
1278                 n = args->num_set_sd_access_denied++;
1279                 if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
1280                         WARNING("Failed to set security descriptor on \"%ls\": "
1281                                 "Access denied!\n", path);
1282                         if (args->num_set_sd_access_denied +
1283                             args->num_set_sacl_priv_notheld == 1)
1284                         {
1285                                 WARNING("%ls", apply_access_denied_msg);
1286                         }
1287                 } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
1288                         WARNING(
1289 "Suppressing further access denied error messages when setting\n"
1290 "          security descriptors");
1291                 }
1292                 return 0;
1293         default:
1294 fail:
1295                 ERROR("Failed to set security descriptor on \"%ls\"", path);
1296                 win32_error(err);
1297                 return WIMLIB_ERR_WRITE;
1298         }
1299 }
1300
1301
1302 static int
1303 win32_extract_chunk(const void *buf, size_t len, void *arg)
1304 {
1305         HANDLE hStream = arg;
1306
1307         DWORD nbytes_written;
1308         wimlib_assert(len <= 0xffffffff);
1309
1310         if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) ||
1311             nbytes_written != len)
1312         {
1313                 DWORD err = GetLastError();
1314                 ERROR("WriteFile(): write error");
1315                 win32_error(err);
1316                 return WIMLIB_ERR_WRITE;
1317         }
1318         return 0;
1319 }
1320
1321 static int
1322 do_win32_extract_stream(HANDLE hStream, struct wim_lookup_table_entry *lte)
1323 {
1324         return extract_wim_resource(lte, wim_resource_size(lte),
1325                                     win32_extract_chunk, hStream);
1326 }
1327
1328 static int
1329 do_win32_extract_encrypted_stream(const wchar_t *path,
1330                                   const struct wim_lookup_table_entry *lte)
1331 {
1332         ERROR("Extracting encryted streams not implemented");
1333         return WIMLIB_ERR_INVALID_PARAM;
1334 }
1335
1336 static bool
1337 path_is_root_of_drive(const wchar_t *path)
1338 {
1339         if (!*path)
1340                 return false;
1341
1342         if (*path != L'/' && *path != L'\\') {
1343                 if (*(path + 1) == L':')
1344                         path += 2;
1345                 else
1346                         return false;
1347         }
1348         while (*path == L'/' || *path == L'\\')
1349                 path++;
1350         return (*path == L'\0');
1351 }
1352
1353 static DWORD
1354 win32_get_create_flags_and_attributes(DWORD i_attributes)
1355 {
1356         DWORD attributes;
1357
1358         /*
1359          * Some attributes cannot be set by passing them to CreateFile().  In
1360          * particular:
1361          *
1362          * FILE_ATTRIBUTE_DIRECTORY:
1363          *   CreateDirectory() must be called instead of CreateFile().
1364          *
1365          * FILE_ATTRIBUTE_SPARSE_FILE:
1366          *   Needs an ioctl.
1367          *   See: win32_set_sparse().
1368          *
1369          * FILE_ATTRIBUTE_COMPRESSED:
1370          *   Not clear from the documentation, but apparently this needs an
1371          *   ioctl as well.
1372          *   See: win32_set_compressed().
1373          *
1374          * FILE_ATTRIBUTE_REPARSE_POINT:
1375          *   Needs an ioctl, with the reparse data specified.
1376          *   See: win32_set_reparse_data().
1377          *
1378          * In addition, clear any file flags in the attributes that we don't
1379          * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and
1380          * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application.
1381          */
1382         attributes = i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE |
1383                                       FILE_ATTRIBUTE_COMPRESSED |
1384                                       FILE_ATTRIBUTE_REPARSE_POINT |
1385                                       FILE_ATTRIBUTE_DIRECTORY |
1386                                       FILE_FLAG_DELETE_ON_CLOSE |
1387                                       FILE_FLAG_NO_BUFFERING |
1388                                       FILE_FLAG_OPEN_NO_RECALL |
1389                                       FILE_FLAG_OVERLAPPED |
1390                                       FILE_FLAG_RANDOM_ACCESS |
1391                                       /*FILE_FLAG_SESSION_AWARE |*/
1392                                       FILE_FLAG_SEQUENTIAL_SCAN |
1393                                       FILE_FLAG_WRITE_THROUGH);
1394         return attributes |
1395                FILE_FLAG_OPEN_REPARSE_POINT |
1396                FILE_FLAG_BACKUP_SEMANTICS;
1397 }
1398
1399 static bool
1400 inode_has_special_attributes(const struct wim_inode *inode)
1401 {
1402         return (inode->i_attributes & (FILE_ATTRIBUTE_COMPRESSED |
1403                                        FILE_ATTRIBUTE_REPARSE_POINT |
1404                                        FILE_ATTRIBUTE_SPARSE_FILE)) != 0;
1405 }
1406
1407 /* Set compression or sparse attributes, and reparse data, if supported by the
1408  * volume. */
1409 static int
1410 win32_set_special_attributes(HANDLE hFile, const struct wim_inode *inode,
1411                              struct wim_lookup_table_entry *unnamed_stream_lte,
1412                              const wchar_t *path, unsigned vol_flags)
1413 {
1414         int ret;
1415
1416         if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) {
1417                 if (vol_flags & FILE_FILE_COMPRESSION) {
1418                         DEBUG("Setting compression flag on \"%ls\"", path);
1419                         ret = win32_set_compressed(hFile, path);
1420                         if (ret)
1421                                 return ret;
1422                 } else {
1423                         DEBUG("Cannot set compression attribute on \"%ls\": "
1424                               "volume does not support transparent compression",
1425                               path);
1426                 }
1427         }
1428
1429         if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
1430                 if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) {
1431                         DEBUG("Setting sparse flag on \"%ls\"", path);
1432                         ret = win32_set_sparse(hFile, path);
1433                         if (ret)
1434                                 return ret;
1435                 } else {
1436                         DEBUG("Cannot set sparse attribute on \"%ls\": "
1437                               "volume does not support sparse files",
1438                               path);
1439                 }
1440         }
1441
1442         if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
1443                 if (vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
1444                         DEBUG("Setting reparse data on \"%ls\"", path);
1445                         ret = win32_set_reparse_data(hFile, inode->i_reparse_tag,
1446                                                      unnamed_stream_lte, path);
1447                         if (ret)
1448                                 return ret;
1449                 } else {
1450                         DEBUG("Cannot set reparse data on \"%ls\": volume "
1451                               "does not support reparse points", path);
1452                 }
1453         }
1454
1455         return 0;
1456 }
1457
1458 static int
1459 win32_extract_stream(const struct wim_inode *inode,
1460                      const wchar_t *path,
1461                      const wchar_t *stream_name_utf16,
1462                      struct wim_lookup_table_entry *lte,
1463                      unsigned vol_flags)
1464 {
1465         wchar_t *stream_path;
1466         HANDLE h;
1467         int ret;
1468         DWORD err;
1469         DWORD creationDisposition = CREATE_ALWAYS;
1470
1471         if (stream_name_utf16) {
1472                 /* Named stream.  Create a buffer that contains the UTF-16LE
1473                  * string [.\]@path:@stream_name_utf16.  This is needed to
1474                  * create and open the stream using CreateFileW().  I'm not
1475                  * aware of any other APIs to do this.  Note: the '$DATA' suffix
1476                  * seems to be unneeded.  Additional note: a "./" prefix needs
1477                  * to be added when the path is not absolute to avoid ambiguity
1478                  * with drive letters. */
1479                 size_t stream_path_nchars;
1480                 size_t path_nchars;
1481                 size_t stream_name_nchars;
1482                 const wchar_t *prefix;
1483
1484                 path_nchars = wcslen(path);
1485                 stream_name_nchars = wcslen(stream_name_utf16);
1486                 stream_path_nchars = path_nchars + 1 + stream_name_nchars;
1487                 if (path[0] != cpu_to_le16(L'\0') &&
1488                     path[0] != cpu_to_le16(L'/') &&
1489                     path[0] != cpu_to_le16(L'\\') &&
1490                     path[1] != cpu_to_le16(L':'))
1491                 {
1492                         prefix = L"./";
1493                         stream_path_nchars += 2;
1494                 } else {
1495                         prefix = L"";
1496                 }
1497                 stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
1498                 swprintf(stream_path, L"%ls%ls:%ls",
1499                          prefix, path, stream_name_utf16);
1500         } else {
1501                 /* Unnamed stream; its path is just the path to the file itself.
1502                  * */
1503                 stream_path = (wchar_t*)path;
1504
1505                 /* Directories must be created with CreateDirectoryW().  Then
1506                  * the call to CreateFileW() will merely open the directory that
1507                  * was already created rather than creating a new file. */
1508                 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
1509                         if (!CreateDirectoryW(stream_path, NULL)) {
1510                                 err = GetLastError();
1511                                 switch (err) {
1512                                 case ERROR_ALREADY_EXISTS:
1513                                         break;
1514                                 case ERROR_ACCESS_DENIED:
1515                                         if (path_is_root_of_drive(path))
1516                                                 break;
1517                                         /* Fall through */
1518                                 default:
1519                                         ERROR("Failed to create directory \"%ls\"",
1520                                               stream_path);
1521                                         win32_error(err);
1522                                         ret = WIMLIB_ERR_MKDIR;
1523                                         goto fail;
1524                                 }
1525                         }
1526                         DEBUG("Created directory \"%ls\"", stream_path);
1527                         if (!inode_has_special_attributes(inode)) {
1528                                 ret = 0;
1529                                 goto out;
1530                         }
1531                         DEBUG("Directory \"%ls\" has special attributes!",
1532                               stream_path);
1533                         creationDisposition = OPEN_EXISTING;
1534                 }
1535         }
1536
1537         DEBUG("Opening \"%ls\"", stream_path);
1538         h = CreateFileW(stream_path,
1539                         GENERIC_READ | GENERIC_WRITE,
1540                         0,
1541                         NULL,
1542                         creationDisposition,
1543                         win32_get_create_flags_and_attributes(inode->i_attributes),
1544                         NULL);
1545         if (h == INVALID_HANDLE_VALUE) {
1546                 err = GetLastError();
1547                 ERROR("Failed to create \"%ls\"", stream_path);
1548                 win32_error(err);
1549                 ret = WIMLIB_ERR_OPEN;
1550                 goto fail;
1551         }
1552
1553         if (stream_name_utf16 == NULL && inode_has_special_attributes(inode)) {
1554                 ret = win32_set_special_attributes(h, inode, lte, path,
1555                                                    vol_flags);
1556                 if (ret)
1557                         goto fail_close_handle;
1558         }
1559
1560         if (!(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
1561                 if (lte) {
1562                         DEBUG("Extracting \"%ls\" (len = %"PRIu64")",
1563                               stream_path, wim_resource_size(lte));
1564                         if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED
1565                             && stream_name_utf16 == NULL
1566                             && (vol_flags & FILE_SUPPORTS_ENCRYPTION))
1567                         {
1568                                 ret = do_win32_extract_encrypted_stream(stream_path,
1569                                                                         lte);
1570                         } else {
1571                                 ret = do_win32_extract_stream(h, lte);
1572                         }
1573                         if (ret)
1574                                 goto fail_close_handle;
1575                 }
1576         }
1577
1578         DEBUG("Closing \"%ls\"", stream_path);
1579         if (!CloseHandle(h)) {
1580                 err = GetLastError();
1581                 ERROR("Failed to close \"%ls\"", stream_path);
1582                 win32_error(err);
1583                 ret = WIMLIB_ERR_WRITE;
1584                 goto fail;
1585         }
1586         ret = 0;
1587         goto out;
1588 fail_close_handle:
1589         CloseHandle(h);
1590 fail:
1591         ERROR("Error extracting %ls", stream_path);
1592 out:
1593         return ret;
1594 }
1595
1596 /*
1597  * Creates a file, directory, or reparse point and extracts all streams to it
1598  * (unnamed data stream and/or reparse point stream, plus any alternate data
1599  * streams).  This in Win32-specific code.
1600  *
1601  * @inode:      WIM inode for this file or directory.
1602  * @path:       UTF-16LE external path to extract the inode to.
1603  *
1604  * Returns 0 on success; nonzero on failure.
1605  */
1606 static int
1607 win32_extract_streams(const struct wim_inode *inode,
1608                       const wchar_t *path, u64 *completed_bytes_p,
1609                       unsigned vol_flags)
1610 {
1611         struct wim_lookup_table_entry *unnamed_lte;
1612         int ret;
1613
1614         unnamed_lte = inode_unnamed_lte_resolved(inode);
1615         ret = win32_extract_stream(inode, path, NULL, unnamed_lte,
1616                                    vol_flags);
1617         if (ret)
1618                 goto out;
1619         if (unnamed_lte)
1620                 *completed_bytes_p += wim_resource_size(unnamed_lte);
1621
1622         if (!(vol_flags & FILE_NAMED_STREAMS))
1623                 goto out;
1624         for (u16 i = 0; i < inode->i_num_ads; i++) {
1625                 const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i];
1626                 if (ads_entry->stream_name_nbytes != 0) {
1627                         /* Skip special UNIX data entries (see documentation for
1628                          * WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA) */
1629                         if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES
1630                             && !memcmp(ads_entry->stream_name,
1631                                        WIMLIB_UNIX_DATA_TAG_UTF16LE,
1632                                        WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES))
1633                                 continue;
1634                         ret = win32_extract_stream(inode,
1635                                                    path,
1636                                                    ads_entry->stream_name,
1637                                                    ads_entry->lte,
1638                                                    vol_flags);
1639                         if (ret)
1640                                 break;
1641                         if (ads_entry->lte)
1642                                 *completed_bytes_p += wim_resource_size(ads_entry->lte);
1643                 }
1644         }
1645 out:
1646         return ret;
1647 }
1648
1649 /* Extract a file, directory, reparse point, or hard link to an
1650  * already-extracted file using the Win32 API */
1651 int
1652 win32_do_apply_dentry(const wchar_t *output_path,
1653                       size_t output_path_num_chars,
1654                       struct wim_dentry *dentry,
1655                       struct apply_args *args)
1656 {
1657         int ret;
1658         struct wim_inode *inode = dentry->d_inode;
1659         DWORD err;
1660
1661         if (!args->have_vol_flags) {
1662                 win32_get_vol_flags(output_path, &args->vol_flags);
1663                 args->have_vol_flags = true;
1664                 /* Warn the user about data that may not be extracted. */
1665                 if (!(args->vol_flags & FILE_SUPPORTS_SPARSE_FILES))
1666                         WARNING("Volume does not support sparse files!\n"
1667                                 "          Sparse files will be extracted as non-sparse.");
1668                 if (!(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1669                         WARNING("Volume does not support reparse points!\n"
1670                                 "          Reparse point data will not be extracted.");
1671                 if (!(args->vol_flags & FILE_NAMED_STREAMS)) {
1672                         WARNING("Volume does not support named data streams!\n"
1673                                 "          Named data streams will not be extracted.");
1674                 }
1675                 if (!(args->vol_flags & FILE_SUPPORTS_ENCRYPTION)) {
1676                         WARNING("Volume does not support encryption!\n"
1677                                 "          Encrypted files will be extracted as raw data.");
1678                 }
1679                 if (!(args->vol_flags & FILE_FILE_COMPRESSION)) {
1680                         WARNING("Volume does not support transparent compression!\n"
1681                                 "          Compressed files will be extracted as non-compressed.");
1682                 }
1683                 if (!(args->vol_flags & FILE_PERSISTENT_ACLS)) {
1684                         if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
1685                                 ERROR("Strict ACLs requested, but the volume does not "
1686                                       "support ACLs!");
1687                                 return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
1688                         } else {
1689                                 WARNING("Volume does not support persistent ACLS!\n"
1690                                         "          File permissions will not be extracted.");
1691                         }
1692                 }
1693         }
1694
1695         if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1696                 /* Linked file, with another name already extracted.  Create a
1697                  * hard link. */
1698
1699                 /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
1700                  * but it's only available on Windows 7 and later.  So no use
1701                  * even checking it, really.  Instead, CreateHardLinkW() will
1702                  * apparently return ERROR_INVALID_FUNCTION if the volume does
1703                  * not support hard links. */
1704                 DEBUG("Creating hard link \"%ls => %ls\"",
1705                       output_path, inode->i_extracted_file);
1706                 if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
1707                         return 0;
1708
1709                 err = GetLastError();
1710                 if (err != ERROR_INVALID_FUNCTION) {
1711                         ERROR("Can't create hard link \"%ls => %ls\"",
1712                               output_path, inode->i_extracted_file);
1713                         win32_error(err);
1714                         return WIMLIB_ERR_LINK;
1715                 } else {
1716                         WARNING("Can't create hard link \"%ls => %ls\":\n"
1717                                 "          Volume does not support hard links!\n"
1718                                 "          Falling back to extracting a copy of the file.");
1719                 }
1720         }
1721         /* Create the file, directory, or reparse point, and extract the
1722          * data streams. */
1723         ret = win32_extract_streams(inode, output_path,
1724                                     &args->progress.extract.completed_bytes,
1725                                     args->vol_flags);
1726         if (ret)
1727                 return ret;
1728
1729         if (inode->i_security_id >= 0 &&
1730             !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
1731             && (args->vol_flags & FILE_PERSISTENT_ACLS))
1732         {
1733                 ret = win32_set_security_data(inode, output_path, args);
1734                 if (ret)
1735                         return ret;
1736         }
1737         if (inode->i_nlink > 1) {
1738                 /* Save extracted path for a later call to
1739                  * CreateHardLinkW() if this inode has multiple links.
1740                  * */
1741                 inode->i_extracted_file = WSTRDUP(output_path);
1742                 if (!inode->i_extracted_file)
1743                         ret = WIMLIB_ERR_NOMEM;
1744         }
1745         return 0;
1746 }
1747
1748 /* Set timestamps on an extracted file using the Win32 API */
1749 int
1750 win32_do_apply_dentry_timestamps(const wchar_t *path,
1751                                  size_t path_num_chars,
1752                                  const struct wim_dentry *dentry,
1753                                  const struct apply_args *args)
1754 {
1755         DWORD err;
1756         HANDLE h;
1757         const struct wim_inode *inode = dentry->d_inode;
1758
1759         DEBUG("Opening \"%ls\" to set timestamps", path);
1760         h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
1761         if (h == INVALID_HANDLE_VALUE) {
1762                 err = GetLastError();
1763                 goto fail;
1764         }
1765
1766         FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1767                                  .dwHighDateTime = inode->i_creation_time >> 32};
1768         FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1769                                   .dwHighDateTime = inode->i_last_access_time >> 32};
1770         FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1771                                   .dwHighDateTime = inode->i_last_write_time >> 32};
1772
1773         DEBUG("Calling SetFileTime() on \"%ls\"", path);
1774         if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1775                 err = GetLastError();
1776                 CloseHandle(h);
1777                 goto fail;
1778         }
1779         DEBUG("Closing \"%ls\"", path);
1780         if (!CloseHandle(h)) {
1781                 err = GetLastError();
1782                 goto fail;
1783         }
1784         goto out;
1785 fail:
1786         /* Only warn if setting timestamps failed; still return 0. */
1787         WARNING("Can't set timestamps on \"%ls\"", path);
1788         win32_error(err);
1789 out:
1790         return 0;
1791 }
1792
1793 /* Replacement for POSIX fsync() */
1794 int
1795 fsync(int fd)
1796 {
1797         DWORD err;
1798         HANDLE h;
1799
1800         h = (HANDLE)_get_osfhandle(fd);
1801         if (h == INVALID_HANDLE_VALUE) {
1802                 err = GetLastError();
1803                 ERROR("Could not get Windows handle for file descriptor");
1804                 win32_error(err);
1805                 errno = EBADF;
1806                 return -1;
1807         }
1808         if (!FlushFileBuffers(h)) {
1809                 err = GetLastError();
1810                 ERROR("Could not flush file buffers to disk");
1811                 win32_error(err);
1812                 errno = EIO;
1813                 return -1;
1814         }
1815         return 0;
1816 }
1817
1818 /* Use the Win32 API to get the number of processors */
1819 unsigned
1820 win32_get_number_of_processors()
1821 {
1822         SYSTEM_INFO sysinfo;
1823         GetSystemInfo(&sysinfo);
1824         return sysinfo.dwNumberOfProcessors;
1825 }
1826
1827 /* Replacement for POSIX-2008 realpath().  Warning: partial functionality only
1828  * (resolved_path must be NULL).   Also I highly doubt that GetFullPathName
1829  * really does the right thing under all circumstances. */
1830 wchar_t *
1831 realpath(const wchar_t *path, wchar_t *resolved_path)
1832 {
1833         DWORD ret;
1834         wimlib_assert(resolved_path == NULL);
1835         DWORD err;
1836
1837         ret = GetFullPathNameW(path, 0, NULL, NULL);
1838         if (!ret) {
1839                 err = GetLastError();
1840                 goto fail_win32;
1841         }
1842
1843         resolved_path = TMALLOC(ret);
1844         if (!resolved_path)
1845                 goto out;
1846         ret = GetFullPathNameW(path, ret, resolved_path, NULL);
1847         if (!ret) {
1848                 err = GetLastError();
1849                 free(resolved_path);
1850                 resolved_path = NULL;
1851                 goto fail_win32;
1852         }
1853         goto out;
1854 fail_win32:
1855         win32_error(err);
1856         errno = -1;
1857 out:
1858         return resolved_path;
1859 }
1860
1861 /* rename() on Windows fails if the destination file exists.  And we need to
1862  * make it work on wide characters.  Fix it. */
1863 int
1864 win32_rename_replacement(const wchar_t *oldpath, const wchar_t *newpath)
1865 {
1866         if (MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING)) {
1867                 return 0;
1868         } else {
1869                 /* As usual, the possible error values are not documented */
1870                 DWORD err = GetLastError();
1871                 ERROR("MoveFileEx(): Can't rename \"%ls\" to \"%ls\"",
1872                       oldpath, newpath);
1873                 win32_error(err);
1874                 errno = -1;
1875                 return -1;
1876         }
1877 }
1878
1879 /* Replacement for POSIX fnmatch() (partial functionality only) */
1880 int
1881 fnmatch(const wchar_t *pattern, const wchar_t *string, int flags)
1882 {
1883         if (PathMatchSpecW(string, pattern))
1884                 return 0;
1885         else
1886                 return FNM_NOMATCH;
1887 }
1888
1889 /* truncate() replacement */
1890 int
1891 win32_truncate_replacement(const wchar_t *path, off_t size)
1892 {
1893         DWORD err = NO_ERROR;
1894         LARGE_INTEGER liOffset;
1895
1896         HANDLE h = win32_open_existing_file(path, GENERIC_WRITE);
1897         if (h == INVALID_HANDLE_VALUE)
1898                 goto fail;
1899
1900         liOffset.QuadPart = size;
1901         if (!SetFilePointerEx(h, liOffset, NULL, FILE_BEGIN))
1902                 goto fail_close_handle;
1903
1904         if (!SetEndOfFile(h))
1905                 goto fail_close_handle;
1906         CloseHandle(h);
1907         return 0;
1908
1909 fail_close_handle:
1910         err = GetLastError();
1911         CloseHandle(h);
1912 fail:
1913         if (err == NO_ERROR)
1914                 err = GetLastError();
1915         ERROR("Can't truncate \"%ls\" to %"PRIu64" bytes", path, size);
1916         win32_error(err);
1917         errno = -1;
1918         return -1;
1919 }
1920
1921
1922 /* This really could be replaced with _wcserror_s, but this doesn't seem to
1923  * actually be available in MSVCRT.DLL on Windows XP (perhaps it's statically
1924  * linked in by Visual Studio...?). */
1925 extern int
1926 win32_strerror_r_replacement(int errnum, wchar_t *buf, size_t buflen)
1927 {
1928         static pthread_mutex_t strerror_lock = PTHREAD_MUTEX_INITIALIZER;
1929
1930         pthread_mutex_lock(&strerror_lock);
1931         mbstowcs(buf, strerror(errnum), buflen);
1932         buf[buflen - 1] = '\0';
1933         pthread_mutex_unlock(&strerror_lock);
1934         return 0;
1935 }
1936
1937 #endif /* __WIN32__ */