Merge branch 'new_extract'
authorEric Biggers <ebiggers3@gmail.com>
Mon, 19 May 2014 17:05:11 +0000 (12:05 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Thu, 22 May 2014 01:38:50 +0000 (20:38 -0500)
This rewrites much of the extraction code to give more freedom to
extraction backends about how they extract the files.  This has
advantages for all three backends:

UNIX:  Now does a lot fewer path lookups and should exhibit better data
locality.  Final "set timestamps" pass only processes directories, since
nondirectory timestamps are set before that point.

NTFS-3g:  Now completely based on inode numbers; no paths are ever
created, unless a printable path is needed for an error message.  Got rid
of final "set attributes" and "set timestamps" passes, since all
attributes and timestamps are set before that point.

Windows:  Now uses NT paths relative to the target directory, performs
fewer path lookups, extracts encrypted files without individually reading
ranges from the WIM (expensive for ESD files), and a few other changes.
A lot of code previously in the generic file extract.c actually could be
moved here.

Also, there are no longer any temporary files needed.  All three backends
simply write all the instances of each stream at the same time, which is
more efficient.

Still TODO:
- New format for UNIX data
- Add fallbacks for when number of open files gets too high
- Add fallback for huge encrypted files
- If possible add hardlink and symlink modes back, however these
  are pretty useless.

24 files changed:
NEWS
configure.ac
doc/man1/imagex-apply.1.in
include/wimlib.h
include/wimlib/apply.h
include/wimlib/dentry.h
include/wimlib/inode.h
include/wimlib/lookup_table.h
include/wimlib/reparse.h
include/wimlib/wimboot.h
include/wimlib/win32_common.h
include/wimlib_tchar.h
programs/imagex.c
src/extract.c
src/lookup_table.c
src/ntfs-3g_apply.c
src/reparse.c
src/unix_apply.c
src/update_image.c
src/wimboot.c
src/win32_apply.c
src/win32_common.c
tests/test-imagex
tests/win32-tree-cmp.c

diff --git a/NEWS b/NEWS
index 47b5d63..26dad0f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,19 +1,20 @@
 Only the most important changes more recent than version 0.6 are noted here.
 
 Version 1.6.3-BETA:
+       Improved compatibility with version 3584 WIM / ESD files.
+
+       Performance improvements, including for extraction.
+
        'add' commands passed to wimupdate will now replace existing
        nondirectory files by default.  Use the --no-replace option to get the
        old behavior.
 
-       Improved compatibility with version 3584 WIM / ESD files.
-
-       Improved extraction performance on Windows, especially for ESD files.
+       Added support for "WIMBoot" capture and extraction.  See the
+       documentation for the new '--wimboot' option to wimcapture and wimapply
+       for more information.
 
-       Added support for "WIMBoot".  On any platform, you can now capture a WIM
-       as WIMBoot-compatible.  In addition, on Windows, you can now apply a WIM
-       archive in a special mode that causes extracted files to be externally
-       backed by the WIM archive.  See the documentation for the new
-       '--wimboot' option to wimcapture and wimapply for more information.
+       Removed the --hardlink and --symlink options to wimapply, since I don't
+       think they are too useful and they got in the way of improving the code.
 
        WIMs will now retain their GUIDs when rebuilt (e.g. with wimoptimize).
 
@@ -22,8 +23,8 @@ Version 1.6.3-BETA:
 
        On Windows, sparse file attributes are no longer set on extracted files.
 
-       The shared library version has been bumped up; however, there are only a
-       few incompatibilities:
+       The shared library version has been bumped up.  The main
+       incompatibilities are:
 
                - WIMLIB_COMPRESSION_TYPE_XPRESS is now 1 and
                  WIMLIB_COMPRESSION_TYPE_LZX is now 2 (so it's the same as
@@ -38,11 +39,18 @@ Version 1.6.3-BETA:
                - Removed deprecated functions: some (de)compression functions,
                  wimlib_extract_files(), and wimlib_print_metadata().
 
+               - Removed extraction flags: WIMLIB_EXTRACT_FLAG_HARDLINK,
+                 WIMLIB_EXTRACT_FLAG_SYMLINK, and
+                 WIMLIB_EXTRACT_FLAG_FILE_ORDER.
+
+               - Removed progress messages:
+                 WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS,
+                 WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
+                 WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END.
+
                - WIM paths passed to progress functions now have a leading
                  slash.
 
-       Made some documentation improvements.
-
 Version 1.6.2:
        Case-insensitive comparisons of strings (e.g. filenames) containing
        UTF-16 codepoints above 32767 are now done correctly.
index cdcc1da..b182ae7 100644 (file)
@@ -48,7 +48,7 @@ AC_PROG_CC
 AM_PROG_CC_C_O
 AC_CANONICAL_HOST
 
-AC_CHECK_FUNCS([utimensat lutimes utime flock mempcpy  \
+AC_CHECK_FUNCS([futimens utimensat utime flock mempcpy \
                openat fstatat readlinkat fdopendir])
 
 # Note: some of the following header checks are only to define the appropriate
index cf29277..53557ca 100644 (file)
@@ -49,7 +49,7 @@ This section documents how \fB@IMAGEX_PROGNAME@ apply\fR (and also
 thereof, in the case of \fB@IMAGEX_PROGNAME@ extract\fR) to a directory on
 UNIX-like systems.  See \fBDIRECTORY EXTRACTION (WINDOWS)\fR for the
 corresponding documentation for Windows.
-
+.PP
 As mentioned, a WIM image can be applied to a directory on a UNIX-like system by
 providing a \fITARGET\fR directory.  However, it is important to keep in mind
 that the WIM format was designed for Windows, and as a result WIM files can
@@ -330,21 +330,6 @@ captured with reparse-point fixups done.  Otherwise, it is \fB--norpfix\fR.
 Reparse point fixups are never done in the NTFS volume extraction mode on
 UNIX-like systems.
 .TP
-\fB--hardlink\fR
-When extracting a file from the WIM that is identical to a file that has already
-extracted, create a hard link rather than creating a separate file.  This option
-causes all identical files to be hard-linked, overriding the hard link groups
-that are specified in the WIM image(s).  In the case of extracting all images
-from the WIM, files may be hard-linked even if they are in different WIM images.
-.IP ""
-However, hard-linked extraction mode does have some additional quirks.  Named
-data streams will not be extracted, and files can be hard linked even if their
-metadata is not fully consistent.
-.TP
-\fB--symlink\fR
-This option is similar to \fB--hardlink\fR, except symbolic links are created
-instead.
-.TP
 \fB--unix-data\fR
 (UNIX-like systems only)  By default, in the directory extraction mode on UNIX,
 \fB@IMAGEX_PROGNAME@ apply\fR will ignore both Windows-style security
index 1b62717..107c740 100644 (file)
@@ -451,135 +451,111 @@ enum wimlib_progress_msg {
         * This message is received only once per wimlib_extract_paths() and
         * wimlib_extract_pathlist(), since wimlib combines all paths into a
         * single extraction operation for optimization purposes.  */
-       WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN,
-
-       /** The directory structure and other preliminary metadata is about to
-        * be extracted.  @p info will point to ::wimlib_progress_info.extract.
-        * This message is received once after either
-        * ::WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN or
-        * ::WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN.  */
-       WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
-
-       /** Confirms that the directory structure and other preliminary metadata
-        * has been successfully extracted.  @p info will point to
-        * ::wimlib_progress_info.extract.  This message is paired with a
-        * preceding ::WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN message.
-        */
-       WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END,
+       WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN = 1,
 
        /** File data is currently being extracted.  @p info will point to
         * ::wimlib_progress_info.extract.  This is the main message to track
         * the progress of an extraction operation.  */
-       WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS,
+       WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS = 4,
 
        /** Starting to read a new part of a split pipable WIM over the pipe.
         * @p info will point to ::wimlib_progress_info.extract.  */
-       WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN,
-
-       /** All data for WIM files and directories have been extracted, and
-        * final metadata such as timestamps is about to be extracted.  @p info
-        * will point to ::wimlib_progress_info.extract.  This message is
-        * received once before ::WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END or
-        * ::WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END.  */
-       WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS,
+       WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN = 5,
 
        /** Confirms that the image has been successfully extracted.  @p info
         * will point to ::wimlib_progress_info.extract.  This is paired with
         * ::WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN.  */
-       WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END,
+       WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END = 7,
 
        /** Confirms that the files or directory trees have been successfully
         * extracted.  @p info will point to ::wimlib_progress_info.extract.
         * This is paired with ::WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN.  */
-       WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END,
+       WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END = 8,
 
        /** The directory or NTFS volume is about to be scanned for metadata.
         * @p info will point to ::wimlib_progress_info.scan.  This message is
         * received once per call to wimlib_add_image(), or once per capture
         * source passed to wimlib_add_image_multisource(), or once per add
         * command passed to wimlib_update_image().  */
-       WIMLIB_PROGRESS_MSG_SCAN_BEGIN,
+       WIMLIB_PROGRESS_MSG_SCAN_BEGIN = 9,
 
        /** A directory or file has been scanned.  @p info will point to
         * ::wimlib_progress_info.scan, and its @p cur_path member will be
         * valid.  This message is only sent if ::WIMLIB_ADD_FLAG_VERBOSE has
         * been specified.  */
-       WIMLIB_PROGRESS_MSG_SCAN_DENTRY,
+       WIMLIB_PROGRESS_MSG_SCAN_DENTRY = 10,
 
        /** Confirms that the directory or NTFS volume has been successfully
         * scanned.  @p info will point to ::wimlib_progress_info.scan.  This is
         * paired with a previous ::WIMLIB_PROGRESS_MSG_SCAN_BEGIN message,
         * possibly with many intervening ::WIMLIB_PROGRESS_MSG_SCAN_DENTRY
         * messages.  */
-       WIMLIB_PROGRESS_MSG_SCAN_END,
+       WIMLIB_PROGRESS_MSG_SCAN_END = 11,
 
        /** File resources ("streams") are currently being written to the WIM.
         * @p info will point to ::wimlib_progress_info.write_streams.  This
         * message may be received many times while the WIM file is being
         * written or appended to with wimlib_write(), wimlib_overwrite(), or
         * wimlib_write_to_fd().  */
-       WIMLIB_PROGRESS_MSG_WRITE_STREAMS,
+       WIMLIB_PROGRESS_MSG_WRITE_STREAMS = 12,
 
        /** Per-image metadata is about to be written to the WIM file.  @p info
         * will not be valid. */
-       WIMLIB_PROGRESS_MSG_WRITE_METADATA_BEGIN,
+       WIMLIB_PROGRESS_MSG_WRITE_METADATA_BEGIN = 13,
 
        /** Confirms that per-image metadata has been successfully been written
         * to the WIM file.  @p info will not be valid.  This message is paired
         * with a preceding ::WIMLIB_PROGRESS_MSG_WRITE_METADATA_BEGIN message.
         */
-       WIMLIB_PROGRESS_MSG_WRITE_METADATA_END,
+       WIMLIB_PROGRESS_MSG_WRITE_METADATA_END = 14,
 
        /** wimlib_overwrite() has successfully renamed the temporary file to
         * the original WIM file, thereby committing the update.  @p info will
         * point to ::wimlib_progress_info.rename.  Note: this message is not
         * received if wimlib_overwrite() chose to append to the WIM file
         * in-place.  */
-       WIMLIB_PROGRESS_MSG_RENAME,
+       WIMLIB_PROGRESS_MSG_RENAME = 15,
 
        /** The contents of the WIM file are being checked against the integrity
         * table.  @p info will point to ::wimlib_progress_info.integrity.  This
         * message is only received (and may be received many times) when
         * wimlib_open_wim() is called with the
         * ::WIMLIB_OPEN_FLAG_CHECK_INTEGRITY flag.  */
-       WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY,
+       WIMLIB_PROGRESS_MSG_VERIFY_INTEGRITY = 16,
 
        /** An integrity table is being calculated for the WIM being written.
         * @p info will point to ::wimlib_progress_info.integrity.  This message
         * is only received (and may be received many times) when a WIM file is
         * being written with the flag ::WIMLIB_WRITE_FLAG_CHECK_INTEGRITY.  */
-       WIMLIB_PROGRESS_MSG_CALC_INTEGRITY,
-
-       /** Reserved.  */
-       WIMLIB_PROGRESS_MSG_RESERVED,
+       WIMLIB_PROGRESS_MSG_CALC_INTEGRITY = 17,
 
        /** A wimlib_split() operation is in progress, and a new split part is
         * about to be started.  @p info will point to
         * ::wimlib_progress_info.split.  */
-       WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART,
+       WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART = 19,
 
        /** A wimlib_split() operation is in progress, and a split part has been
         * finished. @p info will point to ::wimlib_progress_info.split.  */
-       WIMLIB_PROGRESS_MSG_SPLIT_END_PART,
+       WIMLIB_PROGRESS_MSG_SPLIT_END_PART = 20,
 
        /** A WIM update command is just about to be executed. @p info will
         * point to ::wimlib_progress_info.update.  This message is received
         * once per update command when wimlib_update_image() is called with the
         * flag ::WIMLIB_UPDATE_FLAG_SEND_PROGRESS.  */
-       WIMLIB_PROGRESS_MSG_UPDATE_BEGIN_COMMAND,
+       WIMLIB_PROGRESS_MSG_UPDATE_BEGIN_COMMAND = 21,
 
        /** A WIM update command has just been executed. @p info will point to
         * ::wimlib_progress_info.update.  This message is received once per
         * update command when wimlib_update_image() is called with the flag
         * ::WIMLIB_UPDATE_FLAG_SEND_PROGRESS.  */
-       WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND,
+       WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND = 22,
 
        /** A file in the WIM image is being replaced as a result of a
         * ::wimlib_add_command without ::WIMLIB_ADD_FLAG_NO_REPLACE specified.
         * @p info will point to ::wimlib_progress_info.replace.  This is only
         * received when ::WIMLIB_ADD_FLAG_VERBOSE is also specified in the add
         * command.  */
-       WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM,
+       WIMLIB_PROGRESS_MSG_REPLACE_FILE_IN_WIM = 23,
 
        /** A WIM image is being applied with ::WIMLIB_EXTRACT_FLAG_WIMBOOT, and
         * a file is being extracted normally (not as a WIMBoot "pointer file")
@@ -587,7 +563,7 @@ enum wimlib_progress_msg {
         * configuration file \Windows\System32\WimBootCompress.ini in the WIM
         * image.  @info will point to ::wimlib_progress_info.wimboot_exclude.
         */
-       WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE,
+       WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE = 24,
 };
 
 /** A pointer to this union is passed to the user-supplied
@@ -775,12 +751,9 @@ union wimlib_progress_info {
         * ::WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN,
         * ::WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN,
         * ::WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN,
-        * ::WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
-        * ::WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END,
         * ::WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS,
-        * ::WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END,
-        * ::WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END, and
-        * ::WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS.
+        * ::WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END, and
+        * ::WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END.
         *
         * Note: most of the time of an extraction operation will be spent
         * extracting streams, and the application will receive
@@ -1451,30 +1424,13 @@ typedef int (*wimlib_iterate_lookup_table_callback_t)(const struct wimlib_resour
  * a regular file or block device) rather than a directory.  It will be opened
  * using libntfs-3g, and the image will be extracted to the NTFS filesystem's
  * root directory.  Note: this flag cannot be used when wimlib_extract_image()
- * is called with ::WIMLIB_ALL_IMAGES as the @p image.  */
+ * is called with ::WIMLIB_ALL_IMAGES as the @p image, nor can it be used with
+ * wimlib_extract_paths() when passed multiple paths.  */
 #define WIMLIB_EXTRACT_FLAG_NTFS                       0x00000001
 
-/** When identical files are extracted from the WIM, always hard link them
- * together.  This flag cannot be combined with ::WIMLIB_EXTRACT_FLAG_SYMLINK.
- */
-#define WIMLIB_EXTRACT_FLAG_HARDLINK                   0x00000002
-
-/** When identical files are extracted from the WIM, always symlink them
- * together.  This flag cannot be combined with ::WIMLIB_EXTRACT_FLAG_HARDLINK.
- */
-#define WIMLIB_EXTRACT_FLAG_SYMLINK                    0x00000004
-
-/** This flag no longer does anything but is reserved for future use.  */
-#define WIMLIB_EXTRACT_FLAG_VERBOSE                    0x00000008
-
-/** Read the WIM file sequentially while extracting the image.  As of wimlib
- * v1.6.0 this is the default behavior, and this flag no longer does anything.
- */
-#define WIMLIB_EXTRACT_FLAG_SEQUENTIAL                 0x00000010
-
-/** Extract special UNIX data captured with ::WIMLIB_ADD_FLAG_UNIX_DATA.  Only
- * valid on UNIX-like platforms, and when ::WIMLIB_EXTRACT_FLAG_NTFS was not
- * specified.  */
+/** UNIX-like systems only:  Extract special UNIX data captured with
+ * ::WIMLIB_ADD_FLAG_UNIX_DATA.  This flag cannot be combined with
+ * ::WIMLIB_EXTRACT_FLAG_NTFS.  */
 #define WIMLIB_EXTRACT_FLAG_UNIX_DATA                  0x00000020
 
 /** Do not extract security descriptors.  This flag cannot be combined with
@@ -1503,9 +1459,8 @@ typedef int (*wimlib_iterate_lookup_table_callback_t)(const struct wimlib_resour
  * with ::WIMLIB_EXTRACT_FLAG_RPFIX.  */
 #define WIMLIB_EXTRACT_FLAG_NORPFIX                    0x00000200
 
-/** Extract the paths, each of which must name a regular file, to standard
- * output.  Not valid for wimlib_extract_image() and
- * wimlib_extract_image_from_pipe().  */
+/** For wimlib_extract_paths() and wimlib_extract_pathlist() only:  Extract the
+ * paths, each of which must name a regular file, to standard output.  */
 #define WIMLIB_EXTRACT_FLAG_TO_STDOUT                  0x00000400
 
 /** Instead of ignoring files and directories with names that cannot be
@@ -1526,23 +1481,16 @@ typedef int (*wimlib_iterate_lookup_table_callback_t)(const struct wimlib_resour
 /** Do not ignore failure to set short names on extracted files.  */
 #define WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES          0x00004000
 
-/** Do not ignore failure to extract symbolic links (and junction points, on
- * Windows) due to permissions problems.  By default, such failures are ignored
- * since the default configuration of Windows only allows the Administrator to
- * create symbolic links.  */
+/** On Windows, do not ignore failure to extract symbolic links and junctions
+ * due to permissions problems.  By default, such failures are ignored since the
+ * default configuration of Windows only allows the Administrator to create
+ * symbolic links.  */
 #define WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS             0x00008000
 
 /** TODO: this flag is intended to allow resuming an aborted extraction, but the
  * behavior is currently less than satisfactory.  Do not use (yet).  */
 #define WIMLIB_EXTRACT_FLAG_RESUME                     0x00010000
 
-/** Perform the extraction ordered by the tree of files to extract rather than
- * how the underlying streams are arranged in the WIM file.  For regular WIM
- * files this may decrease or increase performance, depending on various
- * factors.  For WIM files containing packed streams this will decrease
- * performance.  */
-#define WIMLIB_EXTRACT_FLAG_FILE_ORDER                 0x00020000
-
 /** For wimlib_extract_paths() and wimlib_extract_pathlist() only:  Treat the
  * paths to extract as wildcard patterns ("globs") which may contain the
  * wildcard characters @c ? and @c *.  The @c ? character matches any
@@ -2479,9 +2427,8 @@ wimlib_export_image(WIMStruct *src_wim, int src_image,
  *     volume.  Flags affected by this include ::WIMLIB_EXTRACT_FLAG_NTFS,
  *     ::WIMLIB_EXTRACT_FLAG_UNIX_DATA, ::WIMLIB_EXTRACT_FLAG_STRICT_ACLS,
  *     ::WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES,
- *     ::WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS,
- *     ::WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS, ::WIMLIB_EXTRACT_FLAG_SYMLINK,
- *     and ::WIMLIB_EXTRACT_FLAG_HARDLINK.  For example, if
+ *     ::WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS, and
+ *     ::WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS.  For example, if
  *     ::WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES is specified in @p
  *     extract_flags, ::WIMLIB_ERR_UNSUPPORTED will be returned if the WIM
  *     image contains one or more files with short names, but extracting short
@@ -2528,9 +2475,7 @@ wimlib_extract_image(WIMStruct *wim, int image,
  * @param target
  *     Same as the corresponding parameter to wimlib_extract_image().
  * @param extract_flags
- *     Same as the corresponding parameter to wimlib_extract_image(), except
- *     that ::WIMLIB_EXTRACT_FLAG_FILE_ORDER cannot be specified and will
- *     result in ::WIMLIB_ERR_INVALID_PARAM being returned.
+ *     Same as the corresponding parameter to wimlib_extract_image().
  * @param progress_func
  *     Same as the corresponding parameter to wimlib_extract_image(), except
  *     ::WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN messages will also be
index 3b124d3..72c21b5 100644 (file)
 #ifndef _WIMLIB_APPLY_H
 #define _WIMLIB_APPLY_H
 
-#include "wimlib/file_io.h"
-#include "wimlib/types.h"
 #include "wimlib/list.h"
+#include "wimlib/types.h"
 #include "wimlib.h"
 
-struct wim_lookup_table_entry;
-struct wimlib_unix_data;
-struct wim_dentry;
-struct apply_ctx;
-
-/* Path to extracted file, or "cookie" identifying the file (e.g. inode number).
- * */
-typedef union {
-       const tchar *path;
-       u64 cookie;
-} file_spec_t;
-
-/*
- * struct apply_operations -  Callback functions for a specific extraction
- * mode/backend.  These are lower-level functions that are called by the generic
- * code in extract.c.
- *
- * Unless otherwise specified, the callbacks in this structure are expected to
- * return 0 on success or a WIMLIB_ERR_* value on failure as well as set errno.
- * When possible, error messages should NOT be printed as they are handled by
- * the generic code.
- *
- * Many callbacks are optional, but to extract the most data from the WIM
- * format, as many as possible should be provided, and the corresponding
- * features should be marked as supported in start_extract().
- */
-struct apply_operations {
-
-       /* OPTIONAL:  Name of this extraction mode.  */
-       const tchar *name;
-
-       /* REQUIRED:  Fill in ctx->supported_features with nonzero values for
-        * features supported by the extraction mode and volume.  This callback
-        * can also be used to do any setup needed to access the volume.  */
-       int (*start_extract)
-               (const tchar *path, struct apply_ctx *ctx);
-
-       /* OPTIONAL:  If root_directory_is_special is set:  provide this
-        * callback to determine whether the path corresponds to the root of the
-        * target volume (%true) or not (%false).  */
-       bool (*target_is_root)
-               (const tchar *target);
-
-       /* REQUIRED:  Create a file.  */
-       int (*create_file)
-               (const tchar *path, struct apply_ctx *ctx, u64 *cookie_ret);
-
-       /* REQUIRED:  Create a directory.  */
-       int (*create_directory)
-               (const tchar *path, struct apply_ctx *ctx, u64 *cookie_ret);
-
-       /* OPTIONAL:  Create a hard link.  In start_extract(), set
-        * ctx->supported_features.hard_links if supported.  */
-       int (*create_hardlink)
-               (const tchar *oldpath, const tchar *newpath,
-                struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Create a symbolic link.  In start_extract(), set
-        * ctx->supported_features.symlink_reparse_points if supported.  */
-       int (*create_symlink)
-               (const tchar *oldpath, const tchar *newpath,
-                struct apply_ctx *ctx);
-
-       /* REQUIRED:  Extract unnamed data stream.  */
-       int (*extract_unnamed_stream)
-               (file_spec_t file, struct wim_lookup_table_entry *lte,
-                struct apply_ctx *ctx, struct wim_dentry *dentry);
-
-       /* OPTIONAL:  Extracted named data stream.  In start_extract(), set
-        * ctx->supported_features.alternate_data_streams if supported.  */
-       int (*extract_named_stream)
-               (file_spec_t file, const utf16lechar *stream_name,
-                size_t stream_name_nchars, struct wim_lookup_table_entry *lte,
-                struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Extracted encrypted stream.  In start_extract(), set
-        * ctx->supported_features.encrypted_files if supported.  */
-       int (*extract_encrypted_stream)
-               (const tchar *path, struct wim_lookup_table_entry *lte,
-                struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Set file attributes.  Calling code calls this if non-NULL.
-        */
-       int (*set_file_attributes)
-               (const tchar *path, u32 attributes, struct apply_ctx *ctx,
-                unsigned pass);
-
-       /* OPTIONAL:  Set reparse data.  In start_extract(), set
-        * ctx->supported_features.reparse_data if supported.  */
-       int (*set_reparse_data)
-               (const tchar *path, const u8 *rpbuf, u16 rpbuflen,
-                struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Set short (DOS) filename.  In start_extract(), set
-        * ctx->supported_features.short_name if supported.  */
-       int (*set_short_name)
-               (const tchar *path, const utf16lechar *short_name,
-                size_t short_name_nchars, struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Set Windows NT security descriptor.  In start_extract(),
-        * set ctx->supported_features.security_descriptors if supported.  */
-       int (*set_security_descriptor)
-               (const tchar *path, const u8 *desc, size_t desc_size,
-                struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Set wimlib-specific UNIX data.  In start_extract(), set
-        * ctx->supported_features.unix_data if supported.  */
-       int (*set_unix_data)
-               (const tchar *path, const struct wimlib_unix_data *data,
-                struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Set timestamps.  Calling code calls this if non-NULL.  */
-       int (*set_timestamps)
-               (const tchar *path, u64 creation_time, u64 last_write_time,
-                u64 last_access_time, struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Called after the extraction operation has succeeded.  */
-       int (*finish_extract)
-               (struct apply_ctx *ctx);
-
-       /* OPTIONAL:  Called after the extraction operation has failed.  */
-       int (*abort_extract)
-               (struct apply_ctx *ctx);
-
-       /* REQUIRED:  Path separator character to use when building paths.  */
-       tchar path_separator;
-
-       /* REQUIRED:  Maximum path length, in tchars, including the
-        * null-terminator.  */
-       unsigned path_max;
-
-       /* OPTIONAL:  String to prefix every path with.  */
-       const tchar *path_prefix;
-
-       /* OPTIONAL:  Length of path_prefix in tchars.  */
-       unsigned path_prefix_nchars;
-
-       /* OPTIONAL:  Set to 1 if paths must be prefixed by the name of the
-        * extraction target (i.e. if it's interpreted as a directory).  */
-       unsigned requires_target_in_paths : 1;
-
-       /* OPTIONAL:  Like above, but operations require real (absolute) path.
-        * */
-       unsigned requires_realtarget_in_paths : 1;
-
-       /* OPTIONAL:  Set to 1 if realpath() can be used to get the real
-        * (absolute) path of a file on the target volume before it's been
-        * created.  */
-       unsigned realpath_works_on_nonexisting_files : 1;
-
-       /* OPTIONAL:  Set to 1 if this extraction mode supports case sensitive
-        * filenames.  */
-       unsigned supports_case_sensitive_filenames : 1;
-
-       /* OPTIONAL:  Set to 1 if the root directory of the volume (see
-        * target_is_root() callback) should not be explicitly extracted.  */
-       unsigned root_directory_is_special : 1;
-
-       /* OPTIONAL:  Set to 1 if extraction cookie, or inode number, is stored
-        * in create_file() and create_directory() callbacks.  This cookie will
-        * then be passed to callbacks taking a 'file_spec_t', rather than the
-        * path.  */
-       unsigned uses_cookies : 1;
-
-       /* OPTIONAL:  Set to 1 if set_file_attributes() needs to be called a
-        * second time towards the end of the extraction.  */
-       unsigned requires_final_set_attributes_pass : 1;
-
-       /* OPTIONAL:  Set to 1 if extract_encrypted_stream() must be used to
-        * create encrypted files.  */
-       unsigned extract_encrypted_stream_creates_file : 1;
-
-       /* OPTIONAL:  Set to 1 if a link to a file with corresponding short name
-        * must be created before all links to the same file without
-        * corresponding short names.  */
-       unsigned requires_short_name_reordering : 1;
-};
-
+/* These can be treated as counts (for required_features) or booleans (for
+ * supported_features).  */
 struct wim_features {
        unsigned long archive_files;
        unsigned long hidden_files;
@@ -201,34 +24,66 @@ struct wim_features {
        unsigned long security_descriptors;
        unsigned long short_names;
        unsigned long unix_data;
+       unsigned long timestamps;
+       unsigned long case_sensitive_filenames;
 };
 
-/* Context for an apply (extract) operation.  */
+struct wim_lookup_table_entry;
+struct read_stream_list_callbacks;
+
 struct apply_ctx {
+       /* The WIMStruct from which files are being extracted from the currently
+        * selected image.  */
        WIMStruct *wim;
-       int extract_flags;
+
+       /* The target of the extraction, usually the path to a directory.  */
        const tchar *target;
+
+       /* Length of @target in tchars.  */
        size_t target_nchars;
+
+       /* Extraction flags (WIMLIB_EXTRACT_FLAG_*)  */
+       int extract_flags;
+
+       /* User-provided progress function, or NULL if not specified.  */
        wimlib_progress_func_t progress_func;
+
+       /* Progress data buffer, with progress.extract initialized.  */
        union wimlib_progress_info progress;
-       const struct apply_operations *ops;
-       struct list_head stream_list;
+
+       /* Features required to extract the files (with counts)  */
+       struct wim_features required_features;
+
+       /* Features supported by the extraction mode (with booleans)  */
        struct wim_features supported_features;
-       bool root_dentry_is_special;
-       u32 supported_attributes_mask;
 
-       struct wim_dentry *target_dentry;
-       tchar *realtarget;
-       size_t realtarget_nchars;
+       /* The members below should not be used outside of extract.c  */
+       u64 next_progress;
        unsigned long invalid_sequence;
-       unsigned long partial_security_descriptors;
-       unsigned long no_security_descriptors;
+       unsigned long num_streams_remaining;
+       struct list_head stream_list;
+       const struct read_stream_list_callbacks *saved_cbs;
        struct wim_lookup_table_entry *cur_stream;
-       struct filedes tmpfile_fd;
-       tchar *tmpfile_name;
-       u64 num_streams_remaining;
-       uint64_t next_progress;
-       intptr_t private[10];
+};
+
+/* Returns any of the aliases of an inode that are being extracted.  */
+#define inode_first_extraction_dentry(inode)           \
+       list_first_entry(&(inode)->i_extraction_aliases,        \
+                        struct wim_dentry, d_extraction_alias_node)
+
+extern int
+extract_stream_list(struct apply_ctx *ctx,
+                   const struct read_stream_list_callbacks *cbs);
+
+struct apply_operations {
+       const char *name;
+       int (*get_supported_features)(const tchar *target,
+                                     struct wim_features *supported_features);
+
+       int (*extract)(struct list_head *dentry_list, struct apply_ctx *ctx);
+
+       size_t context_size;
+       bool single_tree_only;
 };
 
 #ifdef __WIN32__
index 794dba9..df1b3ed 100644 (file)
@@ -64,6 +64,21 @@ struct wim_dentry {
         * long names but share the same case insensitive long name.  */
        struct list_head d_ci_conflict_list;
 
+       /* The parent of this directory entry. */
+       struct wim_dentry *parent;
+
+       /* Linked list node that places this dentry in the list of aliases for
+        * its inode (d_inode) */
+       struct list_head d_alias;
+
+       /* Pointer to the UTF-16LE short filename (malloc()ed buffer), or NULL
+        * if this dentry has no short name.  */
+       utf16lechar *short_name;
+
+       /* Pointer to the UTF-16LE filename (malloc()ed buffer), or NULL if this
+        * dentry has no filename.  */
+       utf16lechar *file_name;
+
        /* Length of UTF-16LE encoded short filename, in bytes, not including
         * the terminating zero wide-character. */
        u16 short_name_nbytes;
@@ -72,14 +87,6 @@ struct wim_dentry {
         * the terminating null character. */
        u16 file_name_nbytes;
 
-       /* Length of full path name encoded using "tchars", in bytes, not
-        * including the terminating null character. */
-       u32 full_path_nbytes;
-
-       /* During extraction extractions, this flag will be set after the
-        * "skeleton" of the dentry has been extracted.  */
-       u8 skeleton_extracted : 1;
-
        /* When capturing from an NTFS volume using NTFS-3g, this flag is set on
         * dentries that were created from a filename in the WIN32 or WIN32+DOS
         * namespaces rather than the POSIX namespace.  Otherwise this will
@@ -89,55 +96,41 @@ struct wim_dentry {
        /* Temporary flag; always reset to 0 when done using.  */
        u8 tmp_flag : 1;
 
-       /* Set to 1 if this name was extracted as a link, so no streams need to
-        * be extracted to it.  */
-       u8 was_linked : 1;
-
        /* Used by wimlib_update_image()  */
        u8 is_orphan : 1;
 
        /* Temporary list field  */
        struct list_head tmp_list;
 
-       /* Links list of dentries being extracted  */
-       struct list_head extraction_list;
-
-       /* Linked list node that places this dentry in the list of aliases for
-        * its inode (d_inode) */
-       struct list_head d_alias;
-
-       /* The parent of this directory entry. */
-       struct wim_dentry *parent;
-
        /* 'length' and 'subdir_offset' are only used while reading and writing
         * this dentry; see the corresponding field in
         * `struct wim_dentry_on_disk' for explanation.  */
        u64 length;
        u64 subdir_offset;
 
-       /* Pointer to the UTF-16LE short filename (malloc()ed buffer), or NULL
-        * if this dentry has no short name.  */
-       utf16lechar *short_name;
-
-       /* Pointer to the UTF-16LE filename (malloc()ed buffer), or NULL if this
-        * dentry has no filename.  */
-       utf16lechar *file_name;
-
        /* Full path to this dentry in the WIM, in platform-dependent tchars
         * that can be printed without conversion.  By default this field will
         * be NULL and will only be calculated on-demand by the
         * calculate_dentry_full_path() or dentry_full_path() functions.  */
        tchar *_full_path;
 
-       /* (Extraction only) Actual name to extract this dentry as, along with
-        * its length in tchars excluding the NULL terminator.  This usually
-        * will be the same as file_name, with the character encoding converted
-        * if needed.  But if file_name contains characters not accepted on the
-        * current platform, then this may be set slightly differently from
-        * file_name.  This will be either NULL or a malloc()ed buffer that may
-        * alias file_name.  */
-       tchar *extraction_name;
-       size_t extraction_name_nchars;
+       /* (Extraction only) Actual name to extract this dentry as.  This may be
+        * either in 'tchars' or in 'utf16lechars', depending on what encoding
+        * the extraction backend needs.  This may alias 'file_name'.  If it
+        * doesn't, it is an allocated buffer which must be freed.  */
+       void *d_extraction_name;
+
+       /* (Extraction only) Number of characters in d_extraction_name.  */
+       size_t d_extraction_name_nchars;
+
+       /* (Extraction only) Linked list node that connects all dentries being
+        * extracted as part of the current extraction operation.  */
+       struct list_head d_extraction_list_node;
+
+       /* (Extraction only) Linked list node that connects all dentries being
+        * extracted as aliases of the same inode as part of the current
+        * extraction operation.  */
+       struct list_head d_extraction_alias_node;
 };
 
 static inline bool
@@ -146,6 +139,12 @@ dentry_is_first_in_inode(const struct wim_dentry *dentry)
        return inode_first_dentry(dentry->d_inode) == dentry;
 }
 
+static inline bool
+will_extract_dentry(const struct wim_dentry *dentry)
+{
+       return dentry->d_extraction_list_node.next != NULL;
+}
+
 extern u64
 dentry_out_total_length(const struct wim_dentry *dentry);
 
index a3f38b7..c787a9d 100644 (file)
@@ -99,9 +99,6 @@ struct wim_inode {
         * error paths.  */
        u8 i_visited : 1;
 
-       /* Set if the DOS name of an inode has already been extracted.  */
-       u8 i_dos_name_extracted : 1;
-
        /* 1 iff all ADS entries of this inode are named or if this inode
         * has no ADS entries  */
        u8 i_canonical_streams : 1;
@@ -153,19 +150,22 @@ struct wim_inode {
                 * to 0 otherwise.  */
                u64 i_devno;
 
+               /* Fields used only during extraction  */
                struct {
-
-                       /* Used only during image extraction: pointer to the first path
-                        * (malloc()ed buffer) at which this inode has been extracted.
-                        * Freed and set to NULL after the extraction is done (either
-                        * success or failure).  */
-                       tchar *i_extracted_file;
-
-                       /** Used only during image extraction: "cookie" that
-                        * identifies this extracted file (inode), for example
-                        * an inode number.  Only used if supported by the
-                        * extraction mode.  */
-                       u64 extract_cookie;
+                       /* List of aliases of this dentry that are being
+                        * extracted in the current extraction operation.  This
+                        * will be a (possibly nonproper) subset of the dentries
+                        * in the i_dentry list.  This list will be constructed
+                        * regardless of whether the extraction backend supports
+                        * hard links or not.  */
+                       struct list_head i_extraction_aliases;
+
+               #ifdef WITH_NTFS_3G
+                       /* In NTFS-3g extraction mode, this is set to the Master
+                        * File Table (MFT) number of the NTFS file that was
+                        * created for this inode.  */
+                       u64 i_mft_no;
+               #endif
                };
 
 #ifdef WITH_FUSE
index 9d6f70a..216565e 100644 (file)
@@ -63,6 +63,11 @@ enum resource_location {
 #endif
 };
 
+struct stream_owner {
+       struct wim_inode *inode;
+       const utf16lechar *stream_name;
+};
+
 /* Specification for a stream, which may be the contents of a file (unnamed data
  * stream), a named data stream, reparse point data, or a WIM metadata resource.
  *
@@ -136,8 +141,8 @@ struct wim_lookup_table_entry {
        /* When a WIM file is written, this is set to the number of references
         * (by dentries) to this stream in the output WIM file.
         *
-        * During extraction, this is set to the number of times the stream must
-        * be extracted.
+        * During extraction, this is the number of slots in stream_owners (or
+        * inline_stream_owners) that have been filled.
         *
         * During image export, this is set to the number of references of this
         * stream that originated from the source WIM.
@@ -176,12 +181,6 @@ struct wim_lookup_table_entry {
         */
        struct list_head rspec_node;
 
-       /* This field is used during the hardlink and symlink image extraction
-        * modes.   In these modes, all identical files are linked together, and
-        * @extracted_file will be set to the filename of the first extracted
-        * file containing this stream.  */
-       tchar *extracted_file;
-
        /* Temporary fields  */
        union {
                /* Fields used temporarily during WIM file writing.  */
@@ -209,14 +208,16 @@ struct wim_lookup_table_entry {
                        struct wim_reshdr out_reshdr;
                };
 
-               /* Used temporarily during extraction  */
+               /* Used temporarily during extraction.  This is an array of
+                * pointers to the inodes being extracted that use this stream.
+                */
                union {
-                       /* Dentries to extract that reference this stream.
+                       /* Inodes to extract that reference this stream.
                         * out_refcnt tracks the number of slots filled.  */
-                       struct wim_dentry *inline_lte_dentries[7];
+                       struct stream_owner inline_stream_owners[3];
                        struct {
-                               struct wim_dentry **lte_dentries;
-                               size_t alloc_lte_dentries;
+                               struct stream_owner *stream_owners;
+                               u32 alloc_stream_owners;
                        };
                };
 
@@ -340,9 +341,6 @@ lte_zero_out_refcnt(struct wim_lookup_table_entry *lte, void *ignore);
 extern int
 lte_zero_real_refcnt(struct wim_lookup_table_entry *lte, void *ignore);
 
-extern int
-lte_free_extracted_file(struct wim_lookup_table_entry *lte, void *ignore);
-
 static inline bool
 lte_is_partial(const struct wim_lookup_table_entry * lte)
 {
@@ -363,6 +361,15 @@ lte_filename_valid(const struct wim_lookup_table_entry *lte)
                ;
 }
 
+static inline const struct stream_owner *
+stream_owners(struct wim_lookup_table_entry *stream)
+{
+       if (stream->out_refcnt <= ARRAY_LEN(stream->inline_stream_owners))
+               return stream->inline_stream_owners;
+       else
+               return stream->stream_owners;
+}
+
 static inline void
 lte_bind_wim_resource_spec(struct wim_lookup_table_entry *lte,
                           struct wim_resource_spec *rspec)
index 04974a1..f7fc397 100644 (file)
@@ -8,6 +8,38 @@ struct wim_lookup_table;
 
 #define REPARSE_POINT_MAX_SIZE (16 * 1024)
 
+/* On-disk format of reparse point buffer  */
+struct reparse_buffer_disk {
+       le32 rptag;
+       le16 rpdatalen;
+       le16 rpreserved;
+       union {
+               u8 rpdata[REPARSE_POINT_MAX_SIZE - 8];
+
+               struct {
+                       le16 substitute_name_offset;
+                       le16 substitute_name_nbytes;
+                       le16 print_name_offset;
+                       le16 print_name_nbytes;
+                       le32 rpflags;
+                       u8 data[REPARSE_POINT_MAX_SIZE - 20];
+               } _packed_attribute symlink;
+
+               struct {
+                       le16 substitute_name_offset;
+                       le16 substitute_name_nbytes;
+                       le16 print_name_offset;
+                       le16 print_name_nbytes;
+                       u8 data[REPARSE_POINT_MAX_SIZE - 16];
+               } _packed_attribute junction;
+       };
+} _packed_attribute;
+
+#define REPARSE_DATA_OFFSET (offsetof(struct reparse_buffer_disk, rpdata))
+
+#define REPARSE_DATA_MAX_SIZE (REPARSE_POINT_MAX_SIZE - REPARSE_DATA_OFFSET)
+
+
 /* Structured format for symbolic link, junction point, or mount point reparse
  * data. */
 struct reparse_data {
@@ -42,16 +74,6 @@ struct reparse_data {
        u16 print_name_nbytes;
 };
 
-enum {
-       SUBST_NAME_IS_RELATIVE_LINK = -1,
-       SUBST_NAME_IS_VOLUME_JUNCTION = -2,
-       SUBST_NAME_IS_UNKNOWN = -3,
-};
-extern int
-parse_substitute_name(const utf16lechar *substitute_name,
-                     u16 substitute_name_nbytes,
-                     u32 rptag);
-
 extern int
 parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen,
                   struct reparse_data * restrict rpdata);
index afb6e65..1c4b0fc 100644 (file)
@@ -14,7 +14,8 @@ wimboot_alloc_data_source_id(const wchar_t *wim_path,
                             bool *wof_running_ret);
 
 extern int
-wimboot_set_pointer(const wchar_t *path,
+wimboot_set_pointer(UNICODE_STRING *name,
+                   const wchar_t *printable_path,
                    const struct wim_lookup_table_entry *lte,
                    u64 data_source_id,
                    const u8 lookup_table_hash[SHA1_HASH_SIZE],
index 6749256..d9a4ff3 100644 (file)
@@ -21,16 +21,20 @@ set_errno_from_win32_error(DWORD err);
 extern void
 set_errno_from_nt_status(NTSTATUS status);
 
-extern HANDLE
-win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess);
-
-/* Vista and later */
-extern BOOL (WINAPI *func_CreateSymbolicLinkW)(const wchar_t *lpSymlinkFileName,
-                                              const wchar_t *lpTargetFileName,
-                                              DWORD dwFlags);
-
 /* ntdll functions  */
 
+extern NTSTATUS (WINAPI *func_NtCreateFile)(PHANDLE FileHandle,
+                                           ACCESS_MASK DesiredAccess,
+                                           POBJECT_ATTRIBUTES ObjectAttributes,
+                                           PIO_STATUS_BLOCK IoStatusBlock,
+                                           PLARGE_INTEGER AllocationSize,
+                                           ULONG FileAttributes,
+                                           ULONG ShareAccess,
+                                           ULONG CreateDisposition,
+                                           ULONG CreateOptions,
+                                           PVOID EaBuffer,
+                                           ULONG EaLength);
+
 extern NTSTATUS (WINAPI *func_NtOpenFile) (PHANDLE FileHandle,
                                           ACCESS_MASK DesiredAccess,
                                           POBJECT_ATTRIBUTES ObjectAttributes,
@@ -48,6 +52,16 @@ extern NTSTATUS (WINAPI *func_NtReadFile) (HANDLE FileHandle,
                                           PLARGE_INTEGER ByteOffset,
                                           PULONG Key);
 
+extern NTSTATUS (WINAPI *func_NtWriteFile) (HANDLE FileHandle,
+                                           HANDLE Event,
+                                           PIO_APC_ROUTINE ApcRoutine,
+                                           PVOID ApcContext,
+                                           PIO_STATUS_BLOCK IoStatusBlock,
+                                           PVOID Buffer,
+                                           ULONG Length,
+                                           PLARGE_INTEGER ByteOffset,
+                                           PULONG Key);
+
 extern NTSTATUS (WINAPI *func_NtQueryInformationFile)(HANDLE FileHandle,
                                                      PIO_STATUS_BLOCK IoStatusBlock,
                                                      PVOID FileInformation,
@@ -78,15 +92,48 @@ extern NTSTATUS (WINAPI *func_NtQueryVolumeInformationFile) (HANDLE FileHandle,
                                                             ULONG Length,
                                                             FS_INFORMATION_CLASS FsInformationClass);
 
+extern NTSTATUS (WINAPI *func_NtSetInformationFile)(HANDLE FileHandle,
+                                                   PIO_STATUS_BLOCK IoStatusBlock,
+                                                   PVOID FileInformation,
+                                                   ULONG Length,
+                                                   FILE_INFORMATION_CLASS FileInformationClass);
 
 extern NTSTATUS (WINAPI *func_NtSetSecurityObject)(HANDLE Handle,
                                                   SECURITY_INFORMATION SecurityInformation,
                                                   PSECURITY_DESCRIPTOR SecurityDescriptor);
 
+extern NTSTATUS (WINAPI *func_NtFsControlFile) (HANDLE FileHandle,
+                                               HANDLE Event,
+                                               PIO_APC_ROUTINE ApcRoutine,
+                                               PVOID ApcContext,
+                                               PIO_STATUS_BLOCK IoStatusBlock,
+                                               ULONG FsControlCode,
+                                               PVOID InputBuffer,
+                                               ULONG InputBufferLength,
+                                               PVOID OutputBuffer,
+                                               ULONG OutputBufferLength);
+
 extern NTSTATUS (WINAPI *func_NtClose) (HANDLE Handle);
 
 extern DWORD (WINAPI *func_RtlNtStatusToDosError)(NTSTATUS status);
 
+typedef struct _RTLP_CURDIR_REF {
+       LONG RefCount;
+       HANDLE Handle;
+} RTLP_CURDIR_REF, *PRTLP_CURDIR_REF;
+
+typedef struct _RTL_RELATIVE_NAME_U {
+       UNICODE_STRING RelativeName;
+       HANDLE ContainingDirectory;
+       PRTLP_CURDIR_REF CurDirRef;
+} RTL_RELATIVE_NAME_U, *PRTL_RELATIVE_NAME_U;
+
+extern NTSTATUS (WINAPI *func_RtlDosPathNameToNtPathName_U_WithStatus)
+               (IN PCWSTR DosName,
+                OUT PUNICODE_STRING NtName,
+                OUT PCWSTR *PartName,
+                OUT PRTL_RELATIVE_NAME_U RelativeName);
+
 extern NTSTATUS (WINAPI *func_RtlCreateSystemVolumeInformationFolder)
                        (PCUNICODE_STRING VolumeRootPath);
 
index b468ef3..d5a5c66 100644 (file)
@@ -52,7 +52,6 @@ typedef wchar_t tchar;
 #  define tstrerror    _wcserror
 #  define taccess      _waccess
 #  define tstrdup      wcsdup
-#  define ttempnam      _wtempnam
 #  define tgetenv      _wgetenv
 #  define totlower(c)  towlower((wchar_t)(c))
 /* The following "tchar" functions do not have exact wide-character equivalents
@@ -110,7 +109,6 @@ typedef char tchar;
 #  define tstrtoul     strtoul
 #  define tmkdir       mkdir
 #  define tstrdup      strdup
-#  define ttempnam      tempnam
 #  define tgetenv      getenv
 #  define totlower(c)  tolower((unsigned char)(c))
 #  define TSTRDUP      STRDUP
index a0db340..4979ee3 100644 (file)
@@ -157,7 +157,6 @@ enum {
        IMAGEX_EXTRACT_XML_OPTION,
        IMAGEX_FLAGS_OPTION,
        IMAGEX_FORCE_OPTION,
-       IMAGEX_HARDLINK_OPTION,
        IMAGEX_HEADER_OPTION,
        IMAGEX_INCLUDE_INVALID_NAMES_OPTION,
        IMAGEX_LAZY_OPTION,
@@ -190,7 +189,6 @@ enum {
        IMAGEX_STAGING_DIR_OPTION,
        IMAGEX_STREAMS_INTERFACE_OPTION,
        IMAGEX_STRICT_ACLS_OPTION,
-       IMAGEX_SYMLINK_OPTION,
        IMAGEX_THREADS_OPTION,
        IMAGEX_TO_STDOUT_OPTION,
        IMAGEX_UNIX_DATA_OPTION,
@@ -203,8 +201,6 @@ enum {
 
 static const struct option apply_options[] = {
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
-       {T("hardlink"),    no_argument,       NULL, IMAGEX_HARDLINK_OPTION},
-       {T("symlink"),     no_argument,       NULL, IMAGEX_SYMLINK_OPTION},
        {T("verbose"),     no_argument,       NULL, IMAGEX_VERBOSE_OPTION},
        {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
        {T("unix-data"),   no_argument,       NULL, IMAGEX_UNIX_DATA_OPTION},
@@ -1159,15 +1155,6 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                                      info->extract.total_parts);
                }
                break;
-       case WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS:
-               imagex_printf(T("Setting timestamps on all extracted files...\n"));
-               break;
-       case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END:
-               if (info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
-                       imagex_printf(T("Unmounting NTFS volume \"%"TS"\"...\n"),
-                               info->extract.target);
-               }
-               break;
        case WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART:
                percent_done = TO_PERCENT(info->split.completed_bytes,
                                          info->split.total_bytes);
@@ -1480,12 +1467,6 @@ imagex_apply(int argc, tchar **argv, int cmd)
                case IMAGEX_CHECK_OPTION:
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
                        break;
-               case IMAGEX_HARDLINK_OPTION:
-                       extract_flags |= WIMLIB_EXTRACT_FLAG_HARDLINK;
-                       break;
-               case IMAGEX_SYMLINK_OPTION:
-                       extract_flags |= WIMLIB_EXTRACT_FLAG_SYMLINK;
-                       break;
                case IMAGEX_VERBOSE_OPTION:
                        /* No longer does anything.  */
                        break;
@@ -3949,8 +3930,8 @@ T(
 "    %"TS" WIMFILE [(IMAGE_NUM | IMAGE_NAME | all)]\n"
 "                    (DIRECTORY | NTFS_VOLUME) [--check] [--ref=\"GLOB\"]\n"
 "                    [--no-acls] [--strict-acls] [--no-attributes]\n"
-"                    [--rpfix] [--norpfix] [--hardlink] [--symlink]\n"
-"                    [--include-invalid-names] [--wimboot]\n"
+"                    [--rpfix] [--norpfix] [--include-invalid-names]\n"
+"                    [--wimboot]\n"
 ),
 [CMD_CAPTURE] =
 T(
index a574f7c..efbae3d 100644 (file)
 /* Keep in sync with wimlib.h  */
 #define WIMLIB_EXTRACT_MASK_PUBLIC                             \
        (WIMLIB_EXTRACT_FLAG_NTFS                       |       \
-        WIMLIB_EXTRACT_FLAG_HARDLINK                   |       \
-        WIMLIB_EXTRACT_FLAG_SYMLINK                    |       \
-        WIMLIB_EXTRACT_FLAG_VERBOSE                    |       \
-        WIMLIB_EXTRACT_FLAG_SEQUENTIAL                 |       \
-        WIMLIB_EXTRACT_FLAG_UNIX_DATA                  |       \
         WIMLIB_EXTRACT_FLAG_NO_ACLS                    |       \
         WIMLIB_EXTRACT_FLAG_STRICT_ACLS                |       \
         WIMLIB_EXTRACT_FLAG_RPFIX                      |       \
         WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS          |       \
         WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES         |       \
         WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS            |       \
-        WIMLIB_EXTRACT_FLAG_RESUME                     |       \
-        WIMLIB_EXTRACT_FLAG_FILE_ORDER                 |       \
         WIMLIB_EXTRACT_FLAG_GLOB_PATHS                 |       \
         WIMLIB_EXTRACT_FLAG_STRICT_GLOB                |       \
         WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES              |       \
         WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE  |       \
         WIMLIB_EXTRACT_FLAG_WIMBOOT)
 
-static bool
-dentry_in_list(const struct wim_dentry *dentry)
-{
-       return dentry->extraction_list.next != NULL;
-}
-
-static inline bool
-is_linked_extraction(const struct apply_ctx *ctx)
-{
-       return 0 != (ctx->extract_flags & (WIMLIB_EXTRACT_FLAG_HARDLINK |
-                                          WIMLIB_EXTRACT_FLAG_SYMLINK));
-}
-
-static inline bool
-can_extract_named_data_streams(const struct apply_ctx *ctx)
-{
-       return ctx->supported_features.named_data_streams &&
-               !is_linked_extraction(ctx);
-}
-/* Inform library user of progress of stream extraction following the successful
- * extraction of a copy of the stream specified by @lte.  */
-static void
-update_extract_progress(struct apply_ctx *ctx,
-                       const struct wim_lookup_table_entry *lte)
-{
-       wimlib_progress_func_t progress_func = ctx->progress_func;
-       union wimlib_progress_info *progress = &ctx->progress;
-
-       progress->extract.completed_bytes += lte->size;
-       if (progress_func &&
-           progress->extract.completed_bytes >= ctx->next_progress)
-       {
-               progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS, progress);
-               if (progress->extract.completed_bytes >=
-                   progress->extract.total_bytes)
-               {
-                       ctx->next_progress = ~0ULL;
-               } else {
-                       ctx->next_progress += progress->extract.total_bytes / 128;
-                       if (ctx->next_progress > progress->extract.total_bytes)
-                               ctx->next_progress = progress->extract.total_bytes;
-               }
-       }
-}
-
-#ifndef __WIN32__
-/* Extract a symbolic link (not directly as reparse data), handling fixing up
- * the target of absolute symbolic links and updating the extract progress.
- *
- * @inode must specify the WIM inode for a symbolic link or junction reparse
- * point.
- *
- * @lte_override overrides the resource used as the reparse data for the
- * symbolic link.  */
-static int
-extract_symlink(const tchar *path, struct apply_ctx *ctx,
-               struct wim_inode *inode,
-               struct wim_lookup_table_entry *lte_override)
-{
-       ssize_t bufsize = ctx->ops->path_max;
-       tchar target[bufsize];
-       tchar *buf = target;
-       tchar *fixed_target;
-       ssize_t sret;
-       int ret;
-
-       /* If absolute symbolic link fixups requested, reserve space in the link
-        * target buffer for the absolute path of the target directory.  */
-       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX)
-       {
-               buf += ctx->realtarget_nchars;
-               bufsize -= ctx->realtarget_nchars;
-       }
-
-       /* Translate the WIM inode's reparse data into the link target.  */
-       sret = wim_inode_readlink(inode, buf, bufsize - 1, lte_override);
-       if (sret < 0) {
-               errno = -sret;
-               return WIMLIB_ERR_READLINK;
-       }
-       buf[sret] = '\0';
-
-       if ((ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) &&
-           buf[0] == '/')
-       {
-               /* Fix absolute symbolic link target to point into the
-                * actual extraction destination.  */
-               tmemcpy(target, ctx->realtarget, ctx->realtarget_nchars);
-               fixed_target = target;
-       } else {
-               /* Keep same link target.  */
-               fixed_target = buf;
-       }
-
-       /* Call into the apply_operations to create the symbolic link.  */
-       DEBUG("Creating symlink \"%"TS"\" => \"%"TS"\"",
-             path, fixed_target);
-       ret = ctx->ops->create_symlink(fixed_target, path, ctx);
-       if (ret) {
-               ERROR_WITH_ERRNO("Failed to create symlink "
-                                "\"%"TS"\" => \"%"TS"\"", path, fixed_target);
-               return ret;
-       }
-
-       /* Account for reparse data consumed.  */
-       update_extract_progress(ctx,
-                               (lte_override ? lte_override :
-                                     inode_unnamed_lte_resolved(inode)));
-       return 0;
-}
-#endif /* !__WIN32__ */
-
-/* Create a file, directory, or symbolic link.  */
-static int
-extract_inode(const tchar *path, struct apply_ctx *ctx, struct wim_inode *inode)
-{
-       int ret;
-
-#ifndef __WIN32__
-       if (ctx->supported_features.symlink_reparse_points &&
-           !ctx->supported_features.reparse_points &&
-           inode_is_symlink(inode))
-       {
-               ret = extract_symlink(path, ctx, inode, NULL);
-       } else
-#endif /* !__WIN32__ */
-       if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
-               ret = ctx->ops->create_directory(path, ctx, &inode->extract_cookie);
-               if (ret) {
-                       ERROR_WITH_ERRNO("Failed to create the directory "
-                                        "\"%"TS"\"", path);
-               }
-       } else if ((inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) &&
-                   ctx->ops->extract_encrypted_stream_creates_file &&
-                   ctx->supported_features.encrypted_files) {
-               ret = ctx->ops->extract_encrypted_stream(
-                               path, inode_unnamed_lte_resolved(inode), ctx);
-               if (ret) {
-                       ERROR_WITH_ERRNO("Failed to create and extract "
-                                        "encrypted file \"%"TS"\"", path);
-               }
-       } else {
-               ret = ctx->ops->create_file(path, ctx, &inode->extract_cookie);
-               if (ret) {
-                       ERROR_WITH_ERRNO("Failed to create the file "
-                                        "\"%"TS"\"", path);
-               }
-       }
-       return ret;
-}
-
-static int
-extract_hardlink(const tchar *oldpath, const tchar *newpath,
-                struct apply_ctx *ctx)
-{
-       int ret;
-
-       DEBUG("Creating hardlink \"%"TS"\" => \"%"TS"\"", newpath, oldpath);
-       ret = ctx->ops->create_hardlink(oldpath, newpath, ctx);
-       if (ret) {
-               ERROR_WITH_ERRNO("Failed to create hardlink "
-                                "\"%"TS"\" => \"%"TS"\"",
-                                newpath, oldpath);
-       }
-       return ret;
-}
-
-#ifdef __WIN32__
-static int
-try_extract_rpfix(u8 *rpbuf,
-                 u16 *rpbuflen_p,
-                 const wchar_t *extract_root_realpath,
-                 unsigned extract_root_realpath_nchars)
-{
-       struct reparse_data rpdata;
-       wchar_t *target;
-       size_t target_nchars;
-       size_t stripped_nchars;
-       wchar_t *stripped_target;
-       wchar_t stripped_target_nchars;
-       int ret;
-
-       utf16lechar *new_target;
-       utf16lechar *new_print_name;
-       size_t new_target_nchars;
-       size_t new_print_name_nchars;
-       utf16lechar *p;
-
-       ret = parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata);
-       if (ret)
-               return ret;
-
-       if (extract_root_realpath[0] == L'\0' ||
-           extract_root_realpath[1] != L':' ||
-           extract_root_realpath[2] != L'\\')
-               return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
-
-       ret = parse_substitute_name(rpdata.substitute_name,
-                                   rpdata.substitute_name_nbytes,
-                                   rpdata.rptag);
-       if (ret < 0)
-               return 0;
-       stripped_nchars = ret;
-       target = rpdata.substitute_name;
-       target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar);
-       stripped_target = target + stripped_nchars;
-       stripped_target_nchars = target_nchars - stripped_nchars;
-
-       new_target = alloca((6 + extract_root_realpath_nchars +
-                            stripped_target_nchars) * sizeof(utf16lechar));
-
-       p = new_target;
-       if (stripped_nchars == 6) {
-               /* Include \??\ prefix if it was present before */
-               p = wmempcpy(p, L"\\??\\", 4);
-       }
-
-       /* Print name excludes the \??\ if present. */
-       new_print_name = p;
-       if (stripped_nchars != 0) {
-               /* Get drive letter from real path to extract root, if a drive
-                * letter was present before. */
-               *p++ = extract_root_realpath[0];
-               *p++ = extract_root_realpath[1];
-       }
-       /* Copy the rest of the extract root */
-       p = wmempcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
-
-       /* Append the stripped target */
-       p = wmempcpy(p, stripped_target, stripped_target_nchars);
-       new_target_nchars = p - new_target;
-       new_print_name_nchars = p - new_print_name;
-
-       if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE ||
-           new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE)
-               return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
-
-       rpdata.substitute_name = new_target;
-       rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar);
-       rpdata.print_name = new_print_name;
-       rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar);
-       return make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p);
-}
-#endif /* __WIN32__ */
-
-/* Set reparse data on extracted file or directory that has
- * FILE_ATTRIBUTE_REPARSE_POINT set.  */
-static int
-extract_reparse_data(const tchar *path, struct apply_ctx *ctx,
-                    struct wim_inode *inode,
-                    struct wim_lookup_table_entry *lte_override)
-{
-       int ret;
-       u8 rpbuf[REPARSE_POINT_MAX_SIZE];
-       u16 rpbuflen;
-
-       ret = wim_inode_get_reparse_data(inode, rpbuf, &rpbuflen, lte_override);
-       if (ret)
-               goto error;
-
-#ifdef __WIN32__
-       /* Fix up target of absolute symbolic link or junction points so
-        * that they point into the actual extraction target.  */
-       if ((ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) &&
-           (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
-            inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) &&
-           !inode->i_not_rpfixed)
-       {
-               ret = try_extract_rpfix(rpbuf, &rpbuflen, ctx->realtarget,
-                                       ctx->realtarget_nchars);
-               if (ret && !(ctx->extract_flags &
-                            WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS))
-               {
-                       WARNING("Reparse point fixup of \"%"TS"\" "
-                               "failed", path);
-                       ret = 0;
-               }
-               if (ret)
-                       goto error;
-       }
-#endif
-
-       ret = ctx->ops->set_reparse_data(path, rpbuf, rpbuflen, ctx);
-
-       /* On Windows, the SeCreateSymbolicLink privilege is required to create
-        * symbolic links.  To be more friendly towards non-Administrator users,
-        * we merely warn the user if symbolic links cannot be created due to
-        * insufficient permissions or privileges, unless
-        * WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS was provided.  */
-#ifdef __WIN32__
-       if (ret && inode_is_symlink(inode) &&
-           (errno == EACCES || errno == EPERM) &&
-           !(ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS))
-       {
-               WARNING("Can't set reparse data on \"%"TS"\": "
-                       "Access denied!\n"
-                       "          You may be trying to "
-                       "extract a symbolic link without the\n"
-                       "          SeCreateSymbolicLink privilege, "
-                       "which by default non-Administrator\n"
-                       "          accounts do not have.",
-                       path);
-               ret = 0;
-       }
-#endif
-       if (ret)
-               goto error;
-
-       /* Account for reparse data consumed.  */
-       update_extract_progress(ctx,
-                               (lte_override ? lte_override :
-                                     inode_unnamed_lte_resolved(inode)));
-       return 0;
-
-error:
-       ERROR_WITH_ERRNO("Failed to set reparse data on \"%"TS"\"", path);
-       return ret;
-}
-
-/*
- * Extract zero or more streams to a file.
- *
- * This function operates slightly differently depending on whether @lte_spec is
- * NULL or not.  When @lte_spec is NULL, the behavior is to extract the default
- * file contents (unnamed stream), and, if named data streams are supported in
- * the extract mode and volume, any named data streams.  When @lte_spec is not
- * NULL, the behavior is to extract only all copies of the stream @lte_spec, and
- * in addition use @lte_spec to set the reparse data or create the symbolic link
- * if appropriate.
- *
- * @path
- *     Path to file to extract (as can be passed to apply_operations
- *     functions).
- * @ctx
- *     Apply context.
- * @dentry
- *     WIM dentry that corresponds to the file being extracted.
- * @lte_spec
- *     If non-NULL, specifies the lookup table entry for a stream to extract,
- *     and only that stream will be extracted (although there may be more than
- *     one instance of it).
- * @lte_override
- *     Used only if @lte_spec != NULL; it is passed to the extraction functions
- *     rather than @lte_spec, allowing the location of the stream to be
- *     overridden.  (This is used when the WIM is being read from a nonseekable
- *     file, such as a pipe, when streams need to be used more than once; each
- *     such stream is extracted to a temporary file.)
- */
-static int
-extract_streams(const tchar *path, struct apply_ctx *ctx,
-               struct wim_dentry *dentry,
-               struct wim_lookup_table_entry *lte_spec,
-               struct wim_lookup_table_entry *lte_override)
-{
-       struct wim_inode *inode = dentry->d_inode;
-       struct wim_lookup_table_entry *lte;
-       file_spec_t file_spec;
-       int ret;
-
-       if (dentry->was_linked)
-               return 0;
-
-#ifdef ENABLE_DEBUG
-       if (lte_spec) {
-               char sha1_str[100];
-               char *p = sha1_str;
-               for (unsigned i = 0; i < SHA1_HASH_SIZE; i++)
-                       p += sprintf(p, "%02x", lte_override->hash[i]);
-               DEBUG("Extracting stream SHA1=%s to \"%"TS"\"",
-                     sha1_str, path, inode->i_ino);
-       } else {
-               DEBUG("Extracting streams to \"%"TS"\"", path, inode->i_ino);
-       }
-#endif
-
-       if (ctx->ops->uses_cookies)
-               file_spec.cookie = inode->extract_cookie;
-       else
-               file_spec.path = path;
-
-       /* Unnamed data stream.  */
-       lte = inode_unnamed_lte_resolved(inode);
-       if (lte && (!lte_spec || lte == lte_spec)) {
-               if (lte_spec)
-                       lte = lte_override;
-               if (!(inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
-                                            FILE_ATTRIBUTE_REPARSE_POINT)))
-               {
-                       if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
-                           ctx->supported_features.encrypted_files) {
-                               if (!ctx->ops->extract_encrypted_stream_creates_file) {
-                                       ret = ctx->ops->extract_encrypted_stream(
-                                                               path, lte, ctx);
-                                       if (ret)
-                                               goto error;
-                               }
-                       } else {
-                               ret = ctx->ops->extract_unnamed_stream(
-                                                       file_spec, lte, ctx,
-                                                       dentry);
-                               if (ret)
-                                       goto error;
-                       }
-                       update_extract_progress(ctx, lte);
-               }
-               else if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
-               {
-                       ret = 0;
-                       if (ctx->supported_features.reparse_points)
-                               ret = extract_reparse_data(path, ctx, inode, lte);
-               #ifndef __WIN32__
-                       else if ((inode_is_symlink(inode) &&
-                                 ctx->supported_features.symlink_reparse_points))
-                               ret = extract_symlink(path, ctx, inode, lte);
-               #endif
-                       if (ret)
-                               return ret;
-               }
-       }
-
-       /* Named data streams.  */
-       if (can_extract_named_data_streams(ctx)) {
-               for (u16 i = 0; i < inode->i_num_ads; i++) {
-                       struct wim_ads_entry *entry = &inode->i_ads_entries[i];
-
-                       if (!ads_entry_is_named_stream(entry))
-                               continue;
-                       lte = entry->lte;
-                       if (!lte)
-                               continue;
-                       if (lte_spec && lte_spec != lte)
-                               continue;
-                       if (lte_spec)
-                               lte = lte_override;
-                       ret = ctx->ops->extract_named_stream(file_spec, entry->stream_name,
-                                                            entry->stream_name_nbytes / 2,
-                                                            lte, ctx);
-                       if (ret)
-                               goto error;
-                       update_extract_progress(ctx, lte);
-               }
-       }
-       return 0;
-
-error:
-       ERROR_WITH_ERRNO("Failed to extract data of \"%"TS"\"", path);
-       return ret;
-}
-
-/* Set attributes on an extracted file or directory if supported by the
- * extraction mode.  */
-static int
-extract_file_attributes(const tchar *path, struct apply_ctx *ctx,
-                       struct wim_dentry *dentry, unsigned pass)
-{
-       int ret;
-
-       if (ctx->ops->set_file_attributes &&
-           !(ctx->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) &&
-           !(dentry == ctx->target_dentry && ctx->root_dentry_is_special)) {
-               u32 attributes = dentry->d_inode->i_attributes;
-
-               /* Clear unsupported attributes.  */
-               attributes &= ctx->supported_attributes_mask;
-
-               if ((attributes & FILE_ATTRIBUTE_DIRECTORY &&
-                    !ctx->supported_features.encrypted_directories) ||
-                   (!(attributes & FILE_ATTRIBUTE_DIRECTORY) &&
-                    !ctx->supported_features.encrypted_files))
-               {
-                       attributes &= ~FILE_ATTRIBUTE_ENCRYPTED;
-               }
-
-               if (attributes == 0)
-                       attributes = FILE_ATTRIBUTE_NORMAL;
-
-               ret = ctx->ops->set_file_attributes(path, attributes, ctx, pass);
-               if (ret) {
-                       ERROR_WITH_ERRNO("Failed to set attributes on "
-                                        "\"%"TS"\"", path);
-                       return ret;
-               }
-       }
-       return 0;
-}
-
-
-/* Set or remove the short (DOS) name on an extracted file or directory if
- * supported by the extraction mode.  Since DOS names are unimportant and it's
- * easy to run into problems setting them on Windows (SetFileShortName()
- * requires SE_RESTORE privilege, which only the Administrator can request, and
- * also requires DELETE access to the file), failure is ignored unless
- * WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES is set.  */
-static int
-extract_short_name(const tchar *path, struct apply_ctx *ctx,
-                  struct wim_dentry *dentry)
-{
-       int ret;
-
-       /* The root of the dentry tree being extracted may not be extracted to
-        * its original name, so its short name should be ignored.  */
-       if (dentry == ctx->target_dentry)
-               return 0;
-
-       if (ctx->supported_features.short_names) {
-               ret = ctx->ops->set_short_name(path,
-                                              dentry->short_name,
-                                              dentry->short_name_nbytes / 2,
-                                              ctx);
-               if (ret && (ctx->extract_flags &
-                           WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES))
-               {
-                       ERROR_WITH_ERRNO("Failed to set short name of "
-                                        "\"%"TS"\"", path);
-                       return ret;
-               }
-       }
-       return 0;
-}
-
-/* Set security descriptor, UNIX data, or neither on an extracted file, taking
- * into account the current extraction mode and flags.  */
-static int
-extract_security(const tchar *path, struct apply_ctx *ctx,
-                struct wim_dentry *dentry)
-{
-       int ret;
-       struct wim_inode *inode = dentry->d_inode;
-
-       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
-               return 0;
-
-       if ((ctx->target_dentry == dentry) && ctx->root_dentry_is_special)
-               return 0;
-
-#ifndef __WIN32__
-       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
-               struct wimlib_unix_data data;
-
-               ret = inode_get_unix_data(inode, &data, NULL);
-               if (ret < 0)
-                       ret = 0;
-               else if (ret == 0)
-                       ret = ctx->ops->set_unix_data(path, &data, ctx);
-               if (ret) {
-                       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
-                               ERROR_WITH_ERRNO("Failed to set UNIX owner, "
-                                                "group, and/or mode on "
-                                                "\"%"TS"\"", path);
-                               return ret;
-                       } else {
-                               WARNING_WITH_ERRNO("Failed to set UNIX owner, "
-                                                  "group, and/or/mode on "
-                                                  "\"%"TS"\"", path);
-                       }
-               }
-       }
-       else
-#endif /* __WIN32__ */
-       if (ctx->supported_features.security_descriptors &&
-           inode->i_security_id != -1)
-       {
-               struct wim_security_data *sd;
-               const u8 *desc;
-               size_t desc_size;
-
-               sd = wim_get_current_security_data(ctx->wim);
-               desc = sd->descriptors[inode->i_security_id];
-               desc_size = sd->sizes[inode->i_security_id];
-
-               ret = ctx->ops->set_security_descriptor(path, desc,
-                                                       desc_size, ctx);
-               if (ret) {
-                       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
-                               ERROR_WITH_ERRNO("Failed to set security "
-                                                "descriptor on \"%"TS"\"", path);
-                               return ret;
-                       } else {
-                       #if 0
-                               if (errno != EACCES) {
-                                       WARNING_WITH_ERRNO("Failed to set "
-                                                          "security descriptor "
-                                                          "on \"%"TS"\"", path);
-                               }
-                       #endif
-                               ctx->no_security_descriptors++;
-                       }
-               }
-       }
-       return 0;
-}
-
-/* Set timestamps on an extracted file.  Failure is warning-only unless
- * WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS is set.  */
-static int
-extract_timestamps(const tchar *path, struct apply_ctx *ctx,
-                  struct wim_dentry *dentry)
-{
-       struct wim_inode *inode = dentry->d_inode;
-       int ret;
-
-       if ((ctx->target_dentry == dentry) && ctx->root_dentry_is_special)
-               return 0;
-
-       if (ctx->ops->set_timestamps) {
-               ret = ctx->ops->set_timestamps(path,
-                                              inode->i_creation_time,
-                                              inode->i_last_write_time,
-                                              inode->i_last_access_time,
-                                              ctx);
-               if (ret) {
-                       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS) {
-                               ERROR_WITH_ERRNO("Failed to set timestamps "
-                                                "on \"%"TS"\"", path);
-                               return ret;
-                       } else {
-                               WARNING_WITH_ERRNO("Failed to set timestamps "
-                                                  "on \"%"TS"\"", path);
-                       }
-               }
-       }
-       return 0;
-}
-
 /* Check whether the extraction of a dentry should be skipped completely.  */
 static bool
 dentry_is_supported(struct wim_dentry *dentry,
-                   const struct wim_features *supported_features)
-{
-       struct wim_inode *inode = dentry->d_inode;
-
-       if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
-               return supported_features->reparse_points ||
-                       (inode_is_symlink(inode) &&
-                        supported_features->symlink_reparse_points);
-       }
-       if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
-               if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
-                       return supported_features->encrypted_directories != 0;
-               else
-                       return supported_features->encrypted_files != 0;
-       }
-       return true;
-}
-
-/* Given a WIM dentry to extract, build the path to which to extract it, in the
- * format understood by the callbacks in the apply_operations being used.
- *
- * Write the resulting path into @path, which must have room for at least
- * ctx->ops->path_max characters.
- *
- * Return %true if successful; %false if this WIM dentry doesn't actually need
- * to be extracted or if the calculated path exceeds ctx->ops->max_path
- * characters.
- *
- * This function clobbers the tmp_list member of @dentry and its ancestors up
- * until the extraction root.  */
-static bool
-build_extraction_path(tchar path[], struct wim_dentry *dentry,
-                     const struct apply_ctx *ctx)
-{
-       size_t path_nchars;
-       LIST_HEAD(ancestor_list);
-       tchar *p = path;
-       const tchar *target_prefix;
-       size_t target_prefix_nchars;
-       struct wim_dentry *d;
-
-       path_nchars = ctx->ops->path_prefix_nchars;
-
-       if (ctx->ops->requires_realtarget_in_paths) {
-               target_prefix        = ctx->realtarget;
-               target_prefix_nchars = ctx->realtarget_nchars;
-       } else if (ctx->ops->requires_target_in_paths) {
-               target_prefix        = ctx->target;
-               target_prefix_nchars = ctx->target_nchars;
-       } else {
-               target_prefix        = NULL;
-               target_prefix_nchars = 0;
-       }
-       path_nchars += target_prefix_nchars;
-
-       for (d = dentry; d != ctx->target_dentry; d = d->parent) {
-               if (!dentry_in_list(d))
-                       break;
-
-               path_nchars += d->extraction_name_nchars + 1;
-               list_add(&d->tmp_list, &ancestor_list);
-       }
-
-       path_nchars++; /* null terminator */
-
-       if (path_nchars > ctx->ops->path_max) {
-               WARNING("\"%"TS"\": Path too long to extract",
-                       dentry_full_path(dentry));
-               return false;
-       }
-
-       p = tmempcpy(p, ctx->ops->path_prefix, ctx->ops->path_prefix_nchars);
-       p = tmempcpy(p, target_prefix, target_prefix_nchars);
-       list_for_each_entry(d, &ancestor_list, tmp_list) {
-               *p++ = ctx->ops->path_separator;
-               p = tmempcpy(p, d->extraction_name, d->extraction_name_nchars);
-       }
-       *p++ = T('\0');
-       wimlib_assert(p - path == path_nchars);
-       return true;
-}
-
-static unsigned
-get_num_path_components(const tchar *path, tchar path_separator)
-{
-       unsigned num_components = 0;
-#ifdef __WIN32__
-       /* Ignore drive letter.  */
-       if (path[0] != L'\0' && path[1] == L':')
-               path += 2;
-#endif
-
-       while (*path) {
-               while (*path == path_separator)
-                       path++;
-               if (*path)
-                       num_components++;
-               while (*path && *path != path_separator)
-                       path++;
-       }
-       return num_components;
-}
-
-static int
-extract_multiimage_symlink(const tchar *oldpath, const tchar *newpath,
-                          struct apply_ctx *ctx, struct wim_dentry *dentry)
-{
-       size_t num_raw_path_components;
-       const struct wim_dentry *d;
-       size_t num_target_path_components;
-       tchar *p;
-       const tchar *p_old;
-       int ret;
-
-       num_raw_path_components = 0;
-       for (d = dentry; d != ctx->target_dentry; d = d->parent)
-               num_raw_path_components++;
-
-       if (ctx->ops->requires_realtarget_in_paths)
-               num_target_path_components = get_num_path_components(ctx->realtarget,
-                                                                    ctx->ops->path_separator);
-       else if (ctx->ops->requires_target_in_paths)
-               num_target_path_components = get_num_path_components(ctx->target,
-                                                                    ctx->ops->path_separator);
-       else
-               num_target_path_components = 0;
-
-       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) {
-               wimlib_assert(num_target_path_components > 0);
-               num_raw_path_components++;
-               num_target_path_components--;
-       }
-
-       p_old = oldpath + ctx->ops->path_prefix_nchars;
-#ifdef __WIN32__
-       if (p_old[0] != L'\0' && p_old[1] == ':')
-               p_old += 2;
-#endif
-       while (*p_old == ctx->ops->path_separator)
-               p_old++;
-       while (--num_target_path_components) {
-               while (*p_old != ctx->ops->path_separator)
-                       p_old++;
-               while (*p_old == ctx->ops->path_separator)
-                       p_old++;
-       }
-
-       tchar symlink_target[tstrlen(p_old) + 3 * num_raw_path_components + 1];
-
-       p = &symlink_target[0];
-       while (num_raw_path_components--) {
-               *p++ = '.';
-               *p++ = '.';
-               *p++ = ctx->ops->path_separator;
-       }
-       tstrcpy(p, p_old);
-       DEBUG("Creating symlink \"%"TS"\" => \"%"TS"\"",
-             newpath, symlink_target);
-       ret = ctx->ops->create_symlink(symlink_target, newpath, ctx);
-       if (ret) {
-               ERROR_WITH_ERRNO("Failed to create symlink "
-                                "\"%"TS"\" => \"%"TS"\"",
-                                newpath, symlink_target);
-       }
-       return ret;
-}
-
-/* Create the "skeleton" of an extracted file or directory.  Don't yet extract
- * data streams, reparse data (including symbolic links), timestamps, and
- * security descriptors.  Basically, everything that doesn't require reading
- * non-metadata resources from the WIM file and isn't delayed until the final
- * pass.  */
-static int
-do_dentry_extract_skeleton(tchar path[], struct wim_dentry *dentry,
-                          struct apply_ctx *ctx)
+                   const struct wim_features *supported_features)
 {
        struct wim_inode *inode = dentry->d_inode;
-       int ret;
-       const tchar *oldpath;
-
-       if (unlikely(is_linked_extraction(ctx))) {
-               struct wim_lookup_table_entry *unnamed_lte;
-
-               unnamed_lte = inode_unnamed_lte_resolved(dentry->d_inode);
-               if (unnamed_lte && unnamed_lte->extracted_file) {
-                       oldpath = unnamed_lte->extracted_file;
-                       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK)
-                               goto hardlink;
-                       else
-                               goto symlink;
-               }
-       }
-
-       /* Create hard link if this dentry corresponds to an already-extracted
-        * inode.  */
-       if (inode->i_extracted_file) {
-               oldpath = inode->i_extracted_file;
-               goto hardlink;
-       }
-
-       /* Skip symlinks unless they can be extracted as reparse points rather
-        * than created directly.  */
-       if (inode_is_symlink(inode) && !ctx->supported_features.reparse_points)
-               return 0;
-
-       /* Create this file or directory unless it's the extraction root, which
-        * was already created if necessary.  */
-       if (dentry != ctx->target_dentry) {
-               ret = extract_inode(path, ctx, inode);
-               if (ret)
-                       return ret;
-       }
-
-       /* Create empty named data streams.  */
-       if (can_extract_named_data_streams(ctx)) {
-               for (u16 i = 0; i < inode->i_num_ads; i++) {
-                       file_spec_t file_spec;
-                       struct wim_ads_entry *entry = &inode->i_ads_entries[i];
-
-                       if (!ads_entry_is_named_stream(entry))
-                               continue;
-                       if (entry->lte)
-                               continue;
-                       if (ctx->ops->uses_cookies)
-                               file_spec.cookie = inode->extract_cookie;
-                       else
-                               file_spec.path = path;
-                       ret = ctx->ops->extract_named_stream(file_spec,
-                                                            entry->stream_name,
-                                                            entry->stream_name_nbytes / 2,
-                                                            entry->lte, ctx);
-                       if (ret) {
-                               ERROR_WITH_ERRNO("\"%"TS"\": failed to create "
-                                                "empty named data stream",
-                                                path);
-                               return ret;
-                       }
-               }
-       }
-
-       /* Set file attributes (if supported).  */
-       ret = extract_file_attributes(path, ctx, dentry, 0);
-       if (ret)
-               return ret;
-
-       /* Set or remove file short name (if supported).  */
-       ret = extract_short_name(path, ctx, dentry);
-       if (ret)
-               return ret;
-
-       /* If inode has multiple links and hard links are supported in this
-        * extraction mode and volume, save the path to the extracted file in
-        * case it's needed to create a hard link.  */
-       if (unlikely(is_linked_extraction(ctx))) {
-               struct wim_lookup_table_entry *unnamed_lte;
-
-               unnamed_lte = inode_unnamed_lte_resolved(dentry->d_inode);
-               if (unnamed_lte) {
-                       unnamed_lte->extracted_file = TSTRDUP(path);
-                       if (!unnamed_lte->extracted_file)
-                               return WIMLIB_ERR_NOMEM;
-               }
-       } else if (inode->i_nlink > 1 && ctx->supported_features.hard_links) {
-               inode->i_extracted_file = TSTRDUP(path);
-               if (!inode->i_extracted_file)
-                       return WIMLIB_ERR_NOMEM;
-       }
-       return 0;
-
-symlink:
-       ret = extract_multiimage_symlink(oldpath, path, ctx, dentry);
-       if (ret)
-               return ret;
-       dentry->was_linked = 1;
-       return 0;
-
-hardlink:
-       ret = extract_hardlink(oldpath, path, ctx);
-       if (ret)
-               return ret;
-       dentry->was_linked = 1;
-       return 0;
-}
-
-/* This is a wrapper around do_dentry_extract_skeleton() that handles building
- * the path, doing short name reordering.  This is also idempotent; dentries
- * already processed have skeleton_extracted set and no action is taken.  See
- * apply_operations.requires_short_name_reordering for more details about short
- * name reordering.  */
-static int
-dentry_extract_skeleton(struct wim_dentry *dentry, struct apply_ctx *ctx)
-{
-       tchar path[ctx->ops->path_max];
-       struct wim_dentry *orig_dentry;
-       struct wim_dentry *other_dentry;
-       int ret;
-
-       if (dentry->skeleton_extracted)
-               return 0;
-
-       orig_dentry = NULL;
-       if (ctx->supported_features.short_names
-           && ctx->ops->requires_short_name_reordering
-           && !dentry_has_short_name(dentry)
-           && !dentry->d_inode->i_dos_name_extracted)
-       {
-               inode_for_each_dentry(other_dentry, dentry->d_inode) {
-                       if (dentry_has_short_name(other_dentry)
-                           && !other_dentry->skeleton_extracted
-                           && dentry_in_list(other_dentry))
-                       {
-                               DEBUG("Creating %"TS" before %"TS" "
-                                     "to guarantee correct DOS name extraction",
-                                     dentry_full_path(other_dentry),
-                                     dentry_full_path(dentry));
-                               orig_dentry = dentry;
-                               dentry = other_dentry;
-                               break;
-                       }
-               }
-       }
-again:
-       if (!build_extraction_path(path, dentry, ctx))
-               return 0;
-       ret = do_dentry_extract_skeleton(path, dentry, ctx);
-       if (ret)
-               return ret;
-
-       dentry->skeleton_extracted = 1;
-
-       if (orig_dentry) {
-               dentry = orig_dentry;
-               orig_dentry = NULL;
-               goto again;
-       }
-       dentry->d_inode->i_dos_name_extracted = 1;
-       return 0;
-}
-
-/* Create a file or directory, then immediately extract all streams.  The WIM
- * may not be read sequentially by this function.  */
-static int
-dentry_extract(struct wim_dentry *dentry, struct apply_ctx *ctx)
-{
-       tchar path[ctx->ops->path_max];
-       int ret;
-
-       ret = dentry_extract_skeleton(dentry, ctx);
-       if (ret)
-               return ret;
-
-       if (!build_extraction_path(path, dentry, ctx))
-               return 0;
-
-       return extract_streams(path, ctx, dentry, NULL, NULL);
-}
-
-/* Finish extracting a file, directory, or symbolic link by setting file
- * security and timestamps.  */
-static int
-dentry_extract_final(struct wim_dentry *dentry, struct apply_ctx *ctx)
-{
-       int ret;
-       tchar path[ctx->ops->path_max];
-
-       if (!build_extraction_path(path, dentry, ctx))
-               return 0;
-
-       ret = extract_security(path, ctx, dentry);
-       if (ret)
-               return ret;
-
-       if (ctx->ops->requires_final_set_attributes_pass) {
-               /* Set file attributes (if supported).  */
-               ret = extract_file_attributes(path, ctx, dentry, 1);
-               if (ret)
-                       return ret;
-       }
-
-       return extract_timestamps(path, ctx, dentry);
-}
-
-static int
-extract_structure(struct list_head *dentry_list, struct apply_ctx *ctx)
-{
-       struct wim_dentry *dentry;
-       int ret;
-
-       list_for_each_entry(dentry, dentry_list, extraction_list) {
-               ret = dentry_extract_skeleton(dentry, ctx);
-               if (ret)
-                       return ret;
-       }
-       return 0;
-}
-
-static int
-extract_dir_structure(struct list_head *dentry_list, struct apply_ctx *ctx)
-{
-       struct wim_dentry *dentry;
-       int ret;
-
-       list_for_each_entry(dentry, dentry_list, extraction_list) {
-               if (dentry_is_directory(dentry)) {
-                       ret = dentry_extract_skeleton(dentry, ctx);
-                       if (ret)
-                               return ret;
-               }
-       }
-       return 0;
-}
-
-static int
-extract_dentries(struct list_head *dentry_list, struct apply_ctx *ctx)
-{
-       struct wim_dentry *dentry;
-       int ret;
-
-       list_for_each_entry(dentry, dentry_list, extraction_list) {
-               ret = dentry_extract(dentry, ctx);
-               if (ret)
-                       return ret;
-       }
-       return 0;
-}
 
