]> wimlib.net Git - wimlib/blob - src/win32_apply.c
c91315cf2f162695d24d9703dc03f8b40c6de95f
[wimlib] / src / win32_apply.c
1 /*
2  * win32_apply.c - Windows-specific code for applying files from a WIM image.
3  */
4
5 /*
6  * Copyright (C) 2013 Eric Biggers
7  *
8  * This file is part of wimlib, a library for working with WIM files.
9  *
10  * wimlib is free software; you can redistribute it and/or modify it under the
11  * terms of the GNU General Public License as published by the Free
12  * Software Foundation; either version 3 of the License, or (at your option)
13  * any later version.
14  *
15  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17  * A PARTICULAR PURPOSE. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with wimlib; if not, see http://www.gnu.org/licenses/.
22  */
23
24 #ifdef __WIN32__
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #include <aclapi.h> /* for SetSecurityInfo() */
31
32 #include "wimlib/win32_common.h"
33
34 #include "wimlib/apply.h"
35 #include "wimlib/dentry.h"
36 #include "wimlib/endianness.h"
37 #include "wimlib/error.h"
38 #include "wimlib/lookup_table.h"
39 #include "wimlib/metadata.h"
40 #include "wimlib/reparse.h"
41 #include "wimlib/security.h"
42
43 #define MAX_CREATE_HARD_LINK_WARNINGS 5
44 #define MAX_CREATE_SOFT_LINK_WARNINGS 5
45
46 #define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1
47 #define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1
48
49 static const wchar_t *apply_access_denied_msg =
50 L"If you are not running this program as the administrator, you may\n"
51  "          need to do so, so that all data and metadata can be extracted\n"
52  "          exactly as the origignal copy.  However, if you do not care that\n"
53  "          the security descriptors are extracted correctly, you could run\n"
54  "          `wimlib-imagex apply' with the --no-acls flag instead.\n"
55  ;
56
57
58 static int
59 win32_extract_try_rpfix(u8 *rpbuf,
60                         const wchar_t *extract_root_realpath,
61                         unsigned extract_root_realpath_nchars)
62 {
63         struct reparse_data rpdata;
64         wchar_t *target;
65         size_t target_nchars;
66         size_t stripped_nchars;
67         wchar_t *stripped_target;
68         wchar_t stripped_target_nchars;
69         int ret;
70
71         utf16lechar *new_target;
72         utf16lechar *new_print_name;
73         size_t new_target_nchars;
74         size_t new_print_name_nchars;
75         utf16lechar *p;
76
77         ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
78                                  &rpdata);
79         if (ret)
80                 return ret;
81
82         if (extract_root_realpath[0] == L'\0' ||
83             extract_root_realpath[1] != L':' ||
84             extract_root_realpath[2] != L'\\')
85         {
86                 ERROR("Can't understand full path format \"%ls\".  "
87                       "Try turning reparse point fixups off...",
88                       extract_root_realpath);
89                 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
90         }
91
92         ret = parse_substitute_name(rpdata.substitute_name,
93                                     rpdata.substitute_name_nbytes,
94                                     rpdata.rptag);
95         if (ret < 0)
96                 return 0;
97         stripped_nchars = ret;
98         target = rpdata.substitute_name;
99         target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar);
100         stripped_target = target + 6;
101         stripped_target_nchars = target_nchars - stripped_nchars;
102
103         new_target = alloca((6 + extract_root_realpath_nchars +
104                              stripped_target_nchars) * sizeof(utf16lechar));
105
106         p = new_target;
107         if (stripped_nchars == 6) {
108                 /* Include \??\ prefix if it was present before */
109                 wmemcpy(p, L"\\??\\", 4);
110                 p += 4;
111         }
112
113         /* Print name excludes the \??\ if present. */
114         new_print_name = p;
115         if (stripped_nchars != 0) {
116                 /* Get drive letter from real path to extract root, if a drive
117                  * letter was present before. */
118                 *p++ = extract_root_realpath[0];
119                 *p++ = extract_root_realpath[1];
120         }
121         /* Copy the rest of the extract root */
122         wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
123         p += extract_root_realpath_nchars - 2;
124
125         /* Append the stripped target */
126         wmemcpy(p, stripped_target, stripped_target_nchars);
127         p += stripped_target_nchars;
128         new_target_nchars = p - new_target;
129         new_print_name_nchars = p - new_print_name;
130
131         if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE ||
132             new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE)
133         {
134                 ERROR("Path names too long to do reparse point fixup!");
135                 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
136         }
137         rpdata.substitute_name = new_target;
138         rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar);
139         rpdata.print_name = new_print_name;
140         rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar);
141         return make_reparse_buffer(&rpdata, rpbuf);
142 }
143
144 /* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on
145  * an extracted reparse point. */
146 static int
147 win32_set_reparse_data(HANDLE h,
148                        const struct wim_inode *inode,
149                        const struct wim_lookup_table_entry *lte,
150                        const wchar_t *path,
151                        struct apply_args *args)
152 {
153         int ret;
154         u8 rpbuf[REPARSE_POINT_MAX_SIZE];
155         DWORD bytesReturned;
156         u16 rpbuflen;
157
158         DEBUG("Setting reparse data on \"%ls\"", path);
159
160         ret = wim_inode_get_reparse_data(inode, rpbuf, &rpbuflen);
161         if (ret)
162                 return ret;
163
164         if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX &&
165             (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
166              inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) &&
167             !inode->i_not_rpfixed)
168         {
169                 ret = win32_extract_try_rpfix(rpbuf,
170                                               args->target_realpath,
171                                               args->target_realpath_len);
172                 if (ret)
173                         return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
174         }
175
176         /* Set the reparse data on the open file using the
177          * FSCTL_SET_REPARSE_POINT ioctl.
178          *
179          * There are contradictions in Microsoft's documentation for this:
180          *
181          * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED,
182          * lpOverlapped is ignored."
183          *
184          * --- So setting lpOverlapped to NULL is okay since it's ignored.
185          *
186          * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an
187          * operation returns no output data and lpOutBuffer is NULL,
188          * DeviceIoControl makes use of lpBytesReturned. After such an
189          * operation, the value of lpBytesReturned is meaningless."
190          *
191          * --- So lpOverlapped not really ignored, as it affects another
192          *  parameter.  This is the actual behavior: lpBytesReturned must be
193          *  specified, even though lpBytesReturned is documented as:
194          *
195          *  "Not used with this operation; set to NULL."
196          */
197         if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf,
198                              rpbuflen,
199                              NULL, 0,
200                              &bytesReturned /* lpBytesReturned */,
201                              NULL /* lpOverlapped */))
202         {
203                 DWORD err = GetLastError();
204                 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
205                 {
206                         args->num_soft_links_failed++;
207                         if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) {
208                                 WARNING("Can't set reparse data on \"%ls\": Access denied!\n"
209                                         "          You may be trying to extract a symbolic "
210                                         "link without the\n"
211                                         "          SeCreateSymbolicLink privilege, which by "
212                                         "default non-Administrator\n"
213                                         "          accounts do not have.", path);
214                         }
215                         if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
216                                 WARNING("Suppressing further warnings regarding failure to extract\n"
217                                         "          reparse points due to insufficient privileges...");
218                         }
219                 } else {
220                         ERROR("Failed to set reparse data on \"%ls\"", path);
221                         win32_error(err);
222                         if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
223                             inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
224                                 return WIMLIB_ERR_LINK;
225                         else
226                                 return WIMLIB_ERR_WRITE;
227                 }
228         }
229         return 0;
230 }
231
232 /* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the
233  * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */
234 static int
235 win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path)
236 {
237         DWORD bytesReturned;
238         if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION,
239                              &format, sizeof(USHORT),
240                              NULL, 0,
241                              &bytesReturned, NULL))
242         {
243                 /* Could be a warning only, but we only call this if the volume
244                  * supports compression.  So I'm calling this an error. */
245                 DWORD err = GetLastError();
246                 ERROR("Failed to set compression flag on \"%ls\"", path);
247                 win32_error(err);
248                 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
249                         return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
250                 else
251                         return WIMLIB_ERR_WRITE;
252         }
253         return 0;
254 }
255
256 /* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */
257 static int
258 win32_set_sparse(HANDLE hFile, const wchar_t *path)
259 {
260         DWORD bytesReturned;
261         if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE,
262                              NULL, 0,
263                              NULL, 0,
264                              &bytesReturned, NULL))
265         {
266                 /* Could be a warning only, but we only call this if the volume
267                  * supports sparse files.  So I'm calling this an error. */
268                 DWORD err = GetLastError();
269                 WARNING("Failed to set sparse flag on \"%ls\"", path);
270                 win32_error(err);
271                 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
272                         return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
273                 else
274                         return WIMLIB_ERR_WRITE;
275         }
276         return 0;
277 }
278
279 /*
280  * Sets the security descriptor on an extracted file.
281  */
282 static int
283 win32_set_security_data(const struct wim_inode *inode,
284                         HANDLE hFile,
285                         const wchar_t *path,
286                         struct apply_args *args)
287 {
288         PSECURITY_DESCRIPTOR descriptor;
289         unsigned long n;
290         DWORD err;
291         const struct wim_security_data *sd;
292
293         SECURITY_INFORMATION securityInformation = 0;
294
295         void *owner = NULL;
296         void *group = NULL;
297         ACL *dacl = NULL;
298         ACL *sacl = NULL;
299
300         BOOL owner_defaulted;
301         BOOL group_defaulted;
302         BOOL dacl_present;
303         BOOL dacl_defaulted;
304         BOOL sacl_present;
305         BOOL sacl_defaulted;
306
307         sd = wim_const_security_data(args->w);
308         descriptor = sd->descriptors[inode->i_security_id];
309
310         GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted);
311         if (owner)
312                 securityInformation |= OWNER_SECURITY_INFORMATION;
313
314         GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted);
315         if (group)
316                 securityInformation |= GROUP_SECURITY_INFORMATION;
317
318         GetSecurityDescriptorDacl(descriptor, &dacl_present,
319                                   &dacl, &dacl_defaulted);
320         if (dacl)
321                 securityInformation |= DACL_SECURITY_INFORMATION;
322
323         GetSecurityDescriptorSacl(descriptor, &sacl_present,
324                                   &sacl, &sacl_defaulted);
325         if (sacl)
326                 securityInformation |= SACL_SECURITY_INFORMATION;
327
328 again:
329         if (securityInformation == 0)
330                 return 0;
331         if (SetSecurityInfo(hFile, SE_FILE_OBJECT,
332                             securityInformation, owner, group, dacl, sacl))
333                 return 0;
334         err = GetLastError();
335         if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)
336                 goto fail;
337         switch (err) {
338         case ERROR_PRIVILEGE_NOT_HELD:
339                 if (securityInformation & SACL_SECURITY_INFORMATION) {
340                         n = args->num_set_sacl_priv_notheld++;
341                         securityInformation &= ~SACL_SECURITY_INFORMATION;
342                         sacl = NULL;
343                         if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
344                                 WARNING(
345 "We don't have enough privileges to set the full security\n"
346 "          descriptor on \"%ls\"!\n", path);
347                                 if (args->num_set_sd_access_denied +
348                                     args->num_set_sacl_priv_notheld == 1)
349                                 {
350                                         WARNING("%ls", apply_access_denied_msg);
351                                 }
352                                 WARNING("Re-trying with SACL omitted.\n", path);
353                         } else if (n == MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
354                                 WARNING(
355 "Suppressing further 'privileges not held' error messages when setting\n"
356 "          security descriptors.");
357                         }
358                         goto again;
359                 }
360                 /* Fall through */
361         case ERROR_INVALID_OWNER:
362         case ERROR_ACCESS_DENIED:
363                 n = args->num_set_sd_access_denied++;
364                 if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
365                         WARNING("Failed to set security descriptor on \"%ls\": "
366                                 "Access denied!\n", path);
367                         if (args->num_set_sd_access_denied +
368                             args->num_set_sacl_priv_notheld == 1)
369                         {
370                                 WARNING("%ls", apply_access_denied_msg);
371                         }
372                 } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
373                         WARNING(
374 "Suppressing further access denied error messages when setting\n"
375 "          security descriptors");
376                 }
377                 return 0;
378         default:
379 fail:
380                 ERROR("Failed to set security descriptor on \"%ls\"", path);
381                 win32_error(err);
382                 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
383                         return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
384                 else
385                         return WIMLIB_ERR_WRITE;
386         }
387 }
388
389
390 static int
391 win32_extract_chunk(const void *buf, size_t len, void *arg)
392 {
393         HANDLE hStream = arg;
394
395         DWORD nbytes_written;
396         wimlib_assert(len <= 0xffffffff);
397
398         if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) ||
399             nbytes_written != len)
400         {
401                 DWORD err = GetLastError();
402                 ERROR("WriteFile(): write error");
403                 win32_error(err);
404                 return WIMLIB_ERR_WRITE;
405         }
406         return 0;
407 }
408
409 static int
410 do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte)
411 {
412         return extract_wim_resource(lte, wim_resource_size(lte),
413                                     win32_extract_chunk, hStream);
414 }
415
416 struct win32_encrypted_extract_ctx {
417         const struct wim_lookup_table_entry *lte;
418         u64 offset;
419 };
420
421 static DWORD WINAPI
422 win32_encrypted_import_cb(unsigned char *data, void *_ctx,
423                           unsigned long *len_p)
424 {
425         struct win32_encrypted_extract_ctx *ctx = _ctx;
426         unsigned long len = *len_p;
427         const struct wim_lookup_table_entry *lte = ctx->lte;
428
429         len = min(len, wim_resource_size(lte) - ctx->offset);
430
431         if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data))
432                 return ERROR_READ_FAULT;
433
434         ctx->offset += len;
435         *len_p = len;
436         return ERROR_SUCCESS;
437 }
438
439 /* Create an encrypted file and extract the raw encrypted data to it.
440  *
441  * @path:  Path to encrypted file to create.
442  * @lte:   WIM lookup_table entry for the raw encrypted data.
443  *
444  * This is separate from do_win32_extract_stream() because the WIM is supposed
445  * to contain the *raw* encrypted data, which needs to be extracted ("imported")
446  * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and
447  * CloseEncryptedFileRaw().
448  *
449  * Returns 0 on success; nonzero on failure.
450  */
451 static int
452 do_win32_extract_encrypted_stream(const wchar_t *path,
453                                   const struct wim_lookup_table_entry *lte)
454 {
455         void *file_ctx;
456         int ret;
457
458         DEBUG("Opening file \"%ls\" to extract raw encrypted data", path);
459
460         ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx);
461         if (ret) {
462                 ERROR("Failed to open \"%ls\" to write raw encrypted data", path);
463                 win32_error(ret);
464                 return WIMLIB_ERR_OPEN;
465         }
466
467         if (lte) {
468                 struct win32_encrypted_extract_ctx ctx;
469
470                 ctx.lte = lte;
471                 ctx.offset = 0;
472                 ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx);
473                 if (ret == ERROR_SUCCESS) {
474                         ret = 0;
475                 } else {
476                         ret = WIMLIB_ERR_WRITE;
477                         ERROR("Failed to extract encrypted file \"%ls\"", path);
478                 }
479         }
480         CloseEncryptedFileRaw(file_ctx);
481         return ret;
482 }
483
484 static bool
485 path_is_root_of_drive(const wchar_t *path)
486 {
487         if (!*path)
488                 return false;
489
490         if (*path != L'/' && *path != L'\\') {
491                 if (*(path + 1) == L':')
492                         path += 2;
493                 else
494                         return false;
495         }
496         while (*path == L'/' || *path == L'\\')
497                 path++;
498         return (*path == L'\0');
499 }
500
501 static inline DWORD
502 win32_mask_attributes(DWORD i_attributes)
503 {
504         return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE |
505                                 FILE_ATTRIBUTE_COMPRESSED |
506                                 FILE_ATTRIBUTE_REPARSE_POINT |
507                                 FILE_ATTRIBUTE_DIRECTORY |
508                                 FILE_ATTRIBUTE_ENCRYPTED |
509                                 FILE_FLAG_DELETE_ON_CLOSE |
510                                 FILE_FLAG_NO_BUFFERING |
511                                 FILE_FLAG_OPEN_NO_RECALL |
512                                 FILE_FLAG_OVERLAPPED |
513                                 FILE_FLAG_RANDOM_ACCESS |
514                                 /*FILE_FLAG_SESSION_AWARE |*/
515                                 FILE_FLAG_SEQUENTIAL_SCAN |
516                                 FILE_FLAG_WRITE_THROUGH);
517 }
518
519 static inline DWORD
520 win32_get_create_flags_and_attributes(DWORD i_attributes)
521 {
522         /*
523          * Some attributes cannot be set by passing them to CreateFile().  In
524          * particular:
525          *
526          * FILE_ATTRIBUTE_DIRECTORY:
527          *   CreateDirectory() must be called instead of CreateFile().
528          *
529          * FILE_ATTRIBUTE_SPARSE_FILE:
530          *   Needs an ioctl.
531          *   See: win32_set_sparse().
532          *
533          * FILE_ATTRIBUTE_COMPRESSED:
534          *   Not clear from the documentation, but apparently this needs an
535          *   ioctl as well.
536          *   See: win32_set_compressed().
537          *
538          * FILE_ATTRIBUTE_REPARSE_POINT:
539          *   Needs an ioctl, with the reparse data specified.
540          *   See: win32_set_reparse_data().
541          *
542          * In addition, clear any file flags in the attributes that we don't
543          * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and
544          * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application.
545          */
546         return win32_mask_attributes(i_attributes) |
547                 FILE_FLAG_OPEN_REPARSE_POINT |
548                 FILE_FLAG_BACKUP_SEMANTICS;
549 }
550
551 /* Set compression and/or sparse attributes on a stream, if supported by the
552  * volume. */
553 static int
554 win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode,
555                                     struct wim_lookup_table_entry *unnamed_stream_lte,
556                                     const wchar_t *path, unsigned vol_flags)
557 {
558         int ret;
559
560         if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) {
561                 if (vol_flags & FILE_FILE_COMPRESSION) {
562                         ret = win32_set_compression_state(hFile,
563                                                           COMPRESSION_FORMAT_DEFAULT,
564                                                           path);
565                         if (ret)
566                                 return ret;
567                 } else {
568                         DEBUG("Cannot set compression attribute on \"%ls\": "
569                               "volume does not support transparent compression",
570                               path);
571                 }
572         }
573
574         if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
575                 if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) {
576                         DEBUG("Setting sparse flag on \"%ls\"", path);
577                         ret = win32_set_sparse(hFile, path);
578                         if (ret)
579                                 return ret;
580                 } else {
581                         DEBUG("Cannot set sparse attribute on \"%ls\": "
582                               "volume does not support sparse files",
583                               path);
584                 }
585         }
586         return 0;
587 }
588
589 /* Pre-create directories; extract encrypted streams */
590 static int
591 win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
592                                    const struct wim_lookup_table_entry *lte,
593                                    const wchar_t *path,
594                                    DWORD *creationDisposition_ret,
595                                    unsigned int vol_flags)
596 {
597         DWORD err;
598         int ret;
599
600         /* Directories must be created with CreateDirectoryW().  Then the call
601          * to CreateFileW() will merely open the directory that was already
602          * created rather than creating a new file. */
603         if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY &&
604             !path_is_root_of_drive(path)) {
605                 if (!CreateDirectoryW(path, NULL)) {
606                         err = GetLastError();
607                         if (err != ERROR_ALREADY_EXISTS) {
608                                 ERROR("Failed to create directory \"%ls\"",
609                                       path);
610                                 win32_error(err);
611                                 return WIMLIB_ERR_MKDIR;
612                         }
613                 }
614                 DEBUG("Created directory \"%ls\"", path);
615                 *creationDisposition_ret = OPEN_EXISTING;
616         }
617         if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
618             vol_flags & FILE_SUPPORTS_ENCRYPTION)
619         {
620                 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
621                         unsigned remaining_sharing_violations = 100;
622                         while (!EncryptFile(path)) {
623                                 if (remaining_sharing_violations &&
624                                     err == ERROR_SHARING_VIOLATION)
625                                 {
626                                         WARNING("Couldn't encrypt directory \"%ls\" "
627                                                 "due to sharing violation; re-trying "
628                                                 "after 100 ms", path);
629                                         Sleep(100);
630                                         remaining_sharing_violations--;
631                                 } else {
632                                         err = GetLastError();
633                                         ERROR("Failed to encrypt directory \"%ls\"",
634                                               path);
635                                         win32_error(err);
636                                         return WIMLIB_ERR_WRITE;
637                                 }
638                         }
639                 } else {
640                         ret = do_win32_extract_encrypted_stream(path, lte);
641                         if (ret)
642                                 return ret;
643                         DEBUG("Extracted encrypted file \"%ls\"", path);
644                 }
645                 *creationDisposition_ret = OPEN_EXISTING;
646         }
647
648         /* Set file attributes if we created the file.  Otherwise, we haven't
649          * created the file set and we will set the attributes in the call to
650          * CreateFileW().
651          *
652          * The FAT filesystem does not let you change the attributes of the root
653          * directory, so treat that as a special case and do not set attributes.
654          * */
655         if (*creationDisposition_ret == OPEN_EXISTING &&
656             !path_is_root_of_drive(path))
657         {
658                 if (!SetFileAttributesW(path,
659                                         win32_mask_attributes(inode->i_attributes)))
660                 {
661                         err = GetLastError();
662                         ERROR("Failed to set attributes on \"%ls\"", path);
663                         win32_error(err);
664                         return WIMLIB_ERR_WRITE;
665                 }
666         }
667         return 0;
668 }
669
670 /* Set security descriptor and extract stream data or reparse data (skip the
671  * unnamed data stream of encrypted files, which was already extracted). */
672 static int
673 win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry,
674                             const struct wim_lookup_table_entry *lte,
675                             const wchar_t *stream_path,
676                             const wchar_t *stream_name_utf16,
677                             struct apply_args *args)
678 {
679         int ret = 0;
680         const struct wim_inode *inode = dentry->d_inode;
681         if (stream_name_utf16 == NULL) {
682                 /* Unnamed stream. */
683
684                 /* Set security descriptor, unless the extract_flags indicate
685                  * not to or the volume does not supported it.  Note that this
686                  * is only done when the unnamed stream is being extracted, as
687                  * security descriptors are per-file and not per-stream. */
688                 if (inode->i_security_id >= 0 &&
689                     !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
690                     && (args->vol_flags & FILE_PERSISTENT_ACLS))
691                 {
692                         ret = win32_set_security_data(inode, h, stream_path, args);
693                         if (ret)
694                                 return ret;
695                 }
696
697                 /* Handle reparse points.  The data for them needs to be set
698                  * using a special ioctl.  Note that the reparse point may have
699                  * been created using CreateFileW() in the case of
700                  * non-directories or CreateDirectoryW() in the case of
701                  * directories; but the ioctl works either way.  Also, it is
702                  * only this step that actually sets the
703                  * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it
704                  * using SetFileAttributesW() or CreateFileW().
705                  *
706                  * If the volume does not support reparse points we simply
707                  * ignore the reparse data.  (N.B. the code currently doesn't
708                  * actually reach this case because reparse points are skipped
709                  * entirely on such volumes.) */
710                 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
711                         if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
712                                 ret = win32_set_reparse_data(h, inode,
713                                                              lte, stream_path,
714                                                              args);
715                                 if (ret)
716                                         return ret;
717                         } else {
718                                 DEBUG("Cannot set reparse data on \"%ls\": volume "
719                                       "does not support reparse points", stream_path);
720                         }
721                 } else if (lte != NULL &&
722                            !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION &&
723                              inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
724                 {
725                         /* Extract the data of the unnamed stream, unless the
726                          * lookup table entry is NULL (indicating an empty
727                          * stream for which no data needs to be extracted), or
728                          * the stream is encrypted and therefore was already
729                          * extracted as a special case. */
730                         ret = do_win32_extract_stream(h, lte);
731                         if (ret)
732                                 return ret;
733                 }
734
735                 if (dentry_has_short_name(dentry))
736                         SetFileShortNameW(h, dentry->short_name);
737                 else if (running_on_windows_7_or_later())
738                         SetFileShortNameW(h, L"");
739         } else {
740                 /* Extract the data for a named data stream. */
741                 if (lte != NULL) {
742                         DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")",
743                               stream_path, wim_resource_size(lte));
744                         ret = do_win32_extract_stream(h, lte);
745                 }
746         }
747         return ret;
748 }
749
750 static int
751 win32_decrypt_file(HANDLE open_handle, const wchar_t *path)
752 {
753         DWORD err;
754         /* We cannot call DecryptFileW() while there is an open handle to the
755          * file.  So close it first. */
756         if (!CloseHandle(open_handle)) {
757                 err = GetLastError();
758                 ERROR("Failed to close handle for \"%ls\"", path);
759                 win32_error(err);
760                 return WIMLIB_ERR_WRITE;
761         }
762         if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) {
763                 err = GetLastError();
764                 ERROR("Failed to decrypt file \"%ls\"", path);
765                 win32_error(err);
766                 return WIMLIB_ERR_WRITE;
767         }
768         return 0;
769 }
770
771 /*
772  * Create and extract a stream to a file, or create a directory, using the
773  * Windows API.
774  *
775  * This handles reparse points, directories, alternate data streams, encrypted
776  * files, compressed files, etc.
777  *
778  * @dentry: WIM dentry for the file or directory being extracted.
779  *
780  * @path:  Path to extract the file to.
781  *
782  * @stream_name_utf16:
783  *         Name of the stream, or NULL if the stream is unnamed.  This will
784  *         be called with a NULL stream_name_utf16 before any non-NULL
785  *         stream_name_utf16's.
786  *
787  * @lte:   WIM lookup table entry for the stream.  May be NULL to indicate
788  *         a stream of length 0.
789  *
790  * @args:  Additional apply context, including flags indicating supported
791  *         volume features.
792  *
793  * Returns 0 on success; nonzero on failure.
794  */
795 static int
796 win32_extract_stream(const struct wim_dentry *dentry,
797                      const wchar_t *path,
798                      const wchar_t *stream_name_utf16,
799                      struct wim_lookup_table_entry *lte,
800                      struct apply_args *args)
801 {
802         wchar_t *stream_path;
803         HANDLE h;
804         int ret;
805         DWORD err;
806         DWORD creationDisposition = CREATE_ALWAYS;
807         DWORD requestedAccess;
808         BY_HANDLE_FILE_INFORMATION file_info;
809         unsigned remaining_sharing_violations = 1000;
810         const struct wim_inode *inode = dentry->d_inode;
811
812         if (stream_name_utf16) {
813                 /* Named stream.  Create a buffer that contains the UTF-16LE
814                  * string [./]path:stream_name_utf16.  This is needed to
815                  * create and open the stream using CreateFileW().  I'm not
816                  * aware of any other APIs to do this.  Note: the '$DATA' suffix
817                  * seems to be unneeded.  Additional note: a "./" prefix needs
818                  * to be added when the path is not absolute to avoid ambiguity
819                  * with drive letters. */
820                 size_t stream_path_nchars;
821                 size_t path_nchars;
822                 size_t stream_name_nchars;
823                 const wchar_t *prefix;
824
825                 path_nchars = wcslen(path);
826                 stream_name_nchars = wcslen(stream_name_utf16);
827                 stream_path_nchars = path_nchars + 1 + stream_name_nchars;
828                 if (path[0] != cpu_to_le16(L'\0') &&
829                     path[0] != cpu_to_le16(L'/') &&
830                     path[0] != cpu_to_le16(L'\\') &&
831                     path[1] != cpu_to_le16(L':'))
832                 {
833                         prefix = L"./";
834                         stream_path_nchars += 2;
835                 } else {
836                         prefix = L"";
837                 }
838                 stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
839                 swprintf(stream_path, L"%ls%ls:%ls",
840                          prefix, path, stream_name_utf16);
841         } else {
842                 /* Unnamed stream; its path is just the path to the file itself.
843                  * */
844                 stream_path = (wchar_t*)path;
845
846                 ret = win32_begin_extract_unnamed_stream(inode, lte, path,
847                                                          &creationDisposition,
848                                                          args->vol_flags);
849                 if (ret)
850                         goto fail;
851         }
852
853         DEBUG("Opening \"%ls\"", stream_path);
854         /* DELETE access is needed for SetFileShortNameW(), for some reason. */
855         requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE |
856                           ACCESS_SYSTEM_SECURITY;
857 try_open_again:
858         /* Open the stream to be extracted.  Depending on what we have set
859          * creationDisposition to, we may be creating this for the first time,
860          * or we may be opening on existing stream we already created using
861          * CreateDirectoryW() or OpenEncryptedFileRawW(). */
862         h = CreateFileW(stream_path,
863                         requestedAccess,
864                         FILE_SHARE_READ,
865                         NULL,
866                         creationDisposition,
867                         win32_get_create_flags_and_attributes(inode->i_attributes),
868                         NULL);
869         if (h == INVALID_HANDLE_VALUE) {
870                 err = GetLastError();
871                 if (err == ERROR_ACCESS_DENIED &&
872                     path_is_root_of_drive(stream_path))
873                 {
874                         ret = 0;
875                         goto out;
876                 }
877                 if ((err == ERROR_PRIVILEGE_NOT_HELD ||
878                      err == ERROR_ACCESS_DENIED) &&
879                     (requestedAccess & ACCESS_SYSTEM_SECURITY))
880                 {
881                         /* Try opening the file again without privilege to
882                          * modify SACL. */
883                         requestedAccess &= ~ACCESS_SYSTEM_SECURITY;
884                         goto try_open_again;
885                 }
886                 if (err == ERROR_SHARING_VIOLATION) {
887                         if (remaining_sharing_violations) {
888                                 --remaining_sharing_violations;
889                                 /* This can happen when restoring encrypted directories
890                                  * for some reason.  Probably a bug in EncryptFile(). */
891                                 WARNING("Couldn't open \"%ls\" due to sharing violation; "
892                                         "re-trying after 100ms", stream_path);
893                                 Sleep(100);
894                                 goto try_open_again;
895                         } else {
896                                 ERROR("Too many sharing violations; giving up...");
897                         }
898                 } else {
899                         if (creationDisposition == OPEN_EXISTING)
900                                 ERROR("Failed to open \"%ls\"", stream_path);
901                         else
902                                 ERROR("Failed to create \"%ls\"", stream_path);
903                         win32_error(err);
904                 }
905                 ret = WIMLIB_ERR_OPEN;
906                 goto fail;
907         }
908
909         /* Check the attributes of the file we just opened, and remove
910          * encryption or compression if either was set by default but is not
911          * supposed to be set based on the WIM inode attributes. */
912         if (!GetFileInformationByHandle(h, &file_info)) {
913                 err = GetLastError();
914                 ERROR("Failed to get attributes of \"%ls\"", stream_path);
915                 win32_error(err);
916                 ret = WIMLIB_ERR_STAT;
917                 goto fail_close_handle;
918         }
919
920         /* Remove encryption? */
921         if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED &&
922             !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
923         {
924                 /* File defaulted to encrypted due to being in an encrypted
925                  * directory, but is not actually supposed to be encrypted.
926                  *
927                  * This is a workaround, because I'm not aware of any way to
928                  * directly (e.g. with CreateFileW()) create an unencrypted file
929                  * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */
930                 ret = win32_decrypt_file(h, stream_path);
931                 if (ret)
932                         goto fail; /* win32_decrypt_file() closed the handle. */
933                 creationDisposition = OPEN_EXISTING;
934                 goto try_open_again;
935         }
936
937         /* Remove compression? */
938         if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED &&
939             !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED))
940         {
941                 /* Similar to the encrypted case, above, if the file defaulted
942                  * to compressed due to being in an compressed directory, but is
943                  * not actually supposed to be compressed, explicitly set the
944                  * compression format to COMPRESSION_FORMAT_NONE. */
945                 ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE,
946                                                   stream_path);
947                 if (ret)
948                         goto fail_close_handle;
949         }
950
951         /* Set compression and/or sparse attributes if needed */
952         ret = win32_set_special_stream_attributes(h, inode, lte, path,
953                                                   args->vol_flags);
954
955         if (ret)
956                 goto fail_close_handle;
957
958         /* At this point we have at least created the needed stream with the
959          * appropriate attributes.  We have yet to set the appropriate security
960          * descriptor and actually extract the stream data (other than for
961          * extracted files, which were already extracted).
962          * win32_finish_extract_stream() handles these additional steps. */
963         ret = win32_finish_extract_stream(h, dentry, lte, stream_path,
964                                           stream_name_utf16, args);
965         if (ret)
966                 goto fail_close_handle;
967
968         /* Done extracting the stream.  Close the handle and return. */
969         DEBUG("Closing \"%ls\"", stream_path);
970         if (!CloseHandle(h)) {
971                 err = GetLastError();
972                 ERROR("Failed to close \"%ls\"", stream_path);
973                 win32_error(err);
974                 ret = WIMLIB_ERR_WRITE;
975                 goto fail;
976         }
977         ret = 0;
978         goto out;
979 fail_close_handle:
980         CloseHandle(h);
981 fail:
982         ERROR("Error extracting \"%ls\"", stream_path);
983 out:
984         return ret;
985 }
986
987 /*
988  * Creates a file, directory, or reparse point and extracts all streams to it
989  * (unnamed data stream and/or reparse point stream, plus any alternate data
990  * streams).  Handles sparse, compressed, and/or encrypted files.
991  *
992  * @dentry:     WIM dentry for this file or directory.
993  * @path:       UTF-16LE external path to extract the inode to.
994  * @args:       Additional extraction context.
995  *
996  * Returns 0 on success; nonzero on failure.
997  */
998 static int
999 win32_extract_streams(const struct wim_dentry *dentry,
1000                       const wchar_t *path, struct apply_args *args)
1001 {
1002         struct wim_lookup_table_entry *unnamed_lte;
1003         int ret;
1004         const struct wim_inode *inode = dentry->d_inode;
1005
1006         /* First extract the unnamed stream. */
1007
1008         unnamed_lte = inode_unnamed_lte_resolved(inode);
1009         ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args);
1010         if (ret)
1011                 goto out;
1012
1013         /* Extract any named streams, if supported by the volume. */
1014
1015         if (!(args->vol_flags & FILE_NAMED_STREAMS))
1016                 goto out;
1017         for (u16 i = 0; i < inode->i_num_ads; i++) {
1018                 const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i];
1019
1020                 /* Skip the unnamed stream if it's in the ADS entries (we
1021                  * already extracted it...) */
1022                 if (ads_entry->stream_name_nbytes == 0)
1023                         continue;
1024
1025                 /* Skip special UNIX data entries (see documentation for
1026                  * WIMLIB_ADD_FLAG_UNIX_DATA) */
1027                 if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES
1028                     && !memcmp(ads_entry->stream_name,
1029                                WIMLIB_UNIX_DATA_TAG_UTF16LE,
1030                                WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES))
1031                         continue;
1032
1033                 /* Extract the named stream */
1034                 ret = win32_extract_stream(dentry,
1035                                            path,
1036                                            ads_entry->stream_name,
1037                                            ads_entry->lte,
1038                                            args);
1039                 if (ret)
1040                         break;
1041         }
1042 out:
1043         return ret;
1044 }
1045
1046 static int
1047 dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore)
1048 {
1049         dentry->d_inode->i_visited = 0;
1050         return 0;
1051 }
1052
1053 static int
1054 dentry_get_features(struct wim_dentry *dentry, void *_features_p)
1055 {
1056         DWORD features = 0;
1057         DWORD *features_p = _features_p;
1058         struct wim_inode *inode = dentry->d_inode;
1059
1060         if (inode->i_visited) {
1061                 features |= FILE_SUPPORTS_HARD_LINKS;
1062         } else {
1063                 inode->i_visited = 1;
1064                 if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE)
1065                         features |= FILE_SUPPORTS_SPARSE_FILES;
1066                 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
1067                         features |= FILE_SUPPORTS_REPARSE_POINTS;
1068                 for (unsigned i = 0; i < inode->i_num_ads; i++)
1069                         if (inode->i_ads_entries[i].stream_name_nbytes)
1070                                 features |= FILE_NAMED_STREAMS;
1071                 if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
1072                         features |= FILE_SUPPORTS_ENCRYPTION;
1073                 if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
1074                         features |= FILE_FILE_COMPRESSION;
1075                 if (inode->i_security_id != -1)
1076                         features |= FILE_PERSISTENT_ACLS;
1077         }
1078         *features_p |= features;
1079         return 0;
1080 }
1081
1082 /* If not done already, load the supported feature flags for the volume onto
1083  * which the image is being extracted, and warn the user about any missing
1084  * features that could be important. */
1085 static int
1086 win32_check_vol_flags(const wchar_t *output_path,
1087                       struct wim_dentry *root, struct apply_args *args)
1088 {
1089         DWORD dentry_features = 0;
1090         DWORD missing_features;
1091
1092         if (args->have_vol_flags)
1093                 return 0;
1094
1095         for_dentry_in_tree(root, dentry_clear_inode_visited, NULL);
1096         for_dentry_in_tree(root, dentry_get_features, &dentry_features);
1097
1098         win32_get_vol_flags(output_path, &args->vol_flags);
1099         args->have_vol_flags = true;
1100
1101         missing_features = dentry_features & ~args->vol_flags;
1102
1103         /* Warn the user about data that may not be extracted. */
1104         if (missing_features & FILE_SUPPORTS_SPARSE_FILES)
1105                 WARNING("Volume does not support sparse files!\n"
1106                         "          Sparse files will be extracted as non-sparse.");
1107         if (missing_features & FILE_SUPPORTS_REPARSE_POINTS)
1108                 WARNING("Volume does not support reparse points!\n"
1109                         "          Reparse point data will not be extracted.");
1110         if (missing_features & FILE_NAMED_STREAMS) {
1111                 WARNING("Volume does not support named data streams!\n"
1112                         "          Named data streams will not be extracted.");
1113         }
1114         if (missing_features & FILE_SUPPORTS_ENCRYPTION) {
1115                 WARNING("Volume does not support encryption!\n"
1116                         "          Encrypted files will be extracted as raw data.");
1117         }
1118         if (missing_features & FILE_FILE_COMPRESSION) {
1119                 WARNING("Volume does not support transparent compression!\n"
1120                         "          Compressed files will be extracted as non-compressed.");
1121         }
1122         if (missing_features & FILE_PERSISTENT_ACLS) {
1123                 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
1124                         ERROR("Strict ACLs requested, but the volume does not "
1125                               "support ACLs!");
1126                         return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
1127                 } else {
1128                         WARNING("Volume does not support persistent ACLS!\n"
1129                                 "          File permissions will not be extracted.");
1130                 }
1131         }
1132         if (running_on_windows_7_or_later() &&
1133             (missing_features & FILE_SUPPORTS_HARD_LINKS))
1134         {
1135                 WARNING("Volume does not support hard links!\n"
1136                         "          Hard links will be extracted as duplicate files.");
1137         }
1138         return 0;
1139 }
1140
1141 /*
1142  * Try extracting a hard link.
1143  *
1144  * @output_path:  Path to link to be extracted.
1145  *
1146  * @inode:        WIM inode that the link is to; inode->i_extracted_file
1147  *                the path to a name of the file that has already been
1148  *                extracted (we use this to create the hard link).
1149  *
1150  * @args:         Additional apply context, used here to keep track of
1151  *                the number of times creating a hard link failed due to
1152  *                ERROR_INVALID_FUNCTION.  This error should indicate that hard
1153  *                links are not supported by the volume, and we would like to
1154  *                warn the user a few times, but not too many times.
1155  *
1156  * Returns 0 if the hard link was successfully extracted.  Returns
1157  * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly
1158  * being unsupported by the volume.  Returns a negative value if creating the
1159  * hard link failed due to ERROR_INVALID_FUNCTION.
1160  */
1161 static int
1162 win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
1163                     struct apply_args *args)
1164 {
1165         DWORD err;
1166
1167         /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
1168          * but it's only available on Windows 7 and later.
1169          *
1170          * Otherwise, CreateHardLinkW() will apparently return
1171          * ERROR_INVALID_FUNCTION if the volume does not support hard links. */
1172
1173         DEBUG("Creating hard link \"%ls => %ls\"",
1174               output_path, inode->i_extracted_file);
1175
1176         if (running_on_windows_7_or_later() &&
1177             !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS))
1178                 goto hard_links_unsupported;
1179
1180         if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
1181                 return 0;
1182
1183         err = GetLastError();
1184         if (err != ERROR_INVALID_FUNCTION) {
1185                 ERROR("Can't create hard link \"%ls => %ls\"",
1186                       output_path, inode->i_extracted_file);
1187                 win32_error(err);
1188                 return WIMLIB_ERR_LINK;
1189         }
1190 hard_links_unsupported:
1191         args->num_hard_links_failed++;
1192         if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
1193                 if (running_on_windows_7_or_later())
1194                 {
1195                         WARNING("Extracting duplicate copy of \"%ls\" "
1196                                 "rather than hard link", output_path);
1197                 } else {
1198                         WARNING("Can't create hard link \"%ls\" => \"%ls\":\n"
1199                                 "          Volume does not support hard links!\n"
1200                                 "          Falling back to extracting a copy of the file.",
1201                                 output_path, inode->i_extracted_file);
1202                 }
1203         }
1204         if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS)
1205                 WARNING("Suppressing further hard linking warnings...");
1206         return -1;
1207 }
1208
1209 /* Extract a file, directory, reparse point, or hard link to an
1210  * already-extracted file using the Win32 API */
1211 int
1212 win32_do_apply_dentry(const wchar_t *output_path,
1213                       size_t output_path_num_chars,
1214                       struct wim_dentry *dentry,
1215                       struct apply_args *args)
1216 {
1217         int ret;
1218         struct wim_inode *inode = dentry->d_inode;
1219
1220         ret = win32_check_vol_flags(output_path, dentry, args);
1221         if (ret)
1222                 return ret;
1223         if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1224                 /* Linked file, with another name already extracted.  Create a
1225                  * hard link. */
1226                 ret = win32_try_hard_link(output_path, inode, args);
1227                 if (ret >= 0)
1228                         return ret;
1229                 /* Negative return value from win32_try_hard_link() indicates
1230                  * that hard links are probably not supported by the volume.
1231                  * Fall back to extracting a copy of the file. */
1232         }
1233
1234         /* If this is a reparse point and the volume does not support reparse
1235          * points, just skip it completely. */
1236         if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1237             !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1238         {
1239                 WARNING("Not extracting reparse point \"%ls\"", output_path);
1240         } else {
1241                 /* Create the file, directory, or reparse point, and extract the
1242                  * data streams. */
1243                 ret = win32_extract_streams(dentry, output_path, args);
1244                 if (ret)
1245                         return ret;
1246         }
1247         if (inode->i_extracted_file == NULL) {
1248                 const struct wim_lookup_table_entry *lte;
1249
1250                 /* Tally bytes extracted, including all alternate data streams,
1251                  * unless we extracted a hard link (or, at least extracted a
1252                  * name that was supposed to be a hard link) */
1253                 for (unsigned i = 0; i <= inode->i_num_ads; i++) {
1254                         lte = inode_stream_lte_resolved(inode, i);
1255                         if (lte)
1256                                 args->progress.extract.completed_bytes +=
1257                                                         wim_resource_size(lte);
1258                 }
1259                 if (inode->i_nlink > 1) {
1260                         /* Save extracted path for a later call to
1261                          * CreateHardLinkW() if this inode has multiple links.
1262                          * */
1263                         inode->i_extracted_file = WSTRDUP(output_path);
1264                         if (!inode->i_extracted_file)
1265                                 return WIMLIB_ERR_NOMEM;
1266                 }
1267         }
1268         return 0;
1269 }
1270
1271 /* Set timestamps on an extracted file using the Win32 API */
1272 int
1273 win32_do_apply_dentry_timestamps(const wchar_t *path,
1274                                  size_t path_num_chars,
1275                                  struct wim_dentry *dentry,
1276                                  struct apply_args *args)
1277 {
1278         DWORD err;
1279         HANDLE h;
1280         const struct wim_inode *inode = dentry->d_inode;
1281
1282         if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1283             !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1284         {
1285                 /* Skip reparse points not extracted */
1286                 return 0;
1287         }
1288
1289         /* Windows doesn't let you change the timestamps of the root directory
1290          * (at least on FAT, which is dumb but expected since FAT doesn't store
1291          * any metadata about the root directory...) */
1292         if (path_is_root_of_drive(path))
1293                 return 0;
1294
1295         DEBUG("Opening \"%ls\" to set timestamps", path);
1296         h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
1297         if (h == INVALID_HANDLE_VALUE) {
1298                 err = GetLastError();
1299                 goto fail;
1300         }
1301
1302         FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1303                                  .dwHighDateTime = inode->i_creation_time >> 32};
1304         FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1305                                   .dwHighDateTime = inode->i_last_access_time >> 32};
1306         FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1307                                   .dwHighDateTime = inode->i_last_write_time >> 32};
1308
1309         DEBUG("Calling SetFileTime() on \"%ls\"", path);
1310         if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1311                 err = GetLastError();
1312                 CloseHandle(h);
1313                 goto fail;
1314         }
1315         DEBUG("Closing \"%ls\"", path);
1316         if (!CloseHandle(h)) {
1317                 err = GetLastError();
1318                 goto fail;
1319         }
1320         goto out;
1321 fail:
1322         /* Only warn if setting timestamps failed; still return 0. */
1323         WARNING("Can't set timestamps on \"%ls\"", path);
1324         win32_error(err);
1325 out:
1326         return 0;
1327 }
1328
1329 #endif /* __WIN32__ */