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