-static int
-extract_final_metadata(struct list_head *dentry_list, struct apply_ctx *ctx)
-{
-       struct wim_dentry *dentry;
-       int ret;
-
-       list_for_each_entry_reverse(dentry, dentry_list, extraction_list) {
-               ret = dentry_extract_final(dentry, ctx);
-               if (ret)
-                       return ret;
-       }
-       return 0;
-}
-
-/* Creates a temporary file opened for writing.  The open file descriptor is
- * returned in @fd_ret and its name is returned in @name_ret (dynamically
- * allocated).  */
-static int
-create_temporary_file(struct filedes *fd_ret, tchar **name_ret)
-{
-       tchar *name;
-       int raw_fd;
-       int open_flags;
-
-retry:
-       name = ttempnam(NULL, T("wimlib"));
-       if (name == NULL) {
-               ERROR_WITH_ERRNO("Failed to create temporary filename");
-               return WIMLIB_ERR_NOMEM;
-       }
-
-       open_flags = O_WRONLY | O_CREAT | O_EXCL | O_BINARY;
-#ifdef __WIN32__
-       open_flags |= _O_SHORT_LIVED;
-#endif
-       raw_fd = topen(name, open_flags, 0600);
-
-       if (raw_fd < 0) {
-               if (errno == EEXIST) {
-                       FREE(name);
-                       goto retry;
-               }
-               ERROR_WITH_ERRNO("Failed to open temporary file \"%"TS"\"", name);
-               FREE(name);
-               return WIMLIB_ERR_OPEN;
+       if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
+               return supported_features->reparse_points ||
+                       (inode_is_symlink(inode) &&
+                        supported_features->symlink_reparse_points);
        }
-
-       filedes_init(fd_ret, raw_fd);
-       *name_ret = name;
-       return 0;
-}
-
-/* Extract all instances of the stream @lte that are being extracted in this
- * call of extract_tree(), but actually read the stream data from @lte_override.
- */
-static int
-extract_stream_instances(struct wim_lookup_table_entry *lte,
-                        struct wim_lookup_table_entry *lte_override,
-                        struct apply_ctx *ctx)
-{
-       struct wim_dentry **lte_dentries;
-       tchar path[ctx->ops->path_max];
-       size_t i;
-       int ret;
-
-       if (lte->out_refcnt <= ARRAY_LEN(lte->inline_lte_dentries))
-               lte_dentries = lte->inline_lte_dentries;
-       else
-               lte_dentries = lte->lte_dentries;
-
-       for (i = 0; i < lte->out_refcnt; i++) {
-               struct wim_dentry *dentry = lte_dentries[i];
-
-               if (dentry->tmp_flag)
-                       continue;
-               if (!build_extraction_path(path, dentry, ctx))
-                       continue;
-               ret = extract_streams(path, ctx, dentry, lte, lte_override);
-               if (ret)
-                       goto out_clear_tmp_flags;
-               dentry->tmp_flag = 1;
+       if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
+               if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
+                       return supported_features->encrypted_directories != 0;
+               else
+                       return supported_features->encrypted_files != 0;
        }
-       ret = 0;
-out_clear_tmp_flags:
-       for (i = 0; i < lte->out_refcnt; i++)
-               lte_dentries[i]->tmp_flag = 0;
-       return ret;
-}
-
-/* Determine whether the specified stream needs to be extracted to a temporary
- * file or not.
- *
- * @lte->out_refcnt specifies the number of instances of this stream that must
- * be extracted.
- *
- * @is_partial_res is %true if this stream is just one of multiple in a single
- * WIM resource being extracted.  */
-static bool
-need_tmpfile_to_extract(struct wim_lookup_table_entry *lte,
-                       bool is_partial_res)
-{
-       /* Temporary file is always required when reading a partial resource,
-        * since in that case we retrieve all the contained streams in one pass.
-        * */
-       if (is_partial_res)
-               return true;
-
-       /* Otherwise we don't need a temporary file if only a single instance of
-        * the stream is needed.  */
-       if (lte->out_refcnt == 1)
-               return false;
-
-       wimlib_assert(lte->out_refcnt >= 2);
-
-       /* We also don't need a temporary file if random access to the stream is
-        * allowed.  */
-       if (lte->resource_location != RESOURCE_IN_WIM ||
-           filedes_is_seekable(&lte->rspec->wim->in_fd))
-               return false;
-
        return true;
 }
 
