2 * win32_apply.c - Windows-specific code for applying files from a WIM image.
6 * Copyright (C) 2013 Eric Biggers
8 * This file is part of wimlib, a library for working with WIM files.
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)
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
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/.
30 #include "wimlib/win32_common.h"
32 #include "wimlib/apply.h"
33 #include "wimlib/capture.h"
34 #include "wimlib/dentry.h"
35 #include "wimlib/error.h"
36 #include "wimlib/lookup_table.h"
37 #include "wimlib/paths.h"
38 #include "wimlib/textfile.h"
39 #include "wimlib/xml.h"
40 #include "wimlib/wim.h"
41 #include "wimlib/wimboot.h"
44 ctx_save_data_source_id(struct apply_ctx *ctx, u64 data_source_id)
46 ctx->private[0] = data_source_id & 0xFFFFFFFF;
47 ctx->private[1] = data_source_id >> 32;
51 ctx_get_data_source_id(const struct apply_ctx *ctx)
53 return (u32)ctx->private[0] | ((u64)(u32)ctx->private[1] << 32);
57 set_prepopulate_pats(struct apply_ctx *ctx, struct string_set *s)
59 ctx->private[2] = (intptr_t)s;
62 static struct string_set *
63 get_prepopulate_pats(struct apply_ctx *ctx)
65 return (struct string_set *)(ctx->private[2]);
69 free_prepopulate_pats(struct apply_ctx *ctx)
73 s = get_prepopulate_pats(ctx);
78 set_prepopulate_pats(ctx, NULL);
80 FREE((void *)ctx->private[3]);
81 ctx->private[3] = (intptr_t)NULL;
85 load_prepopulate_pats(struct apply_ctx *ctx)
88 struct wim_dentry *dentry;
89 struct wim_lookup_table_entry *lte;
91 const tchar *path = WIMLIB_WIM_PATH_SEPARATOR_STRING T("Windows")
92 WIMLIB_WIM_PATH_SEPARATOR_STRING T("System32")
93 WIMLIB_WIM_PATH_SEPARATOR_STRING T("WimBootCompress.ini");
96 struct text_file_section sec;
98 dentry = get_dentry(ctx->wim, path, WIMLIB_CASE_INSENSITIVE);
100 (dentry->d_inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
101 FILE_ATTRIBUTE_REPARSE_POINT |
102 FILE_ATTRIBUTE_ENCRYPTED)) ||
103 !(lte = inode_unnamed_lte(dentry->d_inode, ctx->wim->lookup_table)))
105 WARNING("%"TS" does not exist in WIM image!", path);
106 return WIMLIB_ERR_PATH_DOES_NOT_EXIST;
109 ret = read_full_stream_into_alloc_buf(lte, &buf);
113 s = CALLOC(1, sizeof(struct string_set));
116 return WIMLIB_ERR_NOMEM;
119 sec.name = T("PrepopulateList");
122 ret = do_load_text_file(path, buf, lte->size, &mem, &sec, 1,
123 LOAD_TEXT_FILE_REMOVE_QUOTES |
124 LOAD_TEXT_FILE_NO_WARNINGS,
131 set_prepopulate_pats(ctx, s);
132 ctx->private[3] = (intptr_t)mem;
137 in_prepopulate_list(struct wim_dentry *dentry,
138 struct apply_ctx *ctx)
140 struct string_set *pats;
143 pats = get_prepopulate_pats(ctx);
146 path = dentry_full_path(dentry);
150 return match_pattern(path, path_basename(path), pats);
154 win32_start_extract(const wchar_t *path, struct apply_ctx *ctx)
158 bool supports_SetFileShortName;
160 ret = win32_get_vol_flags(path, &vol_flags, &supports_SetFileShortName);
164 ctx->supported_features.archive_files = 1;
165 ctx->supported_features.hidden_files = 1;
166 ctx->supported_features.system_files = 1;
168 if (vol_flags & FILE_FILE_COMPRESSION)
169 ctx->supported_features.compressed_files = 1;
171 if (vol_flags & FILE_SUPPORTS_ENCRYPTION) {
172 ctx->supported_features.encrypted_files = 1;
173 ctx->supported_features.encrypted_directories = 1;
176 ctx->supported_features.not_context_indexed_files = 1;
178 if (vol_flags & FILE_SUPPORTS_SPARSE_FILES)
179 ctx->supported_features.sparse_files = 1;
181 if (vol_flags & FILE_NAMED_STREAMS)
182 ctx->supported_features.named_data_streams = 1;
184 if (vol_flags & FILE_SUPPORTS_HARD_LINKS)
185 ctx->supported_features.hard_links = 1;
187 if (vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
188 ctx->supported_features.reparse_points = 1;
189 if (win32func_CreateSymbolicLinkW)
190 ctx->supported_features.symlink_reparse_points = 1;
193 if (vol_flags & FILE_PERSISTENT_ACLS)
194 ctx->supported_features.security_descriptors = 1;
196 if (supports_SetFileShortName)
197 ctx->supported_features.short_names = 1;
199 if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) {
201 ret = load_prepopulate_pats(ctx);
202 if (ret == WIMLIB_ERR_NOMEM)
207 if (!wim_info_get_wimboot(ctx->wim->wim_info,
208 ctx->wim->current_image))
209 WARNING("Image is not marked as WIMBoot compatible!");
211 ret = wimboot_alloc_data_source_id(ctx->wim->filename,
212 ctx->wim->current_image,
213 path, &data_source_id);
215 free_prepopulate_pats(ctx);
219 ctx_save_data_source_id(ctx, data_source_id);
226 win32_finish_extract(struct apply_ctx *ctx)
228 free_prepopulate_pats(ctx);
232 /* Delete a non-directory file, working around Windows quirks. */
234 win32_delete_file_wrapper(const wchar_t *path)
239 if (DeleteFile(path))
242 err = GetLastError();
243 attrib = GetFileAttributes(path);
244 if ((attrib != INVALID_FILE_ATTRIBUTES) &&
245 (attrib & FILE_ATTRIBUTE_READONLY))
247 /* Try again with FILE_ATTRIBUTE_READONLY cleared. */
248 attrib &= ~FILE_ATTRIBUTE_READONLY;
249 if (SetFileAttributes(path, attrib)) {
250 if (DeleteFile(path))
253 err = GetLastError();
262 /* Create a normal file, overwriting one already present. */
264 win32_create_file(const wchar_t *path, struct apply_ctx *ctx, u64 *cookie_ret)
270 * WRITE_OWNER and WRITE_DAC privileges are required for some reason,
271 * even through we're creating a new file.
273 * FILE_FLAG_OPEN_REPARSE_POINT is required to prevent an existing
274 * reparse point from redirecting the creation of the new file
275 * (potentially to an arbitrary location).
277 * CREATE_ALWAYS could be used instead of CREATE_NEW. However, there
278 * are quirks that would need to be handled (e.g. having to set
279 * FILE_ATTRIBUTE_HIDDEN and/or FILE_ATTRIBUTE_SYSTEM if the existing
280 * file had them specified, and/or having to clear
281 * FILE_ATTRIBUTE_READONLY on the existing file). It's simpler to just
282 * call win32_delete_file_wrapper() to delete the existing file in such
283 * a way that already handles the FILE_ATTRIBUTE_READONLY quirk.
286 h = CreateFile(path, WRITE_OWNER | WRITE_DAC, 0, NULL, CREATE_NEW,
287 FILE_FLAG_BACKUP_SEMANTICS |
288 FILE_FLAG_OPEN_REPARSE_POINT, NULL);
289 if (h == INVALID_HANDLE_VALUE) {
290 DWORD err = GetLastError();
292 if (err == ERROR_FILE_EXISTS && win32_delete_file_wrapper(path))
294 set_errno_from_win32_error(err);
295 return WIMLIB_ERR_OPEN;
302 win32_create_directory(const wchar_t *path, struct apply_ctx *ctx,
305 if (!CreateDirectory(path, NULL))
306 if (GetLastError() != ERROR_ALREADY_EXISTS)
311 set_errno_from_GetLastError();
312 return WIMLIB_ERR_MKDIR;
316 win32_create_hardlink(const wchar_t *oldpath, const wchar_t *newpath,
317 struct apply_ctx *ctx)
319 if (!CreateHardLink(newpath, oldpath, NULL)) {
320 if (GetLastError() != ERROR_ALREADY_EXISTS)
322 if (!win32_delete_file_wrapper(newpath))
324 if (!CreateHardLink(newpath, oldpath, NULL))
330 set_errno_from_GetLastError();
331 return WIMLIB_ERR_LINK;
335 win32_create_symlink(const wchar_t *oldpath, const wchar_t *newpath,
336 struct apply_ctx *ctx)
338 if (!(*win32func_CreateSymbolicLinkW)(newpath, oldpath, 0)) {
339 if (GetLastError() != ERROR_ALREADY_EXISTS)
341 if (!win32_delete_file_wrapper(newpath))
343 if (!(*win32func_CreateSymbolicLinkW)(newpath, oldpath, 0))
349 set_errno_from_GetLastError();
350 return WIMLIB_ERR_LINK;
354 win32_extract_wim_chunk(const void *buf, size_t len, void *arg)
356 HANDLE h = (HANDLE)arg;
357 DWORD nbytes_written;
359 if (unlikely(!WriteFile(h, buf, len, &nbytes_written, NULL)))
361 if (unlikely(nbytes_written != len))
366 set_errno_from_GetLastError();
367 return WIMLIB_ERR_WRITE;
371 win32_extract_stream(const wchar_t *path, const wchar_t *stream_name,
372 size_t stream_name_nchars,
373 struct wim_lookup_table_entry *lte, struct apply_ctx *ctx)
375 DWORD creationDisposition = OPEN_EXISTING;
376 wchar_t *stream_path = (wchar_t*)path;
380 if (stream_name_nchars) {
381 creationDisposition = CREATE_ALWAYS;
382 stream_path = alloca(sizeof(wchar_t) *
384 wcslen(stream_name) + 1));
385 tsprintf(stream_path, L"%ls:%ls", path, stream_name);
388 h = CreateFile(stream_path, FILE_WRITE_DATA, 0, NULL,
389 creationDisposition, FILE_FLAG_BACKUP_SEMANTICS |
390 FILE_FLAG_OPEN_REPARSE_POINT,
392 if (h == INVALID_HANDLE_VALUE)
397 goto out_close_handle;
398 ret = extract_stream(lte, lte->size, win32_extract_wim_chunk, h);
407 set_errno_from_GetLastError();
408 return WIMLIB_ERR_WRITE;
412 win32_extract_unnamed_stream(file_spec_t file,
413 struct wim_lookup_table_entry *lte,
414 struct apply_ctx *ctx,
415 struct wim_dentry *dentry)
417 if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT
419 && lte->resource_location == RESOURCE_IN_WIM
420 && lte->rspec->wim == ctx->wim
421 && !in_prepopulate_list(dentry, ctx))
423 return wimboot_set_pointer(file.path,
424 ctx_get_data_source_id(ctx),
428 return win32_extract_stream(file.path, NULL, 0, lte, ctx);
432 win32_extract_named_stream(file_spec_t file, const wchar_t *stream_name,
433 size_t stream_name_nchars,
434 struct wim_lookup_table_entry *lte, struct apply_ctx *ctx)
436 return win32_extract_stream(file.path, stream_name,
437 stream_name_nchars, lte, ctx);
440 struct win32_encrypted_extract_ctx {
441 const struct wim_lookup_table_entry *lte;
446 win32_encrypted_import_cb(unsigned char *data, void *_import_ctx,
447 unsigned long *len_p)
449 struct win32_encrypted_extract_ctx *import_ctx = _import_ctx;
450 unsigned long len = *len_p;
451 const struct wim_lookup_table_entry *lte = import_ctx->lte;
453 len = min(len, lte->size - import_ctx->offset);
455 if (read_partial_wim_stream_into_buf(lte, len, import_ctx->offset, data))
456 return ERROR_READ_FAULT;
458 import_ctx->offset += len;
460 return ERROR_SUCCESS;
464 win32_extract_encrypted_stream(const wchar_t *path,
465 struct wim_lookup_table_entry *lte,
466 struct apply_ctx *ctx)
471 struct win32_encrypted_extract_ctx extract_ctx;
473 err = OpenEncryptedFileRaw(path, CREATE_FOR_IMPORT, &file_ctx);
474 if (err != ERROR_SUCCESS) {
475 set_errno_from_win32_error(err);
476 ret = WIMLIB_ERR_OPEN;
480 extract_ctx.lte = lte;
481 extract_ctx.offset = 0;
482 err = WriteEncryptedFileRaw(win32_encrypted_import_cb, &extract_ctx,
484 if (err != ERROR_SUCCESS) {
485 set_errno_from_win32_error(err);
486 ret = WIMLIB_ERR_WRITE;
492 CloseEncryptedFileRaw(file_ctx);
498 win32_set_special_file_attributes(const wchar_t *path, u32 attributes)
502 USHORT compression_format = COMPRESSION_FORMAT_DEFAULT;
503 DWORD bytes_returned;
505 h = win32_open_existing_file(path, GENERIC_READ | GENERIC_WRITE);
506 if (h == INVALID_HANDLE_VALUE)
509 if (attributes & FILE_ATTRIBUTE_SPARSE_FILE)
510 if (!DeviceIoControl(h, FSCTL_SET_SPARSE,
513 &bytes_returned, NULL))
514 goto error_close_handle;
516 if (attributes & FILE_ATTRIBUTE_COMPRESSED)
517 if (!DeviceIoControl(h, FSCTL_SET_COMPRESSION,
518 &compression_format, sizeof(USHORT),
520 &bytes_returned, NULL))
521 goto error_close_handle;
526 if (attributes & FILE_ATTRIBUTE_ENCRYPTED)
527 if (!EncryptFile(path))
533 err = GetLastError();
541 win32_set_file_attributes(const wchar_t *path, u32 attributes,
542 struct apply_ctx *ctx, unsigned pass)
544 u32 special_attributes =
545 FILE_ATTRIBUTE_REPARSE_POINT |
546 FILE_ATTRIBUTE_DIRECTORY |
547 FILE_ATTRIBUTE_SPARSE_FILE |
548 FILE_ATTRIBUTE_COMPRESSED |
549 FILE_ATTRIBUTE_ENCRYPTED;
550 u32 actual_attributes;
552 /* Delay setting FILE_ATTRIBUTE_READONLY on the initial pass (when files
553 * are created, but data not extracted); otherwise the system will
554 * refuse access to the file even if the process has SeRestorePrivilege.
557 attributes &= ~FILE_ATTRIBUTE_READONLY;
559 if (!SetFileAttributes(path, attributes & ~special_attributes))
565 if (attributes & (FILE_ATTRIBUTE_SPARSE_FILE |
566 FILE_ATTRIBUTE_ENCRYPTED |
567 FILE_ATTRIBUTE_COMPRESSED))
568 if (!win32_set_special_file_attributes(path, attributes))
571 /* If file is not supposed to be encrypted or compressed, remove
572 * defaulted encrypted or compressed attributes (from creating file in
573 * encrypted or compressed directory). */
574 actual_attributes = GetFileAttributes(path);
575 if (actual_attributes == INVALID_FILE_ATTRIBUTES)
578 if ((actual_attributes & FILE_ATTRIBUTE_ENCRYPTED) &&
579 !(attributes & FILE_ATTRIBUTE_ENCRYPTED))
580 if (!DecryptFile(path, 0))
582 if ((actual_attributes & FILE_ATTRIBUTE_COMPRESSED) &&
583 !(attributes & FILE_ATTRIBUTE_COMPRESSED))
586 DWORD bytes_returned;
587 USHORT compression_format = COMPRESSION_FORMAT_NONE;
589 h = win32_open_existing_file(path, GENERIC_READ | GENERIC_WRITE);
590 if (h == INVALID_HANDLE_VALUE)
593 if (!DeviceIoControl(h, FSCTL_SET_COMPRESSION,
594 &compression_format, sizeof(USHORT),
596 &bytes_returned, NULL))
598 DWORD err = GetLastError();
611 set_errno_from_GetLastError();
612 return WIMLIB_ERR_SET_ATTRIBUTES;
616 win32_set_reparse_data(const wchar_t *path, const u8 *rpbuf, u16 rpbuflen,
617 struct apply_ctx *ctx)
621 DWORD bytes_returned;
623 h = win32_open_existing_file(path, GENERIC_WRITE);
624 if (h == INVALID_HANDLE_VALUE)
627 if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT,
628 (void*)rpbuf, rpbuflen,
629 NULL, 0, &bytes_returned, NULL))
630 goto error_close_handle;
638 err = GetLastError();
642 set_errno_from_GetLastError();
643 return WIMLIB_ERR_WRITE; /* XXX: need better error code */
647 win32_set_short_name(const wchar_t *path, const wchar_t *short_name,
648 size_t short_name_nchars, struct apply_ctx *ctx)
653 h = win32_open_existing_file(path, GENERIC_WRITE | DELETE);
654 if (h == INVALID_HANDLE_VALUE)
657 if (short_name_nchars) {
658 if (!SetFileShortName(h, short_name))
659 goto error_close_handle;
660 } else if (running_on_windows_7_or_later()) {
661 if (!SetFileShortName(h, L""))
662 goto error_close_handle;
671 err = GetLastError();
675 set_errno_from_GetLastError();
676 return WIMLIB_ERR_WRITE; /* XXX: need better error code */
680 do_win32_set_security_descriptor(HANDLE h, const wchar_t *path,
681 SECURITY_INFORMATION info,
682 PSECURITY_DESCRIPTOR desc)
685 if (func_NtSetSecurityObject) {
686 return (*func_RtlNtStatusToDosError)(
687 (*func_NtSetSecurityObject)(h, info, desc));
690 if (SetFileSecurity(path, info, desc))
691 return ERROR_SUCCESS;
693 return GetLastError();
697 * Set an arbitrary security descriptor on an arbitrary file (or directory),
698 * working around bugs and design flaws in the Windows operating system.
700 * On success, return 0. On failure, return WIMLIB_ERR_SET_SECURITY and set
701 * errno. Note: if WIMLIB_EXTRACT_FLAG_STRICT_ACLS is not set in
702 * ctx->extract_flags, this function succeeds iff any part of the security
703 * descriptor was successfully set.
706 win32_set_security_descriptor(const wchar_t *path, const u8 *desc,
707 size_t desc_size, struct apply_ctx *ctx)
709 SECURITY_INFORMATION info;
713 /* We really just want to set entire the security descriptor as-is, but
714 * all available APIs require specifying the specific parts of the
715 * descriptor being set. Start out by requesting all parts be set. If
716 * permissions problems are encountered, fall back to omitting some
717 * parts (first the SACL, then the DACL, then the owner), unless the
718 * WIMLIB_EXTRACT_FLAG_STRICT_ACLS flag has been enabled. */
719 info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
720 DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION;
722 h = INVALID_HANDLE_VALUE;
724 /* Prefer NtSetSecurityObject() to SetFileSecurity(). SetFileSecurity()
725 * itself necessarily uses NtSetSecurityObject() as the latter is the
726 * underlying system call for setting security information, but
727 * SetFileSecurity() opens the handle with NtCreateFile() without
728 * FILE_OPEN_FILE_BACKUP_INTENT. Hence, access checks are done and due
729 * to the Windows security model, even a process running as the
730 * Administrator can have access denied. (Of course, this not mentioned
731 * in the MS "documentation".) */
734 if (func_NtSetSecurityObject) {
735 DWORD dwDesiredAccess;
737 /* Open a handle for NtSetSecurityObject() with as many relevant
738 * access rights as possible.
740 * We don't know which rights will be actually granted. It
741 * could be less than what is needed to actually assign the full
742 * security descriptor, especially if the process is running as
743 * a non-Administrator. However, by default we just do the best
744 * we can, unless WIMLIB_EXTRACT_FLAG_STRICT_ACLS has been
745 * enabled. The MAXIMUM_ALLOWED access right is seemingly
746 * designed for this use case; however, it does not work
747 * properly in all cases: it can cause CreateFile() to fail with
748 * ERROR_ACCESS_DENIED, even though by definition
749 * MAXIMUM_ALLOWED access only requests access rights that are
750 * *not* denied. (Needless to say, MS does not document this
753 dwDesiredAccess = WRITE_DAC |
755 ACCESS_SYSTEM_SECURITY;
759 h = win32_open_existing_file(path, dwDesiredAccess);
760 if (h != INVALID_HANDLE_VALUE)
762 err = GetLastError();
763 if (err == ERROR_ACCESS_DENIED ||
764 err == ERROR_PRIVILEGE_NOT_HELD)
766 /* Don't increment partial_security_descriptors
767 * here or check WIMLIB_EXTRACT_FLAG_STRICT_ACLS
768 * here. It will be done later if needed; here
769 * we are just trying to get as many relevant
770 * access rights as possible. */
771 if (dwDesiredAccess & ACCESS_SYSTEM_SECURITY) {
772 dwDesiredAccess &= ~ACCESS_SYSTEM_SECURITY;
775 if (dwDesiredAccess & WRITE_DAC) {
776 dwDesiredAccess &= ~WRITE_DAC;
779 if (dwDesiredAccess & WRITE_OWNER) {
780 dwDesiredAccess &= ~WRITE_OWNER;
784 /* Other error, or couldn't open the file even with no
785 * access rights specified. Something else must be
787 set_errno_from_win32_error(err);
788 return WIMLIB_ERR_SET_SECURITY;
793 /* Try setting the security descriptor. */
797 err = do_win32_set_security_descriptor(h, path, info,
798 (PSECURITY_DESCRIPTOR)desc);
799 if (err == ERROR_SUCCESS) {
804 /* Failed to set the requested parts of the security descriptor.
805 * If the error was permissions-related, try to set fewer parts
806 * of the security descriptor, unless
807 * WIMLIB_EXTRACT_FLAG_STRICT_ACLS is enabled. */
808 if ((err == ERROR_PRIVILEGE_NOT_HELD ||
809 err == ERROR_ACCESS_DENIED) &&
810 !(ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
812 if (info & SACL_SECURITY_INFORMATION) {
813 info &= ~SACL_SECURITY_INFORMATION;
814 ctx->partial_security_descriptors++;
817 if (info & DACL_SECURITY_INFORMATION) {
818 info &= ~DACL_SECURITY_INFORMATION;
821 if (info & OWNER_SECURITY_INFORMATION) {
822 info &= ~OWNER_SECURITY_INFORMATION;
825 /* Nothing left except GROUP, and if we removed it we
826 * wouldn't have anything at all. */
828 /* No part of the security descriptor could be set, or
829 * WIMLIB_EXTRACT_FLAG_STRICT_ACLS is enabled and the full
830 * security descriptor could not be set. */
831 if (!(info & SACL_SECURITY_INFORMATION))
832 ctx->partial_security_descriptors--;
833 set_errno_from_win32_error(err);
834 ret = WIMLIB_ERR_SET_SECURITY;
838 /* Close handle opened for NtSetSecurityObject(). */
840 if (func_NtSetSecurityObject)
847 win32_set_timestamps(const wchar_t *path, u64 creation_time,
848 u64 last_write_time, u64 last_access_time,
849 struct apply_ctx *ctx)
853 FILETIME creationTime = {.dwLowDateTime = creation_time & 0xffffffff,
854 .dwHighDateTime = creation_time >> 32};
855 FILETIME lastAccessTime = {.dwLowDateTime = last_access_time & 0xffffffff,
856 .dwHighDateTime = last_access_time >> 32};
857 FILETIME lastWriteTime = {.dwLowDateTime = last_write_time & 0xffffffff,
858 .dwHighDateTime = last_write_time >> 32};
860 h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
861 if (h == INVALID_HANDLE_VALUE)
864 if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime))
865 goto error_close_handle;
873 err = GetLastError();
877 set_errno_from_GetLastError();
878 return WIMLIB_ERR_SET_TIMESTAMPS;
881 const struct apply_operations win32_apply_ops = {
884 .target_is_root = win32_path_is_root_of_drive,
885 .start_extract = win32_start_extract,
886 .finish_extract = win32_finish_extract,
887 .abort_extract = win32_finish_extract,
888 .create_file = win32_create_file,
889 .create_directory = win32_create_directory,
890 .create_hardlink = win32_create_hardlink,
891 .create_symlink = win32_create_symlink,
892 .extract_unnamed_stream = win32_extract_unnamed_stream,
893 .extract_named_stream = win32_extract_named_stream,
894 .extract_encrypted_stream = win32_extract_encrypted_stream,
895 .set_file_attributes = win32_set_file_attributes,
896 .set_reparse_data = win32_set_reparse_data,
897 .set_short_name = win32_set_short_name,
898 .set_security_descriptor = win32_set_security_descriptor,
899 .set_timestamps = win32_set_timestamps,
901 .path_prefix = L"\\\\?\\",
902 .path_prefix_nchars = 4,
903 .path_separator = L'\\',
906 .requires_realtarget_in_paths = 1,
907 .realpath_works_on_nonexisting_files = 1,
908 .root_directory_is_special = 1,
909 .requires_final_set_attributes_pass = 1,
910 .extract_encrypted_stream_creates_file = 1,
911 .requires_short_name_reordering = 1, /* TODO: check if this is really needed */
914 #endif /* __WIN32__ */