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 short_name = dentry->short_name;
731 if (!SetFileShortNameW(h, short_name)) {
733 DWORD err = GetLastError();
734 ERROR("Could not set short name on \"%ls\"", stream_path);
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,
1045 /* If not done already, load the supported feature flags for the volume onto
1046 * which the image is being extracted, and warn the user about any missing
1047 * features that could be important. */
1049 win32_check_vol_flags(const wchar_t *output_path, struct apply_args *args)
1051 if (args->have_vol_flags)
1054 win32_get_vol_flags(output_path, &args->vol_flags);
1055 args->have_vol_flags = true;
1056 /* Warn the user about data that may not be extracted. */
1057 if (!(args->vol_flags & FILE_SUPPORTS_SPARSE_FILES))
1058 WARNING("Volume does not support sparse files!\n"
1059 " Sparse files will be extracted as non-sparse.");
1060 if (!(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1061 WARNING("Volume does not support reparse points!\n"
1062 " Reparse point data will not be extracted.");
1063 if (!(args->vol_flags & FILE_NAMED_STREAMS)) {
1064 WARNING("Volume does not support named data streams!\n"
1065 " Named data streams will not be extracted.");
1067 if (!(args->vol_flags & FILE_SUPPORTS_ENCRYPTION)) {
1068 WARNING("Volume does not support encryption!\n"
1069 " Encrypted files will be extracted as raw data.");
1071 if (!(args->vol_flags & FILE_FILE_COMPRESSION)) {
1072 WARNING("Volume does not support transparent compression!\n"
1073 " Compressed files will be extracted as non-compressed.");
1075 if (!(args->vol_flags & FILE_PERSISTENT_ACLS)) {
1076 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
1077 ERROR("Strict ACLs requested, but the volume does not "
1079 return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
1081 WARNING("Volume does not support persistent ACLS!\n"
1082 " File permissions will not be extracted.");
1089 * Try extracting a hard link.
1091 * @output_path: Path to link to be extracted.
1093 * @inode: WIM inode that the link is to; inode->i_extracted_file
1094 * the path to a name of the file that has already been
1095 * extracted (we use this to create the hard link).
1097 * @args: Additional apply context, used here to keep track of
1098 * the number of times creating a hard link failed due to
1099 * ERROR_INVALID_FUNCTION. This error should indicate that hard
1100 * links are not supported by the volume, and we would like to
1101 * warn the user a few times, but not too many times.
1103 * Returns 0 if the hard link was successfully extracted. Returns
1104 * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly
1105 * being unsupported by the volume. Returns a negative value if creating the
1106 * hard link failed due to ERROR_INVALID_FUNCTION.
1109 win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
1110 struct apply_args *args)
1114 /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
1115 * but it's only available on Windows 7 and later. So no use
1116 * even checking it, really. Instead, CreateHardLinkW() will
1117 * apparently return ERROR_INVALID_FUNCTION if the volume does
1118 * not support hard links. */
1119 DEBUG("Creating hard link \"%ls => %ls\"",
1120 output_path, inode->i_extracted_file);
1121 if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
1124 err = GetLastError();
1125 if (err != ERROR_INVALID_FUNCTION) {
1126 ERROR("Can't create hard link \"%ls => %ls\"",
1127 output_path, inode->i_extracted_file);
1129 return WIMLIB_ERR_LINK;
1131 args->num_hard_links_failed++;
1132 if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
1133 WARNING("Can't create hard link \"%ls => %ls\":\n"
1134 " Volume does not support hard links!\n"
1135 " Falling back to extracting a copy of the file.",
1136 output_path, inode->i_extracted_file);
1138 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
1139 WARNING("Suppressing further hard linking warnings...");
1145 /* Extract a file, directory, reparse point, or hard link to an
1146 * already-extracted file using the Win32 API */
1148 win32_do_apply_dentry(const wchar_t *output_path,
1149 size_t output_path_num_chars,
1150 struct wim_dentry *dentry,
1151 struct apply_args *args)
1154 struct wim_inode *inode = dentry->d_inode;
1156 ret = win32_check_vol_flags(output_path, args);
1159 if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1160 /* Linked file, with another name already extracted. Create a
1162 ret = win32_try_hard_link(output_path, inode, args);
1165 /* Negative return value from win32_try_hard_link() indicates
1166 * that hard links are probably not supported by the volume.
1167 * Fall back to extracting a copy of the file. */
1170 /* If this is a reparse point and the volume does not support reparse
1171 * points, just skip it completely. */
1172 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1173 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1175 WARNING("Skipping extraction of reparse point \"%ls\":\n"
1176 " Not supported by destination filesystem",
1179 /* Create the file, directory, or reparse point, and extract the
1181 ret = win32_extract_streams(dentry, output_path, args);
1185 if (inode->i_extracted_file == NULL) {
1186 const struct wim_lookup_table_entry *lte;
1188 /* Tally bytes extracted, including all alternate data streams,
1189 * unless we extracted a hard link (or, at least extracted a
1190 * name that was supposed to be a hard link) */
1191 for (unsigned i = 0; i <= inode->i_num_ads; i++) {
1192 lte = inode_stream_lte_resolved(inode, i);
1194 args->progress.extract.completed_bytes +=
1195 wim_resource_size(lte);
1197 if (inode->i_nlink > 1) {
1198 /* Save extracted path for a later call to
1199 * CreateHardLinkW() if this inode has multiple links.
1201 inode->i_extracted_file = WSTRDUP(output_path);
1202 if (!inode->i_extracted_file)
1203 return WIMLIB_ERR_NOMEM;
1209 /* Set timestamps on an extracted file using the Win32 API */
1211 win32_do_apply_dentry_timestamps(const wchar_t *path,
1212 size_t path_num_chars,
1213 struct wim_dentry *dentry,
1214 struct apply_args *args)
1218 const struct wim_inode *inode = dentry->d_inode;
1220 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1221 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1223 /* Skip reparse points not extracted */
1227 /* Windows doesn't let you change the timestamps of the root directory
1228 * (at least on FAT, which is dumb but expected since FAT doesn't store
1229 * any metadata about the root directory...) */
1230 if (path_is_root_of_drive(path))
1233 DEBUG("Opening \"%ls\" to set timestamps", path);
1234 h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
1235 if (h == INVALID_HANDLE_VALUE) {
1236 err = GetLastError();
1240 FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1241 .dwHighDateTime = inode->i_creation_time >> 32};
1242 FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1243 .dwHighDateTime = inode->i_last_access_time >> 32};
1244 FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1245 .dwHighDateTime = inode->i_last_write_time >> 32};
1247 DEBUG("Calling SetFileTime() on \"%ls\"", path);
1248 if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1249 err = GetLastError();
1253 DEBUG("Closing \"%ls\"", path);
1254 if (!CloseHandle(h)) {
1255 err = GetLastError();
1260 /* Only warn if setting timestamps failed; still return 0. */
1261 WARNING("Can't set timestamps on \"%ls\"", path);
1267 #endif /* __WIN32__ */