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 const wchar_t *short_name;
672 if (stream_name_utf16 == NULL) {
673 /* Unnamed stream. */
675 /* Set security descriptor, unless the extract_flags indicate
676 * not to or the volume does not supported it. Note that this
677 * is only done when the unnamed stream is being extracted, as
678 * security descriptors are per-file and not per-stream. */
679 if (inode->i_security_id >= 0 &&
680 !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
681 && (args->vol_flags & FILE_PERSISTENT_ACLS))
683 ret = win32_set_security_data(inode, h, stream_path, args);
688 /* Handle reparse points. The data for them needs to be set
689 * using a special ioctl. Note that the reparse point may have
690 * been created using CreateFileW() in the case of
691 * non-directories or CreateDirectoryW() in the case of
692 * directories; but the ioctl works either way. Also, it is
693 * only this step that actually sets the
694 * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it
695 * using SetFileAttributesW() or CreateFileW().
697 * If the volume does not support reparse points we simply
698 * ignore the reparse data. (N.B. the code currently doesn't
699 * actually reach this case because reparse points are skipped
700 * entirely on such volumes.) */
701 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
702 if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
703 ret = win32_set_reparse_data(h, inode,
709 DEBUG("Cannot set reparse data on \"%ls\": volume "
710 "does not support reparse points", stream_path);
712 } else if (lte != NULL &&
713 !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION &&
714 inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
716 /* Extract the data of the unnamed stream, unless the
717 * lookup table entry is NULL (indicating an empty
718 * stream for which no data needs to be extracted), or
719 * the stream is encrypted and therefore was already
720 * extracted as a special case. */
721 ret = do_win32_extract_stream(h, lte);
726 if (dentry_has_short_name(dentry))
727 SetFileShortNameW(h, short_name);
728 else if (running_on_windows_7_or_later())
729 SetFileShortNameW(h, L"");
731 /* Extract the data for a named data stream. */
733 DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")",
734 stream_path, wim_resource_size(lte));
735 ret = do_win32_extract_stream(h, lte);
742 win32_decrypt_file(HANDLE open_handle, const wchar_t *path)
745 /* We cannot call DecryptFileW() while there is an open handle to the
746 * file. So close it first. */
747 if (!CloseHandle(open_handle)) {
748 err = GetLastError();
749 ERROR("Failed to close handle for \"%ls\"", path);
751 return WIMLIB_ERR_WRITE;
753 if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) {
754 err = GetLastError();
755 ERROR("Failed to decrypt file \"%ls\"", path);
757 return WIMLIB_ERR_WRITE;
763 * Create and extract a stream to a file, or create a directory, using the
766 * This handles reparse points, directories, alternate data streams, encrypted
767 * files, compressed files, etc.
769 * @dentry: WIM dentry for the file or directory being extracted.
771 * @path: Path to extract the file to.
773 * @stream_name_utf16:
774 * Name of the stream, or NULL if the stream is unnamed. This will
775 * be called with a NULL stream_name_utf16 before any non-NULL
776 * stream_name_utf16's.
778 * @lte: WIM lookup table entry for the stream. May be NULL to indicate
779 * a stream of length 0.
781 * @args: Additional apply context, including flags indicating supported
784 * Returns 0 on success; nonzero on failure.
787 win32_extract_stream(const struct wim_dentry *dentry,
789 const wchar_t *stream_name_utf16,
790 struct wim_lookup_table_entry *lte,
791 struct apply_args *args)
793 wchar_t *stream_path;
797 DWORD creationDisposition = CREATE_ALWAYS;
798 DWORD requestedAccess;
799 BY_HANDLE_FILE_INFORMATION file_info;
800 unsigned remaining_sharing_violations = 1000;
801 const struct wim_inode *inode = dentry->d_inode;
803 if (stream_name_utf16) {
804 /* Named stream. Create a buffer that contains the UTF-16LE
805 * string [./]path:stream_name_utf16. This is needed to
806 * create and open the stream using CreateFileW(). I'm not
807 * aware of any other APIs to do this. Note: the '$DATA' suffix
808 * seems to be unneeded. Additional note: a "./" prefix needs
809 * to be added when the path is not absolute to avoid ambiguity
810 * with drive letters. */
811 size_t stream_path_nchars;
813 size_t stream_name_nchars;
814 const wchar_t *prefix;
816 path_nchars = wcslen(path);
817 stream_name_nchars = wcslen(stream_name_utf16);
818 stream_path_nchars = path_nchars + 1 + stream_name_nchars;
819 if (path[0] != cpu_to_le16(L'\0') &&
820 path[0] != cpu_to_le16(L'/') &&
821 path[0] != cpu_to_le16(L'\\') &&
822 path[1] != cpu_to_le16(L':'))
825 stream_path_nchars += 2;
829 stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
830 swprintf(stream_path, L"%ls%ls:%ls",
831 prefix, path, stream_name_utf16);
833 /* Unnamed stream; its path is just the path to the file itself.
835 stream_path = (wchar_t*)path;
837 ret = win32_begin_extract_unnamed_stream(inode, lte, path,
838 &creationDisposition,
844 DEBUG("Opening \"%ls\"", stream_path);
845 /* DELETE access is needed for SetFileShortNameW(), for some reason. */
846 requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE |
847 ACCESS_SYSTEM_SECURITY;
849 /* Open the stream to be extracted. Depending on what we have set
850 * creationDisposition to, we may be creating this for the first time,
851 * or we may be opening on existing stream we already created using
852 * CreateDirectoryW() or OpenEncryptedFileRawW(). */
853 h = CreateFileW(stream_path,
858 win32_get_create_flags_and_attributes(inode->i_attributes),
860 if (h == INVALID_HANDLE_VALUE) {
861 err = GetLastError();
862 if (err == ERROR_ACCESS_DENIED &&
863 path_is_root_of_drive(stream_path))
868 if ((err == ERROR_PRIVILEGE_NOT_HELD ||
869 err == ERROR_ACCESS_DENIED) &&
870 (requestedAccess & ACCESS_SYSTEM_SECURITY))
872 /* Try opening the file again without privilege to
874 requestedAccess &= ~ACCESS_SYSTEM_SECURITY;
877 if (err == ERROR_SHARING_VIOLATION) {
878 if (remaining_sharing_violations) {
879 --remaining_sharing_violations;
880 /* This can happen when restoring encrypted directories
881 * for some reason. Probably a bug in EncryptFile(). */
882 WARNING("Couldn't open \"%ls\" due to sharing violation; "
883 "re-trying after 100ms", stream_path);
887 ERROR("Too many sharing violations; giving up...");
890 if (creationDisposition == OPEN_EXISTING)
891 ERROR("Failed to open \"%ls\"", stream_path);
893 ERROR("Failed to create \"%ls\"", stream_path);
896 ret = WIMLIB_ERR_OPEN;
900 /* Check the attributes of the file we just opened, and remove
901 * encryption or compression if either was set by default but is not
902 * supposed to be set based on the WIM inode attributes. */
903 if (!GetFileInformationByHandle(h, &file_info)) {
904 err = GetLastError();
905 ERROR("Failed to get attributes of \"%ls\"", stream_path);
907 ret = WIMLIB_ERR_STAT;
908 goto fail_close_handle;
911 /* Remove encryption? */
912 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED &&
913 !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
915 /* File defaulted to encrypted due to being in an encrypted
916 * directory, but is not actually supposed to be encrypted.
918 * This is a workaround, because I'm not aware of any way to
919 * directly (e.g. with CreateFileW()) create an unencrypted file
920 * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */
921 ret = win32_decrypt_file(h, stream_path);
923 goto fail; /* win32_decrypt_file() closed the handle. */
924 creationDisposition = OPEN_EXISTING;
928 /* Remove compression? */
929 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED &&
930 !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED))
932 /* Similar to the encrypted case, above, if the file defaulted
933 * to compressed due to being in an compressed directory, but is
934 * not actually supposed to be compressed, explicitly set the
935 * compression format to COMPRESSION_FORMAT_NONE. */
936 ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE,
939 goto fail_close_handle;
942 /* Set compression and/or sparse attributes if needed */
943 ret = win32_set_special_stream_attributes(h, inode, lte, path,
947 goto fail_close_handle;
949 /* At this point we have at least created the needed stream with the
950 * appropriate attributes. We have yet to set the appropriate security
951 * descriptor and actually extract the stream data (other than for
952 * extracted files, which were already extracted).
953 * win32_finish_extract_stream() handles these additional steps. */
954 ret = win32_finish_extract_stream(h, dentry, lte, stream_path,
955 stream_name_utf16, args);
957 goto fail_close_handle;
959 /* Done extracting the stream. Close the handle and return. */
960 DEBUG("Closing \"%ls\"", stream_path);
961 if (!CloseHandle(h)) {
962 err = GetLastError();
963 ERROR("Failed to close \"%ls\"", stream_path);
965 ret = WIMLIB_ERR_WRITE;
973 ERROR("Error extracting \"%ls\"", stream_path);
979 * Creates a file, directory, or reparse point and extracts all streams to it
980 * (unnamed data stream and/or reparse point stream, plus any alternate data
981 * streams). Handles sparse, compressed, and/or encrypted files.
983 * @dentry: WIM dentry for this file or directory.
984 * @path: UTF-16LE external path to extract the inode to.
985 * @args: Additional extraction context.
987 * Returns 0 on success; nonzero on failure.
990 win32_extract_streams(const struct wim_dentry *dentry,
991 const wchar_t *path, struct apply_args *args)
993 struct wim_lookup_table_entry *unnamed_lte;
995 const struct wim_inode *inode = dentry->d_inode;
997 /* First extract the unnamed stream. */
999 unnamed_lte = inode_unnamed_lte_resolved(inode);
1000 ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args);
1004 /* Extract any named streams, if supported by the volume. */
1006 if (!(args->vol_flags & FILE_NAMED_STREAMS))
1008 for (u16 i = 0; i < inode->i_num_ads; i++) {
1009 const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i];
1011 /* Skip the unnamed stream if it's in the ADS entries (we
1012 * already extracted it...) */
1013 if (ads_entry->stream_name_nbytes == 0)
1016 /* Skip special UNIX data entries (see documentation for
1017 * WIMLIB_ADD_FLAG_UNIX_DATA) */
1018 if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES
1019 && !memcmp(ads_entry->stream_name,
1020 WIMLIB_UNIX_DATA_TAG_UTF16LE,
1021 WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES))
1024 /* Extract the named stream */
1025 ret = win32_extract_stream(dentry,
1027 ads_entry->stream_name,
1038 dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore)
1040 dentry->d_inode->i_visited = 0;
1045 dentry_get_features(struct wim_dentry *dentry, void *_features_p)
1048 DWORD *features_p = _features_p;
1049 struct wim_inode *inode = dentry->d_inode;
1051 if (inode->i_visited) {
1052 features |= FILE_SUPPORTS_HARD_LINKS;
1054 inode->i_visited = 1;
1055 if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE)
1056 features |= FILE_SUPPORTS_SPARSE_FILES;
1057 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
1058 features |= FILE_SUPPORTS_REPARSE_POINTS;
1059 for (unsigned i = 0; i < inode->i_num_ads; i++)
1060 if (inode->i_ads_entries[i].stream_name_nbytes)
1061 features |= FILE_NAMED_STREAMS;
1062 if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
1063 features |= FILE_SUPPORTS_ENCRYPTION;
1064 if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
1065 features |= FILE_FILE_COMPRESSION;
1066 if (inode->i_security_id != -1)
1067 features |= FILE_PERSISTENT_ACLS;
1069 *features_p |= features;
1073 /* If not done already, load the supported feature flags for the volume onto
1074 * which the image is being extracted, and warn the user about any missing
1075 * features that could be important. */
1077 win32_check_vol_flags(const wchar_t *output_path,
1078 struct wim_dentry *root, struct apply_args *args)
1080 DWORD dentry_features = 0;
1081 DWORD missing_features;
1083 if (args->have_vol_flags)
1086 for_dentry_in_tree(root, dentry_clear_inode_visited, NULL);
1087 for_dentry_in_tree(root, dentry_get_features, &dentry_features);
1089 win32_get_vol_flags(output_path, &args->vol_flags);
1090 args->have_vol_flags = true;
1092 missing_features = dentry_features ^ args->vol_flags;
1094 /* Warn the user about data that may not be extracted. */
1095 if (missing_features & FILE_SUPPORTS_SPARSE_FILES)
1096 WARNING("Volume does not support sparse files!\n"
1097 " Sparse files will be extracted as non-sparse.");
1098 if (missing_features & FILE_SUPPORTS_REPARSE_POINTS)
1099 WARNING("Volume does not support reparse points!\n"
1100 " Reparse point data will not be extracted.");
1101 if (missing_features & FILE_NAMED_STREAMS) {
1102 WARNING("Volume does not support named data streams!\n"
1103 " Named data streams will not be extracted.");
1105 if (missing_features & FILE_SUPPORTS_ENCRYPTION) {
1106 WARNING("Volume does not support encryption!\n"
1107 " Encrypted files will be extracted as raw data.");
1109 if (missing_features & FILE_FILE_COMPRESSION) {
1110 WARNING("Volume does not support transparent compression!\n"
1111 " Compressed files will be extracted as non-compressed.");
1113 if (missing_features & FILE_PERSISTENT_ACLS) {
1114 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
1115 ERROR("Strict ACLs requested, but the volume does not "
1117 return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
1119 WARNING("Volume does not support persistent ACLS!\n"
1120 " File permissions will not be extracted.");
1123 if (running_on_windows_7_or_later() &&
1124 (missing_features & FILE_SUPPORTS_HARD_LINKS))
1126 WARNING("Volume does not support hard links!\n"
1127 " Hard links will be extracted as duplicate files.");
1133 * Try extracting a hard link.
1135 * @output_path: Path to link to be extracted.
1137 * @inode: WIM inode that the link is to; inode->i_extracted_file
1138 * the path to a name of the file that has already been
1139 * extracted (we use this to create the hard link).
1141 * @args: Additional apply context, used here to keep track of
1142 * the number of times creating a hard link failed due to
1143 * ERROR_INVALID_FUNCTION. This error should indicate that hard
1144 * links are not supported by the volume, and we would like to
1145 * warn the user a few times, but not too many times.
1147 * Returns 0 if the hard link was successfully extracted. Returns
1148 * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly
1149 * being unsupported by the volume. Returns a negative value if creating the
1150 * hard link failed due to ERROR_INVALID_FUNCTION.
1153 win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
1154 struct apply_args *args)
1158 /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
1159 * but it's only available on Windows 7 and later.
1161 * Otherwise, CreateHardLinkW() will apparently return
1162 * ERROR_INVALID_FUNCTION if the volume does not support hard links. */
1164 DEBUG("Creating hard link \"%ls => %ls\"",
1165 output_path, inode->i_extracted_file);
1167 if (running_on_windows_7_or_later() &&
1168 !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS))
1169 goto hard_links_unsupported;
1171 if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
1174 err = GetLastError();
1175 if (err != ERROR_INVALID_FUNCTION) {
1176 ERROR("Can't create hard link \"%ls => %ls\"",
1177 output_path, inode->i_extracted_file);
1179 return WIMLIB_ERR_LINK;
1181 hard_links_unsupported:
1182 args->num_hard_links_failed++;
1183 if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
1184 if (running_on_windows_7_or_later())
1186 WARNING("Extracting duplicate copy of \"%ls\" "
1187 "rather than hard link", output_path);
1189 WARNING("Can't create hard link \"%ls\" => \"%ls\":\n"
1190 " Volume does not support hard links!\n"
1191 " Falling back to extracting a copy of the file.",
1192 output_path, inode->i_extracted_file);
1195 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS)
1196 WARNING("Suppressing further hard linking warnings...");
1200 /* Extract a file, directory, reparse point, or hard link to an
1201 * already-extracted file using the Win32 API */
1203 win32_do_apply_dentry(const wchar_t *output_path,
1204 size_t output_path_num_chars,
1205 struct wim_dentry *dentry,
1206 struct apply_args *args)
1209 struct wim_inode *inode = dentry->d_inode;
1211 ret = win32_check_vol_flags(output_path, dentry, args);
1214 if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1215 /* Linked file, with another name already extracted. Create a
1217 ret = win32_try_hard_link(output_path, inode, args);
1220 /* Negative return value from win32_try_hard_link() indicates
1221 * that hard links are probably not supported by the volume.
1222 * Fall back to extracting a copy of the file. */
1225 /* If this is a reparse point and the volume does not support reparse
1226 * points, just skip it completely. */
1227 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1228 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1230 WARNING("Not extracting reparse point \"%ls\"", output_path);
1232 /* Create the file, directory, or reparse point, and extract the
1234 ret = win32_extract_streams(dentry, output_path, args);
1238 if (inode->i_extracted_file == NULL) {
1239 const struct wim_lookup_table_entry *lte;
1241 /* Tally bytes extracted, including all alternate data streams,
1242 * unless we extracted a hard link (or, at least extracted a
1243 * name that was supposed to be a hard link) */
1244 for (unsigned i = 0; i <= inode->i_num_ads; i++) {
1245 lte = inode_stream_lte_resolved(inode, i);
1247 args->progress.extract.completed_bytes +=
1248 wim_resource_size(lte);
1250 if (inode->i_nlink > 1) {
1251 /* Save extracted path for a later call to
1252 * CreateHardLinkW() if this inode has multiple links.
1254 inode->i_extracted_file = WSTRDUP(output_path);
1255 if (!inode->i_extracted_file)
1256 return WIMLIB_ERR_NOMEM;
1262 /* Set timestamps on an extracted file using the Win32 API */
1264 win32_do_apply_dentry_timestamps(const wchar_t *path,
1265 size_t path_num_chars,
1266 struct wim_dentry *dentry,
1267 struct apply_args *args)
1271 const struct wim_inode *inode = dentry->d_inode;
1273 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1274 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1276 /* Skip reparse points not extracted */
1280 /* Windows doesn't let you change the timestamps of the root directory
1281 * (at least on FAT, which is dumb but expected since FAT doesn't store
1282 * any metadata about the root directory...) */
1283 if (path_is_root_of_drive(path))
1286 DEBUG("Opening \"%ls\" to set timestamps", path);
1287 h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
1288 if (h == INVALID_HANDLE_VALUE) {
1289 err = GetLastError();
1293 FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1294 .dwHighDateTime = inode->i_creation_time >> 32};
1295 FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1296 .dwHighDateTime = inode->i_last_access_time >> 32};
1297 FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1298 .dwHighDateTime = inode->i_last_write_time >> 32};
1300 DEBUG("Calling SetFileTime() on \"%ls\"", path);
1301 if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1302 err = GetLastError();
1306 DEBUG("Closing \"%ls\"", path);
1307 if (!CloseHandle(h)) {
1308 err = GetLastError();
1313 /* Only warn if setting timestamps failed; still return 0. */
1314 WARNING("Can't set timestamps on \"%ls\"", path);
1320 #endif /* __WIN32__ */