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