-static int
-begin_extract_stream(struct wim_lookup_table_entry *lte,
-                    u32 flags, void *_ctx)
-{
-       struct apply_ctx *ctx = _ctx;
-       int ret;
-
-       if (flags & BEGIN_STREAM_FLAG_WHOLE_STREAM) {
-               DEBUG("Whole stream (size=%"PRIu64") will be read into memory",
-                     lte->size);
-               ctx->cur_stream = lte;
-               filedes_invalidate(&ctx->tmpfile_fd);
-               return 0;
-       }
-
-       if (!need_tmpfile_to_extract(lte,
-                                    (flags & BEGIN_STREAM_FLAG_PARTIAL_RESOURCE)))
-       {
-               DEBUG("Temporary file not needed "
-                     "for stream (size=%"PRIu64")", lte->size);
-               ret = extract_stream_instances(lte, lte, ctx);
-               if (ret)
-                       return ret;
-
-               return BEGIN_STREAM_STATUS_SKIP_STREAM;
-       }
-
-       DEBUG("Temporary file needed for stream (size=%"PRIu64")", lte->size);
-       return create_temporary_file(&ctx->tmpfile_fd, &ctx->tmpfile_name);
-}
-
-static int
-extract_chunk(const void *chunk, size_t size, void *_ctx)
-{
-       struct apply_ctx *ctx = _ctx;
-       int ret;
-
-       if (filedes_valid(&ctx->tmpfile_fd)) {
-               ret = full_write(&ctx->tmpfile_fd, chunk, size);
-               if (ret)
-                       ERROR_WITH_ERRNO("Error writing to file descriptor");
-       } else {
-               struct wim_lookup_table_entry lte_override;
-
-               memcpy(&lte_override, ctx->cur_stream,
-                      sizeof(struct wim_lookup_table_entry));
-
-               lte_override.resource_location = RESOURCE_IN_ATTACHED_BUFFER;
-               lte_override.size = size;
-               lte_override.attached_buffer = (void *)chunk;
-
-               ret = extract_stream_instances(ctx->cur_stream, &lte_override, ctx);
-       }
-       return ret;
-}
-
-static int
-end_extract_stream(struct wim_lookup_table_entry *lte,
-                  int status, void *_ctx)
-{
-       struct apply_ctx *ctx = _ctx;
-       struct wim_lookup_table_entry lte_override;
-       int ret;
-       int errno_save = errno;
-
-       if (!filedes_valid(&ctx->tmpfile_fd))
-               return status;
-
-       ret = filedes_close(&ctx->tmpfile_fd);
-
-       if (status) {
-               ret = status;
-               errno = errno_save;
-               goto out_delete_tmpfile;
-       }
-
-       if (ret) {
-               ERROR_WITH_ERRNO("Error writing temporary file %"TS, ctx->tmpfile_name);
-               ret = WIMLIB_ERR_WRITE;
-               goto out_delete_tmpfile;
-       }
-
-       /* Now that a full stream has been extracted to a temporary file,
-        * extract all instances of it to the actual target.  */
-
-       memcpy(&lte_override, lte, sizeof(struct wim_lookup_table_entry));
-       lte_override.resource_location = RESOURCE_IN_FILE_ON_DISK;
-       lte_override.file_on_disk = ctx->tmpfile_name;
-
-       ret = extract_stream_instances(lte, &lte_override, ctx);
-
-out_delete_tmpfile:
-       errno_save = errno;
-       tunlink(ctx->tmpfile_name);
-       FREE(ctx->tmpfile_name);
-       errno = errno_save;
-       return ret;
-}
-
-/* Extracts a list of streams (ctx.stream_list), assuming that the directory
- * structure and empty files were already created.  This relies on the
- * per-`struct wim_lookup_table_entry' list of dentries that reference each
- * stream that was constructed earlier.  */
-static int
-extract_stream_list(struct apply_ctx *ctx)
-{
-       struct read_stream_list_callbacks cbs = {
-               .begin_stream           = begin_extract_stream,
-               .begin_stream_ctx       = ctx,
-               .consume_chunk          = extract_chunk,
-               .consume_chunk_ctx      = ctx,
-               .end_stream             = end_extract_stream,
-               .end_stream_ctx         = ctx,
-       };
-       return read_stream_list(&ctx->stream_list,
-                               offsetof(struct wim_lookup_table_entry, extraction_list),
-                               &cbs, VERIFY_STREAM_HASHES);
-}
 
 #define PWM_ALLOW_WIM_HDR 0x00001
 #define PWM_SILENT_EOF   0x00002
@@ -1448,58 +168,47 @@ read_error:
 }
 
 static int
-extract_streams_from_pipe(struct apply_ctx *ctx)
+load_streams_from_pipe(struct apply_ctx *ctx,
+                      const struct read_stream_list_callbacks *cbs)
 {
-       struct wim_lookup_table_entry *found_lte;
-       struct wim_resource_spec *rspec;
-       struct wim_lookup_table_entry *needed_lte;
+       struct wim_lookup_table_entry *found_lte = NULL;
+       struct wim_resource_spec *rspec = NULL;
        struct wim_lookup_table *lookup_table;
-       struct wim_header_disk pwm_hdr;
        int ret;
-       int pwm_flags;
 
        ret = WIMLIB_ERR_NOMEM;
        found_lte = new_lookup_table_entry();
-       if (found_lte == NULL)
+       if (!found_lte)
                goto out;
 
        rspec = MALLOC(sizeof(struct wim_resource_spec));
-       if (rspec == NULL)
-               goto out_free_found_lte;
+       if (!rspec)
+               goto out;
 
        lookup_table = ctx->wim->lookup_table;
-       pwm_flags = PWM_ALLOW_WIM_HDR;
-       if ((ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RESUME))
-               pwm_flags |= PWM_SILENT_EOF;
        memcpy(ctx->progress.extract.guid, ctx->wim->hdr.guid, WIM_GUID_LEN);
        ctx->progress.extract.part_number = ctx->wim->hdr.part_number;
        ctx->progress.extract.total_parts = ctx->wim->hdr.total_parts;
-       if (ctx->progress_func)
+       if (ctx->progress_func) {
                ctx->progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN,
                                   &ctx->progress);
+       }
        while (ctx->num_streams_remaining) {
+               struct wim_header_disk pwm_hdr;
+               struct wim_lookup_table_entry *needed_lte;
+
                if (found_lte->resource_location != RESOURCE_NONEXISTENT)
                        lte_unbind_wim_resource_spec(found_lte);
                ret = read_pwm_stream_header(ctx->wim, found_lte, rspec,
-                                            pwm_flags, &pwm_hdr);
-               if (ret) {
-                       if (ret == WIMLIB_ERR_UNEXPECTED_END_OF_FILE &&
-                           (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RESUME))
-                       {
-                               goto resume_done;
-                       }
-                       goto out_free_found_lte;
-               }
+                                            PWM_ALLOW_WIM_HDR, &pwm_hdr);
+               if (ret)
+                       goto out;
 
                if ((found_lte->resource_location != RESOURCE_NONEXISTENT)
                    && !(found_lte->flags & WIM_RESHDR_FLAG_METADATA)
                    && (needed_lte = lookup_stream(lookup_table, found_lte->hash))
                    && (needed_lte->out_refcnt))
                {
-                       tchar *tmpfile_name = NULL;
-                       struct wim_lookup_table_entry *lte_override;
-                       struct wim_lookup_table_entry tmpfile_lte;
-
                        needed_lte->offset_in_res = found_lte->offset_in_res;
                        needed_lte->flags = found_lte->flags;
                        needed_lte->size = found_lte->size;
@@ -1507,53 +216,27 @@ extract_streams_from_pipe(struct apply_ctx *ctx)
                        lte_unbind_wim_resource_spec(found_lte);
                        lte_bind_wim_resource_spec(needed_lte, rspec);
 
-                       if (needed_lte->out_refcnt > 1) {
-
-                               struct filedes tmpfile_fd;
-
-                               /* Extract stream to temporary file.  */
-                               ret = create_temporary_file(&tmpfile_fd, &tmpfile_name);
-                               if (ret) {
-                                       lte_unbind_wim_resource_spec(needed_lte);
-                                       goto out_free_found_lte;
-                               }
-
-                               ret = extract_full_stream_to_fd(needed_lte,
-                                                               &tmpfile_fd);
-                               if (ret) {
-                                       filedes_close(&tmpfile_fd);
-                                       goto delete_tmpfile;
-                               }
-
-                               if (filedes_close(&tmpfile_fd)) {
-                                       ERROR_WITH_ERRNO("Error writing to temporary "
-                                                        "file \"%"TS"\"", tmpfile_name);
-                                       ret = WIMLIB_ERR_WRITE;
-                                       goto delete_tmpfile;
-                               }
-                               memcpy(&tmpfile_lte, needed_lte,
-                                      sizeof(struct wim_lookup_table_entry));
-                               tmpfile_lte.resource_location = RESOURCE_IN_FILE_ON_DISK;
-                               tmpfile_lte.file_on_disk = tmpfile_name;
-                               lte_override = &tmpfile_lte;
-                       } else {
-                               lte_override = needed_lte;
+                       ret = (*cbs->begin_stream)(needed_lte, 0,
+                                                  cbs->begin_stream_ctx);
+                       if (ret) {
+                               lte_unbind_wim_resource_spec(needed_lte);
+                               goto out;
                        }
 
-                       ret = extract_stream_instances(needed_lte, lte_override, ctx);
-               delete_tmpfile:
+                       ret = extract_stream(needed_lte, needed_lte->size,
+                                            cbs->consume_chunk,
+                                            cbs->consume_chunk_ctx);
+
+                       ret = (*cbs->end_stream)(needed_lte, ret,
+                                                cbs->end_stream_ctx);
                        lte_unbind_wim_resource_spec(needed_lte);
-                       if (tmpfile_name) {
-                               tunlink(tmpfile_name);
-                               FREE(tmpfile_name);
-                       }
                        if (ret)
-                               goto out_free_found_lte;
+                               goto out;
                        ctx->num_streams_remaining--;
                } else if (found_lte->resource_location != RESOURCE_NONEXISTENT) {
                        ret = skip_wim_stream(found_lte);
                        if (ret)
-                               goto out_free_found_lte;
+                               goto out;
                } else {
                        u16 part_number = le16_to_cpu(pwm_hdr.part_number);
                        u16 total_parts = le16_to_cpu(pwm_hdr.total_parts);
@@ -1572,21 +255,89 @@ extract_streams_from_pipe(struct apply_ctx *ctx)
                                                WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN,
                                                           &ctx->progress);
                                }
-
                        }
                }
        }
        ret = 0;
-out_free_found_lte:
+out:
        if (found_lte->resource_location != RESOURCE_IN_WIM)
                FREE(rspec);
        free_lookup_table_entry(found_lte);
-out:
        return ret;
+}
 
