+/* Set the reparse data @rpbuf of length @rpbuflen on the extracted file
+ * corresponding to the WIM dentry @dentry. */
+static int
+do_set_reparse_data(const struct wim_dentry *dentry,
+ const void *rpbuf, u16 rpbuflen,
+ struct win32_apply_ctx *ctx)
+{
+ NTSTATUS status;
+ HANDLE h;
+
+ status = create_file(&h, GENERIC_WRITE, NULL,
+ 0, FILE_OPEN, 0, dentry, ctx);
+ if (!NT_SUCCESS(status))
+ goto fail;
+
+ status = (*func_NtFsControlFile)(h, NULL, NULL, NULL,
+ &ctx->iosb, FSCTL_SET_REPARSE_POINT,
+ (void *)rpbuf, rpbuflen,
+ NULL, 0);
+ (*func_NtClose)(h);
+
+ if (NT_SUCCESS(status))
+ return 0;
+
+ /* On Windows, by default only the Administrator can create symbolic
+ * links for some reason. By default we just issue a warning if this
+ * appears to be the problem. Use WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS
+ * to get a hard error. */
+ if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS)
+ && (status == STATUS_PRIVILEGE_NOT_HELD ||
+ status == STATUS_ACCESS_DENIED)
+ && (dentry->d_inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
+ dentry->d_inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT))
+ {
+ WARNING("Can't create symbolic link \"%ls\"! \n"
+ " (Need Administrator rights, or at least "
+ "the\n"
+ " SeCreateSymbolicLink privilege.)",
+ current_path(ctx));
+ return 0;
+ }
+
+fail:
+ set_errno_from_nt_status(status);
+ ERROR_WITH_ERRNO("Can't set reparse data on \"%ls\" "
+ "(status=0x%08"PRIx32")",
+ current_path(ctx), (u32)status);
+ return WIMLIB_ERR_SET_REPARSE_DATA;
+}
+
+/* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a
+ * pointer to the suffix of the path that begins with the device directly, such
+ * as e:\Windows\System32. */
+static const wchar_t *
+skip_nt_toplevel_component(const wchar_t *path, size_t path_nchars)
+{
+ static const wchar_t * const dirs[] = {
+ L"\\??\\",
+ L"\\DosDevices\\",
+ L"\\Device\\",
+ };
+ size_t first_dir_len = 0;
+ const wchar_t * const end = path + path_nchars;
+
+ for (size_t i = 0; i < ARRAY_LEN(dirs); i++) {
+ size_t len = wcslen(dirs[i]);
+ if (len <= (end - path) && !wcsnicmp(path, dirs[i], len)) {
+ first_dir_len = len;
+ break;
+ }
+ }
+ if (first_dir_len == 0)
+ return path;
+ path += first_dir_len;
+ while (path != end && *path == L'\\')
+ path++;
+ return path;
+}
+
+/* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a
+ * pointer to the suffix of the path that is device-relative, such as
+ * Windows\System32. */
+static const wchar_t *
+get_device_relative_path(const wchar_t *path, size_t path_nchars)
+{
+ const wchar_t * const orig_path = path;
+ const wchar_t * const end = path + path_nchars;
+
+ path = skip_nt_toplevel_component(path, path_nchars);
+ if (path == orig_path)
+ return orig_path;
+
+ path = wmemchr(path, L'\\', (end - path));
+ if (!path)
+ return orig_path;
+ do {
+ path++;
+ } while (path != end && *path == L'\\');
+ return path;
+}
+
+/*
+ * Given a reparse point buffer for a symbolic link or junction, adjust its
+ * contents so that the target of the link is consistent with the new location
+ * of the files.
+ */
+static void
+try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx)
+{
+ struct reparse_data rpdata;
+ size_t orig_subst_name_nchars;
+ const wchar_t *relpath;
+ size_t relpath_nchars;
+ size_t target_ntpath_nchars;
+ size_t fixed_subst_name_nchars;
+ const wchar_t *fixed_print_name;
+ size_t fixed_print_name_nchars;
+
+ if (parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata)) {
+ /* Do nothing if the reparse data is invalid. */
+ return;
+ }
+
+ if (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
+ (rpdata.rpflags & SYMBOLIC_LINK_RELATIVE))
+ {
+ /* Do nothing if it's a relative symbolic link. */
+ return;
+ }
+
+ /* Build the new substitute name from the NT namespace path to the
+ * target directory, then a path separator, then the "device relative"
+ * part of the old substitute name. */
+
+ orig_subst_name_nchars = rpdata.substitute_name_nbytes / sizeof(wchar_t);
+
+ relpath = get_device_relative_path(rpdata.substitute_name,
+ orig_subst_name_nchars);
+ relpath_nchars = orig_subst_name_nchars -
+ (relpath - rpdata.substitute_name);
+
+ target_ntpath_nchars = ctx->target_ntpath.Length / sizeof(wchar_t);
+
+ fixed_subst_name_nchars = target_ntpath_nchars + 1 + relpath_nchars;
+ wchar_t fixed_subst_name[fixed_subst_name_nchars];
+
+ wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer,
+ target_ntpath_nchars);
+ fixed_subst_name[target_ntpath_nchars] = L'\\';
+ wmemcpy(&fixed_subst_name[target_ntpath_nchars + 1],
+ relpath, relpath_nchars);
+ /* Doesn't need to be null-terminated. */
+
+ /* Print name should be Win32, but not all NT names can even be
+ * translated to Win32 names. But we can at least delete the top-level
+ * directory, such as \??\, and this will have the expected result in
+ * the usual case. */
+ fixed_print_name = skip_nt_toplevel_component(fixed_subst_name,
+ fixed_subst_name_nchars);
+ fixed_print_name_nchars = fixed_subst_name_nchars - (fixed_print_name -
+ fixed_subst_name);
+
+ rpdata.substitute_name = fixed_subst_name;
+ rpdata.substitute_name_nbytes = fixed_subst_name_nchars * sizeof(wchar_t);
+ rpdata.print_name = (wchar_t *)fixed_print_name;
+ rpdata.print_name_nbytes = fixed_print_name_nchars * sizeof(wchar_t);
+ make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p);
+}
+
+/* Sets reparse data on the specified file. This handles "fixing" the targets
+ * of absolute symbolic links and junctions if WIMLIB_EXTRACT_FLAG_RPFIX was
+ * specified. */