3 #include <aclapi.h> /* for SetSecurityInfo() */
5 #include "win32_common.h"
6 #include "wimlib_internal.h"
8 #include "lookup_table.h"
9 #include "endianness.h"
11 #define MAX_CREATE_HARD_LINK_WARNINGS 5
12 #define MAX_CREATE_SOFT_LINK_WARNINGS 5
14 #define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1
15 #define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1
17 static const wchar_t *apply_access_denied_msg =
18 L"If you are not running this program as the administrator, you may\n"
19 " need to do so, so that all data and metadata can be extracted\n"
20 " exactly as the origignal copy. However, if you do not care that\n"
21 " the security descriptors are extracted correctly, you could run\n"
22 " `wimlib-imagex apply' with the --no-acls flag instead.\n"
27 win32_extract_try_rpfix(u8 *rpbuf,
28 const wchar_t *extract_root_realpath,
29 unsigned extract_root_realpath_nchars)
31 struct reparse_data rpdata;
34 size_t stripped_nchars;
35 wchar_t *stripped_target;
36 wchar_t stripped_target_nchars;
39 utf16lechar *new_target;
40 utf16lechar *new_print_name;
41 size_t new_target_nchars;
42 size_t new_print_name_nchars;
45 ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
50 if (extract_root_realpath[0] == L'\0' ||
51 extract_root_realpath[1] != L':' ||
52 extract_root_realpath[2] != L'\\')
54 ERROR("Can't understand full path format \"%ls\". "
55 "Try turning reparse point fixups off...",
56 extract_root_realpath);
57 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
60 ret = parse_substitute_name(rpdata.substitute_name,
61 rpdata.substitute_name_nbytes,
65 stripped_nchars = ret;
66 target = rpdata.substitute_name;
67 target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar);
68 stripped_target = target + 6;
69 stripped_target_nchars = target_nchars - stripped_nchars;
71 new_target = alloca((6 + extract_root_realpath_nchars +
72 stripped_target_nchars) * sizeof(utf16lechar));
75 if (stripped_nchars == 6) {
76 /* Include \??\ prefix if it was present before */
77 wmemcpy(p, L"\\??\\", 4);
81 /* Print name excludes the \??\ if present. */
83 if (stripped_nchars != 0) {
84 /* Get drive letter from real path to extract root, if a drive
85 * letter was present before. */
86 *p++ = extract_root_realpath[0];
87 *p++ = extract_root_realpath[1];
89 /* Copy the rest of the extract root */
90 wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
91 p += extract_root_realpath_nchars - 2;
93 /* Append the stripped target */
94 wmemcpy(p, stripped_target, stripped_target_nchars);
95 p += stripped_target_nchars;
96 new_target_nchars = p - new_target;
97 new_print_name_nchars = p - new_print_name;
99 if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE ||
100 new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE)
102 ERROR("Path names too long to do reparse point fixup!");
103 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
105 rpdata.substitute_name = new_target;
106 rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar);
107 rpdata.print_name = new_print_name;
108 rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar);
109 return make_reparse_buffer(&rpdata, rpbuf);
112 /* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on
113 * an extracted reparse point. */
115 win32_set_reparse_data(HANDLE h,
116 const struct wim_inode *inode,
117 const struct wim_lookup_table_entry *lte,
119 struct apply_args *args)
122 u8 rpbuf[REPARSE_POINT_MAX_SIZE];
125 DEBUG("Setting reparse data on \"%ls\"", path);
127 ret = wim_inode_get_reparse_data(inode, rpbuf);
131 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX &&
132 (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
133 inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) &&
134 !inode->i_not_rpfixed)
136 ret = win32_extract_try_rpfix(rpbuf,
137 args->target_realpath,
138 args->target_realpath_len);
140 return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
143 /* Set the reparse data on the open file using the
144 * FSCTL_SET_REPARSE_POINT ioctl.
146 * There are contradictions in Microsoft's documentation for this:
148 * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED,
149 * lpOverlapped is ignored."
151 * --- So setting lpOverlapped to NULL is okay since it's ignored.
153 * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an
154 * operation returns no output data and lpOutBuffer is NULL,
155 * DeviceIoControl makes use of lpBytesReturned. After such an
156 * operation, the value of lpBytesReturned is meaningless."
158 * --- So lpOverlapped not really ignored, as it affects another
159 * parameter. This is the actual behavior: lpBytesReturned must be
160 * specified, even though lpBytesReturned is documented as:
162 * "Not used with this operation; set to NULL."
164 if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf,
165 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
167 &bytesReturned /* lpBytesReturned */,
168 NULL /* lpOverlapped */))
170 DWORD err = GetLastError();
171 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
173 args->num_soft_links_failed++;
174 if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) {
175 WARNING("Can't set reparse data on \"%ls\": Access denied!\n"
176 " You may be trying to extract a symbolic "
178 " SeCreateSymbolicLink privilege, which by "
179 "default non-Administrator\n"
180 " accounts do not have.", path);
182 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
183 WARNING("Suppressing further warnings regarding failure to extract\n"
184 " reparse points due to insufficient privileges...");
187 ERROR("Failed to set reparse data on \"%ls\"", path);
189 if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
190 inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
191 return WIMLIB_ERR_LINK;
193 return WIMLIB_ERR_WRITE;
199 /* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the
200 * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */
202 win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path)
205 if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION,
206 &format, sizeof(USHORT),
208 &bytesReturned, NULL))
210 /* Could be a warning only, but we only call this if the volume
211 * supports compression. So I'm calling this an error. */
212 DWORD err = GetLastError();
213 ERROR("Failed to set compression flag on \"%ls\"", path);
215 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
216 return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
218 return WIMLIB_ERR_WRITE;
223 /* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */
225 win32_set_sparse(HANDLE hFile, const wchar_t *path)
228 if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE,
231 &bytesReturned, NULL))
233 /* Could be a warning only, but we only call this if the volume
234 * supports sparse files. So I'm calling this an error. */
235 DWORD err = GetLastError();
236 WARNING("Failed to set sparse 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;
247 * Sets the security descriptor on an extracted file.
250 win32_set_security_data(const struct wim_inode *inode,
253 struct apply_args *args)
255 PSECURITY_DESCRIPTOR descriptor;
258 const struct wim_security_data *sd;
260 SECURITY_INFORMATION securityInformation = 0;
267 BOOL owner_defaulted;
268 BOOL group_defaulted;
274 sd = wim_const_security_data(args->w);
275 descriptor = sd->descriptors[inode->i_security_id];
277 GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted);
279 securityInformation |= OWNER_SECURITY_INFORMATION;
281 GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted);
283 securityInformation |= GROUP_SECURITY_INFORMATION;
285 GetSecurityDescriptorDacl(descriptor, &dacl_present,
286 &dacl, &dacl_defaulted);
288 securityInformation |= DACL_SECURITY_INFORMATION;
290 GetSecurityDescriptorSacl(descriptor, &sacl_present,
291 &sacl, &sacl_defaulted);
293 securityInformation |= SACL_SECURITY_INFORMATION;
296 if (securityInformation == 0)
298 if (SetSecurityInfo(hFile, SE_FILE_OBJECT,
299 securityInformation, owner, group, dacl, sacl))
301 err = GetLastError();
302 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)
305 case ERROR_PRIVILEGE_NOT_HELD:
306 if (securityInformation & SACL_SECURITY_INFORMATION) {
307 n = args->num_set_sacl_priv_notheld++;
308 securityInformation &= ~SACL_SECURITY_INFORMATION;
310 if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
312 "We don't have enough privileges to set the full security\n"
313 " descriptor on \"%ls\"!\n", path);
314 if (args->num_set_sd_access_denied +
315 args->num_set_sacl_priv_notheld == 1)
317 WARNING("%ls", apply_access_denied_msg);
319 WARNING("Re-trying with SACL omitted.\n", path);
320 } else if (n == MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
322 "Suppressing further 'privileges not held' error messages when setting\n"
323 " security descriptors.");
328 case ERROR_INVALID_OWNER:
329 case ERROR_ACCESS_DENIED:
330 n = args->num_set_sd_access_denied++;
331 if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
332 WARNING("Failed to set security descriptor on \"%ls\": "
333 "Access denied!\n", path);
334 if (args->num_set_sd_access_denied +
335 args->num_set_sacl_priv_notheld == 1)
337 WARNING("%ls", apply_access_denied_msg);
339 } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
341 "Suppressing further access denied error messages when setting\n"
342 " security descriptors");
347 ERROR("Failed to set security descriptor on \"%ls\"", path);
349 if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
350 return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
352 return WIMLIB_ERR_WRITE;
358 win32_extract_chunk(const void *buf, size_t len, void *arg)
360 HANDLE hStream = arg;
362 DWORD nbytes_written;
363 wimlib_assert(len <= 0xffffffff);
365 if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) ||
366 nbytes_written != len)
368 DWORD err = GetLastError();
369 ERROR("WriteFile(): write error");
371 return WIMLIB_ERR_WRITE;
377 do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte)
379 return extract_wim_resource(lte, wim_resource_size(lte),
380 win32_extract_chunk, hStream);
383 struct win32_encrypted_extract_ctx {
384 const struct wim_lookup_table_entry *lte;
389 win32_encrypted_import_cb(unsigned char *data, void *_ctx,
390 unsigned long *len_p)
392 struct win32_encrypted_extract_ctx *ctx = _ctx;
393 unsigned long len = *len_p;
394 const struct wim_lookup_table_entry *lte = ctx->lte;
396 len = min(len, wim_resource_size(lte) - ctx->offset);
398 if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data))
399 return ERROR_READ_FAULT;
403 return ERROR_SUCCESS;
406 /* Create an encrypted file and extract the raw encrypted data to it.
408 * @path: Path to encrypted file to create.
409 * @lte: WIM lookup_table entry for the raw encrypted data.
411 * This is separate from do_win32_extract_stream() because the WIM is supposed
412 * to contain the *raw* encrypted data, which needs to be extracted ("imported")
413 * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and
414 * CloseEncryptedFileRaw().
416 * Returns 0 on success; nonzero on failure.
419 do_win32_extract_encrypted_stream(const wchar_t *path,
420 const struct wim_lookup_table_entry *lte)
425 DEBUG("Opening file \"%ls\" to extract raw encrypted data", path);
427 ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx);
429 ERROR("Failed to open \"%ls\" to write raw encrypted data", path);
431 return WIMLIB_ERR_OPEN;
435 struct win32_encrypted_extract_ctx ctx;
439 ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx);
440 if (ret == ERROR_SUCCESS) {
443 ret = WIMLIB_ERR_WRITE;
444 ERROR("Failed to extract encrypted file \"%ls\"", path);
447 CloseEncryptedFileRaw(file_ctx);
452 path_is_root_of_drive(const wchar_t *path)
457 if (*path != L'/' && *path != L'\\') {
458 if (*(path + 1) == L':')
463 while (*path == L'/' || *path == L'\\')
465 return (*path == L'\0');
469 win32_mask_attributes(DWORD i_attributes)
471 return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE |
472 FILE_ATTRIBUTE_COMPRESSED |
473 FILE_ATTRIBUTE_REPARSE_POINT |
474 FILE_ATTRIBUTE_DIRECTORY |
475 FILE_ATTRIBUTE_ENCRYPTED |
476 FILE_FLAG_DELETE_ON_CLOSE |
477 FILE_FLAG_NO_BUFFERING |
478 FILE_FLAG_OPEN_NO_RECALL |
479 FILE_FLAG_OVERLAPPED |
480 FILE_FLAG_RANDOM_ACCESS |
481 /*FILE_FLAG_SESSION_AWARE |*/
482 FILE_FLAG_SEQUENTIAL_SCAN |
483 FILE_FLAG_WRITE_THROUGH);
487 win32_get_create_flags_and_attributes(DWORD i_attributes)
490 * Some attributes cannot be set by passing them to CreateFile(). In
493 * FILE_ATTRIBUTE_DIRECTORY:
494 * CreateDirectory() must be called instead of CreateFile().
496 * FILE_ATTRIBUTE_SPARSE_FILE:
498 * See: win32_set_sparse().
500 * FILE_ATTRIBUTE_COMPRESSED:
501 * Not clear from the documentation, but apparently this needs an
503 * See: win32_set_compressed().
505 * FILE_ATTRIBUTE_REPARSE_POINT:
506 * Needs an ioctl, with the reparse data specified.
507 * See: win32_set_reparse_data().
509 * In addition, clear any file flags in the attributes that we don't
510 * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and
511 * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application.
513 return win32_mask_attributes(i_attributes) |
514 FILE_FLAG_OPEN_REPARSE_POINT |
515 FILE_FLAG_BACKUP_SEMANTICS;
518 /* Set compression and/or sparse attributes on a stream, if supported by the
521 win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode,
522 struct wim_lookup_table_entry *unnamed_stream_lte,
523 const wchar_t *path, unsigned vol_flags)
527 if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) {
528 if (vol_flags & FILE_FILE_COMPRESSION) {
529 ret = win32_set_compression_state(hFile,
530 COMPRESSION_FORMAT_DEFAULT,
535 DEBUG("Cannot set compression attribute on \"%ls\": "
536 "volume does not support transparent compression",
541 if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
542 if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) {
543 DEBUG("Setting sparse flag on \"%ls\"", path);
544 ret = win32_set_sparse(hFile, path);
548 DEBUG("Cannot set sparse attribute on \"%ls\": "
549 "volume does not support sparse files",
556 /* Pre-create directories; extract encrypted streams */
558 win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
559 const struct wim_lookup_table_entry *lte,
561 DWORD *creationDisposition_ret,
562 unsigned int vol_flags)
567 /* Directories must be created with CreateDirectoryW(). Then the call
568 * to CreateFileW() will merely open the directory that was already
569 * created rather than creating a new file. */
570 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY &&
571 !path_is_root_of_drive(path)) {
572 if (!CreateDirectoryW(path, NULL)) {
573 err = GetLastError();
574 if (err != ERROR_ALREADY_EXISTS) {
575 ERROR("Failed to create directory \"%ls\"",
578 return WIMLIB_ERR_MKDIR;
581 DEBUG("Created directory \"%ls\"", path);
582 *creationDisposition_ret = OPEN_EXISTING;
584 if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
585 vol_flags & FILE_SUPPORTS_ENCRYPTION)
587 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
588 unsigned remaining_sharing_violations = 100;
589 while (!EncryptFile(path)) {
590 if (remaining_sharing_violations &&
591 err == ERROR_SHARING_VIOLATION)
593 WARNING("Couldn't encrypt directory \"%ls\" "
594 "due to sharing violation; re-trying "
595 "after 100 ms", path);
597 remaining_sharing_violations--;
599 err = GetLastError();
600 ERROR("Failed to encrypt directory \"%ls\"",
603 return WIMLIB_ERR_WRITE;
607 ret = do_win32_extract_encrypted_stream(path, lte);
610 DEBUG("Extracted encrypted file \"%ls\"", path);
612 *creationDisposition_ret = OPEN_EXISTING;
615 /* Set file attributes if we created the file. Otherwise, we haven't
616 * created the file set and we will set the attributes in the call to
619 * The FAT filesystem does not let you change the attributes of the root
620 * directory, so treat that as a special case and do not set attributes.
622 if (*creationDisposition_ret == OPEN_EXISTING &&
623 !path_is_root_of_drive(path))
625 if (!SetFileAttributesW(path,
626 win32_mask_attributes(inode->i_attributes)))
628 err = GetLastError();
629 ERROR("Failed to set attributes on \"%ls\"", path);
631 return WIMLIB_ERR_WRITE;
637 /* Set security descriptor and extract stream data or reparse data (skip the
638 * unnamed data stream of encrypted files, which was already extracted). */
640 win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry,
641 const struct wim_lookup_table_entry *lte,
642 const wchar_t *stream_path,
643 const wchar_t *stream_name_utf16,
644 struct apply_args *args)
647 const struct wim_inode *inode = dentry->d_inode;
648 const wchar_t *short_name;
649 if (stream_name_utf16 == NULL) {
650 /* Unnamed stream. */
652 /* Set security descriptor, unless the extract_flags indicate
653 * not to or the volume does not supported it. Note that this
654 * is only done when the unnamed stream is being extracted, as
655 * security descriptors are per-file and not per-stream. */
656 if (inode->i_security_id >= 0 &&
657 !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
658 && (args->vol_flags & FILE_PERSISTENT_ACLS))
660 ret = win32_set_security_data(inode, h, stream_path, args);
665 /* Handle reparse points. The data for them needs to be set
666 * using a special ioctl. Note that the reparse point may have
667 * been created using CreateFileW() in the case of
668 * non-directories or CreateDirectoryW() in the case of
669 * directories; but the ioctl works either way. Also, it is
670 * only this step that actually sets the
671 * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it
672 * using SetFileAttributesW() or CreateFileW().
674 * If the volume does not support reparse points we simply
675 * ignore the reparse data. (N.B. the code currently doesn't
676 * actually reach this case because reparse points are skipped
677 * entirely on such volumes.) */
678 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
679 if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
680 ret = win32_set_reparse_data(h, inode,
686 DEBUG("Cannot set reparse data on \"%ls\": volume "
687 "does not support reparse points", stream_path);
689 } else if (lte != NULL &&
690 !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION &&
691 inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
693 /* Extract the data of the unnamed stream, unless the
694 * lookup table entry is NULL (indicating an empty
695 * stream for which no data needs to be extracted), or
696 * the stream is encrypted and therefore was already
697 * extracted as a special case. */
698 ret = do_win32_extract_stream(h, lte);
703 if (dentry_has_short_name(dentry))
704 short_name = dentry->short_name;
708 if (!SetFileShortNameW(h, short_name)) {
710 DWORD err = GetLastError();
711 ERROR("Could not set short name on \"%ls\"", stream_path);
716 /* Extract the data for a named data stream. */
718 DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")",
719 stream_path, wim_resource_size(lte));
720 ret = do_win32_extract_stream(h, lte);
727 win32_decrypt_file(HANDLE open_handle, const wchar_t *path)
730 /* We cannot call DecryptFileW() while there is an open handle to the
731 * file. So close it first. */
732 if (!CloseHandle(open_handle)) {
733 err = GetLastError();
734 ERROR("Failed to close handle for \"%ls\"", path);
736 return WIMLIB_ERR_WRITE;
738 if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) {
739 err = GetLastError();
740 ERROR("Failed to decrypt file \"%ls\"", path);
742 return WIMLIB_ERR_WRITE;
748 * Create and extract a stream to a file, or create a directory, using the
751 * This handles reparse points, directories, alternate data streams, encrypted
752 * files, compressed files, etc.
754 * @dentry: WIM dentry for the file or directory being extracted.
756 * @path: Path to extract the file to.
758 * @stream_name_utf16:
759 * Name of the stream, or NULL if the stream is unnamed. This will
760 * be called with a NULL stream_name_utf16 before any non-NULL
761 * stream_name_utf16's.
763 * @lte: WIM lookup table entry for the stream. May be NULL to indicate
764 * a stream of length 0.
766 * @args: Additional apply context, including flags indicating supported
769 * Returns 0 on success; nonzero on failure.
772 win32_extract_stream(const struct wim_dentry *dentry,
774 const wchar_t *stream_name_utf16,
775 struct wim_lookup_table_entry *lte,
776 struct apply_args *args)
778 wchar_t *stream_path;
782 DWORD creationDisposition = CREATE_ALWAYS;
783 DWORD requestedAccess;
784 BY_HANDLE_FILE_INFORMATION file_info;
785 unsigned remaining_sharing_violations = 1000;
786 const struct wim_inode *inode = dentry->d_inode;
788 if (stream_name_utf16) {
789 /* Named stream. Create a buffer that contains the UTF-16LE
790 * string [./]path:stream_name_utf16. This is needed to
791 * create and open the stream using CreateFileW(). I'm not
792 * aware of any other APIs to do this. Note: the '$DATA' suffix
793 * seems to be unneeded. Additional note: a "./" prefix needs
794 * to be added when the path is not absolute to avoid ambiguity
795 * with drive letters. */
796 size_t stream_path_nchars;
798 size_t stream_name_nchars;
799 const wchar_t *prefix;
801 path_nchars = wcslen(path);
802 stream_name_nchars = wcslen(stream_name_utf16);
803 stream_path_nchars = path_nchars + 1 + stream_name_nchars;
804 if (path[0] != cpu_to_le16(L'\0') &&
805 path[0] != cpu_to_le16(L'/') &&
806 path[0] != cpu_to_le16(L'\\') &&
807 path[1] != cpu_to_le16(L':'))
810 stream_path_nchars += 2;
814 stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
815 swprintf(stream_path, L"%ls%ls:%ls",
816 prefix, path, stream_name_utf16);
818 /* Unnamed stream; its path is just the path to the file itself.
820 stream_path = (wchar_t*)path;
822 ret = win32_begin_extract_unnamed_stream(inode, lte, path,
823 &creationDisposition,
829 DEBUG("Opening \"%ls\"", stream_path);
830 /* DELETE access is needed for SetFileShortNameW(), for some reason. */
831 requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE |
832 ACCESS_SYSTEM_SECURITY;
834 /* Open the stream to be extracted. Depending on what we have set
835 * creationDisposition to, we may be creating this for the first time,
836 * or we may be opening on existing stream we already created using
837 * CreateDirectoryW() or OpenEncryptedFileRawW(). */
838 h = CreateFileW(stream_path,
843 win32_get_create_flags_and_attributes(inode->i_attributes),
845 if (h == INVALID_HANDLE_VALUE) {
846 err = GetLastError();
847 if (err == ERROR_ACCESS_DENIED &&
848 path_is_root_of_drive(stream_path))
853 if ((err == ERROR_PRIVILEGE_NOT_HELD ||
854 err == ERROR_ACCESS_DENIED) &&
855 (requestedAccess & ACCESS_SYSTEM_SECURITY))
857 /* Try opening the file again without privilege to
859 requestedAccess &= ~ACCESS_SYSTEM_SECURITY;
862 if (err == ERROR_SHARING_VIOLATION) {
863 if (remaining_sharing_violations) {
864 --remaining_sharing_violations;
865 /* This can happen when restoring encrypted directories
866 * for some reason. Probably a bug in EncryptFile(). */
867 WARNING("Couldn't open \"%ls\" due to sharing violation; "
868 "re-trying after 100ms", stream_path);
872 ERROR("Too many sharing violations; giving up...");
875 if (creationDisposition == OPEN_EXISTING)
876 ERROR("Failed to open \"%ls\"", stream_path);
878 ERROR("Failed to create \"%ls\"", stream_path);
881 ret = WIMLIB_ERR_OPEN;
885 /* Check the attributes of the file we just opened, and remove
886 * encryption or compression if either was set by default but is not
887 * supposed to be set based on the WIM inode attributes. */
888 if (!GetFileInformationByHandle(h, &file_info)) {
889 err = GetLastError();
890 ERROR("Failed to get attributes of \"%ls\"", stream_path);
892 ret = WIMLIB_ERR_STAT;
893 goto fail_close_handle;
896 /* Remove encryption? */
897 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED &&
898 !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
900 /* File defaulted to encrypted due to being in an encrypted
901 * directory, but is not actually supposed to be encrypted.
903 * This is a workaround, because I'm not aware of any way to
904 * directly (e.g. with CreateFileW()) create an unencrypted file
905 * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */
906 ret = win32_decrypt_file(h, stream_path);
908 goto fail; /* win32_decrypt_file() closed the handle. */
909 creationDisposition = OPEN_EXISTING;
913 /* Remove compression? */
914 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED &&
915 !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED))
917 /* Similar to the encrypted case, above, if the file defaulted
918 * to compressed due to being in an compressed directory, but is
919 * not actually supposed to be compressed, explicitly set the
920 * compression format to COMPRESSION_FORMAT_NONE. */
921 ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE,
924 goto fail_close_handle;
927 /* Set compression and/or sparse attributes if needed */
928 ret = win32_set_special_stream_attributes(h, inode, lte, path,
932 goto fail_close_handle;
934 /* At this point we have at least created the needed stream with the
935 * appropriate attributes. We have yet to set the appropriate security
936 * descriptor and actually extract the stream data (other than for
937 * extracted files, which were already extracted).
938 * win32_finish_extract_stream() handles these additional steps. */
939 ret = win32_finish_extract_stream(h, dentry, lte, stream_path,
940 stream_name_utf16, args);
942 goto fail_close_handle;
944 /* Done extracting the stream. Close the handle and return. */
945 DEBUG("Closing \"%ls\"", stream_path);
946 if (!CloseHandle(h)) {
947 err = GetLastError();
948 ERROR("Failed to close \"%ls\"", stream_path);
950 ret = WIMLIB_ERR_WRITE;
958 ERROR("Error extracting \"%ls\"", stream_path);
964 * Creates a file, directory, or reparse point and extracts all streams to it
965 * (unnamed data stream and/or reparse point stream, plus any alternate data
966 * streams). Handles sparse, compressed, and/or encrypted files.
968 * @dentry: WIM dentry for this file or directory.
969 * @path: UTF-16LE external path to extract the inode to.
970 * @args: Additional extraction context.
972 * Returns 0 on success; nonzero on failure.
975 win32_extract_streams(const struct wim_dentry *dentry,
976 const wchar_t *path, struct apply_args *args)
978 struct wim_lookup_table_entry *unnamed_lte;
980 const struct wim_inode *inode = dentry->d_inode;
982 /* First extract the unnamed stream. */
984 unnamed_lte = inode_unnamed_lte_resolved(inode);
985 ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args);
989 /* Extract any named streams, if supported by the volume. */
991 if (!(args->vol_flags & FILE_NAMED_STREAMS))
993 for (u16 i = 0; i < inode->i_num_ads; i++) {
994 const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i];
996 /* Skip the unnamed stream if it's in the ADS entries (we
997 * already extracted it...) */
998 if (ads_entry->stream_name_nbytes == 0)
1001 /* Skip special UNIX data entries (see documentation for
1002 * WIMLIB_ADD_FLAG_UNIX_DATA) */
1003 if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES
1004 && !memcmp(ads_entry->stream_name,
1005 WIMLIB_UNIX_DATA_TAG_UTF16LE,
1006 WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES))
1009 /* Extract the named stream */
1010 ret = win32_extract_stream(dentry,
1012 ads_entry->stream_name,
1022 /* If not done already, load the supported feature flags for the volume onto
1023 * which the image is being extracted, and warn the user about any missing
1024 * features that could be important. */
1026 win32_check_vol_flags(const wchar_t *output_path, struct apply_args *args)
1028 if (args->have_vol_flags)
1031 win32_get_vol_flags(output_path, &args->vol_flags);
1032 args->have_vol_flags = true;
1033 /* Warn the user about data that may not be extracted. */
1034 if (!(args->vol_flags & FILE_SUPPORTS_SPARSE_FILES))
1035 WARNING("Volume does not support sparse files!\n"
1036 " Sparse files will be extracted as non-sparse.");
1037 if (!(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1038 WARNING("Volume does not support reparse points!\n"
1039 " Reparse point data will not be extracted.");
1040 if (!(args->vol_flags & FILE_NAMED_STREAMS)) {
1041 WARNING("Volume does not support named data streams!\n"
1042 " Named data streams will not be extracted.");
1044 if (!(args->vol_flags & FILE_SUPPORTS_ENCRYPTION)) {
1045 WARNING("Volume does not support encryption!\n"
1046 " Encrypted files will be extracted as raw data.");
1048 if (!(args->vol_flags & FILE_FILE_COMPRESSION)) {
1049 WARNING("Volume does not support transparent compression!\n"
1050 " Compressed files will be extracted as non-compressed.");
1052 if (!(args->vol_flags & FILE_PERSISTENT_ACLS)) {
1053 if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
1054 ERROR("Strict ACLs requested, but the volume does not "
1056 return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
1058 WARNING("Volume does not support persistent ACLS!\n"
1059 " File permissions will not be extracted.");
1066 * Try extracting a hard link.
1068 * @output_path: Path to link to be extracted.
1070 * @inode: WIM inode that the link is to; inode->i_extracted_file
1071 * the path to a name of the file that has already been
1072 * extracted (we use this to create the hard link).
1074 * @args: Additional apply context, used here to keep track of
1075 * the number of times creating a hard link failed due to
1076 * ERROR_INVALID_FUNCTION. This error should indicate that hard
1077 * links are not supported by the volume, and we would like to
1078 * warn the user a few times, but not too many times.
1080 * Returns 0 if the hard link was successfully extracted. Returns
1081 * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly
1082 * being unsupported by the volume. Returns a negative value if creating the
1083 * hard link failed due to ERROR_INVALID_FUNCTION.
1086 win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
1087 struct apply_args *args)
1091 /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
1092 * but it's only available on Windows 7 and later. So no use
1093 * even checking it, really. Instead, CreateHardLinkW() will
1094 * apparently return ERROR_INVALID_FUNCTION if the volume does
1095 * not support hard links. */
1096 DEBUG("Creating hard link \"%ls => %ls\"",
1097 output_path, inode->i_extracted_file);
1098 if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
1101 err = GetLastError();
1102 if (err != ERROR_INVALID_FUNCTION) {
1103 ERROR("Can't create hard link \"%ls => %ls\"",
1104 output_path, inode->i_extracted_file);
1106 return WIMLIB_ERR_LINK;
1108 args->num_hard_links_failed++;
1109 if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
1110 WARNING("Can't create hard link \"%ls => %ls\":\n"
1111 " Volume does not support hard links!\n"
1112 " Falling back to extracting a copy of the file.",
1113 output_path, inode->i_extracted_file);
1115 if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
1116 WARNING("Suppressing further hard linking warnings...");
1122 /* Extract a file, directory, reparse point, or hard link to an
1123 * already-extracted file using the Win32 API */
1125 win32_do_apply_dentry(const wchar_t *output_path,
1126 size_t output_path_num_chars,
1127 struct wim_dentry *dentry,
1128 struct apply_args *args)
1131 struct wim_inode *inode = dentry->d_inode;
1133 ret = win32_check_vol_flags(output_path, args);
1136 if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1137 /* Linked file, with another name already extracted. Create a
1139 ret = win32_try_hard_link(output_path, inode, args);
1142 /* Negative return value from win32_try_hard_link() indicates
1143 * that hard links are probably not supported by the volume.
1144 * Fall back to extracting a copy of the file. */
1147 /* If this is a reparse point and the volume does not support reparse
1148 * points, just skip it completely. */
1149 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1150 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1152 WARNING("Skipping extraction of reparse point \"%ls\":\n"
1153 " Not supported by destination filesystem",
1156 /* Create the file, directory, or reparse point, and extract the
1158 ret = win32_extract_streams(dentry, output_path, args);
1162 if (inode->i_extracted_file == NULL) {
1163 const struct wim_lookup_table_entry *lte;
1165 /* Tally bytes extracted, including all alternate data streams,
1166 * unless we extracted a hard link (or, at least extracted a
1167 * name that was supposed to be a hard link) */
1168 for (unsigned i = 0; i <= inode->i_num_ads; i++) {
1169 lte = inode_stream_lte_resolved(inode, i);
1171 args->progress.extract.completed_bytes +=
1172 wim_resource_size(lte);
1174 if (inode->i_nlink > 1) {
1175 /* Save extracted path for a later call to
1176 * CreateHardLinkW() if this inode has multiple links.
1178 inode->i_extracted_file = WSTRDUP(output_path);
1179 if (!inode->i_extracted_file)
1180 return WIMLIB_ERR_NOMEM;
1186 /* Set timestamps on an extracted file using the Win32 API */
1188 win32_do_apply_dentry_timestamps(const wchar_t *path,
1189 size_t path_num_chars,
1190 struct wim_dentry *dentry,
1191 struct apply_args *args)
1195 const struct wim_inode *inode = dentry->d_inode;
1197 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
1198 !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
1200 /* Skip reparse points not extracted */
1204 /* Windows doesn't let you change the timestamps of the root directory
1205 * (at least on FAT, which is dumb but expected since FAT doesn't store
1206 * any metadata about the root directory...) */
1207 if (path_is_root_of_drive(path))
1210 DEBUG("Opening \"%ls\" to set timestamps", path);
1211 h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
1212 if (h == INVALID_HANDLE_VALUE) {
1213 err = GetLastError();
1217 FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1218 .dwHighDateTime = inode->i_creation_time >> 32};
1219 FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1220 .dwHighDateTime = inode->i_last_access_time >> 32};
1221 FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1222 .dwHighDateTime = inode->i_last_write_time >> 32};
1224 DEBUG("Calling SetFileTime() on \"%ls\"", path);
1225 if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1226 err = GetLastError();
1230 DEBUG("Closing \"%ls\"", path);
1231 if (!CloseHandle(h)) {
1232 err = GetLastError();
1237 /* Only warn if setting timestamps failed; still return 0. */
1238 WARNING("Can't set timestamps on \"%ls\"", path);
1244 #endif /* __WIN32__ */