-resume_done:
-       /* TODO */
-       return 0;
+static int
+begin_extract_stream_with_progress(struct wim_lookup_table_entry *lte,
+                                  u32 flags, void *_ctx)
+{
+       struct apply_ctx *ctx = _ctx;
+
+       ctx->cur_stream = lte;
+
+       return (*ctx->saved_cbs->begin_stream)(lte, flags,
+                                              ctx->saved_cbs->begin_stream_ctx);
+}
+
+static int
+consume_chunk_with_progress(const void *chunk, size_t size, void *_ctx)
+{
+       struct apply_ctx *ctx = _ctx;
+       wimlib_progress_func_t progress_func = ctx->progress_func;
+       union wimlib_progress_info *progress = &ctx->progress;
+       u32 num_copies = ctx->cur_stream->out_refcnt;
+
+       progress->extract.completed_bytes += size * num_copies;
+       if (progress->extract.completed_bytes >= ctx->next_progress) {
+               progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS, progress);
+               if (progress->extract.completed_bytes >=
+                   progress->extract.total_bytes)
+               {
+                       ctx->next_progress = UINT64_MAX;
+               } else {
+                       ctx->next_progress += progress->extract.total_bytes / 128;
+                       if (ctx->next_progress > progress->extract.total_bytes)
+                               ctx->next_progress = progress->extract.total_bytes;
+               }
+       }
+       return (*ctx->saved_cbs->consume_chunk)(chunk, size,
+                                               ctx->saved_cbs->consume_chunk_ctx);
+}
+
+/*
+ * Read the list of single-instance streams to extract and feed their data into
+ * the specified callback functions.
+ *
+ * This handles checksumming each stream.
+ *
+ * This also handles sending WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS.
+ *
+ * This also works if the WIM is being read from a pipe, whereas attempting to
+ * read streams directly (e.g. with read_full_stream_into_buf()) will not.
+ */
+int
+extract_stream_list(struct apply_ctx *ctx,
+                   const struct read_stream_list_callbacks *cbs)
+{
+       struct read_stream_list_callbacks wrapper_cbs = {
+               .begin_stream      = begin_extract_stream_with_progress,
+               .begin_stream_ctx  = ctx,
+               .consume_chunk     = consume_chunk_with_progress,
+               .consume_chunk_ctx = ctx,
+               .end_stream        = cbs->end_stream,
+               .end_stream_ctx    = cbs->end_stream_ctx,
+       };
+       if (ctx->progress_func) {
+               ctx->saved_cbs = cbs;
+               cbs = &wrapper_cbs;
+       }
+       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) {
+               return load_streams_from_pipe(ctx, cbs);
+       } else {
+               return read_stream_list(&ctx->stream_list,
+                                       offsetof(struct wim_lookup_table_entry,
+                                                extraction_list),
+                                       cbs, VERIFY_STREAM_HASHES);
+       }
 }
 
 /* Extract a WIM dentry to standard output.
@@ -1691,20 +442,20 @@ static int
 dentry_append_to_list(struct wim_dentry *dentry, void *_dentry_list)
 {
        struct list_head *dentry_list = _dentry_list;
-       list_add_tail(&dentry->extraction_list, dentry_list);
+       list_add_tail(&dentry->d_extraction_list_node, dentry_list);
        return 0;
 }
 
 static void
 dentry_reset_extraction_list_node(struct wim_dentry *dentry)
 {
-       dentry->extraction_list = (struct list_head){NULL, NULL};
+       dentry->d_extraction_list_node = (struct list_head){NULL, NULL};
 }
 
 static int
 dentry_delete_from_list(struct wim_dentry *dentry, void *_ignore)
 {
-       list_del(&dentry->extraction_list);
+       list_del(&dentry->d_extraction_list_node);
        dentry_reset_extraction_list_node(dentry);
        return 0;
 }
@@ -1739,8 +490,8 @@ build_dentry_list(struct list_head *dentry_list, struct wim_dentry **trees,
                        ancestor = dentry;
                        do {
                                ancestor = ancestor->parent;
-                               if (dentry_in_list(ancestor)) {
-                                       place_after = &ancestor->extraction_list;
+                               if (will_extract_dentry(ancestor)) {
+                                       place_after = &ancestor->d_extraction_list_node;
                                        break;
                                }
                        } while (!dentry_is_root(ancestor));
@@ -1748,26 +499,39 @@ build_dentry_list(struct list_head *dentry_list, struct wim_dentry **trees,
                        ancestor = dentry;
                        do {
                                ancestor = ancestor->parent;
-                               if (dentry_in_list(ancestor))
+                               if (will_extract_dentry(ancestor))
                                        break;
-                               list_add(&ancestor->extraction_list, place_after);
+                               list_add(&ancestor->d_extraction_list_node, place_after);
                        } while (!dentry_is_root(ancestor));
                }
        }
 }
 
-static const struct apply_operations *
-select_apply_operations(int extract_flags)
+static void
+destroy_dentry_list(struct list_head *dentry_list)
 {
-#ifdef WITH_NTFS_3G
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS)
-               return &ntfs_3g_apply_ops;
-#endif
-#ifdef __WIN32__
-       return &win32_apply_ops;
-#else
-       return &unix_apply_ops;
-#endif
+       struct wim_dentry *dentry, *tmp;
+       struct wim_inode *inode;
+
+       list_for_each_entry_safe(dentry, tmp, dentry_list, d_extraction_list_node) {
+               inode = dentry->d_inode;
+               dentry_reset_extraction_list_node(dentry);
+               inode->i_visited = 0;
+               if ((void *)dentry->d_extraction_name != (void *)dentry->file_name)
+                       FREE(dentry->d_extraction_name);
+               dentry->d_extraction_name = NULL;
+               dentry->d_extraction_name_nchars = 0;
+       }
+}
+
+static void
+destroy_stream_list(struct list_head *stream_list)
+{
+       struct wim_lookup_table_entry *lte;
+
+       list_for_each_entry(lte, stream_list, extraction_list)
+               if (lte->out_refcnt > ARRAY_LEN(lte->inline_stream_owners))
+                       FREE(lte->stream_owners);
 }
 
 #ifdef __WIN32__
@@ -1823,19 +587,25 @@ dentry_calculate_extraction_name(struct wim_dentry *dentry,
 {
        int ret;
 
-       if (dentry == ctx->target_dentry)
-               return 0;
-
        if (!dentry_is_supported(dentry, &ctx->supported_features))
                goto skip_dentry;
 
-       if (!ctx->ops->supports_case_sensitive_filenames)
-       {
+       if (dentry_is_root(dentry))
+               return 0;
+
+       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
+               dentry->d_extraction_name = dentry->file_name;
+               dentry->d_extraction_name_nchars = dentry->file_name_nbytes /
+                                                  sizeof(utf16lechar);
+               return 0;
+       }
+
+       if (!ctx->supported_features.case_sensitive_filenames) {
                struct wim_dentry *other;
                list_for_each_entry(other, &dentry->d_ci_conflict_list,
                                    d_ci_conflict_list)
                {
-                       if (dentry_in_list(other)) {
+                       if (will_extract_dentry(other)) {
                                if (ctx->extract_flags &
                                    WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS) {
                                        WARNING("\"%"TS"\" has the same "
@@ -1860,9 +630,9 @@ dentry_calculate_extraction_name(struct wim_dentry *dentry,
        if (file_name_valid(dentry->file_name, dentry->file_name_nbytes / 2, false)) {
                ret = utf16le_get_tstr(dentry->file_name,
                                       dentry->file_name_nbytes,
-                                      (const tchar **)&dentry->extraction_name,
-                                      &dentry->extraction_name_nchars);
-               dentry->extraction_name_nchars /= sizeof(tchar);
+                                      (const tchar **)&dentry->d_extraction_name,
+                                      &dentry->d_extraction_name_nchars);
+               dentry->d_extraction_name_nchars /= sizeof(tchar);
                return ret;
        } else {
                if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES)
@@ -1908,11 +678,11 @@ out_replace:
 
                utf16le_put_tstr(tchar_name);
 
-               dentry->extraction_name = memdup(fixed_name,
-                                                2 * fixed_name_num_chars + 2);
-               if (!dentry->extraction_name)
+               dentry->d_extraction_name = memdup(fixed_name,
+                                                  2 * fixed_name_num_chars + 2);
+               if (!dentry->d_extraction_name)
                        return WIMLIB_ERR_NOMEM;
-               dentry->extraction_name_nchars = fixed_name_num_chars;
+               dentry->d_extraction_name_nchars = fixed_name_num_chars;
        }
        return 0;
 
@@ -1926,9 +696,7 @@ skip_dentry:
  * extracted, with special handling for dentries that are unsupported by the
  * extraction backend or have invalid names.
  *
- * Note: this has a dependency on start_extract() being called because
- * ctx.supported_features must be filled in in order to determine whether each
- * dentry is supported.
+ * ctx->supported_features must be filled in.
  *
  * Possible error codes: WIMLIB_ERR_NOMEM, WIMLIB_ERR_INVALID_UTF16_STRING
  */
@@ -1951,7 +719,7 @@ dentry_list_calculate_extraction_names(struct list_head *dentry_list,
                if (cur == dentry_list)
                        break;
 
-               dentry = list_entry(cur, struct wim_dentry, extraction_list);
+               dentry = list_entry(cur, struct wim_dentry, d_extraction_list_node);
 
                ret = dentry_calculate_extraction_name(dentry, ctx);
                if (ret)
@@ -2006,7 +774,7 @@ dentry_list_resolve_streams(struct list_head *dentry_list,
        struct wim_dentry *dentry;
        int ret;
 
-       list_for_each_entry(dentry, dentry_list, extraction_list) {
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
                ret = dentry_resolve_streams(dentry,
                                             ctx->extract_flags,
                                             ctx->wim->lookup_table);
@@ -2017,68 +785,71 @@ dentry_list_resolve_streams(struct list_head *dentry_list,
 }
 
 static int
-ref_stream(struct wim_lookup_table_entry *lte,
+ref_stream(struct wim_lookup_table_entry *lte, u32 stream_idx,
           struct wim_dentry *dentry, struct apply_ctx *ctx)
 {
+       struct wim_inode *inode = dentry->d_inode;
+       struct stream_owner *stream_owners;
+
        if (!lte)
                return 0;
 
        /* Tally the size only for each extraction of the stream (not hard
         * links).  */
-       if (!(dentry->d_inode->i_visited &&
-             ctx->supported_features.hard_links) &&
-           (!is_linked_extraction(ctx) || (lte->out_refcnt == 0 &&
-                                           lte->extracted_file == NULL)))
-       {
-               ctx->progress.extract.total_bytes += lte->size;
-               ctx->progress.extract.num_streams++;
-       }
+       if (inode->i_visited && ctx->supported_features.hard_links)
+               return 0;
+
+       ctx->progress.extract.total_bytes += lte->size;
+       ctx->progress.extract.num_streams++;
+
+       if (inode->i_visited)
+               return 0;
 
        /* Add stream to the dentry_list only one time, even if it's going
-        * to be extracted to multiple locations.  */
+        * to be extracted to multiple inodes.  */
        if (lte->out_refcnt == 0) {
                list_add_tail(&lte->extraction_list, &ctx->stream_list);
                ctx->num_streams_remaining++;
        }
 
-       if (!(ctx->extract_flags & WIMLIB_EXTRACT_FLAG_FILE_ORDER)) {
-               struct wim_dentry **lte_dentries;
+       /* If inode not yet been visited, append it to the stream_owners array.  */
+       if (lte->out_refcnt < ARRAY_LEN(lte->inline_stream_owners)) {
+               stream_owners = lte->inline_stream_owners;
+       } else {
+               struct stream_owner *prev_stream_owners;
+               size_t alloc_stream_owners;
 
-               /* Append dentry to this stream's array of dentries referencing
-                * it.  Use inline array to avoid memory allocation until the
-                * number of dentries becomes too large.  */
-               if (lte->out_refcnt < ARRAY_LEN(lte->inline_lte_dentries)) {
-                       lte_dentries = lte->inline_lte_dentries;
+               if (lte->out_refcnt == ARRAY_LEN(lte->inline_stream_owners)) {
+                       prev_stream_owners = NULL;
+                       alloc_stream_owners = ARRAY_LEN(lte->inline_stream_owners);
                } else {
-                       struct wim_dentry **prev_lte_dentries;
-                       size_t alloc_lte_dentries;
-
-                       if (lte->out_refcnt == ARRAY_LEN(lte->inline_lte_dentries)) {
-                               prev_lte_dentries = NULL;
-                               alloc_lte_dentries = ARRAY_LEN(lte->inline_lte_dentries);
-                       } else {
-                               prev_lte_dentries = lte->lte_dentries;
-                               alloc_lte_dentries = lte->alloc_lte_dentries;
-                       }
+                       prev_stream_owners = lte->stream_owners;
+                       alloc_stream_owners = lte->alloc_stream_owners;
+               }
 
-                       if (lte->out_refcnt == alloc_lte_dentries) {
-                               alloc_lte_dentries *= 2;
-                               lte_dentries = REALLOC(prev_lte_dentries,
-                                                      alloc_lte_dentries *
-                                                       sizeof(lte_dentries[0]));
-                               if (lte_dentries == NULL)
-                                       return WIMLIB_ERR_NOMEM;
-                               if (prev_lte_dentries == NULL) {
-                                       memcpy(lte_dentries,
-                                              lte->inline_lte_dentries,
-                                              sizeof(lte->inline_lte_dentries));
-                               }
-                               lte->lte_dentries = lte_dentries;
-                               lte->alloc_lte_dentries = alloc_lte_dentries;
+               if (lte->out_refcnt == alloc_stream_owners) {
+                       alloc_stream_owners *= 2;
+                       stream_owners = REALLOC(prev_stream_owners,
+                                              alloc_stream_owners *
+                                               sizeof(stream_owners[0]));
+                       if (!stream_owners)
+                               return WIMLIB_ERR_NOMEM;
+                       if (!prev_stream_owners) {
+                               memcpy(stream_owners,
+                                      lte->inline_stream_owners,
+                                      sizeof(lte->inline_stream_owners));
                        }
-                       lte_dentries = lte->lte_dentries;
+                       lte->stream_owners = stream_owners;
+                       lte->alloc_stream_owners = alloc_stream_owners;
                }
-               lte_dentries[lte->out_refcnt] = dentry;
+               stream_owners = lte->stream_owners;
+       }
+       stream_owners[lte->out_refcnt].inode = inode;
+       if (stream_idx == 0) {
+               stream_owners[lte->out_refcnt].stream_name = NULL;
+       } else {
+               stream_owners[lte->out_refcnt].stream_name =
+                       inode->i_ads_entries[stream_idx - 1].stream_name;
        }
        lte->out_refcnt++;
        return 0;
@@ -2093,8 +864,11 @@ dentry_ref_streams(struct wim_dentry *dentry, struct apply_ctx *ctx)
        /* The unnamed data stream will always be extracted, except in an
         * unlikely case.  */
        if (!inode_is_encrypted_directory(inode)) {
-               ret = ref_stream(inode_unnamed_lte_resolved(inode),
-                                dentry, ctx);
+               u16 stream_idx;
+               struct wim_lookup_table_entry *stream;
+
+               stream = inode_unnamed_stream_resolved(inode, &stream_idx);
+               ret = ref_stream(stream, stream_idx, dentry, ctx);
                if (ret)
                        return ret;
        }
@@ -2102,11 +876,11 @@ dentry_ref_streams(struct wim_dentry *dentry, struct apply_ctx *ctx)
        /* Named data streams will be extracted only if supported in the current
         * extraction mode and volume, and to avoid complications, if not doing
         * a linked extraction.  */
-       if (can_extract_named_data_streams(ctx)) {
+       if (ctx->supported_features.named_data_streams) {
                for (u16 i = 0; i < inode->i_num_ads; i++) {
                        if (!ads_entry_is_named_stream(&inode->i_ads_entries[i]))
                                continue;
-                       ret = ref_stream(inode->i_ads_entries[i].lte,
+                       ret = ref_stream(inode->i_ads_entries[i].lte, i + 1,
                                         dentry, ctx);
                        if (ret)
                                return ret;
@@ -2122,18 +896,12 @@ dentry_ref_streams(struct wim_dentry *dentry, struct apply_ctx *ctx)
  * the supported features or extraction flags, add it to the list of streams to
  * be extracted (ctx->stream_list) if not already done so.
  *
- * Also, if doing a sequential extraction, build a mapping from each stream to
- * the dentries referencing it.
+ * Also builds a mapping from each stream to the inodes referencing it.
  *
  * This also initializes the extract progress info with byte and stream
  * information.
  *
- * Note: This has a dependency on start_extract being called because
- * ctx.supported_features must be filled in in order to determine whether named
- * data streams are supported.
- *
- * Note: this uses the i_visited member of the inodes (assumed to be 0
- * initially), but does not reset it.
+ * ctx->supported_features must be filled in.
  *
  * Possible error codes: WIMLIB_ERR_NOMEM.
  */
@@ -2143,20 +911,38 @@ dentry_list_ref_streams(struct list_head *dentry_list, struct apply_ctx *ctx)
        struct wim_dentry *dentry;
        int ret;
 
-       list_for_each_entry(dentry, dentry_list, extraction_list) {
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
                ret = dentry_ref_streams(dentry, ctx);
                if (ret)
                        return ret;
        }
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node)
+               dentry->d_inode->i_visited = 0;
        return 0;
 }
 
-/* Tally features necessary to extract a dentry and the corresponding inode.  */
 static void
-dentry_tally_features(struct wim_dentry *dentry, struct wim_features *features)
+dentry_list_build_inode_alias_lists(struct list_head *dentry_list)
 {
-       struct wim_inode *inode = dentry->d_inode;
+       struct wim_dentry *dentry;
+       struct wim_inode *inode;
+
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               inode = dentry->d_inode;
+               if (!inode->i_visited)
+                       INIT_LIST_HEAD(&inode->i_extraction_aliases);
+               list_add_tail(&dentry->d_extraction_alias_node,
+                             &inode->i_extraction_aliases);
+               inode->i_visited = 1;
+       }
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node)
+               dentry->d_inode->i_visited = 0;
+}
 
+static void
+inode_tally_features(const struct wim_inode *inode,
+                    struct wim_features *features)
+{
        if (inode->i_attributes & FILE_ATTRIBUTE_ARCHIVE)
                features->archive_files++;
        if (inode->i_attributes & FILE_ATTRIBUTE_HIDDEN)
@@ -2177,8 +963,6 @@ dentry_tally_features(struct wim_dentry *dentry, struct wim_features *features)
                features->sparse_files++;
        if (inode_has_named_stream(inode))
                features->named_data_streams++;
-       if (inode->i_visited)
-               features->hard_links++;
        if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
                features->reparse_points++;
                if (inode_is_symlink(inode))
@@ -2188,11 +972,25 @@ dentry_tally_features(struct wim_dentry *dentry, struct wim_features *features)
        }
        if (inode->i_security_id != -1)
                features->security_descriptors++;
-       if (dentry->short_name_nbytes)
-               features->short_names++;
        if (inode_has_unix_data(inode))
                features->unix_data++;
-       inode->i_visited = 1;
+}
+
+/* Tally features necessary to extract a dentry and the corresponding inode.  */
+static void
+dentry_tally_features(struct wim_dentry *dentry, struct wim_features *features)
+{
+       struct wim_inode *inode = dentry->d_inode;
+
+       if (dentry_has_short_name(dentry))
+               features->short_names++;
+
+       if (inode->i_visited) {
+               features->hard_links++;
+       } else {
+               inode_tally_features(inode, features);
+               inode->i_visited = 1;
+       }
 }
 
 /* Tally the features necessary to extract the specified dentries.  */
@@ -2202,48 +1000,17 @@ dentry_list_get_features(struct list_head *dentry_list,
 {
        struct wim_dentry *dentry;
 
-       memset(features, 0, sizeof(struct wim_features));
-
-       list_for_each_entry(dentry, dentry_list, extraction_list)
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node)
                dentry_tally_features(dentry, features);
 
-       list_for_each_entry(dentry, dentry_list, extraction_list)
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node)
                dentry->d_inode->i_visited = 0;
 }
 
-static u32
-compute_supported_attributes_mask(const struct wim_features *supported_features)
-{
-       u32 mask = (u32)~0UL;
-
-       if (!supported_features->archive_files)
-               mask &= ~FILE_ATTRIBUTE_ARCHIVE;
-
-       if (!supported_features->hidden_files)
-               mask &= ~FILE_ATTRIBUTE_HIDDEN;
-
-       if (!supported_features->system_files)
-               mask &= ~FILE_ATTRIBUTE_SYSTEM;
-
-       if (!supported_features->not_context_indexed_files)
-               mask &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
-
-       if (!supported_features->compressed_files)
-               mask &= ~FILE_ATTRIBUTE_COMPRESSED;
-
-       if (!supported_features->sparse_files)
-               mask &= ~FILE_ATTRIBUTE_SPARSE_FILE;
-
-       if (!supported_features->reparse_points)
-               mask &= ~FILE_ATTRIBUTE_REPARSE_POINT;
-
-       return mask;
-}
-
 static int
 do_feature_check(const struct wim_features *required_features,
                 const struct wim_features *supported_features,
-                int extract_flags, const struct apply_operations *ops)
+                int extract_flags)
 {
        /* File attributes.  */
        if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) {
@@ -2290,30 +1057,16 @@ do_feature_check(const struct wim_features *required_features,
 
        /* Named data streams.  */
        if (required_features->named_data_streams &&
-           (!supported_features->named_data_streams ||
-            (extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
-                              WIMLIB_EXTRACT_FLAG_HARDLINK))))
+           (!supported_features->named_data_streams))
                WARNING("Ignoring named data streams of %lu files",
                        required_features->named_data_streams);
 
        /* Hard links.  */
-       if ((extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) &&
-           !supported_features->hard_links)
-       {
-               ERROR("Extraction backend does not support hard links!");
-               return WIMLIB_ERR_UNSUPPORTED;
-       }
        if (required_features->hard_links && !supported_features->hard_links)
                WARNING("Extracting %lu hard links as independent files",
                        required_features->hard_links);
 
        /* Symbolic links and reparse points.  */
-       if ((extract_flags & WIMLIB_EXTRACT_FLAG_SYMLINK) &&
-           !supported_features->symlink_reparse_points)
-       {
-               ERROR("Extraction backend does not support symbolic links!");
-               return WIMLIB_ERR_UNSUPPORTED;
-       }
        if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS) &&
            required_features->symlink_reparse_points &&
            !supported_features->symlink_reparse_points &&
@@ -2375,7 +1128,7 @@ do_feature_check(const struct wim_features *required_features,
 
        /* Timestamps.  */
        if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS) &&
-           !ops->set_timestamps)
+           !supported_features->timestamps)
        {
                ERROR("Extraction backend does not support timestamps!");
                return WIMLIB_ERR_UNSUPPORTED;
@@ -2384,156 +1137,99 @@ do_feature_check(const struct wim_features *required_features,
        return 0;
 }
 
-static void
-do_extract_warnings(struct apply_ctx *ctx)
+static const struct apply_operations *
+select_apply_operations(int extract_flags)
 {
-       if (ctx->partial_security_descriptors == 0 &&
-           ctx->no_security_descriptors == 0)
-               return;
-
-       WARNING("Extraction to \"%"TS"\" complete, but with one or more warnings:",
-               ctx->target);
-       if (ctx->partial_security_descriptors != 0) {
-               WARNING("- Could only partially set the security descriptor\n"
-                       "            on %lu files or directories.",
-                       ctx->partial_security_descriptors);
-       }
-       if (ctx->no_security_descriptors != 0) {
-               WARNING("- Could not set security descriptor at all\n"
-                       "            on %lu files or directories.",
-                       ctx->no_security_descriptors);
-       }
+#ifdef WITH_NTFS_3G
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS)
+               return &ntfs_3g_apply_ops;
+#endif
 #ifdef __WIN32__
-       WARNING("To fully restore all security descriptors, run the program\n"
-               "          with Administrator rights.");
+       return &win32_apply_ops;
+#else
+       return &unix_apply_ops;
 #endif
 }
 
-static void
-destroy_dentry_list(struct list_head *dentry_list)
-{
-       struct wim_dentry *dentry, *tmp;
-       struct wim_inode *inode;
-
-       list_for_each_entry_safe(dentry, tmp, dentry_list, extraction_list) {
-               inode = dentry->d_inode;
-               dentry_reset_extraction_list_node(dentry);
-               dentry->was_linked = 0;
-               dentry->skeleton_extracted = 0;
-               inode->i_visited = 0;
-               FREE(inode->i_extracted_file);
-               inode->i_extracted_file = NULL;
-               inode->i_dos_name_extracted = 0;
-               if ((void*)dentry->extraction_name != (void*)dentry->file_name)
-                       FREE(dentry->extraction_name);
-               dentry->extraction_name = NULL;
-       }
-}
-
-static void
-destroy_stream_list(struct list_head *stream_list)
-{
-       struct wim_lookup_table_entry *lte;
-
-       list_for_each_entry(lte, stream_list, extraction_list)
-               if (lte->out_refcnt > ARRAY_LEN(lte->inline_lte_dentries))
-                       FREE(lte->lte_dentries);
-}
-
 static int
 extract_trees(WIMStruct *wim, struct wim_dentry **trees, size_t num_trees,
              const tchar *target, int extract_flags,
              wimlib_progress_func_t progress_func)
 {
+       const struct apply_operations *ops;
+       struct apply_ctx *ctx;
        int ret;
-       struct apply_ctx ctx;
-       struct list_head dentry_list;
-       struct wim_features required_features;
-
-       /* Handle stdout extraction as a separate case.  */
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_TO_STDOUT)
-               return extract_dentries_to_stdout(trees, num_trees,
-                                                 wim->lookup_table);
-
-       /* Start initializing the apply_ctx.  */
-       memset(&ctx, 0, sizeof(struct apply_ctx));
-       ctx.wim = wim;
-       ctx.extract_flags = extract_flags;
-       ctx.target = target;
-       ctx.target_nchars = tstrlen(target);
-       ctx.progress_func = progress_func;
-       if (progress_func) {
-               ctx.progress.extract.wimfile_name = wim->filename;
-               ctx.progress.extract.image = wim->current_image;
-               ctx.progress.extract.extract_flags = (extract_flags &
-                                                     WIMLIB_EXTRACT_MASK_PUBLIC);
-               ctx.progress.extract.image_name = wimlib_get_image_name(wim,
-                                                                       wim->current_image);
-               ctx.progress.extract.target = target;
-       }
+       LIST_HEAD(dentry_list);
 
-       ctx.target_dentry = wim_get_current_root_dentry(wim);
-       /* Note: ctx.target_dentry represents the dentry that gets extracted to
-        * @target.  There may be none, in which case it gets set to the image
-        * root and never matches any of the dentries actually being extracted.
-        */
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_TO_STDOUT) {
+               ret = extract_dentries_to_stdout(trees, num_trees,
+                                                wim->lookup_table);
+               goto out;
+       }
 
        num_trees = remove_duplicate_trees(trees, num_trees);
+       num_trees = remove_contained_trees(trees, num_trees);
 
-       /* All trees are now distinct.  */
+       ops = select_apply_operations(extract_flags);
 
-       num_trees = remove_contained_trees(trees, num_trees);
+       if (num_trees > 1 && ops->single_tree_only) {
+               ERROR("Extracting multiple directory trees "
+                     "at once is not supported in %s extraction mode!",
+                     ops->name);
+               ret = WIMLIB_ERR_UNSUPPORTED;
+               goto out;
+       }
+
+       ctx = CALLOC(1, ops->context_size);
+       if (!ctx) {
+               ret = WIMLIB_ERR_NOMEM;
+               goto out;
+       }
 
-       /* All trees are now distinct and non-overlapping.  */
+       ctx->wim = wim;
+       ctx->target = target;
+       ctx->target_nchars = tstrlen(target);
+       ctx->extract_flags = extract_flags;
+       if (progress_func) {
+               ctx->progress_func = progress_func;
+               ctx->progress.extract.image = wim->current_image;
+               ctx->progress.extract.extract_flags = (extract_flags &
+                                                      WIMLIB_EXTRACT_MASK_PUBLIC);
+               ctx->progress.extract.wimfile_name = wim->filename;
+               ctx->progress.extract.image_name = wimlib_get_image_name(wim,
+                                                                        wim->current_image);
+               ctx->progress.extract.target = target;
+       }
+       INIT_LIST_HEAD(&ctx->stream_list);
+
+       ret = (*ops->get_supported_features)(target, &ctx->supported_features);
+       if (ret)
+               goto out_cleanup;
 
-       /* Build list of dentries to be extracted.  */
        build_dentry_list(&dentry_list, trees, num_trees,
-                         !(extract_flags & WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE));
-
-       /* Select the appropriate apply_operations based on the platform and
-        * extract_flags.  */
-       ctx.ops = select_apply_operations(extract_flags);
-
-       /* Figure out whether the root dentry is being extracted to the root of
-        * a volume and therefore needs to be treated "specially", for example
-        * not being explicitly created and not having attributes set.  */
-       if (ctx.ops->target_is_root && ctx.ops->root_directory_is_special)
-               ctx.root_dentry_is_special = ctx.ops->target_is_root(target);
-
-       /* Call the start_extract() callback.  This gives the apply_operations
-        * implementation a chance to do any setup needed to access the volume.
-        * Furthermore, start_extract() is expected to set the supported
-        * features of this extraction mode (ctx.supported_features), which are
-        * determined at runtime as they may vary depending on the actual
-        * volume.  */
-       ret = ctx.ops->start_extract(target, &ctx);
-       if (ret)
-               goto out_destroy_dentry_list;
+                         !(extract_flags &
+                           WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE));
 
-       /* Get and check the features required to extract the dentries.  */
-       dentry_list_get_features(&dentry_list, &required_features);
-       ret = do_feature_check(&required_features, &ctx.supported_features,
-                              extract_flags, ctx.ops);
-       if (ret)
-               goto out_finish_or_abort_extract;
+       dentry_list_get_features(&dentry_list, &ctx->required_features);
 
-       ctx.supported_attributes_mask =
-               compute_supported_attributes_mask(&ctx.supported_features);
+       ret = do_feature_check(&ctx->required_features, &ctx->supported_features,
+                              ctx->extract_flags);
+       if (ret)
+               goto out_cleanup;
 
-       /* Calculate extraction name for each dentry and remove subtrees that
-        * can't be extracted due to naming problems.  */
-       ret = dentry_list_calculate_extraction_names(&dentry_list, &ctx);
+       ret = dentry_list_calculate_extraction_names(&dentry_list, ctx);
        if (ret)
-               goto out_finish_or_abort_extract;
+               goto out_cleanup;
 
-       /* Build list of streams to extract.  */
-       ret = dentry_list_resolve_streams(&dentry_list, &ctx);
+       ret = dentry_list_resolve_streams(&dentry_list, ctx);
        if (ret)
-               goto out_finish_or_abort_extract;
-       INIT_LIST_HEAD(&ctx.stream_list);
-       ret = dentry_list_ref_streams(&dentry_list, &ctx);
+               goto out_cleanup;
+
+       ret = dentry_list_ref_streams(&dentry_list, ctx);
        if (ret)
-               goto out_destroy_stream_list;
+               goto out_cleanup;
+
+       dentry_list_build_inode_alias_lists(&dentry_list);
 
        if (extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) {
                /* When extracting from a pipe, the number of bytes of data to
@@ -2546,177 +1242,53 @@ extract_trees(WIMStruct *wim, struct wim_dentry **trees, size_t num_trees,
                 * but currently there is no API for doing otherwise.  (Also,
                 * subtract <HARDLINKBYTES> from this if hard links are
                 * supported by the extraction mode.)  */
-               ctx.progress.extract.total_bytes =
+               ctx->progress.extract.total_bytes =
                        wim_info_get_image_total_bytes(wim->wim_info,
                                                       wim->current_image);
-               if (ctx.supported_features.hard_links) {
-                       ctx.progress.extract.total_bytes -=
+               if (ctx->supported_features.hard_links) {
+                       ctx->progress.extract.total_bytes -=
                                wim_info_get_image_hard_link_bytes(wim->wim_info,
                                                                   wim->current_image);
                }
        }
 
-       if (ctx.ops->realpath_works_on_nonexisting_files &&
-           ((extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) ||
-            ctx.ops->requires_realtarget_in_paths))
-       {
-               ctx.realtarget = realpath(target, NULL);
-               if (!ctx.realtarget) {
-                       ret = WIMLIB_ERR_NOMEM;
-                       goto out_destroy_stream_list;
-               }
-               ctx.realtarget_nchars = tstrlen(ctx.realtarget);
-       #ifdef __WIN32__
-              /* Strip trailing slashes.  If we don't do this, we may create a
-               * path with multiple consecutive backslashes, which for some
-               * reason causes Windows to report that the file cannot be found.
-               */
-               while (ctx.realtarget_nchars >= 2
-                      && ctx.realtarget[ctx.realtarget_nchars - 1] == L'\\'
-                      && ctx.realtarget[ctx.realtarget_nchars - 2] != L':')
-               {
-                       ctx.realtarget[--ctx.realtarget_nchars] = L'\0';
-               }
-       #endif
-       }
-
-       if (progress_func) {
+       if (ctx->progress_func) {
                int msg;
                if (extract_flags & WIMLIB_EXTRACT_FLAG_IMAGEMODE)
                        msg = WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN;
                else
                        msg = WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN;
-               progress_func(msg, &ctx.progress);
-       }
-
-       if (!ctx.root_dentry_is_special) {
-               tchar path[ctx.ops->path_max];
-               if (build_extraction_path(path, ctx.target_dentry, &ctx)) {
-                       ret = extract_inode(path, &ctx, ctx.target_dentry->d_inode);
-                       if (ret)
-                               goto out_free_realtarget;
-               }
-       }
-
-       /* If we need to fix up the targets of absolute symbolic links
-        * (WIMLIB_EXTRACT_FLAG_RPFIX) or the extraction mode requires paths to
-        * be absolute, use realpath() (or its replacement on Windows) to get
-        * the absolute path to the extraction target.  Note that this requires
-        * the target directory to exist, unless
-        * realpath_works_on_nonexisting_files is set in the apply_operations.
-        * */
-       if (!ctx.realtarget &&
-           (((extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) &&
-             required_features.symlink_reparse_points) ||
-            ctx.ops->requires_realtarget_in_paths))
-       {
-               ctx.realtarget = realpath(target, NULL);
-               if (!ctx.realtarget) {
-                       ret = WIMLIB_ERR_NOMEM;
-                       goto out_free_realtarget;
-               }
-               ctx.realtarget_nchars = tstrlen(ctx.realtarget);
-       }
-
-       if (ctx.ops->requires_short_name_reordering) {
-               if (progress_func)
-                       progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
-                                     &ctx.progress);
-               ret = extract_dir_structure(&dentry_list, &ctx);
-               if (ret)
-                       goto out_free_realtarget;
+               (*ctx->progress_func)(msg, &ctx->progress);
        }
 
-       /* Finally, the important part: extract the tree of files.  */
-       if (!(extract_flags & WIMLIB_EXTRACT_FLAG_FILE_ORDER)) {
-               /* Sequential extraction requested, so two passes are needed
-                * (one for file structure, one for streams.)  */
-               if (progress_func && !ctx.ops->requires_short_name_reordering)
-                       progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
-                                     &ctx.progress);
-
-               if (!(extract_flags & WIMLIB_EXTRACT_FLAG_RESUME)) {
-                       ret = extract_structure(&dentry_list, &ctx);
-                       if (ret)
-                               goto out_free_realtarget;
-               }
-               if (progress_func)
-                       progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END,
-                                     &ctx.progress);
-               if (extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE)
-                       ret = extract_streams_from_pipe(&ctx);
-               else
-                       ret = extract_stream_list(&ctx);
-               if (ret)
-                       goto out_free_realtarget;
-       } else {
-               /* Sequential extraction was not requested, so we can make do
-                * with one pass where we both create the files and extract
-                * streams.   */
-               if (progress_func && !ctx.ops->requires_short_name_reordering)
-                       progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
-                                     &ctx.progress);
-               ret = extract_dentries(&dentry_list, &ctx);
-               if (ret)
-                       goto out_free_realtarget;
-               if (progress_func)
-                       progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END,
-                                     &ctx.progress);
-       }
+       ret = (*ops->extract)(&dentry_list, ctx);
+       if (ret)
+               goto out_cleanup;
 
-       /* If the total number of bytes to extract was miscalculated, just jump
-        * to the calculated number in order to avoid confusing the progress
-        * function.  This should only occur when extracting from a pipe.  */
-       if (ctx.progress.extract.completed_bytes != ctx.progress.extract.total_bytes)
+       if (ctx->progress_func &&
+           ctx->progress.extract.completed_bytes <
+               ctx->progress.extract.total_bytes)
        {
-               DEBUG("Calculated %"PRIu64" bytes to extract, but actually "
-                     "extracted %"PRIu64,
-                     ctx.progress.extract.total_bytes,
-                     ctx.progress.extract.completed_bytes);
+               ctx->progress.extract.completed_bytes =
+                       ctx->progress.extract.total_bytes;
+               (*ctx->progress_func)(WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS,
+                                     &ctx->progress);
        }
