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/.
26 #include <aclapi.h> /* for SetSecurityInfo() */
28 #include "win32_common.h"
29 #include "wimlib_internal.h"
31 #include "lookup_table.h"
32 #include "endianness.h"
34 #define MAX_CREATE_HARD_LINK_WARNINGS 5
35 #define MAX_CREATE_SOFT_LINK_WARNINGS 5
37 #define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1
38 #define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1
40 static const wchar_t *apply_access_denied_msg =
41 L"If you are not running this program as the administrator, you may\n"
42 " need to do so, so that all data and metadata can be extracted\n"
43 " exactly as the origignal copy. However, if you do not care that\n"
44 " the security descriptors are extracted correctly, you could run\n"
45 " `wimlib-imagex apply' with the --no-acls flag instead.\n"
50 win32_extract_try_rpfix(u8 *rpbuf,
51 const wchar_t *extract_root_realpath,
52 unsigned extract_root_realpath_nchars)
54 struct reparse_data rpdata;
57 size_t stripped_nchars;
58 wchar_t *stripped_target;
59 wchar_t stripped_target_nchars;
62 utf16lechar *new_target;
63 utf16lechar *new_print_name;
64 size_t new_target_nchars;
65 size_t new_print_name_nchars;
68 ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
73 if (extract_root_realpath[0] == L'\0' ||
74 extract_root_realpath[1] != L':' ||
75 extract_root_realpath[2] != L'\\')
77 ERROR("Can't understand full path format \"%ls\". "
78 "Try turning reparse point fixups off...",
79 extract_root_realpath);
80 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
83 ret = parse_substitute_name(rpdata.substitute_name,
84 rpdata.substitute_name_nbytes,
88 stripped_nchars = ret;
89 target = rpdata.substitute_name;
90 target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar);
91 stripped_target = target + 6;
92 stripped_target_nchars = target_nchars - stripped_nchars;
94 new_target = alloca((6 + extract_root_realpath_nchars +
95 stripped_target_nchars) * sizeof(utf16lechar));
98 if (stripped_nchars == 6) {
99 /* Include \??\ prefix if it was present before */
100 wmemcpy(p, L"\\??\\", 4);
104 /* Print name excludes the \??\ if present. */
106 if (stripped_nchars != 0) {
107 /* Get drive letter from real path to extract root, if a drive
108 * letter was present before. */
109 *p++ = extract_root_realpath[0];
110 *p++ = extract_root_realpath[1];
112 /* Copy the rest of the extract root */
113 wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
114 p += extract_root_realpath_nchars - 2;
116 /* Append the stripped target */
117 wmemcpy(p, stripped_target, stripped_target_nchars);
118 p += stripped_target_nchars;
119 new_target_nchars = p - new_target;
120 new_print_name_nchars = p - new_print_name;
122 if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE ||
123 new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE)
125 ERROR("Path names too long to do reparse point fixup!");
126 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
128 rpdata.substitute_name = new_target;
129 rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar);
130 rpdata.print_name = new_print_name;
131 rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar);
132 return make_reparse_buffer(&rpdata, rpbuf);
135 /* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on
136 * an extracted reparse point. */
138 win32_set_reparse_data(HANDLE h,
139 const struct wim_inode *inode,
140 const struct wim_lookup_table_entry *lte,
142 struct apply_args *args)
145 u8 rpbuf[REPARSE_POINT_MAX_SIZE];
148 DEBUG("Setting reparse data on \"%ls\"", path);
150 ret = wim_inode_get_reparse_data(inode, rpbuf);
154 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX &&
155 (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
156 inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) &&
157 !inode->i_not_rpfixed)
159 ret = win32_extract_try_rpfix(rpbuf,
160 args->target_realpath,
161 args->target_realpath_len);
163 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
166 /* Set the reparse data on the open file using the
167 * FSCTL_SET_REPARSE_POINT ioctl.
169 * There are contradictions in Microsoft's documentation for this:
171 * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED,
172 * lpOverlapped is ignored."
174 * --- So setting lpOverlapped to NULL is okay since it's ignored.
176 * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an
177 * operation returns no output data and lpOutBuffer is NULL,
178 * DeviceIoControl makes use of lpBytesReturned. After such an
179 * operation, the value of lpBytesReturned is meaningless."
181 * --- So lpOverlapped not really ignored, as it affects another
182 * parameter. This is the actual behavior: lpBytesReturned must be
183 * specified, even though lpBytesReturned is documented as:
185 * "Not used with this operation; set to NULL."
187 if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf,
188 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
190 &bytesReturned /* lpBytesReturned */,
191 NULL /* lpOverlapped */))
193 DWORD err = GetLastError();
194 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
196 args->num_soft_links_failed++;
197 if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) {
198 WARNING("Can't set reparse data on \"%ls\": Access denied!\n"
199 " You may be trying to extract a symbolic "
201 " SeCreateSymbolicLink privilege, which by "
202 "default non-Administrator\n"
203 " accounts do not have.", path);
205 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
206 WARNING("Suppressing further warnings regarding failure to extract\n"
207 " reparse points due to insufficient privileges...");
210 ERROR("Failed to set reparse data on \"%ls\"", path);
212 if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
213 inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
214 return WIMLIB_ERR_LINK;
216 return WIMLIB_ERR_WRITE;
222 /* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the
223 * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */
225 win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path)
228 if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION,
229 &format, sizeof(USHORT),
231 &bytesReturned, NULL))
233 /* Could be a warning only, but we only call this if the volume
234 * supports compression. So I'm calling this an error. */
235 DWORD err = GetLastError();
236 ERROR("Failed to set compression flag on \"%ls\"", path);
238 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
239 return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
241 return WIMLIB_ERR_WRITE;
246 /* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */
248 win32_set_sparse(HANDLE hFile, const wchar_t *path)
251 if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE,
254 &bytesReturned, NULL))
256 /* Could be a warning only, but we only call this if the volume
257 * supports sparse files. So I'm calling this an error. */
258 DWORD err = GetLastError();
259 WARNING("Failed to set sparse flag on \"%ls\"", path);
261 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
262 return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
264 return WIMLIB_ERR_WRITE;
270 * Sets the security descriptor on an extracted file.
273 win32_set_security_data(const struct wim_inode *inode,
276 struct apply_args *args)
278 PSECURITY_DESCRIPTOR descriptor;
281 const struct wim_security_data *sd;
283 SECURITY_INFORMATION securityInformation = 0;
290 BOOL owner_defaulted;
291 BOOL group_defaulted;
297 sd = wim_const_security_data(args->w);
298 descriptor = sd->descriptors[inode->i_security_id];
300 GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted);
302 securityInformation |= OWNER_SECURITY_INFORMATION;
304 GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted);
306 securityInformation |= GROUP_SECURITY_INFORMATION;
308 GetSecurityDescriptorDacl(descriptor, &dacl_present,
309 &dacl, &dacl_defaulted);
311 securityInformation |= DACL_SECURITY_INFORMATION;
313 GetSecurityDescriptorSacl(descriptor, &sacl_present,
314 &sacl, &sacl_defaulted);
316 securityInformation |= SACL_SECURITY_INFORMATION;
319 if (securityInformation == 0)
321 if (SetSecurityInfo(hFile, SE_FILE_OBJECT,
322 securityInformation, owner, group, dacl, sacl))
324 err = GetLastError();
325 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)
328 case ERROR_PRIVILEGE_NOT_HELD:
329 if (securityInformation & SACL_SECURITY_INFORMATION) {
330 n = args->num_set_sacl_priv_notheld++;
331 securityInformation &= ~SACL_SECURITY_INFORMATION;
333 if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
335 "We don't have enough privileges to set the full security\n"
336 " descriptor on \"%ls\"!\n", path);
337 if (args->num_set_sd_access_denied +
338 args->num_set_sacl_priv_notheld == 1)
340 WARNING("%ls", apply_access_denied_msg);
342 WARNING("Re-trying with SACL omitted.\n", path);
343 } else if (n == MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
345 "Suppressing further 'privileges not held' error messages when setting\n"
346 " security descriptors.");
351 case ERROR_INVALID_OWNER:
352 case ERROR_ACCESS_DENIED:
353 n = args->num_set_sd_access_denied++;
354 if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
355 WARNING("Failed to set security descriptor on \"%ls\": "
356 "Access denied!\n", path);
357 if (args->num_set_sd_access_denied +
358 args->num_set_sacl_priv_notheld == 1)
360 WARNING("%ls", apply_access_denied_msg);
362 } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
364 "Suppressing further access denied error messages when setting\n"
365 " security descriptors");
370 ERROR("Failed to set security descriptor on \"%ls\"", path);
372 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
373 return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
375 return WIMLIB_ERR_WRITE;
381 win32_extract_chunk(const void *buf, size_t len, void *arg)
383 HANDLE hStream = arg;
385 DWORD nbytes_written;
386 wimlib_assert(len <= 0xffffffff);
388 if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) ||
389 nbytes_written != len)
391 DWORD err = GetLastError();
392 ERROR("WriteFile(): write error");
394 return WIMLIB_ERR_WRITE;
400 do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte)
402 return extract_wim_resource(lte, wim_resource_size(lte),
403 win32_extract_chunk, hStream);
406 struct win32_encrypted_extract_ctx {
407 const struct wim_lookup_table_entry *lte;
412 win32_encrypted_import_cb(unsigned char *data, void *_ctx,
413 unsigned long *len_p)
415 struct win32_encrypted_extract_ctx *ctx = _ctx;
416 unsigned long len = *len_p;
417 const struct wim_lookup_table_entry *lte = ctx->lte;
419 len = min(len, wim_resource_size(lte) - ctx->offset);
421 if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data))
422 return ERROR_READ_FAULT;
426 return ERROR_SUCCESS;
429 /* Create an encrypted file and extract the raw encrypted data to it.
431 * @path: Path to encrypted file to create.
432 * @lte: WIM lookup_table entry for the raw encrypted data.
434 * This is separate from do_win32_extract_stream() because the WIM is supposed
435 * to contain the *raw* encrypted data, which needs to be extracted ("imported")
436 * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and
437 * CloseEncryptedFileRaw().
439 * Returns 0 on success; nonzero on failure.
442 do_win32_extract_encrypted_stream(const wchar_t *path,
443 const struct wim_lookup_table_entry *lte)
448 DEBUG("Opening file \"%ls\" to extract raw encrypted data", path);
450 ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx);
452 ERROR("Failed to open \"%ls\" to write raw encrypted data", path);
454 return WIMLIB_ERR_OPEN;
458 struct win32_encrypted_extract_ctx ctx;
462 ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx);
463 if (ret == ERROR_SUCCESS) {
466 ret = WIMLIB_ERR_WRITE;
467 ERROR("Failed to extract encrypted file \"%ls\"", path);
470 CloseEncryptedFileRaw(file_ctx);
475 path_is_root_of_drive(const wchar_t *path)
480 if (*path != L'/' && *path != L'\\') {
481 if (*(path + 1) == L':')
486 while (*path == L'/' || *path == L'\\')
488 return (*path == L'\0');
492 win32_mask_attributes(DWORD i_attributes)
494 return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE |
495 FILE_ATTRIBUTE_COMPRESSED |
496 FILE_ATTRIBUTE_REPARSE_POINT |
497 FILE_ATTRIBUTE_DIRECTORY |
498 FILE_ATTRIBUTE_ENCRYPTED |
499 FILE_FLAG_DELETE_ON_CLOSE |
500 FILE_FLAG_NO_BUFFERING |
501 FILE_FLAG_OPEN_NO_RECALL |
502 FILE_FLAG_OVERLAPPED |
503 FILE_FLAG_RANDOM_ACCESS |
504 /*FILE_FLAG_SESSION_AWARE |*/
505 FILE_FLAG_SEQUENTIAL_SCAN |
506 FILE_FLAG_WRITE_THROUGH);
510 win32_get_create_flags_and_attributes(DWORD i_attributes)
513 * Some attributes cannot be set by passing them to CreateFile(). In
516 * FILE_ATTRIBUTE_DIRECTORY:
517 * CreateDirectory() must be called instead of CreateFile().
519 * FILE_ATTRIBUTE_SPARSE_FILE:
521 * See: win32_set_sparse().
523 * FILE_ATTRIBUTE_COMPRESSED:
524 * Not clear from the documentation, but apparently this needs an
526 * See: win32_set_compressed().
528 * FILE_ATTRIBUTE_REPARSE_POINT:
529 * Needs an ioctl, with the reparse data specified.
530 * See: win32_set_reparse_data().
532 * In addition, clear any file flags in the attributes that we don't
533 * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and
534 * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application.
536 return win32_mask_attributes(i_attributes) |
537 FILE_FLAG_OPEN_REPARSE_POINT |
538 FILE_FLAG_BACKUP_SEMANTICS;
541 /* Set compression and/or sparse attributes on a stream, if supported by the
544 win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode,
545 struct wim_lookup_table_entry *unnamed_stream_lte,
546 const wchar_t *path, unsigned vol_flags)
550 if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) {
551 if (vol_flags & FILE_FILE_COMPRESSION) {
552 ret = win32_set_compression_state(hFile,
553 COMPRESSION_FORMAT_DEFAULT,
558 DEBUG("Cannot set compression attribute on \"%ls\": "
559 "volume does not support transparent compression",
564 if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
565 if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) {
566 DEBUG("Setting sparse flag on \"%ls\"", path);
567 ret = win32_set_sparse(hFile, path);
571 DEBUG("Cannot set sparse attribute on \"%ls\": "
572 "volume does not support sparse files",
579 /* Pre-create directories; extract encrypted streams */
581 win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
582 const struct wim_lookup_table_entry *lte,
584 DWORD *creationDisposition_ret,
585 unsigned int vol_flags)
590 /* Directories must be created with CreateDirectoryW(). Then the call
591 * to CreateFileW() will merely open the directory that was already
592 * created rather than creating a new file. */
593 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY &&
594 !path_is_root_of_drive(path)) {
595 if (!CreateDirectoryW(path, NULL)) {
596 err = GetLastError();
597 if (err != ERROR_ALREADY_EXISTS) {
598 ERROR("Failed to create directory \"%ls\"",
601 return WIMLIB_ERR_MKDIR;
604 DEBUG("Created directory \"%ls\"", path);
605 *creationDisposition_ret = OPEN_EXISTING;
607 if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
608 vol_flags & FILE_SUPPORTS_ENCRYPTION)
610 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
611 unsigned remaining_sharing_violations = 100;
612 while (!EncryptFile(path)) {
613 if (remaining_sharing_violations &&
614 err == ERROR_SHARING_VIOLATION)
616 WARNING("Couldn't encrypt directory \"%ls\" "
617 "due to sharing violation; re-trying "
618 "after 100 ms", path);
620 remaining_sharing_violations--;
622 err = GetLastError();
623 ERROR("Failed to encrypt directory \"%ls\"",
626 return WIMLIB_ERR_WRITE;
630 ret = do_win32_extract_encrypted_stream(path, lte);
633 DEBUG("Extracted encrypted file \"%ls\"", path);
635 *creationDisposition_ret = OPEN_EXISTING;
638 /* Set file attributes if we created the file. Otherwise, we haven't
639 * created the file set and we will set the attributes in the call to
642 * The FAT filesystem does not let you change the attributes of the root
643 * directory, so treat that as a special case and do not set attributes.
645 if (*creationDisposition_ret == OPEN_EXISTING &&
646 !path_is_root_of_drive(path))
648 if (!SetFileAttributesW(path,
649 win32_mask_attributes(inode->i_attributes)))
651 err = GetLastError();
652 ERROR("Failed to set attributes on \"%ls\"", path);
654 return WIMLIB_ERR_WRITE;
660 /* Set security descriptor and extract stream data or reparse data (skip the
661 * unnamed data stream of encrypted files, which was already extracted). */
663 win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry,
664 const struct wim_lookup_table_entry *lte,
665 const wchar_t *stream_path,
666 const wchar_t *stream_name_utf16,
667 struct apply_args *args)
670 const struct wim_inode *inode = dentry->d_inode;
671 if (stream_name_utf16 == NULL) {
672 /* Unnamed stream. */
674 /* Set security descriptor, unless the extract_flags indicate
675 * not to or the volume does not supported it. Note that this
676 * is only done when the unnamed stream is being extracted, as
677 * security descriptors are per-file and not per-stream. */
678 if (inode->i_security_id >= 0 &&
679 !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
680 && (args->vol_flags & FILE_PERSISTENT_ACLS))
682 ret = win32_set_security_data(inode, h, stream_path, args);
687 /* Handle reparse points. The data for them needs to be set
688 * using a special ioctl. Note that the reparse point may have
689 * been created using CreateFileW() in the case of
690 * non-directories or CreateDirectoryW() in the case of
691 * directories; but the ioctl works either way. Also, it is
692 * only this step that actually sets the
693 * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it
694 * using SetFileAttributesW() or CreateFileW().
696 * If the volume does not support reparse points we simply
697 * ignore the reparse data. (N.B. the code currently doesn't
698 * actually reach this case because reparse points are skipped
699 * entirely on such volumes.) */
700 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
701 if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
702 ret = win32_set_reparse_data(h, inode,
708 DEBUG("Cannot set reparse data on \"%ls\": volume "
709 "does not support reparse points", stream_path);
711 } else if (lte != NULL &&
712 !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION &&
713 inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
715 /* Extract the data of the unnamed stream, unless the
716 * lookup table entry is NULL (indicating an empty
717 * stream for which no data needs to be extracted), or
718 * the stream is encrypted and therefore was already
719 * extracted as a special case. */
720 ret = do_win32_extract_stream(h, lte);
725 if (dentry_has_short_name(dentry))
726 SetFileShortNameW(h, dentry->short_name);
727 else if (running_on_windows_7_or_later())
728 SetFileShortNameW(h, L"");
730 /* Extract the data for a named data stream. */
732 DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")",
733 stream_path, wim_resource_size(lte));
734 ret = do_win32_extract_stream(h, lte);
741 win32_decrypt_file(HANDLE open_handle, const wchar_t *path)
744 /* We cannot call DecryptFileW() while there is an open handle to the
745 * file. So close it first. */
746 if (!CloseHandle(open_handle)) {
747 err = GetLastError();
748 ERROR("Failed to close handle for \"%ls\"", path);
750 return WIMLIB_ERR_WRITE;
752 if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) {
753 err = GetLastError();
754 ERROR("Failed to decrypt file \"%ls\"", path);
756 return WIMLIB_ERR_WRITE;
762 * Create and extract a stream to a file, or create a directory, using the
765 * This handles reparse points, directories, alternate data streams, encrypted
766 * files, compressed files, etc.
768 * @dentry: WIM dentry for the file or directory being extracted.
770 * @path: Path to extract the file to.
772 * @stream_name_utf16:
773 * Name of the stream, or NULL if the stream is unnamed. This will
774 * be called with a NULL stream_name_utf16 before any non-NULL
775 * stream_name_utf16's.
777 * @lte: WIM lookup table entry for the stream. May be NULL to indicate
778 * a stream of length 0.
780 * @args: Additional apply context, including flags indicating supported
783 * Returns 0 on success; nonzero on failure.
786 win32_extract_stream(const struct wim_dentry *dentry,
788 const wchar_t *stream_name_utf16,
789 struct wim_lookup_table_entry *lte,
790 struct apply_args *args)
792 wchar_t *stream_path;
796 DWORD creationDisposition = CREATE_ALWAYS;
797 DWORD requestedAccess;
798 BY_HANDLE_FILE_INFORMATION file_info;
799 unsigned remaining_sharing_violations = 1000;
800 const struct wim_inode *inode = dentry->d_inode;
802 if (stream_name_utf16) {
803 /* Named stream. Create a buffer that contains the UTF-16LE
804 * string [./]path:stream_name_utf16. This is needed to
805 * create and open the stream using CreateFileW(). I'm not
806 * aware of any other APIs to do this. Note: the '$DATA' suffix
807 * seems to be unneeded. Additional note: a "./" prefix needs
808 * to be added when the path is not absolute to avoid ambiguity
809 * with drive letters. */
810 size_t stream_path_nchars;
812 size_t stream_name_nchars;
813 const wchar_t *prefix;
815 path_nchars = wcslen(path);
816 stream_name_nchars = wcslen(stream_name_utf16);
817 stream_path_nchars = path_nchars + 1 + stream_name_nchars;
818 if (path[0] != cpu_to_le16(L'\0') &&
819 path[0] != cpu_to_le16(L'/') &&
820 path[0] != cpu_to_le16(L'\\') &&
821 path[1] != cpu_to_le16(L':'))
824 stream_path_nchars += 2;
828 stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
829 swprintf(stream_path, L"%ls%ls:%ls",
830 prefix, path, stream_name_utf16);
832 /* Unnamed stream; its path is just the path to the file itself.
834 stream_path = (wchar_t*)path;
836 ret = win32_begin_extract_unnamed_stream(inode, lte, path,
837 &creationDisposition,
843 DEBUG("Opening \"%ls\"", stream_path);
844 /* DELETE access is needed for SetFileShortNameW(), for some reason. */
845 requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE |
846 ACCESS_SYSTEM_SECURITY;
848 /* Open the stream to be extracted. Depending on what we have set
849 * creationDisposition to, we may be creating this for the first time,
850 * or we may be opening on existing stream we already created using
851 * CreateDirectoryW() or OpenEncryptedFileRawW(). */
852 h = CreateFileW(stream_path,
857 win32_get_create_flags_and_attributes(inode->i_attributes),
859 if (h == INVALID_HANDLE_VALUE) {
860 err = GetLastError();
861 if (err == ERROR_ACCESS_DENIED &&
862 path_is_root_of_drive(stream_path))
867 if ((err == ERROR_PRIVILEGE_NOT_HELD ||
868 err == ERROR_ACCESS_DENIED) &&
869 (requestedAccess & ACCESS_SYSTEM_SECURITY))
871 /* Try opening the file again without privilege to
873 requestedAccess &= ~ACCESS_SYSTEM_SECURITY;
876 if (err == ERROR_SHARING_VIOLATION) {
877 if (remaining_sharing_violations) {
878 --remaining_sharing_violations;
879 /* This can happen when restoring encrypted directories
880 * for some reason. Probably a bug in EncryptFile(). */
881 WARNING("Couldn't open \"%ls\" due to sharing violation; "
882 "re-trying after 100ms", stream_path);
886 ERROR("Too many sharing violations; giving up...");
889 if (creationDisposition == OPEN_EXISTING)
890 ERROR("Failed to open \"%ls\"", stream_path);
892 ERROR("Failed to create \"%ls\"", stream_path);
895 ret = WIMLIB_ERR_OPEN;
899 /* Check the attributes of the file we just opened, and remove
900 * encryption or compression if either was set by default but is not
901 * supposed to be set based on the WIM inode attributes. */
902 if (!GetFileInformationByHandle(h, &file_info)) {
903 err = GetLastError();
904 ERROR("Failed to get attributes of \"%ls\"", stream_path);
906 ret = WIMLIB_ERR_STAT;
907 goto fail_close_handle;
910 /* Remove encryption? */
911 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED &&
912 !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
914 /* File defaulted to encrypted due to being in an encrypted
915 * directory, but is not actually supposed to be encrypted.
917 * This is a workaround, because I'm not aware of any way to
918 * directly (e.g. with CreateFileW()) create an unencrypted file
919 * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */
920 ret = win32_decrypt_file(h, stream_path);
922 goto fail; /* win32_decrypt_file() closed the handle. */
923 creationDisposition = OPEN_EXISTING;
927 /* Remove compression? */
928 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED &&
929 !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED))
931 /* Similar to the encrypted case, above, if the file defaulted
932 * to compressed due to being in an compressed directory, but is
933 * not actually supposed to be compressed, explicitly set the
934 * compression format to COMPRESSION_FORMAT_NONE. */
935 ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE,
938 goto fail_close_handle;
941 /* Set compression and/or sparse attributes if needed */
942 ret = win32_set_special_stream_attributes(h, inode, lte, path,
946 goto fail_close_handle;
948 /* At this point we have at least created the needed stream with the
949 * appropriate attributes. We have yet to set the appropriate security
950 * descriptor and actually extract the stream data (other than for
951 * extracted files, which were already extracted).
952 * win32_finish_extract_stream() handles these additional steps. */
953 ret = win32_finish_extract_stream(h, dentry, lte, stream_path,
954 stream_name_utf16, args);
956 goto fail_close_handle;
958 /* Done extracting the stream. Close the handle and return. */
959 DEBUG("Closing \"%ls\"", stream_path);
960 if (!CloseHandle(h)) {
961 err = GetLastError();
962 ERROR("Failed to close \"%ls\"", stream_path);
964 ret = WIMLIB_ERR_WRITE;
972 ERROR("Error extracting \"%ls\"", stream_path);
978 * Creates a file, directory, or reparse point and extracts all streams to it
979 * (unnamed data stream and/or reparse point stream, plus any alternate data
980 * streams). Handles sparse, compressed, and/or encrypted files.
982 * @dentry: WIM dentry for this file or directory.
983 * @path: UTF-16LE external path to extract the inode to.
984 * @args: Additional extraction context.
986 * Returns 0 on success; nonzero on failure.
989 win32_extract_streams(const struct wim_dentry *dentry,
990 const wchar_t *path, struct apply_args *args)
992 struct wim_lookup_table_entry *unnamed_lte;
994 const struct wim_inode *inode = dentry->d_inode;
996 /* First extract the unnamed stream. */
998 unnamed_lte = inode_unnamed_lte_resolved(inode);
999 ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args);
1003 /* Extract any named streams, if supported by the volume. */
1005 if (!(args->vol_flags & FILE_NAMED_STREAMS))
1007 for (u16 i = 0; i < inode->i_num_ads; i++) {
1008 const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i];
1010 /* Skip the unnamed stream if it's in the ADS entries (we
1011 * already extracted it...) */
1012 if (ads_entry->stream_name_nbytes == 0)
1015 /* Skip special UNIX data entries (see documentation for
1016 * WIMLIB_ADD_FLAG_UNIX_DATA) */
1017 if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES
1018 && !memcmp(ads_entry->stream_name,
1019 WIMLIB_UNIX_DATA_TAG_UTF16LE,
1020 WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES))
1023 /* Extract the named stream */
1024 ret = win32_extract_stream(dentry,
1026 ads_entry->stream_name,
1037 dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore)
1039 dentry->d_inode->i_visited = 0;
1044 dentry_get_features(struct wim_dentry *dentry, void *_features_p)
1047 DWORD *features_p = _features_p;
1048 struct wim_inode *inode = dentry->d_inode;
1050 if (inode->i_visited) {
1051 features |= FILE_SUPPORTS_HARD_LINKS;
1053 inode->i_visited = 1;
1054 if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE)
1055 features |= FILE_SUPPORTS_SPARSE_FILES;
1056 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
1057 features |= FILE_SUPPORTS_REPARSE_POINTS;
1058 for (unsigned i = 0; i < inode->i_num_ads; i++)
1059 if (inode->i_ads_entries[i].stream_name_nbytes)
1060 features |= FILE_NAMED_STREAMS;
1061 if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
1062 features |= FILE_SUPPORTS_ENCRYPTION;
1063 if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
1064 features |= FILE_FILE_COMPRESSION;
1065 if (inode->i_security_id != -1)
1066 features |= FILE_PERSISTENT_ACLS;
1068 *features_p |= features;
1072 /* If not done already, load the supported feature flags for the volume onto
1073 * which the image is being extracted, and warn the user about any missing
1074 * features that could be important. */
1076 win32_check_vol_flags(const wchar_t *output_path,
1077 struct wim_dentry *root, struct apply_args *args)
1079 DWORD dentry_features = 0;
1080 DWORD missing_features;
1082 if (args->have_vol_flags)
1085 for_dentry_in_tree(root, dentry_clear_inode_visited, NULL);
1086 for_dentry_in_tree(root, dentry_get_features, &dentry_features);
1088 win32_get_vol_flags(output_path, &args->vol_flags);
1089 args->have_vol_flags = true;
1091 missing_features = dentry_features & ~args->vol_flags;
1093 /* Warn the user about data that may not be extracted. */
1094 if (missing_features & FILE_SUPPORTS_SPARSE_FILES)
1095 WARNING("Volume does not support sparse files!\n"
1096 " Sparse files will be extracted as non-sparse.");
1097 if (missing_features & FILE_SUPPORTS_REPARSE_POINTS)
1098 WARNING("Volume does not support reparse points!\n"
1099 " Reparse point data will not be extracted.");
1100 if (missing_features & FILE_NAMED_STREAMS) {
1101 WARNING("Volume does not support named data streams!\n"
1102 " Named data streams will not be extracted.");
1104 if (missing_features & FILE_SUPPORTS_ENCRYPTION) {
1105 WARNING("Volume does not support encryption!\n"
1106 " Encrypted files will be extracted as raw data.");
1108 if (missing_features & FILE_FILE_COMPRESSION) {
1109 WARNING("Volume does not support transparent compression!\n"
1110 " Compressed files will be extracted as non-compressed.");
1112 if (missing_features & FILE_PERSISTENT_ACLS) {
1113 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
1114 ERROR("Strict ACLs requested, but the volume does not "
1116 return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
1118 WARNING("Volume does not support persistent ACLS!\n"
1119 " File permissions will not be extracted.");
1122 if (running_on_windows_7_or_later() &&
1123 (missing_features & FILE_SUPPORTS_HARD_LINKS))
1125 WARNING("Volume does not support hard links!\n"
1126 " Hard links will be extracted as duplicate files.");
1132 * Try extracting a hard link.
1134 * @output_path: Path to link to be extracted.
1136 * @inode: WIM inode that the link is to; inode->i_extracted_file
1137 * the path to a name of the file that has already been
1138 * extracted (we use this to create the hard link).
1140 * @args: Additional apply context, used here to keep track of
1141 * the number of times creating a hard link failed due to
1142 * ERROR_INVALID_FUNCTION. This error should indicate that hard
1143 * links are not supported by the volume, and we would like to
1144 * warn the user a few times, but not too many times.
1146 * Returns 0 if the hard link was successfully extracted. Returns
1147 * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly
1148 * being unsupported by the volume. Returns a negative value if creating the
1149 * hard link failed due to ERROR_INVALID_FUNCTION.
1152 win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
1153 struct apply_args *args)
1157 /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
1158 * but it's only available on Windows 7 and later.
1160 * Otherwise, CreateHardLinkW() will apparently return
1161 * ERROR_INVALID_FUNCTION if the volume does not support hard links. */
1163 DEBUG("Creating hard link \"%ls => %ls\"",
1164 output_path, inode->i_extracted_file);
1166 if (running_on_windows_7_or_later() &&
1167 !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS))
1168 goto hard_links_unsupported;
1170 if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
1173 err = GetLastError();
1174 if (err != ERROR_INVALID_FUNCTION) {
1175 ERROR("Can't create hard link \"%ls => %ls\"",
1176 output_path, inode->i_extracted_file);
1178 return WIMLIB_ERR_LINK;
1180 hard_links_unsupported:
1181 args->num_hard_links_failed++;
1182 if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
1183 if (running_on_windows_7_or_later())
1185 WARNING("Extracting duplicate copy of \"%ls\" "
1186 "rather than hard link", output_path);
1188 WARNING("Can't create hard link \"%ls\" => \"%ls\":\n"
1189 " Volume does not support hard links!\n"
1190 " Falling back to extracting a copy of the file.",
1191 output_path, inode->i_extracted_file);
1194 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS)
1195 WARNING("Suppressing further hard linking warnings...");
1199 /* Extract a file, directory, reparse point, or hard link to an
1200 * already-extracted file using the Win32 API */
1202 win32_do_apply_dentry(const wchar_t *output_path,
1203 size_t output_path_num_chars,
1204 struct wim_dentry *dentry,
1205 struct apply_args *args)
1208 struct wim_inode *inode = dentry->d_inode;
1210 ret = win32_check_vol_flags(output_path, dentry, args);
1213 if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1214 /* Linked file, with another name already extracted. Create a
1216 ret = win32_try_hard_link(output_path, inode, args);
1219 /* Negative return value from win32_try_hard_link() indicates
1220 * that hard links are probably not supported by the volume.
1221 * Fall back to extracting a copy of the file. */
1224 /* If this is a reparse point and the volume does not support reparse
1225 * points, just skip it completely. */
1226 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1227 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1229 WARNING("Not extracting reparse point \"%ls\"", output_path);
1231 /* Create the file, directory, or reparse point, and extract the
1233 ret = win32_extract_streams(dentry, output_path, args);
1237 if (inode->i_extracted_file == NULL) {
1238 const struct wim_lookup_table_entry *lte;
1240 /* Tally bytes extracted, including all alternate data streams,
1241 * unless we extracted a hard link (or, at least extracted a
1242 * name that was supposed to be a hard link) */
1243 for (unsigned i = 0; i <= inode->i_num_ads; i++) {
1244 lte = inode_stream_lte_resolved(inode, i);
1246 args->progress.extract.completed_bytes +=
1247 wim_resource_size(lte);
1249 if (inode->i_nlink > 1) {
1250 /* Save extracted path for a later call to
1251 * CreateHardLinkW() if this inode has multiple links.
1253 inode->i_extracted_file = WSTRDUP(output_path);
1254 if (!inode->i_extracted_file)
1255 return WIMLIB_ERR_NOMEM;
1261 /* Set timestamps on an extracted file using the Win32 API */
1263 win32_do_apply_dentry_timestamps(const wchar_t *path,
1264 size_t path_num_chars,
1265 struct wim_dentry *dentry,
1266 struct apply_args *args)
1270 const struct wim_inode *inode = dentry->d_inode;
1272 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1273 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1275 /* Skip reparse points not extracted */
1279 /* Windows doesn't let you change the timestamps of the root directory
1280 * (at least on FAT, which is dumb but expected since FAT doesn't store
1281 * any metadata about the root directory...) */
1282 if (path_is_root_of_drive(path))
1285 DEBUG("Opening \"%ls\" to set timestamps", path);
1286 h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
1287 if (h == INVALID_HANDLE_VALUE) {
1288 err = GetLastError();
1292 FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1293 .dwHighDateTime = inode->i_creation_time >> 32};
1294 FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1295 .dwHighDateTime = inode->i_last_access_time >> 32};
1296 FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1297 .dwHighDateTime = inode->i_last_write_time >> 32};
1299 DEBUG("Calling SetFileTime() on \"%ls\"", path);
1300 if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1301 err = GetLastError();
1305 DEBUG("Closing \"%ls\"", path);
1306 if (!CloseHandle(h)) {
1307 err = GetLastError();
1312 /* Only warn if setting timestamps failed; still return 0. */
1313 WARNING("Can't set timestamps on \"%ls\"", path);
1319 #endif /* __WIN32__ */