90051f425041cb85e43c04075d3a2de92f9131f3
[wimlib] / src / win32_apply.c
1 #ifdef __WIN32__
2
3 #include <aclapi.h> /* for SetSecurityInfo() */
4
5 #include "win32_common.h"
6 #include "wimlib_internal.h"
7 #include "dentry.h"
8 #include "lookup_table.h"
9 #include "endianness.h"
10
11 #define MAX_CREATE_HARD_LINK_WARNINGS 5
12 #define MAX_CREATE_SOFT_LINK_WARNINGS 5
13
14 #define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1
15 #define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1
16
17 static const wchar_t *apply_access_denied_msg =
18 L"If you are not running this program as the administrator, you may\n"
19  "          need to do so, so that all data and metadata can be extracted\n"
20  "          exactly as the origignal copy.  However, if you do not care that\n"
21  "          the security descriptors are extracted correctly, you could run\n"
22  "          `wimlib-imagex apply' with the --no-acls flag instead.\n"
23  ;
24
25
26 static int
27 win32_extract_try_rpfix(u8 *rpbuf,
28                         const wchar_t *extract_root_realpath,
29                         unsigned extract_root_realpath_nchars)
30 {
31         struct reparse_data rpdata;
32         wchar_t *target;
33         size_t target_nchars;
34         size_t stripped_nchars;
35         wchar_t *stripped_target;
36         wchar_t stripped_target_nchars;
37         int ret;
38
39         utf16lechar *new_target;
40         utf16lechar *new_print_name;
41         size_t new_target_nchars;
42         size_t new_print_name_nchars;
43         utf16lechar *p;
44
45         ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
46                                  &rpdata);
47         if (ret)
48                 return ret;
49
50         if (extract_root_realpath[0] == L'\0' ||
51             extract_root_realpath[1] != L':' ||
52             extract_root_realpath[2] != L'\\')
53         {
54                 ERROR("Can't understand full path format \"%ls\".  "
55                       "Try turning reparse point fixups off...",
56                       extract_root_realpath);
57                 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
58         }
59
60         ret = parse_substitute_name(rpdata.substitute_name,
61                                     rpdata.substitute_name_nbytes,
62                                     rpdata.rptag);
63         if (ret < 0)
64                 return 0;
65         stripped_nchars = ret;
66         target = rpdata.substitute_name;
67         target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar);
68         stripped_target = target + 6;
69         stripped_target_nchars = target_nchars - stripped_nchars;
70
71         new_target = alloca((6 + extract_root_realpath_nchars +
72                              stripped_target_nchars) * sizeof(utf16lechar));
73
74         p = new_target;
75         if (stripped_nchars == 6) {
76                 /* Include \??\ prefix if it was present before */
77                 wmemcpy(p, L"\\??\\", 4);
78                 p += 4;
79         }
80
81         /* Print name excludes the \??\ if present. */
82         new_print_name = p;
83         if (stripped_nchars != 0) {
84                 /* Get drive letter from real path to extract root, if a drive
85                  * letter was present before. */
86                 *p++ = extract_root_realpath[0];
87                 *p++ = extract_root_realpath[1];
88         }
89         /* Copy the rest of the extract root */
90         wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
91         p += extract_root_realpath_nchars - 2;
92
93         /* Append the stripped target */
94         wmemcpy(p, stripped_target, stripped_target_nchars);
95         p += stripped_target_nchars;
96         new_target_nchars = p - new_target;
97         new_print_name_nchars = p - new_print_name;
98
99         if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE ||
100             new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE)
101         {
102                 ERROR("Path names too long to do reparse point fixup!");
103                 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
104         }
105         rpdata.substitute_name = new_target;
106         rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar);
107         rpdata.print_name = new_print_name;
108         rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar);
109         return make_reparse_buffer(&rpdata, rpbuf);
110 }
111
112 /* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on
113  * an extracted reparse point. */
114 static int
115 win32_set_reparse_data(HANDLE h,
116                        const struct wim_inode *inode,
117                        const struct wim_lookup_table_entry *lte,
118                        const wchar_t *path,
119                        struct apply_args *args)
120 {
121         int ret;
122         u8 rpbuf[REPARSE_POINT_MAX_SIZE];
123         DWORD bytesReturned;
124
125         DEBUG("Setting reparse data on \"%ls\"", path);
126
127         ret = wim_inode_get_reparse_data(inode, rpbuf);
128         if (ret)
129                 return ret;
130
131         if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX &&
132             (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
133              inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) &&
134             !inode->i_not_rpfixed)
135         {
136                 ret = win32_extract_try_rpfix(rpbuf,
137                                               args->target_realpath,
138                                               args->target_realpath_len);
139                 if (ret)
140                         return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
141         }
142
143         /* Set the reparse data on the open file using the
144          * FSCTL_SET_REPARSE_POINT ioctl.
145          *
146          * There are contradictions in Microsoft's documentation for this:
147          *
148          * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED,
149          * lpOverlapped is ignored."
150          *
151          * --- So setting lpOverlapped to NULL is okay since it's ignored.
152          *
153          * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an
154          * operation returns no output data and lpOutBuffer is NULL,
155          * DeviceIoControl makes use of lpBytesReturned. After such an
156          * operation, the value of lpBytesReturned is meaningless."
157          *
158          * --- So lpOverlapped not really ignored, as it affects another
159          *  parameter.  This is the actual behavior: lpBytesReturned must be
160          *  specified, even though lpBytesReturned is documented as:
161          *
162          *  "Not used with this operation; set to NULL."
163          */
164         if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf,
165                              8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
166                              NULL, 0,
167                              &bytesReturned /* lpBytesReturned */,
168                              NULL /* lpOverlapped */))
169         {
170                 DWORD err = GetLastError();
171                 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
172                 {
173                         args->num_soft_links_failed++;
174                         if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) {
175                                 WARNING("Can't set reparse data on \"%ls\": Access denied!\n"
176                                         "          You may be trying to extract a symbolic "
177                                         "link without the\n"
178                                         "          SeCreateSymbolicLink privilege, which by "
179                                         "default non-Administrator\n"
180                                         "          accounts do not have.", path);
181                         }
182                         if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
183                                 WARNING("Suppressing further warnings regarding failure to extract\n"
184                                         "          reparse points due to insufficient privileges...");
185                         }
186                 } else {
187                         ERROR("Failed to set reparse data on \"%ls\"", path);
188                         win32_error(err);
189                         if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
190                             inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
191                                 return WIMLIB_ERR_LINK;
192                         else
193                                 return WIMLIB_ERR_WRITE;
194                 }
195         }
196         return 0;
197 }
198
199 /* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the
200  * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */
201 static int
202 win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path)
203 {
204         DWORD bytesReturned;
205         if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION,
206                              &format, sizeof(USHORT),
207                              NULL, 0,
208                              &bytesReturned, NULL))
209         {
210                 /* Could be a warning only, but we only call this if the volume
211                  * supports compression.  So I'm calling this an error. */
212                 DWORD err = GetLastError();
213                 ERROR("Failed to set compression flag on \"%ls\"", path);
214                 win32_error(err);
215                 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
216                         return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
217                 else
218                         return WIMLIB_ERR_WRITE;
219         }
220         return 0;
221 }
222
223 /* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */
224 static int
225 win32_set_sparse(HANDLE hFile, const wchar_t *path)
226 {
227         DWORD bytesReturned;
228         if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE,
229                              NULL, 0,
230                              NULL, 0,
231                              &bytesReturned, NULL))
232         {
233                 /* Could be a warning only, but we only call this if the volume
234                  * supports sparse files.  So I'm calling this an error. */
235                 DWORD err = GetLastError();
236                 WARNING("Failed to set sparse flag on \"%ls\"", path);
237                 win32_error(err);
238                 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
239                         return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
240                 else
241                         return WIMLIB_ERR_WRITE;
242         }
243         return 0;
244 }
245
246 /*
247  * Sets the security descriptor on an extracted file.
248  */
249 static int
250 win32_set_security_data(const struct wim_inode *inode,
251                         HANDLE hFile,
252                         const wchar_t *path,
253                         struct apply_args *args)
254 {
255         PSECURITY_DESCRIPTOR descriptor;
256         unsigned long n;
257         DWORD err;
258         const struct wim_security_data *sd;
259
260         SECURITY_INFORMATION securityInformation = 0;
261
262         void *owner = NULL;
263         void *group = NULL;
264         ACL *dacl = NULL;
265         ACL *sacl = NULL;
266
267         BOOL owner_defaulted;
268         BOOL group_defaulted;
269         BOOL dacl_present;
270         BOOL dacl_defaulted;
271         BOOL sacl_present;
272         BOOL sacl_defaulted;
273
274         sd = wim_const_security_data(args->w);
275         descriptor = sd->descriptors[inode->i_security_id];
276
277         GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted);
278         if (owner)
279                 securityInformation |= OWNER_SECURITY_INFORMATION;
280
281         GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted);
282         if (group)
283                 securityInformation |= GROUP_SECURITY_INFORMATION;
284
285         GetSecurityDescriptorDacl(descriptor, &dacl_present,
286                                   &dacl, &dacl_defaulted);
287         if (dacl)
288                 securityInformation |= DACL_SECURITY_INFORMATION;
289
290         GetSecurityDescriptorSacl(descriptor, &sacl_present,
291                                   &sacl, &sacl_defaulted);
292         if (sacl)
293                 securityInformation |= SACL_SECURITY_INFORMATION;
294
295 again:
296         if (securityInformation == 0)
297                 return 0;
298         if (SetSecurityInfo(hFile, SE_FILE_OBJECT,
299                             securityInformation, owner, group, dacl, sacl))
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                         sacl = NULL;
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 bool
452 path_is_root_of_drive(const wchar_t *path)
453 {
454         if (!*path)
455                 return false;
456
457         if (*path != L'/' && *path != L'\\') {
458                 if (*(path + 1) == L':')
459                         path += 2;
460                 else
461                         return false;
462         }
463         while (*path == L'/' || *path == L'\\')
464                 path++;
465         return (*path == L'\0');
466 }
467
468 static inline DWORD
469 win32_mask_attributes(DWORD i_attributes)
470 {
471         return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE |
472                                 FILE_ATTRIBUTE_COMPRESSED |
473                                 FILE_ATTRIBUTE_REPARSE_POINT |
474                                 FILE_ATTRIBUTE_DIRECTORY |
475                                 FILE_ATTRIBUTE_ENCRYPTED |
476                                 FILE_FLAG_DELETE_ON_CLOSE |
477                                 FILE_FLAG_NO_BUFFERING |
478                                 FILE_FLAG_OPEN_NO_RECALL |
479                                 FILE_FLAG_OVERLAPPED |
480                                 FILE_FLAG_RANDOM_ACCESS |
481                                 /*FILE_FLAG_SESSION_AWARE |*/
482                                 FILE_FLAG_SEQUENTIAL_SCAN |
483                                 FILE_FLAG_WRITE_THROUGH);
484 }
485
486 static inline DWORD
487 win32_get_create_flags_and_attributes(DWORD i_attributes)
488 {
489         /*
490          * Some attributes cannot be set by passing them to CreateFile().  In
491          * particular:
492          *
493          * FILE_ATTRIBUTE_DIRECTORY:
494          *   CreateDirectory() must be called instead of CreateFile().
495          *
496          * FILE_ATTRIBUTE_SPARSE_FILE:
497          *   Needs an ioctl.
498          *   See: win32_set_sparse().
499          *
500          * FILE_ATTRIBUTE_COMPRESSED:
501          *   Not clear from the documentation, but apparently this needs an
502          *   ioctl as well.
503          *   See: win32_set_compressed().
504          *
505          * FILE_ATTRIBUTE_REPARSE_POINT:
506          *   Needs an ioctl, with the reparse data specified.
507          *   See: win32_set_reparse_data().
508          *
509          * In addition, clear any file flags in the attributes that we don't
510          * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and
511          * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application.
512          */
513         return win32_mask_attributes(i_attributes) |
514                 FILE_FLAG_OPEN_REPARSE_POINT |
515                 FILE_FLAG_BACKUP_SEMANTICS;
516 }
517
518 /* Set compression and/or sparse attributes on a stream, if supported by the
519  * volume. */
520 static int
521 win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode,
522                                     struct wim_lookup_table_entry *unnamed_stream_lte,
523                                     const wchar_t *path, unsigned vol_flags)
524 {
525         int ret;
526
527         if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) {
528                 if (vol_flags & FILE_FILE_COMPRESSION) {
529                         ret = win32_set_compression_state(hFile,
530                                                           COMPRESSION_FORMAT_DEFAULT,
531                                                           path);
532                         if (ret)
533                                 return ret;
534                 } else {
535                         DEBUG("Cannot set compression attribute on \"%ls\": "
536                               "volume does not support transparent compression",
537                               path);
538                 }
539         }
540
541         if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
542                 if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) {
543                         DEBUG("Setting sparse flag on \"%ls\"", path);
544                         ret = win32_set_sparse(hFile, path);
545                         if (ret)
546                                 return ret;
547                 } else {
548                         DEBUG("Cannot set sparse attribute on \"%ls\": "
549                               "volume does not support sparse files",
550                               path);
551                 }
552         }
553         return 0;
554 }
555
556 /* Pre-create directories; extract encrypted streams */
557 static int
558 win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
559                                    const struct wim_lookup_table_entry *lte,
560                                    const wchar_t *path,
561                                    DWORD *creationDisposition_ret,
562                                    unsigned int vol_flags)
563 {
564         DWORD err;
565         int ret;
566
567         /* Directories must be created with CreateDirectoryW().  Then the call
568          * to CreateFileW() will merely open the directory that was already
569          * created rather than creating a new file. */
570         if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY &&
571             !path_is_root_of_drive(path)) {
572                 if (!CreateDirectoryW(path, NULL)) {
573                         err = GetLastError();
574                         if (err != ERROR_ALREADY_EXISTS) {
575                                 ERROR("Failed to create directory \"%ls\"",
576                                       path);
577                                 win32_error(err);
578                                 return WIMLIB_ERR_MKDIR;
579                         }
580                 }
581                 DEBUG("Created directory \"%ls\"", path);
582                 *creationDisposition_ret = OPEN_EXISTING;
583         }
584         if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
585             vol_flags & FILE_SUPPORTS_ENCRYPTION)
586         {
587                 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
588                         unsigned remaining_sharing_violations = 100;
589                         while (!EncryptFile(path)) {
590                                 if (remaining_sharing_violations &&
591                                     err == ERROR_SHARING_VIOLATION)
592                                 {
593                                         WARNING("Couldn't encrypt directory \"%ls\" "
594                                                 "due to sharing violation; re-trying "
595                                                 "after 100 ms", path);
596                                         Sleep(100);
597                                         remaining_sharing_violations--;
598                                 } else {
599                                         err = GetLastError();
600                                         ERROR("Failed to encrypt directory \"%ls\"",
601                                               path);
602                                         win32_error(err);
603                                         return WIMLIB_ERR_WRITE;
604                                 }
605                         }
606                 } else {
607                         ret = do_win32_extract_encrypted_stream(path, lte);
608                         if (ret)
609                                 return ret;
610                         DEBUG("Extracted encrypted file \"%ls\"", path);
611                 }
612                 *creationDisposition_ret = OPEN_EXISTING;
613         }
614
615         /* Set file attributes if we created the file.  Otherwise, we haven't
616          * created the file set and we will set the attributes in the call to
617          * CreateFileW().
618          *
619          * The FAT filesystem does not let you change the attributes of the root
620          * directory, so treat that as a special case and do not set attributes.
621          * */
622         if (*creationDisposition_ret == OPEN_EXISTING &&
623             !path_is_root_of_drive(path))
624         {
625                 if (!SetFileAttributesW(path,
626                                         win32_mask_attributes(inode->i_attributes)))
627                 {
628                         err = GetLastError();
629                         ERROR("Failed to set attributes on \"%ls\"", path);
630                         win32_error(err);
631                         return WIMLIB_ERR_WRITE;
632                 }
633         }
634         return 0;
635 }
636
637 /* Set security descriptor and extract stream data or reparse data (skip the
638  * unnamed data stream of encrypted files, which was already extracted). */
639 static int
640 win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry,
641                             const struct wim_lookup_table_entry *lte,
642                             const wchar_t *stream_path,
643                             const wchar_t *stream_name_utf16,
644                             struct apply_args *args)
645 {
646         int ret = 0;
647         const struct wim_inode *inode = dentry->d_inode;
648         const wchar_t *short_name;
649         if (stream_name_utf16 == NULL) {
650                 /* Unnamed stream. */
651
652                 /* Set security descriptor, unless the extract_flags indicate
653                  * not to or the volume does not supported it.  Note that this
654                  * is only done when the unnamed stream is being extracted, as
655                  * security descriptors are per-file and not per-stream. */
656                 if (inode->i_security_id >= 0 &&
657                     !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
658                     && (args->vol_flags & FILE_PERSISTENT_ACLS))
659                 {
660                         ret = win32_set_security_data(inode, h, stream_path, args);
661                         if (ret)
662                                 return ret;
663                 }
664
665                 /* Handle reparse points.  The data for them needs to be set
666                  * using a special ioctl.  Note that the reparse point may have
667                  * been created using CreateFileW() in the case of
668                  * non-directories or CreateDirectoryW() in the case of
669                  * directories; but the ioctl works either way.  Also, it is
670                  * only this step that actually sets the
671                  * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it
672                  * using SetFileAttributesW() or CreateFileW().
673                  *
674                  * If the volume does not support reparse points we simply
675                  * ignore the reparse data.  (N.B. the code currently doesn't
676                  * actually reach this case because reparse points are skipped
677                  * entirely on such volumes.) */
678                 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
679                         if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
680                                 ret = win32_set_reparse_data(h, inode,
681                                                              lte, stream_path,
682                                                              args);
683                                 if (ret)
684                                         return ret;
685                         } else {
686                                 DEBUG("Cannot set reparse data on \"%ls\": volume "
687                                       "does not support reparse points", stream_path);
688                         }
689                 } else if (lte != NULL &&
690                            !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION &&
691                              inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
692                 {
693                         /* Extract the data of the unnamed stream, unless the
694                          * lookup table entry is NULL (indicating an empty
695                          * stream for which no data needs to be extracted), or
696                          * the stream is encrypted and therefore was already
697                          * extracted as a special case. */
698                         ret = do_win32_extract_stream(h, lte);
699                         if (ret)
700                                 return ret;
701                 }
702
703                 if (dentry_has_short_name(dentry))
704                         short_name = dentry->short_name;
705                 else
706                         short_name = L"";
707                 /* Set short name */
708                 if (!SetFileShortNameW(h, short_name)) {
709                 #if 0
710                         DWORD err = GetLastError();
711                         ERROR("Could not set short name on \"%ls\"", stream_path);
712                         win32_error(err);
713                 #endif
714                 }
715         } else {
716                 /* Extract the data for a named data stream. */
717                 if (lte != NULL) {
718                         DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")",
719                               stream_path, wim_resource_size(lte));
720                         ret = do_win32_extract_stream(h, lte);
721                 }
722         }
723         return ret;
724 }
725
726 static int
727 win32_decrypt_file(HANDLE open_handle, const wchar_t *path)
728 {
729         DWORD err;
730         /* We cannot call DecryptFileW() while there is an open handle to the
731          * file.  So close it first. */
732         if (!CloseHandle(open_handle)) {
733                 err = GetLastError();
734                 ERROR("Failed to close handle for \"%ls\"", path);
735                 win32_error(err);
736                 return WIMLIB_ERR_WRITE;
737         }
738         if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) {
739                 err = GetLastError();
740                 ERROR("Failed to decrypt file \"%ls\"", path);
741                 win32_error(err);
742                 return WIMLIB_ERR_WRITE;
743         }
744         return 0;
745 }
746
747 /*
748  * Create and extract a stream to a file, or create a directory, using the
749  * Windows API.
750  *
751  * This handles reparse points, directories, alternate data streams, encrypted
752  * files, compressed files, etc.
753  *
754  * @dentry: WIM dentry for the file or directory being extracted.
755  *
756  * @path:  Path to extract the file to.
757  *
758  * @stream_name_utf16:
759  *         Name of the stream, or NULL if the stream is unnamed.  This will
760  *         be called with a NULL stream_name_utf16 before any non-NULL
761  *         stream_name_utf16's.
762  *
763  * @lte:   WIM lookup table entry for the stream.  May be NULL to indicate
764  *         a stream of length 0.
765  *
766  * @args:  Additional apply context, including flags indicating supported
767  *         volume features.
768  *
769  * Returns 0 on success; nonzero on failure.
770  */
771 static int
772 win32_extract_stream(const struct wim_dentry *dentry,
773                      const wchar_t *path,
774                      const wchar_t *stream_name_utf16,
775                      struct wim_lookup_table_entry *lte,
776                      struct apply_args *args)
777 {
778         wchar_t *stream_path;
779         HANDLE h;
780         int ret;
781         DWORD err;
782         DWORD creationDisposition = CREATE_ALWAYS;
783         DWORD requestedAccess;
784         BY_HANDLE_FILE_INFORMATION file_info;
785         unsigned remaining_sharing_violations = 1000;
786         const struct wim_inode *inode = dentry->d_inode;
787
788         if (stream_name_utf16) {
789                 /* Named stream.  Create a buffer that contains the UTF-16LE
790                  * string [./]path:stream_name_utf16.  This is needed to
791                  * create and open the stream using CreateFileW().  I'm not
792                  * aware of any other APIs to do this.  Note: the '$DATA' suffix
793                  * seems to be unneeded.  Additional note: a "./" prefix needs
794                  * to be added when the path is not absolute to avoid ambiguity
795                  * with drive letters. */
796                 size_t stream_path_nchars;
797                 size_t path_nchars;
798                 size_t stream_name_nchars;
799                 const wchar_t *prefix;
800
801                 path_nchars = wcslen(path);
802                 stream_name_nchars = wcslen(stream_name_utf16);
803                 stream_path_nchars = path_nchars + 1 + stream_name_nchars;
804                 if (path[0] != cpu_to_le16(L'\0') &&
805                     path[0] != cpu_to_le16(L'/') &&
806                     path[0] != cpu_to_le16(L'\\') &&
807                     path[1] != cpu_to_le16(L':'))
808                 {
809                         prefix = L"./";
810                         stream_path_nchars += 2;
811                 } else {
812                         prefix = L"";
813                 }
814                 stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
815                 swprintf(stream_path, L"%ls%ls:%ls",
816                          prefix, path, stream_name_utf16);
817         } else {
818                 /* Unnamed stream; its path is just the path to the file itself.
819                  * */
820                 stream_path = (wchar_t*)path;
821
822                 ret = win32_begin_extract_unnamed_stream(inode, lte, path,
823                                                          &creationDisposition,
824                                                          args->vol_flags);
825                 if (ret)
826                         goto fail;
827         }
828
829         DEBUG("Opening \"%ls\"", stream_path);
830         /* DELETE access is needed for SetFileShortNameW(), for some reason. */
831         requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE |
832                           ACCESS_SYSTEM_SECURITY;
833 try_open_again:
834         /* Open the stream to be extracted.  Depending on what we have set
835          * creationDisposition to, we may be creating this for the first time,
836          * or we may be opening on existing stream we already created using
837          * CreateDirectoryW() or OpenEncryptedFileRawW(). */
838         h = CreateFileW(stream_path,
839                         requestedAccess,
840                         FILE_SHARE_READ,
841                         NULL,
842                         creationDisposition,
843                         win32_get_create_flags_and_attributes(inode->i_attributes),
844                         NULL);
845         if (h == INVALID_HANDLE_VALUE) {
846                 err = GetLastError();
847                 if (err == ERROR_ACCESS_DENIED &&
848                     path_is_root_of_drive(stream_path))
849                 {
850                         ret = 0;
851                         goto out;
852                 }
853                 if ((err == ERROR_PRIVILEGE_NOT_HELD ||
854                      err == ERROR_ACCESS_DENIED) &&
855                     (requestedAccess & ACCESS_SYSTEM_SECURITY))
856                 {
857                         /* Try opening the file again without privilege to
858                          * modify SACL. */
859                         requestedAccess &= ~ACCESS_SYSTEM_SECURITY;
860                         goto try_open_again;
861                 }
862                 if (err == ERROR_SHARING_VIOLATION) {
863                         if (remaining_sharing_violations) {
864                                 --remaining_sharing_violations;
865                                 /* This can happen when restoring encrypted directories
866                                  * for some reason.  Probably a bug in EncryptFile(). */
867                                 WARNING("Couldn't open \"%ls\" due to sharing violation; "
868                                         "re-trying after 100ms", stream_path);
869                                 Sleep(100);
870                                 goto try_open_again;
871                         } else {
872                                 ERROR("Too many sharing violations; giving up...");
873                         }
874                 } else {
875                         if (creationDisposition == OPEN_EXISTING)
876                                 ERROR("Failed to open \"%ls\"", stream_path);
877                         else
878                                 ERROR("Failed to create \"%ls\"", stream_path);
879                         win32_error(err);
880                 }
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 /* If not done already, load the supported feature flags for the volume onto
1023  * which the image is being extracted, and warn the user about any missing
1024  * features that could be important. */
1025 static int
1026 win32_check_vol_flags(const wchar_t *output_path, struct apply_args *args)
1027 {
1028         if (args->have_vol_flags)
1029                 return 0;
1030
1031         win32_get_vol_flags(output_path, &args->vol_flags);
1032         args->have_vol_flags = true;
1033         /* Warn the user about data that may not be extracted. */
1034         if (!(args->vol_flags & FILE_SUPPORTS_SPARSE_FILES))
1035                 WARNING("Volume does not support sparse files!\n"
1036                         "          Sparse files will be extracted as non-sparse.");
1037         if (!(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1038                 WARNING("Volume does not support reparse points!\n"
1039                         "          Reparse point data will not be extracted.");
1040         if (!(args->vol_flags & FILE_NAMED_STREAMS)) {
1041                 WARNING("Volume does not support named data streams!\n"
1042                         "          Named data streams will not be extracted.");
1043         }
1044         if (!(args->vol_flags & FILE_SUPPORTS_ENCRYPTION)) {
1045                 WARNING("Volume does not support encryption!\n"
1046                         "          Encrypted files will be extracted as raw data.");
1047         }
1048         if (!(args->vol_flags & FILE_FILE_COMPRESSION)) {
1049                 WARNING("Volume does not support transparent compression!\n"
1050                         "          Compressed files will be extracted as non-compressed.");
1051         }
1052         if (!(args->vol_flags & FILE_PERSISTENT_ACLS)) {
1053                 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
1054                         ERROR("Strict ACLs requested, but the volume does not "
1055                               "support ACLs!");
1056                         return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
1057                 } else {
1058                         WARNING("Volume does not support persistent ACLS!\n"
1059                                 "          File permissions will not be extracted.");
1060                 }
1061         }
1062         return 0;
1063 }
1064
1065 /*
1066  * Try extracting a hard link.
1067  *
1068  * @output_path:  Path to link to be extracted.
1069  *
1070  * @inode:        WIM inode that the link is to; inode->i_extracted_file
1071  *                the path to a name of the file that has already been
1072  *                extracted (we use this to create the hard link).
1073  *
1074  * @args:         Additional apply context, used here to keep track of
1075  *                the number of times creating a hard link failed due to
1076  *                ERROR_INVALID_FUNCTION.  This error should indicate that hard
1077  *                links are not supported by the volume, and we would like to
1078  *                warn the user a few times, but not too many times.
1079  *
1080  * Returns 0 if the hard link was successfully extracted.  Returns
1081  * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly
1082  * being unsupported by the volume.  Returns a negative value if creating the
1083  * hard link failed due to ERROR_INVALID_FUNCTION.
1084  */
1085 static int
1086 win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
1087                     struct apply_args *args)
1088 {
1089         DWORD err;
1090
1091         /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
1092          * but it's only available on Windows 7 and later.  So no use
1093          * even checking it, really.  Instead, CreateHardLinkW() will
1094          * apparently return ERROR_INVALID_FUNCTION if the volume does
1095          * not support hard links. */
1096         DEBUG("Creating hard link \"%ls => %ls\"",
1097               output_path, inode->i_extracted_file);
1098         if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
1099                 return 0;
1100
1101         err = GetLastError();
1102         if (err != ERROR_INVALID_FUNCTION) {
1103                 ERROR("Can't create hard link \"%ls => %ls\"",
1104                       output_path, inode->i_extracted_file);
1105                 win32_error(err);
1106                 return WIMLIB_ERR_LINK;
1107         } else {
1108                 args->num_hard_links_failed++;
1109                 if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
1110                         WARNING("Can't create hard link \"%ls => %ls\":\n"
1111                                 "          Volume does not support hard links!\n"
1112                                 "          Falling back to extracting a copy of the file.",
1113                                 output_path, inode->i_extracted_file);
1114                 }
1115                 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
1116                         WARNING("Suppressing further hard linking warnings...");
1117                 }
1118                 return -1;
1119         }
1120 }
1121
1122 /* Extract a file, directory, reparse point, or hard link to an
1123  * already-extracted file using the Win32 API */
1124 int
1125 win32_do_apply_dentry(const wchar_t *output_path,
1126                       size_t output_path_num_chars,
1127                       struct wim_dentry *dentry,
1128                       struct apply_args *args)
1129 {
1130         int ret;
1131         struct wim_inode *inode = dentry->d_inode;
1132
1133         ret = win32_check_vol_flags(output_path, args);
1134         if (ret)
1135                 return ret;
1136         if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1137                 /* Linked file, with another name already extracted.  Create a
1138                  * hard link. */
1139                 ret = win32_try_hard_link(output_path, inode, args);
1140                 if (ret >= 0)
1141                         return ret;
1142                 /* Negative return value from win32_try_hard_link() indicates
1143                  * that hard links are probably not supported by the volume.
1144                  * Fall back to extracting a copy of the file. */
1145         }
1146
1147         /* If this is a reparse point and the volume does not support reparse
1148          * points, just skip it completely. */
1149         if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1150             !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1151         {
1152                 WARNING("Skipping extraction of reparse point \"%ls\":\n"
1153                         "          Not supported by destination filesystem",
1154                         output_path);
1155         } else {
1156                 /* Create the file, directory, or reparse point, and extract the
1157                  * data streams. */
1158                 ret = win32_extract_streams(dentry, output_path, args);
1159                 if (ret)
1160                         return ret;
1161         }
1162         if (inode->i_extracted_file == NULL) {
1163                 const struct wim_lookup_table_entry *lte;
1164
1165                 /* Tally bytes extracted, including all alternate data streams,
1166                  * unless we extracted a hard link (or, at least extracted a
1167                  * name that was supposed to be a hard link) */
1168                 for (unsigned i = 0; i <= inode->i_num_ads; i++) {
1169                         lte = inode_stream_lte_resolved(inode, i);
1170                         if (lte)
1171                                 args->progress.extract.completed_bytes +=
1172                                                         wim_resource_size(lte);
1173                 }
1174                 if (inode->i_nlink > 1) {
1175                         /* Save extracted path for a later call to
1176                          * CreateHardLinkW() if this inode has multiple links.
1177                          * */
1178                         inode->i_extracted_file = WSTRDUP(output_path);
1179                         if (!inode->i_extracted_file)
1180                                 return WIMLIB_ERR_NOMEM;
1181                 }
1182         }
1183         return 0;
1184 }
1185
1186 /* Set timestamps on an extracted file using the Win32 API */
1187 int
1188 win32_do_apply_dentry_timestamps(const wchar_t *path,
1189                                  size_t path_num_chars,
1190                                  struct wim_dentry *dentry,
1191                                  struct apply_args *args)
1192 {
1193         DWORD err;
1194         HANDLE h;
1195         const struct wim_inode *inode = dentry->d_inode;
1196
1197         if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1198             !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1199         {
1200                 /* Skip reparse points not extracted */
1201                 return 0;
1202         }
1203
1204         /* Windows doesn't let you change the timestamps of the root directory
1205          * (at least on FAT, which is dumb but expected since FAT doesn't store
1206          * any metadata about the root directory...) */
1207         if (path_is_root_of_drive(path))
1208                 return 0;
1209
1210         DEBUG("Opening \"%ls\" to set timestamps", path);
1211         h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
1212         if (h == INVALID_HANDLE_VALUE) {
1213                 err = GetLastError();
1214                 goto fail;
1215         }
1216
1217         FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1218                                  .dwHighDateTime = inode->i_creation_time >> 32};
1219         FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1220                                   .dwHighDateTime = inode->i_last_access_time >> 32};
1221         FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1222                                   .dwHighDateTime = inode->i_last_write_time >> 32};
1223
1224         DEBUG("Calling SetFileTime() on \"%ls\"", path);
1225         if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1226                 err = GetLastError();
1227                 CloseHandle(h);
1228                 goto fail;
1229         }
1230         DEBUG("Closing \"%ls\"", path);
1231         if (!CloseHandle(h)) {
1232                 err = GetLastError();
1233                 goto fail;
1234         }
1235         goto out;
1236 fail:
1237         /* Only warn if setting timestamps failed; still return 0. */
1238         WARNING("Can't set timestamps on \"%ls\"", path);
1239         win32_error(err);
1240 out:
1241         return 0;
1242 }
1243
1244 #endif /* __WIN32__ */