-       if (progress_func &&
-           ctx.progress.extract.completed_bytes < ctx.progress.extract.total_bytes)
-       {
-               ctx.progress.extract.completed_bytes = ctx.progress.extract.total_bytes;
-               progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS, &ctx.progress);
-       }
-
-       /* Apply security descriptors and timestamps.  This is done at the end,
-        * and in a depth-first manner, to prevent timestamps from getting
-        * changed by subsequent extract operations and to minimize the chance
-        * of the restored security descriptors getting in our way.  */
-       if (progress_func)
-               progress_func(WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS,
-                             &ctx.progress);
-       ret = extract_final_metadata(&dentry_list, &ctx);
-       if (ret)
-               goto out_free_realtarget;
 
-       if (progress_func) {
+       if (ctx->progress_func) {
                int msg;
                if (extract_flags & WIMLIB_EXTRACT_FLAG_IMAGEMODE)
                        msg = WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END;
                else
                        msg = WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END;
-               progress_func(msg, &ctx.progress);
+               (*ctx->progress_func)(msg, &ctx->progress);
        }
-       do_extract_warnings(&ctx);
        ret = 0;
-out_free_realtarget:
-       FREE(ctx.realtarget);
-out_destroy_stream_list:
-       if (!(ctx.extract_flags & WIMLIB_EXTRACT_FLAG_FILE_ORDER))
-               destroy_stream_list(&ctx.stream_list);
-out_finish_or_abort_extract:
-       if (ret) {
-               if (ctx.ops->abort_extract)
-                       ctx.ops->abort_extract(&ctx);
-       } else {
-               if (ctx.ops->finish_extract)
-                       ret = ctx.ops->finish_extract(&ctx);
-       }
-out_destroy_dentry_list:
+out_cleanup:
+       destroy_stream_list(&ctx->stream_list);
        destroy_dentry_list(&dentry_list);
+       FREE(ctx);
+out:
        return ret;
 }
 
@@ -2749,11 +1321,6 @@ check_extract_flags(const WIMStruct *wim, int *extract_flags_p)
        int extract_flags = *extract_flags_p;
 
        /* Check for invalid flag combinations  */
-       if ((extract_flags &
-            (WIMLIB_EXTRACT_FLAG_SYMLINK |
-             WIMLIB_EXTRACT_FLAG_HARDLINK)) == (WIMLIB_EXTRACT_FLAG_SYMLINK |
-                                                WIMLIB_EXTRACT_FLAG_HARDLINK))
-               return WIMLIB_ERR_INVALID_PARAM;
 
        if ((extract_flags &
             (WIMLIB_EXTRACT_FLAG_NO_ACLS |
@@ -2767,11 +1334,6 @@ check_extract_flags(const WIMStruct *wim, int *extract_flags_p)
                                                WIMLIB_EXTRACT_FLAG_NORPFIX))
                return WIMLIB_ERR_INVALID_PARAM;
 
-       if ((extract_flags &
-            (WIMLIB_EXTRACT_FLAG_RESUME |
-             WIMLIB_EXTRACT_FLAG_FROM_PIPE)) == WIMLIB_EXTRACT_FLAG_RESUME)
-               return WIMLIB_ERR_INVALID_PARAM;
-
 #ifndef WITH_NTFS_3G
        if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
                ERROR("wimlib was compiled without support for NTFS-3g, so\n"
@@ -2798,26 +1360,6 @@ check_extract_flags(const WIMStruct *wim, int *extract_flags_p)
                        extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
        }
 
-       /* TODO: Since UNIX data entries are stored in the file resources, in a
-        * completely sequential extraction they may come up before the
-        * corresponding file or symbolic link data.  This needs to be handled
-        * better.  */
-       if ((extract_flags & (WIMLIB_EXTRACT_FLAG_UNIX_DATA |
-                             WIMLIB_EXTRACT_FLAG_FILE_ORDER))
-                                   == WIMLIB_EXTRACT_FLAG_UNIX_DATA)
-       {
-               if (extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) {
-                       WARNING("Setting UNIX file/owner group may "
-                               "be impossible on some\n"
-                               "          symbolic links "
-                               "when applying from a pipe.");
-               } else {
-                       extract_flags |= WIMLIB_EXTRACT_FLAG_FILE_ORDER;
-                       WARNING("Disabling sequential extraction for "
-                               "UNIX data mode");
-               }
-       }
-
        *extract_flags_p = extract_flags;
        return 0;
 }
@@ -3043,15 +1585,6 @@ extract_all_images(WIMStruct *wim,
        return 0;
 }
 
-static void
-clear_lte_extracted_file(WIMStruct *wim, int extract_flags)
-{
-       if (unlikely(extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
-                                     WIMLIB_EXTRACT_FLAG_HARDLINK)))
-               for_lookup_table_entry(wim->lookup_table,
-                                      lte_free_extracted_file, NULL);
-}
-
 static int
 do_wimlib_extract_image(WIMStruct *wim,
                        int image,
@@ -3059,22 +1592,17 @@ do_wimlib_extract_image(WIMStruct *wim,
                        int extract_flags,
                        wimlib_progress_func_t progress_func)
 {
-       int ret;
-
        if (extract_flags & (WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE |
                             WIMLIB_EXTRACT_FLAG_TO_STDOUT |
                             WIMLIB_EXTRACT_FLAG_GLOB_PATHS))
                return WIMLIB_ERR_INVALID_PARAM;
 
        if (image == WIMLIB_ALL_IMAGES)
-               ret = extract_all_images(wim, target, extract_flags,
-                                        progress_func);
+               return extract_all_images(wim, target, extract_flags,
+                                         progress_func);
        else
-               ret = extract_single_image(wim, image, target, extract_flags,
-                                          progress_func);
-
-       clear_lte_extracted_file(wim, extract_flags);
-       return ret;
+               return extract_single_image(wim, image, target, extract_flags,
+                                           progress_func);
 }
 
 
@@ -3087,15 +1615,11 @@ wimlib_extract_paths(WIMStruct *wim, int image, const tchar *target,
                     const tchar * const *paths, size_t num_paths,
                     int extract_flags, wimlib_progress_func_t progress_func)
 {
-       int ret;
-
        if (extract_flags & ~WIMLIB_EXTRACT_MASK_PUBLIC)
                return WIMLIB_ERR_INVALID_PARAM;
 
-       ret = do_wimlib_extract_paths(wim, image, target, paths, num_paths,
-                                     extract_flags, progress_func);
-       clear_lte_extracted_file(wim, extract_flags);
-       return ret;
+       return do_wimlib_extract_paths(wim, image, target, paths, num_paths,
+                                      extract_flags, progress_func);
 }
 
 WIMLIBAPI int
@@ -3137,9 +1661,6 @@ wimlib_extract_image_from_pipe(int pipe_fd, const tchar *image_num_or_name,
        if (extract_flags & ~WIMLIB_EXTRACT_MASK_PUBLIC)
                return WIMLIB_ERR_INVALID_PARAM;
 
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_FILE_ORDER)
-               return WIMLIB_ERR_INVALID_PARAM;
-
        /* Read the WIM header from the pipe and get a WIMStruct to represent
         * the pipable WIM.  Caveats:  Unlike getting a WIMStruct with
         * wimlib_open_wim(), getting a WIMStruct in this way will result in
index b5beb9f..1950ab2 100644 (file)
@@ -127,7 +127,6 @@ clone_lookup_table_entry(const struct wim_lookup_table_entry *old)
        if (new == NULL)
                return NULL;
 
-       new->extracted_file = NULL;
        switch (new->resource_location) {
        case RESOURCE_IN_WIM:
                list_add(&new->rspec_node, &new->rspec->stream_list);
@@ -1272,16 +1271,6 @@ lte_zero_out_refcnt(struct wim_lookup_table_entry *lte, void *_ignore)
        return 0;
 }
 
-int
-lte_free_extracted_file(struct wim_lookup_table_entry *lte, void *_ignore)
-{
-       if (lte->extracted_file != NULL) {
-               FREE(lte->extracted_file);
-               lte->extracted_file = NULL;
-       }
-       return 0;
-}
-
 /* Allocate a stream entry for the contents of the buffer, or re-use an existing
  * entry in @lookup_table for the same stream.  */
 struct wim_lookup_table_entry *
index 77dbb23..c61a888 100644 (file)
@@ -4,10 +4,13 @@
  * Apply a WIM image directly to an NTFS volume using libntfs-3g.  Restore as
  * much information as possible, including security data, file attributes, DOS
  * names, and alternate data streams.
+ *
+ * Note: because NTFS-3g offers inode-based interfaces, we actually don't need
+ * to deal with paths at all!  (Other than for error messages.)
  */
 
 /*
- * Copyright (C) 2012, 2013 Eric Biggers
+ * Copyright (C) 2012, 2013, 2014 Eric Biggers
  *
  * This file is part of wimlib, a library for working with WIM files.
  *
 #  include "config.h"
 #endif
 
-#ifdef WITH_NTFS_3G
-
-#include <errno.h>
 #include <locale.h>
-#include <stdlib.h>
 #include <string.h>
-#ifdef HAVE_ALLOCA_H
-#  include <alloca.h>
-#endif
 
 #include <ntfs-3g/attrib.h>
 #include <ntfs-3g/reparse.h>
 #include <ntfs-3g/security.h>
 
+#include "wimlib/assert.h"
 #include "wimlib/apply.h"
+#include "wimlib/dentry.h"
 #include "wimlib/encoding.h"
 #include "wimlib/error.h"
-#include "wimlib/lookup_table.h"
+#include "wimlib/metadata.h"
 #include "wimlib/ntfs_3g.h"
-#include "wimlib/paths.h"
-#include "wimlib/resource.h"
+#include "wimlib/reparse.h"
+#include "wimlib/security.h"
 #include "wimlib/security_descriptor.h"
 
-static ntfs_volume *
-ntfs_3g_apply_ctx_get_volume(struct apply_ctx *ctx)
-{
-       return (ntfs_volume*)ctx->private[0];
-}
-
-static void
-ntfs_3g_apply_ctx_set_volume(struct apply_ctx *ctx, ntfs_volume *vol)
-{
-       ctx->private[0] = (intptr_t)vol;
-}
-
-static ntfs_inode *
-ntfs_3g_apply_pathname_to_inode(const char *path, struct apply_ctx *ctx)
-{
-       ntfs_volume *vol = ntfs_3g_apply_ctx_get_volume(ctx);
-       return ntfs_pathname_to_inode(vol, NULL, path);
-}
-
-struct ntfs_attr_extract_ctx {
-       u64 offset;
-       ntfs_attr *na;
-};
-
 static int
-ntfs_3g_extract_wim_chunk(const void *buf, size_t len, void *_ctx)
+ntfs_3g_get_supported_features(const char *target,
+                              struct wim_features *supported_features)
 {
-       struct ntfs_attr_extract_ctx *ctx = _ctx;
-
-       if (ntfs_attr_pwrite(ctx->na, ctx->offset, len, buf) != len)
-               return WIMLIB_ERR_WRITE;
-       ctx->offset += len;
+       supported_features->archive_files             = 1;
+       supported_features->hidden_files              = 1;
+       supported_features->system_files              = 1;
+       supported_features->compressed_files          = 1;
+       supported_features->encrypted_directories     = 1;
+       supported_features->not_context_indexed_files = 1;
+       supported_features->named_data_streams        = 1;
+       supported_features->hard_links                = 1;
+       supported_features->reparse_points            = 1;
+       supported_features->security_descriptors      = 1;
+       supported_features->short_names               = 1;
+       supported_features->timestamps                = 1;
+       supported_features->case_sensitive_filenames  = 1;
        return 0;
 }
 
-static ntfs_inode *
-ntfs_3g_open_parent_inode(const char *path, ntfs_volume *vol)
-{
-       char *p;
-       ntfs_inode *dir_ni;
+#define MAX_OPEN_ATTRS 1024
 
-       p = strrchr(path, '/');
-       *p = '\0';
-       dir_ni = ntfs_pathname_to_inode(vol, NULL, path);
-       *p = '/';
-       return dir_ni;
-}
+struct ntfs_3g_apply_ctx {
+       /* Extract flags, the pointer to the WIMStruct, etc.  */
+       struct apply_ctx common;
 
-static int
-ntfs_3g_create(const char *path, struct apply_ctx *ctx, u64 *cookie_ret,
-              mode_t mode)
-{
+       /* Pointer to the open NTFS volume  */
        ntfs_volume *vol;
-       ntfs_inode *dir_ni, *ni;
-       const char *name;
-       utf16lechar *name_utf16le;
-       size_t name_utf16le_nbytes;
-       int ret;
-
-       vol = ntfs_3g_apply_ctx_get_volume(ctx);
-
-       ret = WIMLIB_ERR_OPEN;
-       dir_ni = ntfs_3g_open_parent_inode(path, vol);
-       if (!dir_ni)
-               goto out;
 
-       name = path_basename(path);
-       ret = tstr_to_utf16le(name, strlen(name),
-                             &name_utf16le, &name_utf16le_nbytes);
-       if (ret)
-               goto out_close_dir_ni;
+       ntfs_attr *open_attrs[MAX_OPEN_ATTRS];
+       unsigned num_open_attrs;
+       ntfs_inode *open_inodes[MAX_OPEN_ATTRS];
+       unsigned num_open_inodes;
 
-       ret = WIMLIB_ERR_OPEN;
-       ni = ntfs_create(dir_ni, 0, name_utf16le,
-                        name_utf16le_nbytes / 2, mode);
-       if (!ni)
-               goto out_free_name_utf16le;
-       *cookie_ret = MK_MREF(ni->mft_no, le16_to_cpu(ni->mrec->sequence_number));
-       if (ntfs_inode_close_in_dir(ni, dir_ni))
-               goto out_free_name_utf16le;
-       ret = 0;
-out_free_name_utf16le:
-       FREE(name_utf16le);
-out_close_dir_ni:
-       if (ntfs_inode_close(dir_ni))
-               ret = WIMLIB_ERR_WRITE;
-out:
-       return ret;
-}
+       struct reparse_buffer_disk rpbuf;
+       u8 *reparse_ptr;
 
-static int
-ntfs_3g_create_file(const char *path, struct apply_ctx *ctx,
-                   u64 *cookie_ret)
-{
-       return ntfs_3g_create(path, ctx, cookie_ret, S_IFREG);
-}
-
-static int
-ntfs_3g_create_directory(const char *path, struct apply_ctx *ctx,
-                        u64 *cookie_ret)
-{
-       return ntfs_3g_create(path, ctx, cookie_ret, S_IFDIR);
-}
-
-static int
-ntfs_3g_create_hardlink(const char *oldpath, const char *newpath,
-                       struct apply_ctx *ctx)
-{
-       ntfs_volume *vol;
-       ntfs_inode *dir_ni, *ni;
-       const char *name;
-       utf16lechar *name_utf16le;
-       size_t name_utf16le_nbytes;
-       int ret;
-
-       vol = ntfs_3g_apply_ctx_get_volume(ctx);
-
-       ret = WIMLIB_ERR_OPEN;
-       ni = ntfs_pathname_to_inode(vol, NULL, oldpath);
-       if (!ni)
-               goto out;
-
-       ret = WIMLIB_ERR_OPEN;
-       dir_ni = ntfs_3g_open_parent_inode(newpath, vol);
-       if (!dir_ni)
-               goto out_close_ni;
-
-       name = path_basename(newpath);
-       ret = tstr_to_utf16le(name, strlen(name),
-                             &name_utf16le, &name_utf16le_nbytes);
-       if (ret)
-               goto out_close_dir_ni;
-       ret = 0;
-       if (ntfs_link(ni, dir_ni, name_utf16le, name_utf16le_nbytes / 2))
-               ret = WIMLIB_ERR_LINK;
-       FREE(name_utf16le);
-out_close_dir_ni:
-       if (ntfs_inode_close(dir_ni))
-               ret = WIMLIB_ERR_WRITE;
-out_close_ni:
-       if (ntfs_inode_close(ni))
-               ret = WIMLIB_ERR_WRITE;
-out:
-       return ret;
-}
-
-/*
- * Extract a stream (default or alternate data) to an attribute of an NTFS file.
- */
-static int
-ntfs_3g_extract_stream(file_spec_t file, const utf16lechar *raw_stream_name,
-                      size_t stream_name_nchars,
-                      struct wim_lookup_table_entry *lte, struct apply_ctx *ctx)
-{
-       ntfs_inode *ni;
-       ntfs_attr *na;
-       int ret;
-       struct ntfs_attr_extract_ctx extract_ctx;
-       utf16lechar *stream_name;
-
-       if (stream_name_nchars == 0) {
-               stream_name = AT_UNNAMED;
-       } else {
-               stream_name = alloca((stream_name_nchars + 1) * sizeof(utf16lechar));
-               memcpy(stream_name, raw_stream_name,
-                      stream_name_nchars * sizeof(utf16lechar));
-               stream_name[stream_name_nchars] = 0;
-       }
-
-       ret = 0;
-       if (!stream_name_nchars && !lte)
-               goto out;
-
-       /* Open NTFS inode to which to extract the stream.  */
-       ret = WIMLIB_ERR_OPEN;
-       ni = ntfs_inode_open(ntfs_3g_apply_ctx_get_volume(ctx), file.cookie);
-       if (!ni)
-               goto out;
-
-       /* Add the stream if it's not the default (unnamed) stream.  */
-       ret = WIMLIB_ERR_OPEN;
-       if (stream_name_nchars)
-               if (ntfs_attr_add(ni, AT_DATA, stream_name,
-                                 stream_name_nchars, NULL, 0))
-                       goto out_close;
-
-       /* If stream is empty, no need to open and extract it.  */
-       ret = 0;
-       if (!lte)
-               goto out_close;
-
-       /* Open the stream (NTFS attribute).  */
-       ret = WIMLIB_ERR_OPEN;
-       na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_nchars);
-       if (!na)
-               goto out_close;
-
-       /* (Optional) Immediately resize attribute to size of stream.
-        *
-        * This dramatically speeds up extraction, as demonstrated with the
-        * following timing results:
-        *
-        * 18 mins. 27 sec. to apply Windows 7 image (with resize)
-        * 32 mins. 45 sec. to apply Windows 7 image (no resize)
-        *
-        * It probably would speed things up even more if we could get NTFS-3g
-        * to skip even more useless work (for example it fills resized
-        * attributes with 0's, then we just override it.)  */
-       ret = WIMLIB_ERR_WRITE;
-       if (ntfs_attr_truncate_solid(na, lte->size))
-               goto out_attr_close;
-
-       /* Extract stream data to the NTFS attribute.  */
-       extract_ctx.na = na;
-       extract_ctx.offset = 0;
-       ret = extract_stream(lte, lte->size,
-                            ntfs_3g_extract_wim_chunk, &extract_ctx);
-       /* Clean up and return.  */
-out_attr_close:
-       ntfs_attr_close(na);
-out_close:
-       if (ntfs_inode_close(ni))
-               ret = WIMLIB_ERR_WRITE;
-out:
-       if (ret && !errno)
-               errno = -1;
-       return ret;
-}
-
-static int
-ntfs_3g_extract_unnamed_stream(file_spec_t file,
-                              struct wim_lookup_table_entry *lte,
-                              struct apply_ctx *ctx,
-                              struct wim_dentry *_ignore)
-{
-       return ntfs_3g_extract_stream(file, NULL, 0, lte, ctx);
-}
-
-static int
-ntfs_3g_extract_named_stream(file_spec_t file, const utf16lechar *stream_name,
-                            size_t stream_name_nchars,
-                            struct wim_lookup_table_entry *lte, struct apply_ctx *ctx)
-{
-       return ntfs_3g_extract_stream(file, stream_name,
-                                     stream_name_nchars, lte, ctx);
-}
-
-static int
-ntfs_3g_set_file_attributes(const char *path, u32 attributes,
-                           struct apply_ctx *ctx, unsigned pass)
-{
-       ntfs_inode *ni;
-       int ret = 0;
-
-       ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
-       if (!ni)
-               return WIMLIB_ERR_OPEN;
-       if (ntfs_set_ntfs_attrib(ni, (const char*)&attributes, sizeof(u32), 0))
-               ret = WIMLIB_ERR_SET_ATTRIBUTES;
-       if (ntfs_inode_close(ni))
-               ret = WIMLIB_ERR_WRITE;
-       return ret;
-}
-
-static int
-ntfs_3g_set_reparse_data(const char *path, const u8 *rpbuf, u16 rpbuflen,
-                        struct apply_ctx *ctx)
-{
-       ntfs_inode *ni;
-       int ret = 0;
-
-       ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
-       if (!ni)
-               return WIMLIB_ERR_OPEN;
-       if (ntfs_set_ntfs_reparse_data(ni, rpbuf, rpbuflen, 0))
-               ret = WIMLIB_ERR_SET_REPARSE_DATA;
-       if (ntfs_inode_close(ni))
-               ret = WIMLIB_ERR_WRITE;
-       return ret;
-}
-
-static int
-ntfs_3g_set_short_name(const char *path, const utf16lechar *short_name,
-                      size_t short_name_nchars, struct apply_ctx *ctx)
-{
-       ntfs_inode *ni, *dir_ni;
-       ntfs_volume *vol;
-       int ret;
-       char *dosname = NULL;
-       size_t dosname_nbytes;
-
-       ret = 0;
-       if (short_name_nchars == 0)
-               goto out;
-
-       vol = ntfs_3g_apply_ctx_get_volume(ctx);
-
-       ret = WIMLIB_ERR_OPEN;
-       dir_ni = ntfs_3g_open_parent_inode(path, vol);
-       if (!dir_ni)
-               goto out;
-
-       ret = WIMLIB_ERR_OPEN;
-       ni = ntfs_pathname_to_inode(vol, NULL, path);
-       if (!ni)
-               goto out_close_dir_ni;
-
-       ret = utf16le_to_tstr(short_name, short_name_nchars * 2,
-                             &dosname, &dosname_nbytes);
-       if (ret)
-               goto out_close_ni;
+       /* Offset in the stream currently being read  */
+       u64 offset;
 
-       ret = 0;
-       if (ntfs_set_ntfs_dos_name(ni, dir_ni, dosname,
-                                  dosname_nbytes, 0))
-               ret = WIMLIB_ERR_SET_SHORT_NAME;
-       /* ntfs_set_ntfs_dos_name() always closes the inodes.  */
-       FREE(dosname);
-       goto out;
-out_close_ni:
-       if (ntfs_inode_close_in_dir(ni, dir_ni))
-               ret = WIMLIB_ERR_WRITE;
-out_close_dir_ni:
-       if (ntfs_inode_close(dir_ni))
-               ret = WIMLIB_ERR_WRITE;
-out:
-       return ret;
-}
+       unsigned num_reparse_inodes;
+       ntfs_inode *ntfs_reparse_inodes[MAX_OPEN_ATTRS];
+       struct wim_inode *wim_reparse_inodes[MAX_OPEN_ATTRS];
+};
 
 static size_t
 sid_size(const wimlib_SID *sid)
@@ -475,7 +189,7 @@ sd_fixup(const u8 *_desc, size_t *size_p)
        }
 
        desc_new = MALLOC(size + sid_size(sid));
-       if (desc_new == NULL)
+       if (!desc_new)
                return (u8*)_desc;
 
        memcpy(desc_new, desc, size);
@@ -488,181 +202,754 @@ sd_fixup(const u8 *_desc, size_t *size_p)
        return (u8*)desc_new;
 }
 
+/* Set the security descriptor @desc of size @desc_size on the NTFS inode @ni.
+  */
 static int
-ntfs_3g_set_security_descriptor(const char *path, const u8 *desc, size_t desc_size,
-                               struct apply_ctx *ctx)
+ntfs_3g_set_security_descriptor(ntfs_inode *ni, const void *desc, size_t desc_size)
 {
-       ntfs_volume *vol;
-       ntfs_inode *ni;
        struct SECURITY_CONTEXT sec_ctx;
        u8 *desc_fixed;
-       int ret;
-
-       vol = ntfs_3g_apply_ctx_get_volume(ctx);
-
-       ni = ntfs_pathname_to_inode(vol, NULL, path);
-       if (!ni)
-               return WIMLIB_ERR_OPEN;
+       int ret = 0;
 
        memset(&sec_ctx, 0, sizeof(sec_ctx));
-       sec_ctx.vol = vol;
+       sec_ctx.vol = ni->vol;
 
        desc_fixed = sd_fixup(desc, &desc_size);
 
-       ret = 0;
-
        if (ntfs_set_ntfs_acl(&sec_ctx, ni, desc_fixed, desc_size, 0))
                ret = WIMLIB_ERR_SET_SECURITY;
 
        if (desc_fixed != desc)
                FREE(desc_fixed);
 
-       if (ntfs_inode_close(ni))
-               ret = WIMLIB_ERR_WRITE;
-
        return ret;
 }
 
 static int
-ntfs_3g_set_timestamps(const char *path, u64 creation_time,
-                      u64 last_write_time, u64 last_access_time,
-                      struct apply_ctx *ctx)
+ntfs_3g_set_timestamps(ntfs_inode *ni, const struct wim_inode *inode)
+{
+       u64 times[3] = {
+               inode->i_creation_time,
+               inode->i_last_write_time,
+               inode->i_last_access_time,
+       };
+
+       if (ntfs_inode_set_times(ni, (const char *)times, sizeof(times), 0))
+               return WIMLIB_ERR_SET_TIMESTAMPS;
+       return 0;
+}
+
+/* Restore the timestamps on the NTFS inode corresponding to @inode.  */
+static int
+ntfs_3g_restore_timestamps(ntfs_volume *vol, const struct wim_inode *inode)
 {
-       u64 ntfs_timestamps[3];
        ntfs_inode *ni;
-       int ret = 0;
+       int res;
 
-       ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
+       ni = ntfs_inode_open(vol, inode->i_mft_no);
        if (!ni)
-               return WIMLIB_ERR_OPEN;
-
-       /* Note: ntfs_inode_set_times() expects the times in native byte order,
-        * not little endian. */
-       ntfs_timestamps[0] = creation_time;
-       ntfs_timestamps[1] = last_write_time;
-       ntfs_timestamps[2] = last_access_time;
-
-       if (ntfs_inode_set_times(ni, (const char*)ntfs_timestamps,
-                                sizeof(ntfs_timestamps), 0))
-               ret = WIMLIB_ERR_SET_TIMESTAMPS;
-       if (ntfs_inode_close(ni))
-               ret = WIMLIB_ERR_WRITE;
+               goto fail;
+
+       res = ntfs_3g_set_timestamps(ni, inode);
+
+       if (ntfs_inode_close(ni) || res)
+               goto fail;
+
+       return 0;
+
+fail:
+       ERROR_WITH_ERRNO("Failed to update timestamps of \"%s\" in NTFS volume",
+                        dentry_full_path(inode_first_extraction_dentry(inode)));
+       return WIMLIB_ERR_SET_TIMESTAMPS;
+}
+
+/* Restore the DOS name of the @dentry.
+ * This closes both @ni and @dir_ni.
+ * If either is NULL, then they are opened temporarily.  */
+static int
+ntfs_3g_restore_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
+                        struct wim_dentry *dentry, ntfs_volume *vol)
+{
+       int ret;
+       const char *dos_name;
+       size_t dos_name_nbytes;
+
+       /* Note: ntfs_set_ntfs_dos_name() closes both inodes (even if it fails).
+        * And it takes in a multibyte string, even though it translates it to
+        * UTF-16LE internally... which is annoying because we currently have
+        * the UTF-16LE string but not the multibyte string.  */
+
+       ret = utf16le_get_tstr(dentry->short_name, dentry->short_name_nbytes,
+                              &dos_name, &dos_name_nbytes);
+       if (ret)
+               goto out_close;
+
+       if (!dir_ni)
+               dir_ni = ntfs_inode_open(vol, dentry->parent->d_inode->i_mft_no);
+       if (!ni)
+               ni = ntfs_inode_open(vol, dentry->d_inode->i_mft_no);
+       if (dir_ni && ni) {
+               ret = ntfs_set_ntfs_dos_name(ni, dir_ni,
+                                            dos_name, dos_name_nbytes, 0);
+               dir_ni = NULL;
+               ni = NULL;
+       } else {
+               ret = -1;
+       }
+       utf16le_put_tstr(dos_name);
+       if (ret) {
+               ERROR_WITH_ERRNO("Failed to set DOS name of \"%s\" in NTFS "
+                                "volume", dentry_full_path(dentry));
+               ret = WIMLIB_ERR_SET_SHORT_NAME;
+               goto out_close;
+       }
+
+       /* Unlike most other NTFS-3g functions, ntfs_set_ntfs_dos_name()
+        * changes the directory's last modification timestamp...
+        * Change it back.  */
+       return ntfs_3g_restore_timestamps(vol, dentry->parent->d_inode);
+
+out_close:
+       /* ntfs_inode_close() can take a NULL argument, but it's probably best
+        * not to rely on this behavior.  */
+       if (ni)
+               ntfs_inode_close(ni);
+       if (dir_ni)
+               ntfs_inode_close(dir_ni);
        return ret;
 }
 
-static bool
-ntfs_3g_target_is_root(const char *target)
+/* Create empty named data streams.
+ *
+ * Since these won't have 'struct wim_lookup_table_entry's, they won't show up
+ * in the call to extract_stream_list().  Hence the need for the special case.
+ */
+static int
+ntfs_3g_create_any_empty_ads(ntfs_inode *ni, const struct wim_inode *inode,
+                            const struct ntfs_3g_apply_ctx *ctx)
 {
-       /* We always extract to the root of the NTFS volume.  */
-       return true;
+       for (u16 i = 0; i < inode->i_num_ads; i++) {
+               const struct wim_ads_entry *entry;
+
+               entry = &inode->i_ads_entries[i];
+
+               /* Not named?  */
+               if (!entry->stream_name_nbytes)
+                       continue;
+
+               /* Not empty?  */
+               if (entry->lte)
+                       continue;
+
+               if (ntfs_attr_add(ni, AT_DATA, entry->stream_name,
+                                 entry->stream_name_nbytes /
+                                       sizeof(utf16lechar),
+                                 NULL, 0))
+               {
+                       ERROR_WITH_ERRNO("Failed to create named data stream "
+                                        "of \"%s\"", dentry_full_path(
+                                               inode_first_extraction_dentry(inode)));
+                       return WIMLIB_ERR_NTFS_3G;
+               }
+       }
+       return 0;
 }
 
+/* Set attributes, security descriptor, and timestamps on the NTFS inode @ni.
+ */
 static int
-ntfs_3g_start_extract(const char *path, struct apply_ctx *ctx)
+ntfs_3g_set_metadata(ntfs_inode *ni, const struct wim_inode *inode,
+                    const struct ntfs_3g_apply_ctx *ctx)
 {
-       ntfs_volume *vol;
+       int extract_flags;
+       const struct wim_security_data *sd;
+       struct wim_dentry *one_dentry;
+       int ret;
 
-       vol = ntfs_mount(ctx->target, 0);
-       if (!vol) {
-               ERROR_WITH_ERRNO("Failed to mount \"%"TS"\" with NTFS-3g", ctx->target);
-               return WIMLIB_ERR_OPEN;
+       extract_flags = ctx->common.extract_flags;
+       sd = wim_get_current_security_data(ctx->common.wim);
+       one_dentry = inode_first_extraction_dentry(inode);
+
+       /* Attributes  */
+       if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) {
+               u32 attrib = inode->i_attributes;
+
+               attrib &= ~(FILE_ATTRIBUTE_SPARSE_FILE |
+                           FILE_ATTRIBUTE_ENCRYPTED);
+
+               if (ntfs_set_ntfs_attrib(ni, (const char *)&attrib,
+                                        sizeof(attrib), 0))
+               {
+                       ERROR_WITH_ERRNO("Failed to set attributes on \"%s\" "
+                                        "in NTFS volume",
+                                        dentry_full_path(one_dentry));
+                       return WIMLIB_ERR_SET_ATTRIBUTES;
+               }
+       }
+
+       /* Security descriptor  */
+       if ((inode->i_security_id >= 0)
+           && !(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS))
+       {
+               const void *desc;
+               size_t desc_size;
+
+               desc = sd->descriptors[inode->i_security_id];
+               desc_size = sd->sizes[inode->i_security_id];
+
+               ret = ntfs_3g_set_security_descriptor(ni, desc, desc_size);
+               if (ret) {
+                       if (wimlib_print_errors) {
+                               ERROR_WITH_ERRNO("Failed to set security descriptor "
+                                                "on \"%s\" in NTFS volume",
+                                                dentry_full_path(one_dentry));
+                               fprintf(stderr, "The security descriptor is: ");
+                               print_byte_field(desc, desc_size, stderr);
+                               fprintf(stderr, "\n");
+                       }
+                       return ret;
+               }
+       }
+
+       /* Timestamps  */
+       ret = ntfs_3g_set_timestamps(ni, inode);
+       if (ret) {
+               ERROR_WITH_ERRNO("Failed to set timestamps on \"%s\" "
+                                "in NTFS volume",
+                                dentry_full_path(one_dentry));
+               return ret;
        }
-       ntfs_3g_apply_ctx_set_volume(ctx, vol);
-
-       ctx->supported_features.archive_files             = 1;
-       ctx->supported_features.hidden_files              = 1;
-       ctx->supported_features.system_files              = 1;
-       ctx->supported_features.compressed_files          = 1;
-       ctx->supported_features.encrypted_files           = 0;
-       ctx->supported_features.not_context_indexed_files = 1;
-       ctx->supported_features.sparse_files              = 1;
-       ctx->supported_features.named_data_streams        = 1;
-       ctx->supported_features.hard_links                = 1;
-       ctx->supported_features.reparse_points            = 1;
-       ctx->supported_features.security_descriptors      = 1;
-       ctx->supported_features.short_names               = 1;
        return 0;
 }
 
+/* Recursively creates all the subdirectories of @dir, which has been created as
+ * the NTFS inode @dir_ni.  */
 static int
-ntfs_3g_finish_or_abort_extract(struct apply_ctx *ctx)
+ntfs_3g_create_dirs_recursive(ntfs_inode *dir_ni, struct wim_dentry *dir,
+                             const struct ntfs_3g_apply_ctx *ctx)
 {
-       ntfs_volume *vol;
+       struct wim_dentry *child;
+
+       for_dentry_child(child, dir) {
+               ntfs_inode *ni;
+               int ret;
+
+               if (!(child->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))
+                       continue;
+               if (!will_extract_dentry(child))
+                       continue;
+
+               ni = ntfs_create(dir_ni, 0, child->d_extraction_name,
+                                child->d_extraction_name_nchars, S_IFDIR);
+               if (!ni) {
+                       ERROR_WITH_ERRNO("Error creating \"%s\" in NTFS volume",
+                                        dentry_full_path(child));
+                       return WIMLIB_ERR_NTFS_3G;
+               }
+
+               child->d_inode->i_mft_no = ni->mft_no;
+
+               ret = ntfs_3g_set_metadata(ni, child->d_inode, ctx);
+               if (!ret)
+                       ret = ntfs_3g_create_any_empty_ads(ni, child->d_inode, ctx);
+               if (!ret)
+                       ret = ntfs_3g_create_dirs_recursive(ni, child, ctx);
+
+               if (ntfs_inode_close_in_dir(ni, dir_ni) && !ret) {
+                       ERROR_WITH_ERRNO("Error closing \"%s\" in NTFS volume",
+                                        dentry_full_path(child));
+                       ret = WIMLIB_ERR_NTFS_3G;
+               }
+               if (ret)
+                       return ret;
+       }
+       return 0;
+}
 
