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 <aclapi.h> /* for SetSecurityInfo() */
32 #include "wimlib/win32_common.h"
34 #include "wimlib/apply.h"
35 #include "wimlib/dentry.h"
36 #include "wimlib/endianness.h"
37 #include "wimlib/error.h"
38 #include "wimlib/lookup_table.h"
39 #include "wimlib/metadata.h"
40 #include "wimlib/reparse.h"
41 #include "wimlib/security.h"
43 #define MAX_CREATE_HARD_LINK_WARNINGS 5
44 #define MAX_CREATE_SOFT_LINK_WARNINGS 5
46 #define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1
47 #define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1
49 static const wchar_t *apply_access_denied_msg =
50 L"If you are not running this program as the administrator, you may\n"
51 " need to do so, so that all data and metadata can be extracted\n"
52 " exactly as the origignal copy. However, if you do not care that\n"
53 " the security descriptors are extracted correctly, you could run\n"
54 " `wimlib-imagex apply' with the --no-acls flag instead.\n"
59 win32_extract_try_rpfix(u8 *rpbuf,
60 const wchar_t *extract_root_realpath,
61 unsigned extract_root_realpath_nchars)
63 struct reparse_data rpdata;
66 size_t stripped_nchars;
67 wchar_t *stripped_target;
68 wchar_t stripped_target_nchars;
71 utf16lechar *new_target;
72 utf16lechar *new_print_name;
73 size_t new_target_nchars;
74 size_t new_print_name_nchars;
77 ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
82 if (extract_root_realpath[0] == L'\0' ||
83 extract_root_realpath[1] != L':' ||
84 extract_root_realpath[2] != L'\\')
86 ERROR("Can't understand full path format \"%ls\". "
87 "Try turning reparse point fixups off...",
88 extract_root_realpath);
89 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
92 ret = parse_substitute_name(rpdata.substitute_name,
93 rpdata.substitute_name_nbytes,
97 stripped_nchars = ret;
98 target = rpdata.substitute_name;
99 target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar);
100 stripped_target = target + 6;
101 stripped_target_nchars = target_nchars - stripped_nchars;
103 new_target = alloca((6 + extract_root_realpath_nchars +
104 stripped_target_nchars) * sizeof(utf16lechar));
107 if (stripped_nchars == 6) {
108 /* Include \??\ prefix if it was present before */
109 wmemcpy(p, L"\\??\\", 4);
113 /* Print name excludes the \??\ if present. */
115 if (stripped_nchars != 0) {
116 /* Get drive letter from real path to extract root, if a drive
117 * letter was present before. */
118 *p++ = extract_root_realpath[0];
119 *p++ = extract_root_realpath[1];
121 /* Copy the rest of the extract root */
122 wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
123 p += extract_root_realpath_nchars - 2;
125 /* Append the stripped target */
126 wmemcpy(p, stripped_target, stripped_target_nchars);
127 p += stripped_target_nchars;
128 new_target_nchars = p - new_target;
129 new_print_name_nchars = p - new_print_name;
131 if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE ||
132 new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE)
134 ERROR("Path names too long to do reparse point fixup!");
135 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
137 rpdata.substitute_name = new_target;
138 rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar);
139 rpdata.print_name = new_print_name;
140 rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar);
141 return make_reparse_buffer(&rpdata, rpbuf);
144 /* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on
145 * an extracted reparse point. */
147 win32_set_reparse_data(HANDLE h,
148 const struct wim_inode *inode,
149 const struct wim_lookup_table_entry *lte,
151 struct apply_args *args)
154 u8 rpbuf[REPARSE_POINT_MAX_SIZE];
157 DEBUG("Setting reparse data on \"%ls\"", path);
159 ret = wim_inode_get_reparse_data(inode, rpbuf);
163 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX &&
164 (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
165 inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) &&
166 !inode->i_not_rpfixed)
168 ret = win32_extract_try_rpfix(rpbuf,
169 args->target_realpath,
170 args->target_realpath_len);
172 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
175 /* Set the reparse data on the open file using the
176 * FSCTL_SET_REPARSE_POINT ioctl.
178 * There are contradictions in Microsoft's documentation for this:
180 * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED,
181 * lpOverlapped is ignored."
183 * --- So setting lpOverlapped to NULL is okay since it's ignored.
185 * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an
186 * operation returns no output data and lpOutBuffer is NULL,
187 * DeviceIoControl makes use of lpBytesReturned. After such an
188 * operation, the value of lpBytesReturned is meaningless."
190 * --- So lpOverlapped not really ignored, as it affects another
191 * parameter. This is the actual behavior: lpBytesReturned must be
192 * specified, even though lpBytesReturned is documented as:
194 * "Not used with this operation; set to NULL."
196 if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf,
197 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
199 &bytesReturned /* lpBytesReturned */,
200 NULL /* lpOverlapped */))
202 DWORD err = GetLastError();
203 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
205 args->num_soft_links_failed++;
206 if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) {
207 WARNING("Can't set reparse data on \"%ls\": Access denied!\n"
208 " You may be trying to extract a symbolic "
210 " SeCreateSymbolicLink privilege, which by "
211 "default non-Administrator\n"
212 " accounts do not have.", path);
214 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
215 WARNING("Suppressing further warnings regarding failure to extract\n"
216 " reparse points due to insufficient privileges...");
219 ERROR("Failed to set reparse data on \"%ls\"", path);
221 if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
222 inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
223 return WIMLIB_ERR_LINK;
225 return WIMLIB_ERR_WRITE;
231 /* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the
232 * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */
234 win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path)
237 if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION,
238 &format, sizeof(USHORT),
240 &bytesReturned, NULL))
242 /* Could be a warning only, but we only call this if the volume
243 * supports compression. So I'm calling this an error. */
244 DWORD err = GetLastError();
245 ERROR("Failed to set compression flag on \"%ls\"", path);
247 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
248 return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
250 return WIMLIB_ERR_WRITE;
255 /* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */
257 win32_set_sparse(HANDLE hFile, const wchar_t *path)
260 if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE,
263 &bytesReturned, NULL))
265 /* Could be a warning only, but we only call this if the volume
266 * supports sparse files. So I'm calling this an error. */
267 DWORD err = GetLastError();
268 WARNING("Failed to set sparse flag on \"%ls\"", path);
270 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
271 return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
273 return WIMLIB_ERR_WRITE;
279 * Sets the security descriptor on an extracted file.
282 win32_set_security_data(const struct wim_inode *inode,
285 struct apply_args *args)
287 PSECURITY_DESCRIPTOR descriptor;
290 const struct wim_security_data *sd;
292 SECURITY_INFORMATION securityInformation = 0;
299 BOOL owner_defaulted;
300 BOOL group_defaulted;
306 sd = wim_const_security_data(args->w);
307 descriptor = sd->descriptors[inode->i_security_id];
309 GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted);
311 securityInformation |= OWNER_SECURITY_INFORMATION;
313 GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted);
315 securityInformation |= GROUP_SECURITY_INFORMATION;
317 GetSecurityDescriptorDacl(descriptor, &dacl_present,
318 &dacl, &dacl_defaulted);
320 securityInformation |= DACL_SECURITY_INFORMATION;
322 GetSecurityDescriptorSacl(descriptor, &sacl_present,
323 &sacl, &sacl_defaulted);
325 securityInformation |= SACL_SECURITY_INFORMATION;
328 if (securityInformation == 0)
330 if (SetSecurityInfo(hFile, SE_FILE_OBJECT,
331 securityInformation, owner, group, dacl, sacl))
333 err = GetLastError();
334 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)
337 case ERROR_PRIVILEGE_NOT_HELD:
338 if (securityInformation & SACL_SECURITY_INFORMATION) {
339 n = args->num_set_sacl_priv_notheld++;
340 securityInformation &= ~SACL_SECURITY_INFORMATION;
342 if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
344 "We don't have enough privileges to set the full security\n"
345 " descriptor on \"%ls\"!\n", path);
346 if (args->num_set_sd_access_denied +
347 args->num_set_sacl_priv_notheld == 1)
349 WARNING("%ls", apply_access_denied_msg);
351 WARNING("Re-trying with SACL omitted.\n", path);
352 } else if (n == MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
354 "Suppressing further 'privileges not held' error messages when setting\n"
355 " security descriptors.");
360 case ERROR_INVALID_OWNER:
361 case ERROR_ACCESS_DENIED:
362 n = args->num_set_sd_access_denied++;
363 if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
364 WARNING("Failed to set security descriptor on \"%ls\": "
365 "Access denied!\n", path);
366 if (args->num_set_sd_access_denied +
367 args->num_set_sacl_priv_notheld == 1)
369 WARNING("%ls", apply_access_denied_msg);
371 } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
373 "Suppressing further access denied error messages when setting\n"
374 " security descriptors");
379 ERROR("Failed to set security descriptor on \"%ls\"", path);
381 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
382 return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
384 return WIMLIB_ERR_WRITE;
390 win32_extract_chunk(const void *buf, size_t len, void *arg)
392 HANDLE hStream = arg;
394 DWORD nbytes_written;
395 wimlib_assert(len <= 0xffffffff);
397 if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) ||
398 nbytes_written != len)
400 DWORD err = GetLastError();
401 ERROR("WriteFile(): write error");
403 return WIMLIB_ERR_WRITE;
409 do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte)
411 return extract_wim_resource(lte, wim_resource_size(lte),
412 win32_extract_chunk, hStream);
415 struct win32_encrypted_extract_ctx {
416 const struct wim_lookup_table_entry *lte;
421 win32_encrypted_import_cb(unsigned char *data, void *_ctx,
422 unsigned long *len_p)
424 struct win32_encrypted_extract_ctx *ctx = _ctx;
425 unsigned long len = *len_p;
426 const struct wim_lookup_table_entry *lte = ctx->lte;
428 len = min(len, wim_resource_size(lte) - ctx->offset);
430 if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data))
431 return ERROR_READ_FAULT;
435 return ERROR_SUCCESS;
438 /* Create an encrypted file and extract the raw encrypted data to it.
440 * @path: Path to encrypted file to create.
441 * @lte: WIM lookup_table entry for the raw encrypted data.
443 * This is separate from do_win32_extract_stream() because the WIM is supposed
444 * to contain the *raw* encrypted data, which needs to be extracted ("imported")
445 * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and
446 * CloseEncryptedFileRaw().
448 * Returns 0 on success; nonzero on failure.
451 do_win32_extract_encrypted_stream(const wchar_t *path,
452 const struct wim_lookup_table_entry *lte)
457 DEBUG("Opening file \"%ls\" to extract raw encrypted data", path);
459 ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx);
461 ERROR("Failed to open \"%ls\" to write raw encrypted data", path);
463 return WIMLIB_ERR_OPEN;
467 struct win32_encrypted_extract_ctx ctx;
471 ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx);
472 if (ret == ERROR_SUCCESS) {
475 ret = WIMLIB_ERR_WRITE;
476 ERROR("Failed to extract encrypted file \"%ls\"", path);
479 CloseEncryptedFileRaw(file_ctx);
484 path_is_root_of_drive(const wchar_t *path)
489 if (*path != L'/' && *path != L'\\') {
490 if (*(path + 1) == L':')
495 while (*path == L'/' || *path == L'\\')
497 return (*path == L'\0');
501 win32_mask_attributes(DWORD i_attributes)
503 return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE |
504 FILE_ATTRIBUTE_COMPRESSED |
505 FILE_ATTRIBUTE_REPARSE_POINT |
506 FILE_ATTRIBUTE_DIRECTORY |
507 FILE_ATTRIBUTE_ENCRYPTED |
508 FILE_FLAG_DELETE_ON_CLOSE |
509 FILE_FLAG_NO_BUFFERING |
510 FILE_FLAG_OPEN_NO_RECALL |
511 FILE_FLAG_OVERLAPPED |
512 FILE_FLAG_RANDOM_ACCESS |
513 /*FILE_FLAG_SESSION_AWARE |*/
514 FILE_FLAG_SEQUENTIAL_SCAN |
515 FILE_FLAG_WRITE_THROUGH);
519 win32_get_create_flags_and_attributes(DWORD i_attributes)
522 * Some attributes cannot be set by passing them to CreateFile(). In
525 * FILE_ATTRIBUTE_DIRECTORY:
526 * CreateDirectory() must be called instead of CreateFile().
528 * FILE_ATTRIBUTE_SPARSE_FILE:
530 * See: win32_set_sparse().
532 * FILE_ATTRIBUTE_COMPRESSED:
533 * Not clear from the documentation, but apparently this needs an
535 * See: win32_set_compressed().
537 * FILE_ATTRIBUTE_REPARSE_POINT:
538 * Needs an ioctl, with the reparse data specified.
539 * See: win32_set_reparse_data().
541 * In addition, clear any file flags in the attributes that we don't
542 * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and
543 * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application.
545 return win32_mask_attributes(i_attributes) |
546 FILE_FLAG_OPEN_REPARSE_POINT |
547 FILE_FLAG_BACKUP_SEMANTICS;
550 /* Set compression and/or sparse attributes on a stream, if supported by the
553 win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode,
554 struct wim_lookup_table_entry *unnamed_stream_lte,
555 const wchar_t *path, unsigned vol_flags)
559 if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) {
560 if (vol_flags & FILE_FILE_COMPRESSION) {
561 ret = win32_set_compression_state(hFile,
562 COMPRESSION_FORMAT_DEFAULT,
567 DEBUG("Cannot set compression attribute on \"%ls\": "
568 "volume does not support transparent compression",
573 if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
574 if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) {
575 DEBUG("Setting sparse flag on \"%ls\"", path);
576 ret = win32_set_sparse(hFile, path);
580 DEBUG("Cannot set sparse attribute on \"%ls\": "
581 "volume does not support sparse files",
588 /* Pre-create directories; extract encrypted streams */
590 win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
591 const struct wim_lookup_table_entry *lte,
593 DWORD *creationDisposition_ret,
594 unsigned int vol_flags)
599 /* Directories must be created with CreateDirectoryW(). Then the call
600 * to CreateFileW() will merely open the directory that was already
601 * created rather than creating a new file. */
602 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY &&
603 !path_is_root_of_drive(path)) {
604 if (!CreateDirectoryW(path, NULL)) {
605 err = GetLastError();
606 if (err != ERROR_ALREADY_EXISTS) {
607 ERROR("Failed to create directory \"%ls\"",
610 return WIMLIB_ERR_MKDIR;
613 DEBUG("Created directory \"%ls\"", path);
614 *creationDisposition_ret = OPEN_EXISTING;
616 if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
617 vol_flags & FILE_SUPPORTS_ENCRYPTION)
619 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
620 unsigned remaining_sharing_violations = 100;
621 while (!EncryptFile(path)) {
622 if (remaining_sharing_violations &&
623 err == ERROR_SHARING_VIOLATION)
625 WARNING("Couldn't encrypt directory \"%ls\" "
626 "due to sharing violation; re-trying "
627 "after 100 ms", path);
629 remaining_sharing_violations--;
631 err = GetLastError();
632 ERROR("Failed to encrypt directory \"%ls\"",
635 return WIMLIB_ERR_WRITE;
639 ret = do_win32_extract_encrypted_stream(path, lte);
642 DEBUG("Extracted encrypted file \"%ls\"", path);
644 *creationDisposition_ret = OPEN_EXISTING;
647 /* Set file attributes if we created the file. Otherwise, we haven't
648 * created the file set and we will set the attributes in the call to
651 * The FAT filesystem does not let you change the attributes of the root
652 * directory, so treat that as a special case and do not set attributes.
654 if (*creationDisposition_ret == OPEN_EXISTING &&
655 !path_is_root_of_drive(path))
657 if (!SetFileAttributesW(path,
658 win32_mask_attributes(inode->i_attributes)))
660 err = GetLastError();
661 ERROR("Failed to set attributes on \"%ls\"", path);
663 return WIMLIB_ERR_WRITE;
669 /* Set security descriptor and extract stream data or reparse data (skip the
670 * unnamed data stream of encrypted files, which was already extracted). */
672 win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry,
673 const struct wim_lookup_table_entry *lte,
674 const wchar_t *stream_path,
675 const wchar_t *stream_name_utf16,
676 struct apply_args *args)
679 const struct wim_inode *inode = dentry->d_inode;
680 if (stream_name_utf16 == NULL) {
681 /* Unnamed stream. */
683 /* Set security descriptor, unless the extract_flags indicate
684 * not to or the volume does not supported it. Note that this
685 * is only done when the unnamed stream is being extracted, as
686 * security descriptors are per-file and not per-stream. */
687 if (inode->i_security_id >= 0 &&
688 !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
689 && (args->vol_flags & FILE_PERSISTENT_ACLS))
691 ret = win32_set_security_data(inode, h, stream_path, args);
696 /* Handle reparse points. The data for them needs to be set
697 * using a special ioctl. Note that the reparse point may have
698 * been created using CreateFileW() in the case of
699 * non-directories or CreateDirectoryW() in the case of
700 * directories; but the ioctl works either way. Also, it is
701 * only this step that actually sets the
702 * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it
703 * using SetFileAttributesW() or CreateFileW().
705 * If the volume does not support reparse points we simply
706 * ignore the reparse data. (N.B. the code currently doesn't
707 * actually reach this case because reparse points are skipped
708 * entirely on such volumes.) */
709 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
710 if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
711 ret = win32_set_reparse_data(h, inode,
717 DEBUG("Cannot set reparse data on \"%ls\": volume "
718 "does not support reparse points", stream_path);
720 } else if (lte != NULL &&
721 !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION &&
722 inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
724 /* Extract the data of the unnamed stream, unless the
725 * lookup table entry is NULL (indicating an empty
726 * stream for which no data needs to be extracted), or
727 * the stream is encrypted and therefore was already
728 * extracted as a special case. */
729 ret = do_win32_extract_stream(h, lte);
734 if (dentry_has_short_name(dentry))
735 SetFileShortNameW(h, dentry->short_name);
736 else if (running_on_windows_7_or_later())
737 SetFileShortNameW(h, L"");
739 /* Extract the data for a named data stream. */
741 DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")",
742 stream_path, wim_resource_size(lte));
743 ret = do_win32_extract_stream(h, lte);
750 win32_decrypt_file(HANDLE open_handle, const wchar_t *path)
753 /* We cannot call DecryptFileW() while there is an open handle to the
754 * file. So close it first. */
755 if (!CloseHandle(open_handle)) {
756 err = GetLastError();
757 ERROR("Failed to close handle for \"%ls\"", path);
759 return WIMLIB_ERR_WRITE;
761 if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) {
762 err = GetLastError();
763 ERROR("Failed to decrypt file \"%ls\"", path);
765 return WIMLIB_ERR_WRITE;
771 * Create and extract a stream to a file, or create a directory, using the
774 * This handles reparse points, directories, alternate data streams, encrypted
775 * files, compressed files, etc.
777 * @dentry: WIM dentry for the file or directory being extracted.
779 * @path: Path to extract the file to.
781 * @stream_name_utf16:
782 * Name of the stream, or NULL if the stream is unnamed. This will
783 * be called with a NULL stream_name_utf16 before any non-NULL
784 * stream_name_utf16's.
786 * @lte: WIM lookup table entry for the stream. May be NULL to indicate
787 * a stream of length 0.
789 * @args: Additional apply context, including flags indicating supported
792 * Returns 0 on success; nonzero on failure.
795 win32_extract_stream(const struct wim_dentry *dentry,
797 const wchar_t *stream_name_utf16,
798 struct wim_lookup_table_entry *lte,
799 struct apply_args *args)
801 wchar_t *stream_path;
805 DWORD creationDisposition = CREATE_ALWAYS;
806 DWORD requestedAccess;
807 BY_HANDLE_FILE_INFORMATION file_info;
808 unsigned remaining_sharing_violations = 1000;
809 const struct wim_inode *inode = dentry->d_inode;
811 if (stream_name_utf16) {
812 /* Named stream. Create a buffer that contains the UTF-16LE
813 * string [./]path:stream_name_utf16. This is needed to
814 * create and open the stream using CreateFileW(). I'm not
815 * aware of any other APIs to do this. Note: the '$DATA' suffix
816 * seems to be unneeded. Additional note: a "./" prefix needs
817 * to be added when the path is not absolute to avoid ambiguity
818 * with drive letters. */
819 size_t stream_path_nchars;
821 size_t stream_name_nchars;
822 const wchar_t *prefix;
824 path_nchars = wcslen(path);
825 stream_name_nchars = wcslen(stream_name_utf16);
826 stream_path_nchars = path_nchars + 1 + stream_name_nchars;
827 if (path[0] != cpu_to_le16(L'\0') &&
828 path[0] != cpu_to_le16(L'/') &&
829 path[0] != cpu_to_le16(L'\\') &&
830 path[1] != cpu_to_le16(L':'))
833 stream_path_nchars += 2;
837 stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
838 swprintf(stream_path, L"%ls%ls:%ls",
839 prefix, path, stream_name_utf16);
841 /* Unnamed stream; its path is just the path to the file itself.
843 stream_path = (wchar_t*)path;
845 ret = win32_begin_extract_unnamed_stream(inode, lte, path,
846 &creationDisposition,
852 DEBUG("Opening \"%ls\"", stream_path);
853 /* DELETE access is needed for SetFileShortNameW(), for some reason. */
854 requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE |
855 ACCESS_SYSTEM_SECURITY;
857 /* Open the stream to be extracted. Depending on what we have set
858 * creationDisposition to, we may be creating this for the first time,
859 * or we may be opening on existing stream we already created using
860 * CreateDirectoryW() or OpenEncryptedFileRawW(). */
861 h = CreateFileW(stream_path,
866 win32_get_create_flags_and_attributes(inode->i_attributes),
868 if (h == INVALID_HANDLE_VALUE) {
869 err = GetLastError();
870 if (err == ERROR_ACCESS_DENIED &&
871 path_is_root_of_drive(stream_path))
876 if ((err == ERROR_PRIVILEGE_NOT_HELD ||
877 err == ERROR_ACCESS_DENIED) &&
878 (requestedAccess & ACCESS_SYSTEM_SECURITY))
880 /* Try opening the file again without privilege to
882 requestedAccess &= ~ACCESS_SYSTEM_SECURITY;
885 if (err == ERROR_SHARING_VIOLATION) {
886 if (remaining_sharing_violations) {
887 --remaining_sharing_violations;
888 /* This can happen when restoring encrypted directories
889 * for some reason. Probably a bug in EncryptFile(). */
890 WARNING("Couldn't open \"%ls\" due to sharing violation; "
891 "re-trying after 100ms", stream_path);
895 ERROR("Too many sharing violations; giving up...");
898 if (creationDisposition == OPEN_EXISTING)
899 ERROR("Failed to open \"%ls\"", stream_path);
901 ERROR("Failed to create \"%ls\"", stream_path);
904 ret = WIMLIB_ERR_OPEN;
908 /* Check the attributes of the file we just opened, and remove
909 * encryption or compression if either was set by default but is not
910 * supposed to be set based on the WIM inode attributes. */
911 if (!GetFileInformationByHandle(h, &file_info)) {
912 err = GetLastError();
913 ERROR("Failed to get attributes of \"%ls\"", stream_path);
915 ret = WIMLIB_ERR_STAT;
916 goto fail_close_handle;
919 /* Remove encryption? */
920 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED &&
921 !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
923 /* File defaulted to encrypted due to being in an encrypted
924 * directory, but is not actually supposed to be encrypted.
926 * This is a workaround, because I'm not aware of any way to
927 * directly (e.g. with CreateFileW()) create an unencrypted file
928 * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */
929 ret = win32_decrypt_file(h, stream_path);
931 goto fail; /* win32_decrypt_file() closed the handle. */
932 creationDisposition = OPEN_EXISTING;
936 /* Remove compression? */
937 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED &&
938 !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED))
940 /* Similar to the encrypted case, above, if the file defaulted
941 * to compressed due to being in an compressed directory, but is
942 * not actually supposed to be compressed, explicitly set the
943 * compression format to COMPRESSION_FORMAT_NONE. */
944 ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE,
947 goto fail_close_handle;
950 /* Set compression and/or sparse attributes if needed */
951 ret = win32_set_special_stream_attributes(h, inode, lte, path,
955 goto fail_close_handle;
957 /* At this point we have at least created the needed stream with the
958 * appropriate attributes. We have yet to set the appropriate security
959 * descriptor and actually extract the stream data (other than for
960 * extracted files, which were already extracted).
961 * win32_finish_extract_stream() handles these additional steps. */
962 ret = win32_finish_extract_stream(h, dentry, lte, stream_path,
963 stream_name_utf16, args);
965 goto fail_close_handle;
967 /* Done extracting the stream. Close the handle and return. */
968 DEBUG("Closing \"%ls\"", stream_path);
969 if (!CloseHandle(h)) {
970 err = GetLastError();
971 ERROR("Failed to close \"%ls\"", stream_path);
973 ret = WIMLIB_ERR_WRITE;
981 ERROR("Error extracting \"%ls\"", stream_path);
987 * Creates a file, directory, or reparse point and extracts all streams to it
988 * (unnamed data stream and/or reparse point stream, plus any alternate data
989 * streams). Handles sparse, compressed, and/or encrypted files.
991 * @dentry: WIM dentry for this file or directory.
992 * @path: UTF-16LE external path to extract the inode to.
993 * @args: Additional extraction context.
995 * Returns 0 on success; nonzero on failure.
998 win32_extract_streams(const struct wim_dentry *dentry,
999 const wchar_t *path, struct apply_args *args)
1001 struct wim_lookup_table_entry *unnamed_lte;
1003 const struct wim_inode *inode = dentry->d_inode;
1005 /* First extract the unnamed stream. */
1007 unnamed_lte = inode_unnamed_lte_resolved(inode);
1008 ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args);
1012 /* Extract any named streams, if supported by the volume. */
1014 if (!(args->vol_flags & FILE_NAMED_STREAMS))
1016 for (u16 i = 0; i < inode->i_num_ads; i++) {
1017 const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i];
1019 /* Skip the unnamed stream if it's in the ADS entries (we
1020 * already extracted it...) */
1021 if (ads_entry->stream_name_nbytes == 0)
1024 /* Skip special UNIX data entries (see documentation for
1025 * WIMLIB_ADD_FLAG_UNIX_DATA) */
1026 if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES
1027 && !memcmp(ads_entry->stream_name,
1028 WIMLIB_UNIX_DATA_TAG_UTF16LE,
1029 WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES))
1032 /* Extract the named stream */
1033 ret = win32_extract_stream(dentry,
1035 ads_entry->stream_name,
1046 dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore)
1048 dentry->d_inode->i_visited = 0;
1053 dentry_get_features(struct wim_dentry *dentry, void *_features_p)
1056 DWORD *features_p = _features_p;
1057 struct wim_inode *inode = dentry->d_inode;
1059 if (inode->i_visited) {
1060 features |= FILE_SUPPORTS_HARD_LINKS;
1062 inode->i_visited = 1;
1063 if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE)
1064 features |= FILE_SUPPORTS_SPARSE_FILES;
1065 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
1066 features |= FILE_SUPPORTS_REPARSE_POINTS;
1067 for (unsigned i = 0; i < inode->i_num_ads; i++)
1068 if (inode->i_ads_entries[i].stream_name_nbytes)
1069 features |= FILE_NAMED_STREAMS;
1070 if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
1071 features |= FILE_SUPPORTS_ENCRYPTION;
1072 if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
1073 features |= FILE_FILE_COMPRESSION;
1074 if (inode->i_security_id != -1)
1075 features |= FILE_PERSISTENT_ACLS;
1077 *features_p |= features;
1081 /* If not done already, load the supported feature flags for the volume onto
1082 * which the image is being extracted, and warn the user about any missing
1083 * features that could be important. */
1085 win32_check_vol_flags(const wchar_t *output_path,
1086 struct wim_dentry *root, struct apply_args *args)
1088 DWORD dentry_features = 0;
1089 DWORD missing_features;
1091 if (args->have_vol_flags)
1094 for_dentry_in_tree(root, dentry_clear_inode_visited, NULL);
1095 for_dentry_in_tree(root, dentry_get_features, &dentry_features);
1097 win32_get_vol_flags(output_path, &args->vol_flags);
1098 args->have_vol_flags = true;
1100 missing_features = dentry_features & ~args->vol_flags;
1102 /* Warn the user about data that may not be extracted. */
1103 if (missing_features & FILE_SUPPORTS_SPARSE_FILES)
1104 WARNING("Volume does not support sparse files!\n"
1105 " Sparse files will be extracted as non-sparse.");
1106 if (missing_features & FILE_SUPPORTS_REPARSE_POINTS)
1107 WARNING("Volume does not support reparse points!\n"
1108 " Reparse point data will not be extracted.");
1109 if (missing_features & FILE_NAMED_STREAMS) {
1110 WARNING("Volume does not support named data streams!\n"
1111 " Named data streams will not be extracted.");
1113 if (missing_features & FILE_SUPPORTS_ENCRYPTION) {
1114 WARNING("Volume does not support encryption!\n"
1115 " Encrypted files will be extracted as raw data.");
1117 if (missing_features & FILE_FILE_COMPRESSION) {
1118 WARNING("Volume does not support transparent compression!\n"
1119 " Compressed files will be extracted as non-compressed.");
1121 if (missing_features & FILE_PERSISTENT_ACLS) {
1122 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
1123 ERROR("Strict ACLs requested, but the volume does not "
1125 return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
1127 WARNING("Volume does not support persistent ACLS!\n"
1128 " File permissions will not be extracted.");
1131 if (running_on_windows_7_or_later() &&
1132 (missing_features & FILE_SUPPORTS_HARD_LINKS))
1134 WARNING("Volume does not support hard links!\n"
1135 " Hard links will be extracted as duplicate files.");
1141 * Try extracting a hard link.
1143 * @output_path: Path to link to be extracted.
1145 * @inode: WIM inode that the link is to; inode->i_extracted_file
1146 * the path to a name of the file that has already been
1147 * extracted (we use this to create the hard link).
1149 * @args: Additional apply context, used here to keep track of
1150 * the number of times creating a hard link failed due to
1151 * ERROR_INVALID_FUNCTION. This error should indicate that hard
1152 * links are not supported by the volume, and we would like to
1153 * warn the user a few times, but not too many times.
1155 * Returns 0 if the hard link was successfully extracted. Returns
1156 * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly
1157 * being unsupported by the volume. Returns a negative value if creating the
1158 * hard link failed due to ERROR_INVALID_FUNCTION.
1161 win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
1162 struct apply_args *args)
1166 /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
1167 * but it's only available on Windows 7 and later.
1169 * Otherwise, CreateHardLinkW() will apparently return
1170 * ERROR_INVALID_FUNCTION if the volume does not support hard links. */
1172 DEBUG("Creating hard link \"%ls => %ls\"",
1173 output_path, inode->i_extracted_file);
1175 if (running_on_windows_7_or_later() &&
1176 !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS))
1177 goto hard_links_unsupported;
1179 if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
1182 err = GetLastError();
1183 if (err != ERROR_INVALID_FUNCTION) {
1184 ERROR("Can't create hard link \"%ls => %ls\"",
1185 output_path, inode->i_extracted_file);
1187 return WIMLIB_ERR_LINK;
1189 hard_links_unsupported:
1190 args->num_hard_links_failed++;
1191 if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
1192 if (running_on_windows_7_or_later())
1194 WARNING("Extracting duplicate copy of \"%ls\" "
1195 "rather than hard link", output_path);
1197 WARNING("Can't create hard link \"%ls\" => \"%ls\":\n"
1198 " Volume does not support hard links!\n"
1199 " Falling back to extracting a copy of the file.",
1200 output_path, inode->i_extracted_file);
1203 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS)
1204 WARNING("Suppressing further hard linking warnings...");
1208 /* Extract a file, directory, reparse point, or hard link to an
1209 * already-extracted file using the Win32 API */
1211 win32_do_apply_dentry(const wchar_t *output_path,
1212 size_t output_path_num_chars,
1213 struct wim_dentry *dentry,
1214 struct apply_args *args)
1217 struct wim_inode *inode = dentry->d_inode;
1219 ret = win32_check_vol_flags(output_path, dentry, args);
1222 if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1223 /* Linked file, with another name already extracted. Create a
1225 ret = win32_try_hard_link(output_path, inode, args);
1228 /* Negative return value from win32_try_hard_link() indicates
1229 * that hard links are probably not supported by the volume.
1230 * Fall back to extracting a copy of the file. */
1233 /* If this is a reparse point and the volume does not support reparse
1234 * points, just skip it completely. */
1235 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1236 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1238 WARNING("Not extracting reparse point \"%ls\"", output_path);
1240 /* Create the file, directory, or reparse point, and extract the
1242 ret = win32_extract_streams(dentry, output_path, args);
1246 if (inode->i_extracted_file == NULL) {
1247 const struct wim_lookup_table_entry *lte;
1249 /* Tally bytes extracted, including all alternate data streams,
1250 * unless we extracted a hard link (or, at least extracted a
1251 * name that was supposed to be a hard link) */
1252 for (unsigned i = 0; i <= inode->i_num_ads; i++) {
1253 lte = inode_stream_lte_resolved(inode, i);
1255 args->progress.extract.completed_bytes +=
1256 wim_resource_size(lte);
1258 if (inode->i_nlink > 1) {
1259 /* Save extracted path for a later call to
1260 * CreateHardLinkW() if this inode has multiple links.
1262 inode->i_extracted_file = WSTRDUP(output_path);
1263 if (!inode->i_extracted_file)
1264 return WIMLIB_ERR_NOMEM;
1270 /* Set timestamps on an extracted file using the Win32 API */
1272 win32_do_apply_dentry_timestamps(const wchar_t *path,
1273 size_t path_num_chars,
1274 struct wim_dentry *dentry,
1275 struct apply_args *args)
1279 const struct wim_inode *inode = dentry->d_inode;
1281 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1282 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1284 /* Skip reparse points not extracted */
1288 /* Windows doesn't let you change the timestamps of the root directory
1289 * (at least on FAT, which is dumb but expected since FAT doesn't store
1290 * any metadata about the root directory...) */
1291 if (path_is_root_of_drive(path))
1294 DEBUG("Opening \"%ls\" to set timestamps", path);
1295 h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
1296 if (h == INVALID_HANDLE_VALUE) {
1297 err = GetLastError();
1301 FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1302 .dwHighDateTime = inode->i_creation_time >> 32};
1303 FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1304 .dwHighDateTime = inode->i_last_access_time >> 32};
1305 FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1306 .dwHighDateTime = inode->i_last_write_time >> 32};
1308 DEBUG("Calling SetFileTime() on \"%ls\"", path);
1309 if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1310 err = GetLastError();
1314 DEBUG("Closing \"%ls\"", path);
1315 if (!CloseHandle(h)) {
1316 err = GetLastError();
1321 /* Only warn if setting timestamps failed; still return 0. */
1322 WARNING("Can't set timestamps on \"%ls\"", path);
1328 #endif /* __WIN32__ */