-       vol = ntfs_3g_apply_ctx_get_volume(ctx);
-       if (ntfs_umount(vol, FALSE)) {
-               ERROR_WITH_ERRNO("Failed to unmount \"%"TS"\" with NTFS-3g",
-                                ctx->target);
-               return WIMLIB_ERR_WRITE;
+/* For each WIM dentry in the @root tree that represents a directory, create the
+ * corresponding directory in the NTFS volume @ctx->vol.  */
+static int
+ntfs_3g_create_directories(struct wim_dentry *root,
+                          struct list_head *dentry_list,
+                          const struct ntfs_3g_apply_ctx *ctx)
+{
+       ntfs_inode *root_ni;
+       int ret;
+       struct wim_dentry *dentry;
+
+       /* Create the directories using POSIX names.  */
+
+       root_ni = ntfs_inode_open(ctx->vol, FILE_root);
+       if (!root_ni) {
+               ERROR_WITH_ERRNO("Can't open root of NTFS volume");
+               return WIMLIB_ERR_NTFS_3G;
+       }
+
+       root->d_inode->i_mft_no = FILE_root;
+
+       ret = ntfs_3g_create_dirs_recursive(root_ni, root, ctx);
+
+       if (ntfs_inode_close(root_ni) && !ret) {
+               ERROR_WITH_ERRNO("Error closing root of NTFS volume");
+               ret = WIMLIB_ERR_NTFS_3G;
+       }
+       if (ret)
+               return ret;
+
+       /* Set the DOS name of any directory that has one.  */
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               if (!(dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))
+                       continue;
+               if (!dentry_has_short_name(dentry))
+                       continue;
+               ret = ntfs_3g_restore_dos_name(NULL, NULL, dentry, ctx->vol);
+               if (ret)
+                       return ret;
        }
        return 0;
 }
 
-void
-libntfs3g_global_init(void)
+/* When creating an inode that will have a short (DOS) name, we create it using
+ * the long name associated with the short name.  This ensures that the short
+ * name gets associated with the correct long name.  */
+static struct wim_dentry *
+ntfs_3g_first_extraction_alias(struct wim_inode *inode)
+{
+       struct list_head *next = inode->i_extraction_aliases.next;
+       struct wim_dentry *dentry;
+
+       do {
+               dentry = list_entry(next, struct wim_dentry,
+                                   d_extraction_alias_node);
+               if (dentry_has_short_name(dentry))
+                       break;
+               next = next->next;
+       } while (next != &inode->i_extraction_aliases);
+       return dentry;
+}
+
+/*
+ * Add a hard link for the NTFS inode @ni at the location corresponding to the
+ * WIM dentry @dentry.
+ *
+ * The parent directory must have already been created on the NTFS volume.
+ *
+ * Returns 0 on success; returns WIMLIB_ERR_NTFS_3G and sets errno on failure.
+ */
+static int
+ntfs_3g_add_link(ntfs_inode *ni, struct wim_dentry *dentry)
+{
+       ntfs_inode *dir_ni;
+       int res;
+
+       /* Open the inode of the parent directory.  */
+       dir_ni = ntfs_inode_open(ni->vol, dentry->parent->d_inode->i_mft_no);
+       if (!dir_ni)
+               goto fail;
+
+       /* Create the link.  */
+       res = ntfs_link(ni, dir_ni, dentry->d_extraction_name,
+                       dentry->d_extraction_name_nchars);
+
+       /* Close the parent directory.  */
+       if (ntfs_inode_close(dir_ni) || res)
+               goto fail;
+
+       return 0;
+
+fail:
+       ERROR_WITH_ERRNO("Can't create link \"%s\" in NTFS volume",
+                        dentry_full_path(dentry));
+       return WIMLIB_ERR_NTFS_3G;
+}
+
+static int
+ntfs_3g_create_nondirectory(struct wim_inode *inode,
+                           const struct ntfs_3g_apply_ctx *ctx)
+{
+       struct wim_dentry *first_dentry;
+       ntfs_inode *dir_ni;
+       ntfs_inode *ni;
+       struct list_head *next;
+       struct wim_dentry *dentry;
+       int ret;
+
+       first_dentry = ntfs_3g_first_extraction_alias(inode);
+
+       /* Create first link.  */
+
+       dir_ni = ntfs_inode_open(ctx->vol, first_dentry->parent->d_inode->i_mft_no);
+       if (!dir_ni) {
+               ERROR_WITH_ERRNO("Can't open \"%s\" in NTFS volume",
+                                dentry_full_path(first_dentry->parent));
+               return WIMLIB_ERR_NTFS_3G;
+       }
+
+       ni = ntfs_create(dir_ni, 0, first_dentry->d_extraction_name,
+                        first_dentry->d_extraction_name_nchars, S_IFREG);
+
+       if (!ni) {
+               ERROR_WITH_ERRNO("Can't create \"%s\" in NTFS volume",
+                                dentry_full_path(first_dentry));
+               ntfs_inode_close(dir_ni);
+               return WIMLIB_ERR_NTFS_3G;
+       }
+
+       inode->i_mft_no = ni->mft_no;
+
+       /* Set short name if present.  */
+       if (dentry_has_short_name(first_dentry)) {
+
+               ret = ntfs_3g_restore_dos_name(ni, dir_ni, first_dentry, ctx->vol);
+
+               /* ntfs_3g_restore_dos_name() closed both 'ni' and 'dir_ni'.  */
+
+               if (ret)
+                       return ret;
+
+               /* Reopen the inode.  */
+               ni = ntfs_inode_open(ctx->vol, inode->i_mft_no);
+               if (!ni) {
+                       ERROR_WITH_ERRNO("Failed to reopen \"%s\" "
+                                        "in NTFS volume",
+                                        dentry_full_path(first_dentry));
+                       return WIMLIB_ERR_NTFS_3G;
+               }
+       } else {
+               /* Close the directory in which the first link was created.  */
+               if (ntfs_inode_close(dir_ni)) {
+                       ERROR_WITH_ERRNO("Failed to close \"%s\" in NTFS volume",
+                                        dentry_full_path(first_dentry->parent));
+                       ret = WIMLIB_ERR_NTFS_3G;
+                       goto out_close_ni;
+               }
+       }
+
+       /* Create additional links if present.  */
+       next = inode->i_extraction_aliases.next;
+       ret = 0;
+       do {
+               dentry = list_entry(next, struct wim_dentry,
+                                   d_extraction_alias_node);
+               if (dentry != first_dentry) {
+                       ret = ntfs_3g_add_link(ni, dentry);
+                       if (ret)
+                               goto out_close_ni;
+               }
+               next = next->next;
+       } while (next != &inode->i_extraction_aliases);
+
+       /* Set metadata.  */
+       ret = ntfs_3g_set_metadata(ni, inode, ctx);
+       if (ret)
+               goto out_close_ni;
+
+       ret = ntfs_3g_create_any_empty_ads(ni, inode, ctx);
+
+out_close_ni:
+       /* Close the inode.  */
+       if (ntfs_inode_close(ni) && !ret) {
+               ERROR_WITH_ERRNO("Error closing \"%s\" in NTFS volume",
+                                dentry_full_path(first_dentry));
+               ret = WIMLIB_ERR_NTFS_3G;
+       }
+       return ret;
+}
+
+/* For each WIM dentry in the @dentry_list that represents a nondirectory file,
+ * create the corresponding nondirectory file in the NTFS volume.
+ *
+ * Directories must have already been created.  */
+static int
+ntfs_3g_create_nondirectories(struct list_head *dentry_list,
+                             const struct ntfs_3g_apply_ctx *ctx)
+{
+       struct wim_dentry *dentry;
+       struct wim_inode *inode;
+       int ret;
+
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               inode = dentry->d_inode;
+               if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
+                       continue;
+               if (dentry != inode_first_extraction_dentry(inode))
+                       continue;
+               ret = ntfs_3g_create_nondirectory(inode, ctx);
+               if (ret)
+                       return ret;
+       }
+       return 0;
+}
+
+static int
+ntfs_3g_begin_extract_stream_to_attr(struct wim_lookup_table_entry *stream,
+                                    ntfs_inode *ni,
+                                    struct wim_inode *inode,
+                                    ntfschar *stream_name,
+                                    struct ntfs_3g_apply_ctx *ctx)
+{
+       struct wim_dentry *one_dentry = inode_first_extraction_dentry(inode);
+       size_t stream_name_nchars = 0;
+       ntfs_attr *attr;
+
+       if (stream_name)
+               for (const ntfschar *p = stream_name; *p; p++)
+                       stream_name_nchars++;
+
+       if (stream_name_nchars == 0)
+               stream_name = AT_UNNAMED;
+       if ((inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+           && (stream_name_nchars == 0))
+       {
+               if (stream->size > REPARSE_DATA_MAX_SIZE) {
+                       ERROR("Reparse data of \"%s\" has size "
+                             "%"PRIu64" bytes (exceeds %u bytes)",
+                             dentry_full_path(one_dentry),
+                             stream->size, REPARSE_DATA_MAX_SIZE);
+                       return WIMLIB_ERR_INVALID_REPARSE_DATA;
+               }
+               ctx->reparse_ptr = ctx->rpbuf.rpdata;
+               ctx->rpbuf.rpdatalen = cpu_to_le16(stream->size);
+               ctx->rpbuf.rpreserved = cpu_to_le16(0);
+               ctx->ntfs_reparse_inodes[ctx->num_reparse_inodes] = ni;
+               ctx->wim_reparse_inodes[ctx->num_reparse_inodes] = inode;
+               ctx->num_reparse_inodes++;
+               return 0;
+       }
+
+       if (stream_name_nchars &&
+           (ntfs_attr_add(ni, AT_DATA, stream_name,
+                          stream_name_nchars, NULL, 0)))
+       {
+               ERROR_WITH_ERRNO("Failed to create named data stream of \"%s\"",
+                                dentry_full_path(one_dentry));
+               return WIMLIB_ERR_NTFS_3G;
+       }
+
+       attr = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_nchars);
+       if (!attr) {
+               ERROR_WITH_ERRNO("Failed to open data stream of \"%s\"",
+                                dentry_full_path(one_dentry));
+               return WIMLIB_ERR_NTFS_3G;
+       }
+       ctx->open_attrs[ctx->num_open_attrs++] = attr;
+       ntfs_attr_truncate_solid(attr, stream->size);
+       return 0;
+}
+
+static int
+ntfs_3g_cleanup_stream_extract(struct ntfs_3g_apply_ctx *ctx)
+{
+       int ret = 0;
+
+       for (unsigned i = 0; i < ctx->num_open_attrs; i++) {
+               if (ntfs_attr_pclose(ctx->open_attrs[i]))
+                       ret = -1;
+               ntfs_attr_close(ctx->open_attrs[i]);
+       }
+
+       ctx->num_open_attrs = 0;
+
+       for (unsigned i = 0; i < ctx->num_open_inodes; i++) {
+               if (ntfs_inode_close(ctx->open_inodes[i]))
+                       ret = -1;
+       }
+       ctx->num_open_inodes = 0;
+
+       ctx->offset = 0;
+       ctx->reparse_ptr = NULL;
+       ctx->num_reparse_inodes = 0;
+       return ret;
+}
+
+static ntfs_inode *
+ntfs_3g_open_inode(struct wim_inode *inode, struct ntfs_3g_apply_ctx *ctx)
+{
+       ntfs_inode *ni = NULL;
+
+       if (inode->i_visited) {
+               for (u32 i = 0; i < ctx->num_open_inodes; i++) {
+                       if (ctx->open_inodes[i]->mft_no == inode->i_mft_no) {
+                               ni = ctx->open_inodes[i];
+                               break;
+                       }
+               }
+       }
+       if (!ni) {
+               ni = ntfs_inode_open(ctx->vol, inode->i_mft_no);
+               ctx->open_inodes[ctx->num_open_inodes++] = ni;
+               inode->i_visited = 1;
+       }
+
+       if (!ni) {
+               ERROR_WITH_ERRNO("Can't open \"%s\" in NTFS volume",
+                                dentry_full_path(
+                                       inode_first_extraction_dentry(inode)));
+               return NULL;
+       }
+       return ni;
+}
+
+static int
+ntfs_3g_begin_extract_stream(struct wim_lookup_table_entry *stream,
+                            u32 flags, void *_ctx)
+{
+       struct ntfs_3g_apply_ctx *ctx = _ctx;
+       const struct stream_owner *owners = stream_owners(stream);
+       int ret;
+
+       for (u32 i = 0; i < stream->out_refcnt; i++) {
+               struct wim_inode *inode = owners[i].inode;
+               ntfschar *stream_name = (ntfschar *)owners[i].stream_name;
+               ntfs_inode *ni;
+
+               ret = WIMLIB_ERR_NTFS_3G;
+               ni = ntfs_3g_open_inode(inode, ctx);
+               if (!ni)
+                       goto out_cleanup;
+
+               ret = ntfs_3g_begin_extract_stream_to_attr(stream, ni, inode,
+                                                          stream_name, ctx);
+               if (ret)
+                       goto out_cleanup;
+       }
+       ret = 0;
+       goto out;
+
+out_cleanup:
+       ntfs_3g_cleanup_stream_extract(ctx);
+out:
+       for (u32 i = 0; i < stream->out_refcnt; i++)
+               owners[i].inode->i_visited = 0;
+       return ret;
+}
+
+static int
+ntfs_3g_extract_chunk(const void *chunk, size_t size, void *_ctx)
 {
-       ntfs_set_char_encoding(setlocale(LC_ALL, ""));
+       struct ntfs_3g_apply_ctx *ctx = _ctx;
+       s64 res;
+
+       for (unsigned i = 0; i < ctx->num_open_attrs; i++) {
+               res = ntfs_attr_pwrite(ctx->open_attrs[i],
+                                      ctx->offset, size, chunk);
+               if (res != size) {
+                       ERROR_WITH_ERRNO("Error writing data to NTFS volume");
+                       return WIMLIB_ERR_NTFS_3G;
+               }
+       }
+       if (ctx->reparse_ptr)
+               ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size);
+       ctx->offset += size;
+       return 0;
+}
+
+static int
+ntfs_3g_end_extract_stream(struct wim_lookup_table_entry *stream,
+                          int status, void *_ctx)
+{
+       struct ntfs_3g_apply_ctx *ctx = _ctx;
+       int ret;
+
+       if (status) {
+               ret = status;
+               goto out;
+       }
+
+       for (u32 i = 0; i < ctx->num_reparse_inodes; i++) {
+               struct wim_inode *inode = ctx->wim_reparse_inodes[i];
+
+               ctx->rpbuf.rptag = cpu_to_le32(inode->i_reparse_tag);
+
+               if (ntfs_set_ntfs_reparse_data(ctx->ntfs_reparse_inodes[i],
+                                              (const char *)&ctx->rpbuf,
+                                              stream->size + REPARSE_DATA_OFFSET,
+                                              0))
+               {
+                       ERROR_WITH_ERRNO("Failed to set reparse "
+                                        "data on \"%s\"",
+                                        dentry_full_path(
+                                               inode_first_extraction_dentry(inode)));
+                       ret = WIMLIB_ERR_NTFS_3G;
+                       goto out;
+               }
+       }
+       ret = 0;
+out:
+       if (ntfs_3g_cleanup_stream_extract(ctx) && !ret) {
+               ERROR_WITH_ERRNO("Error writing data to NTFS volume");
+               ret = WIMLIB_ERR_NTFS_3G;
+       }
+       return ret;
+}
+
+static int
+ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
+{
+       struct ntfs_3g_apply_ctx *ctx = (struct ntfs_3g_apply_ctx *)_ctx;
+       ntfs_volume *vol;
+       struct wim_dentry *root;
+       int ret;
+
+       /* For NTFS-3g extraction mode we require that the dentries to extract
+        * form a single tree.  */
+       root = list_first_entry(dentry_list, struct wim_dentry,
+                               d_extraction_list_node);
+
+       /* Mount the NTFS volume.  */
+       vol = ntfs_mount(ctx->common.target, 0);
+       if (!vol) {
+               ERROR_WITH_ERRNO("Failed to mount \"%s\" with NTFS-3g",
+                                ctx->common.target);
+               return WIMLIB_ERR_NTFS_3G;
+       }
+       ctx->vol = vol;
+
+       /* Create all inodes and aliases, including short names, and set
+        * metadata (attributes, security descriptors, and timestamps).  */
+
+       ret = ntfs_3g_create_directories(root, dentry_list, ctx);
+       if (ret)
+               goto out_unmount;
+
+       ret = ntfs_3g_create_nondirectories(dentry_list, ctx);
+       if (ret)
+               goto out_unmount;
+
+       /* Extract streams.  */
+       struct read_stream_list_callbacks cbs = {
+               .begin_stream      = ntfs_3g_begin_extract_stream,
+               .begin_stream_ctx  = ctx,
+               .consume_chunk     = ntfs_3g_extract_chunk,
+               .consume_chunk_ctx = ctx,
+               .end_stream        = ntfs_3g_end_extract_stream,
+               .end_stream_ctx    = ctx,
+       };
+       ret = extract_stream_list(&ctx->common, &cbs);
+
+       /* We do not need a final pass to set timestamps because libntfs-3g does
+        * not update timestamps automatically (exception:
+        * ntfs_set_ntfs_dos_name() does, but we handle this elsewhere).  */
+
+out_unmount:
+       if (ntfs_umount(ctx->vol, FALSE) && !ret) {
+               ERROR_WITH_ERRNO("Failed to unmount \"%s\" with NTFS-3g",
+                                ctx->common.target);
+               ret = WIMLIB_ERR_NTFS_3G;
+       }
+       return ret;
 }
 
 const struct apply_operations ntfs_3g_apply_ops = {
-       .name = "NTFS-3g",
-
-       .target_is_root          = ntfs_3g_target_is_root,
-       .start_extract           = ntfs_3g_start_extract,
-       .create_file             = ntfs_3g_create_file,
-       .create_directory        = ntfs_3g_create_directory,
-       .create_hardlink         = ntfs_3g_create_hardlink,
-       .extract_unnamed_stream  = ntfs_3g_extract_unnamed_stream,
-       .extract_named_stream    = ntfs_3g_extract_named_stream,
-       .set_file_attributes     = ntfs_3g_set_file_attributes,
-       .set_reparse_data        = ntfs_3g_set_reparse_data,
-       .set_short_name          = ntfs_3g_set_short_name,
-       .set_security_descriptor = ntfs_3g_set_security_descriptor,
-       .set_timestamps          = ntfs_3g_set_timestamps,
-       .abort_extract           = ntfs_3g_finish_or_abort_extract,
-       .finish_extract          = ntfs_3g_finish_or_abort_extract,
-
-       .path_prefix = "/",
-       .path_prefix_nchars = 1,
-       .path_separator = '/',
-       .path_max = 32768,
-
-       /* By default, NTFS-3g creates names in the NTFS POSIX namespace, which
-        * is case-sensitive.  */
-       .supports_case_sensitive_filenames = 1,
-
-       /* The root directory of the NTFS volume should not be created
-        * explicitly.  */
-       .root_directory_is_special = 1,
-
-       /* NTFS-3g can open files by MFT reference.  */
-       .uses_cookies = 1,
-
-       /*
-        * With NTFS-3g, the extraction order of the names of a file that has a
-        * short name needs to be:
-        *
-        * 1. Create file using the long name that has an associated short name.
-        *    This long name is temporarily placed in the POSIX namespace.
-        * 2. Set the short name on the file.  This will either change the POSIX
-        *    name to Win32 and create a new DOS name, or replace the POSIX name
-        *    with a Win32+DOS name.
-        * 3. Create additional long names (links) of the file, which are placed
-        *    in the POSIX namespace.
-        *
-        * The reason for this is that two issues can come up when the
-        * extraction is done otherwise:
-        *
-        * - If a DOS name is set on a file in a directory with several long
-        *   names, it is ambiguous which long name to use (at least with the
-        *   exported ntfs_set_ntfs_dos_name() function).
-        * - NTFS-3g 2013.1.13 will no longer allow even setting the DOS name on
-        *   a file with multiple existing long names, even if those long names
-        *   are in different directories and the ntfs_set_ntfs_dos_name() call
-        *   is therefore unambiguous.  (This was apparently changed with the
-        *   FUSE interface in mind.)
-        */
-       .requires_short_name_reordering    = 1,
+       .name                   = "NTFS-3g",
+       .get_supported_features = ntfs_3g_get_supported_features,
+       .extract                = ntfs_3g_extract,
+       .context_size           = sizeof(struct ntfs_3g_apply_ctx),
+       .single_tree_only       = true,
 };
 
-#endif /* WITH_NTFS_3G */
+void
+libntfs3g_global_init(void)
+{
+       ntfs_set_char_encoding(setlocale(LC_ALL, ""));
+}
index 2897692..4df2a4b 100644 (file)
 #include <errno.h>
 #include <stdlib.h>
 
-/* On-disk format of a symbolic link (WIM_IO_REPARSE_TAG_SYMLINK) or junction
- * point (WIM_IO_REPARSE_TAG_MOUNT_POINT) reparse data buffer.  */
-struct reparse_buffer_disk {
-       le32 rptag;
-       le16 rpdatalen;
-       le16 rpreserved;
-       le16 substitute_name_offset;
-       le16 substitute_name_nbytes;
-       le16 print_name_offset;
-       le16 print_name_nbytes;
-       union {
-               struct {
-                       le32 rpflags;
-                       u8 data[REPARSE_POINT_MAX_SIZE - 20];
-               } _packed_attribute symlink;
-               struct {
-                       u8 data[REPARSE_POINT_MAX_SIZE - 16];
-               } _packed_attribute junction;
-       };
-} _packed_attribute;
-
-static const utf16lechar volume_junction_prefix[11] = {
-       cpu_to_le16('\\'),
-       cpu_to_le16('?'),
-       cpu_to_le16('?'),
-       cpu_to_le16('\\'),
-       cpu_to_le16('V'),
-       cpu_to_le16('o'),
-       cpu_to_le16('l'),
-       cpu_to_le16('u'),
-       cpu_to_le16('m'),
-       cpu_to_le16('e'),
-       cpu_to_le16('{'),
-};
-
-/* Parse the "substitute name" (link target) from a symbolic link or junction
- * reparse point.
- *
- * Return value is:
- *
- * Non-negative integer:
- *     The name is an absolute symbolic link in one of several formats,
- *     and the return value is the number of UTF-16LE characters that need to
- *     be advanced to reach a simple "absolute" path starting with a backslash
- *     (i.e. skip over \??\ and/or drive letter)
- * Negative integer:
- *     SUBST_NAME_IS_VOLUME_JUNCTION:
- *             The name is a volume junction.
- *     SUBST_NAME_IS_RELATIVE_LINK:
- *             The name is a relative symbolic link.
- *     SUBST_NAME_IS_UNKNOWN:
- *             The name does not appear to be a valid symbolic link, junction,
- *             or mount point.
- */
-int
-parse_substitute_name(const utf16lechar *substitute_name,
-                     u16 substitute_name_nbytes, u32 rptag)
-{
-       u16 substitute_name_nchars = substitute_name_nbytes / 2;
-
-       if (substitute_name_nchars >= 7 &&
-           substitute_name[0] == cpu_to_le16('\\') &&
-           substitute_name[1] == cpu_to_le16('?') &&
-           substitute_name[2] == cpu_to_le16('?') &&
-           substitute_name[3] == cpu_to_le16('\\') &&
-           substitute_name[4] != cpu_to_le16('\0') &&
-           substitute_name[5] == cpu_to_le16(':') &&
-           substitute_name[6] == cpu_to_le16('\\'))
-       {
-               /* "Full" symlink or junction (\??\x:\ prefixed path) */
-               return 6;
-       } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
-                  substitute_name_nchars >= 12 &&
-                  memcmp(substitute_name, volume_junction_prefix,
-                         sizeof(volume_junction_prefix)) == 0 &&
-                  substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\'))
-       {
-               /* Volume junction.  Can't really do anything with it. */
-               return SUBST_NAME_IS_VOLUME_JUNCTION;
-       } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
-                  substitute_name_nchars >= 3 &&
-                  substitute_name[0] != cpu_to_le16('\0') &&
-                  substitute_name[1] == cpu_to_le16(':') &&
-                  substitute_name[2] == cpu_to_le16('\\'))
-       {
-               /* "Absolute" symlink, with drive letter */
-               return 2;
-       } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
-                  substitute_name_nchars >= 1)
-       {
-               if (substitute_name[0] == cpu_to_le16('\\'))
-                       /* "Absolute" symlink, without drive letter */
-                       return 0;
-               else
-                       /* "Relative" symlink, without drive letter */
-                       return SUBST_NAME_IS_RELATIVE_LINK;
-       } else {
-               return SUBST_NAME_IS_UNKNOWN;
-       }
-}
-
 /*
  * Read the data from a symbolic link, junction, or mount point reparse point
  * buffer into a `struct reparse_data'.
@@ -167,10 +66,10 @@ parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen,
                      rpdata->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
        rpdata->rpdatalen = le16_to_cpu(rpbuf_disk->rpdatalen);
        rpdata->rpreserved = le16_to_cpu(rpbuf_disk->rpreserved);
-       substitute_name_offset = le16_to_cpu(rpbuf_disk->substitute_name_offset);
-       rpdata->substitute_name_nbytes = le16_to_cpu(rpbuf_disk->substitute_name_nbytes);
-       print_name_offset = le16_to_cpu(rpbuf_disk->print_name_offset);
-       rpdata->print_name_nbytes = le16_to_cpu(rpbuf_disk->print_name_nbytes);
+       substitute_name_offset = le16_to_cpu(rpbuf_disk->symlink.substitute_name_offset);
+       rpdata->substitute_name_nbytes = le16_to_cpu(rpbuf_disk->symlink.substitute_name_nbytes);
+       print_name_offset = le16_to_cpu(rpbuf_disk->symlink.print_name_offset);
+       rpdata->print_name_nbytes = le16_to_cpu(rpbuf_disk->symlink.print_name_nbytes);
 
        if ((substitute_name_offset & 1) | (print_name_offset & 1) |
            (rpdata->substitute_name_nbytes & 1) | (rpdata->print_name_nbytes & 1))
@@ -220,10 +119,10 @@ make_reparse_buffer(const struct reparse_data * restrict rpdata,
 
        rpbuf_disk->rptag = cpu_to_le32(rpdata->rptag);
        rpbuf_disk->rpreserved = cpu_to_le16(rpdata->rpreserved);
-       rpbuf_disk->substitute_name_offset = cpu_to_le16(0);
-       rpbuf_disk->substitute_name_nbytes = cpu_to_le16(rpdata->substitute_name_nbytes);
-       rpbuf_disk->print_name_offset = cpu_to_le16(rpdata->substitute_name_nbytes + 2);
-       rpbuf_disk->print_name_nbytes = cpu_to_le16(rpdata->print_name_nbytes);
+       rpbuf_disk->symlink.substitute_name_offset = cpu_to_le16(0);
+       rpbuf_disk->symlink.substitute_name_nbytes = cpu_to_le16(rpdata->substitute_name_nbytes);
+       rpbuf_disk->symlink.print_name_offset = cpu_to_le16(rpdata->substitute_name_nbytes + 2);
+       rpbuf_disk->symlink.print_name_nbytes = cpu_to_le16(rpdata->print_name_nbytes);
 
        if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) {
                rpbuf_disk->symlink.rpflags = cpu_to_le32(rpdata->rpflags);
@@ -319,6 +218,92 @@ wim_inode_get_reparse_data(const struct wim_inode * restrict inode,
 /* UNIX version of getting and setting the data in reparse points */
 #ifndef __WIN32__
 
+static const utf16lechar volume_junction_prefix[11] = {
+       cpu_to_le16('\\'),
+       cpu_to_le16('?'),
+       cpu_to_le16('?'),
+       cpu_to_le16('\\'),
+       cpu_to_le16('V'),
+       cpu_to_le16('o'),
+       cpu_to_le16('l'),
+       cpu_to_le16('u'),
+       cpu_to_le16('m'),
+       cpu_to_le16('e'),
+       cpu_to_le16('{'),
+};
+
+enum {
+       SUBST_NAME_IS_RELATIVE_LINK = -1,
+       SUBST_NAME_IS_VOLUME_JUNCTION = -2,
+       SUBST_NAME_IS_UNKNOWN = -3,
+};
+
+/* Parse the "substitute name" (link target) from a symbolic link or junction
+ * reparse point.
+ *
+ * Return value is:
+ *
+ * Non-negative integer:
+ *     The name is an absolute symbolic link in one of several formats,
+ *     and the return value is the number of UTF-16LE characters that need to
+ *     be advanced to reach a simple "absolute" path starting with a backslash
+ *     (i.e. skip over \??\ and/or drive letter)
+ * Negative integer:
+ *     SUBST_NAME_IS_VOLUME_JUNCTION:
+ *             The name is a volume junction.
+ *     SUBST_NAME_IS_RELATIVE_LINK:
+ *             The name is a relative symbolic link.
+ *     SUBST_NAME_IS_UNKNOWN:
+ *             The name does not appear to be a valid symbolic link, junction,
+ *             or mount point.
+ */
+static int
+parse_substitute_name(const utf16lechar *substitute_name,
+                     u16 substitute_name_nbytes, u32 rptag)
+{
+       u16 substitute_name_nchars = substitute_name_nbytes / 2;
+
+       if (substitute_name_nchars >= 7 &&
+           substitute_name[0] == cpu_to_le16('\\') &&
+           substitute_name[1] == cpu_to_le16('?') &&
+           substitute_name[2] == cpu_to_le16('?') &&
+           substitute_name[3] == cpu_to_le16('\\') &&
+           substitute_name[4] != cpu_to_le16('\0') &&
+           substitute_name[5] == cpu_to_le16(':') &&
+           substitute_name[6] == cpu_to_le16('\\'))
+       {
+               /* "Full" symlink or junction (\??\x:\ prefixed path) */
+               return 6;
+       } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
+                  substitute_name_nchars >= 12 &&
+                  memcmp(substitute_name, volume_junction_prefix,
+                         sizeof(volume_junction_prefix)) == 0 &&
+                  substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\'))
+       {
+               /* Volume junction.  Can't really do anything with it. */
+               return SUBST_NAME_IS_VOLUME_JUNCTION;
+       } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
+                  substitute_name_nchars >= 3 &&
+                  substitute_name[0] != cpu_to_le16('\0') &&
+                  substitute_name[1] == cpu_to_le16(':') &&
+                  substitute_name[2] == cpu_to_le16('\\'))
+       {
+               /* "Absolute" symlink, with drive letter */
+               return 2;
+       } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
+                  substitute_name_nchars >= 1)
+       {
+               if (substitute_name[0] == cpu_to_le16('\\'))
+                       /* "Absolute" symlink, without drive letter */
+                       return 0;
+               else
+                       /* "Relative" symlink, without drive letter */
+                       return SUBST_NAME_IS_RELATIVE_LINK;
+       } else {
+               return SUBST_NAME_IS_UNKNOWN;
+       }
+}
+
 /*
  * Get the UNIX-style symlink target from the WIM inode for a reparse point.
  * Specifically, this translates the target from UTF-16 to the current multibyte
@@ -367,7 +352,7 @@ wim_inode_readlink(const struct wim_inode * restrict inode,
                return -EIO;
 
        if (parse_reparse_data((const u8*)&rpbuf_disk, rpbuflen, &rpdata))
-               return -EIO;
+               return -EINVAL;
 
        ret = utf16le_to_tstr(rpdata.substitute_name,
                              rpdata.substitute_name_nbytes,
index d188844..c4f5318 100644 (file)
@@ -3,7 +3,7 @@
  */
 
 /*
- * Copyright (C) 2012, 2013 Eric Biggers
+ * Copyright (C) 2012, 2013, 2014 Eric Biggers
  *
  * This file is part of wimlib, a library for working with WIM files.
  *
  * along with wimlib; if not, see http://www.gnu.org/licenses/.
  */
 
-#ifndef __WIN32__
-
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
 
 #include "wimlib/apply.h"
+#include "wimlib/dentry.h"
 #include "wimlib/error.h"
-#include "wimlib/resource.h"
+#include "wimlib/file_io.h"
+#include "wimlib/reparse.h"
 #include "wimlib/timestamp.h"
-#include "wimlib/unix_data.h"
 
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <stdlib.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <unistd.h>
 
-#ifdef HAVE_UTIME_H
-#  include <utime.h>
-#endif
-
+/* We don't require O_NOFOLLOW, but the advantage of having it is that if we
+ * need to extract a file to a location at which there exists a symbolic link,
+ * open(..., O_NOFOLLOW | ...) recognizes the symbolic link rather than
+ * following it and creating the file somewhere else.  (Equivalent to
+ * FILE_OPEN_REPARSE_POINT on Windows.)  */
 #ifndef O_NOFOLLOW
 #  define O_NOFOLLOW 0
 #endif
 
 static int
-unix_start_extract(const char *target, struct apply_ctx *ctx)
+unix_get_supported_features(const char *target,
+                           struct wim_features *supported_features)
 {
-       ctx->supported_features.hard_links = 1;
-       ctx->supported_features.symlink_reparse_points = 1;
-       ctx->supported_features.unix_data = 1;
+       supported_features->hard_links = 1;
+       supported_features->symlink_reparse_points = 1;
+       supported_features->unix_data = 1;
+       supported_features->timestamps = 1;
+       supported_features->case_sensitive_filenames = 1;
        return 0;
 }
 
+#define NUM_PATHBUFS 2  /* We need 2 when creating hard links  */
+#define MAX_OPEN_FDS 1024 /* TODO: Add special case for when the number of
+                            identical streams exceeds this number.  */
+
+struct unix_apply_ctx {
+       /* Extract flags, the pointer to the WIMStruct, etc.  */
+       struct apply_ctx common;
+
+       /* Buffers for building extraction paths (allocated).  */
+       char *pathbufs[NUM_PATHBUFS];
+
+       /* Index of next pathbuf to use  */
+       unsigned which_pathbuf;
+
+       /* Currently open file descriptors for extraction  */
+       struct filedes open_fds[MAX_OPEN_FDS];
+
+       /* Number of currently open file descriptors in open_fds, starting from
+        * the beginning of the array.  */
+       unsigned num_open_fds;
+
+       /* Buffer for reading reparse data streams into memory  */
+       u8 reparse_data[REPARSE_DATA_MAX_SIZE];
+
+       /* Pointer to the next byte in @reparse_data to fill  */
+       u8 *reparse_ptr;
+
+       /* Absolute path to the target directory (allocated buffer).  Only set
+        * if needed for absolute symbolic link fixups.  */
+       char *target_abspath;
+
+       /* Number of characters in target_abspath.  */
+       size_t target_abspath_nchars;
+};
+
+/* Returns the number of characters needed to represent the path to the
+ * specified @dentry when extracted, not including the null terminator or the
+ * path to the target directory itself.  */
+static size_t
+unix_dentry_path_length(const struct wim_dentry *dentry)
+{
+       size_t len = 0;
+       const struct wim_dentry *d;
+
+       d = dentry;
+       do {
+               len += d->d_extraction_name_nchars + 1;
+               d = d->parent;
+       } while (!dentry_is_root(d) && will_extract_dentry(d));
+
+       return len;
+}
+
+/* Returns the maximum number of characters needed to represent the path to any
+ * dentry in @dentry_list when extracted, including the null terminator and the
+ * path to the target directory itself.  */
+static size_t
+unix_compute_path_max(const struct list_head *dentry_list,
+                     const struct unix_apply_ctx *ctx)
+{
+       size_t max = 0;
+       size_t len;
+       const struct wim_dentry *dentry;
+
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               len = unix_dentry_path_length(dentry);
+               if (len > max)
+                       max = len;
+       }
+
+       /* Account for target and null terminator.  */
+       return ctx->common.target_nchars + max + 1;
+}
+
+/* Builds and returns the filesystem path to which to extract @dentry.
+ * This cycles through NUM_PATHBUFS different buffers.  */
+static const char *
+unix_build_extraction_path(const struct wim_dentry *dentry,
+                          struct unix_apply_ctx *ctx)
+{
+       char *pathbuf;
+       char *p;
+       const struct wim_dentry *d;
+
+       pathbuf = ctx->pathbufs[ctx->which_pathbuf];
+       ctx->which_pathbuf = (ctx->which_pathbuf + 1) % NUM_PATHBUFS;
+
+       p = &pathbuf[ctx->common.target_nchars +
+                    unix_dentry_path_length(dentry)];
+       *p = '\0';
+       d = dentry;
+       do {
+               p -= d->d_extraction_name_nchars;
+               memcpy(p, d->d_extraction_name, d->d_extraction_name_nchars);
+               *--p = '/';
+               d = d->parent;
+       } while (!dentry_is_root(d) && will_extract_dentry(d));
+
+       return pathbuf;
+}
+
+/* This causes the next call to unix_build_extraction_path() to use the same
+ * path buffer as the previous call.  */
+static void
+unix_reuse_pathbuf(struct unix_apply_ctx *ctx)
+{
+       ctx->which_pathbuf = (ctx->which_pathbuf - 1) % NUM_PATHBUFS;
+}
+
+/* Builds and returns the filesystem path to which to extract an unspecified
+ * alias of the @inode.  This cycles through NUM_PATHBUFS different buffers.  */
+static const char *
+unix_build_inode_extraction_path(const struct wim_inode *inode,
+                                struct unix_apply_ctx *ctx)
+{
+       return unix_build_extraction_path(inode_first_extraction_dentry(inode), ctx);
+}
+
+/* Sets the timestamps on a file being extracted.
+ *
+ * Either @fd or @path must be specified (not -1 and not NULL, respectively).
+ */
 static int
-unix_create_file(const char *path, struct apply_ctx *ctx, u64 *cookie_ret)
+unix_set_timestamps(int fd, const char *path, u64 atime, u64 mtime)
 {
-       int fd = open(path, O_TRUNC | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
-       if (fd < 0)
-               return WIMLIB_ERR_OPEN;
-       close(fd);
-       return 0;
+       {
+               struct timespec times[2];
+
+               times[0] = wim_timestamp_to_timespec(atime);
+               times[1] = wim_timestamp_to_timespec(mtime);
+
+               errno = ENOSYS;
+#ifdef HAVE_FUTIMENS
+               if (fd >= 0 && !futimens(fd, times))
+                       return 0;
+#endif
+#ifdef HAVE_UTIMENSAT
+               if (fd < 0 && !utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW))
+                       return 0;
+#endif
+               if (errno != ENOSYS)
+                       return WIMLIB_ERR_SET_TIMESTAMPS;
+       }
+       {
+               struct timeval times[2];
+
+               times[0] = wim_timestamp_to_timeval(atime);
+               times[1] = wim_timestamp_to_timeval(mtime);
+
+               if (fd >= 0 && !futimes(fd, times))
+                       return 0;
+               if (fd < 0 && !lutimes(path, times))
+                       return 0;
+               return WIMLIB_ERR_SET_TIMESTAMPS;
+       }
 }
 
+/*
+ * Set metadata on an extracted file.
+ *
+ * @fd is an open file descriptor to the extracted file, or -1.  @path is the
+ * path to the extracted file, or NULL.  If valid, this function uses @fd.
+ * Otherwise, if valid, it uses @path.  Otherwise, it calculates the path to one
+ * alias of the extracted file and uses it.
+ */
 static int
-unix_create_directory(const tchar *path, struct apply_ctx *ctx, u64 *cookie_ret)
+unix_set_metadata(int fd, const struct wim_inode *inode,
+                 const char *path, struct unix_apply_ctx *ctx)
 {
-       struct stat stbuf;
+       int ret;
+
+       if (fd < 0 && !path)
+               path = unix_build_inode_extraction_path(inode, ctx);
 
-       if (mkdir(path, 0755)) {
-               if (errno != EEXIST)
-                       return WIMLIB_ERR_MKDIR;
-               if (lstat(path, &stbuf))
-                       return WIMLIB_ERR_MKDIR;
-               errno = EEXIST;
-               if (!S_ISDIR(stbuf.st_mode))
-                       return WIMLIB_ERR_MKDIR;
+       ret = unix_set_timestamps(fd, path,
+                                 inode->i_last_access_time,
+                                 inode->i_last_write_time);
+       if (ret) {
+               if (!path)
+                       path = unix_build_inode_extraction_path(inode, ctx);
+               if (ctx->common.extract_flags &
+                   WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS)
+               {
+                       ERROR_WITH_ERRNO("Can't set timestamps on \"%s\"", path);
+                       return ret;
+               } else {
+                       WARNING_WITH_ERRNO("Can't set timestamps on \"%s\"", path);
+               }
        }
        return 0;
 }
 
+/* Extract all needed aliases of the @inode, where one alias, corresponding to
+ * @first_dentry, has already been extracted to @first_path.  */
 static int
-unix_makelink(const tchar *oldpath, const tchar *newpath,
-             int (*makelink)(const tchar *oldpath, const tchar *newpath))
+unix_create_hardlinks(const struct wim_inode *inode,
+                     const struct wim_dentry *first_dentry,
+                     const char *first_path, struct unix_apply_ctx *ctx)
 {
-       if ((*makelink)(oldpath, newpath)) {
-               if (errno != EEXIST)
-                       return WIMLIB_ERR_LINK;
-               if (unlink(newpath))
-                       return WIMLIB_ERR_LINK;
-               if ((*makelink)(oldpath, newpath))
+       const struct wim_dentry *dentry;
+       const char *newpath;
+
+       list_for_each_entry(dentry, &inode->i_extraction_aliases,
+                           d_extraction_alias_node)
+       {
+               if (dentry == first_dentry)
+                       continue;
+
+               newpath = unix_build_extraction_path(dentry, ctx);
+       retry_link:
+               if (link(first_path, newpath)) {
+                       if (errno == EEXIST && !unlink(newpath))
+                               goto retry_link;
+                       ERROR_WITH_ERRNO("Can't create hard link "
+                                        "\"%s\" => \"%s\"", newpath, first_path);
                        return WIMLIB_ERR_LINK;
+               }
+               unix_reuse_pathbuf(ctx);
        }
        return 0;
 }
+
+/* If @dentry represents a directory, create it.  */
 static int
-unix_create_hardlink(const tchar *oldpath, const tchar *newpath,
-                    struct apply_ctx *ctx)
+unix_create_if_directory(const struct wim_dentry *dentry,
+                        struct unix_apply_ctx *ctx)
 {
-       return unix_makelink(oldpath, newpath, link);
+       const char *path;
+       struct stat stbuf;
+
+       if (!dentry_is_directory(dentry))
+               return 0;
+
+       path = unix_build_extraction_path(dentry, ctx);
+       if (mkdir(path, 0755) &&
+           /* It's okay if the path already exists, as long as it's a
+            * directory.  */
+           !(errno == EEXIST && !lstat(path, &stbuf) && S_ISDIR(stbuf.st_mode)))
+       {
+               ERROR_WITH_ERRNO("Can't create directory \"%s\"", path);
+               return WIMLIB_ERR_MKDIR;
+       }
+       return 0;
 }
 
+/* If @dentry represents an empty regular file, create it, set its metadata, and
+ * create any needed hard links.  */
 static int
-unix_create_symlink(const tchar *oldpath, const tchar *newpath,
-                   struct apply_ctx *ctx)
+unix_extract_if_empty_file(const struct wim_dentry *dentry,
+                          struct unix_apply_ctx *ctx)
 {
-       return unix_makelink(oldpath, newpath, symlink);
+       const struct wim_inode *inode;
+       const char *path;
+       int fd;
+       int ret;
+
+       inode = dentry->d_inode;
+
+       /* Extract all aliases only when the "first" comes up.  */
+       if (dentry != inode_first_extraction_dentry(inode))
+               return 0;
+
+       /* Not an empty regular file?  */
+       if (inode_is_directory(inode) || inode_is_symlink(inode) ||
+           inode_unnamed_lte_resolved(inode))
+               return 0;
+
+       path = unix_build_extraction_path(dentry, ctx);
+retry_create:
+       fd = open(path, O_TRUNC | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
+       if (fd < 0) {
+               if (errno == EEXIST && !unlink(path))
+                       goto retry_create;
+               ERROR_WITH_ERRNO("Can't create regular file \"%s\"", path);
+               return WIMLIB_ERR_OPEN;
+       }
+       /* On empty files, we can set timestamps immediately because we don't
+        * need to write any data to them.  */
+       ret = unix_set_metadata(fd, inode, path, ctx);
+       if (close(fd) && !ret) {
+               ERROR_WITH_ERRNO("Error closing \"%s\"", path);
+               ret = WIMLIB_ERR_WRITE;
+       }
+       if (ret)
+               return ret;
+
+       return unix_create_hardlinks(inode, dentry, path, ctx);
 }
 
 static int
-unix_extract_unnamed_stream(file_spec_t file,
-                           struct wim_lookup_table_entry *lte,
-                           struct apply_ctx *ctx, struct wim_dentry *_ignore)
+unix_create_dirs_and_empty_files(const struct list_head *dentry_list,
+                                struct unix_apply_ctx *ctx)
 {
-       const char *path = file.path;
-       struct filedes fd;
-       int raw_fd;
+       const struct wim_dentry *dentry;
        int ret;
 
-       raw_fd = open(path, O_WRONLY | O_TRUNC | O_NOFOLLOW);
-       if (raw_fd < 0)
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               ret = unix_create_if_directory(dentry, ctx);
+               if (ret)
+                       return ret;
+       }
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               ret = unix_extract_if_empty_file(dentry, ctx);
+               if (ret)
+                       return ret;
+       }
+       return 0;
+}
+
+static int
+unix_create_symlink(const struct wim_inode *inode, const char *path,
+                   const u8 *rpdata, u16 rpdatalen, bool rpfix,
+                   const char *apply_dir, size_t apply_dir_nchars)
+{
+       char link_target[REPARSE_DATA_MAX_SIZE];
+       int ret;
+       struct wim_lookup_table_entry lte_override;
+
+       lte_override.resource_location = RESOURCE_IN_ATTACHED_BUFFER;
+       lte_override.attached_buffer = (void *)rpdata;
+       lte_override.size = rpdatalen;
+
+       ret = wim_inode_readlink(inode, link_target,
+                                sizeof(link_target) - 1, &lte_override);
+       if (ret < 0) {
+               errno = -ret;
+               return WIMLIB_ERR_READLINK;
+       }
+
+       link_target[ret] = 0;
+
+       if (rpfix && link_target[0] == '/') {
+
+               /* "Fix" the absolute symbolic link by prepending the absolute
+                * path to the target directory.  */
+
+               if (sizeof(link_target) - (ret + 1) < apply_dir_nchars) {
+                       errno = ENAMETOOLONG;
+                       return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
+               }
+               memmove(link_target + apply_dir_nchars, link_target,
+                       ret + 1);
+               memcpy(link_target, apply_dir, apply_dir_nchars);
+       }
+retry_symlink:
+       if (symlink(link_target, path)) {
+               if (errno == EEXIST && !unlink(path))
+                       goto retry_symlink;
+               return WIMLIB_ERR_LINK;
+       }
+       return 0;
+}
+
+static void
+unix_cleanup_open_fds(struct unix_apply_ctx *ctx, unsigned offset)
+{
+       for (unsigned i = offset; i < ctx->num_open_fds; i++)
+               filedes_close(&ctx->open_fds[i]);
+       ctx->num_open_fds = 0;
+}
+
+static int
+unix_begin_extract_stream_instance(const struct wim_lookup_table_entry *stream,
+                                  const struct wim_inode *inode,
+                                  struct unix_apply_ctx *ctx)
+{
+       const struct wim_dentry *first_dentry;
+       const char *first_path;
+       int fd;
+
+       if (inode_is_symlink(inode)) {
+               /* On UNIX, symbolic links must be created with symlink(), which
+                * requires that the full link target be available.  */
+               if (stream->size > REPARSE_DATA_MAX_SIZE) {
+                       ERROR_WITH_ERRNO("Reparse data of \"%s\" has size "
+                                        "%"PRIu64" bytes (exceeds %u bytes)",
+                                        inode_first_full_path(inode),
+                                        stream->size, REPARSE_DATA_MAX_SIZE);
+                       return WIMLIB_ERR_INVALID_REPARSE_DATA;
+               }
+               ctx->reparse_ptr = ctx->reparse_data;
+               return 0;
+       }
+
+       first_dentry = inode_first_extraction_dentry(inode);
+       first_path = unix_build_extraction_path(first_dentry, ctx);
+retry_create:
+       fd = open(first_path, O_TRUNC | O_CREAT | O_WRONLY | O_NOFOLLOW, 0644);
+       if (fd < 0) {
+               if (errno == EEXIST && !unlink(first_path))
+                       goto retry_create;
+               ERROR_WITH_ERRNO("Can't create regular file \"%s\"", first_path);
                return WIMLIB_ERR_OPEN;
-       filedes_init(&fd, raw_fd);
-       ret = extract_full_stream_to_fd(lte, &fd);
-       if (filedes_close(&fd) && !ret)
-               ret = WIMLIB_ERR_WRITE;
+       }
+       filedes_init(&ctx->open_fds[ctx->num_open_fds++], fd);
+       return unix_create_hardlinks(inode, first_dentry, first_path, ctx);
+}
+
+/* Called when starting to read a single-instance stream for extraction  */
+static int
+unix_begin_extract_stream(struct wim_lookup_table_entry *stream,
+                         u32 flags, void *_ctx)
+{
+       struct unix_apply_ctx *ctx = _ctx;
+       const struct stream_owner *owners = stream_owners(stream);
+       int ret;
+
+       for (u32 i = 0; i < stream->out_refcnt; i++) {
+               const struct wim_inode *inode = owners[i].inode;
+
+               ret = unix_begin_extract_stream_instance(stream, inode, ctx);
+               if (ret) {
+                       ctx->reparse_ptr = NULL;
+                       unix_cleanup_open_fds(ctx, 0);
+                       return ret;
+               }
+       }
+       return 0;
+}
+
+/* Called when the next chunk of a single-instance stream has been read for
+ * extraction  */
+static int
+unix_extract_chunk(const void *chunk, size_t size, void *_ctx)
+{
+       struct unix_apply_ctx *ctx = _ctx;
+       int ret;
+
+       for (unsigned i = 0; i < ctx->num_open_fds; i++) {
+               ret = full_write(&ctx->open_fds[i], chunk, size);
+               if (ret) {
+                       ERROR_WITH_ERRNO("Error writing data to filesystem");
+                       return ret;
+               }
+       }
+       if (ctx->reparse_ptr)
+               ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size);
+       return 0;
+}
+
+/* Called when a single-instance stream has been fully read for extraction  */
+static int
+unix_end_extract_stream(struct wim_lookup_table_entry *stream, int status,
+                       void *_ctx)
+{
+       struct unix_apply_ctx *ctx = _ctx;
+       int ret;
+       unsigned j;
+       const struct stream_owner *owners = stream_owners(stream);
+
+       ctx->reparse_ptr = NULL;
+
+       if (status) {
+               unix_cleanup_open_fds(ctx, 0);
+               return status;
+       }
+
+       j = 0;
+       ret = 0;
+       for (u32 i = 0; i < stream->out_refcnt; i++) {
+               struct wim_inode *inode = owners[i].inode;
+
+               if (inode_is_symlink(inode)) {
+                       /* We finally have the symlink data, so we can create
+                        * the symlink.  */
+                       const char *path;
+
+                       path = unix_build_inode_extraction_path(inode, ctx);
+                       ret = unix_create_symlink(inode, path,
+                                                 ctx->reparse_data,
+                                                 stream->size,
+                                                 (ctx->common.extract_flags &
+                                                  WIMLIB_EXTRACT_FLAG_RPFIX),
+                                                 ctx->target_abspath,
+                                                 ctx->target_abspath_nchars);
+                       if (ret) {
+                               ERROR_WITH_ERRNO("Can't create symbolic link "
+                                                "\"%s\"", path);
+                               break;
+                       }
+                       ret = unix_set_metadata(-1, inode, path, ctx);
+                       if (ret)
+                               break;
+               } else {
+                       /* Set metadata on regular file just before closing it.
+                        */
+                       struct filedes *fd = &ctx->open_fds[j];
+
+                       ret = unix_set_metadata(fd->fd, inode, NULL, ctx);
+                       if (ret)
+                               break;
+
+                       if (filedes_close(fd)) {
+                               ERROR_WITH_ERRNO("Error closing \"%s\"",
+                                                unix_build_inode_extraction_path(inode, ctx));
+                               ret = WIMLIB_ERR_WRITE;
+                               break;
+                       }
+                       j++;
+               }
+       }
+       unix_cleanup_open_fds(ctx, j);
        return ret;
 }
 
 static int
-unix_set_unix_data(const tchar *path, const struct wimlib_unix_data *data,
-                  struct apply_ctx *ctx)
+unix_set_dir_metadata(struct list_head *dentry_list, struct unix_apply_ctx *ctx)
 {
-       struct stat stbuf;
+       const struct wim_dentry *dentry;
+       int ret;
 
-       if (lstat(path, &stbuf))
-               return WIMLIB_ERR_SET_SECURITY;
-       if (!S_ISLNK(stbuf.st_mode))
-               if (chmod(path, data->mode))
-                       return WIMLIB_ERR_SET_SECURITY;
-       if (lchown(path, data->uid, data->gid))
-               return WIMLIB_ERR_SET_SECURITY;
+       list_for_each_entry_reverse(dentry, dentry_list, d_extraction_list_node) {
+               if (dentry_is_directory(dentry)) {
+                       ret = unix_set_metadata(-1, dentry->d_inode, NULL, ctx);
+                       if (ret)
+                               return ret;
+               }
+       }
        return 0;
 }
 
 static int
-unix_set_timestamps(const tchar *path, u64 creation_time, u64 last_write_time,
-                   u64 last_access_time, struct apply_ctx *ctx)
+unix_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
 {
        int ret;
+       struct unix_apply_ctx *ctx = (struct unix_apply_ctx *)_ctx;
+       size_t path_max;
 
-#ifdef HAVE_UTIMENSAT
-       /* Convert the WIM timestamps, which are accurate to 100 nanoseconds,
-        * into `struct timespec's for passing to utimensat(), which is accurate
-        * to 1 nanosecond. */
-
-       struct timespec ts[2];
-       ts[0] = wim_timestamp_to_timespec(last_access_time);
-       ts[1] = wim_timestamp_to_timespec(last_write_time);
-       ret = utimensat(AT_FDCWD, path, ts, AT_SYMLINK_NOFOLLOW);
+       /* Compute the maximum path length that will be needed, then allocate
+        * some path buffers.  */
+       path_max = unix_compute_path_max(dentry_list, ctx);
+
+       for (unsigned i = 0; i < NUM_PATHBUFS; i++) {
+               ctx->pathbufs[i] = MALLOC(path_max);
+               if (!ctx->pathbufs[i]) {
+                       ret = WIMLIB_ERR_NOMEM;
+                       goto out;
+               }
+               /* Pre-fill the target in each path buffer.  We'll just append
+                * the rest of the paths after this.  */
+               memcpy(ctx->pathbufs[i],
+                      ctx->common.target, ctx->common.target_nchars);
+       }
+
+       /* Extract directories and empty regular files.  Directories are needed
+        * because we can't extract any other files until their directories
+        * exist.  Empty files are needed because they don't have
+        * representatives in the stream list.  */
+       ret = unix_create_dirs_and_empty_files(dentry_list, ctx);
        if (ret)
-               ret = errno;
-#else
-       ret = ENOSYS;
-#endif
+               goto out;
 
-       if (ret == ENOSYS) {
-               /* utimensat() not implemented or not available */
-       #ifdef HAVE_LUTIMES
-               /* Convert the WIM timestamps, which are accurate to 100
-                * nanoseconds, into `struct timeval's for passing to lutimes(),
-                * which is accurate to 1 microsecond. */
-               struct timeval tv[2];
-               tv[0] = wim_timestamp_to_timeval(last_access_time);
-               tv[1] = wim_timestamp_to_timeval(last_write_time);
-               ret = lutimes(path, tv);
-               if (ret)
-                       ret = errno;
-       #endif
-       }
-
-       if (ret == ENOSYS) {
-               /* utimensat() and lutimes() both not implemented or not
-                * available */
-       #ifdef HAVE_UTIME
-               /* Convert the WIM timestamps, which are accurate to 100
-                * nanoseconds, into a `struct utimbuf's for passing to
-                * utime(), which is accurate to 1 second. */
-               struct utimbuf buf;
-               buf.actime = wim_timestamp_to_unix(last_access_time);
-               buf.modtime = wim_timestamp_to_unix(last_write_time);
-               ret = utime(path, &buf);
-       #endif
+       /* Get full path to target if needed for absolute symlink fixups.  */
+       if ((ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) &&
+           ctx->common.required_features.symlink_reparse_points)
+       {
+               ctx->target_abspath = realpath(ctx->common.target, NULL);
+               if (!ctx->target_abspath) {
+                       ret = WIMLIB_ERR_NOMEM;
+                       goto out;
+               }
+               ctx->target_abspath_nchars = strlen(ctx->target_abspath);
        }
+
+       /* Extract nonempty regular files and symbolic links.  */
+
+       struct read_stream_list_callbacks cbs = {
+               .begin_stream      = unix_begin_extract_stream,
+               .begin_stream_ctx  = ctx,
+               .consume_chunk     = unix_extract_chunk,
+               .consume_chunk_ctx = ctx,
+               .end_stream        = unix_end_extract_stream,
+               .end_stream_ctx    = ctx,
+       };
+       ret = extract_stream_list(&ctx->common, &cbs);
        if (ret)
-               return WIMLIB_ERR_SET_TIMESTAMPS;
-       return 0;
+               goto out;
+
+       /* Set directory metadata.  We do this last so that we get the right
+        * directory timestamps.  */
+       ret = unix_set_dir_metadata(dentry_list, ctx);
+out:
+       for (unsigned i = 0; i < NUM_PATHBUFS; i++)
+               FREE(ctx->pathbufs[i]);
+       FREE(ctx->target_abspath);
+       return ret;
 }
 
 const struct apply_operations unix_apply_ops = {
-       .name = "UNIX",
-
-       .start_extract          = unix_start_extract,
-       .create_file            = unix_create_file,
-       .create_directory       = unix_create_directory,
-       .create_hardlink        = unix_create_hardlink,
-       .create_symlink         = unix_create_symlink,
-       .extract_unnamed_stream = unix_extract_unnamed_stream,
-       .set_unix_data          = unix_set_unix_data,
-       .set_timestamps         = unix_set_timestamps,
-
-       .path_separator = '/',
-       .path_max = PATH_MAX,
-
-       .requires_target_in_paths = 1,
-       .supports_case_sensitive_filenames = 1,
+       .name                   = "UNIX",
+       .get_supported_features = unix_get_supported_features,
+       .extract                = unix_extract,
+       .context_size           = sizeof(struct unix_apply_ctx),
 };
-
-#endif /* !__WIN32__ */
index af784f9..6779a12 100644 (file)
@@ -919,7 +919,7 @@ free_dentry_full_path(struct wim_dentry *dentry, void *_ignore)
 
 /* Is @d1 a (possibly nonproper) ancestor of @d2?  */
 static bool
-is_ancestor(struct wim_dentry *d1, struct wim_dentry *d2)
+is_ancestor(const struct wim_dentry *d1, const struct wim_dentry *d2)
 {
        for (;;) {
                if (d2 == d1)
index 3dd6da1..d5cfe4d 100644 (file)
@@ -34,8 +34,6 @@
 #  include "config.h"
 #endif
 
-#ifdef __WIN32__
-
 #include "wimlib/win32_common.h"
 #include "wimlib/win32.h"
 #include "wimlib/assert.h"
@@ -49,8 +47,7 @@ static HANDLE
 open_file(const wchar_t *device_name, DWORD desiredAccess)
 {
        return CreateFile(device_name, desiredAccess,
-                         FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
-                         NULL, OPEN_EXISTING,
+                         FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING,
                          FILE_FLAG_BACKUP_SEMANTICS, NULL);
 }
 
@@ -902,7 +899,7 @@ try_to_attach_wof(const wchar_t *drive)
  * @image
  *     Number of the image in the WIM being applied.
  * @target
- *     Path to the target drive.
+ *     Path to the target directory.
  * @data_source_id_ret
  *     On success, an identifier for the backing WIM file will be returned
  *     here.
@@ -1043,8 +1040,10 @@ out:
  * This turns it into a reparse point that redirects accesses to it, to the
  * corresponding resource in the WIM archive.
  *
- * @path
- *     Path to extracted file (already created).
+ * @name
+ *     Path to extracted file (already created), NT namespace.
+ * @printable_name
+ *     Printable representation of @name.
  * @lte
  *     Unnamed data stream of the file.
  * @data_source_id
@@ -1058,23 +1057,33 @@ out:
  * Returns 0 on success, or a positive error code on failure.
  */
 int
-wimboot_set_pointer(const wchar_t *path,
+wimboot_set_pointer(UNICODE_STRING *name,
+                   const wchar_t *printable_name,
                    const struct wim_lookup_table_entry *lte,
                    u64 data_source_id,
                    const u8 lookup_table_hash[SHA1_HASH_SIZE],
                    bool wof_running)
 {
-       HANDLE h;
-       DWORD bytes_returned;
        int ret;
-
-
-       /* Open the file  */
-       h = win32_open_existing_file(path, GENERIC_WRITE);
-       if (h == INVALID_HANDLE_VALUE) {
-               set_errno_from_GetLastError();
-               ret = WIMLIB_ERR_OPEN;
-               goto out;
+       HANDLE h = NULL;
+       NTSTATUS status;
+       OBJECT_ATTRIBUTES attr;
+       IO_STATUS_BLOCK iosb;
+       DWORD bytes_returned;
+       DWORD err;
+
+       memset(&attr, 0, sizeof(attr));
+       attr.Length = sizeof(attr);
+       attr.ObjectName = name;
+
+       status = (*func_NtOpenFile)(&h, GENERIC_WRITE | SYNCHRONIZE, &attr,
+                                   &iosb, FILE_SHARE_VALID_FLAGS,
+                                   FILE_OPEN_FOR_BACKUP_INTENT |
+                                       FILE_OPEN_REPARSE_POINT |
+                                       FILE_SYNCHRONOUS_IO_NONALERT);
+       if (!NT_SUCCESS(status)) {
+               SetLastError((*func_RtlNtStatusToDosError)(status));
+               goto fail;
        }
 
        if (wof_running) {
@@ -1097,7 +1106,8 @@ wimboot_set_pointer(const wchar_t *path,
                /* lookup_table_hash is not necessary  */
 
                if (!DeviceIoControl(h, FSCTL_SET_EXTERNAL_BACKING,
-                                    &in, sizeof(in), NULL, 0, &bytes_returned, NULL))
+                                    &in, sizeof(in), NULL, 0,
+                                    &bytes_returned, NULL))
                        goto fail;
        } else {
 
@@ -1156,20 +1166,17 @@ wimboot_set_pointer(const wchar_t *path,
        }
 
        ret = 0;
-out_close_handle:
-       CloseHandle(h);
+       goto out;
+
+fail:
+       err = GetLastError();
+       set_errno_from_win32_error(err);
+       ERROR_WITH_ERRNO("\"%ls\": Couldn't set WIMBoot pointer data "
+                        "(err=%"PRIu32")", printable_name, (u32)err);
+       ret = WIMLIB_ERR_WIMBOOT;
 out:
+       if (h)
+               (*func_NtClose)(h);
        return ret;
 
-fail:
-       {
-               DWORD err = GetLastError();
-               set_errno_from_win32_error(err);
-               ERROR_WITH_ERRNO("\"%ls\": Couldn't set WIMBoot pointer data "
-                                "(err=0x%08"PRIx32")", path, (u32)err);
-               ret = WIMLIB_ERR_WIMBOOT;
-               goto out_close_handle;
-       }
 }
-
-#endif /* __WIN32__ */
index 3f4a456..f249197 100644 (file)
@@ -21,8 +21,6 @@
  * along with wimlib; if not, see http://www.gnu.org/licenses/.
  */
 
-#ifdef __WIN32__
-
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
 #include "wimlib/dentry.h"
 #include "wimlib/error.h"
 #include "wimlib/lookup_table.h"
-#include "wimlib/resource.h"
+#include "wimlib/metadata.h"
+#include "wimlib/reparse.h"
 #include "wimlib/textfile.h"
 #include "wimlib/xml.h"
-#include "wimlib/wim.h"
 #include "wimlib/wimboot.h"
 
-struct win32_apply_private_data {
-       u64 data_source_id;
-       struct string_set *prepopulate_pats;
-       void *mem_prepopulate_pats;
-       u8 wim_lookup_table_hash[SHA1_HASH_SIZE];
-       bool wof_running;
+/* TODO: Add workaround for when a stream needs to be extracted to more places
+ * than this  */
+#define MAX_OPEN_HANDLES 32768
+
+struct win32_apply_ctx {
+
+       /* Extract flags, the pointer to the WIMStruct, etc.  */
+       struct apply_ctx common;
+
+       /* WIMBoot information, only filled in if WIMLIB_EXTRACT_FLAG_WIMBOOT
+        * was provided  */
+       struct {
+               u64 data_source_id;
+               struct string_set *prepopulate_pats;
+               void *mem_prepopulate_pats;
+               u8 wim_lookup_table_hash[SHA1_HASH_SIZE];
+               bool wof_running;
+       } wimboot;
+
+       /* Open handle to the target directory  */
+       HANDLE h_target;
+
+       /* NT namespace path to the target directory (buffer allocated)  */
+       UNICODE_STRING target_ntpath;
+
+       /* Temporary buffer for building paths (buffer allocated)  */
+       UNICODE_STRING pathbuf;
+
+       /* Object attributes to reuse for opening files in the target directory.
+        * (attr.ObjectName == &pathbuf) and (attr.RootDirectory == h_target).
+        */
+       OBJECT_ATTRIBUTES attr;
+
+       /* Temporary I/O status block for system calls  */
+       IO_STATUS_BLOCK iosb;
+
+       /* Allocated buffer for creating "printable" paths from our
+        * target-relative NT paths  */
+       wchar_t *print_buffer;
+
+       /* Allocated buffer for reading stream data when it cannot be extracted
+        * directly  */
+       u8 *data_buffer;
+
+       /* Pointer to the next byte in @data_buffer to fill  */
+       u8 *data_buffer_ptr;
+
+       /* Size allocated in @data_buffer  */
+       size_t data_buffer_size;
+
+       /* Current offset in the raw encrypted file being written  */
+       size_t encrypted_offset;
+
+       /* Current size of the raw encrypted file being written  */
+       size_t encrypted_size;
+
+       /* Temporary buffer for reparse data  */
+       struct reparse_buffer_disk rpbuf;
+
+       /* Temporary buffer for reparse data of "fixed" absolute symbolic links
+        * and junction  */
+       struct reparse_buffer_disk rpfixbuf;
+
+       /* Array of open handles to filesystem streams currently being written
+        */
+       HANDLE open_handles[MAX_OPEN_HANDLES];
+
+       /* Number of handles in @open_handles currently open (filled in from the
+        * beginning of the array)  */
+       unsigned num_open_handles;
+
+       /* List of dentries, joined by @tmp_list, that need to have reparse data
+        * extracted as soon as the whole stream has been read into
+        * @data_buffer.  */
+       struct list_head reparse_dentries;
+
+       /* List of dentries, joined by @tmp_list, that need to have raw
+        * encrypted data extracted as soon as the whole stream has been read
+        * into @data_buffer.  */
+       struct list_head encrypted_dentries;
+
+       /* Number of files for which we didn't have permission to set the full
+        * security descriptor.  */
+       unsigned long partial_security_descriptors;
+
+       /* Number of files for which we didn't have permission to set any part
+        * of the security descriptor.  */
+       unsigned long no_security_descriptors;
 };
 
-static struct win32_apply_private_data *
-get_private_data(struct apply_ctx *ctx)
+/* Get the drive letter from a Windows path, or return the null character if the
+ * path is relative.  */
+static wchar_t
+get_drive_letter(const wchar_t *path)
 {
-       BUILD_BUG_ON(sizeof(ctx->private) < sizeof(struct win32_apply_private_data));
-       return (struct win32_apply_private_data *)(ctx->private);
+       /* Skip \\?\ prefix  */
+       if (!wcsncmp(path, L"\\\\?\\", 4))
+               path += 4;
+
+       /* Return drive letter if valid  */
+       if (((path[0] >= L'a' && path[0] <= L'z') ||
+            (path[0] >= L'A' && path[0] <= L'Z')) && path[1] == L':')
+               return path[0];
+
+       return L'\0';
 }
 
 static void
-free_prepopulate_pats(struct win32_apply_private_data *dat)
+get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret,
+             bool *short_names_supported_ret)
 {
-       if (dat->prepopulate_pats) {
-               FREE(dat->prepopulate_pats->strings);
-               FREE(dat->prepopulate_pats);
-               dat->prepopulate_pats = NULL;
+       wchar_t filesystem_name[MAX_PATH + 1];
+       wchar_t drive[4];
+       wchar_t *volume = NULL;
+
+       *vol_flags_ret = 0;
+       *short_names_supported_ret = false;
+
+       drive[0] = get_drive_letter(target);
+       if (drive[0]) {
+               drive[1] = L':';
+               drive[2] = L'\\';
+               drive[3] = L'\0';
+               volume = drive;
+       }
+
+       if (!GetVolumeInformation(volume, NULL, 0, NULL, NULL,
+                                 vol_flags_ret, filesystem_name,
+                                 ARRAY_LEN(filesystem_name)))
+       {
+               DWORD err = GetLastError();
+               set_errno_from_win32_error(err);
+               WARNING_WITH_ERRNO("Failed to get volume information for "
+                                  "\"%ls\" (err=%"PRIu32")",
+                                  target, (u32)err);
+               return;
        }
 
-       if (dat->mem_prepopulate_pats) {
-               FREE(dat->mem_prepopulate_pats);
-               dat->mem_prepopulate_pats = NULL;
+       if (wcsstr(filesystem_name, L"NTFS")) {
+               /* FILE_SUPPORTS_HARD_LINKS is only supported on Windows 7 and
+                * later.  Force it on anyway if filesystem is NTFS.  */
+               *vol_flags_ret |= FILE_SUPPORTS_HARD_LINKS;
+
+               /* There's no volume flag for short names, but according to the
+                * MS documentation they are only user-settable on NTFS.  */
+               *short_names_supported_ret = true;
        }
 }
 
 static int
-load_prepopulate_pats(struct apply_ctx *ctx)
+win32_get_supported_features(const wchar_t *target,
+                            struct wim_features *supported_features)
 {
-       int ret;
+       DWORD vol_flags;
+       bool short_names_supported;
+
+       /* Query the features of the target volume.  */
+
+       get_vol_flags(target, &vol_flags, &short_names_supported);
+
+       supported_features->archive_files = 1;
+       supported_features->hidden_files = 1;
+       supported_features->system_files = 1;
+
+       if (vol_flags & FILE_FILE_COMPRESSION)
+               supported_features->compressed_files = 1;
+
+       if (vol_flags & FILE_SUPPORTS_ENCRYPTION) {
+               supported_features->encrypted_files = 1;
+               supported_features->encrypted_directories = 1;
+       }
+
+       supported_features->not_context_indexed_files = 1;
+
+       /* Don't do anything with FILE_SUPPORTS_SPARSE_FILES.  */
+
+       if (vol_flags & FILE_NAMED_STREAMS)
+               supported_features->named_data_streams = 1;
+
+       if (vol_flags & FILE_SUPPORTS_HARD_LINKS)
+               supported_features->hard_links = 1;
+
+       if (vol_flags & FILE_SUPPORTS_REPARSE_POINTS)
+               supported_features->reparse_points = 1;
+
+       if (vol_flags & FILE_PERSISTENT_ACLS)
+               supported_features->security_descriptors = 1;
+
+       if (short_names_supported)
+               supported_features->short_names = 1;
+
+       supported_features->timestamps = 1;
+
+       /* Note: Windows does not support case sensitive filenames!  At least
+        * not without changing the registry and rebooting...  */
+
+       return 0;
+}
+
+/* Load the patterns from [PrepopulateList] of WimBootCompresse.ini in the WIM
+ * image being extracted.  */
+static int
+load_prepopulate_pats(struct win32_apply_ctx *ctx)
+{
+       const wchar_t *path = L"\\Windows\\System32\\WimBootCompress.ini";
        struct wim_dentry *dentry;
        struct wim_lookup_table_entry *lte;
-       struct string_set *s;
-       const tchar *path = WIMLIB_WIM_PATH_SEPARATOR_STRING T("Windows")
-                           WIMLIB_WIM_PATH_SEPARATOR_STRING T("System32")
-                           WIMLIB_WIM_PATH_SEPARATOR_STRING T("WimBootCompress.ini");
+       int ret;
        void *buf;
+       struct string_set *s;
        void *mem;
        struct text_file_section sec;
-       struct win32_apply_private_data *dat = get_private_data(ctx);
 
-       dentry = get_dentry(ctx->wim, path, WIMLIB_CASE_INSENSITIVE);
+       dentry = get_dentry(ctx->common.wim, path, WIMLIB_CASE_INSENSITIVE);
        if (!dentry ||
            (dentry->d_inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
                                              FILE_ATTRIBUTE_REPARSE_POINT |
                                              FILE_ATTRIBUTE_ENCRYPTED)) ||
-           !(lte = inode_unnamed_lte(dentry->d_inode, ctx->wim->lookup_table)))
+           !(lte = inode_unnamed_lte(dentry->d_inode, ctx->common.wim->lookup_table)))
        {
-               WARNING("%"TS" does not exist in WIM image!", path);
+               WARNING("%ls does not exist in WIM image!", path);
                return WIMLIB_ERR_PATH_DOES_NOT_EXIST;
        }
 
@@ -119,762 +285,1528 @@ load_prepopulate_pats(struct apply_ctx *ctx)
                FREE(s);
                return ret;
        }
-       dat->prepopulate_pats = s;
-       dat->mem_prepopulate_pats = mem;
+       ctx->wimboot.prepopulate_pats = s;
+       ctx->wimboot.mem_prepopulate_pats = mem;
        return 0;
 }
 
+/* Returns %true if the path to @dentry matches a pattern in [PrepopulateList]
+ * of WimBootCompress.ini.  Otherwise returns %false.
+ *
+ * @dentry must have had its full path calculated.  */
 static bool
-in_prepopulate_list(struct wim_dentry *dentry, struct apply_ctx *ctx)
+in_prepopulate_list(struct wim_dentry *dentry,
+                   const struct win32_apply_ctx *ctx)
 {
-       struct string_set *pats;
-       const tchar *path;
+       const struct string_set *pats = ctx->wimboot.prepopulate_pats;
 
-       pats = get_private_data(ctx)->prepopulate_pats;
        if (!pats || !pats->num_strings)
                return false;
 
-       path = dentry_full_path(dentry);
-       if (!path)
-               return false;
-
-       return match_pattern_list(path, tstrlen(path), pats);
+       return match_pattern_list(dentry->_full_path,
+                                 wcslen(dentry->_full_path), pats);
 }
 
+/* Calculates the SHA-1 message digest of the WIM's lookup table.  */
 static int
 hash_lookup_table(WIMStruct *wim, u8 hash[SHA1_HASH_SIZE])
 {
        return wim_reshdr_to_hash(&wim->hdr.lookup_table_reshdr, wim, hash);
 }
 
-/* Given a Windows-style path, return the number of characters of the prefix
- * that specify the path to the root directory of a drive, or return 0 if the
- * drive is relative (or at least on the current drive, in the case of
- * absolute-but-not-really-absolute paths like \Windows\System32) */
-static size_t
-win32_path_drive_spec_len(const wchar_t *path)
+/* Prepare for doing a "WIMBoot" extraction by loading patterns from
+ * [PrepopulateList] of WimBootCompress.ini and allocating a WOF data source ID
+ * on the target volume.  */
+static int
+start_wimboot_extraction(struct win32_apply_ctx *ctx)
 {
-       size_t n = 0;
-
-       if (!wcsncmp(path, L"\\\\?\\", 4)) {
-               /* \\?\-prefixed path.  Check for following drive letter and
-                * path separator. */
-               if (path[4] != L'\0' && path[5] == L':' &&
-                   is_any_path_separator(path[6]))
-                       n = 7;
-       } else {
-               /* Not a \\?\-prefixed path.  Check for an initial drive letter
-                * and path separator. */
-               if (path[0] != L'\0' && path[1] == L':' &&
-                   is_any_path_separator(path[2]))
-                       n = 3;
-       }
-       /* Include any additional path separators.*/
-       if (n > 0)
-               while (is_any_path_separator(path[n]))
-                       n++;
-       return n;
-}
+       int ret;
+       WIMStruct *wim = ctx->common.wim;
 
-static bool
-win32_path_is_root_of_drive(const wchar_t *path)
-{
-       size_t drive_spec_len;
-       wchar_t full_path[32768];
-       DWORD ret;
-
-       ret = GetFullPathName(path, ARRAY_LEN(full_path), full_path, NULL);
-       if (ret > 0 && ret < ARRAY_LEN(full_path))
-               path = full_path;
-
-       /* Explicit drive letter and path separator? */
-       drive_spec_len = win32_path_drive_spec_len(path);
-       if (drive_spec_len > 0 && path[drive_spec_len] == L'\0')
-               return true;
-
-       /* All path separators? */
-       for (const wchar_t *p = path; *p != L'\0'; p++)
-               if (!is_any_path_separator(*p))
-                       return false;
-       return true;
-}
+       ret = load_prepopulate_pats(ctx);
+       if (ret == WIMLIB_ERR_NOMEM)
+               return ret;
 
-/* Given a path, which may not yet exist, get a set of flags that describe the
- * features of the volume the path is on. */
-static int
-win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret,
-                   bool *supports_SetFileShortName_ret)
-{
-       wchar_t *volume;
-       BOOL bret;
-       DWORD vol_flags;
-       size_t drive_spec_len;
-       wchar_t filesystem_name[MAX_PATH + 1];
+       if (!wim_info_get_wimboot(wim->wim_info,
+                                 wim->current_image))
+               WARNING("Image is not marked as WIMBoot compatible!");
 
-       if (supports_SetFileShortName_ret)
-               *supports_SetFileShortName_ret = false;
+       ret = hash_lookup_table(ctx->common.wim,
+                               ctx->wimboot.wim_lookup_table_hash);
+       if (ret)
+               return ret;
 
-       drive_spec_len = win32_path_drive_spec_len(path);
+       return wimboot_alloc_data_source_id(wim->filename,
+                                           wim->hdr.guid,
+                                           wim->current_image,
+                                           ctx->common.target,
+                                           &ctx->wimboot.data_source_id,
+                                           &ctx->wimboot.wof_running);
+}
 
-       if (drive_spec_len == 0)
-               if (path[0] != L'\0' && path[1] == L':') /* Drive-relative path? */
-                       drive_spec_len = 2;
+/* Returns the number of wide characters needed to represent the path to the
+ * specified @dentry, relative to the target directory, when extracted.
+ *
+ * Does not include null terminator (not needed for NtCreateFile).  */
+static size_t
+dentry_extraction_path_length(const struct wim_dentry *dentry)
+{
+       size_t len = 0;
+       const struct wim_dentry *d;
 
-       if (drive_spec_len == 0) {
-               /* Path does not start with a drive letter; use the volume of
-                * the current working directory. */
-               volume = NULL;
-       } else {
-               /* Path starts with a drive letter (or \\?\ followed by a drive
-                * letter); use it. */
-               volume = alloca((drive_spec_len + 2) * sizeof(wchar_t));
-               wmemcpy(volume, path, drive_spec_len);
-               /* Add trailing backslash in case this was a drive-relative
-                * path. */
-               volume[drive_spec_len] = L'\\';
-               volume[drive_spec_len + 1] = L'\0';
-       }
-       bret = GetVolumeInformation(
-                       volume,                         /* lpRootPathName */
-                       NULL,                           /* lpVolumeNameBuffer */
-                       0,                              /* nVolumeNameSize */
-                       NULL,                           /* lpVolumeSerialNumber */
-                       NULL,                           /* lpMaximumComponentLength */
-                       &vol_flags,                     /* lpFileSystemFlags */
-                       filesystem_name,                /* lpFileSystemNameBuffer */
-                       ARRAY_LEN(filesystem_name));    /* nFileSystemNameSize */
-       if (!bret) {
-               set_errno_from_GetLastError();
-               WARNING_WITH_ERRNO("Failed to get volume information for "
-                                  "path \"%ls\"", path);
-               vol_flags = 0xffffffff;
-               goto out;
-       }
+       d = dentry;
+       do {
+               len += d->d_extraction_name_nchars + 1;
+               d = d->parent;
+       } while (!dentry_is_root(d) && will_extract_dentry(d));
 
-       if (wcsstr(filesystem_name, L"NTFS")) {
-               /* FILE_SUPPORTS_HARD_LINKS is only supported on Windows 7 and later.
-                * Force it on anyway if filesystem is NTFS.  */
-               vol_flags |= FILE_SUPPORTS_HARD_LINKS;
+       return --len;  /* No leading slash  */
+}
 
-               if (supports_SetFileShortName_ret)
-                       *supports_SetFileShortName_ret = true;
+/* Returns the length of the longest string that might need to be appended to
+ * the path to an alias of an inode to open or create a named data stream.
+ *
+ * If the inode has no named data streams, this will be 0.  Otherwise, this will
+ * be 1 plus the length of the longest-named data stream, since the data stream
+ * name must be separated form the path by the ':' character.  */
+static size_t
+inode_longest_named_data_stream_spec(const struct wim_inode *inode)
+{
+       size_t max = 0;
+       for (u16 i = 0; i < inode->i_num_ads; i++) {
+               size_t len = inode->i_ads_entries[i].stream_name_nbytes;
+               if (len > max)
+                       max = len;
        }
-
-out:
-       DEBUG("using vol_flags = %x", vol_flags);
-       *vol_flags_ret = vol_flags;
-       return 0;
+       if (max)
+               max = 1 + (max / sizeof(wchar_t));
+       return max;
 }
 
-static int
-win32_start_extract(const wchar_t *path, struct apply_ctx *ctx)
+/* Find the length, in wide characters, of the longest path needed for
+ * extraction of any file in @dentry_list relative to the target directory.
+ *
+ * Accounts for named data streams, but does not include null terminator (not
+ * needed for NtCreateFile).  */
+static size_t
+compute_path_max(struct list_head *dentry_list)
 {
-       int ret;
-       unsigned vol_flags;
-       bool supports_SetFileShortName;
-       struct win32_apply_private_data *dat = get_private_data(ctx);
+       size_t max = 0;
+       const struct wim_dentry *dentry;
 
-       ret = win32_get_vol_flags(path, &vol_flags, &supports_SetFileShortName);
-       if (ret)
-               goto err;
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               size_t len;
 
-       ctx->supported_features.archive_files = 1;
-       ctx->supported_features.hidden_files = 1;
-       ctx->supported_features.system_files = 1;
+               len = dentry_extraction_path_length(dentry);
 
-       if (vol_flags & FILE_FILE_COMPRESSION)
-               ctx->supported_features.compressed_files = 1;
+               /* Account for named data streams  */
+               len += inode_longest_named_data_stream_spec(dentry->d_inode);
 
-       if (vol_flags & FILE_SUPPORTS_ENCRYPTION) {
-               ctx->supported_features.encrypted_files = 1;
-               ctx->supported_features.encrypted_directories = 1;
+               if (len > max)
+                       max = len;
        }
 
-       ctx->supported_features.not_context_indexed_files = 1;
-
-#if 0
-       if (vol_flags & FILE_SUPPORTS_SPARSE_FILES)
-               ctx->supported_features.sparse_files = 1;
-#endif
+       return max;
+}
 
-       if (vol_flags & FILE_NAMED_STREAMS)
-               ctx->supported_features.named_data_streams = 1;
+/* Build the path at which to extract the @dentry, relative to the target
+ * directory.
+ *
+ * The path is saved in ctx->pathbuf.  */
+static void
+build_extraction_path(const struct wim_dentry *dentry,
+                     struct win32_apply_ctx *ctx)
+{
+       size_t len;
+       wchar_t *p;
+       const struct wim_dentry *d;
 
-       if (vol_flags & FILE_SUPPORTS_HARD_LINKS)
-               ctx->supported_features.hard_links = 1;
+       len = dentry_extraction_path_length(dentry);
 
-       if (vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
-               ctx->supported_features.reparse_points = 1;
-               if (func_CreateSymbolicLinkW)
-                       ctx->supported_features.symlink_reparse_points = 1;
+       ctx->pathbuf.Length = len * sizeof(wchar_t);
+       p = ctx->pathbuf.Buffer + len;
+       for (d = dentry;
+            !dentry_is_root(d->parent) && will_extract_dentry(d->parent);
+            d = d->parent)
+       {
+               p -= d->d_extraction_name_nchars;
+               wmemcpy(p, d->d_extraction_name, d->d_extraction_name_nchars);
+               *--p = '\\';
        }
+       /* No leading slash  */
+       p -= d->d_extraction_name_nchars;
+       wmemcpy(p, d->d_extraction_name, d->d_extraction_name_nchars);
+}
 
-       if (vol_flags & FILE_PERSISTENT_ACLS)
-               ctx->supported_features.security_descriptors = 1;
+/* Build the path at which to extract the @dentry, relative to the target
+ * directory, adding the suffix for a named data stream.
+ *
+ * The path is saved in ctx->pathbuf.  */
+static void
+build_extraction_path_with_ads(const struct wim_dentry *dentry,
+                              struct win32_apply_ctx *ctx,
+                              const wchar_t *stream_name,
+                              size_t stream_name_nchars)
+{
+       wchar_t *p;
 
-       if (supports_SetFileShortName)
-               ctx->supported_features.short_names = 1;
+       build_extraction_path(dentry, ctx);
 
-       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) {
+       /* Add :NAME for named data stream  */
+       p = ctx->pathbuf.Buffer + (ctx->pathbuf.Length / sizeof(wchar_t));
+       *p++ = L':';
+       wmemcpy(p, stream_name, stream_name_nchars);
+       ctx->pathbuf.Length += (1 + stream_name_nchars) * sizeof(wchar_t);
+}
 
-               ret = load_prepopulate_pats(ctx);
-               if (ret == WIMLIB_ERR_NOMEM)
-                       goto err;
+/* Build the Win32 namespace path to the specified @dentry when extracted.
+ *
+ * The path is saved in ctx->pathbuf and will be null terminated.
+ *
+ * XXX: We could get rid of this if it wasn't needed for the file encryption
+ * APIs.  */
+static void
+build_win32_extraction_path(const struct wim_dentry *dentry,
+                           struct win32_apply_ctx *ctx)
+{
+       build_extraction_path(dentry, ctx);
 
-               if (!wim_info_get_wimboot(ctx->wim->wim_info,
-                                         ctx->wim->current_image))
-                       WARNING("Image is not marked as WIMBoot compatible!");
+       /* Prepend target_ntpath to our relative path, then change \??\ into \\?\  */
 
+       memmove(ctx->pathbuf.Buffer +
+                       (ctx->target_ntpath.Length / sizeof(wchar_t)) + 1,
+               ctx->pathbuf.Buffer, ctx->pathbuf.Length);
+       memcpy(ctx->pathbuf.Buffer, ctx->target_ntpath.Buffer,
+               ctx->target_ntpath.Length);
+       ctx->pathbuf.Buffer[ctx->target_ntpath.Length / sizeof(wchar_t)] = L'\\';
+       ctx->pathbuf.Length += ctx->target_ntpath.Length + sizeof(wchar_t);
+       ctx->pathbuf.Buffer[ctx->pathbuf.Length / sizeof(wchar_t)] = L'\0';
 
-               ret = hash_lookup_table(ctx->wim, dat->wim_lookup_table_hash);
-               if (ret)
-                       goto err;
-
-               ret = wimboot_alloc_data_source_id(ctx->wim->filename,
-                                                  ctx->wim->hdr.guid,
-                                                  ctx->wim->current_image,
-                                                  path,
-                                                  &dat->data_source_id,
-                                                  &dat->wof_running);
-               if (ret)
-                       goto err;
-       }
+       wimlib_assert(ctx->pathbuf.Length >= 8 &&
+                     !wmemcmp(ctx->pathbuf.Buffer, L"\\??\\", 4));
 
-       return 0;
+       ctx->pathbuf.Buffer[1] = L'\\';
 
-err:
-       free_prepopulate_pats(dat);
-       return ret;
 }
 
-static int
-win32_finish_extract(struct apply_ctx *ctx)
+/* Returns a "printable" representation of the last relative NT path that was
+ * constructed with build_extraction_path() or build_extraction_path_with_ads().
+ *
+ * This will be overwritten by the next call to this function.  */
+static const wchar_t *
+current_path(struct win32_apply_ctx *ctx)
 {
-       free_prepopulate_pats(get_private_data(ctx));
-       return 0;
+       wchar_t *p = ctx->print_buffer;
+
+       p = wmempcpy(p, ctx->common.target, ctx->common.target_nchars);
+       *p++ = L'\\';
+       p = wmempcpy(p, ctx->pathbuf.Buffer, ctx->pathbuf.Length / sizeof(wchar_t));
+       *p = L'\0';
+       return ctx->print_buffer;
 }
 
-/* Delete a non-directory file, working around Windows quirks.  */
-static BOOL
-win32_delete_file_wrapper(const wchar_t *path)
+/*
+ * Ensures the target directory exists and opens a handle to it, in preparation
+ * of using paths relative to it.
+ */
+static int
+prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx)
 {
-       DWORD err;
-       DWORD attrib;
+       NTSTATUS status;
+       size_t path_max;
 
-       if (DeleteFile(path))
-               return TRUE;
+       /* Open handle to the target directory (possibly creating it).  */
 
-       err = GetLastError();
-       attrib = GetFileAttributes(path);
-       if ((attrib != INVALID_FILE_ATTRIBUTES) &&
-           (attrib & FILE_ATTRIBUTE_READONLY))
-       {
-               /* Try again with FILE_ATTRIBUTE_READONLY cleared.  */
-               attrib &= ~FILE_ATTRIBUTE_READONLY;
-               if (SetFileAttributes(path, attrib)) {
-                       if (DeleteFile(path))
-                               return TRUE;
-                       else
-                               err = GetLastError();
+       status = (*func_RtlDosPathNameToNtPathName_U_WithStatus)(ctx->common.target,
+                                                                &ctx->target_ntpath,
+                                                                NULL, NULL);
+       if (!NT_SUCCESS(status)) {
+               if (status == STATUS_NO_MEMORY) {
+                       return WIMLIB_ERR_NOMEM;
+               } else {
+                       ERROR("\"%ls\": invalid path name "
+                             "(status=0x%08"PRIx32")",
+                             ctx->common.target, (u32)status);
+                       return WIMLIB_ERR_INVALID_PARAM;
                }
        }
 
-       SetLastError(err);
-       return FALSE;
-}
+       ctx->attr.Length = sizeof(ctx->attr);
+       ctx->attr.ObjectName = &ctx->target_ntpath;
+
+       status = (*func_NtCreateFile)(&ctx->h_target,
+                                     FILE_TRAVERSE,
+                                     &ctx->attr,
+                                     &ctx->iosb,
+                                     NULL,
+                                     0,
+                                     FILE_SHARE_VALID_FLAGS,
+                                     FILE_OPEN_IF,
+                                     FILE_DIRECTORY_FILE |
+                                             FILE_OPEN_REPARSE_POINT |
+                                             FILE_OPEN_FOR_BACKUP_INTENT,
+                                     NULL,
+                                     0);
+
+       if (!NT_SUCCESS(status)) {
+               set_errno_from_nt_status(status);
+               ERROR_WITH_ERRNO("Can't open or create directory \"%ls\" "
+                                "(status=0x%08"PRIx32")",
+                                ctx->common.target, (u32)status);
+               return WIMLIB_ERR_OPENDIR;
+       }
 
+       path_max = compute_path_max(dentry_list);
 
-/* Create a normal file, overwriting one already present.  */
-static int
-win32_create_file(const wchar_t *path, struct apply_ctx *ctx, u64 *cookie_ret)
-{
-       HANDLE h;
+       /* Add some extra for building Win32 paths for the file encryption APIs
+        * ...  */
+       path_max += 2 + (ctx->target_ntpath.Length / sizeof(wchar_t));
 
-       /* Notes:
-        *
-        * WRITE_OWNER and WRITE_DAC privileges are required for some reason,
-        * even through we're creating a new file.
-        *
-        * FILE_FLAG_OPEN_REPARSE_POINT is required to prevent an existing
-        * reparse point from redirecting the creation of the new file
-        * (potentially to an arbitrary location).
-        *
-        * CREATE_ALWAYS could be used instead of CREATE_NEW.  However, there
-        * are quirks that would need to be handled (e.g. having to set
-        * FILE_ATTRIBUTE_HIDDEN and/or FILE_ATTRIBUTE_SYSTEM if the existing
-        * file had them specified, and/or having to clear
-        * FILE_ATTRIBUTE_READONLY on the existing file).  It's simpler to just
-        * call win32_delete_file_wrapper() to delete the existing file in such
-        * a way that already handles the FILE_ATTRIBUTE_READONLY quirk.
-        */
-retry:
-       h = CreateFile(path, WRITE_OWNER | WRITE_DAC, 0, NULL, CREATE_NEW,
-                      FILE_FLAG_BACKUP_SEMANTICS |
-                               FILE_FLAG_OPEN_REPARSE_POINT, NULL);
-       if (h == INVALID_HANDLE_VALUE) {
-               DWORD err = GetLastError();
+       ctx->pathbuf.MaximumLength = path_max * sizeof(wchar_t);
+       ctx->pathbuf.Buffer = MALLOC(ctx->pathbuf.MaximumLength);
+       if (!ctx->pathbuf.Buffer)
+               return WIMLIB_ERR_NOMEM;
+
+       ctx->attr.RootDirectory = ctx->h_target;
+       ctx->attr.ObjectName = &ctx->pathbuf;
+
+       ctx->print_buffer = MALLOC((ctx->common.target_nchars + 1 + path_max + 1) *
+                                  sizeof(wchar_t));
+       if (!ctx->print_buffer)
+               return WIMLIB_ERR_NOMEM;
 
-               if (err == ERROR_FILE_EXISTS && win32_delete_file_wrapper(path))
-                       goto retry;
-               set_errno_from_win32_error(err);
-               return WIMLIB_ERR_OPEN;
-       }
-       CloseHandle(h);
        return 0;
 }
 
-static int
-win32_create_directory(const wchar_t *path, struct apply_ctx *ctx,
-                      u64 *cookie_ret)
+/* When creating an inode that will have a short (DOS) name, we create it using
+ * the long name associated with the short name.  This ensures that the short
+ * name gets associated with the correct long name.  */
+static const struct wim_dentry *
+first_extraction_alias(const struct wim_inode *inode)
 {
-       if (!CreateDirectory(path, NULL))
-               if (GetLastError() != ERROR_ALREADY_EXISTS)
-                       goto error;
-       return 0;
-
-error:
-       set_errno_from_GetLastError();
-       return WIMLIB_ERR_MKDIR;
+       const struct list_head *next = inode->i_extraction_aliases.next;
+       const struct wim_dentry *dentry;
+
+       do {
+               dentry = list_entry(next, struct wim_dentry,
+                                   d_extraction_alias_node);
+               if (dentry_has_short_name(dentry))
+                       break;
+               next = next->next;
+       } while (next != &inode->i_extraction_aliases);
+       return dentry;
 }
 
+/*
+ * Set or clear FILE_ATTRIBUTE_COMPRESSED if the inherited value is different
+ * from the desired value.
+ *
+ * Note that you can NOT override the inherited value of
+ * FILE_ATTRIBUTE_COMPRESSED directly with NtCreateFile().
+ */
 static int
-win32_create_hardlink(const wchar_t *oldpath, const wchar_t *newpath,
-                     struct apply_ctx *ctx)
+adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
+                            struct win32_apply_ctx *ctx)
 {
-       if (!CreateHardLink(newpath, oldpath, NULL)) {
-               if (GetLastError() != ERROR_ALREADY_EXISTS)
-                       goto error;
-               if (!win32_delete_file_wrapper(newpath))
-                       goto error;
-               if (!CreateHardLink(newpath, oldpath, NULL))
-                       goto error;
-       }
-       return 0;
+       const bool compressed = (dentry->d_inode->i_attributes &
+                                FILE_ATTRIBUTE_COMPRESSED);
 
-error:
-       set_errno_from_GetLastError();
-       return WIMLIB_ERR_LINK;
-}
+       if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)
+               return 0;
 
-static int
-win32_create_symlink(const wchar_t *oldpath, const wchar_t *newpath,
-                    struct apply_ctx *ctx)
-{
-       if (!(*func_CreateSymbolicLinkW)(newpath, oldpath, 0)) {
-               if (GetLastError() != ERROR_ALREADY_EXISTS)
-                       goto error;
-               if (!win32_delete_file_wrapper(newpath))
-                       goto error;
-               if (!(*func_CreateSymbolicLinkW)(newpath, oldpath, 0))
-                       goto error;
+       if (!ctx->common.supported_features.compressed_files)
+               return 0;
+
+       FILE_BASIC_INFORMATION info;
+       NTSTATUS status;
+       USHORT compression_state;
+
+       /* Get current attributes  */
+       status = (*func_NtQueryInformationFile)(h, &ctx->iosb,
+                                               &info, sizeof(info),
+                                               FileBasicInformation);
+       if (NT_SUCCESS(status) &&
+           compressed == !!(info.FileAttributes & FILE_ATTRIBUTE_COMPRESSED))
+       {
+               /* Nothing needs to be done.  */
+               return 0;
        }
-       return 0;
 
-error:
-       set_errno_from_GetLastError();
-       return WIMLIB_ERR_LINK;
+       /* Set the new compression state  */
+
+       if (compressed)
+               compression_state = COMPRESSION_FORMAT_DEFAULT;
+       else
+               compression_state = COMPRESSION_FORMAT_NONE;
+
+       status = (*func_NtFsControlFile)(h,
+                                        NULL,
+                                        NULL,
+                                        NULL,
+                                        &ctx->iosb,
+                                        FSCTL_SET_COMPRESSION,
+                                        &compression_state,
+                                        sizeof(USHORT),
+                                        NULL,
+                                        0);
+       if (NT_SUCCESS(status))
+               return 0;
+
+       set_errno_from_nt_status(status);
+       ERROR_WITH_ERRNO("Can't %s compression attribute on \"%ls\" "
+                        "(status=0x%08"PRIx32")",
+                        (compressed ? "set" : "clear"),
+                        current_path(ctx), status);
+       return WIMLIB_ERR_SET_ATTRIBUTES;
 }
 
+/*
+ * Clear FILE_ATTRIBUTE_ENCRYPTED if the file or directory is not supposed to be
+ * encrypted.
+ *
+ * You can provide FILE_ATTRIBUTE_ENCRYPTED to NtCreateFile() to set it on the
+ * created file.  However, the file or directory will otherwise default to the
+ * encryption state of the parent directory.  This function works around this
+ * limitation by using DecryptFile() to remove FILE_ATTRIBUTE_ENCRYPTED on files
+ * (and directories) that are not supposed to have it set.
+ *
+ * Regardless of whether it succeeds or fails, this function may close the
+ * handle to the file.  If it does, it sets it to NULL.
+ */
 static int
-win32_extract_wim_chunk(const void *buf, size_t len, void *arg)
+maybe_clear_encryption_attribute(HANDLE *h_ret, const struct wim_dentry *dentry,
+                                struct win32_apply_ctx *ctx)
 {
-       HANDLE h = (HANDLE)arg;
-       DWORD nbytes_written;
+       if (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
+               return 0;
 
-       if (unlikely(!WriteFile(h, buf, len, &nbytes_written, NULL)))
-               goto error;
-       if (unlikely(nbytes_written != len))
-               goto error;
-       return 0;
+       if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)
+               return 0;
 
-error:
-       set_errno_from_GetLastError();
-       return WIMLIB_ERR_WRITE;
-}
+       if (!ctx->common.supported_features.encrypted_files)
+               return 0;
 
-static int
-win32_extract_stream(const wchar_t *path, const wchar_t *stream_name,
-                    size_t stream_name_nchars,
-                    struct wim_lookup_table_entry *lte, struct apply_ctx *ctx)
-{
-       DWORD creationDisposition = OPEN_EXISTING;
-       wchar_t *stream_path = (wchar_t*)path;
-       HANDLE h;
-       int ret;
+       FILE_BASIC_INFORMATION info;
+       NTSTATUS status;
+       BOOL bret;
 
-       if (stream_name_nchars) {
-               creationDisposition = CREATE_ALWAYS;
-               stream_path = alloca(sizeof(wchar_t) *
-                                    (wcslen(path) + 1 +
-                                     wcslen(stream_name) + 1));
-               tsprintf(stream_path, L"%ls:%ls", path, stream_name);
+       /* Get current attributes  */
+       status = (*func_NtQueryInformationFile)(*h_ret, &ctx->iosb,
+                                               &info, sizeof(info),
+                                               FileBasicInformation);
+       if (NT_SUCCESS(status) &&
+           !(info.FileAttributes & FILE_ATTRIBUTE_ENCRYPTED))
+       {
+               /* Nothing needs to be done.  */
+               return 0;
        }
 
-       h = CreateFile(stream_path, FILE_WRITE_DATA, 0, NULL,
-                      creationDisposition, FILE_FLAG_BACKUP_SEMANTICS |
-                                           FILE_FLAG_OPEN_REPARSE_POINT,
-                      NULL);
-       if (h == INVALID_HANDLE_VALUE) {
-               set_errno_from_GetLastError();
-               ret = WIMLIB_ERR_OPEN;
-               goto out;
-       }
+       /* Set the new encryption state  */
 
-       if (!lte) {
-               ret = 0;
-               goto out_close_handle;
-       }
+       /* Due to Windows' crappy file encryption APIs, we need to close the
+        * handle to the file so we don't get ERROR_SHARING_VIOLATION.  We also
+        * hack together a Win32 path, although we will use the \\?\ prefix so
+        * it will actually be a NT path in disguise...  */
+       (*func_NtClose)(*h_ret);
+       *h_ret = NULL;
 
-       if (!SetFilePointerEx(h,
-                             (LARGE_INTEGER) { .QuadPart = lte->size},
-                             NULL,
-                             FILE_BEGIN))
-               goto write_error;
-
-       if (!SetEndOfFile(h))
-               goto write_error;
-
-       if (!SetFilePointerEx(h,
-                             (LARGE_INTEGER) { .QuadPart = 0},
-                             NULL,
-                             FILE_BEGIN))
-               goto write_error;
-
-       ret = extract_stream(lte, lte->size, win32_extract_wim_chunk, h);
-       goto out_close_handle;
-
-write_error:
-       set_errno_from_GetLastError();
-       ret = WIMLIB_ERR_WRITE;
-
-out_close_handle:
-       if (!CloseHandle(h)) {
-               if (!ret) {
-                       set_errno_from_GetLastError();
-                       ret = WIMLIB_ERR_WRITE;
-               }
+       build_win32_extraction_path(dentry, ctx);
+
+       bret = DecryptFile(ctx->pathbuf.Buffer, 0);
+
+       /* Restore the NT namespace path  */
+       build_extraction_path(dentry, ctx);
+
+       if (!bret) {
+               DWORD err = GetLastError();
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Can't decrypt file \"%ls\" (err=%"PRIu32")",
+                                 current_path(ctx), (u32)err);
+               return WIMLIB_ERR_SET_ATTRIBUTES;
        }
-out:
-       return ret;
+       return 0;
 }
 
+/* Set the short name on the open file @h which has been created at the location
+ * indicated by @dentry.
+ *
+ * Note that this may add, change, or remove the short name.
+ *
+ * @h must be opened with DELETE access.
+ *
+ * Returns 0 or WIMLIB_ERR_SET_SHORT_NAME.  The latter only happens in
+ * STRICT_SHORT_NAMES mode.
+ */
 static int
-win32_extract_unnamed_stream(file_spec_t file,
-                            struct wim_lookup_table_entry *lte,
-                            struct apply_ctx *ctx,
-                            struct wim_dentry *dentry)
+set_short_name(HANDLE h, const struct wim_dentry *dentry,
+              struct win32_apply_ctx *ctx)
 {
-       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT
-           && lte
-           && lte->resource_location == RESOURCE_IN_WIM
-           && lte->rspec->wim