]> wimlib.net Git - wimlib/commitdiff
Merge branch with pipable WIM support
authorEric Biggers <ebiggers3@gmail.com>
Tue, 13 Aug 2013 06:19:23 +0000 (01:19 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Tue, 13 Aug 2013 06:19:23 +0000 (01:19 -0500)
Many changes; see changes to NEWS for additional details.

78 files changed:
Makefile.am
NEWS
README
archlinux/PKGBUILD
configure.ac
debian/changelog
doc/imagex-apply.1.in
doc/imagex-capture.1.in
doc/imagex-delete.1.in
doc/imagex-dir.1.in
doc/imagex-export.1.in
doc/imagex-extract.1.in
doc/imagex-info.1.in
doc/imagex-join.1.in
doc/imagex-mount.1.in
doc/imagex-optimize.1.in
doc/imagex-split.1.in
doc/imagex-update.1.in
doc/imagex.1.in
doc/mkwinpeimg.1.in
include/wimlib.h
include/wimlib/apply.h
include/wimlib/dentry.h
include/wimlib/file_io.h
include/wimlib/header.h
include/wimlib/integrity.h
include/wimlib/lookup_table.h
include/wimlib/reparse.h
include/wimlib/resource.h
include/wimlib/wim.h
include/wimlib/win32_common.h
include/wimlib/write.h
include/wimlib/xml.h
include/wimlib_tchar.h
make-release
programs/imagex-win32.c
programs/imagex-win32.h
programs/imagex.c
rpm/wimlib-without-fuse-or-ntfs-3g.spec
rpm/wimlib.spec
src/add_image.c
src/decompress.c
src/delete_image.c
src/dentry.c
src/export_image.c
src/extract.c
src/file_io.c
src/hardlink.c
src/header.c
src/integrity.c
src/join.c
src/lookup_table.c
src/lzx-compress.c
src/lzx-decompress.c
src/metadata_resource.c
src/mount_image.c
src/ntfs-3g_apply.c
src/paths.c
src/reparse.c
src/resource.c
src/security.c
src/split.c
src/unix_apply.c
src/update_image.c
src/util.c
src/verify.c
src/wim.c
src/win32_apply.c
src/win32_capture.c
src/win32_common.c
src/win32_replacements.c
src/write.c
src/xml.c
src/xpress-compress.c
src/xpress-decompress.c
tests/test-imagex
tests/test-imagex-capture_and_apply
tests/tests-common.sh

index 8f6dc96bb38ed3ad9b211fe4ca686a04740ed84d..b6d4da951ebddbc1a8d9009e1a2fb52478e468cc 100644 (file)
@@ -15,7 +15,7 @@ endif
 
 lib_LTLIBRARIES = libwim.la
 
-libwim_la_LDFLAGS = -version-info 8:0:1 $(WINDOWS_LDFLAGS)
+libwim_la_LDFLAGS = -version-info 9:0:0 $(WINDOWS_LDFLAGS)
 
 libwim_la_SOURCES =            \
        src/add_image.c         \
diff --git a/NEWS b/NEWS
index 1d8e64388e16b25cdef2497ac714efa31316ce91..d0d257530e12e5120c4e8340f5225e9070b98e31 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,35 @@
 Only the most important changes more recent than version 0.6 are noted here.
 
+Version 1.5.0:
+       Added support for "pipable" WIMs.  This support allows capturing images
+       to standard output or applying images from standard input, but they are
+       not compatible with Microsoft's software and are not created by default.
+       See documentation for --pipable flag of `wimlib-imagex capture' for more
+       information.  Two new functions have been added to the library to fully
+       support this: wimlib_write_to_fd() and wimlib_extract_image_from_pipe().
+
+       wimlib now preserve WIM integrity tables by default, even if
+       WIMLIB_WRITE_FLAG_CHECK_INTEGRITY is not specified.  This affects
+       wimlib-imagex's behavior when --check is not specified.
+
+       Security descriptors are now extracted correctly on Windows.
+
+       `mkwinpeimg' now supports grabbing files from the WAIK supplement rather
+       than the WAIK itself.
+
+       The test suite no longer fails when run in a locale where the decimal
+       separator is not a period.
+
+       WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY has been removed and
+       WIMLIB_EXTRACT_FLAG_VERBOSE re-reserved for future use.
+
+       The extraction code has been rewritten and it will now be easier to
+       support new features on all supported backends (currently Win32, UNIX,
+       and NTFS-3g).  For example, hard-linked extraction mode (--hardlink) is
+       now supported on all backends, not just UNIX.
+
+       A few changes were made to the error codes returned by library routines.
+
 Version 1.4.2:
        Fixed bug in `wimlib-imagex export' that made it impossible to export an
        image from a WIM that is readonly at the filesystem level.
diff --git a/README b/README
index 881833b836569018485a0f7f4b7c430255d6468d..f473d725310359216d42c4eed0c1627a551ce4d5 100644 (file)
--- a/README
+++ b/README
@@ -1,6 +1,6 @@
                                   INTRODUCTION
 
-This is wimlib version 1.4.2 (June 2013).  wimlib is a C library for creating,
+This is wimlib version 1.5.0 (August 2013).  wimlib is a C library for creating,
 modifying, extracting, and mounting files in the Windows Imaging Format (WIM
 files).  These files are normally created by using the `imagex.exe' utility on
 Windows, but wimlib is distributed with a free implementation of ImageX called
index 837caf692aa16feca82e386b0390a582e4508211..2887ca68398407615635ca6ab534defd8b3df4ff 100644 (file)
@@ -1,7 +1,7 @@
 # Maintainer:  Eric Biggers <ebiggers3 at gmail dot com>
 
 pkgname=wimlib
-pkgver=1.4.2
+pkgver=1.5.0
 pkgrel=1
 pkgdesc="A library to extract, create, and modify WIM files"
 arch=("i686" "x86_64")
index 50ca230a7d6eea3dfed3212bf0ba98ae33cfd77e..8d96cc370095a534e2bb24802990bae9204f3593 100644 (file)
@@ -1,4 +1,4 @@
-AC_INIT([wimlib], [1.4.2], [ebiggers3@gmail.com])
+AC_INIT([wimlib], [1.5.0], [ebiggers3@gmail.com])
 AC_CONFIG_SRCDIR([src/wim.c])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
index f7aaf1fb219576a86b96f5cd0551c58afc51bf55..0990ed1c6c3f54a2de439e1f0d87112c2043bdf2 100644 (file)
@@ -1,3 +1,9 @@
+wimlib (1.5.0-1) unstable; urgency=low
+
+  * Update to v1.5.0
+
+ -- Eric Biggers <ebiggers3@gmail.com>  Tue, 13 Aug 2013 00:58:52 -0500
+
 wimlib (1.4.2-1) unstable; urgency=low
 
   * Update to v1.4.2
index 439a3bde9805a220256121dd3f6b84ecb0a07895..4b1a79aaaa71486284b04028797692d1e9dc0674 100644 (file)
@@ -1,11 +1,12 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-apply \- Extract one image, or all images, from a WIM archive
 .SH SYNOPSIS
 \fB@IMAGEX_PROGNAME@ apply\fR \fIWIMFILE\fR [\fIIMAGE\fR] \fITARGET\fR [\fIOPTION\fR...]
 .SH DESCRIPTION
 \fB@IMAGEX_PROGNAME@ apply\fR extracts an image, or all images, from the Windows
-Imaging (WIM) file \fIWIMFILE\fR.
+Imaging (WIM) file \fIWIMFILE\fR.  \fIWIMFILE\fR may be "-" to read the WIM from
+standard input, but see \fBPIPABLE WIMS\fR.
 .PP
 This command is designed to extract, or "apply", one or more full WIM images.
 If you instead want to extract only certain files or directories contained in a
@@ -237,6 +238,29 @@ To apply the first image of this split WIM to the directory "dir", run:
 @IMAGEX_PROGNAME@ apply mywim.swm 1 dir --ref="mywim*.swm"
 .RE
 .PP
+As a special case, if you are applying an image from standard input from a split
+WIM that is also pipable (as described in \fBPIPABLE WIMS\fR), the \fB--ref\fR
+option is unneeded; instead you must ensure that all the split WIM parts are
+concatenated together on standard input.  They can be provided in any other,
+with the exception of the first part, which must be first.
+.SH PIPABLE WIMS
+As of wimlib 1.5.0, \fB@IMAGEX_PROGNAME@ apply\fR supports applying a WIM from a
+nonseekable file, such as a pipe, provided that the WIM was captured with
+\fB--pipable\fR (see \fB@IMAGEX_PROGNAME@ capture\fR(1)).  To use standard input
+as the WIM, specify "-" as \fIWIMFILE\fR.  A useful use of this ability is to
+apply an image from a WIM while streaming it from a webserver; for example, to
+apply the first image from a WIM file to a NTFS volume on /dev/sda1:
+.PP
+.RS
+wget -O - http://myserver/mywim.wim | @IMAGEX_PROGNAME@ apply - 1 /dev/sda1
+.RE
+.PP
+Note: WIM files are \fInot\fR pipable by default; you have to explicitly capture
+them with \fB--pipable\fR, and they are \fInot\fR compatible with Microsoft's
+software.  See \fB@IMAGEX_PROGNAME@ capture\fR(1) for more information.
+.PP
+It is possible to apply an image from a pipable WIM split into multiple parts;
+see \fBSPLIT WIMS\fR.
 .SH OPTIONS
 .TP 6
 \fB--check\fR
@@ -266,12 +290,12 @@ Print the path to of each file or directory within the WIM image as it is
 extracted.
 .TP
 \fB--hardlink\fR
-(UNIX only) 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.  This option is not available in the NTFS extraction mode.
+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.
+This option is not available in the NTFS extraction mode.
 .TP
 \fB--symlink\fR
 (UNIX only) This option is similar to \fB--hardlink\fR, except symbolic links are created
@@ -330,9 +354,6 @@ Microsoft's ImageX does.  Please note that this is separate from the integrity
 table of the WIM, which provides SHA1 message digests over raw chunks of the
 entire WIM file and is checked separately if the \fB--check\fR option is
 specified.
-.PP
-You cannot use \fB@IMAGEX_PROGNAME@ apply\fR to apply a WIM from a pipe (such as standard
-input) because the WIM file format is not designed for this.
 .SH EXAMPLES
 .SS Applying a WIM image to a directory (both UNIX and Windows)
 Extract the first image from the Windows PE image from the Windows Vista/7/8
index 8930fe42eac51f1a9a4b67531b74d204c05b2bf0..cb651651d69c37349fb0434985e9cb74aad6b563 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-capture, @IMAGEX_PROGNAME@-append \- Create or append a WIM image
 .SH SYNOPSIS
@@ -31,6 +31,10 @@ which a WIM image is to be captured.
 the new image.  If \fIIMAGE_NAME\fR is not given, it is taken to be the same as
 the base name of \fISOURCE\fR.  If \fIIMAGE_DESCRIPTION\fR is not given, no
 description is given to the new image.
+.PP
+As a special case, if \fIWIMFILE\fR is "-", the \fB--pipable\fR option is
+assumed and the WIM file is written to standard output in a special pipable
+format.   See the documentation for \fB--pipable\fR for more details.
 .SH NORMAL MODE (UNIX)
 This section documents how files are captured from a directory on UNIX.  See
 \fBWINDOWS VERSION\fR for the corresponding documentation for the Windows
@@ -131,9 +135,13 @@ anyway).
 Specifies that the new image is to be made the bootable image of the WIM archive.
 .TP
 \fB--check\fR
-Include an integrity table in the new WIM file or the modified WIM file.  If
-this option is not specified, no integrity table is included in \fIWIMFILE\fR,
-even if there was one before in the case of \fB@IMAGEX_PROGNAME@ append\fR.
+For \fB@IMAGEX_PROGNAME@ append\fR, check the integrity of \fIWIMFILE\fR if an
+integrity was present.  Furthermore, include an integrity table in the new WIM
+file (\fB@IMAGEX_PROGNAME@ capture\fR) or the modified WIM file
+(\fB@IMAGEX_PROGNAME@ append\fR).  If this option is not specified, no integrity
+table is included in a WIM file created with \fB@IMAGEX_PROGNAME@ capture\fR,
+while a WIM file updated with \fB@IMAGEX_PROGNAME@ append\fR will be written
+with an integrity table if and only if one was present before.
 .TP
 \fB--compress\fR=\fITYPE\fR
 Specifies the compression type for the new WIM file.  This flag is only valid
@@ -220,7 +228,8 @@ used:
 is done by adding a special alternate data stream to each directory entry that
 contains this information.  Please note that this flag is for convenience only,
 in case you want to use \fB@IMAGEX_PROGNAME@\fR to archive files on UNIX.
-Microsoft's software will not understand this special information.
+Microsoft's software will not understand this special information.  You also may
+run into problems when applying an image with UNIX data from a pipable WIM.
 .TP
 \fB--no-acls\fR
 Do not capture files' security descriptors.  This option is available in the
@@ -300,6 +309,43 @@ input rather than an external file.
 .IP ""
 The NTFS capture mode cannot be used with \fB--source-list\fR, as only capturing
 a full NTFS volume is supported.
+.TP
+\fB--pipable\fR
+Create a "pipable" WIM, which can be applied fully sequentially, including from
+a pipe.  An image in the resulting WIM can be applied with \fB@IMAGEX_PROGNAME@
+apply\fR, either normally by specifying the WIM file name, or with
+\fB@IMAGEX_PROGNAME@ apply -\fR to read the WIM from standard input.  See
+\fB@IMAGEX_PROGNAME@ apply\fR(1) for more details.
+.IP ""
+For append operations, this option will result in a full rebuild of the WIM to
+make it pipable.  For capture operations, the captured WIM is simply created as
+pipable.  Beware that the more images you add to a pipable WIM, the less
+efficient piping it will be, since more unneeded data will be sent through the
+pipe.
+.IP ""
+When wimlib creates a pipable WIM, it carefully re-arranges the components of
+the WIM so that they can be read sequentially, and also makes several other
+modifications.  As a result, these "pipable" WIMs are \fInot compatible with
+Microsoft's software\fR, so keep this in mind if you're going to use them.  If
+desired, you can use \fB@IMAGEX_PROGNAME@ optimize --not-pipable\fR to re-write
+a pipable WIM as a regular WIM.  (\fB@IMAGEX_PROGNAME@ export\fR also provides
+the capability to export images from a pipable WIM into a non-pipable WIM, or
+vice versa.)
+.IP ""
+For the most part, wimlib operates on pipable WIMs transparently.  You can
+modify them, add or delete images, export images, and even create split pipable
+WIMs.  The main disadvantages are that appending is (currently) less efficient,
+and also they aren't compatible with Microsoft's software.
+.IP ""
+\fB@IMAGEX_PROGNAME@ capture\fR and \fB@IMAGEX_PROGNAME@ append\fR can both
+write a pipable WIM directly to standard output; this is done automatically if
+\fIWIMFILE\fR is specified as "-".  (In that case, \fB--pipable\fR is assumed.)
+.TP
+\fB--not-pipable\fR
+Ensure the resulting WIM is in the normal, non-pipable WIM format.  This is the
+default for \fB@IMAGEX_PROGNAME@ capture\fR, except when writing to standard
+output, and for \fB@IMAGEX_PROGNAME@ append\fR, except when appending to a WIM
+that is already pipable.
 .SH NOTES
 \fB@IMAGEX_PROGNAME@ append\fR does not support appending an image to a split WIM.
 .PP
@@ -308,9 +354,9 @@ specify a special WIM format.  A WIM file can contain images captured using
 different modes.  However, all images in a WIM must have the same compression
 type, and \fB@IMAGEX_PROGNAME@\fR always enforces this.
 .PP
-\fB@IMAGEX_PROGNAME@\fR writes WIMs having the version number 0x10d00 and a compressed
-stream chunk size of 32768.  The only WIMs I've seen that are different from
-this are some pre-Vista WIMs that had a different version number.
+\fB@IMAGEX_PROGNAME@\fR writes WIMs having the version number 0x10d00 and a
+compressed stream chunk size of 32768.  The only WIMs I've seen that are
+different from this are some pre-Vista WIMs that had a different version number.
 .PP
 It is safe to abort an \fB@IMAGEX_PROGNAME@ append\fR command partway through;
 however, after doing this, it is recommended to run \fB@IMAGEX_PROGNAME@
@@ -349,5 +395,12 @@ integrity table to be discarded.
 @IMAGEX_PROGNAME@ append /dev/sda2 mywim.wim --check "Windows 7"
 .RE
 .PP
+Capture a WIM from a NTFS volume and write it directly to standard output, using
+the wimlib-specific pipable WIM format:
+.RS
+.PP
+@IMAGEX_PROGNAME@ capture /dev/sda1 -
+.RE
+.PP
 .SH SEE ALSO
 .BR @IMAGEX_PROGNAME@ (1)
index 3dfd674090444f35094796ccf4bde6743440b2a5..b6158c4ba65a3427606b537862299f27f866e25b 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-delete \- Delete an image from a WIM archive
 .SH SYNOPSIS
@@ -25,9 +25,11 @@ WIM and have a WIM with 0 images, although such a file wouldn't be very useful.
 .TP 6
 \fB--check\fR
 When reading \fIWIMFILE\fR, verify its integrity if the integrity table is
-present; additionally, when rewriting \fIWIMFILE\fR after the specified image was
-deleted, write an integrity table.  If this option is not specified, no integrity
-table is included in the new WIM file, even if there was one before.
+present; additionally, when rewriting \fIWIMFILE\fR after the specified image
+was deleted, always write an integrity table.  If this option is not specified,
+the integrity of \fIWIMFILE\fR will not be checked when it's opened, but an
+integrity table will be written in the updated WIM if and only if one was
+present before.
 .TP 6
 \fB--soft\fR
 Perform a "soft delete".  Specifying this flag overrides the default behavior of
index 92534bdca7636a65f52760b72c3010b7922854d0..a000340a8950b6ae4ee99d679adb64abaa961d9d 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-dir \- Show the files contained in a WIM archive
 .SH SYNOPSIS
index ee071ea810801de46e1f910154700d298bddd8d7..daca7ea19cf1926e20cac2335065fb536402ea0e 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-export \- Exports an image from a WIM archive to an existing or new WIM archive
 .SH SYNOPSIS
@@ -29,6 +29,10 @@ image being exported to \fIDEST_WIMFILE\fR.  The default is its description in
 from split WIMs.  However, you cannot export an image to a split WIM.  See
 \fBSPLIT WIMS\fR.
 .PP
+\fB@IMAGEX_PROGNAME@ export\fR also supports exporting images from a non-pipable
+WIM into a (possibly new) pipable WIM, and vice versa.  See \fB--pipable\fR and
+\fB--not-pipable\fR.
+.PP
 .SH OPTIONS
 .TP 6
 \fB--boot\fR
@@ -43,9 +47,17 @@ error.
 \fB--check\fR
 When reading \fISRC_WIMFILE\fR, and \fIDEST_WIMFILE\fR if it exists, verify the
 file's integrity if the integrity table is present; additionally, when writing
-\fIDEST_WIMFILE\fR with the new image added, write an integrity table.  If this
-option is not specified, no integrity table is included in \fIDEST_WIMFILE\fR,
-even if there was one before.
+\fIDEST_WIMFILE\fR with the new image(s) added, write an integrity table.
+If neither \fB--check\fR nor \fB--nocheck\fR is specified, an integrity
+table is included in \fIDEST_WIMFILE\fR if and only if \fIDEST_WIMFILE\fR
+already existed and it had an integrity table before.
+.TP
+\fB--nocheck\fR
+When writing \fIDEST_WIMFILE\fR with the new image(s) added, do not write an
+integrity table.
+If neither \fB--check\fR nor \fB--nocheck\fR is specified, an integrity
+table is included in \fIDEST_WIMFILE\fR if and only if \fIDEST_WIMFILE\fR
+already existed and it had an integrity table before.
 .TP
 \fB--compress\fR=\fITYPE\fR
 Specifies the compression type for \fIDEST_WIMFILE\fR.  This is only valid if
@@ -74,6 +86,18 @@ little bit of space that would otherwise be left as a hole in the WIM.  Also see
 \fB--ref\fR="\fIGLOB\fR"
 File glob of additional split WIM parts that are part of the split WIM being
 exported.  See \fBSPLIT_WIMS\fR.
+.TP
+\fB--pipable\fR
+Build, or rebuild, \fIDEST_WIMFILE\fR as a "pipable WIM" so that it can be
+applied fully sequentially, including from a pipe.  See \fB@IMAGEX_PROGNAME@
+capture\fR(1) for more details about creating pipable WIMs.  The default without
+this option is to make \fIDEST_WIMFILE\fR pipable if and only if it already
+existed and was already pipable.
+.TP
+\fB--not-pipable\fR
+Build, or rebuld, \fIDEST_WIMFILE\fR as a normal, non-pipable WIM.  This is the
+default behavior, unless \fIDEST_WIMFILE\fR already existed and was already
+pipable.
 .SH SPLIT WIMS
 You may use \fB@IMAGEX_PROGNAME@ export\fR to export images from a split WIM.  The
 \fISRC_WIMFILE\fR argument is used to specify the first part of the split WIM, and
index b6f7bdce6024f2f6fd697158fb6461b4f4c6e7f2..dee6eb250e3fd8b2984d359a0e481c31a9973d75 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-extract \- Extract files or directories from a WIM image
 .SH SYNOPSIS
index 59c12afe3079f112ee4da06ec70eac647a92d3eb..b3b4a5b7142f44d57332b341867434674918846d 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-info \- Display information about a WIM file, or change information about
 an image
@@ -33,8 +33,14 @@ archive.
 When reading \fIWIMFILE\fR, verify its integrity if the integrity table is
 present; additionally if an action that requires modifying the WIM archive is
 specified, include an integrity table in the modified WIM.  If this option is
-not specified and \fIWIMFILE\fR is modified, no integrity table is included in
-the modified WIM, even if there was one before.
+not specified and \fIWIMFILE\fR is modified, an integrity table will be included
+in the modified WIM if and only if there was one before.
+.TP
+\fB--nocheck\fR
+If an action that requires modifying the WIM archive is specified, do not
+include an integrity table in the modified WIM.  If this option is not specified
+and \fIWIMFILE\fR is modified, an integrity table will be included in the
+modified WIM if and only if there was one before.
 .TP
 \fB--extract-xml\fR=\fIFILE\fR
 Extracts the raw data from the XML resource in the WIM file to \fIFILE\fR.
index 048adf979ee96b51787096e789cb80b025e35093..f2d2c3626ab69488f05462c7d1b60758b209bb60 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-join \- Join split WIMs into a standalone one-part WIM
 .SH SYNOPSIS
@@ -8,13 +8,16 @@ Joins the \fISPLIT_WIMs\fR into a standalone one-part WIM \fIOUT_WIMFILE\fR.
 .PP
 All parts of the split WIM  must be specified.  You probably want to do so using
 a shell wildcard.
+.PP
+\fB@IMAGEX_PROGNAME@ join\fR can join both non-pipable and pipable split WIMs.
 .SH OPTIONS
 .TP 6
 \fB--check\fR
-When reading each \fISPLIT_WIM\fR, verify its integrity if the integrity table is
-present; additionally, when writing \fIOUT_WIMFILE\fR,
-write an integrity table.  If this option is not specified, no integrity table
-is included in the new WIM file, even if there was one before.
+When reading each \fISPLIT_WIM\fR, verify its integrity if the integrity table
+is present; additionally, when writing \fIOUT_WIMFILE\fR, write an integrity
+table.  If this option is not specified, an integrity table will be included in
+\fIOUT_WIMFILE\fR if and only if one was present in the first part of the split
+WIM.
 .SH EXAMPLES
 Join a split WIM, with the parts named `windows*.swm' where the * is anything
 (usually the number of the part, except for the first part which may have no
index 2a0873278f0979c6d4eefa51bd2768f076e6de0f..6c040f7a80eb12addf7d334167c7adce3484dfc2 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-mount, @IMAGEX_PROGNAME@-mountrw, @IMAGEX_PROGNAME@-unmount \- Mount and unmount an image from a WIM archive
 .SH SYNOPSIS
@@ -140,7 +140,8 @@ mount is read-only.
 .TP
 \fB--check\fR
 When writing \fIWIMFILE\fR, include an integrity table.  Has no effect if the
-mount is read-only or if \fB--commit\fR was not specified.
+mount is read-only or if \fB--commit\fR was not specified.  The default behavior
+is to include an integrity table if and only if there was one present before.
 .TP
 \fB--rebuild\fR
 Rebuild the entire WIM rather than appending any new data to the end of it.
index 9a575aa87037d8aff6e6c380b3b1cf53fc280718..aa3b947d92c06c8564da3be8a2e555ef973657ea 100644 (file)
@@ -1,8 +1,8 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-optimize \- Optimize a WIM archive
 .SH SYNOPSIS
-\fB@IMAGEX_PROGNAME@ optimize\fR \fIWIMFILE\fR [--check] [--recompress]
+\fB@IMAGEX_PROGNAME@ optimize\fR \fIWIMFILE\fR [\fIOPTION\fR...]
 .SH DESCRIPTION
 \fB@IMAGEX_PROGNAME@ optimize\fR will rebuild the stand-alone WIM \fIWIMFILE\fR.  The new
 WIM is written to a temporary file, and it is renamed to the original file when
@@ -13,10 +13,15 @@ In addition, some errors in the original WIM may be fixed by re-writing it
 .SH OPTIONS
 .TP 6
 \fB--check\fR
-When reading \fIWIMFILE\fR, verify its integrity if the integrity table is
-present; in addition, include an integrity table in the optimized WIM.  If this
-option is not specified, no integrity table is included in the optimized WIM,
-even if there was one in the original WIM.
+When reading \fIWIMFILE\fR, verify its integrity if an integrity table is
+present.  In addition, include an integrity table in the optimized WIM.  If this
+option is not specified, by default the integrity table (if present) is not
+checked, but an integrity table is included in the new WIM if there was one in
+the old WIM.
+.TP
+\fB--nocheck\fR
+Neither verify the integrity of \fIWIMFILE\fR using the integrity table, nor
+include an integrity table in the rebuilt WIM file.
 .TP
 \fB--recompress\fR
 Recompress all compressed streams in \fIWIMFILE\fR when rebuilding it.  This
@@ -36,13 +41,24 @@ created on Windows with Microsoft's ImageX.
 Number of threads to use for compressing data.  Default: autodetect (number of
 processors).  This parameter is only meaningful when \fB--recompress\fR is also
 specified.
+.TP
+\fB--pipable\fR
+Rebuild the WIM that it can be applied fully sequentially, including from a
+pipe.  See \fB@IMAGEX_PROGNAME@ capture\fR(1) for more details about creating
+pipable WIMs.  By default, when neither \fB--pipable\fR or \fB--not-pipable\fR
+is specified, the rebuilt WIM will be pipable if and only if it was already
+pipable.
+.TP
+\fB--not-pipable\fR
+Rebuild the WIM in the non-pipable format.  (This is the default if
+\fIWIMFILE\fR is not pipable.)
 .SH NOTES
 \fB@IMAGEX_PROGNAME@ optimize\fR does not support split WIMs.
 .PP
 \fB@IMAGEX_PROGNAME@ optimize\fR is roughly equivalent to:
 .RS
 .PP
-\fB@IMAGEX_PROGNAME@ export\fR \fIWIMFILE\fR all tmp.wim [--check] && mv tmp.wim \fIWIMFILE\fR
+\fB@IMAGEX_PROGNAME@ export\fR \fIWIMFILE\fR all tmp.wim && mv tmp.wim \fIWIMFILE\fR
 .RE
 .PP
 .SH SEE ALSO
index fa53ee2a55b1f72ef704dba1831678b5676ef6d2..5c21576d2bf18f6f3f057229b51ce0f4a6bb846c 100644 (file)
@@ -1,19 +1,21 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-split \- Split a WIM into multiple parts
 .SH SYNOPSIS
-\fB@IMAGEX_PROGNAME@ split\fR \fIWIMFILE\fR \fISPLIT_WIMFILE\fR \fIPART_SIZE\fR [\fIOPTION...\fR]
+\fB@IMAGEX_PROGNAME@ split\fR \fIWIMFILE\fR \fISPLIT_WIM_PART\fR \fIPART_SIZE\fR [\fIOPTION...\fR]
 .SH DESCRIPTION
 Splits \fIWIMFILE\fR into parts with size at most \fIPART_SIZE\fR mebibytes,
-with the first part having the name \fISPLIT_WIMFILE\fR and the other parts
+with the first part having the name \fISPLIT_WIM_PART\fR and the other parts
 having names numbered in order of the parts.
+.PP
+\fB@IMAGEX_PROGNAME@ split\fR can split both non-pipable and pipable WIMs.
 .SH OPTIONS
 .TP 6
 \fB--check\fR
 When reading \fIWIMFILE\fR, verify its integrity if the integrity table is
-present; additionally, when writing each \fISPLIT_WIMFILE\fR, write an integrity
-table.  If this option is not specified, no integrity tables are included in the
-split WIM files, even if there was one in the original WIM.
+present; additionally, when writing each \fISPLIT_WIM_PART\fR, write an integrity
+table.  If this option is not specified, integrity tables will be included in
+the split WIMs if and only if one was present in \fIWIMFILE\fR.
 .SH EXAMPLES
 Splits the WIM 'windows.wim' into 'windows.swm', 'windows2.swm', 'windows3.swm',
 etc. where each part is at most 100 MiB:
index 77fecc397ad11f48ccb95905d99f53ddbf412cb5..fe2ef9b2b1023d5bb7b8406ab4749fe499026998 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX "1" "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX "1" "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@-update \- Update a WIM image
 .SH SYNOPSIS
@@ -113,8 +113,8 @@ Use \fB--recursive\fR for all \fBdelete\fR commands.
 \fB--check\fR
 When reading \fIWIMFILE\fR, verify its integrity if the integrity table is
 present; in addition, include an integrity table in the updated WIM.  If this
-option is not specified, no integrity table is included in the updated WIM, even
-if there was one in the original WIM.
+option is not specified, an integrity table will be included in the updated WIM
+if and only if one was present before.
 .TP
 \fB--threads\fR=\fINUM_THREADS\fR
 Number of threads to use for compressing newly added files.  Default: autodetect
index 69222a85d4b5b9a7d67bc66e0d72e73d08d68ff4..b913c45e52cbb31fd7667eba892f0567d53a2f61 100644 (file)
@@ -1,4 +1,4 @@
-.TH IMAGEX 1 "June 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
+.TH IMAGEX 1 "August 2013" "@IMAGEX_PROGNAME@ @VERSION@" "User Commands"
 .SH NAME
 @IMAGEX_PROGNAME@ \- Create, modify, extract, mount, or unmount a WIM (Windows Imaging Format) archive
 .SH SYNOPSIS
@@ -48,12 +48,18 @@ but not Windows):
 .IP \[bu] 4
 Create a stand-alone WIM from a directory or NTFS volume (\fB@IMAGEX_PROGNAME@ capture\fR)
 .IP \[bu]
+Capture a WIM image directly to standard output in a special pipable format
+(\fB@IMAGEX_PROGNAME@ capture\fR)
+.IP \[bu]
 Append a directory or NTFS volume onto a stand-alone WIM as a new image (\fB@IMAGEX_PROGNAME@
 append\fR)
 .IP \[bu]
 Apply an image from a stand-alone or split WIM to a directory or NTFS volume
 (\fB@IMAGEX_PROGNAME@ apply\fR)
 .IP \[bu]
+Apply an image from a special pipable WIM format sent over standard input
+(\fB@IMAGEX_PROGNAME@ apply\fR)
+.IP \[bu]
 Mount an image from a stand-alone or split WIM read-only (\fB@IMAGEX_PROGNAME@ mount\fR)
 .IP \[bu]
 Mount an image from a stand-alone WIM read-write (\fB@IMAGEX_PROGNAME@ mountrw\fR)
@@ -92,6 +98,16 @@ thus, much functionality was designed around this.
 .IP \[bu]
 The command-line syntax of the two programs is similar but not exactly the same.
 .IP \[bu]
+As of wimlib v1.5.0, for convenience \fB@IMAGEX_PROGNAME@\fR automatically
+preserves the integrity table in WIMs that have one, even when \fB--check\fR is
+not specified.
+.IP \[bu]
+As of wimlib v1.5.0, a special "pipable" WIM format that is not compatible with
+Microsoft's software is supported.  This allows capturing and applying images
+directly to standard output or from standard input, respectively; this can be
+used to pipe images to or from a server over the network to implement fast
+filesystem imaging and restore.
+.IP \[bu]
 On UNIX, because Microsoft designed the WIM file format to accomodate
 Windows-specific and NTFS-specific features, wimlib must have two separate image
 capture and application modes (although the \fB@IMAGEX_PROGNAME@\fR subcommands
index fb47fd2ee08310fb6e46e46af70b84cfab7e1604..3095023f8c2c3ac63af2e38e19968a0630c826cf 100644 (file)
@@ -1,4 +1,4 @@
-.TH MKWINPEIMG "1" "June 2013" "mkwinpeimg (wimlib @VERSION@)" "User Commands"
+.TH MKWINPEIMG "1" "August 2013" "mkwinpeimg (wimlib @VERSION@)" "User Commands"
 .SH NAME
 mkwinpeimg \- Make a customized bootable image of Windows PE
 .SH SYNOPSIS
index d80089c6d95f02f99fbf14e28d927031a7e13552..faececa310df642f92c3514ba32937dd235f9235 100644 (file)
@@ -31,7 +31,7 @@
  *
  * \section intro Introduction
  *
- * This is the documentation for the library interface of wimlib 1.4.2, a C
+ * This is the documentation for the library interface of wimlib 1.5.0, a C
  * library for creating, modifying, extracting, and mounting files in the
  * Windows Imaging Format.  This documentation is intended for developers only.
  * If you have installed wimlib and want to know how to use the @b wimlib-imagex
  * After creating or modifying a WIM file, you can write it to a file using
  * wimlib_write().  Alternatively,  if the WIM was originally read from a file
  * (using wimlib_open_wim() rather than wimlib_create_new_wim()), you can use
- * wimlib_overwrite() to overwrite the original file.
+ * wimlib_overwrite() to overwrite the original file.  Still alternatively, you
+ * can write a WIM directly to a file descriptor by calling wimlib_write_to_fd()
+ * instead.
+ *
+ * wimlib supports a special "pipable" WIM format (which unfortunately is @b not
+ * compatible with Microsoft's software).  To create a pipable WIM, call
+ * wimlib_write(), wimlib_write_to_fd(), or wimlib_overwrite() with
+ * ::WIMLIB_WRITE_FLAG_PIPABLE specified.  Pipable WIMs are pipable in both
+ * directions, so wimlib_write_to_fd() can be used to write a pipable WIM to a
+ * pipe, and wimlib_extract_image_from_pipe() can be used to apply an image from
+ * a pipable WIM.
  *
  * Please note: merely by calling wimlib_add_image() or many of the other
  * functions in this library that operate on ::WIMStruct's, you are @b not
 #define WIMLIB_MAJOR_VERSION 1
 
 /** Minor version of the library (for example, the 2 in 1.2.5). */
-#define WIMLIB_MINOR_VERSION 4
+#define WIMLIB_MINOR_VERSION 5
 
 /** Patch version of the library (for example, the 5 in 1.2.5). */
-#define WIMLIB_PATCH_VERSION 2
+#define WIMLIB_PATCH_VERSION 0
 
 /**
  * Opaque structure that represents a WIM file.  This is an in-memory structure
@@ -250,10 +260,8 @@ enum wimlib_progress_msg {
         * info will point to ::wimlib_progress_info.extract. */
        WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS,
 
-       /** A file or directory is being extracted.  @a info will point to
-        * ::wimlib_progress_info.extract, and the @a cur_path member will be
-        * valid. */
-       WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY,
+       /** Reserved.  */
+       WIMLIB_PROGRESS_MSG_EXTRACT_RESERVED,
 
        /** All the WIM files and directories have been extracted, and
         * timestamps are about to be applied.  @a info will point to
@@ -432,14 +440,12 @@ union wimlib_progress_info {
                 * extracted. */
                const wimlib_tchar *target;
 
-               /** Current dentry being extracted.  (Valid only if message is
-                * ::WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY.) */
+               /** Reserved.  */
                const wimlib_tchar *cur_path;
 
                /** Number of bytes of uncompressed data that will be extracted.
                 * Takes into account hard links (they are not counted for each
-                * link.)
-                * */
+                * link.)  */
                uint64_t total_bytes;
 
                /** Number of bytes that have been written so far.  Will be 0
@@ -548,6 +554,9 @@ union wimlib_progress_info {
                 * finished (::WIMLIB_PROGRESS_MSG_SPLIT_END_PART). */
                unsigned cur_part_number;
 
+               /** Total number of split WIM parts that are being written.  */
+               unsigned total_parts;
+
                /** Name of the split WIM part that is about to be started
                 * (::WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART) or has just been
                 * finished (::WIMLIB_PROGRESS_MSG_SPLIT_END_PART). */
@@ -714,7 +723,10 @@ struct wimlib_wim_info {
        uint32_t write_in_progress : 1;
        uint32_t metadata_only : 1;
        uint32_t resource_only : 1;
-       uint32_t reserved_flags : 23;
+
+       /** 1 if the WIM is pipable (see ::WIMLIB_WRITE_FLAG_PIPABLE).  */
+       uint32_t pipable : 1;
+       uint32_t reserved_flags : 22;
        uint32_t reserved[9];
 };
 
@@ -989,10 +1001,7 @@ typedef int (*wimlib_iterate_lookup_table_callback_t)(const struct wimlib_resour
  * together.  Cannot be used with ::WIMLIB_EXTRACT_FLAG_NTFS. */
 #define WIMLIB_EXTRACT_FLAG_SYMLINK                    0x00000004
 
-/** Call the progress function with the argument
- * ::WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY each time a file or directory is
- * extracted.  Note: these calls will be interspersed with calls for the message
- * ::WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS. */
+/** 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. */
@@ -1039,6 +1048,19 @@ typedef int (*wimlib_iterate_lookup_table_callback_t)(const struct wimlib_resour
  * one. */
 #define WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS         0x00001000
 
+/** Do not ignore failure to set timestamps on extracted files.  */
+#define WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS          0x00002000
+
+/** 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.  */
+#define WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS             0x00008000
+
+
 /******************************
  * WIMLIB_MOUNT_FLAG_*
  ******************************/
@@ -1123,37 +1145,62 @@ typedef int (*wimlib_iterate_lookup_table_callback_t)(const struct wimlib_resour
  * WIMLIB_WRITE_FLAG_*
  ******************************/
 
-/** Include an integrity table in the new WIM file. */
+/** Include an integrity table in the WIM.
+ *
+ * For WIMs created with wimlib_open_wim(), the default behavior is to include
+ * an integrity table if and only if one was present before.  For WIMs created
+ * with wimlib_create_new_wim(), the default behavior is to not include an
+ * integrity table.  */
 #define WIMLIB_WRITE_FLAG_CHECK_INTEGRITY              0x00000001
 
-/** Re-build the entire WIM file rather than appending data to it, if possible.
- * (Applies to wimlib_overwrite(), not wimlib_write()). */
-#define WIMLIB_WRITE_FLAG_REBUILD                      0x00000002
-
-/** Recompress all resources, even if they could otherwise be copied from a
- * different WIM with the same compression type (in the case of
- * wimlib_export_image() being called previously). */
-#define WIMLIB_WRITE_FLAG_RECOMPRESS                   0x00000004
-
-/** Call fsync() when the WIM file is closed */
-#define WIMLIB_WRITE_FLAG_FSYNC                                0x00000008
+/** Do not include an integrity table in the new WIM file.  This is the default
+ * behavior, unless the WIM already included an integrity table.  */
+#define WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY           0x00000002
 
-/* Specifying this flag overrides the default behavior of wimlib_overwrite()
- * after one or more calls to wimlib_delete_image(), which is to rebuild the
- * entire WIM.
+/** Write the WIM as "pipable".  After writing a WIM with this flag specified,
+ * images from it can be applied directly from a pipe using
+ * wimlib_extract_image_from_pipe().  See the documentation for the --pipable
+ * flag of `wimlib-imagex capture' for more information.  Beware: WIMs written
+ * with this flag will not be compatible with Microsoft's software.
  *
- * If you specifiy this flag to wimlib_overwrite(), only minimal changes to
- * correctly remove the image from the WIM will be taken.  In particular, all
- * streams will be left alone, even if they are no longer referenced.  This is
- * probably not what you want, because almost no space will be spaced by
- * deleting an image in this way. */
-#define WIMLIB_WRITE_FLAG_SOFT_DELETE                  0x00000010
+ * For WIMs created with wimlib_open_wim(), the default behavior is to write the
+ * WIM as pipable if and only if it was pipable before.  For WIMs created with
+ * wimlib_create_new_wim(), the default behavior is to write the WIM as
+ * non-pipable.  */
+#define WIMLIB_WRITE_FLAG_PIPABLE                      0x00000004
+
+/** Do not write the WIM as "pipable".  This is the default behavior, unless the
+ * WIM was pipable already.  */
+#define WIMLIB_WRITE_FLAG_NOT_PIPABLE                  0x00000008
 
-/** With wimlib_overwrite(), allow overwriting the WIM even if the readonly flag
- * is set in the WIM header; this can be used in combination with
+/** Recompress all resources, even if they could otherwise be copied from a
+ * different WIM with the same compression type (in the case of
+ * wimlib_export_image() being called previously).  This flag is also valid in
+ * the @p wim_write_flags of wimlib_join(), in which case all resources included
+ * in the joined WIM file will be recompressed.  */
+#define WIMLIB_WRITE_FLAG_RECOMPRESS                   0x00000010
+
+/** Call fsync() just before the WIM file is closed.  */
+#define WIMLIB_WRITE_FLAG_FSYNC                                0x00000020
+
+/** wimlib_overwrite() only:  Re-build the entire WIM file rather than appending
+ * data to it if possible.  */
+#define WIMLIB_WRITE_FLAG_REBUILD                      0x00000040
+
+/** wimlib_overwrite() only:  Specifying this flag overrides the default
+ * behavior of wimlib_overwrite() after one or more calls to
+ * wimlib_delete_image(), which is to rebuild the entire WIM.  With this flag,
+ * only minimal changes to correctly remove the image from the WIM will be
+ * taken.  In particular, all streams will be left alone, even if they are no
+ * longer referenced.  This is probably not what you want, because almost no
+ * space will be saved by deleting an image in this way. */
+#define WIMLIB_WRITE_FLAG_SOFT_DELETE                  0x00000080
+
+/** wimlib_overwrite() only:  Allow overwriting the WIM even if the readonly
+ * flag is set in the WIM header.  This can be used in combination with
  * wimlib_set_wim_info() with the ::WIMLIB_CHANGE_READONLY_FLAG flag to actually
- * set the readonly flag on the on-disk WIM file. */
-#define WIMLIB_WRITE_FLAG_IGNORE_READONLY_FLAG         0x00000020
+ * set the readonly flag on the on-disk WIM file.  */
+#define WIMLIB_WRITE_FLAG_IGNORE_READONLY_FLAG         0x00000100
 
 /******************************
  * WIMLIB_INIT_FLAG_*
@@ -1244,7 +1291,6 @@ struct wimlib_extract_command {
 enum wimlib_error_code {
        WIMLIB_ERR_SUCCESS = 0,
        WIMLIB_ERR_ALREADY_LOCKED,
-       WIMLIB_ERR_COMPRESSED_LOOKUP_TABLE,
        WIMLIB_ERR_DECOMPRESSION,
        WIMLIB_ERR_DELETE_STAGING_DIR,
        WIMLIB_ERR_FILESYSTEM_DAEMON_CRASHED,
@@ -1254,23 +1300,22 @@ enum wimlib_error_code {
        WIMLIB_ERR_ICONV_NOT_AVAILABLE,
        WIMLIB_ERR_IMAGE_COUNT,
        WIMLIB_ERR_IMAGE_NAME_COLLISION,
-       WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT,
        WIMLIB_ERR_INTEGRITY,
        WIMLIB_ERR_INVALID_CAPTURE_CONFIG,
        WIMLIB_ERR_INVALID_CHUNK_SIZE,
        WIMLIB_ERR_INVALID_COMPRESSION_TYPE,
-       WIMLIB_ERR_INVALID_DENTRY,
-       WIMLIB_ERR_INVALID_HEADER_SIZE,
+       WIMLIB_ERR_INVALID_HEADER,
        WIMLIB_ERR_INVALID_IMAGE,
        WIMLIB_ERR_INVALID_INTEGRITY_TABLE,
        WIMLIB_ERR_INVALID_LOOKUP_TABLE_ENTRY,
+       WIMLIB_ERR_INVALID_METADATA_RESOURCE,
        WIMLIB_ERR_INVALID_MULTIBYTE_STRING,
        WIMLIB_ERR_INVALID_OVERLAY,
        WIMLIB_ERR_INVALID_PARAM,
        WIMLIB_ERR_INVALID_PART_NUMBER,
+       WIMLIB_ERR_INVALID_PIPABLE_WIM,
        WIMLIB_ERR_INVALID_REPARSE_DATA,
        WIMLIB_ERR_INVALID_RESOURCE_HASH,
-       WIMLIB_ERR_INVALID_RESOURCE_SIZE,
        WIMLIB_ERR_INVALID_SECURITY_DATA,
        WIMLIB_ERR_INVALID_UNMOUNT_MESSAGE,
        WIMLIB_ERR_INVALID_UTF16_STRING,
@@ -1285,6 +1330,7 @@ enum wimlib_error_code {
        WIMLIB_ERR_NOTEMPTY,
        WIMLIB_ERR_NOT_A_REGULAR_FILE,
        WIMLIB_ERR_NOT_A_WIM_FILE,
+       WIMLIB_ERR_NOT_PIPABLE,
        WIMLIB_ERR_NO_FILENAME,
        WIMLIB_ERR_NTFS_3G,
        WIMLIB_ERR_OPEN,
@@ -1295,20 +1341,24 @@ enum wimlib_error_code {
        WIMLIB_ERR_RENAME,
        WIMLIB_ERR_REOPEN,
        WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED,
+       WIMLIB_ERR_RESOURCE_NOT_FOUND,
        WIMLIB_ERR_RESOURCE_ORDER,
+       WIMLIB_ERR_SET_ATTRIBUTES,
+       WIMLIB_ERR_SET_SECURITY,
+       WIMLIB_ERR_SET_TIMESTAMPS,
        WIMLIB_ERR_SPECIAL_FILE,
        WIMLIB_ERR_SPLIT_INVALID,
        WIMLIB_ERR_SPLIT_UNSUPPORTED,
        WIMLIB_ERR_STAT,
        WIMLIB_ERR_TIMEOUT,
+       WIMLIB_ERR_UNEXPECTED_END_OF_FILE,
        WIMLIB_ERR_UNICODE_STRING_NOT_REPRESENTABLE,
        WIMLIB_ERR_UNKNOWN_VERSION,
        WIMLIB_ERR_UNSUPPORTED,
        WIMLIB_ERR_VOLUME_LACKS_FEATURES,
+       WIMLIB_ERR_WIM_IS_READONLY,
        WIMLIB_ERR_WRITE,
        WIMLIB_ERR_XML,
-       WIMLIB_ERR_WIM_IS_READONLY,
-       WIMLIB_ERR_RESOURCE_NOT_FOUND,
 };
 
 
@@ -1321,7 +1371,11 @@ enum wimlib_error_code {
 /**
  * Appends an empty image to a WIM file.  This empty image will initially
  * contain no files or directories, although if written without further
- * modifications, a root directory will be created automatically for it.
+ * modifications, a root directory will be created automatically for it.  After
+ * calling this function, you can use wimlib_update_image() to add files to the
+ * new WIM image.  This gives you slightly more control over making the new
+ * image compared to calling wimlib_add_image() or
+ * wimlib_add_image_multisource() directly.
  *
  * @param wim
  *     Pointer to the ::WIMStruct for the WIM file to which the image is to be
@@ -1478,9 +1532,6 @@ wimlib_create_new_wim(int ctype, WIMStruct **wim_ret);
  *
  * @retval ::WIMLIB_ERR_DECOMPRESSION
  *     Could not decompress the metadata resource for @a image.
- * @retval ::WIMLIB_ERR_INVALID_DENTRY
- *     A directory entry in the metadata resource for @a image in the WIM is
- *     invalid.
  * @retval ::WIMLIB_ERR_INVALID_IMAGE
  *     @a image does not exist in the WIM and is not ::WIMLIB_ALL_IMAGES.
  * @retval ::WIMLIB_ERR_INVALID_RESOURCE_SIZE
@@ -1571,9 +1622,6 @@ wimlib_delete_image(WIMStruct *wim, int image);
  * @retval ::WIMLIB_ERR_IMAGE_NAME_COLLISION
  *     One or more of the names being given to an exported image was already in
  *     use in the destination WIM.
- * @retval ::WIMLIB_ERR_INVALID_DENTRY
- *     A directory entry in the metadata resource for @a src_image in @a
- *     src_wim is invalid.
  * @retval ::WIMLIB_ERR_INVALID_IMAGE
  *     @a src_image does not exist in @a src_wim.
  * @retval ::WIMLIB_ERR_INVALID_PARAM
@@ -1755,9 +1803,6 @@ wimlib_extract_files(WIMStruct *wim,
  * @retval ::WIMLIB_ERR_DECOMPRESSION
  *     Could not decompress a resource (file or metadata) for @a image in @a
  *     wim.
- * @retval ::WIMLIB_ERR_INVALID_DENTRY
- *     A directory entry in the metadata resource for @a image in @a wim is
- *     invalid.
  * @retval ::WIMLIB_ERR_INVALID_PARAM
  *     @a target was @c NULL, or both ::WIMLIB_EXTRACT_FLAG_HARDLINK and
  *     ::WIMLIB_EXTRACT_FLAG_SYMLINK were specified in @a extract_flags; or
@@ -1796,6 +1841,16 @@ wimlib_extract_files(WIMStruct *wim,
  *     One of the dentries in the image referenced a stream not present in the
  *     WIM's lookup table (or in any of the lookup tables of the split WIM
  *     parts).
+ * @retval ::WIMLIB_ERR_SET_ATTRIBUTES
+ *     Failed to set attributes on a file.
+ * @retval ::WIMLIB_ERR_SET_SECURITY
+ *     Failed to set security descriptor on a file
+ *     (only if ::WIMLIB_EXTRACT_FLAG_STRICT_ACLS) specified in @p
+ *     extract_flags.
+ * @retval ::WIMLIB_ERR_SET_SECURITY
+ *     Failed to set security descriptor on a file
+ *     (only if ::WIMLIB_EXTRACT_FLAG_STRICT_ACLS) specified in @p
+ *     extract_flags.
  * @retval ::WIMLIB_ERR_SPLIT_INVALID
  *     The WIM is a split WIM, but the parts specified do not form a complete
  *     split WIM because they do not include all the parts of the original WIM,
@@ -1816,6 +1871,49 @@ wimlib_extract_image(WIMStruct *wim, int image,
                     unsigned num_additional_swms,
                     wimlib_progress_func_t progress_func);
 
+/**
+ * Extract one or more images from a pipe on which a pipable WIM is being sent.
+ *
+ * See the documentation for ::WIMLIB_WRITE_FLAG_PIPABLE for more information
+ * about pipable WIMs.
+ *
+ * This function operates in a special way to read the WIM fully sequentially.
+ * As a result, there is no ::WIMStruct is made visible to library users, and
+ * you cannot call wimlib_open_wim() on the pipe.  (You can, however, use
+ * wimlib_open_wim() to transparently open a pipable WIM if it's available as a
+ * seekable file, not a pipe.)
+ *
+ * @param pipe_fd
+ *     File descriptor, which may be a pipe, opened for reading and positioned
+ *     at the start of the pipable WIM.
+ * @param image_num_or_name
+ *     String that specifies the 1-based index or name of the image to extract.
+ *     It is translated to an image index using the same rules that
+ *     wimlib_resolve_image() uses.  However, unlike wimlib_extract_image(),
+ *     only a single image (not all images) can be specified.
+ * @param target
+ *     Same as the corresponding parameter to wimlib_extract_image().
+ * @param extract_flags
+ *     Same as the corresponding parameter to wimlib_extract_image(), except
+ *     for the following exceptions:  ::WIMLIB_EXTRACT_FLAG_SEQUENTIAL is
+ *     always implied, since data is always read from @p pipe_fd sequentially
+ *     in this mode; also, ::WIMLIB_EXTRACT_FLAG_TO_STDOUT is invalid and will
+ *     result in ::WIMLIB_ERR_INVALID_PARAM being returned.
+ * @param progress_func
+ *     Same as the corresponding parameter to wimlib_extract_image().
+ *
+ * @return 0 on success; nonzero on error.  The possible error codes include
+ * those returned by wimlib_extract_image() as well as the following:
+ *
+ * @retval ::WIMLIB_ERR_NOT_PIPABLE
+ *     The WIM being piped in a @p pipe_fd is a normal WIM, not a pipable WIM.
+ */
+extern int
+wimlib_extract_image_from_pipe(int pipe_fd,
+                              const wimlib_tchar *image_num_or_name,
+                              const wimlib_tchar *target, int extract_flags,
+                              wimlib_progress_func_t progress_func);
+
 /**
  * Extracts the XML data of a WIM file to a file stream.  Every WIM file
  * includes a string of XML that describes the images contained in the WIM.
@@ -2238,9 +2336,6 @@ wimlib_lzx_decompress(const void *compressed_data, unsigned compressed_len,
  *     Could not decompress the metadata resource for @a image in @a wim.
  * @retval ::WIMLIB_ERR_FUSE
  *     A non-zero status was returned by @c fuse_main().
- * @retval ::WIMLIB_ERR_INVALID_DENTRY
- *     A directory entry in the metadata resource for @a image in @a wim is
- *     invalid.
  * @retval ::WIMLIB_ERR_INVALID_IMAGE
  *     @a image does not specify an existing, single image in @a wim.
  * @retval ::WIMLIB_ERR_INVALID_PARAM
@@ -2327,7 +2422,7 @@ wimlib_mount_image(WIMStruct *wim,
  * @retval ::WIMLIB_ERR_INVALID_COMPRESSION_TYPE
  *     The header of @a wim_file says that resources in the WIM are compressed,
  *     but the header flag indicating LZX or XPRESS compression is not set.
- * @retval ::WIMLIB_ERR_INVALID_HEADER_SIZE
+ * @retval ::WIMLIB_ERR_INVALID_HEADER
  *     The length field of the WIM header is not 208.
  * @retval ::WIMLIB_ERR_INVALID_INTEGRITY_TABLE
  *     ::WIMLIB_OPEN_FLAG_CHECK_INTEGRITY was specified in @a open_flags and @a
@@ -2337,6 +2432,8 @@ wimlib_mount_image(WIMStruct *wim,
  *     The lookup table for the WIM contained duplicate entries that are not
  *     for metadata resources, or it contained an entry with a SHA1 message
  *     digest of all 0's.
+ * @retval ::WIMLIB_ERR_INVALID_PARAM
+ *     @p wim_ret was @c NULL.
  * @retval ::WIMLIB_ERR_NOMEM
  *     Failed to allocated needed memory.
  * @retval ::WIMLIB_ERR_NOT_A_WIM_FILE
@@ -2735,7 +2832,7 @@ wimlib_set_print_errors(bool show_messages);
 extern int
 wimlib_split(WIMStruct *wim,
             const wimlib_tchar *swm_name,
-            size_t part_size,
+            uint64_t part_size,
             int write_flags,
             wimlib_progress_func_t progress_func);
 
@@ -2833,8 +2930,6 @@ wimlib_unmount_image(const wimlib_tchar *dir,
  * @retval ::WIMLIB_ERR_INVALID_CAPTURE_CONFIG
  *     The capture configuration structure specified for an add command was
  *     invalid.
- * @retval ::WIMLIB_ERR_INVALID_DENTRY
- *     A directory entry for @a image in @a wim is invalid.
  * @retval ::WIMLIB_ERR_INVALID_IMAGE
  *     @a image did not specify a single, existing image in @a wim.
  * @retval ::WIMLIB_ERR_INVALID_OVERLAY
@@ -2932,12 +3027,10 @@ wimlib_update_image(WIMStruct *wim,
  * @param path
  *     The path to the file to write the WIM to.
  * @param image
- *     The image inside the WIM to write.  Use ::WIMLIB_ALL_IMAGES to include all
- *     images.
+ *     The 1-based index of the image inside the WIM to write.  Use
+ *     ::WIMLIB_ALL_IMAGES to include all images.
  * @param write_flags
- *     Bitwise OR of the flags ::WIMLIB_WRITE_FLAG_CHECK_INTEGRITY,
- *     ::WIMLIB_WRITE_FLAG_RECOMPRESS, ::WIMLIB_WRITE_FLAG_FSYNC, and/or
- *     ::WIMLIB_WRITE_FLAG_SOFT_DELETE.
+ *     Bitwise OR of any of the flags prefixed with @c WIMLIB_WRITE_FLAG.
  * @param num_threads
  *     Number of threads to use for compressing data.  If 0, the number of
  *     threads is taken to be the number of online processors.  Note: if no
@@ -2948,14 +3041,14 @@ wimlib_update_image(WIMStruct *wim,
  *     write_flags).
  * @param progress_func
  *     If non-NULL, a function that will be called periodically with the
- *     progress of the current operation.
+ *     progress of the current operation.  The possible messages are
+ *     ::WIMLIB_PROGRESS_MSG_WRITE_METADATA_BEGIN,
+ *     ::WIMLIB_PROGRESS_MSG_WRITE_METADATA_END, and
+ *     ::WIMLIB_PROGRESS_MSG_WRITE_STREAMS.
  *
  * @return 0 on success; nonzero on error.
  * @retval ::WIMLIB_ERR_DECOMPRESSION
  *     Failed to decompress a metadata or file resource in @a wim.
- * @retval ::WIMLIB_ERR_INVALID_DENTRY
- *     A directory entry in the metadata resource for @a image in @a wim is
- *     invalid.
  * @retval ::WIMLIB_ERR_INVALID_IMAGE
  *     @a image does not specify a single existing image in @a wim, and is not
  *     ::WIMLIB_ALL_IMAGES.
@@ -2964,7 +3057,7 @@ wimlib_update_image(WIMStruct *wim,
  *     wimlib_add_image() was concurrently modified, so it failed the SHA1
  *     message digest check.
  * @retval ::WIMLIB_ERR_INVALID_PARAM
- *     @a path was @c NULL.
+ *     @p path was @c NULL.
  * @retval ::WIMLIB_ERR_INVALID_RESOURCE_SIZE
  *     The metadata resource for @a image in @a wim is invalid.
  * @retval ::WIMLIB_ERR_INVALID_SECURITY_DATA
@@ -2972,20 +3065,18 @@ wimlib_update_image(WIMStruct *wim,
  * @retval ::WIMLIB_ERR_NOMEM
  *     Failed to allocate needed memory.
  * @retval ::WIMLIB_ERR_OPEN
- *     Failed to open @a path for writing, or some file resources in @a
- *     wim refer to files in the outside filesystem, and one of these files
- *     could not be opened for reading.
+ *     Failed to open @a path for writing, or some file resources in @a wim
+ *     refer to files in the outside filesystem, and one of these files could
+ *     not be opened for reading.
  * @retval ::WIMLIB_ERR_READ
  *     An error occurred when trying to read data from the WIM file associated
  *     with @a wim, or some file resources in @a wim refer to files in the
  *     outside filesystem, and a read error occurred when reading one of these
  *     files.
  * @retval ::WIMLIB_ERR_SPLIT_UNSUPPORTED
- *     @a wim is part of a split WIM.  You may not call this function on a
- *     split WIM.
+ *     @a wim is part of a split WIM, not a standalone WIM.
  * @retval ::WIMLIB_ERR_WRITE
- *     An error occurred when trying to write data to the new WIM file at @a
- *     path.
+ *     An error occurred when trying to write data to the new WIM file.
  */
 extern int
 wimlib_write(WIMStruct *wim,
@@ -2995,6 +3086,28 @@ wimlib_write(WIMStruct *wim,
             unsigned num_threads,
             wimlib_progress_func_t progress_func);
 
+/**
+ * Same as wimlib_write(), but write the WIM directly to a file descriptor,
+ * which need not be seekable if the write is done in a special pipable WIM
+ * format by providing ::WIMLIB_WRITE_FLAG_PIPABLE in @p write_flags.  This can,
+ * for example, allow capturing a WIM image and streaming it over the network.
+ * See the documentation for ::WIMLIB_WRITE_FLAG_PIPABLE for more information
+ * about pipable WIMs.
+ *
+ * Return values are mostly the same as wimlib_write(), but also:
+ *
+ * @retval ::WIMLIB_ERR_INVALID_PARAM
+ *     @p fd was not seekable, but ::WIMLIB_WRITE_FLAG_PIPABLE was not
+ *     specified in @p write_flags.
+ */
+extern int
+wimlib_write_to_fd(WIMStruct *wim,
+                  int fd,
+                  int image,
+                  int write_flags,
+                  unsigned num_threads,
+                  wimlib_progress_func_t progress_func);
+
 /**
  * This function is equivalent to wimlib_lzx_compress(), but instead compresses
  * the data using "XPRESS" compression.
index 4fd22f83a6ff017b0dcdd63f7db2308eadf4992c..5063f805db282c385e8c3a19fd72e66c163612dd 100644 (file)
 #ifndef _WIMLIB_APPLY_H
 #define _WIMLIB_APPLY_H
 
-#include "wimlib.h"
 #include "wimlib/types.h"
+#include "wimlib/list.h"
+#include "wimlib.h"
 
-#ifdef WITH_NTFS_3G
-struct _ntfs_volume;
-#endif
+struct wim_lookup_table_entry;
+struct wimlib_unix_data;
+struct wim_dentry;
+struct apply_ctx;
 
-struct apply_args {
-       WIMStruct *wim;
+/*
+ * 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 {
 
-       /* Directory to which we're extracting the WIM image or directory tree,
-        * in user-specified form (may be slightly altered) */
-       const tchar *target;
-       unsigned target_nchars;
+       /* OPTIONAL:  Name of this extraction mode.  */
+       const tchar *name;
 
-#ifdef __WIN32__
-       /* \\?\-prefixed full path to the above directory; needed to work around
-        * lack of default support for long paths on Windoze. */
-       tchar *target_lowlevel_path;
-       unsigned target_lowlevel_path_nchars;
-#endif
+       /* 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);
 
-       /* Absolute path to the above directory; on UNIX this is simply a path
-        * beginning with /, while on Windoze this will be a path beginning with
-        * a drive letter followed by a backslash, but not with \\?\. */
-       tchar *target_realpath;
-       unsigned target_realpath_len;
+       /* 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);
 
-       struct wim_dentry *extract_root;
-       unsigned long invalid_sequence;
-       int extract_flags;
-       union wimlib_progress_info progress;
-       wimlib_progress_func_t progress_func;
-       int (*apply_dentry)(struct wim_dentry *, void *);
-       union {
-       #ifdef WITH_NTFS_3G
-               struct {
-                       /* NTFS apply only */
-                       struct _ntfs_volume *vol;
-               };
-       #endif
-       #ifdef __WIN32__
-               struct {
-                       /* Normal apply only (Win32) */
-                       unsigned long num_set_sacl_priv_notheld;
-                       unsigned long num_set_sd_access_denied;
-                       unsigned vol_flags;
-                       unsigned long num_hard_links_failed;
-                       unsigned long num_soft_links_failed;
-                       unsigned long num_long_paths;
-                       bool have_vol_flags;
-               };
-       #else
-               struct {
-                       /* Normal apply only (UNIX) */
-                       unsigned long num_utime_warnings;
-               };
-       #endif
-       };
+       /* REQUIRED:  Create a file.  */
+       int (*create_file)
+               (const tchar *path, struct apply_ctx *ctx);
+
+       /* REQUIRED:  Create a directory.  */
+       int (*create_directory)
+               (const tchar *path, struct apply_ctx *ctx);
+
+       /* 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)
+               (const tchar *path, struct wim_lookup_table_entry *lte,
+                struct apply_ctx *ctx);
+
+       /* OPTIONAL:  Extracted named data stream.  In start_extract(), set
+        * ctx->supported_features.alternate_data_streams if supported.  */
+       int (*extract_named_stream)
+               (const tchar *path, 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);
+
+       /* 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, bool strict);
+
+       /* 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;
 };
 
-#ifdef WITH_NTFS_3G
-extern int
-apply_dentry_ntfs(struct wim_dentry *dentry, void *arg);
+struct wim_features {
+       unsigned long archive_files;
+       unsigned long hidden_files;
+       unsigned long system_files;
+       unsigned long compressed_files;
+       unsigned long encrypted_files;
+       unsigned long not_context_indexed_files;
+       unsigned long sparse_files;
+       unsigned long named_data_streams;
+       unsigned long hard_links;
+       unsigned long reparse_points;
+       unsigned long symlink_reparse_points;
+       unsigned long other_reparse_points;
+       unsigned long security_descriptors;
+       unsigned long short_names;
+       unsigned long unix_data;
+};
 
-extern int
-apply_dentry_timestamps_ntfs(struct wim_dentry *dentry, void *arg);
-#endif
+/* Context for an apply (extract) operation.  */
+struct apply_ctx {
+       WIMStruct *wim;
+       int extract_flags;
+       const tchar *target;
+       size_t target_nchars;
+       wimlib_progress_func_t progress_func;
+       union wimlib_progress_info progress;
+       struct wim_dentry *extract_root;
+       const struct apply_operations *ops;
+       struct wim_features supported_features;
+       struct list_head stream_list;
+       tchar *realtarget;
+       size_t realtarget_nchars;
+       unsigned long invalid_sequence;
+       u64 num_streams_remaining;
+       bool root_dentry_is_special;
+       uint64_t next_progress;
+       intptr_t private[8];
+};
 
 #ifdef __WIN32__
-extern int
-win32_do_apply_dentry(const tchar *output_path,
-                     size_t output_path_nbytes,
-                     struct wim_dentry *dentry,
-                     struct apply_args *args);
-
-extern int
-win32_do_apply_dentry_timestamps(const tchar *output_path,
-                                size_t output_path_nbytes,
-                                struct wim_dentry *dentry,
-                                struct apply_args *args);
-#else /* __WIN32__ */
-extern int
-unix_do_apply_dentry(const tchar *output_path, size_t output_path_nbytes,
-                    struct wim_dentry *dentry, struct apply_args *args);
-extern int
-unix_do_apply_dentry_timestamps(const tchar *output_path,
-                               size_t output_path_nbytes,
-                               struct wim_dentry *dentry,
-                               struct apply_args *args);
-#endif /* !__WIN32__ */
-
-/* Internal use only */
-#define WIMLIB_EXTRACT_FLAG_MULTI_IMAGE                0x80000000
-#define WIMLIB_EXTRACT_FLAG_NO_STREAMS         0x40000000
-#define WIMLIB_EXTRACT_MASK_PUBLIC             0x3fffffff
+  extern const struct apply_operations win32_apply_ops;
+#else
+  extern const struct apply_operations unix_apply_ops;
+#endif
 
+#ifdef WITH_NTFS_3G
+  extern const struct apply_operations ntfs_3g_apply_ops;
+#endif
 
 #endif /* _WIMLIB_APPLY_H */
index 94393663273e61f778a91b2d0d84d990d0c2ad13..6d8c557ee0b1bb7a752f1c922780ff50738152b7 100644 (file)
@@ -153,16 +153,11 @@ struct wim_dentry {
         * including the terminating null character. */
        u32 full_path_nbytes;
 
-       /* For extraction operations, a subtree of dentries will have this flag
-        * set so we can keep track of which dentries still need to be
-        * extracted.  Otherwise this will always be 0.  */
-       u8 needs_extraction : 1;
-
        /* For extraction operations, this flag will be set when a dentry in the
         * tree being extracted is not being extracted for some reason (file
         * type not supported by target filesystem or contains invalid
         * characters).  Otherwise this will always be 0. */
-       u8 not_extracted : 1;
+       u8 extraction_skipped : 1;
 
        /* When capturing from a NTFS volume using NTFS-3g, this flag is set on
         * dentries that were created from a filename in the WIN32 or WIN32+DOS
@@ -177,6 +172,10 @@ struct wim_dentry {
         * names.  */
        u8 dos_name_invalid : 1;
 
+       u8 tmp_flag : 1;
+
+       u8 was_hardlinked : 1;
+
        /* Temporary list field used to make lists of dentries in a few places.
         * */
        struct list_head tmp_list;
@@ -312,10 +311,6 @@ struct wim_inode {
         * error paths.  */
        u8 i_visited : 1;
 
-       /* For NTFS-3g extraction:  Set after the DOS name for this inode has
-        * been extracted.  */
-       u8 i_dos_name_extracted : 1;
-
        /* Pointer to a malloc()ed array of i_num_ads alternate data stream
         * entries for this inode.  */
        struct wim_ads_entry *i_ads_entries;
@@ -522,6 +517,9 @@ inode_add_ads_with_data(struct wim_inode *inode, const tchar *name,
                        const void *value, size_t size,
                        struct wim_lookup_table *lookup_table);
 
+bool
+inode_has_named_stream(const struct wim_inode *inode);
+
 extern int
 inode_set_unnamed_stream(struct wim_inode *inode, const void *data, size_t len,
                         struct wim_lookup_table *lookup_table);
@@ -537,6 +535,22 @@ inode_remove_ads(struct wim_inode *inode, u16 idx,
 #define WIMLIB_UNIX_DATA_TAG_UTF16LE "$\0$\0_\0_\0w\0i\0m\0l\0i\0b\0_\0U\0N\0I\0X\0_\0d\0a\0t\0a\0"
 #define WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES (sizeof(WIMLIB_UNIX_DATA_TAG_UTF16LE) - 1)
 
+static inline bool
+ads_entry_is_unix_data(const struct wim_ads_entry *entry)
+{
+       return (entry->stream_name_nbytes ==
+                       WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES) &&
+               !memcmp(entry->stream_name, WIMLIB_UNIX_DATA_TAG_UTF16LE,
+                       WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES);
+}
+
+static inline bool
+ads_entry_is_named_stream(const struct wim_ads_entry *entry)
+{
+       return entry->stream_name_nbytes != 0 && !ads_entry_is_unix_data(entry);
+}
+
+#ifndef __WIN32__
 /* Format for special alternate data stream entries to store UNIX data for files
  * and directories (see: WIMLIB_ADD_FLAG_UNIX_DATA) */
 struct wimlib_unix_data {
@@ -546,8 +560,6 @@ struct wimlib_unix_data {
        u16 mode;
 } _packed_attribute;
 
-#ifndef __WIN32__
-
 #define NO_UNIX_DATA (-1)
 #define BAD_UNIX_DATA (-2)
 extern int
@@ -563,7 +575,10 @@ inode_get_unix_data(const struct wim_inode *inode,
 extern int
 inode_set_unix_data(struct wim_inode *inode, uid_t uid, gid_t gid, mode_t mode,
                    struct wim_lookup_table *lookup_table, int which);
-#endif
+#endif /* __WIN32__ */
+
+extern bool
+inode_has_unix_data(const struct wim_inode *inode);
 
 extern int
 read_dentry(const u8 * restrict metadata_resource,
@@ -593,6 +608,14 @@ inode_is_directory(const struct wim_inode *inode)
                        == FILE_ATTRIBUTE_DIRECTORY;
 }
 
+static inline bool
+inode_is_encrypted_directory(const struct wim_inode *inode)
+{
+       return ((inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
+                                       FILE_ATTRIBUTE_ENCRYPTED))
+               == (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_ENCRYPTED));
+}
+
 static inline bool
 dentry_is_directory(const struct wim_dentry *dentry)
 {
index 1d258983354d2e5f95964d740fced0aa87e6840d..de59cfa07d8b9be48d0a1efcb123c1b5f1723c91 100644 (file)
@@ -4,18 +4,29 @@
 #include <stddef.h>
 #include <sys/types.h>
 
-extern size_t
-full_read(int fd, void *buf, size_t n);
+struct filedes {
+       int fd;
+       unsigned int is_pipe : 1;
+       off_t offset;
+};
+
+extern int
+full_read(struct filedes *fd, void *buf, size_t n);
 
-extern size_t
-full_write(int fd, const void *buf, size_t n);
+extern int
+full_pread(struct filedes *fd, void *buf, size_t nbyte, off_t offset);
 
-extern size_t
-full_pread(int fd, void *buf, size_t nbyte, off_t offset);
+extern int
+full_write(struct filedes *fd, const void *buf, size_t n);
 
-extern size_t
-full_pwrite(int fd, const void *buf, size_t count, off_t offset);
+extern int
+full_pwrite(struct filedes *fd, const void *buf, size_t count, off_t offset);
 
+extern ssize_t
+raw_pread(struct filedes *fd, void *buf, size_t nbyte, off_t offset);
+
+extern ssize_t
+raw_pwrite(struct filedes *fd, const void *buf, size_t count, off_t offset);
 
 #ifdef __WIN32__
 struct iovec {
@@ -26,14 +37,42 @@ struct iovec {
 struct iovec;
 #endif
 
-extern size_t
-full_writev(int fd, struct iovec *iov, int iovcnt);
-
-extern off_t
-filedes_offset(int fd);
+extern int
+full_writev(struct filedes *fd, struct iovec *iov, int iovcnt);
 
 #ifndef __WIN32__
 #  define O_BINARY 0
 #endif
 
+extern off_t
+filedes_seek(struct filedes *fd, off_t offset);
+
+extern bool
+filedes_is_seekable(struct filedes *fd);
+
+static inline void filedes_init(struct filedes *fd, int raw_fd)
+{
+       fd->fd = raw_fd;
+       fd->offset = 0;
+       fd->is_pipe = 0;
+}
+
+static inline void filedes_invalidate(struct filedes *fd)
+{
+       fd->fd = -1;
+}
+
+static inline void filedes_copy(struct filedes *dst, const struct filedes *src)
+{
+       *dst = *src;
+}
+
+#define filedes_close(f) close((f)->fd)
+
+static inline bool
+filedes_valid(const struct filedes *fd)
+{
+       return fd->fd != -1;
+}
+
 #endif /* _WIMLIB_FILE_IO_H */
index 0702b45bfa9b9c30af662ee0e074cc46033b510e..238b6b22122858bd3ba8fe2870484b6062089c5e 100644 (file)
@@ -3,24 +3,54 @@
 
 #include "wimlib/resource.h"
 #include "wimlib/types.h"
+#include "wimlib/endianness.h"
 
+/* Length of "Globally Unique ID" field in WIM header.  */
 #define WIM_GID_LEN    16
 
-/* Length of the WIM header on disk. */
+/* Length of the WIM header on disk.  */
 #define WIM_HEADER_DISK_SIZE 208
 
 /* Compressed resources in the WIM are divided into separated compressed chunks
- * of this size. */
+ * of this size.  This value is unfortunately not configurable (at least when
+ * compatibility with Microsoft's software is desired).  */
 #define WIM_CHUNK_SIZE 32768
 
-/* Version of the WIM file.  There is an older version, but we don't support it
- * yet.  The differences between the versions are undocumented. */
+/* Version of the WIM file.  There is an older version, but wimlib doesn't
+ * support it.  The differences between the versions are undocumented.  */
 #define WIM_VERSION 0x10d00
 
+/* WIM magic characters, translated to a single 64-bit little endian number.  */
+#define WIM_MAGIC \
+               cpu_to_le64(((u64)'M' << 0) |           \
+                           ((u64)'S' << 8) |           \
+                           ((u64)'W' << 16) |          \
+                           ((u64)'I' << 24) |          \
+                           ((u64)'M' << 32) |          \
+                           ((u64)'\0' << 40) |         \
+                           ((u64)'\0' << 48) |         \
+                           ((u64)'\0' << 54))
+
+/* wimlib pipable WIM magic characters, translated to a single 64-bit little
+ * endian number.  */
+#define PWM_MAGIC \
+               cpu_to_le64(((u64)'W' << 0) |           \
+                           ((u64)'L' << 8) |           \
+                           ((u64)'P' << 16) |          \
+                           ((u64)'W' << 24) |          \
+                           ((u64)'M' << 32) |          \
+                           ((u64)'\0' << 40) |         \
+                           ((u64)'\0' << 48) |         \
+                           ((u64)'\0' << 54))
+
 /* Header at the very beginning of the WIM file.  This is the in-memory
  * representation and does not include all fields; see `struct wim_header_disk'
- * for the on-disk structure. */
+ * for the on-disk structure.  */
 struct wim_header {
+
+       /* Magic characters: either WIM_MAGIC or PWM_MAGIC.  */
+       le64 magic;
+
        /* Bitwise OR of one or more of the WIM_HDR_FLAG_* defined below. */
        u32 flags;
 
index aa541f6afde4ebdd456db5e07edff6e2afb73fb0..c9e07ca8e55bc1d1c5359a3d8cee737f808a9dc5 100644 (file)
@@ -8,11 +8,8 @@
 #define WIM_INTEGRITY_NOT_OK -1
 #define WIM_INTEGRITY_NONEXISTENT -2
 
-struct resource_entry;
-
 extern int
-write_integrity_table(int fd,
-                     struct resource_entry *integrity_res_entry,
+write_integrity_table(WIMStruct *wim,
                      off_t new_lookup_table_end,
                      off_t old_lookup_table_end,
                      wimlib_progress_func_t progress_func);
index ff6a12a28b1e8fb6de620088b038c30a1535e4a3..e9532971748f96717d8086c7c710c293da7a6e6a 100644 (file)
@@ -59,17 +59,20 @@ enum resource_location {
        RESOURCE_NONEXISTENT = 0,
 
        /* The stream resource is located in a WIM file.  The WIMStruct for the
-        * WIM file will be pointed to by the @wim member. */
+        * WIM file will be pointed to by the @wim member.  The compression type
+        * of the resource will be cached in @compression_type, and the pipable
+        * status of the resource will be cached in @pipable.  */
        RESOURCE_IN_WIM,
 
-#ifndef __WIN32__
        /* The stream resource is located in an external file.  The name of the
-        * file will be provided by @file_on_disk member. */
+        * file will be provided by @file_on_disk member.
+        *
+        * Note: On Windows @file_on_disk may actually specify a named data
+        * stream.  */
        RESOURCE_IN_FILE_ON_DISK,
-#endif
 
        /* The stream resource is directly attached in an in-memory buffer
-        * pointed to by @attached_buffer. */
+        * pointed to by @attached_buffer.  */
        RESOURCE_IN_ATTACHED_BUFFER,
 
 #ifdef WITH_FUSE
@@ -82,15 +85,11 @@ enum resource_location {
        /* The stream resource is located in an NTFS volume.  It is identified
         * by volume, filename, data stream name, and by whether it is a reparse
         * point or not. @ntfs_loc points to a structure containing this
-        * information. */
+        * information.  */
        RESOURCE_IN_NTFS_VOLUME,
 #endif
 
 #ifdef __WIN32__
-       /* Resource must be accessed using Win32 API (may be a named data
-        * stream) */
-       RESOURCE_WIN32,
-
        /* Windows only: the file is on disk in the file named @file_on_disk,
         * but the file is encrypted and must be read using special functions.
         * */
@@ -154,23 +153,33 @@ struct wim_lookup_table_entry {
         * same as WIMLIB_COMPRESSION_TYPE_NONE.  */
        u16 compression_type : 2;
 
+       /* If resource_location == RESOURCE_IN_WIM, this flag will be set if the
+        * WIM is pipable and therefore the stream is in a slightly different
+        * format.  See comment above write_pipable_wim().  */
+       u16 is_pipable : 1;
+
+       /* Set to 1 when a metadata entry has its checksum changed; in such
+        * cases the hash is no longer valid to verify the data if the metadata
+        * resource is read again.  */
+       u16 dont_check_metadata_hash : 1;
+
        /* (On-disk field)
         * Number of times this lookup table entry is referenced by dentries.
         * Unfortunately, this field is not always set correctly in Microsoft's
         * WIMs, so we have no choice but to fix it if more references to the
-        * lookup table entry are found than stated here. */
+        * lookup table entry are found than stated here.  */
        u32 refcnt;
 
        union {
                /* (On-disk field) SHA1 message digest of the stream referenced
-                * by this lookup table entry */
+                * by this lookup table entry */
                u8  hash[SHA1_HASH_SIZE];
 
                /* First 4 or 8 bytes of the SHA1 message digest, used for
                 * inserting the entry into the hash table.  Since the SHA1
                 * message digest can be considered random, we don't really need
                 * the full 20 byte hash just to insert the entry in a hash
-                * table. */
+                * table.  */
                size_t hash_short;
 
                /* Unhashed entries only (unhashed == 1): these variables make
@@ -223,7 +232,19 @@ struct wim_lookup_table_entry {
                tchar *extracted_file;
        };
 
+       /* Temporary fields  */
        union {
+               /* Used temporarily during WIM file writing  */
+               struct {
+                       struct hlist_node hash_list_2;
+               };
+
+               /* Used temporarily during WIM file writing (after above)  */
+               struct {
+                       struct list_head msg_list;
+                       struct list_head being_compressed_list;
+               };
+
                /* When a WIM file is written, @output_resource_entry is filled
                 * in with the resource entry for the output WIM.  This will not
                 * necessarily be the same as the @resource_entry since:
@@ -233,27 +254,36 @@ struct wim_lookup_table_entry {
                 */
                struct resource_entry output_resource_entry;
 
-               struct {
-                       struct list_head msg_list;
-                       struct list_head being_compressed_list;
-               };
-               struct list_head lte_dentry_list;
 
-               struct {
-                       struct hlist_node hash_list_2;
-
-                       struct list_head write_streams_list;
+               /* Used temporarily during extraction  */
+               union {
+                       /* out_refcnt tracks number of slots filled */
+                       struct wim_dentry *inline_lte_dentries[4];
+                       struct {
+                               struct wim_dentry **lte_dentries;
+                               unsigned long alloc_lte_dentries;
+                       };
                };
        };
 
        /* Temporary list fields */
        union {
-               struct list_head unhashed_list;
-               struct list_head swm_stream_list;
+               /* Links streams when writing lookup table.  */
                struct list_head lookup_table_list;
+
+               /* Links streams being extracted.  */
                struct list_head extraction_list;
+
+               /* Links streams being exported.  */
                struct list_head export_stream_list;
        };
+
+       /* Links streams that are still unhashed after being been added
+        * to a WIM.  */
+       struct list_head unhashed_list;
+
+       /* Links streams being written to the WIM.  */
+       struct list_head write_streams_list;
 };
 
 static inline u64
@@ -284,12 +314,9 @@ wim_resource_compression_type(const struct wim_lookup_table_entry *lte)
 static inline bool
 lte_filename_valid(const struct wim_lookup_table_entry *lte)
 {
-       return 0
+       return     lte->resource_location == RESOURCE_IN_FILE_ON_DISK
        #ifdef __WIN32__
-               || lte->resource_location == RESOURCE_WIN32
                || lte->resource_location == RESOURCE_WIN32_ENCRYPTED
-       #else
-               || lte->resource_location == RESOURCE_IN_FILE_ON_DISK
        #endif
        #ifdef WITH_FUSE
                || lte->resource_location == RESOURCE_IN_STAGING_FILE
@@ -301,15 +328,12 @@ extern struct wim_lookup_table *
 new_lookup_table(size_t capacity) _malloc_attribute;
 
 extern int
-read_lookup_table(WIMStruct *wim);
+read_wim_lookup_table(WIMStruct *wim);
 
 extern int
-write_lookup_table(WIMStruct *wim, int image, struct resource_entry *out_res_entry);
-
-extern int
-write_lookup_table_from_stream_list(struct list_head *stream_list,
-                                   int out_fd,
-                                   struct resource_entry *out_res_entry);
+write_wim_lookup_table(WIMStruct *wim, int image, int write_flags,
+                      struct resource_entry *out_res_entry,
+                      struct list_head *stream_list_override);
 
 extern void
 free_lookup_table(struct wim_lookup_table *table);
@@ -384,8 +408,12 @@ lte_zero_real_refcnt(struct wim_lookup_table_entry *entry, void *ignore);
 extern int
 lte_free_extracted_file(struct wim_lookup_table_entry *lte, void *ignore);
 
+extern void
+lte_init_wim(struct wim_lookup_table_entry *lte, WIMStruct *wim);
+
 extern int
-inode_resolve_ltes(struct wim_inode *inode, struct wim_lookup_table *table);
+inode_resolve_ltes(struct wim_inode *inode, struct wim_lookup_table *table,
+                  bool force);
 
 extern void
 inode_unresolve_ltes(struct wim_inode *inode);
index 577e932a16cc657c2c070c4734ba23801b589217..f37e2f4d2d4f1b352f4413aabe1e95dc4e385e20 100644 (file)
@@ -64,12 +64,13 @@ make_reparse_buffer(const struct reparse_data * restrict rpdata,
 extern int
 wim_inode_get_reparse_data(const struct wim_inode * restrict inode,
                           u8 * restrict rpbuf,
-                          u16 * restrict rpbuflen_ret);
+                          u16 * restrict rpbuflen_ret,
+                          struct wim_lookup_table_entry *lte_override);
 
 #ifndef __WIN32__
 ssize_t
 wim_inode_readlink(const struct wim_inode * restrict inode, char * restrict buf,
-                  size_t buf_len);
+                  size_t buf_len, struct wim_lookup_table_entry *lte);
 
 extern int
 wim_inode_set_symlink(struct wim_inode *inode, const char *target,
index 60da5bf9cd4f0972f2e6dcac598fbf654e7f0557..e030e3362a4c94ee2a58c4761bb9c07b04bea56c 100644 (file)
@@ -2,27 +2,39 @@
 #define _WIMLIB_RESOURCE_H
 
 #include "wimlib/types.h"
+#include "wimlib/endianness.h"
 #include "wimlib/callback.h"
+#include "wimlib/file_io.h"
+#include "wimlib/sha1.h"
 
 struct wim_lookup_table_entry;
 struct wim_image_metadata;
 
-/* Metadata for a resource in a WIM file. */
+/* Description of the location, size, and compression status of a WIM resource
+ * (stream).  This is the in-memory version of `struct resource_entry_disk'.  */
 struct resource_entry {
-       /* Size, in bytes, of the resource in the WIM file. */
+       /* Size, in bytes, of the resource as it appears in the WIM file.  If
+        * the resource is uncompressed, this will be the same as
+        * @original_size.  If the resource is compressed, this will be the
+        * compressed size of the resource, including all compressed chunks as
+        * well as the chunk table.
+        *
+        * Note: if the WIM is "pipable", this value does not include the stream
+        * header.  */
        u64 size  : 56;
 
-       /* Bitwise or of one or more of the WIM_RESHDR_FLAG_* flags. */
+       /* Bitwise OR of one or more of the WIM_RESHDR_FLAG_* flags.  */
        u64 flags : 8;
 
-       /* Offset, in bytes, of the resource in the WIM file. */
+       /* Offset, in bytes, of the resource from the start of the WIM file.  */
        u64 offset;
 
-       /* Uncompressed size of the resource in the WIM file.  Is the same as
-        * @size if the resource is uncompressed. */
+       /* Uncompressed size, in bytes, of the resource (stream).  */
        u64 original_size;
 };
 
+/* On-disk version of `struct resource_entry'.  See `struct resource_entry' for
+ * description of fields.  */
 struct resource_entry_disk {
        u8 size[7];
        u8 flags;
@@ -35,60 +47,42 @@ struct resource_entry_disk {
 /* I haven't seen this flag used in any of the WIMs I have examined.  I assume
  * it means that there are no references to the stream, so the space is free.
  * However, even after deleting files from a WIM mounted with `imagex.exe
- * /mountrw', I could not see this flag being used.  Either way, we don't
+ * /mountrw', I could not see this flag being used.  Either way, wimlib doesn't
  * actually use this flag for anything. */
 #define WIM_RESHDR_FLAG_FREE            0x01
 
-/* Indicates that the stream is a metadata resource for a WIM image. */
+/* Indicates that the stream is a metadata resource for a WIM image.  This flag
+ * is also set in the resource entry for the lookup table in the WIM header.  */
 #define WIM_RESHDR_FLAG_METADATA        0x02
 
-/* Indicates that the stream is compressed. */
+/* Indicates that the stream is compressed (using the WIM's set compression
+ * type).  */
 #define WIM_RESHDR_FLAG_COMPRESSED     0x04
 
 /* I haven't seen this flag used in any of the WIMs I have examined.  Perhaps it
  * means that a stream could possibly be split among multiple split WIM parts.
  * However, `imagex.exe /split' does not seem to create any WIMs like this.
- * Either way, we don't actually use this flag for anything.  */
+ * Either way, wimlib doesn't actually use this flag for anything.  */
 #define WIM_RESHDR_FLAG_SPANNED         0x08
 
-/* Nonzero if a struct resource_entry indicates a compressed resource. */
+/* Functions that operate directly on `struct resource_entry's.  */
+
 static inline int
 resource_is_compressed(const struct resource_entry *entry)
 {
        return (entry->flags & WIM_RESHDR_FLAG_COMPRESSED);
 }
 
-#if 1
-#  define copy_resource_entry(dst, src) memcpy(dst, src, sizeof(struct resource_entry))
-#  define zero_resource_entry(entry) memset(entry, 0, sizeof(struct resource_entry))
-#else
-static inline void
-copy_resource_entry(struct resource_entry *dst,
-                   const struct resource_entry *src)
+static inline void copy_resource_entry(struct resource_entry *dst,
+                                      const struct resource_entry *src)
 {
-       BUILD_BUG_ON(sizeof(struct resource_entry) != 24);
-       ((u64*)dst)[0] = ((u64*)src)[0];
-       ((u64*)dst)[1] = ((u64*)src)[1];
-       ((u64*)dst)[2] = ((u64*)src)[2];
+       memcpy(dst, src, sizeof(struct resource_entry));
 }
 
-static inline void
-zero_resource_entry(struct resource_entry *entry)
+static inline void zero_resource_entry(struct resource_entry *entry)
 {
-       BUILD_BUG_ON(sizeof(struct resource_entry) != 24);
-       ((u64*)entry)[0] = 0;
-       ((u64*)entry)[1] = 0;
-       ((u64*)entry)[2] = 0;
+       memset(entry, 0, sizeof(struct resource_entry));
 }
-#endif
-
-#define WIMLIB_RESOURCE_FLAG_RAW               0x1
-#define WIMLIB_RESOURCE_FLAG_RECOMPRESS                0x2
-
-extern int
-read_resource_prefix(const struct wim_lookup_table_entry *lte,
-                    u64 size, consume_data_callback_t cb, void *ctx_or_buf,
-                    int flags);
 
 extern void
 get_resource_entry(const struct resource_entry_disk *disk_entry,
@@ -98,6 +92,26 @@ extern void
 put_resource_entry(const struct resource_entry *entry,
                   struct resource_entry_disk *disk_entry);
 
+/* wimlib internal flags used when reading or writing resources.  */
+#define WIMLIB_WRITE_RESOURCE_FLAG_RECOMPRESS          0x00000001
+#define WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE             0x00000002
+#define WIMLIB_WRITE_RESOURCE_MASK                     0x0000ffff
+
+#define WIMLIB_READ_RESOURCE_FLAG_RAW_FULL             0x80000000
+#define WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS           0x40000000
+#define WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY            0x20000000
+#define WIMLIB_READ_RESOURCE_FLAG_RAW          (WIMLIB_READ_RESOURCE_FLAG_RAW_FULL |  \
+                                                WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS)
+#define WIMLIB_READ_RESOURCE_MASK                      0xffff0000
+
+
+/* Functions to read a resource.  */
+
+extern int
+read_partial_wim_resource(const struct wim_lookup_table_entry *lte,
+                         u64 size, consume_data_callback_t cb,
+                         void *ctx_or_buf, int flags, u64 offset);
+
 extern int
 read_partial_wim_resource_into_buf(const struct wim_lookup_table_entry *lte,
                                   size_t size, u64 offset, void *buf);
@@ -105,9 +119,33 @@ extern int
 read_full_resource_into_buf(const struct wim_lookup_table_entry *lte, void *buf);
 
 extern int
-write_wim_resource(struct wim_lookup_table_entry *lte, int out_fd,
+read_full_resource_into_alloc_buf(const struct wim_lookup_table_entry *lte,
+                                 void **buf_ret);
+
+extern int
+res_entry_to_data(const struct resource_entry *res_entry,
+                 WIMStruct *wim, void **buf_ret);
+
+extern int
+read_resource_prefix(const struct wim_lookup_table_entry *lte,
+                    u64 size, consume_data_callback_t cb, void *ctx_or_buf,
+                    int flags);
+
+/* Functions to write a resource.  */
+
+extern int
+write_wim_resource(struct wim_lookup_table_entry *lte, struct filedes *out_fd,
                   int out_ctype, struct resource_entry *out_res_entry,
-                  int flags);
+                  int write_resource_flags);
+
+extern int
+write_wim_resource_from_buffer(const void *buf, size_t buf_size,
+                              int reshdr_flags, struct filedes *out_fd,
+                              int out_ctype,
+                              struct resource_entry *out_res_entry,
+                              u8 *hash_ret, int write_resource_flags);
+
+/* Functions to extract a resource.  */
 
 extern int
 extract_wim_resource(const struct wim_lookup_table_entry *lte,
@@ -117,19 +155,47 @@ extract_wim_resource(const struct wim_lookup_table_entry *lte,
 
 extern int
 extract_wim_resource_to_fd(const struct wim_lookup_table_entry *lte,
-                          int fd, u64 size);
+                          struct filedes *fd, u64 size);
+
+/* Miscellaneous resource functions.  */
 
 extern int
 sha1_resource(struct wim_lookup_table_entry *lte);
 
-extern int
-copy_resource(struct wim_lookup_table_entry *lte, void *wim);
+/* Functions to read/write metadata resources.  */
 
 extern int
 read_metadata_resource(WIMStruct *wim,
                       struct wim_image_metadata *image_metadata);
 
 extern int
-write_metadata_resource(WIMStruct *wim);
+write_metadata_resource(WIMStruct *wim, int image, int write_resource_flags);
+
+/* Definitions specific to pipable WIM resources.  */
+
+/* Arbitrary number to begin each stream in the pipable WIM, used for sanity
+ * checking.  */
+#define PWM_STREAM_MAGIC 0x2b9b9ba2443db9d8ULL
+
+/* Header that precedes each resource in a pipable WIM.  */
+struct pwm_stream_hdr {
+       le64 magic;                     /* +0   */
+       le64 uncompressed_size;         /* +8   */
+       u8 hash[SHA1_HASH_SIZE];        /* +16  */
+       le32 flags;                     /* +36  */
+                                       /* +40  */
+} _packed_attribute;
+
+/* Extra flag for the @flags field in `struct pipable_wim_stream_hdr': Indicates
+ * that the SHA1 message digest of the stream has not been calculated.
+ * Currently only used for the XML data.  */
+#define PWM_RESHDR_FLAG_UNHASHED         0x100
+
+/* Header that precedes each chunk of a compressed resource in a pipable WIM.
+ */
+struct pwm_chunk_hdr {
+       le32 compressed_size;
+} _packed_attribute;
+
 
 #endif /* _WIMLIB_RESOURCE_H */
index 1ef55ddd6b0721a26acd8319101c679cbd7dbfea..dfd31fa3f9a758720762faa5d922335202169f45 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "wimlib/header.h"
 #include "wimlib/types.h"
+#include "wimlib/file_io.h"
 
 struct wim_info;
 struct wim_lookup_table;
@@ -11,14 +12,15 @@ struct wim_image_metadata;
 /* The opaque structure exposed to the wimlib API. */
 struct WIMStruct {
 
-       /* File descriptor for the WIM file, opened for reading, or -1 if it has
-        * not been opened or there is no associated file backing it yet. */
-       int in_fd;
+       /* File descriptor for the WIM file, opened for reading.  in_fd.fd is -1
+        * if the WIM file has not been opened or there is no associated file
+        * backing it yet. */
+       struct filedes in_fd;
 
        /* File descriptor, opened either for writing only or for
         * reading+writing, for the WIM file (if any) currently being written.
         * */
-       int out_fd;
+       struct filedes out_fd;
 
        /* The name of the WIM file (if any) that has been opened. */
        tchar *filename;
@@ -60,23 +62,35 @@ struct WIMStruct {
        u8 compression_type : 2;
 };
 
+static inline bool wim_is_pipable(const WIMStruct *wim)
+{
+       return (wim->hdr.magic == PWM_MAGIC);
+}
+
+static inline bool wim_has_integrity_table(const WIMStruct *wim)
+{
+       return (wim->hdr.integrity.offset != 0);
+}
+
 extern void
 wim_recalculate_refcnts(WIMStruct *wim);
 
 extern int
-read_header(const tchar *filename, int in_fd, struct wim_header *hdr);
+init_wim_header(struct wim_header *hdr, int ctype);
 
 extern int
-write_header(const struct wim_header *hdr, int out_fd);
+read_wim_header(const tchar *filename, struct filedes *in_fd,
+               struct wim_header *hdr);
 
 extern int
-init_header(struct wim_header *hdr, int ctype);
+write_wim_header(const struct wim_header *hdr, struct filedes *out_fd);
 
 extern int
-write_header_flags(u32 hdr_flags, int out_fd);
+write_wim_header_at_offset(const struct wim_header *hdr, struct filedes *out_fd,
+                          off_t offset);
 
 extern int
-write_header_part_data(u16 part_number, u16 total_parts, int out_fd);
+write_wim_header_flags(u32 hdr_flags, struct filedes *out_fd);
 
 extern int
 rename_wim_path(WIMStruct *wim, const tchar *from, const tchar *to);
@@ -93,6 +107,16 @@ wim_checksum_unhashed_streams(WIMStruct *wim);
 extern int
 reopen_wim(WIMStruct *wim);
 
+/* Internal open flags (pass to open_wim_as_WIMStruct(), not wimlib_open_wim())
+ */
+#define WIMLIB_OPEN_FLAG_FROM_PIPE     0x80000000
+#define WIMLIB_OPEN_MASK_PUBLIC                0x7fffffff
+
+extern int
+open_wim_as_WIMStruct(const void *wim_filename_or_fd, int open_flags,
+                     WIMStruct **wim_ret,
+                     wimlib_progress_func_t progress_func);
+
 extern int
 close_wim(WIMStruct *wim);
 
index d84b0ffd46720a020ba64221577c1d5d26103f8c..4366c1afbfc11377b21e9174eba4efd35c9f128c 100644 (file)
@@ -30,7 +30,8 @@ extern int
 win32_error_to_errno(DWORD err_code);
 
 extern int
-win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret);
+win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret,
+                   bool *supports_SetFileShortName_ret);
 
 extern HANDLE
 win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess);
index 1722dab82e95f930b38e504f1e0c29e33e9664f9..7a7bae619a760b13c1a6a7321ba5d4ab58970b6a 100644 (file)
@@ -5,20 +5,14 @@
 #include "wimlib/types.h"
 
 /* Internal use only */
-#define WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE      0x80000000
-#define WIMLIB_WRITE_FLAG_REUSE_INTEGRITY_TABLE 0x40000000
-#define WIMLIB_WRITE_FLAG_CHECKPOINT_AFTER_XML  0x20000000
-#define WIMLIB_WRITE_MASK_PUBLIC               0x1fffffff
-
-extern int
-begin_write(WIMStruct *wim, const tchar *path, int write_flags);
-
-extern void
-close_wim_writable(WIMStruct *wim);
-
-extern int
-finish_write(WIMStruct *wim, int image, int write_flags,
-            wimlib_progress_func_t progress_func);
+#define WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE              0x80000000
+#define WIMLIB_WRITE_FLAG_CHECKPOINT_AFTER_XML         0x40000000
+#define WIMLIB_WRITE_FLAG_REUSE_INTEGRITY_TABLE                0x20000000
+#define WIMLIB_WRITE_FLAG_HEADER_AT_END                        0x10000000
+#define WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR              0x08000000
+#define WIMLIB_WRITE_FLAG_USE_EXISTING_TOTALBYTES      0x04000000
+#define WIMLIB_WRITE_FLAG_NO_METADATA                   0x02000000
+#define WIMLIB_WRITE_MASK_PUBLIC                       0x01ffffff
 
 #if defined(HAVE_SYS_FILE_H) && defined(HAVE_FLOCK)
 extern int
@@ -31,4 +25,18 @@ lock_wim(WIMStruct *wim, int fd)
 }
 #endif
 
+struct list_head;
+
+int
+write_wim_part(WIMStruct *wim,
+              const void *path_or_fd,
+              int image,
+              int write_flags,
+              unsigned num_threads,
+              wimlib_progress_func_t progress_func,
+              unsigned part_number,
+              unsigned total_parts,
+              struct list_head *stream_list_override,
+              const u8 *guid);
+
 #endif /* _WIMLIB_WRITE_H */
index 0f0d36834f8279921d0185ce9f52ee6a058b07dd..ce82b14a44411310ee24dd92b873246ad17f345d 100644 (file)
@@ -2,20 +2,20 @@
 #define _WIMLIB_XML_H
 
 #include "wimlib/types.h"
+#include "wimlib/file_io.h"
 
-struct image_info;
+struct wim_info;
 struct resource_entry;
 
-/* A struct wim_info structure corresponds to the entire XML data for a WIM file. */
-struct wim_info {
-       u64 total_bytes;
-       int num_images;
-       /* Array of `struct image_info's, one for each image in the WIM that is
-        * mentioned in the XML data. */
-       struct image_info *images;
-};
+extern u64
+wim_info_get_total_bytes(const struct wim_info *info);
+
+extern u64
+wim_info_get_image_total_bytes(const struct wim_info *info, int image);
+
+extern unsigned
+wim_info_get_num_images(const struct wim_info *info);
 
-/* xml.c */
 extern int
 xml_export_image(const struct wim_info *old_wim_info, int image,
                 struct wim_info **new_wim_info_p,
@@ -40,13 +40,16 @@ free_wim_info(struct wim_info *info);
 extern void
 print_image_info(const struct wim_info *wim_info, int image);
 
+#define WIM_TOTALBYTES_USE_EXISTING  ((u64)0 - 1)
+#define WIM_TOTALBYTES_OMIT          ((u64)0 - 2)
+
 extern int
-read_xml_data(int in_fd, const struct resource_entry *res,
-             struct wim_info **info_ret);
+read_wim_xml_data(WIMStruct *wim);
 
 extern int
-write_xml_data(const struct wim_info *wim_info, int image, int out_fd,
-              u64 total_bytes, struct resource_entry *out_res_entry);
+write_wim_xml_data(WIMStruct *wim, int image,
+                  u64 total_bytes, struct resource_entry *out_res_entry,
+                  int write_resource_flags);
 
 extern void
 libxml_global_init(void);
@@ -54,18 +57,6 @@ libxml_global_init(void);
 extern void
 libxml_global_cleanup(void);
 
-static inline u64
-wim_info_get_total_bytes(const struct wim_info *info)
-{
-       return info->total_bytes;
-}
-
-static inline unsigned
-wim_info_get_num_images(const struct wim_info *info)
-{
-       return info->num_images;
-}
-
 #ifdef ENABLE_CUSTOM_MEMORY_ALLOCATOR
 extern void
 xml_set_memory_allocator(void *(*malloc_func)(size_t),
index f0b17f01e51d06feda1f35b270994e1226a168c7..3ec85fc58007002ba70abb32a7f79a96a28fd912 100644 (file)
@@ -51,6 +51,7 @@ typedef wchar_t tchar;
 #  define tstrerror    _wcserror
 #  define taccess      _waccess
 #  define tstrdup      wcsdup
+#  define ttempnam      _wtempnam
 /* The following "tchar" functions do not have exact wide-character equivalents
  * on Windows so require parameter rearrangement or redirection to a replacement
  * function defined ourselves. */
@@ -109,6 +110,7 @@ typedef char tchar;
 #  define tstrtoul     strtoul
 #  define tmkdir       mkdir
 #  define tstrdup      strdup
+#  define ttempnam      tempnam
 #  define TSTRDUP      STRDUP
 #  define tstrerror_r  strerror_r
 #  define trename      rename
index 8a22681952415d410ead9b38825cad63b48b4c0d..840ea2c757109e955cdd54b55f8a57e8e5dc1291 100755 (executable)
@@ -1,13 +1,13 @@
 #!/bin/bash
 
-oldver='1\.4\.1'
-oldmonth=May
+oldver='1\.4\.2'
+oldmonth=June
 oldyear=2013
 
 newmajor=1
-newminor=4
-newpatch=2
-newmonth=June
+newminor=5
+newpatch=0
+newmonth=August
 newyear=2013
 
 
index 9e93c4cc8510cf9b27931d6358918e6b610facb6..e6e751aa303a979c9472dd40a23655936a728540 100644 (file)
@@ -13,6 +13,8 @@
 #include <string.h>
 #include <assert.h>
 #include <stdio.h>
+#include <fcntl.h>
+#include <io.h>
 
 /* Replacement for glob() in Windows native builds that operates on wide
  * characters. */
@@ -260,3 +262,7 @@ win32_wbasename(wchar_t *path)
        return p;
 }
 
+void set_fd_to_binary_mode(int fd)
+{
+       _setmode(fd, _O_BINARY);
+}
index 5f21fd948836f9c0129b49d57c866f6e49c1149a..0d5f5ad2a4a446e56ffa4f98193c6b8e5b5fb47b 100644 (file)
@@ -45,6 +45,9 @@ win32_mbs_to_wcs(const char *mbs, size_t mbs_nbytes, size_t *num_wchars_ret);
 extern wchar_t *
 win32_wbasename(wchar_t *path);
 
+extern void
+set_fd_to_binary_mode(int fd);
+
 #include "wgetopt.h"
 
 #define optarg                 woptarg
index 4e05f5b4fbe335dce37e1d9f07cdbd8e56772ef0..f66bcd5bf5d1021c0c2613a1277d680f62d5c41b 100644 (file)
@@ -60,6 +60,9 @@
 #  define tglob                glob
 #  define OS_PREFERRED_PATH_SEPARATOR '/'
 #  define OS_PREFERRED_PATH_SEPARATOR_STRING "/"
+static inline void set_fd_to_binary_mode(int fd)
+{
+}
 #endif /* !__WIN32 */
 
 
@@ -90,17 +93,22 @@ static void usage(int cmd_type);
 static void usage_all(void);
 
 static bool imagex_be_quiet = false;
+static FILE *imagex_info_file;
+
+#define imagex_printf(format, ...) \
+               tfprintf(imagex_info_file, format, ##__VA_ARGS__)
 
 
 static const tchar *usage_strings[] = {
 [APPEND] =
 T(
 IMAGEX_PROGNAME" append (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
-"                     [DESCRIPTION] [--boot] [--check] [--flags EDITION_ID]\n"
-"                     [--verbose] [--dereference] [--config=FILE]\n"
-"                     [--threads=NUM_THREADS] [--rebuild] [--unix-data]\n"
-"                     [--source-list] [--no-acls] [--strict-acls]\n"
-"                     [--rpfix] [--norpfix]\n"
+"                    [DESCRIPTION] [--boot] [--check] [--nocheck]\n"
+"                    [--flags EDITION_ID] [--verbose] [--dereference]\n"
+"                    [--config=FILE] [--threads=NUM_THREADS] [--rebuild]\n"
+"                    [--unix-data] [--source-list] [--no-acls]\n"
+"                    [--strict-acls] [--rpfix] [--norpfix] [--pipable]\n"
+"                    [--not-pipable]\n"
 ),
 [APPLY] =
 T(
@@ -113,15 +121,17 @@ IMAGEX_PROGNAME" apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all]\n"
 [CAPTURE] =
 T(
 IMAGEX_PROGNAME" capture (DIRECTORY | NTFS_VOLUME) WIMFILE [IMAGE_NAME]\n"
-"                      [DESCRIPTION] [--boot] [--check] [--compress=TYPE]\n"
-"                      [--flags EDITION_ID] [--verbose] [--dereference]\n"
-"                      [--config=FILE] [--threads=NUM_THREADS] [--unix-data]\n"
-"                      [--source-list] [--no-acls] [--strict-acls]\n"
-"                      [--rpfix] [--norpfix]\n"
+"                    [DESCRIPTION] [--boot] [--check] [--nocheck]\n"
+"                    [--compress=TYPE] [--flags EDITION_ID] [--verbose]\n"
+"                    [--dereference] [--config=FILE]\n"
+"                    [--threads=NUM_THREADS] [--unix-data] [--source-list]\n"
+"                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
+"                    [--pipable] [--not-pipable]\n"
 ),
 [DELETE] =
 T(
-IMAGEX_PROGNAME" delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check] [--soft]\n"
+IMAGEX_PROGNAME" delete WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--check]\n"
+"                    [--soft]\n"
 ),
 [DIR] =
 T(
@@ -130,22 +140,24 @@ IMAGEX_PROGNAME" dir WIMFILE (IMAGE_NUM | IMAGE_NAME | all) [--path=PATH]\n"
 [EXPORT] =
 T(
 IMAGEX_PROGNAME" export SRC_WIMFILE (SRC_IMAGE_NUM | SRC_IMAGE_NAME | all ) \n"
-"              DEST_WIMFILE [DEST_IMAGE_NAME] [DEST_IMAGE_DESCRIPTION]\n"
-"              [--boot] [--check] [--compress=TYPE] [--ref=\"GLOB\"]\n"
-"              [--threads=NUM_THREADS] [--rebuild]\n"
+"                    DEST_WIMFILE [DEST_IMAGE_NAME] [DEST_IMAGE_DESCRIPTION]\n"
+"                    [--boot] [--check] [--nocheck] [--compress=TYPE]\n"
+"                    [--ref=\"GLOB\"] [--threads=NUM_THREADS] [--rebuild]\n"
+"                    [--pipable] [--not-pipable]\n"
 ),
 [EXTRACT] =
 T(
 IMAGEX_PROGNAME" extract WIMFILE (IMAGE_NUM | IMAGE_NAME) [PATH...]\n"
-"              [--check] [--ref=\"GLOB\"] [--verbose] [--unix-data]\n"
-"              [--no-acls] [--strict-acls] [--to-stdout] [--dest-dir=DIR]\n"
-"              [--include-invalid-names]\n"
+"                    [--check] [--ref=\"GLOB\"] [--verbose] [--unix-data]\n"
+"                    [--no-acls] [--strict-acls] [--to-stdout] [--dest-dir=DIR]\n"
+"                    [--include-invalid-names]\n"
 ),
 [INFO] =
 T(
 IMAGEX_PROGNAME" info WIMFILE [IMAGE_NUM | IMAGE_NAME] [NEW_NAME]\n"
-"                   [NEW_DESC] [--boot] [--check] [--header] [--lookup-table]\n"
-"                   [--xml] [--extract-xml FILE] [--metadata]\n"
+"                    [NEW_DESC] [--boot] [--check] [--nocheck] [--header]\n"
+"                    [--lookup-table] [--xml] [--extract-xml FILE]\n"
+"                    [--metadata]\n"
 ),
 [JOIN] =
 T(
@@ -160,13 +172,13 @@ IMAGEX_PROGNAME" mount WIMFILE (IMAGE_NUM | IMAGE_NAME) DIRECTORY\n"
 [MOUNTRW] =
 T(
 IMAGEX_PROGNAME" mountrw WIMFILE [IMAGE_NUM | IMAGE_NAME] DIRECTORY\n"
-"                      [--check] [--debug] [--streams-interface=INTERFACE]\n"
-"                      [--staging-dir=DIR] [--unix-data] [--allow-other]\n"
+"                    [--check] [--debug] [--streams-interface=INTERFACE]\n"
+"                    [--staging-dir=DIR] [--unix-data] [--allow-other]\n"
 ),
 [OPTIMIZE] =
 T(
-IMAGEX_PROGNAME" optimize WIMFILE [--check] [--recompress]\n"
-"                      [--threads=NUM_THREADS]\n"
+IMAGEX_PROGNAME" optimize WIMFILE [--check] [--nocheck] [--recompress]\n"
+"                    [--threads=NUM_THREADS] [--pipable] [--not-pipable]\n"
 ),
 [SPLIT] =
 T(
@@ -179,8 +191,8 @@ IMAGEX_PROGNAME" unmount DIRECTORY [--commit] [--check] [--rebuild] [--lazy]\n"
 [UPDATE] =
 T(
 IMAGEX_PROGNAME" update WIMFILE [IMAGE_NUM | IMAGE_NAME] [--check] [--rebuild]\n"
-"                       [--threads=NUM_THREADS] [DEFAULT_ADD_OPTIONS]\n"
-"                       [DEFAULT_DELETE_OPTIONS] [--command=STRING] [< CMDFILE]\n"
+"                    [--threads=NUM_THREADS] [DEFAULT_ADD_OPTIONS]\n"
+"                    [DEFAULT_DELETE_OPTIONS] [--command=STRING] [< CMDFILE]\n"
 ),
 };
 
@@ -220,8 +232,11 @@ enum {
        IMAGEX_LOOKUP_TABLE_OPTION,
        IMAGEX_METADATA_OPTION,
        IMAGEX_NORPFIX_OPTION,
+       IMAGEX_NOCHECK_OPTION,
        IMAGEX_NO_ACLS_OPTION,
+       IMAGEX_NOT_PIPABLE_OPTION,
        IMAGEX_PATH_OPTION,
+       IMAGEX_PIPABLE_OPTION,
        IMAGEX_REBUILD_OPTION,
        IMAGEX_RECOMPRESS_OPTION,
        IMAGEX_RECURSIVE_OPTION,
@@ -258,6 +273,8 @@ static const struct option apply_options[] = {
 static const struct option capture_or_append_options[] = {
        {T("boot"),        no_argument,       NULL, IMAGEX_BOOT_OPTION},
        {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
+       {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
        {T("config"),      required_argument, NULL, IMAGEX_CONFIG_OPTION},
        {T("dereference"), no_argument,       NULL, IMAGEX_DEREFERENCE_OPTION},
@@ -272,6 +289,8 @@ static const struct option capture_or_append_options[] = {
        {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
        {T("rpfix"),       no_argument,       NULL, IMAGEX_RPFIX_OPTION},
        {T("norpfix"),     no_argument,       NULL, IMAGEX_NORPFIX_OPTION},
+       {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
+       {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
        {NULL, 0, NULL, 0},
 };
 static const struct option delete_options[] = {
@@ -286,12 +305,16 @@ static const struct option dir_options[] = {
 };
 
 static const struct option export_options[] = {
-       {T("boot"),       no_argument,       NULL, IMAGEX_BOOT_OPTION},
-       {T("check"),      no_argument,       NULL, IMAGEX_CHECK_OPTION},
-       {T("compress"),   required_argument, NULL, IMAGEX_COMPRESS_OPTION},
-       {T("ref"),        required_argument, NULL, IMAGEX_REF_OPTION},
-       {T("threads"),    required_argument, NULL, IMAGEX_THREADS_OPTION},
-       {T("rebuild"),    no_argument,       NULL, IMAGEX_REBUILD_OPTION},
+       {T("boot"),        no_argument,       NULL, IMAGEX_BOOT_OPTION},
+       {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
+       {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("compress"),    required_argument, NULL, IMAGEX_COMPRESS_OPTION},
+       {T("ref"),         required_argument, NULL, IMAGEX_REF_OPTION},
+       {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
+       {T("rebuild"),     no_argument,       NULL, IMAGEX_REBUILD_OPTION},
+       {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
+       {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -312,6 +335,8 @@ static const struct option extract_options[] = {
 static const struct option info_options[] = {
        {T("boot"),         no_argument,       NULL, IMAGEX_BOOT_OPTION},
        {T("check"),        no_argument,       NULL, IMAGEX_CHECK_OPTION},
+       {T("nocheck"),      no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("no-check"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
        {T("extract-xml"),  required_argument, NULL, IMAGEX_EXTRACT_XML_OPTION},
        {T("header"),       no_argument,       NULL, IMAGEX_HEADER_OPTION},
        {T("lookup-table"), no_argument,       NULL, IMAGEX_LOOKUP_TABLE_OPTION},
@@ -337,9 +362,13 @@ static const struct option mount_options[] = {
 };
 
 static const struct option optimize_options[] = {
-       {T("check"),      no_argument, NULL, IMAGEX_CHECK_OPTION},
-       {T("recompress"), no_argument, NULL, IMAGEX_RECOMPRESS_OPTION},
-       {T("threads"),    required_argument, NULL, IMAGEX_THREADS_OPTION},
+       {T("check"),       no_argument,       NULL, IMAGEX_CHECK_OPTION},
+       {T("nocheck"),     no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("no-check"),    no_argument,       NULL, IMAGEX_NOCHECK_OPTION},
+       {T("recompress"),  no_argument,       NULL, IMAGEX_RECOMPRESS_OPTION},
+       {T("threads"),     required_argument, NULL, IMAGEX_THREADS_OPTION},
+       {T("pipable"),     no_argument,       NULL, IMAGEX_PIPABLE_OPTION},
+       {T("not-pipable"), no_argument,       NULL, IMAGEX_NOT_PIPABLE_OPTION},
        {NULL, 0, NULL, 0},
 };
 
@@ -1053,11 +1082,11 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                        const tchar *data_type;
 
                        data_type = get_data_type(info->write_streams.compression_type);
-                       tprintf(T("Writing %"TS" data using %u thread%"TS"\n"),
+                       imagex_printf(T("Writing %"TS" data using %u thread%"TS"\n"),
                                data_type, info->write_streams.num_threads,
                                (info->write_streams.num_threads == 1) ? T("") : T("s"));
                }
-               tprintf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
+               imagex_printf(T("\r%"PRIu64" %"TS" of %"PRIu64" %"TS" (uncompressed) "
                        "written (%u%% done)"),
                        info->write_streams.completed_bytes >> unit_shift,
                        unit_name,
@@ -1065,23 +1094,23 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                        unit_name,
                        percent_done);
                if (info->write_streams.completed_bytes >= info->write_streams.total_bytes)
-                       tputchar(T('\n'));
+                       imagex_printf(T("\n"));
                break;
        case WIMLIB_PROGRESS_MSG_SCAN_BEGIN:
-               tprintf(T("Scanning \"%"TS"\""), info->scan.source);
+               imagex_printf(T("Scanning \"%"TS"\""), info->scan.source);
                if (*info->scan.wim_target_path) {
-                       tprintf(T(" (loading as WIM path: "
+                       imagex_printf(T(" (loading as WIM path: "
                                  "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\")...\n"),
                               info->scan.wim_target_path);
                } else {
-                       tprintf(T(" (loading as root of WIM image)...\n"));
+                       imagex_printf(T(" (loading as root of WIM image)...\n"));
                }
                break;
        case WIMLIB_PROGRESS_MSG_SCAN_DENTRY:
                if (info->scan.excluded)
-                       tprintf(T("Excluding \"%"TS"\" from capture\n"), info->scan.cur_path);
+                       imagex_printf(T("Excluding \"%"TS"\" from capture\n"), info->scan.cur_path);
                else
-                       tprintf(T("Scanning \"%"TS"\"\n"), info->scan.cur_path);
+                       imagex_printf(T("Scanning \"%"TS"\"\n"), info->scan.cur_path);
                break;
        /*case WIMLIB_PROGRESS_MSG_SCAN_END:*/
                /*break;*/
@@ -1089,7 +1118,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
                percent_done = TO_PERCENT(info->integrity.completed_bytes,
                                          info->integrity.total_bytes);
-               tprintf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
+               imagex_printf(T("\rVerifying integrity of \"%"TS"\": %"PRIu64" %"TS" "
                        "of %"PRIu64" %"TS" (%u%%) done"),
                        info->integrity.filename,
                        info->integrity.completed_bytes >> unit_shift,
@@ -1098,13 +1127,13 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                        unit_name,
                        percent_done);
                if (info->integrity.completed_bytes == info->integrity.total_bytes)
-                       tputchar(T('\n'));
+                       imagex_printf(T("\n"));
                break;
        case WIMLIB_PROGRESS_MSG_CALC_INTEGRITY:
                unit_shift = get_unit(info->integrity.total_bytes, &unit_name);
                percent_done = TO_PERCENT(info->integrity.completed_bytes,
                                          info->integrity.total_bytes);
-               tprintf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
+               imagex_printf(T("\rCalculating integrity table for WIM: %"PRIu64" %"TS" "
                          "of %"PRIu64" %"TS" (%u%%) done"),
                        info->integrity.completed_bytes >> unit_shift,
                        unit_name,
@@ -1112,10 +1141,10 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                        unit_name,
                        percent_done);
                if (info->integrity.completed_bytes == info->integrity.total_bytes)
-                       tputchar(T('\n'));
+                       imagex_printf(T("\n"));
                break;
        case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN:
-               tprintf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
+               imagex_printf(T("Applying image %d (\"%"TS"\") from \"%"TS"\" "
                          "to %"TS" \"%"TS"\"\n"),
                        info->extract.image,
                        info->extract.image_name,
@@ -1125,7 +1154,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                        info->extract.target);
                break;
        case WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN:
-               tprintf(T("Extracting "
+               imagex_printf(T("Extracting "
                          "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\" from image %d (\"%"TS"\") "
                          "in \"%"TS"\" to \"%"TS"\"\n"),
                        info->extract.extract_root_wim_source_path,
@@ -1135,14 +1164,14 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                        info->extract.target);
                break;
        /*case WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN:*/
-               /*tprintf(T("Applying directory structure to %"TS"\n"),*/
+               /*imagex_printf(T("Applying directory structure to %"TS"\n"),*/
                        /*info->extract.target);*/
                /*break;*/
        case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS:
                percent_done = TO_PERCENT(info->extract.completed_bytes,
                                          info->extract.total_bytes);
                unit_shift = get_unit(info->extract.total_bytes, &unit_name);
-               tprintf(T("\rExtracting files: "
+               imagex_printf(T("\rExtracting files: "
                          "%"PRIu64" %"TS" of %"PRIu64" %"TS" (%u%%) done"),
                        info->extract.completed_bytes >> unit_shift,
                        unit_name,
@@ -1150,18 +1179,15 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                        unit_name,
                        percent_done);
                if (info->extract.completed_bytes >= info->extract.total_bytes)
-                       tputchar(T('\n'));
-               break;
-       case WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY:
-               tprintf(T("%"TS"\n"), info->extract.cur_path);
+                       imagex_printf(T("\n"));
                break;
        case WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS:
                if (info->extract.extract_root_wim_source_path[0] == T('\0'))
-                       tprintf(T("Setting timestamps on all extracted files...\n"));
+                       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) {
-                       tprintf(T("Unmounting NTFS volume \"%"TS"\"...\n"),
+                       imagex_printf(T("Unmounting NTFS volume \"%"TS"\"...\n"),
                                info->extract.target);
                }
                break;
@@ -1169,7 +1195,7 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                percent_done = TO_PERCENT(info->join.completed_bytes,
                                          info->join.total_bytes);
                unit_shift = get_unit(info->join.total_bytes, &unit_name);
-               tprintf(T("Writing resources from part %u of %u: "
+               imagex_printf(T("Writing resources from part %u of %u: "
                          "%"PRIu64 " %"TS" of %"PRIu64" %"TS" (%u%%) written\n"),
                        (info->join.completed_parts == info->join.total_parts) ?
                        info->join.completed_parts : info->join.completed_parts + 1,
@@ -1184,9 +1210,11 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                percent_done = TO_PERCENT(info->split.completed_bytes,
                                          info->split.total_bytes);
                unit_shift = get_unit(info->split.total_bytes, &unit_name);
-               tprintf(T("Writing \"%"TS"\": %"PRIu64" %"TS" of "
+               imagex_printf(T("Writing \"%"TS"\" (part %u of %u): %"PRIu64" %"TS" of "
                          "%"PRIu64" %"TS" (%u%%) written\n"),
                        info->split.part_name,
+                       info->split.cur_part_number,
+                       info->split.total_parts,
                        info->split.completed_bytes >> unit_shift,
                        unit_name,
                        info->split.total_bytes >> unit_shift,
@@ -1195,19 +1223,20 @@ imagex_progress_func(enum wimlib_progress_msg msg,
                break;
        case WIMLIB_PROGRESS_MSG_SPLIT_END_PART:
                if (info->split.completed_bytes == info->split.total_bytes) {
-                       tprintf(T("Finished writing %u split WIM parts\n"),
-                               info->split.cur_part_number);
+                       imagex_printf(T("Finished writing part %u of %u WIM parts\n"),
+                               info->split.cur_part_number,
+                               info->split.total_parts);
                }
                break;
        case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND:
                switch (info->update.command->op) {
                case WIMLIB_UPDATE_OP_DELETE:
-                       tprintf(T("Deleted WIM path "
+                       imagex_printf(T("Deleted WIM path "
                                  "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\"\n"),
                                info->update.command->delete.wim_path);
                        break;
                case WIMLIB_UPDATE_OP_RENAME:
-                       tprintf(T("Renamed WIM path "
+                       imagex_printf(T("Renamed WIM path "
                                  "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\" => "
                                  "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\"\n"),
                                info->update.command->rename.wim_source_path,
@@ -1511,7 +1540,7 @@ parse_update_command_file(tchar **cmd_file_contents_p, size_t cmd_file_nchars,
 }
 
 /* Apply one image, or all images, from a WIM file into a directory, OR apply
- * one image from a WIM file to a NTFS volume. */
+ * one image from a WIM file to a NTFS volume.  */
 static int
 imagex_apply(int argc, tchar **argv)
 {
@@ -1522,6 +1551,7 @@ imagex_apply(int argc, tchar **argv)
        int ret;
        const tchar *wimfile;
        const tchar *target;
+       const tchar *image_num_or_name;
        int extract_flags = WIMLIB_EXTRACT_FLAG_SEQUENTIAL;
 
        const tchar *swm_glob = NULL;
@@ -1575,46 +1605,62 @@ imagex_apply(int argc, tchar **argv)
 
        wimfile = argv[0];
 
-       ret = wimlib_open_wim(wimfile, open_flags, &wim, imagex_progress_func);
-       if (ret)
-               goto out;
-
-       if (argc >= 3) {
-               /* Image explicitly specified.  */
-               image = wimlib_resolve_image(wim, argv[1]);
-               ret = verify_image_exists(image, argv[1], wimfile);
-               if (ret)
-                       goto out_wimlib_free;
+       if (!tstrcmp(wimfile, T("-"))) {
+               /* Attempt to apply pipable WIM from standard input.  */
+               if (argc < 3) {
+                       imagex_error(T("Imagex index or name must be explicitly "
+                                      "specified when applying pipable WIM on "
+                                      "standard input."));
+                       goto out_usage;
+               }
+               image_num_or_name = argv[1];
                target = argv[2];
+               wim = NULL;
+               num_additional_swms = 0;
+               additional_swms = NULL;
        } else {
-               /* No image specified; default to image 1, but only if the WIM
-                * contains exactly one image.  */
-               struct wimlib_wim_info info;
+               ret = wimlib_open_wim(wimfile, open_flags, &wim,
+                                     imagex_progress_func);
+               if (ret)
+                       goto out;
 
-               wimlib_get_wim_info(wim, &info);
-               if (info.image_count != 1) {
-                       imagex_error(T("\"%"TS"\" contains %d images; "
-                                      "Please select one (or all)."),
-                                    wimfile, info.image_count);
-                       wimlib_free(wim);
-                       goto out_usage;
+               if (argc >= 3) {
+                       /* Image explicitly specified.  */
+                       image_num_or_name = argv[1];
+                       image = wimlib_resolve_image(wim, image_num_or_name);
+                       ret = verify_image_exists(image, image_num_or_name, wimfile);
+                       if (ret)
+                               goto out_wimlib_free;
+                       target = argv[2];
+               } else {
+                       /* No image specified; default to image 1, but only if the WIM
+                        * contains exactly one image.  */
+                       struct wimlib_wim_info info;
+
+                       wimlib_get_wim_info(wim, &info);
+                       if (info.image_count != 1) {
+                               imagex_error(T("\"%"TS"\" contains %d images; "
+                                              "Please select one (or all)."),
+                                            wimfile, info.image_count);
+                               wimlib_free(wim);
+                               goto out_usage;
+                       }
+                       image = 1;
+                       target = argv[1];
                }
-               image = 1;
-               target = argv[1];
-       }
 
-       if (swm_glob) {
-               ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
-                                         &additional_swms,
-                                         &num_additional_swms);
-               if (ret)
-                       goto out_wimlib_free;
-       } else {
-               additional_swms = NULL;
-               num_additional_swms = 0;
+               if (swm_glob) {
+                       ret = open_swms_from_glob(swm_glob, wimfile, open_flags,
+                                                 &additional_swms,
+                                                 &num_additional_swms);
+                       if (ret)
+                               goto out_wimlib_free;
+               } else {
+                       additional_swms = NULL;
+                       num_additional_swms = 0;
+               }
        }
 
-
 #ifndef __WIN32__
        {
                /* Interpret a regular file or block device target as a NTFS
@@ -1638,11 +1684,19 @@ imagex_apply(int argc, tchar **argv)
 #ifdef __WIN32__
        win32_acquire_restore_privileges();
 #endif
-       ret = wimlib_extract_image(wim, image, target, extract_flags,
-                                  additional_swms, num_additional_swms,
-                                  imagex_progress_func);
+       if (wim) {
+               ret = wimlib_extract_image(wim, image, target, extract_flags,
+                                          additional_swms, num_additional_swms,
+                                          imagex_progress_func);
+       } else {
+               set_fd_to_binary_mode(STDIN_FILENO);
+               ret = wimlib_extract_image_from_pipe(STDIN_FILENO,
+                                                    image_num_or_name,
+                                                    target, extract_flags,
+                                                    imagex_progress_func);
+       }
        if (ret == 0)
-               tprintf(T("Done applying WIM image.\n"));
+               imagex_printf(T("Done applying WIM image.\n"));
 #ifdef __WIN32__
        win32_release_restore_privileges();
 #endif
@@ -1674,6 +1728,7 @@ imagex_capture_or_append(int argc, tchar **argv)
        int write_flags = 0;
        int compression_type = WIMLIB_COMPRESSION_TYPE_XPRESS;
        const tchar *wimfile;
+       int wim_fd;
        const tchar *name;
        const tchar *desc;
        const tchar *flags_element = NULL;
@@ -1706,6 +1761,9 @@ imagex_capture_or_append(int argc, tchar **argv)
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
+               case IMAGEX_NOCHECK_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
+                       break;
                case IMAGEX_CONFIG_OPTION:
                        config_file = optarg;
                        break;
@@ -1749,6 +1807,12 @@ imagex_capture_or_append(int argc, tchar **argv)
                case IMAGEX_NORPFIX_OPTION:
                        add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NORPFIX;
                        break;
+               case IMAGEX_PIPABLE_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+                       break;
+               case IMAGEX_NOT_PIPABLE_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
+                       break;
                default:
                        goto out_usage;
                }
@@ -1762,6 +1826,28 @@ imagex_capture_or_append(int argc, tchar **argv)
        source = argv[0];
        wimfile = argv[1];
 
+       if (!tstrcmp(wimfile, T("-"))) {
+       #if 0
+               if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
+                       imagex_error("Can't write a non-pipable WIM to "
+                                    "standard output!  Specify --pipable\n"
+                                    "       if you want to create a pipable WIM "
+                                    "(but read the docs first).");
+                       goto out_err;
+               }
+       #else
+               write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+       #endif
+               if (cmd == APPEND) {
+                       imagex_error(T("Using standard output for append does "
+                                      "not make sense."));
+                       goto out_err;
+               }
+               wim_fd = STDOUT_FILENO;
+               wimfile = NULL;
+               imagex_info_file = stderr;
+       }
+
        if (argc >= 3) {
                name = argv[2];
                name_defaulted = false;
@@ -1845,7 +1931,7 @@ imagex_capture_or_append(int argc, tchar **argv)
 
                if (tstat(source, &stbuf) == 0) {
                        if (S_ISBLK(stbuf.st_mode) || S_ISREG(stbuf.st_mode)) {
-                               tprintf(T("Capturing WIM image from NTFS "
+                               imagex_printf(T("Capturing WIM image from NTFS "
                                          "filesystem on \"%"TS"\"\n"), source);
                                add_image_flags |= WIMLIB_ADD_IMAGE_FLAG_NTFS;
                        }
@@ -1911,16 +1997,20 @@ imagex_capture_or_append(int argc, tchar **argv)
                }
        }
 
+       /* Write the new WIM or overwrite the existing WIM with the new image
+        * appended.  */
        if (cmd == APPEND) {
                ret = wimlib_overwrite(wim, write_flags, num_threads,
                                       imagex_progress_func);
+       } else if (wimfile) {
+               ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES,
+                                  write_flags, num_threads,
+                                  imagex_progress_func);
        } else {
-               ret = wimlib_write(wim, wimfile, WIMLIB_ALL_IMAGES, write_flags,
-                                  num_threads, imagex_progress_func);
+               ret = wimlib_write_to_fd(wim, wim_fd, WIMLIB_ALL_IMAGES,
+                                        write_flags, num_threads,
+                                        imagex_progress_func);
        }
-       if (ret)
-               imagex_error(T("Failed to write the WIM file \"%"TS"\""),
-                            wimfile);
 out_release_privs:
 #ifdef __WIN32__
        win32_release_capture_privileges();
@@ -2115,6 +2205,7 @@ imagex_export(int argc, tchar **argv)
        const tchar *src_wimfile;
        const tchar *src_image_num_or_name;
        const tchar *dest_wimfile;
+       int dest_wim_fd;
        const tchar *dest_name;
        const tchar *dest_desc;
        WIMStruct *src_wim;
@@ -2137,6 +2228,9 @@ imagex_export(int argc, tchar **argv)
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
+               case IMAGEX_NOCHECK_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
+                       break;
                case IMAGEX_COMPRESS_OPTION:
                        compression_type = get_compression_type(optarg);
                        if (compression_type == WIMLIB_COMPRESSION_TYPE_INVALID)
@@ -2153,6 +2247,12 @@ imagex_export(int argc, tchar **argv)
                case IMAGEX_REBUILD_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_REBUILD;
                        break;
+               case IMAGEX_PIPABLE_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+                       break;
+               case IMAGEX_NOT_PIPABLE_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
+                       break;
                default:
                        goto out_usage;
                }
@@ -2172,10 +2272,30 @@ imagex_export(int argc, tchar **argv)
        if (ret)
                goto out;
 
-       /* Determine if the destination is an existing file or not.
-        * If so, we try to append the exported image(s) to it; otherwise, we
-        * create a new WIM containing the exported image(s). */
-       if (tstat(dest_wimfile, &stbuf) == 0) {
+       /* Determine if the destination is an existing file or not.  If so, we
+        * try to append the exported image(s) to it; otherwise, we create a new
+        * WIM containing the exported image(s).  Furthermore, determine if we
+        * need to write a pipable WIM directly to standard output.  */
+
+       if (tstrcmp(dest_wimfile, T("-")) == 0) {
+       #if 0
+               if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
+                       imagex_error("Can't write a non-pipable WIM to "
+                                    "standard output!  Specify --pipable\n"
+                                    "       if you want to create a pipable WIM "
+                                    "(but read the docs first).");
+                       ret = -1;
+                       goto out_free_src_wim;
+               }
+       #else
+               write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+       #endif
+               dest_wimfile = NULL;
+               dest_wim_fd = STDOUT_FILENO;
+               imagex_info_file = stderr;
+       }
+       errno = ENOENT;
+       if (dest_wimfile != NULL && tstat(dest_wimfile, &stbuf) == 0) {
                wim_is_new = false;
                /* Destination file exists. */
 
@@ -2254,13 +2374,19 @@ imagex_export(int argc, tchar **argv)
        if (ret)
                goto out_free_swms;
 
-       if (wim_is_new)
+       if (!wim_is_new)
+               ret = wimlib_overwrite(dest_wim, write_flags, num_threads,
+                                      imagex_progress_func);
+       else if (dest_wimfile)
                ret = wimlib_write(dest_wim, dest_wimfile, WIMLIB_ALL_IMAGES,
                                   write_flags, num_threads,
                                   imagex_progress_func);
        else
-               ret = wimlib_overwrite(dest_wim, write_flags, num_threads,
-                                      imagex_progress_func);
+               ret = wimlib_write_to_fd(dest_wim, dest_wim_fd,
+                                        WIMLIB_ALL_IMAGES, write_flags,
+                                        num_threads, imagex_progress_func);
+       if (ret)
+               imagex_error(T("Export failed."));
 out_free_swms:
        for (unsigned i = 0; i < num_additional_swms; i++)
                wimlib_free(additional_swms[i]);
@@ -2386,6 +2512,7 @@ imagex_extract(int argc, tchar **argv)
                        break;
                case IMAGEX_TO_STDOUT_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_TO_STDOUT;
+                       imagex_info_file = stderr;
                        imagex_be_quiet = true;
                        break;
                case IMAGEX_INCLUDE_INVALID_NAMES_OPTION:
@@ -2444,7 +2571,7 @@ imagex_extract(int argc, tchar **argv)
                                   imagex_progress_func);
        if (ret == 0) {
                if (!imagex_be_quiet)
-                       tprintf(T("Done extracting files.\n"));
+                       imagex_printf(T("Done extracting files.\n"));
        } else if (ret == WIMLIB_ERR_PATH_DOES_NOT_EXIST) {
                tfprintf(stderr, T("Note: You can use `"IMAGEX_PROGNAME" dir' to see what "
                                   "files and directories\n"
@@ -2495,6 +2622,8 @@ print_wim_information(const tchar *wimfile, const struct wimlib_wim_info *info)
                info->has_integrity_table ? T("yes") : T("no"));
        tprintf(T("Relative path junction: %"TS"\n"),
                info->has_rpfix ? T("yes") : T("no"));
+       tprintf(T("Pipable:        %"TS"\n"),
+               info->pipable ? T("yes") : T("no"));
        tputchar(T('\n'));
 }
 
@@ -2548,6 +2677,7 @@ imagex_info(int argc, tchar **argv)
        int c;
        bool boot         = false;
        bool check        = false;
+       bool nocheck      = false;
        bool header       = false;
        bool lookup_table = false;
        bool xml          = false;
@@ -2572,6 +2702,9 @@ imagex_info(int argc, tchar **argv)
                case IMAGEX_CHECK_OPTION:
                        check = true;
                        break;
+               case IMAGEX_NOCHECK_OPTION:
+                       nocheck = true;
+                       break;
                case IMAGEX_HEADER_OPTION:
                        header = true;
                        short_header = false;
@@ -2607,6 +2740,11 @@ imagex_info(int argc, tchar **argv)
        new_name          = (argc >= 3) ? argv[2] : NULL;
        new_desc          = (argc >= 4) ? argv[3] : NULL;
 
+       if (check && nocheck) {
+               imagex_error(T("Can't specify both --check and --nocheck"));
+               goto out_err;
+       }
+
        if (check)
                open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
 
@@ -2730,11 +2868,11 @@ imagex_info(int argc, tchar **argv)
 
                if (boot) {
                        if (image == info.boot_index) {
-                               tprintf(T("Image %d is already marked as "
+                               imagex_printf(T("Image %d is already marked as "
                                          "bootable.\n"), image);
                                boot = false;
                        } else {
-                               tprintf(T("Marking image %d as bootable.\n"),
+                               imagex_printf(T("Marking image %d as bootable.\n"),
                                        image);
                                info.boot_index = image;
                                ret = wimlib_set_wim_info(wim, &info,
@@ -2746,11 +2884,11 @@ imagex_info(int argc, tchar **argv)
                if (new_name) {
                        if (!tstrcmp(wimlib_get_image_name(wim, image), new_name))
                        {
-                               tprintf(T("Image %d is already named \"%"TS"\".\n"),
+                               imagex_printf(T("Image %d is already named \"%"TS"\".\n"),
                                        image, new_name);
                                new_name = NULL;
                        } else {
-                               tprintf(T("Changing the name of image %d to "
+                               imagex_printf(T("Changing the name of image %d to "
                                          "\"%"TS"\".\n"), image, new_name);
                                ret = wimlib_set_image_name(wim, image, new_name);
                                if (ret)
@@ -2761,11 +2899,11 @@ imagex_info(int argc, tchar **argv)
                        const tchar *old_desc;
                        old_desc = wimlib_get_image_description(wim, image);
                        if (old_desc && !tstrcmp(old_desc, new_desc)) {
-                               tprintf(T("The description of image %d is already "
+                               imagex_printf(T("The description of image %d is already "
                                          "\"%"TS"\".\n"), image, new_desc);
                                new_desc = NULL;
                        } else {
-                               tprintf(T("Changing the description of image %d "
+                               imagex_printf(T("Changing the description of image %d "
                                          "to \"%"TS"\".\n"), image, new_desc);
                                ret = wimlib_set_image_descripton(wim, image,
                                                                  new_desc);
@@ -2775,19 +2913,23 @@ imagex_info(int argc, tchar **argv)
                }
 
                /* Only call wimlib_overwrite() if something actually needs to
-                * be changed. */
+                * be changed.  */
                if (boot || new_name || new_desc ||
-                   (check && !info.has_integrity_table))
+                   (check && !info.has_integrity_table) ||
+                   (nocheck && info.has_integrity_table))
                {
                        int write_flags = 0;
 
                        if (check)
                                write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
+                       if (nocheck)
+                               write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
                        ret = wimlib_overwrite(wim, write_flags, 1,
                                               imagex_progress_func);
                } else {
-                       tprintf(T("The file \"%"TS"\" was not modified because nothing "
-                                 "needed to be done.\n"), wimfile);
+                       imagex_printf(T("The file \"%"TS"\" was not modified "
+                                       "because nothing needed to be done.\n"),
+                                     wimfile);
                        ret = 0;
                }
        }
@@ -2798,6 +2940,7 @@ out:
 
 out_usage:
        usage(INFO);
+out_err:
        ret = -1;
        goto out;
 }
@@ -2992,6 +3135,9 @@ imagex_optimize(int argc, tchar **argv)
                        open_flags |= WIMLIB_OPEN_FLAG_CHECK_INTEGRITY;
                        write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
                        break;
+               case IMAGEX_NOCHECK_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY;
+                       break;
                case IMAGEX_RECOMPRESS_OPTION:
                        write_flags |= WIMLIB_WRITE_FLAG_RECOMPRESS;
                        break;
@@ -3000,6 +3146,12 @@ imagex_optimize(int argc, tchar **argv)
                        if (num_threads == UINT_MAX)
                                goto out_err;
                        break;
+               case IMAGEX_PIPABLE_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+                       break;
+               case IMAGEX_NOT_PIPABLE_OPTION:
+                       write_flags |= WIMLIB_WRITE_FLAG_NOT_PIPABLE;
+                       break;
                default:
                        goto out_usage;
                }
@@ -3025,8 +3177,10 @@ imagex_optimize(int argc, tchar **argv)
 
        ret = wimlib_overwrite(wim, write_flags, num_threads,
                               imagex_progress_func);
-       if (ret)
+       if (ret) {
+               imagex_error(T("Optimization of \"%"TS"\" failed."), wimfile);
                goto out_wimlib_free;
+       }
 
        new_size = file_get_size(wimfile);
        tprintf(T("\"%"TS"\" optimized size: "), wimfile);
@@ -3470,7 +3624,7 @@ usage_all(void)
 {
        tfputs(T("Usage:\n"), stdout);
        for (int i = 0; i < ARRAY_LEN(usage_strings); i++)
-               tprintf(T("    %"TS), usage_strings[i]);
+               tprintf(T("    %"TS"\n"), usage_strings[i]);
        static const tchar *extra =
        T(
 "    "IMAGEX_PROGNAME" --help\n"
@@ -3498,6 +3652,8 @@ main(int argc, char **argv)
        int ret;
        int init_flags = 0;
 
+       imagex_info_file = stdout;
+
 #ifndef __WIN32__
        if (getenv("WIMLIB_IMAGEX_USE_UTF8")) {
                init_flags |= WIMLIB_INIT_FLAG_ASSUME_UTF8;
index b700e3a70d9a71de79c453e7615491675a6a5722..81761f9d84113397d7d0ce6d5994485d7338e916 100644 (file)
@@ -1,6 +1,6 @@
 Summary:   Library to extract, create, modify, and mount WIM files
 Name:      wimlib
-Version:   1.4.2
+Version:   1.5.0
 Release:   1
 License:   GPLv3+
 Group:     System/Libraries
index 55c4c75f439ff08e242c6ecce3642f791747fad2..7ea2f0683a84f5dea687a61100f199e86a126341 100644 (file)
@@ -1,6 +1,6 @@
 Summary:   Library to extract, create, modify, and mount WIM files
 Name:      wimlib
-Version:   1.4.2
+Version:   1.5.0
 Release:   1
 License:   GPLv3+
 Group:     System/Libraries
index d40dfa097487d6efb1108775d9a30847e50f6339..746c06a92dd43ed6d1cf0c427b5e1080988699ec 100644 (file)
@@ -68,7 +68,7 @@ add_new_dentry_tree(WIMStruct *wim, struct wim_dentry *root_dentry,
        return ret;
 }
 
-/* Append an empty image to the WIMStruct. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_add_empty_image(WIMStruct *wim, const tchar *name, int *new_idx_ret)
 {
@@ -148,8 +148,7 @@ capture_sources_to_add_cmds(const struct wimlib_capture_source *sources,
        return add_cmds;
 }
 
-/* Adds an image to the WIMStruct from multiple on-disk directory trees, or a
- * NTFS volume. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_add_image_multisource(WIMStruct *wim,
                             const struct wimlib_capture_source *sources,
@@ -200,7 +199,7 @@ out:
        return ret;
 }
 
-/* Adds an image to the WIMStruct from an on-disk directory tree or NTFS volume. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_add_image(WIMStruct *wim,
                 const tchar *source,
index cc01ba449a9305a787119664273ffdcb9bce8a83..1ef88236a3df38de28c8b569b9fb893881335361 100644 (file)
@@ -147,7 +147,7 @@ make_huffman_decode_table(u16 *decode_table,  unsigned num_syms,
                left <<= 1;
                left -= len_counts[len];
                if (unlikely(left < 0)) { /* over-subscribed */
-                       ERROR("Invalid Huffman code (over-subscribed)");
+                       DEBUG("Invalid Huffman code (over-subscribed)");
                        return -1;
                }
        }
@@ -159,7 +159,7 @@ make_huffman_decode_table(u16 *decode_table,  unsigned num_syms,
                               table_num_entries * sizeof(decode_table[0]));
                        return 0;
                } else {
-                       ERROR("Invalid Huffman code (incomplete set)");
+                       DEBUG("Invalid Huffman code (incomplete set)");
                        return -1;
                }
        }
@@ -417,7 +417,7 @@ read_huffsym_near_end_of_input(struct input_bitstream *istream,
                bitstream_remove_bits(istream, key_size);
                do {
                        if (bitsleft == 0) {
-                               ERROR("Input stream exhausted");
+                               DEBUG("Input stream exhausted");
                                return -1;
                        }
                        key_bits = sym + bitstream_peek_bits(istream, 1);
index e73efab2bd021031acd7a4cd650d2df0744d559f..2ad5a27a8f0d4e95962968dc90852e6e5631f190 100644 (file)
@@ -32,9 +32,7 @@
 #include "wimlib/wim.h"
 #include "wimlib/xml.h"
 
-/*
- * Deletes an image from the WIM.
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_delete_image(WIMStruct *wim, int image)
 {
index 02db923af710b33cb192e55176027715f797239b..c9a84d718eee2952197886e634cf1fcb12594335 100644 (file)
@@ -791,8 +791,12 @@ get_dentry_utf16le(WIMStruct *wim, const utf16lechar *path)
        return cur_dentry;
 }
 
-/* Returns the dentry corresponding to the @path, or NULL if there is no such
- * dentry. */
+/*
+ * Returns the dentry in the currently selected WIM image named by @path
+ * starting from the root of the WIM image, or NULL if there is no such dentry.
+ *
+ * On Windows, the search is done case-insensitively.
+ */
 struct wim_dentry *
 get_dentry(WIMStruct *wim, const tchar *path)
 {
@@ -1188,11 +1192,13 @@ do_free_dentry(struct wim_dentry *dentry, void *_lookup_table)
 /*
  * Unlinks and frees a dentry tree.
  *
- * @root:              The root of the tree.
- * @lookup_table:      The lookup table for dentries.  If non-NULL, the
- *                     reference counts in the lookup table for the lookup
- *                     table entries corresponding to the dentries will be
- *                     decremented.
+ * @root:
+ *     The root of the tree.
+ *
+ * @lookup_table:
+ *     The lookup table for dentries.  If non-NULL, the reference counts in the
+ *     lookup table for the lookup table entries corresponding to the dentries
+ *     will be decremented.
  */
 void
 free_dentry_tree(struct wim_dentry *root, struct wim_lookup_table *lookup_table)
@@ -1487,6 +1493,15 @@ inode_add_ads_with_data(struct wim_inode *inode, const tchar *name,
        return 0;
 }
 
+bool
+inode_has_named_stream(const struct wim_inode *inode)
+{
+       for (u16 i = 0; i < inode->i_num_ads; i++)
+               if (ads_entry_is_named_stream(&inode->i_ads_entries[i]))
+                       return true;
+       return false;
+}
+
 /* Set the unnamed stream of a WIM inode, given a data buffer containing the
  * stream contents. */
 int
@@ -1527,6 +1542,15 @@ inode_remove_ads(struct wim_inode *inode, u16 idx,
        inode->i_num_ads--;
 }
 
+bool
+inode_has_unix_data(const struct wim_inode *inode)
+{
+       for (u16 i = 0; i < inode->i_num_ads; i++)
+               if (ads_entry_is_unix_data(&inode->i_ads_entries[i]))
+                       return true;
+       return false;
+}
+
 #ifndef __WIN32__
 int
 inode_get_unix_data(const struct wim_inode *inode,
@@ -1602,19 +1626,24 @@ inode_set_unix_data(struct wim_inode *inode, uid_t uid, gid_t gid, mode_t mode,
 /*
  * Reads the alternate data stream entries of a WIM dentry.
  *
- * @p: Pointer to buffer that starts with the first alternate stream entry.
+ * @p:
+ *     Pointer to buffer that starts with the first alternate stream entry.
  *
- * @inode:     Inode to load the alternate data streams into.
- *             @inode->i_num_ads must have been set to the number of
- *             alternate data streams that are expected.
+ * @inode:
+ *     Inode to load the alternate data streams into.  @inode->i_num_ads must
+ *     have been set to the number of alternate data streams that are expected.
  *
- * @remaining_size:    Number of bytes of data remaining in the buffer pointed
- *                     to by @p.
+ * @remaining_size:
+ *     Number of bytes of data remaining in the buffer pointed to by @p.
  *
+ * On success, inode->i_ads_entries is set to an array of `struct
+ * wim_ads_entry's of length inode->i_num_ads.  On failure, @inode is not
+ * modified.
  *
- * Return 0 on success or nonzero on failure.  On success, inode->i_ads_entries
- * is set to an array of `struct wim_ads_entry's of length inode->i_num_ads.  On
- * failure, @inode is not modified.
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_METADATA_RESOURCE
+ *     WIMLIB_ERR_NOMEM
  */
 static int
 read_ads_entries(const u8 * restrict p, struct wim_inode * restrict inode,
@@ -1718,7 +1747,7 @@ out_of_memory:
        goto out_free_ads_entries;
 out_invalid:
        ERROR("An alternate data stream entry is invalid");
-       ret = WIMLIB_ERR_INVALID_DENTRY;
+       ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
 out_free_ads_entries:
        if (ads_entries) {
                for (u16 i = 0; i < num_ads; i++)
@@ -1739,7 +1768,7 @@ out:
  * @metadata_resource_len:
  *             Length of the metadata resource buffer, in bytes.
  *
- * @offset:    Offset of the dentry within the metadata resource.
+ * @offset:    Offset of the dentry within the metadata resource.
  *
  * @dentry:    A `struct wim_dentry' that will be filled in by this function.
  *
@@ -1749,9 +1778,10 @@ out:
  * this was a special "end of directory" dentry and not a real dentry.  If
  * nonzero, this was a real dentry.
  *
- * Possible errors include:
- *     WIMLIB_ERR_NOMEM
- *     WIMLIB_ERR_INVALID_DENTRY
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_METADATA_RESOURCE
+ *     WIMLIB_ERR_NOMEM
  */
 int
 read_dentry(const u8 * restrict metadata_resource, u64 metadata_resource_len,
@@ -1785,7 +1815,7 @@ read_dentry(const u8 * restrict metadata_resource, u64 metadata_resource_len,
                ERROR("Directory entry starting at %"PRIu64" ends past the "
                      "end of the metadata resource (size %"PRIu64")",
                      offset, metadata_resource_len);
-               return WIMLIB_ERR_INVALID_DENTRY;
+               return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
        }
        dentry->length = le64_to_cpu(disk_dentry->length);
 
@@ -1807,7 +1837,7 @@ read_dentry(const u8 * restrict metadata_resource, u64 metadata_resource_len,
                      "%"PRIu64" ends past the end of the metadata resource "
                      "(size %"PRIu64")",
                      offset, dentry->length, metadata_resource_len);
-               return WIMLIB_ERR_INVALID_DENTRY;
+               return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
        }
 
        /* Make sure the dentry length is at least as large as the number of
@@ -1815,7 +1845,7 @@ read_dentry(const u8 * restrict metadata_resource, u64 metadata_resource_len,
        if (dentry->length < sizeof(struct wim_dentry_on_disk)) {
                ERROR("Directory entry has invalid length of %"PRIu64" bytes",
                      dentry->length);
-               return WIMLIB_ERR_INVALID_DENTRY;
+               return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
        }
 
        /* Allocate a `struct wim_inode' for this `struct wim_dentry'. */
@@ -1861,7 +1891,7 @@ read_dentry(const u8 * restrict metadata_resource, u64 metadata_resource_len,
        if ((short_name_nbytes & 1) | (file_name_nbytes & 1))
        {
                ERROR("Dentry name is not valid UTF-16LE (odd number of bytes)!");
-               ret = WIMLIB_ERR_INVALID_DENTRY;
+               ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
                goto out_free_inode;
        }
 
@@ -1878,7 +1908,7 @@ read_dentry(const u8 * restrict metadata_resource, u64 metadata_resource_len,
                ERROR("Unexpected end of directory entry! (Expected "
                      "at least %"PRIu64" bytes, got %"PRIu64" bytes.)",
                      calculated_size, dentry->length);
-               ret = WIMLIB_ERR_INVALID_DENTRY;
+               ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
                goto out_free_inode;
        }
 
@@ -1932,7 +1962,7 @@ read_dentry(const u8 * restrict metadata_resource, u64 metadata_resource_len,
         * be included in the dentry->length field for some reason.
         */
        if (inode->i_num_ads != 0) {
-               ret = WIMLIB_ERR_INVALID_DENTRY;
+               ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
                if (offset + dentry->length > metadata_resource_len ||
                    (ret = read_ads_entries(&metadata_resource[offset + dentry->length],
                                            inode,
@@ -1977,18 +2007,22 @@ dentry_get_file_type_string(const struct wim_dentry *dentry)
 /* Reads the children of a dentry, and all their children, ..., etc. from the
  * metadata resource and into the dentry tree.
  *
- * @metadata_resource: An array that contains the uncompressed metadata
- *                     resource for the WIM file.
+ * @metadata_resource:
+ *     An array that contains the uncompressed metadata resource for the WIM
+ *     file.
  *
- * @metadata_resource_len:  The length of the uncompressed metadata resource, in
- *                         bytes.
+ * @metadata_resource_len:
+ *     The length of the uncompressed metadata resource, in bytes.
  *
- * @dentry:    A pointer to a `struct wim_dentry' that is the root of the directory
- *             tree and has already been read from the metadata resource.  It
- *             does not need to be the real root because this procedure is
- *             called recursively.
+ * @dentry:
+ *     A pointer to a `struct wim_dentry' that is the root of the directory
+ *     tree and has already been read from the metadata resource.  It does not
+ *     need to be the real root because this procedure is called recursively.
  *
- * Returns zero on success; nonzero on failure.
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_METADATA_RESOURCE
+ *     WIMLIB_ERR_NOMEM
  */
 int
 read_dentry_tree(const u8 * restrict metadata_resource,
@@ -2019,7 +2053,7 @@ read_dentry_tree(const u8 * restrict metadata_resource,
                              "of \"%"TS"\" coincide with children of \"%"TS"\"",
                              dentry_full_path(dentry),
                              dentry_full_path(parent));
-                       return WIMLIB_ERR_INVALID_DENTRY;
+                       return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
                }
        }
 
@@ -2113,7 +2147,7 @@ write_dentry(const struct wim_dentry * restrict dentry, u8 * restrict p)
        wimlib_assert(((uintptr_t)p & 7) == 0); /* 8 byte aligned */
        orig_p = p;
 
-       inode = dentry->d_inode;
+       inode = dentry->d_inode;
        disk_dentry = (struct wim_dentry_on_disk*)p;
 
        disk_dentry->attributes = cpu_to_le32(inode->i_attributes);
@@ -2321,7 +2355,7 @@ init_wimlib_dentry(struct wimlib_dir_entry *wdentry,
                wdentry->num_named_streams++;
                if (lte) {
                        lte_to_wimlib_resource_entry(lte, &wdentry->streams[
-                                                               wdentry->num_named_streams].resource);
+                                                               wdentry->num_named_streams].resource);
                }
        #if TCHAR_IS_UTF16LE
                wdentry->streams[wdentry->num_named_streams].stream_name =
@@ -2332,7 +2366,7 @@ init_wimlib_dentry(struct wimlib_dir_entry *wdentry,
                ret = utf16le_to_tstr(inode->i_ads_entries[i].stream_name,
                                      inode->i_ads_entries[i].stream_name_nbytes,
                                      (tchar**)&wdentry->streams[
-                                               wdentry->num_named_streams].stream_name,
+                                               wdentry->num_named_streams].stream_name,
                                      &dummy);
                if (ret)
                        return ret;
@@ -2385,8 +2419,8 @@ do_iterate_dir_tree(WIMStruct *wim,
 
 
        wdentry = CALLOC(1, sizeof(struct wimlib_dir_entry) +
-                                 (1 + dentry->d_inode->i_num_ads) *
-                                       sizeof(struct wimlib_stream_entry));
+                                 (1 + dentry->d_inode->i_num_ads) *
+                                       sizeof(struct wimlib_stream_entry));
        if (!wdentry)
                goto out;
 
@@ -2437,6 +2471,7 @@ image_do_iterate_dir_tree(WIMStruct *wim)
        return do_iterate_dir_tree(wim, dentry, ctx->flags, ctx->cb, ctx->user_ctx);
 }
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_iterate_dir_tree(WIMStruct *wim, int image, const tchar *path,
                        int flags,
index 9f8bfa3f9eebdc4f472aee68341695d9ba660190..17c4f7dd8f3e06f45ef191b62263ef44125b99c0 100644 (file)
@@ -95,9 +95,7 @@ inode_move_ltes_to_table(struct wim_inode *inode,
        }
 }
 
-/*
- * Exports an image, or all the images, from a WIM file, into another WIM file.
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_export_image(WIMStruct *src_wim,
                    int src_image,
@@ -255,6 +253,7 @@ wimlib_export_image(WIMStruct *src_wim,
                 * the flag is set on the source WIM. */
                dest_wim->hdr.flags |= WIM_HDR_FLAG_RP_FIX;
        }
+       DEBUG("Successfully exported image.");
        ret = 0;
        goto out;
 out_xml_delete_image:
index 0f5a38d023fec344a8aff1ed4d9161409e6019bf..95071bd7bfbb35b45dec106755a88b1c0db204e8 100644 (file)
  * along with wimlib; if not, see http://www.gnu.org/licenses/.
  */
 
+/*
+ * This file provides the API functions wimlib_extract_image(),
+ * wimlib_extract_files(), and wimlib_extract_image_from_pipe().  Internally,
+ * all end up calling extract_tree() zero or more times to extract a tree of
+ * files from the currently selected WIM image to the specified target directory
+ * or NTFS volume.
+ *
+ * Although wimlib supports multiple extraction modes/backends (NTFS-3g, UNIX,
+ * Win32), this file does not itself have code to extract files or directories
+ * to any specific target; instead, it handles generic functionality and relies
+ * on lower-level callback functions declared in `struct apply_operations' to do
+ * the actual extraction.
+ */
+
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
 
-#ifdef __WIN32__
-#  include "wimlib/win32_common.h" /* For GetFullPathName() */
-#endif
-
 #include "wimlib/apply.h"
 #include "wimlib/dentry.h"
 #include "wimlib/encoding.h"
 #include "wimlib/endianness.h"
 #include "wimlib/error.h"
 #include "wimlib/lookup_table.h"
+#include "wimlib/metadata.h"
 #include "wimlib/paths.h"
+#include "wimlib/reparse.h"
 #include "wimlib/resource.h"
+#include "wimlib/security.h"
 #include "wimlib/swm.h"
 #ifdef __WIN32__
 #  include "wimlib/win32.h" /* for realpath() equivalent */
 #endif
 #include "wimlib/xml.h"
+#include "wimlib/wim.h"
 
 #include <errno.h>
-#include <limits.h>
-#ifdef WITH_NTFS_3G
-#  include <ntfs-3g/volume.h> /* for ntfs_mount(), ntfs_umount() */
-#endif
+#include <fcntl.h>
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
-#define MAX_EXTRACT_LONG_PATH_WARNINGS 5
+#define WIMLIB_EXTRACT_FLAG_MULTI_IMAGE 0x80000000
+#define WIMLIB_EXTRACT_FLAG_FROM_PIPE   0x40000000
+#define WIMLIB_EXTRACT_MASK_PUBLIC      0x3fffffff
 
+/* Given a WIM dentry in the tree to be extracted, resolve all streams in the
+ * corresponding inode and set 'out_refcnt' in each to 0.  */
 static int
-do_apply_op(struct wim_dentry *dentry, struct apply_args *args,
-           int (*apply_dentry_func)(const tchar *, size_t,
-                                    struct wim_dentry *, struct apply_args *))
+dentry_resolve_and_zero_lte_refcnt(struct wim_dentry *dentry, void *_ctx)
 {
-       tchar *p;
-       size_t extraction_path_nchars;
-       struct wim_dentry *d;
-       LIST_HEAD(ancestor_list);
-       const tchar *target;
+       struct apply_ctx *ctx = _ctx;
+       struct wim_inode *inode = dentry->d_inode;
+       struct wim_lookup_table_entry *lte;
+       int ret;
+       bool force = false;
+
+       if (dentry->extraction_skipped)
+               return 0;
+
+       /* Special case:  when extracting from a pipe, the WIM lookup table is
+        * initially empty, so "resolving" an inode's streams is initially not
+        * possible.  However, we still need to keep track of which streams,
+        * identified by SHA1 message digests, need to be extracted, so we
+        * "resolve" the inode's streams anyway by allocating new entries.  */
+       if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE)
+               force = true;
+       ret = inode_resolve_ltes(inode, ctx->wim->lookup_table, force);
+       if (ret)
+               return ret;
+       for (unsigned i = 0; i <= inode->i_num_ads; i++) {
+               lte = inode_stream_lte_resolved(inode, i);
+               if (lte)
+                       lte->out_refcnt = 0;
+       }
+       return 0;
+}
+
+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);
+}
+
+static int
+ref_stream_to_extract(struct wim_lookup_table_entry *lte,
+                     struct wim_dentry *dentry, struct apply_ctx *ctx)
+{
+       if (!lte)
+               return 0;
+
+       if (likely(!is_linked_extraction(ctx)) || (lte->out_refcnt == 0 &&
+                                                  lte->extracted_file == NULL))
+       {
+               ctx->progress.extract.total_bytes += wim_resource_size(lte);
+               ctx->progress.extract.num_streams++;
+       }
+
+       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_SEQUENTIAL) {
+               struct wim_dentry **lte_dentries;
+
+               /* 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;
+               } 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;
+                       }
+
+                       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)
+                                       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;
+                       }
+                       lte_dentries = lte->lte_dentries;
+               }
+               lte_dentries[lte->out_refcnt] = dentry;
+       }
+       lte->out_refcnt++;
+       return 0;
+}
+
+/* Given a WIM dentry in the tree to be extracted, iterate through streams that
+ * need to be extracted.  For each one, add it to the list of streams to be
+ * extracted (ctx->stream_list) if not already done so, and also update the
+ * progress information (ctx->progress) with the stream.  Furthermore, if doing
+ * a sequential extraction, build a mapping from each the stream to the dentries
+ * referencing it.  */
+static int
+dentry_add_streams_to_extract(struct wim_dentry *dentry, void *_ctx)
+{
+       struct apply_ctx *ctx = _ctx;
+       struct wim_inode *inode = dentry->d_inode;
+       int ret;
+
+       /* Don't process dentries marked as skipped.  */
+       if (dentry->extraction_skipped)
+               return 0;
+
+       /* Don't process additional hard links.  */
+       if (inode->i_visited && ctx->supported_features.hard_links)
+               return 0;
+
+       /* The unnamed data stream will always be extracted, except in an
+        * unlikely case.  */
+       if (!inode_is_encrypted_directory(inode)) {
+               ret = ref_stream_to_extract(inode_unnamed_lte_resolved(inode),
+                                           dentry, ctx);
+               if (ret)
+                       return ret;
+       }
+
+       /* 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)) {
+               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_to_extract(inode->i_ads_entries[i].lte,
+                                                   dentry, ctx);
+                       if (ret)
+                               return ret;
+               }
+       }
+       inode->i_visited = 1;
+       return 0;
+}
+
+/* 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 += wim_resource_size(lte);
+       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);
+               if (ret) {
+                       ERROR_WITH_ERRNO("Failed to create the directory "
+                                        "\"%"TS"\"", path);
+               }
+       } else {
+               ret = ctx->ops->create_file(path, ctx);
+               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__
-       if (args->target_lowlevel_path) {
-               target = args->target_lowlevel_path;
-               target_nchars = args->target_lowlevel_path_nchars;
-       } else
+       /* 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))
        {
-               target = args->target;
-               target_nchars = args->target_nchars;
+               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;
 
-       extraction_path_nchars = target_nchars;
+       /* Account for reparse data consumed.  */
+       update_extract_progress(ctx,
+                               (lte_override ? lte_override :
+                                     inode_unnamed_lte_resolved(inode)));
+       return 0;
 
-       for (d = dentry; d != args->extract_root; d = d->parent) {
-               if (d->not_extracted)
-                       return 0;
-               extraction_path_nchars += d->extraction_name_nchars + 1;
-               list_add(&d->tmp_list, &ancestor_list);
+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 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;
+       int ret;
+
+       if (dentry->was_hardlinked)
+               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
 
-       tchar extraction_path[extraction_path_nchars + 1];
-       p = tmempcpy(extraction_path, target, target_nchars);
+       /* 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)
+                               ret = ctx->ops->extract_encrypted_stream(path, lte, ctx);
+                       else
+                               ret = ctx->ops->extract_unnamed_stream(path, lte, ctx);
+                       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(path, entry->stream_name,
+                                                            entry->stream_name_nbytes / 2,
+                                                            lte, ctx);
+                       if (ret)
+                               goto error;
+                       update_extract_progress(ctx, lte);
+               }
+       }
+       return 0;
 
-       list_for_each_entry(d, &ancestor_list, tmp_list) {
-               *p++ = OS_PREFERRED_PATH_SEPARATOR;
-               p = tmempcpy(p, d->extraction_name, d->extraction_name_nchars);
+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)
+{
+       int ret;
+
+       if (ctx->ops->set_file_attributes) {
+               if (dentry == ctx->extract_root && ctx->root_dentry_is_special)
+                       return 0;
+               ret = ctx->ops->set_file_attributes(path,
+                                                   dentry->d_inode->i_attributes,
+                                                   ctx);
+               if (ret) {
+                       ERROR_WITH_ERRNO("Failed to set attributes on "
+                                        "\"%"TS"\"", path);
+                       return ret;
+               }
        }
-       *p = T('\0');
+       return 0;
+}
 
-#ifdef __WIN32__
-       /* Warn the user if the path exceeds MAX_PATH */
 
-       /* + 1 for '\0', -4 for \\?\.  */
-       if (extraction_path_nchars + 1 - 4 > MAX_PATH) {
-               if (dentry->needs_extraction &&
-                   args->num_long_paths < MAX_EXTRACT_LONG_PATH_WARNINGS)
+/* 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->extract_root)
+               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))
                {
-                       WARNING("Path \"%ls\" exceeds MAX_PATH and will not be accessible "
-                               "to most Windows software", extraction_path);
-                       if (++args->num_long_paths == MAX_EXTRACT_LONG_PATH_WARNINGS)
-                               WARNING("Suppressing further warnings about long paths");
+                       ERROR_WITH_ERRNO("Failed to set short name of "
+                                        "\"%"TS"\"", path);
+                       return ret;
                }
        }
-#endif
-       return (*apply_dentry_func)(extraction_path, extraction_path_nchars,
-                                   dentry, args);
+       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->extract_root == 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)
+       {
+               const struct wim_security_data *sd;
+               const u8 *desc;
+               size_t desc_size;
+
+               sd = wim_const_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,
+                                                       !!(ctx->extract_flags &
+                                                          WIMLIB_EXTRACT_FLAG_STRICT_ACLS));
+               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 (errno != EACCES) {
+                                       WARNING_WITH_ERRNO("Failed to set "
+                                                          "security descriptor "
+                                                          "on \"%"TS"\"", path);
+                               }
+                       }
+               }
+       }
+       return 0;
+}
 
-/* Extracts a file, directory, or symbolic link from the WIM archive. */
+/* Set timestamps on an extracted file.  Failure is warning-only unless
+ * WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS is set.  */
 static int
-apply_dentry_normal(struct wim_dentry *dentry, void *arg)
+extract_timestamps(const tchar *path, struct apply_ctx *ctx,
+                  struct wim_dentry *dentry)
 {
-#ifdef __WIN32__
-       return do_apply_op(dentry, arg, win32_do_apply_dentry);
-#else
-       return do_apply_op(dentry, arg, unix_do_apply_dentry);
-#endif
+       struct wim_inode *inode = dentry->d_inode;
+       int ret;
+
+       if ((ctx->extract_root == 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) {
+               if (supported_features->reparse_points)
+                       return true;
+               if (supported_features->symlink_reparse_points &&
+                   inode_is_symlink(inode))
+                       return true;
+               return false;
+       }
+       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->max_path characters including the null-terminator.
+ *
+ * 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,
+                     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;
+
+       if (dentry->extraction_skipped)
+               return false;
+
+       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->extract_root; d = d->parent) {
+               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;
+
+       while (*path) {
+               while (*path == path_separator)
+                       path++;
+               if (*path)
+                       num_components++;
+               while (*path && *path != path_separator)
+                       path++;
+       }
+       return num_components;
+}
 
-/* Apply timestamps to an extracted file or directory */
 static int
-apply_dentry_timestamps_normal(struct wim_dentry *dentry, void *arg)
+extract_multiimage_symlink(const tchar *oldpath, const tchar *newpath,
+                          struct apply_ctx *ctx, struct wim_dentry *dentry)
 {
-#ifdef __WIN32__
-       return do_apply_op(dentry, arg, win32_do_apply_dentry_timestamps);
-#else
-       return do_apply_op(dentry, arg, unix_do_apply_dentry_timestamps);
-#endif
+       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->extract_root; 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;
+       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)
+{
+       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->extract_root) {
+               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++) {
+                       struct wim_ads_entry *entry = &inode->i_ads_entries[i];
+
+                       if (!ads_entry_is_named_stream(entry))
+                               continue;
+                       if (entry->lte)
+                               continue;
+                       ret = ctx->ops->extract_named_stream(path,
+                                                            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);
+       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_hardlinked = 1;
+       return 0;
+
+hardlink:
+       ret = extract_hardlink(oldpath, path, ctx);
+       if (ret)
+               return ret;
+       dentry->was_hardlinked = 1;
+       return 0;
 }
 
-static bool
-dentry_is_dot_or_dotdot(const struct wim_dentry *dentry)
+static int
+dentry_extract_skeleton(struct wim_dentry *dentry, void *_ctx)
 {
-       const utf16lechar *file_name = dentry->file_name;
-       return file_name != NULL &&
-               file_name[0] == cpu_to_le16('.') &&
-               (file_name[1] == cpu_to_le16('\0') ||
-                (file_name[1] == cpu_to_le16('.') &&
-                 file_name[2] == cpu_to_le16('\0')));
+       struct apply_ctx *ctx = _ctx;
+       tchar path[ctx->ops->path_max];
+
+       if (!build_extraction_path(path, dentry, ctx))
+               return 0;
+       return do_dentry_extract_skeleton(path, dentry, ctx);
 }
 
-/* Extract a dentry if it hasn't already been extracted and either
- * WIMLIB_EXTRACT_FLAG_NO_STREAMS is not specified, or the dentry is a directory
- * and/or has no unnamed stream. */
+/* Create a file or directory, then immediately extract all streams.  This
+ * assumes that WIMLIB_EXTRACT_FLAG_SEQUENTIAL is not specified, since the WIM
+ * may not be read sequentially by this function.  */
 static int
-maybe_apply_dentry(struct wim_dentry *dentry, void *arg)
+dentry_extract(struct wim_dentry *dentry, void *_ctx)
 {
-       struct apply_args *args = arg;
+       struct apply_ctx *ctx = _ctx;
+       tchar path[ctx->ops->path_max];
        int ret;
 
-       if (!dentry->needs_extraction)
+       if (!build_extraction_path(path, dentry, ctx))
                return 0;
 
-       if (args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_STREAMS &&
-           !dentry_is_directory(dentry) &&
-           inode_unnamed_lte_resolved(dentry->d_inode) != NULL)
-               return 0;
+       ret = do_dentry_extract_skeleton(path, dentry, ctx);
+       if (ret)
+               return ret;
 
-       if ((args->extract_flags & WIMLIB_EXTRACT_FLAG_VERBOSE) &&
-            args->progress_func) {
-               ret = calculate_dentry_full_path(dentry);
-               if (ret)
-                       return ret;
-               args->progress.extract.cur_path = dentry->_full_path;
-               args->progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DENTRY,
-                                   &args->progress);
-       }
-       ret = args->apply_dentry(dentry, args);
-       if (ret == 0)
-               dentry->needs_extraction = 0;
-       return ret;
+       return extract_streams(path, ctx, dentry, NULL, NULL);
 }
 
-static void
-calculate_bytes_to_extract(struct list_head *stream_list,
-                          int extract_flags,
-                          union wimlib_progress_info *progress)
+/* Extract all instances of the stream @lte that are being extracted in this
+ * call of extract_tree().  @can_seek specifies whether the WIM file descriptor
+ * is seekable or not (e.g. is a pipe).  If not and the stream needs to be
+ * extracted multiple times, it is extracted to a temporary file first.
+ *
+ * This is intended for use with sequential extraction of a WIM image
+ * (WIMLIB_EXTRACT_FLAG_SEQUENTIAL specified).  */
+static int
+extract_stream_instances(struct wim_lookup_table_entry *lte,
+                        struct apply_ctx *ctx, bool can_seek)
 {
-       struct wim_lookup_table_entry *lte;
-       u64 total_bytes = 0;
-       u64 num_streams = 0;
+       struct wim_dentry **lte_dentries;
+       struct wim_lookup_table_entry *lte_tmp = NULL;
+       struct wim_lookup_table_entry *lte_override;
+       tchar *stream_tmp_filename = NULL;
+       tchar path[ctx->ops->path_max];
+       unsigned i;
+       int ret;
 
-       /* For each stream to be extracted... */
-       list_for_each_entry(lte, stream_list, extraction_list) {
-               if (extract_flags &
-                   (WIMLIB_EXTRACT_FLAG_SYMLINK | WIMLIB_EXTRACT_FLAG_HARDLINK))
-               {
-                       /* In the symlink or hard link extraction mode, each
-                        * stream will be extracted one time regardless of how
-                        * many dentries share the stream. */
-                       wimlib_assert(!(extract_flags & WIMLIB_EXTRACT_FLAG_NTFS));
-                       if (!lte->extracted_file) {
-                               num_streams++;
-                               total_bytes += wim_resource_size(lte);
-                       }
-               } else {
-                       num_streams += lte->out_refcnt;
-                       total_bytes += lte->out_refcnt * wim_resource_size(lte);
+       if (lte->out_refcnt <= ARRAY_LEN(lte->inline_lte_dentries))
+               lte_dentries = lte->inline_lte_dentries;
+       else
+               lte_dentries = lte->lte_dentries;
+
+       if (likely(can_seek || lte->out_refcnt < 2)) {
+               lte_override = lte;
+       } else {
+               /* Need to extract stream to temporary file.  */
+               struct filedes fd;
+               int raw_fd;
+
+               stream_tmp_filename = ttempnam(NULL, T("wimlib"));
+               if (!stream_tmp_filename) {
+                       ERROR_WITH_ERRNO("Failed to create temporary filename");
+                       ret = WIMLIB_ERR_OPEN;
+                       goto out;
+               }
+
+               lte_tmp = memdup(lte, sizeof(struct wim_lookup_table_entry));
+               if (!lte_tmp) {
+                       ret = WIMLIB_ERR_NOMEM;
+                       goto out_free_stream_tmp_filename;
+               }
+               lte_tmp->resource_location = RESOURCE_IN_FILE_ON_DISK;
+               lte_tmp->file_on_disk = stream_tmp_filename;
+               lte_override = lte_tmp;
+
+               raw_fd = topen(stream_tmp_filename,
+                              O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600);
+               if (raw_fd < 0) {
+                       ERROR_WITH_ERRNO("Failed to open temporary file");
+                       ret = WIMLIB_ERR_OPEN;
+                       goto out_free_lte_tmp;
                }
+               filedes_init(&fd, raw_fd);
+               ret = extract_wim_resource_to_fd(lte, &fd,
+                                                wim_resource_size(lte));
+               if (filedes_close(&fd) && !ret)
+                       ret = WIMLIB_ERR_WRITE;
+               if (ret)
+                       goto out_unlink_stream_tmp_file;
        }
-       progress->extract.num_streams = num_streams;
-       progress->extract.total_bytes = total_bytes;
-       progress->extract.completed_bytes = 0;
-}
 
-static void
-maybe_add_stream_for_extraction(struct wim_lookup_table_entry *lte,
-                               struct list_head *stream_list)
-{
-       if (++lte->out_refcnt == 1) {
-               INIT_LIST_HEAD(&lte->lte_dentry_list);
-               list_add_tail(&lte->extraction_list, stream_list);
+       /* Extract all instances of the stream, reading either from the stream
+        * in the WIM file or from the temporary file containing the stream.
+        * dentry->tmp_flag is used to ensure that each dentry is processed only
+        * once regardless of how many times this stream appears in the streams
+        * of the corresponding inode.  */
+       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;
        }
+       ret = 0;
+out_clear_tmp_flags:
+       for (i = 0; i < lte->out_refcnt; i++)
+               lte_dentries[i]->tmp_flag = 0;
+out_unlink_stream_tmp_file:
+       if (stream_tmp_filename)
+               tunlink(stream_tmp_filename);
+out_free_lte_tmp:
+       FREE(lte_tmp);
+out_free_stream_tmp_filename:
+       FREE(stream_tmp_filename);
+out:
+       return ret;
 }
 
-struct find_streams_ctx {
-       struct list_head stream_list;
-       int extract_flags;
-};
-
+/* 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.  Streams are extracted exactly in the
+ * order of the stream list; however, unless the WIM's file descriptor is
+ * detected to be non-seekable, streams may be read from the WIM file more than
+ * one time if multiple copies need to be extracted.  */
 static int
-dentry_find_streams_to_extract(struct wim_dentry *dentry, void *_ctx)
+extract_stream_list(struct apply_ctx *ctx)
 {
-       struct find_streams_ctx *ctx = _ctx;
-       struct wim_inode *inode = dentry->d_inode;
        struct wim_lookup_table_entry *lte;
-       bool dentry_added = false;
-       struct list_head *stream_list = &ctx->stream_list;
-       int extract_flags = ctx->extract_flags;
-
-       if (!dentry->needs_extraction)
-               return 0;
+       bool can_seek;
+       int ret;
 
-       lte = inode_unnamed_lte_resolved(inode);
-       if (lte) {
-               if (!inode->i_visited)
-                       maybe_add_stream_for_extraction(lte, stream_list);
-               list_add_tail(&dentry->extraction_stream_list, &lte->lte_dentry_list);
-               dentry_added = true;
-       }
-
-       /* Determine whether to include alternate data stream entries or not.
-        *
-        * UNIX:  Include them if extracting using NTFS-3g.
-        *
-        * Windows: Include them undconditionally, although if the filesystem is
-        * not NTFS we won't actually be able to extract them. */
-#if defined(WITH_NTFS_3G)
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS)
-#elif defined(__WIN32__)
-       if (1)
-#else
-       if (0)
-#endif
-       {
-               for (unsigned i = 0; i < inode->i_num_ads; i++) {
-                       if (inode->i_ads_entries[i].stream_name_nbytes != 0) {
-                               lte = inode->i_ads_entries[i].lte;
-                               if (lte) {
-                                       if (!inode->i_visited) {
-                                               maybe_add_stream_for_extraction(lte,
-                                                                               stream_list);
-                                       }
-                                       if (!dentry_added) {
-                                               list_add_tail(&dentry->extraction_stream_list,
-                                                             &lte->lte_dentry_list);
-                                               dentry_added = true;
-                                       }
-                               }
-                       }
-               }
+       can_seek = !(lseek(ctx->wim->in_fd.fd, 0, SEEK_CUR) == (off_t)-1 &&
+                    errno == ESPIPE);
+       list_for_each_entry(lte, &ctx->stream_list, extraction_list) {
+               ret = extract_stream_instances(lte, ctx, can_seek);
+               if (ret)
+                       return ret;
        }
-       inode->i_visited = 1;
        return 0;
 }
 
+/* Read the header from a stream in a pipable WIM.  */
 static int
-dentry_resolve_and_zero_lte_refcnt(struct wim_dentry *dentry, void *_lookup_table)
+read_pwm_stream_header(WIMStruct *pwm, struct wim_lookup_table_entry *lte,
+                      bool allow_header)
 {
-       struct wim_inode *inode = dentry->d_inode;
-       struct wim_lookup_table *lookup_table = _lookup_table;
-       struct wim_lookup_table_entry *lte;
+       struct pwm_stream_hdr stream_hdr;
        int ret;
 
-       ret = inode_resolve_ltes(inode, lookup_table);
+       ret = full_read(&pwm->in_fd, &stream_hdr, sizeof(stream_hdr));
        if (ret)
-               return ret;
-       for (unsigned i = 0; i <= inode->i_num_ads; i++) {
-               lte = inode_stream_lte_resolved(inode, i);
-               if (lte)
-                       lte->out_refcnt = 0;
+               goto read_error;
+
+       if (allow_header && stream_hdr.magic == PWM_MAGIC) {
+               u8 buf[WIM_HEADER_DISK_SIZE - sizeof(stream_hdr)];
+               ret = full_read(&pwm->in_fd, buf, sizeof(buf));
+               if (ret)
+                       goto read_error;
+               lte->resource_location = RESOURCE_NONEXISTENT;
+               return 0;
+       }
+
+       if (stream_hdr.magic != PWM_STREAM_MAGIC) {
+               ERROR("Data read on pipe is invalid (expected stream header).");
+               return WIMLIB_ERR_INVALID_PIPABLE_WIM;
        }
+
+       lte->resource_entry.original_size = le64_to_cpu(stream_hdr.uncompressed_size);
+       copy_hash(lte->hash, stream_hdr.hash);
+       lte->resource_entry.flags = le32_to_cpu(stream_hdr.flags);
+       lte->resource_entry.offset = pwm->in_fd.offset;
+       lte->resource_location = RESOURCE_IN_WIM;
+       lte->wim = pwm;
+       if (lte->resource_entry.flags & WIM_RESHDR_FLAG_COMPRESSED) {
+               lte->compression_type = pwm->compression_type;
+               lte->resource_entry.size = 0;
+       } else {
+               lte->compression_type = WIMLIB_COMPRESSION_TYPE_NONE;
+               lte->resource_entry.size = lte->resource_entry.original_size;
+       }
+       lte->is_pipable = 1;
        return 0;
+
+read_error:
+       ERROR_WITH_ERRNO("Error reading pipable WIM from pipe");
+       return ret;
+}
+
+/* Skip over an unneeded stream in a pipable WIM being read from a pipe.  */
+static int
+skip_pwm_stream(struct wim_lookup_table_entry *lte)
+{
+       return read_partial_wim_resource(lte, wim_resource_size(lte),
+                                        NULL, NULL,
+                                        WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY,
+                                        0);
 }
 
 static int
-find_streams_for_extraction(struct wim_dentry *root,
-                           struct list_head *stream_list,
-                           struct wim_lookup_table *lookup_table,
-                           int extract_flags)
+extract_streams_from_pipe(struct apply_ctx *ctx)
 {
-       struct find_streams_ctx ctx;
+       struct wim_lookup_table_entry *found_lte;
+       struct wim_lookup_table_entry *needed_lte;
+       struct wim_lookup_table *lookup_table;
        int ret;
 
-       INIT_LIST_HEAD(&ctx.stream_list);
-       ctx.extract_flags = extract_flags;
-       ret = for_dentry_in_tree(root, dentry_resolve_and_zero_lte_refcnt, lookup_table);
-       if (ret)
-               return ret;
-       for_dentry_in_tree(root, dentry_find_streams_to_extract, &ctx);
-       list_transfer(&ctx.stream_list, stream_list);
-       return 0;
-}
+       ret = WIMLIB_ERR_NOMEM;
+       found_lte = new_lookup_table_entry();
+       if (!found_lte)
+               goto out;
 
-struct apply_operations {
-       int (*apply_dentry)(struct wim_dentry *dentry, void *arg);
-       int (*apply_dentry_timestamps)(struct wim_dentry *dentry, void *arg);
-};
+       lookup_table = ctx->wim->lookup_table;
 
-static const struct apply_operations normal_apply_operations = {
-       .apply_dentry = apply_dentry_normal,
-       .apply_dentry_timestamps = apply_dentry_timestamps_normal,
-};
+       while (ctx->num_streams_remaining) {
+               ret = read_pwm_stream_header(ctx->wim, found_lte, true);
+               if (ret)
+                       goto out_free_found_lte;
 
-#ifdef WITH_NTFS_3G
-static const struct apply_operations ntfs_apply_operations = {
-       .apply_dentry = apply_dentry_ntfs,
-       .apply_dentry_timestamps = apply_dentry_timestamps_ntfs,
-};
-#endif
+               if ((found_lte->resource_location != RESOURCE_NONEXISTENT)
+                   && !(found_lte->resource_entry.flags & WIM_RESHDR_FLAG_METADATA)
+                   && (needed_lte = __lookup_resource(lookup_table, found_lte->hash))
+                   && (needed_lte->out_refcnt))
+               {
+                       copy_resource_entry(&needed_lte->resource_entry,
+                                           &found_lte->resource_entry);
+                       needed_lte->resource_location = found_lte->resource_location;
+                       needed_lte->wim               = found_lte->wim;
+                       needed_lte->compression_type  = found_lte->compression_type;
+                       needed_lte->is_pipable        = found_lte->is_pipable;
+
+                       ret = extract_stream_instances(needed_lte, ctx, false);
+                       if (ret)
+                               goto out_free_found_lte;
+                       ctx->num_streams_remaining--;
+               } else if (found_lte->resource_location != RESOURCE_NONEXISTENT) {
+                       ret = skip_pwm_stream(found_lte);
+                       if (ret)
+                               goto out_free_found_lte;
+               }
+       }
+       ret = 0;
+out_free_found_lte:
+       free_lookup_table_entry(found_lte);
+out:
+       return ret;
+}
 
+/* Finish extracting a file, directory, or symbolic link by setting file
+ * security and timestamps.  */
 static int
-apply_stream_list(struct list_head *stream_list,
-                 struct apply_args *args,
-                 const struct apply_operations *ops,
-                 wimlib_progress_func_t progress_func)
+dentry_extract_final(struct wim_dentry *dentry, void *_ctx)
 {
-       uint64_t bytes_per_progress = args->progress.extract.total_bytes / 100;
-       uint64_t next_progress = bytes_per_progress;
-       struct wim_lookup_table_entry *lte;
-       struct wim_dentry *dentry;
+       struct apply_ctx *ctx = _ctx;
        int ret;
+       tchar path[ctx->ops->path_max];
 
-       /* This complicated loop is essentially looping through the dentries,
-        * although dentries may be visited more than once (if a dentry contains
-        * two different nonempty streams) or not at all (if a dentry contains
-        * no non-empty streams).
-        *
-        * The outer loop is over the distinct streams to be extracted so that
-        * sequential reading of the WIM can be implemented. */
-
-       /* For each distinct stream to be extracted */
-       list_for_each_entry(lte, stream_list, extraction_list) {
-               /* For each dentry to be extracted that is a name for an inode
-                * containing the stream */
-               list_for_each_entry(dentry, &lte->lte_dentry_list, extraction_stream_list) {
-                       /* Extract the dentry if it was not already
-                        * extracted */
-                       ret = maybe_apply_dentry(dentry, args);
-                       if (ret)
-                               return ret;
-                       if (progress_func &&
-                           args->progress.extract.completed_bytes >= next_progress)
-                       {
-                               progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS,
-                                             &args->progress);
-                               if (args->progress.extract.completed_bytes >=
-                                   args->progress.extract.total_bytes)
-                               {
-                                       next_progress = ~0ULL;
-                               } else {
-                                       next_progress =
-                                               min (args->progress.extract.completed_bytes +
-                                                    bytes_per_progress,
-                                                    args->progress.extract.total_bytes);
-                               }
-                       }
-               }
-       }
-       return 0;
+       if (!build_extraction_path(path, dentry, ctx))
+               return 0;
+
+       ret = extract_security(path, ctx, dentry);
+       if (ret)
+               return ret;
+
+       return extract_timestamps(path, ctx, dentry);
 }
 
+/* Sorts a list of streams in ascending order of their offset in the WIM file in
+ * order to prepare for sequential extraction.  */
 static int
 sort_stream_list_by_wim_position(struct list_head *stream_list)
 {
@@ -429,7 +1375,7 @@ sort_stream_list_by_wim_position(struct list_head *stream_list)
 }
 
 /*
- * Extract a dentry to standard output.
+ * Extract a WIM dentry to standard output.
  *
  * This obviously doesn't make sense in all cases.  We return an error if the
  * dentry does not correspond to a regular file.  Otherwise we extract the
@@ -450,7 +1396,9 @@ extract_dentry_to_stdout(struct wim_dentry *dentry)
 
                lte = inode_unnamed_lte_resolved(dentry->d_inode);
                if (lte) {
-                       ret = extract_wim_resource_to_fd(lte, STDOUT_FILENO,
+                       struct filedes _stdout;
+                       filedes_init(&_stdout, STDOUT_FILENO);
+                       ret = extract_wim_resource_to_fd(lte, &_stdout,
                                                         wim_resource_size(lte));
                }
        }
@@ -504,6 +1452,24 @@ file_name_valid(utf16lechar *name, size_t num_chars, bool fix)
        return true;
 }
 
+static bool
+dentry_is_dot_or_dotdot(const struct wim_dentry *dentry)
+{
+       const utf16lechar *file_name = dentry->file_name;
+       return file_name != NULL &&
+               file_name[0] == cpu_to_le16('.') &&
+               (file_name[1] == cpu_to_le16('\0') ||
+                (file_name[1] == cpu_to_le16('.') &&
+                 file_name[2] == cpu_to_le16('\0')));
+}
+
+static int
+dentry_mark_skipped(struct wim_dentry *dentry, void *_ignore)
+{
+       dentry->extraction_skipped = 1;
+       return 0;
+}
+
 /*
  * dentry_calculate_extraction_path-
  *
@@ -514,7 +1480,7 @@ file_name_valid(utf16lechar *name, size_t num_chars, bool fix)
  * UNIX, converted into the platform's multibyte encoding).  However, if the
  * file name contains characters that are not valid on the current platform or
  * has some other format that is not valid, leave dentry->extraction_name as
- * NULL and clear dentry->needs_extraction to indicate that this dentry should
+ * NULL and set dentry->extraction_skipped to indicate that this dentry should
  * not be extracted, unless the appropriate flag
  * WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES is set in the extract flags, in
  * which case a substitute filename will be created and set instead.
@@ -525,37 +1491,42 @@ file_name_valid(utf16lechar *name, size_t num_chars, bool fix)
 static int
 dentry_calculate_extraction_path(struct wim_dentry *dentry, void *_args)
 {
-       struct apply_args *args = _args;
+       struct apply_ctx *ctx = _args;
        int ret;
 
-       dentry->needs_extraction = 1;
-
-       if (dentry == args->extract_root)
+       if (dentry == ctx->extract_root || dentry->extraction_skipped)
                return 0;
 
+       if (!dentry_is_supported(dentry, &ctx->supported_features))
+               goto skip_dentry;
+
        if (dentry_is_dot_or_dotdot(dentry)) {
                /* WIM files shouldn't contain . or .. entries.  But if they are
                 * there, don't attempt to extract them. */
-               WARNING("Skipping extraction of unexpected . or .. file \"%"TS"\"",
-                       dentry_full_path(dentry));
+               WARNING("Skipping extraction of unexpected . or .. file "
+                       "\"%"TS"\"", dentry_full_path(dentry));
                goto skip_dentry;
        }
 
 #ifdef __WIN32__
-       struct wim_dentry *other;
-       list_for_each_entry(other, &dentry->case_insensitive_conflict_list,
-                           case_insensitive_conflict_list)
+       if (!ctx->ops->supports_case_sensitive_filenames)
        {
-               if (other->needs_extraction) {
-                       if (args->extract_flags & WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS)
-                       {
-                               WARNING("\"%"TS"\" has the same case-insensitive "
-                                       "name as \"%"TS"\"; extracting dummy name instead",
+               struct wim_dentry *other;
+               list_for_each_entry(other, &dentry->case_insensitive_conflict_list,
+                                   case_insensitive_conflict_list)
+               {
+                       if (ctx->extract_flags &
+                           WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS) {
+                               WARNING("\"%"TS"\" has the same "
+                                       "case-insensitive name as "
+                                       "\"%"TS"\"; extracting "
+                                       "dummy name instead",
                                        dentry_full_path(dentry),
                                        dentry_full_path(other));
                                goto out_replace;
                        } else {
-                               WARNING("Not extracting \"%"TS"\": has same case-insensitive "
+                               WARNING("Not extracting \"%"TS"\": "
+                                       "has same case-insensitive "
                                        "name as \"%"TS"\"",
                                        dentry_full_path(dentry),
                                        dentry_full_path(other));
@@ -563,7 +1534,9 @@ dentry_calculate_extraction_path(struct wim_dentry *dentry, void *_args)
                        }
                }
        }
-#endif
+#else  /* __WIN32__ */
+       wimlib_assert(ctx->ops->supports_case_sensitive_filenames);
+#endif /* !__WIN32__ */
 
        if (file_name_valid(dentry->file_name, dentry->file_name_nbytes / 2, false)) {
 #ifdef __WIN32__
@@ -577,7 +1550,7 @@ dentry_calculate_extraction_path(struct wim_dentry *dentry, void *_args)
                                       &dentry->extraction_name_nchars);
 #endif
        } else {
-               if (args->extract_flags & WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES)
+               if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_REPLACE_INVALID_FILENAMES)
                {
                        WARNING("\"%"TS"\" has an invalid filename "
                                "that is not supported on this platform; "
@@ -592,78 +1565,323 @@ dentry_calculate_extraction_path(struct wim_dentry *dentry, void *_args)
                }
        }
 
-out_replace:
+out_replace:
+       {
+               utf16lechar utf16_name_copy[dentry->file_name_nbytes / 2];
+
+               memcpy(utf16_name_copy, dentry->file_name, dentry->file_name_nbytes);
+               file_name_valid(utf16_name_copy, dentry->file_name_nbytes / 2, true);
+
+               tchar *tchar_name;
+               size_t tchar_nchars;
+       #ifdef __WIN32__
+               tchar_name = utf16_name_copy;
+               tchar_nchars = dentry->file_name_nbytes / 2;
+       #else
+               ret = utf16le_to_tstr(utf16_name_copy,
+                                     dentry->file_name_nbytes,
+                                     &tchar_name, &tchar_nchars);
+               if (ret)
+                       return ret;
+       #endif
+               size_t fixed_name_num_chars = tchar_nchars;
+               tchar fixed_name[tchar_nchars + 50];
+
+               tmemcpy(fixed_name, tchar_name, tchar_nchars);
+               fixed_name_num_chars += tsprintf(fixed_name + tchar_nchars,
+                                                T(" (invalid filename #%lu)"),
+                                                ++ctx->invalid_sequence);
+       #ifndef __WIN32__
+               FREE(tchar_name);
+       #endif
+               dentry->extraction_name = memdup(fixed_name,
+                                                2 * fixed_name_num_chars + 2);
+               if (!dentry->extraction_name)
+                       return WIMLIB_ERR_NOMEM;
+               dentry->extraction_name_nchars = fixed_name_num_chars;
+       }
+       return 0;
+
+skip_dentry:
+       for_dentry_in_tree(dentry, dentry_mark_skipped, NULL);
+       return 0;
+}
+
+/* Clean up dentry and inode structure after extraction.  */
+static int
+dentry_reset_needs_extraction(struct wim_dentry *dentry, void *_ignore)
+{
+       struct wim_inode *inode = dentry->d_inode;
+
+       dentry->extraction_skipped = 0;
+       dentry->was_hardlinked = 0;
+       inode->i_visited = 0;
+       FREE(inode->i_extracted_file);
+       inode->i_extracted_file = NULL;
+       if ((void*)dentry->extraction_name != (void*)dentry->file_name)
+               FREE(dentry->extraction_name);
+       dentry->extraction_name = NULL;
+       return 0;
+}
+
+/* Tally features necessary to extract a dentry and the corresponding inode.  */
+static int
+dentry_tally_features(struct wim_dentry *dentry, void *_features)
+{
+       struct wim_features *features = _features;
+       struct wim_inode *inode = dentry->d_inode;
+
+       if (inode->i_attributes & FILE_ATTRIBUTE_ARCHIVE)
+               features->archive_files++;
+       if (inode->i_attributes & FILE_ATTRIBUTE_HIDDEN)
+               features->hidden_files++;
+       if (inode->i_attributes & FILE_ATTRIBUTE_SYSTEM)
+               features->system_files++;
+       if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
+               features->compressed_files++;
+       if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
+               features->encrypted_files++;
+       if (inode->i_attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
+               features->not_context_indexed_files++;
+       if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE)
+               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))
+                       features->symlink_reparse_points++;
+               else
+                       features->other_reparse_points++;
+       }
+       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;
+       return 0;
+}
+
+static int
+dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore)
+{
+       dentry->d_inode->i_visited = 0;
+       return 0;
+}
+
+/* Tally the features necessary to extract a dentry tree.  */
+static void
+dentry_tree_get_features(struct wim_dentry *root, struct wim_features *features)
+{
+       memset(features, 0, sizeof(struct wim_features));
+       for_dentry_in_tree(root, dentry_tally_features, features);
+       for_dentry_in_tree(root, dentry_clear_inode_visited, NULL);
+}
+
+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)
+{
+       if (required_features->archive_files && !supported_features->archive_files)
+       {
+               WARNING(
+          "%lu files are marked as archived, but this attribute\n"
+"          is not supported in this extraction mode or volume.",
+                       required_features->archive_files);
+       }
+
+       if (required_features->hidden_files && !supported_features->hidden_files)
+       {
+               WARNING(
+          "%lu files are marked as hidden, but this attribute\n"
+"          is not supported in this extraction mode or volume.",
+                       required_features->hidden_files);
+       }
+
+       if (required_features->system_files && !supported_features->system_files)
+       {
+               WARNING(
+          "%lu files are marked as system files, but this attribute\n"
+"          is not supported in this extraction mode or volume.",
+                       required_features->system_files);
+       }
+
+       if (required_features->compressed_files && !supported_features->compressed_files)
+       {
+               WARNING(
+          "%lu files are marked as being transparently compressed, but\n"
+"          transparent compression is not supported in this extraction\n"
+"          mode or volume.  These files will be extracted as uncompressed.",
+                       required_features->compressed_files);
+       }
+
+       if (required_features->encrypted_files && !supported_features->encrypted_files)
+       {
+               WARNING(
+          "%lu files are marked as being encrypted, but encryption is not\n"
+"          supported in this extraction mode or volume.  These files will be\n"
+"          extracted as raw encrypted data instead.",
+                       required_features->encrypted_files);
+       }
+
+       if (required_features->not_context_indexed_files &&
+           !supported_features->not_context_indexed_files)
+       {
+               WARNING(
+          "%lu files are marked as not content indexed, but this attribute\n"
+"          is not supported in this extraction mode or volume.",
+                       required_features->not_context_indexed_files);
+       }
+
+       if (required_features->sparse_files && !supported_features->sparse_files)
+       {
+               WARNING(
+          "%lu files are marked as sparse, but creating sparse files is not\n"
+"          supported in this extraction mode or volume.  These files will be\n"
+"          extracted as non-sparse.",
+                       required_features->not_context_indexed_files);
+       }
+
+       if (required_features->named_data_streams &&
+           !supported_features->named_data_streams)
+       {
+               WARNING(
+          "%lu files contain one or more alternate (named) data streams,\n"
+"          which are not supported in this extraction mode or volume.\n"
+"          Alternate data streams will NOT be extracted.",
+                       required_features->named_data_streams);
+       }
+
+       if (unlikely(extract_flags & (WIMLIB_EXTRACT_FLAG_HARDLINK |
+                                     WIMLIB_EXTRACT_FLAG_SYMLINK)) &&
+           required_features->named_data_streams &&
+           supported_features->named_data_streams)
+       {
+               WARNING(
+          "%lu files contain one or more alternate (named) data streams,\n"
+"          which are not supported in linked extraction mode.\n"
+"          Alternate data streams will NOT be extracted.",
+                       required_features->named_data_streams);
+       }
+
+       if (required_features->hard_links && !supported_features->hard_links)
+       {
+               WARNING(
+          "%lu files are hard links, but hard links are not supported in\n"
+"          this extraction mode or volume.  Hard links will be extracted as\n"
+"          duplicate copies of the linked files.",
+                       required_features->hard_links);
+       }
+
+       if (required_features->reparse_points && !supported_features->reparse_points)
+       {
+               if (supported_features->symlink_reparse_points) {
+                       if (required_features->other_reparse_points) {
+                               WARNING(
+          "%lu files are reparse points that are neither symbolic links\n"
+"          nor junction points and are not supported in this extraction mode\n"
+"          or volume.  These reparse points will not be extracted.",
+                                       required_features->other_reparse_points);
+                       }
+               } else {
+                       WARNING(
+          "%lu files are reparse points, which are not supported in this\n"
+"          extraction mode or volume and will not be extracted.",
+                               required_features->reparse_points);
+               }
+       }
+
+       if (required_features->security_descriptors &&
+           !supported_features->security_descriptors)
+       {
+               WARNING(
+          "%lu files have Windows NT security descriptors, but extracting\n"
+"          security descriptors is not supported in this extraction mode\n"
+"          or volume.  No security descriptors will be extracted.",
+                       required_features->security_descriptors);
+       }
+
+       if (required_features->short_names && !supported_features->short_names)
+       {
+               WARNING(
+          "%lu files have short (DOS) names, but extracting short names\n"
+"          is not supported in this extraction mode or volume.  Short names\n"
+"          will not be extracted.\n",
+                       required_features->short_names);
+       }
+
+       if ((extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) &&
+           required_features->unix_data && !supported_features->unix_data)
+       {
+               ERROR("UNIX data not supported in this extraction mode "
+                     "or volume", ops->name);
+               return WIMLIB_ERR_UNSUPPORTED;
+       }
+       if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES) &&
+           required_features->short_names && !supported_features->short_names)
+       {
+               ERROR("Short names are not supported in this extraction "
+                     "mode or volume", ops->name);
+               return WIMLIB_ERR_UNSUPPORTED;
+       }
+       if ((extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_TIMESTAMPS) &&
+           !ops->set_timestamps)
+       {
+               ERROR("Timestamps are not supported in this extraction "
+                     "mode or volume", ops->name);
+               return WIMLIB_ERR_UNSUPPORTED;
+       }
+       if (((extract_flags & (WIMLIB_EXTRACT_FLAG_STRICT_ACLS |
+                              WIMLIB_EXTRACT_FLAG_UNIX_DATA))
+            == WIMLIB_EXTRACT_FLAG_STRICT_ACLS) &&
+           required_features->security_descriptors &&
+           !supported_features->security_descriptors)
+       {
+               ERROR("Security descriptors not supported in this extraction "
+                     "mode or volume.");
+               return WIMLIB_ERR_UNSUPPORTED;
+       }
+
+       if ((extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) &&
+           !supported_features->hard_links)
        {
-               utf16lechar utf16_name_copy[dentry->file_name_nbytes / 2];
-
-               memcpy(utf16_name_copy, dentry->file_name, dentry->file_name_nbytes);
-               file_name_valid(utf16_name_copy, dentry->file_name_nbytes / 2, true);
-
-               tchar *tchar_name;
-               size_t tchar_nchars;
-       #ifdef __WIN32__
-               tchar_name = utf16_name_copy;
-               tchar_nchars = dentry->file_name_nbytes / 2;
-       #else
-               ret = utf16le_to_tstr(utf16_name_copy,
-                                     dentry->file_name_nbytes,
-                                     &tchar_name, &tchar_nchars);
-               if (ret)
-                       return ret;
-       #endif
-               size_t fixed_name_num_chars = tchar_nchars;
-               tchar fixed_name[tchar_nchars + 50];
-
-               tmemcpy(fixed_name, tchar_name, tchar_nchars);
-               fixed_name_num_chars += tsprintf(fixed_name + tchar_nchars,
-                                                T(" (invalid filename #%lu)"),
-                                                ++args->invalid_sequence);
-       #ifndef __WIN32__
-               FREE(tchar_name);
-       #endif
-               dentry->extraction_name = memdup(fixed_name, 2 * fixed_name_num_chars + 2);
-               if (!dentry->extraction_name)
-                       return WIMLIB_ERR_NOMEM;
-               dentry->extraction_name_nchars = fixed_name_num_chars;
+               ERROR("Hard link extraction mode requested, but "
+                     "extraction mode or volume does not support hard links!");
+               return WIMLIB_ERR_UNSUPPORTED;
        }
-       return 0;
-skip_dentry:
-       dentry->needs_extraction = 0;
-       dentry->not_extracted = 1;
-       return 0;
-}
-
-static int
-dentry_reset_needs_extraction(struct wim_dentry *dentry, void *_ignore)
-{
-       struct wim_inode *inode = dentry->d_inode;
 
-       dentry->needs_extraction = 0;
-       dentry->not_extracted = 0;
-       inode->i_visited = 0;
-       inode->i_dos_name_extracted = 0;
-       FREE(inode->i_extracted_file);
-       inode->i_extracted_file = NULL;
-       if ((void*)dentry->extraction_name != (void*)dentry->file_name)
-               FREE(dentry->extraction_name);
-       dentry->extraction_name = NULL;
+       if ((extract_flags & WIMLIB_EXTRACT_FLAG_SYMLINK) &&
+           !supported_features->symlink_reparse_points)
+       {
+               ERROR("Symbolic link extraction mode requested, but "
+                     "extraction mode or volume does not support symbolic "
+                     "links!");
+               return WIMLIB_ERR_UNSUPPORTED;
+       }
        return 0;
 }
 
-#define WINDOWS_NT_MAX_PATH 32768
-
 /*
  * extract_tree - Extract a file or directory tree from the currently selected
  *               WIM image.
  *
  * @wim:       WIMStruct for the WIM file, with the desired image selected
  *             (as wim->current_image).
+ *
  * @wim_source_path:
  *             "Canonical" (i.e. no leading or trailing slashes, path
- *             separators forwald slashes) path inside the WIM image to
+ *             separators WIM_PATH_SEPARATOR) path inside the WIM image to
  *             extract.  An empty string means the full image.
+ *
  * @target:
  *             Filesystem path to extract the file or directory tree to.
+ *             (Or, with WIMLIB_EXTRACT_FLAG_NTFS: the name of a NTFS volume.)
  *
  * @extract_flags:
  *             WIMLIB_EXTRACT_FLAG_*.  Also, the private flag
@@ -673,7 +1891,7 @@ dentry_reset_needs_extraction(struct wim_dentry *dentry, void *_ignore)
  *
  * @progress_func:
  *             If non-NULL, progress function for the extraction.  The messages
- *             we may in this function are:
+ *             that may be sent in this function are:
  *
  *             WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN or
  *                     WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN;
@@ -685,195 +1903,281 @@ dentry_reset_needs_extraction(struct wim_dentry *dentry, void *_ignore)
  *             WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END or
  *                     WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END.
  *
- * Returns 0 on success; nonzero on failure.
+ * Returns 0 on success; a positive WIMLIB_ERR_* code on failure.
  */
 static int
 extract_tree(WIMStruct *wim, const tchar *wim_source_path, const tchar *target,
             int extract_flags, wimlib_progress_func_t progress_func)
 {
-       int ret;
-       struct list_head stream_list;
-       struct apply_args args;
-       const struct apply_operations *ops;
        struct wim_dentry *root;
+       struct wim_features required_features;
+       struct apply_ctx ctx;
+       int ret;
+       struct wim_lookup_table_entry *lte;
 
-       memset(&args, 0, sizeof(args));
-
-
-       args.wim                    = wim;
-       args.target                 = target;
-       args.target_nchars          = tstrlen(target);
-       args.extract_flags          = extract_flags;
-       args.progress_func          = progress_func;
-
-#ifdef __WIN32__
-       /* Work around defective behavior in Windows where paths longer than 260
-        * characters are not supported by default; instead they need to be
-        * turned into absolute paths and prefixed with "\\?\".  */
-       args.target_lowlevel_path = MALLOC(WINDOWS_NT_MAX_PATH * sizeof(wchar_t));
-       if (!args.target_lowlevel_path)
-       {
-               ret = WIMLIB_ERR_NOMEM;
-               goto out;
-       }
-       args.target_lowlevel_path_nchars =
-               GetFullPathName(args.target, WINDOWS_NT_MAX_PATH - 4,
-                               &args.target_lowlevel_path[4], NULL);
-
-       if (args.target_lowlevel_path_nchars == 0 ||
-           args.target_lowlevel_path_nchars >= WINDOWS_NT_MAX_PATH - 4)
-       {
-               WARNING("Can't get full path name for \"%ls\"", args.target);
-               FREE(args.target_lowlevel_path);
-               args.target_lowlevel_path = NULL;
-       } else {
-               wmemcpy(args.target_lowlevel_path, L"\\\\?\\", 4);
-               args.target_lowlevel_path_nchars += 4;
-       }
-#endif
-
+       /* 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) {
-               args.progress.extract.wimfile_name = wim->filename;
-               args.progress.extract.image = wim->current_image;
-               args.progress.extract.extract_flags = (extract_flags &
-                                                      WIMLIB_EXTRACT_MASK_PUBLIC);
-               args.progress.extract.image_name = wimlib_get_image_name(wim,
-                                                                        wim->current_image);
-               args.progress.extract.extract_root_wim_source_path = wim_source_path;
-               args.progress.extract.target = target;
+               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.extract_root_wim_source_path = wim_source_path;
+               ctx.progress.extract.target = target;
        }
+       INIT_LIST_HEAD(&ctx.stream_list);
 
-#ifdef WITH_NTFS_3G
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
-               args.vol = ntfs_mount(target, 0);
-               if (!args.vol) {
-                       ERROR_WITH_ERRNO("Failed to mount NTFS volume `%"TS"'",
-                                        target);
-                       ret = WIMLIB_ERR_NTFS_3G;
-                       goto out_free_target_lowlevel_path;
-               }
-               ops = &ntfs_apply_operations;
-       } else
-#endif
-               ops = &normal_apply_operations;
-
+       /* Translate the path to extract into the corresponding
+        * `struct wim_dentry', which will be the root of the
+        * "dentry tree" to extract.  */
        root = get_dentry(wim, wim_source_path);
        if (!root) {
                ERROR("Path \"%"TS"\" does not exist in WIM image %d",
                      wim_source_path, wim->current_image);
                ret = WIMLIB_ERR_PATH_DOES_NOT_EXIST;
-               goto out_ntfs_umount;
+               goto out;
        }
-       args.extract_root = root;
 
-       /* Calculate the actual filename component of each extracted dentry, and
-        * in the process set the dentry->needs_extraction flag on dentries that
-        * will be extracted. */
-       ret = for_dentry_in_tree(root, dentry_calculate_extraction_path, &args);
+       ctx.extract_root = root;
+
+       /* Select the appropriate apply_operations based on the
+        * platform and extract_flags.  */
+#ifdef __WIN32__
+       ctx.ops = &win32_apply_ops;
+#else
+       ctx.ops = &unix_apply_ops;
+#endif
+
+#ifdef WITH_NTFS_3G
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS)
+               ctx.ops = &ntfs_3g_apply_ops;
+#endif
+
+       /* Call the start_extract() callback.  This gives the apply_operations
+        * implementation a chance to do any setup needed to access the volume.
+        * Furthermore, it's 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.  These
+        * features are then compared with the actual features extracting this
+        * dentry tree requires.  Some mismatches will merely produce warnings
+        * and the unsupported data will be ignored; others will produce errors.
+        */
+       ret = ctx.ops->start_extract(target, &ctx);
+       if (ret)
+               goto out;
+
+       dentry_tree_get_features(root, &required_features);
+       ret = do_feature_check(&required_features, &ctx.supported_features,
+                              extract_flags, ctx.ops);
+       if (ret)
+               goto out_finish_or_abort_extract;
+
+       /* 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);
+
+       /* Calculate the actual filename component of each extracted dentry.  In
+        * the process, set the dentry->extraction_skipped flag on dentries that
+        * are being skipped for some reason (e.g. invalid filename).  */
+       ret = for_dentry_in_tree(root, dentry_calculate_extraction_path, &ctx);
        if (ret)
                goto out_dentry_reset_needs_extraction;
 
-       /* Build a list of the streams that need to be extracted */
-       ret = find_streams_for_extraction(root,
-                                         &stream_list,
-                                         wim->lookup_table, extract_flags);
+       /* Build the list of the streams that need to be extracted and
+        * initialize ctx.progress.extract with stream information.  */
+       ret = for_dentry_in_tree(ctx.extract_root,
+                                dentry_resolve_and_zero_lte_refcnt, &ctx);
        if (ret)
                goto out_dentry_reset_needs_extraction;
 
-       /* Calculate the number of bytes of data that will be extracted */
-       calculate_bytes_to_extract(&stream_list, extract_flags,
-                                  &args.progress);
+       ret = for_dentry_in_tree(ctx.extract_root,
+                                dentry_add_streams_to_extract, &ctx);
+       if (ret)
+               goto out_teardown_stream_list;
+
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) {
+               /* When extracting from a pipe, the number of bytes of data to
+                * extract can't be determined in the normal way (examining the
+                * lookup table), since at this point all we have is a set of
+                * SHA1 message digests of streams that need to be extracted.
+                * However, we can get a reasonably accurate estimate by taking
+                * <TOTALBYTES> from the corresponding <IMAGE> in the WIM XML
+                * data.  This does assume that a full image is being extracted,
+                * but currently there is no API for doing otherwise.  */
+               ctx.progress.extract.total_bytes =
+                       wim_info_get_image_total_bytes(wim->wim_info,
+                                                      wim->current_image);
+       }
 
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_TO_STDOUT) {
+       /* Handle the special case of extracting a file to standard
+        * output.  In that case, "root" should be a single file, not a
+        * directory tree.  (If not, extract_dentry_to_stdout() will
+        * return an error.)  */
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_TO_STDOUT &&
+           !(extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE)) {
                ret = extract_dentry_to_stdout(root);
-               goto out_dentry_reset_needs_extraction;
+               goto out_teardown_stream_list;
        }
 
-       if (progress_func) {
-               progress_func(*wim_source_path ? WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN :
-                             WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN,
-                             &args.progress);
+       /* If a sequential extraction was specified, sort the streams to be
+        * extracted by their position in the WIM file so that the WIM file can
+        * be read sequentially.  */
+       if ((extract_flags & (WIMLIB_EXTRACT_FLAG_SEQUENTIAL |
+                             WIMLIB_EXTRACT_FLAG_FROM_PIPE))
+                                       == WIMLIB_EXTRACT_FLAG_SEQUENTIAL)
+       {
+               ret = sort_stream_list_by_wim_position(&ctx.stream_list);
+               if (ret)
+                       goto out_teardown_stream_list;
        }
 
-       /* If a sequential extraction was specified, sort the streams to be
-        * extracted by their position in the WIM file, so that the WIM file can
-        * be read sequentially. */
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_SEQUENTIAL) {
-               ret = sort_stream_list_by_wim_position(&stream_list);
-               if (ret != 0) {
-                       WARNING("Falling back to non-sequential extraction");
-                       extract_flags &= ~WIMLIB_EXTRACT_FLAG_SEQUENTIAL;
+       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_teardown_stream_list;
                }
+               ctx.realtarget_nchars = tstrlen(ctx.realtarget);
        }
 
        if (progress_func) {
-               progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
-                             &args.progress);
+               progress_func(*wim_source_path ? WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN :
+                                                WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_BEGIN,
+                             &ctx.progress);
        }
 
-       /* Make the directory structure and extract empty files */
-       args.extract_flags |= WIMLIB_EXTRACT_FLAG_NO_STREAMS;
-       args.apply_dentry = ops->apply_dentry;
-       ret = for_dentry_in_tree(root, maybe_apply_dentry, &args);
-       args.extract_flags &= ~WIMLIB_EXTRACT_FLAG_NO_STREAMS;
-       if (ret)
-               goto out_dentry_reset_needs_extraction;
-
-       if (progress_func) {
-               progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END,
-                             &args.progress);
+       if (!ctx.root_dentry_is_special)
+       {
+               tchar path[ctx.ops->path_max];
+               if (build_extraction_path(path, root, &ctx))
+               {
+                       ret = extract_inode(path, &ctx, root->d_inode);
+                       if (ret)
+                               goto out_free_realtarget;
+               }
        }
 
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) {
-               args.target_realpath = realpath(target, NULL);
-               if (!args.target_realpath) {
+       /* 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_dentry_reset_needs_extraction;
+                       goto out_free_realtarget;
                }
-               args.target_realpath_len = tstrlen(args.target_realpath);
+               ctx.realtarget_nchars = tstrlen(ctx.realtarget);
        }
 
-       /* Extract non-empty files */
-       ret = apply_stream_list(&stream_list, &args, ops, progress_func);
-       if (ret)
-               goto out_free_target_realpath;
+       /* Finally, the important part: extract the tree of files.  */
+       if (extract_flags & (WIMLIB_EXTRACT_FLAG_SEQUENTIAL |
+                            WIMLIB_EXTRACT_FLAG_FROM_PIPE)) {
+               /* Sequential extraction requested, so two passes are needed
+                * (one for directory structure, one for streams.)  */
+               if (progress_func)
+                       progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
+                                     &ctx.progress);
+               ret = for_dentry_in_tree(root, dentry_extract_skeleton, &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)
+                       progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_BEGIN,
+                                     &ctx.progress);
+               ret = for_dentry_in_tree(root, dentry_extract, &ctx);
+               if (ret)
+                       goto out_free_realtarget;
+               if (progress_func)
+                       progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_DIR_STRUCTURE_END,
+                                     &ctx.progress);
+       }
 
-       if (progress_func) {
-               progress_func(WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS,
-                             &args.progress);
+       /* 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)
+       {
+               DEBUG("Calculated %"PRIu64" bytes to extract, but actually "
+                     "extracted %"PRIu64,
+                     ctx.progress.extract.total_bytes,
+                     ctx.progress.extract.completed_bytes);
+       }
+       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 timestamps */
-       ret = for_dentry_in_tree_depth(root,
-                                      ops->apply_dentry_timestamps, &args);
+       /* 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 = for_dentry_in_tree_depth(root, dentry_extract_final, &ctx);
        if (ret)
-               goto out_free_target_realpath;
+               goto out_free_realtarget;
 
        if (progress_func) {
                progress_func(*wim_source_path ? WIMLIB_PROGRESS_MSG_EXTRACT_TREE_END :
                              WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END,
-                             &args.progress);
+                             &ctx.progress);
        }
-out_free_target_realpath:
-       FREE(args.target_realpath);
+
+       ret = 0;
+out_free_realtarget:
+       FREE(ctx.realtarget);
+out_teardown_stream_list:
+       /* Free memory allocated as part of the mapping from each
+        * wim_lookup_table_entry to the dentries that reference it.  */
+       if (ctx.extract_flags & WIMLIB_EXTRACT_FLAG_SEQUENTIAL)
+               list_for_each_entry(lte, &ctx.stream_list, extraction_list)
+                       if (lte->out_refcnt > ARRAY_LEN(lte->inline_lte_dentries))
+                               FREE(lte->lte_dentries);
 out_dentry_reset_needs_extraction:
        for_dentry_in_tree(root, dentry_reset_needs_extraction, NULL);
-out_ntfs_umount:
-#ifdef WITH_NTFS_3G
-       /* Unmount the NTFS volume */
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
-               if (ntfs_umount(args.vol, FALSE) != 0) {
-                       ERROR_WITH_ERRNO("Failed to unmount NTFS volume `%"TS"'",
-                                        args.target);
-                       if (ret == 0)
-                               ret = WIMLIB_ERR_NTFS_3G;
-               }
+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);
        }
-#endif
-out_free_target_lowlevel_path:
-#ifdef __WIN32__
-       FREE(args.target_lowlevel_path);
-#endif
 out:
        return ret;
 }
@@ -884,7 +2188,6 @@ static int
 check_extract_command(struct wimlib_extract_command *cmd, int wim_header_flags)
 {
        int extract_flags;
-       bool is_entire_image = (cmd->wim_source_path[0] == T('\0'));
 
        /* Empty destination path? */
        if (cmd->fs_dest_path[0] == T('\0'))
@@ -892,77 +2195,61 @@ check_extract_command(struct wimlib_extract_command *cmd, int wim_header_flags)
 
        extract_flags = cmd->extract_flags;
 
-       /* Specified both symlink and hardlink modes? */
+       /* 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;
 
-#ifdef __WIN32__
-       /* Wanted UNIX data on Windows? */
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
-               ERROR("Extracting UNIX data is not supported on Windows");
+       if ((extract_flags &
+            (WIMLIB_EXTRACT_FLAG_NO_ACLS |
+             WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) == (WIMLIB_EXTRACT_FLAG_NO_ACLS |
+                                                   WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
                return WIMLIB_ERR_INVALID_PARAM;
-       }
-       /* Wanted linked extraction on Windows?  (XXX This is possible, just not
-        * implemented yet.) */
-       if (extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
-                            WIMLIB_EXTRACT_FLAG_HARDLINK))
-       {
-               ERROR("Linked extraction modes are not supported on Windows");
+
+       if ((extract_flags &
+            (WIMLIB_EXTRACT_FLAG_RPFIX |
+             WIMLIB_EXTRACT_FLAG_NORPFIX)) == (WIMLIB_EXTRACT_FLAG_RPFIX |
+                                               WIMLIB_EXTRACT_FLAG_NORPFIX))
                return WIMLIB_ERR_INVALID_PARAM;
-       }
-#endif
 
        if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
-               /* NTFS-3g extraction mode requested */
-#ifdef WITH_NTFS_3G
-               if ((extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
-                                     WIMLIB_EXTRACT_FLAG_HARDLINK))) {
-                       ERROR("Cannot specify symlink or hardlink flags when applying\n"
-                             "        directly to a NTFS volume");
-                       return WIMLIB_ERR_INVALID_PARAM;
-               }
-               if (!is_entire_image &&
-                   (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS))
-               {
-                       ERROR("When applying directly to a NTFS volume you can "
-                             "only extract a full image, not part of one");
-                       return WIMLIB_ERR_INVALID_PARAM;
-               }
-               if (extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
-                       ERROR("Cannot restore UNIX-specific data in "
-                             "the NTFS extraction mode");
-                       return WIMLIB_ERR_INVALID_PARAM;
-               }
-#else
-               ERROR("wimlib was compiled without support for NTFS-3g, so");
-               ERROR("we cannot apply a WIM image directly to a NTFS volume");
+#ifndef WITH_NTFS_3G
+               ERROR("wimlib was compiled without support for NTFS-3g, so\n"
+                     "        we cannot apply a WIM image directly to a NTFS volume.");
                return WIMLIB_ERR_UNSUPPORTED;
 #endif
        }
 
-       if ((extract_flags & (WIMLIB_EXTRACT_FLAG_RPFIX |
-                             WIMLIB_EXTRACT_FLAG_NORPFIX)) ==
-               (WIMLIB_EXTRACT_FLAG_RPFIX | WIMLIB_EXTRACT_FLAG_NORPFIX))
-       {
-               ERROR("Cannot specify RPFIX and NORPFIX flags at the same time!");
-               return WIMLIB_ERR_INVALID_PARAM;
-       }
-
        if ((extract_flags & (WIMLIB_EXTRACT_FLAG_RPFIX |
                              WIMLIB_EXTRACT_FLAG_NORPFIX)) == 0)
        {
                /* Do reparse point fixups by default if the WIM header says
                 * they are enabled and we are extracting a full image. */
-               if ((wim_header_flags & WIM_HDR_FLAG_RP_FIX) && is_entire_image)
+               if (wim_header_flags & WIM_HDR_FLAG_RP_FIX)
                        extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
        }
 
-       if (!is_entire_image && (extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX)) {
-               ERROR("Cannot specify --rpfix when not extracting entire image");
-               return WIMLIB_ERR_INVALID_PARAM;
+       /* 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_SEQUENTIAL))
+                                   == (WIMLIB_EXTRACT_FLAG_UNIX_DATA |
+                                       WIMLIB_EXTRACT_FLAG_SEQUENTIAL))
+       {
+               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_SEQUENTIAL;
+                       WARNING("Disabling sequential extraction for "
+                               "UNIX data mode");
+               }
        }
 
        cmd->extract_flags = extract_flags;
@@ -970,7 +2257,8 @@ check_extract_command(struct wimlib_extract_command *cmd, int wim_header_flags)
 }
 
 
-/* Internal function to execute extraction commands for a WIM image. */
+/* Internal function to execute extraction commands for a WIM image.  The paths
+ * in the extract commands are expected to be already "canonicalized".  */
 static int
 do_wimlib_extract_files(WIMStruct *wim,
                        int image,
@@ -988,7 +2276,8 @@ do_wimlib_extract_files(WIMStruct *wim,
                return ret;
 
        /* Make sure there are no streams in the WIM that have not been
-        * checksummed yet. */
+        * checksummed yet.  Needed at least because 'unhashed_list' aliases
+        * 'extraction_list' in `struct wim_lookup_table_entry'.  */
        ret = wim_checksum_unhashed_streams(wim);
        if (ret)
                return ret;
@@ -1024,7 +2313,7 @@ do_wimlib_extract_files(WIMStruct *wim,
        return 0;
 }
 
-/* Extract files or directories from a WIM image. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_extract_files(WIMStruct *wim,
                     int image,
@@ -1170,12 +2459,18 @@ extract_all_images(WIMStruct *wim,
        const tchar *image_name;
        struct stat stbuf;
 
+       extract_flags |= WIMLIB_EXTRACT_FLAG_MULTI_IMAGE;
+
+#ifdef WITH_NTFS_3G
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
+               ERROR("Cannot extract multiple images in NTFS extraction mode.");
+               return WIMLIB_ERR_INVALID_PARAM;
+       }
+#endif
+
        if (tstat(target, &stbuf)) {
-               if (errno == ENOENT)
-               {
-                       if (tmkdir(target, S_IRWXU | S_IRGRP | S_IXGRP |
-                                          S_IROTH | S_IXOTH))
-                       {
+               if (errno == ENOENT) {
+                       if (tmkdir(target, 0755)) {
                                ERROR_WITH_ERRNO("Failed to create directory \"%"TS"\"", target);
                                return WIMLIB_ERR_MKDIR;
                        }
@@ -1207,31 +2502,31 @@ extract_all_images(WIMStruct *wim,
        return 0;
 }
 
-/* Extracts a single image or all images from a WIM file to a directory or NTFS
- * volume. */
-WIMLIBAPI int
-wimlib_extract_image(WIMStruct *wim,
-                    int image,
-                    const tchar *target,
-                    int extract_flags,
-                    WIMStruct **additional_swms,
-                    unsigned num_additional_swms,
-                    wimlib_progress_func_t progress_func)
+static int
+do_wimlib_extract_image(WIMStruct *wim,
+                       int image,
+                       const tchar *target,
+                       int extract_flags,
+                       WIMStruct **additional_swms,
+                       unsigned num_additional_swms,
+                       wimlib_progress_func_t progress_func)
 {
        int ret;
 
-       extract_flags &= WIMLIB_EXTRACT_MASK_PUBLIC;
-
-       ret = verify_swm_set(wim, additional_swms, num_additional_swms);
-       if (ret)
-               return ret;
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) {
+               wimlib_assert(wim->hdr.part_number == 1);
+               wimlib_assert(num_additional_swms == 0);
+       } else {
+               ret = verify_swm_set(wim, additional_swms, num_additional_swms);
+               if (ret)
+                       return ret;
 
-       if (num_additional_swms)
-               merge_lookup_tables(wim, additional_swms, num_additional_swms);
+               if (num_additional_swms)
+                       merge_lookup_tables(wim, additional_swms, num_additional_swms);
+       }
 
        if (image == WIMLIB_ALL_IMAGES) {
-               ret = extract_all_images(wim, target,
-                                        extract_flags | WIMLIB_EXTRACT_FLAG_MULTI_IMAGE,
+               ret = extract_all_images(wim, target, extract_flags,
                                         progress_func);
        } else {
                ret = extract_single_image(wim, image, target, extract_flags,
@@ -1249,3 +2544,166 @@ wimlib_extract_image(WIMStruct *wim,
                unmerge_lookup_table(wim);
        return ret;
 }
+
+/* API function documented in wimlib.h  */
+WIMLIBAPI int
+wimlib_extract_image_from_pipe(int pipe_fd, const tchar *image_num_or_name,
+                              const tchar *target, int extract_flags,
+                              wimlib_progress_func_t progress_func)
+{
+       int ret;
+       WIMStruct *pwm;
+       struct filedes *in_fd;
+       int image;
+       unsigned i;
+
+       extract_flags &= WIMLIB_EXTRACT_MASK_PUBLIC;
+
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_TO_STDOUT)
+               return WIMLIB_ERR_INVALID_PARAM;
+
+       extract_flags |= WIMLIB_EXTRACT_FLAG_SEQUENTIAL;
+
+       /* 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
+        * an empty lookup table, no XML data read, and no filename set.  */
+       ret = open_wim_as_WIMStruct(&pipe_fd,
+                                   WIMLIB_OPEN_FLAG_FROM_PIPE |
+                                               WIMLIB_OPEN_FLAG_SPLIT_OK,
+                                   &pwm, progress_func);
+       if (ret)
+               return ret;
+
+       /* Sanity check to make sure this is a pipable WIM.  */
+       if (pwm->hdr.magic != PWM_MAGIC) {
+               ERROR("The WIM being read from file descriptor %d "
+                     "is not pipable!", pipe_fd);
+               ret = WIMLIB_ERR_NOT_PIPABLE;
+               goto out_wimlib_free;
+       }
+
+       /* Sanity check to make sure the first part of a pipable split WIM is
+        * sent over the pipe first.  */
+       if (pwm->hdr.part_number != 1) {
+               ERROR("The first part of the split WIM must be "
+                     "sent over the pipe first.");
+               ret = WIMLIB_ERR_INVALID_PIPABLE_WIM;
+               goto out_wimlib_free;
+       }
+
+       in_fd = &pwm->in_fd;
+       wimlib_assert(in_fd->offset == WIM_HEADER_DISK_SIZE);
+
+       /* As mentioned, the WIMStruct we created from the pipe does not have
+        * XML data yet.  Fix this by reading the extra copy of the XML data
+        * that directly follows the header in pipable WIMs.  (Note: see
+        * write_pipable_wim() for more details about the format of pipable
+        * WIMs.)  */
+       {
+               struct wim_lookup_table_entry xml_lte;
+               ret = read_pwm_stream_header(pwm, &xml_lte, false);
+               if (ret)
+                       goto out_wimlib_free;
+
+               if (!(xml_lte.resource_entry.flags & WIM_RESHDR_FLAG_METADATA))
+               {
+                       ERROR("Expected XML data, but found non-metadata "
+                             "stream.");
+                       ret = WIMLIB_ERR_INVALID_PIPABLE_WIM;
+                       goto out_wimlib_free;
+               }
+
+               copy_resource_entry(&pwm->hdr.xml_res_entry,
+                                   &xml_lte.resource_entry);
+
+               ret = read_wim_xml_data(pwm);
+               if (ret)
+                       goto out_wimlib_free;
+               if (wim_info_get_num_images(pwm->wim_info) != pwm->hdr.image_count) {
+                       ERROR("Image count in XML data is not the same as in WIM header.");
+                       ret = WIMLIB_ERR_XML;
+                       goto out_wimlib_free;
+               }
+       }
+
+       /* Get image index (this may use the XML data that was just read to
+        * resolve an image name).  */
+       image = wimlib_resolve_image(pwm, image_num_or_name);
+       if (image == WIMLIB_NO_IMAGE) {
+               ERROR("\"%"TS"\" is not a valid image in the pipable WIM!",
+                     image_num_or_name);
+               ret = WIMLIB_ERR_INVALID_IMAGE;
+               goto out_wimlib_free;
+       } else if (image == WIMLIB_ALL_IMAGES) {
+               ERROR("Applying all images from a pipe is not supported.");
+               ret = WIMLIB_ERR_INVALID_IMAGE;
+               goto out_wimlib_free;
+       }
+
+       /* Load the needed metadata resource.  */
+       for (i = 1; i <= pwm->hdr.image_count; i++) {
+               struct wim_lookup_table_entry *metadata_lte;
+               struct wim_image_metadata *imd;
+
+               metadata_lte = new_lookup_table_entry();
+               if (!metadata_lte) {
+                       ret = WIMLIB_ERR_NOMEM;
+                       goto out_wimlib_free;
+               }
+
+               ret = read_pwm_stream_header(pwm, metadata_lte, false);
+               imd = pwm->image_metadata[i - 1];
+               imd->metadata_lte = metadata_lte;
+               if (ret)
+                       goto out_wimlib_free;
+
+               if (!(metadata_lte->resource_entry.flags &
+                     WIM_RESHDR_FLAG_METADATA))
+               {
+                       ERROR("Expected metadata resource, but found "
+                             "non-metadata stream.");
+                       ret = WIMLIB_ERR_INVALID_PIPABLE_WIM;
+                       goto out_wimlib_free;
+               }
+
+               if (i == image) {
+                       /* Metadata resource is for the images being extracted.
+                        * Parse it and save the metadata in memory.  */
+                       ret = read_metadata_resource(pwm, imd);
+                       if (ret)
+                               goto out_wimlib_free;
+                       imd->modified = 1;
+               } else {
+                       /* Metadata resource is not for the image being
+                        * extracted.  Skip over it.  */
+                       ret = skip_pwm_stream(metadata_lte);
+                       if (ret)
+                               goto out_wimlib_free;
+               }
+       }
+       /* Extract the image.  */
+       extract_flags |= WIMLIB_EXTRACT_FLAG_FROM_PIPE;
+       ret = do_wimlib_extract_image(pwm, image, target,
+                                     extract_flags, NULL, 0, progress_func);
+       /* Clean up and return.  */
+out_wimlib_free:
+       wimlib_free(pwm);
+       return ret;
+}
+
+/* API function documented in wimlib.h  */
+WIMLIBAPI int
+wimlib_extract_image(WIMStruct *wim,
+                    int image,
+                    const tchar *target,
+                    int extract_flags,
+                    WIMStruct **additional_swms,
+                    unsigned num_additional_swms,
+                    wimlib_progress_func_t progress_func)
+{
+       extract_flags &= WIMLIB_EXTRACT_MASK_PUBLIC;
+       return do_wimlib_extract_image(wim, image, target, extract_flags,
+                                      additional_swms, num_additional_swms,
+                                      progress_func);
+}
index 7a3673235226e20b9974283ac78097121cd33de5..2852571c413cb13ea1f29ae4174c1155779b7b11 100644 (file)
 #  include "config.h"
 #endif
 
+#include "wimlib/assert.h"
+#include "wimlib/error.h"
 #include "wimlib/file_io.h"
+#include "wimlib/util.h"
 #ifdef __WIN32__
 #  include "wimlib/win32.h" /* For pread(), pwrite() replacements */
 #else
 #include <unistd.h>
 
 
-/* Like read(), but keep trying until everything has been written or we know for
- * sure that there was an error (or end-of-file). */
-size_t
-full_read(int fd, void *buf, size_t count)
+/* Wrapper around read() that keeps retrying until all requested bytes have been
+ * read or until end-of file has occurred.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS                      (0)
+ *     WIMLIB_ERR_READ                         (errno set)
+ *     WIMLIB_ERR_UNEXPECTED_END_OF_FILE       (errno set to 0)
+ */
+int
+full_read(struct filedes *fd, void *buf, size_t count)
 {
        ssize_t bytes_read;
        size_t bytes_remaining;
@@ -48,69 +57,134 @@ full_read(int fd, void *buf, size_t count)
             bytes_remaining != 0;
             bytes_remaining -= bytes_read, buf += bytes_read)
        {
-               bytes_read = read(fd, buf, bytes_remaining);
-               if (bytes_read <= 0) {
-                       if (bytes_read == 0)
-                               errno = EIO;
-                       else if (errno == EINTR)
+               bytes_read = read(fd->fd, buf, bytes_remaining);
+               if (unlikely(bytes_read <= 0)) {
+                       if (bytes_read == 0) {
+                               errno = 0;
+                               return WIMLIB_ERR_UNEXPECTED_END_OF_FILE;
+                       } else if (errno == EINTR) {
                                continue;
-                       break;
+                       } else {
+                               return WIMLIB_ERR_READ;
+                       }
                }
        }
-       return count - bytes_remaining;
+       count -= bytes_remaining;
+       fd->offset += count;
+       return 0;
 }
 
-/* Like write(), but keep trying until everything has been written or we know
- * for sure that there was an error. */
-size_t
-full_write(int fd, const void *buf, size_t count)
+static int
+pipe_read(struct filedes *fd, void *buf, size_t count, off_t offset)
 {
-       ssize_t bytes_written;
+       int ret;
+
+       if (offset < fd->offset) {
+               ERROR("Can't seek backwards in pipe "
+                     "(offset %"PRIu64" => %"PRIu64").\n"
+                     "      Make sure the WIM was captured as "
+                     "pipable.",
+                       fd->offset, offset);
+               errno = ESPIPE;
+               return WIMLIB_ERR_RESOURCE_ORDER;
+       }
+       while (fd->offset != offset) {
+               size_t bytes_to_read = min(offset - fd->offset, BUFFER_SIZE);
+               u8 dummy[bytes_to_read];
+
+               ret = full_read(fd, dummy, bytes_to_read);
+               if (ret)
+                       return ret;
+       }
+       return full_read(fd, buf, count);
+}
+
+/* Wrapper around pread() that keep retrying until all requested bytes have been
+ * read or until end-of file has occurred.  This also transparently handle
+ * reading from pipe files, but the caller needs to be sure the requested offset
+ * is greater than or equal to the current offset, or else
+ * WIMLIB_ERR_RESOURCE_ORDER will be returned.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS                      (0)
+ *     WIMLIB_ERR_READ                         (errno set)
+ *     WIMLIB_ERR_UNEXPECTED_END_OF_FILE       (errno set to 0)
+ *     WIMLIB_ERR_RESOURCE_ORDER               (errno set to ESPIPE)
+ * */
+int
+full_pread(struct filedes *fd, void *buf, size_t count, off_t offset)
+{
+       ssize_t bytes_read;
        size_t bytes_remaining;
 
+       if (fd->is_pipe)
+               goto is_pipe;
+
        for (bytes_remaining = count;
             bytes_remaining != 0;
-            bytes_remaining -= bytes_written, buf += bytes_written)
+            bytes_remaining -= bytes_read, buf += bytes_read,
+               offset += bytes_read)
        {
-               bytes_written = write(fd, buf, bytes_remaining);
-               if (bytes_written < 0) {
-                       if (errno == EINTR)
+               bytes_read = pread(fd->fd, buf, bytes_remaining, offset);
+               if (unlikely(bytes_read <= 0)) {
+                       if (bytes_read == 0) {
+                               errno = 0;
+                               return WIMLIB_ERR_UNEXPECTED_END_OF_FILE;
+                       } else if (errno == EINTR) {
                                continue;
-                       break;
+                       } else if (errno == ESPIPE) {
+                               wimlib_assert(count == bytes_remaining);
+                               fd->is_pipe = 1;
+                               goto is_pipe;
+                       } else {
+                               return WIMLIB_ERR_READ;
+                       }
                }
        }
-       return count - bytes_remaining;
+       return 0;
+
+is_pipe:
+       return pipe_read(fd, buf, count, offset);
 }
 
-/* Like pread(), but keep trying until everything has been read or we know for
- * sure that there was an error (or end-of-file) */
-size_t
-full_pread(int fd, void *buf, size_t count, off_t offset)
+/* Wrapper around write() that keeps retrying until all requested bytes have
+ * been written.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS                      (0)
+ *     WIMLIB_ERR_WRITE                        (errno set)
+ */
+int
+full_write(struct filedes *fd, const void *buf, size_t count)
 {
-       ssize_t bytes_read;
+       ssize_t bytes_written;
        size_t bytes_remaining;
 
        for (bytes_remaining = count;
             bytes_remaining != 0;
-            bytes_remaining -= bytes_read, buf += bytes_read,
-               offset += bytes_read)
+            bytes_remaining -= bytes_written, buf += bytes_written)
        {
-               bytes_read = pread(fd, buf, bytes_remaining, offset);
-               if (bytes_read <= 0) {
-                       if (bytes_read == 0)
-                               errno = EIO;
-                       else if (errno == EINTR)
+               bytes_written = write(fd->fd, buf, bytes_remaining);
+               if (unlikely(bytes_written < 0)) {
+                       if (errno == EINTR)
                                continue;
-                       break;
+                       return WIMLIB_ERR_WRITE;
                }
        }
-       return count - bytes_remaining;
+       fd->offset += count;
+       return 0;
 }
 
-/* Like pwrite(), but keep trying until everything has been written or we know
- * for sure that there was an error. */
-size_t
-full_pwrite(int fd, const void *buf, size_t count, off_t offset)
+
+/* Wrapper around pwrite() that keep retrying until all requested bytes have been
+ * written.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS      (0)
+ *     WIMLIB_ERR_WRITE        (errno set)
+ * */
+int
+full_pwrite(struct filedes *fd, const void *buf, size_t count, off_t offset)
 {
        ssize_t bytes_written;
        size_t bytes_remaining;
@@ -120,30 +194,35 @@ full_pwrite(int fd, const void *buf, size_t count, off_t offset)
             bytes_remaining -= bytes_written, buf += bytes_written,
                offset += bytes_written)
        {
-               bytes_written = pwrite(fd, buf, bytes_remaining, offset);
-               if (bytes_written < 0) {
+               bytes_written = pwrite(fd->fd, buf, bytes_remaining, offset);
+               if (unlikely(bytes_written < 0)) {
                        if (errno == EINTR)
                                continue;
-                       break;
+                       return WIMLIB_ERR_WRITE;
                }
        }
-       return count - bytes_remaining;
+       return 0;
 }
 
-/* Like writev(), but keep trying until everything has been written or we know
- * for sure that there was an error. */
-size_t
-full_writev(int fd, struct iovec *iov, int iovcnt)
+/* Wrapper around writev() that keep retrying until all requested bytes have been
+ * written.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS      (0)
+ *     WIMLIB_ERR_WRITE        (errno set)
+ * */
+int
+full_writev(struct filedes *fd, struct iovec *iov, int iovcnt)
 {
        size_t total_bytes_written = 0;
        while (iovcnt > 0) {
                ssize_t bytes_written;
 
-               bytes_written = writev(fd, iov, iovcnt);
+               bytes_written = writev(fd->fd, iov, iovcnt);
                if (bytes_written < 0) {
                        if (errno == EINTR)
                                continue;
-                       break;
+                       return WIMLIB_ERR_WRITE;
                }
                total_bytes_written += bytes_written;
                while (bytes_written) {
@@ -158,11 +237,37 @@ full_writev(int fd, struct iovec *iov, int iovcnt)
                        }
                }
        }
-       return total_bytes_written;
+       fd->offset += total_bytes_written;
+       return 0;
+}
+
+ssize_t
+raw_pread(struct filedes *fd, void *buf, size_t count, off_t offset)
+{
+       return pread(fd->fd, buf, count, offset);
+}
+
+ssize_t
+raw_pwrite(struct filedes *fd, const void *buf, size_t count, off_t offset)
+{
+       return pwrite(fd->fd, buf, count, offset);
+}
+
+off_t filedes_seek(struct filedes *fd, off_t offset)
+{
+       if (fd->is_pipe) {
+               errno = ESPIPE;
+               return -1;
+       }
+       if (fd->offset != offset) {
+               if (lseek(fd->fd, offset, SEEK_SET) == -1)
+                       return -1;
+               fd->offset = offset;
+       }
+       return offset;
 }
 
-off_t
-filedes_offset(int fd)
+bool filedes_is_seekable(struct filedes *fd)
 {
-       return lseek(fd, 0, SEEK_CUR);
+       return !fd->is_pipe && lseek(fd->fd, 0, SEEK_CUR) != -1;
 }
index 475534400ce2d5a3369085441467f579031d88ce..3f316f1d993d1f84fa9852fde53e9ec900ad3f09 100644 (file)
@@ -108,7 +108,7 @@ inode_table_insert(struct wim_dentry *dentry, void *_table)
                                              "\"%"TS"\" <=> \"%"TS"\"",
                                              dentry_full_path(dentry),
                                              dentry_full_path(inode_first_dentry(inode)));
-                                       return WIMLIB_ERR_INVALID_DENTRY;
+                                       return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
                                }
                                inode_add_dentry(dentry, inode);
                                return 0;
@@ -311,7 +311,7 @@ fix_true_inode(struct wim_inode *inode, struct list_head *inode_list)
                if (dentry != ref_dentry) {
                        if (!inodes_consistent(ref_inode, dentry->d_inode)) {
                                inconsistent_inode(ref_inode);
-                               return WIMLIB_ERR_INVALID_DENTRY;
+                               return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
                        }
                        /* Free the unneeded `struct wim_inode'. */
                        wimlib_assert(dentry->d_inode->i_nlink == 1);
@@ -349,7 +349,6 @@ fix_nominal_inode(struct wim_inode *inode, struct list_head *inode_list,
        struct hlist_node *cur, *tmp;
        int ret;
        size_t num_true_inodes;
-       unsigned nominal_group_size = inode_link_count(inode);
 
        LIST_HEAD(dentries_with_data_streams);
        LIST_HEAD(dentries_with_no_data_streams);
@@ -378,6 +377,7 @@ fix_nominal_inode(struct wim_inode *inode, struct list_head *inode_list,
         * inode to be a true inode */
        if (list_empty(&dentries_with_data_streams)) {
        #ifdef ENABLE_DEBUG
+               unsigned nominal_group_size = inode_link_count(inode);
                if (nominal_group_size > 1) {
                        DEBUG("Found link group of size %u without "
                              "any data streams:", nominal_group_size);
@@ -420,13 +420,13 @@ next_dentry_2:
          * streamless dentries to. */
        if (!list_empty(&dentries_with_no_data_streams)) {
                if (num_true_inodes != 1) {
-                       ERROR("Hard inode ambiguity detected!");
+                       ERROR("Hard link ambiguity detected!");
                        ERROR("We split up inode 0x%"PRIx64" due to "
                              "inconsistencies,", inode->i_ino);
                        ERROR("but dentries with no stream information remained. "
                              "We don't know which inode");
                        ERROR("to assign them to.");
-                       ret = WIMLIB_ERR_INVALID_DENTRY;
+                       ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
                        goto out_cleanup_true_inode_list;
                }
                inode = container_of(true_inodes.first, struct wim_inode, i_hlist);
@@ -514,9 +514,13 @@ fix_inodes(struct wim_inode_table *table, struct list_head *inode_list,
  * WIM) is examined for consistency and may be split into multiple "true" inodes
  * that are maximally sized consistent sets of dentries.
  *
- * Return 0 on success; WIMLIB_ERR_NOMEM or WIMLIB_ERR_INVALID_DENTRY on
- * failure.  On success, the list of "true" inodes, linked by the i_hlist field,
+ * On success, the list of "true" inodes, linked by the i_hlist field,
  * is returned in the hlist @inode_list.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_METADATA_RESOURCE
+ *     WIMLIB_ERR_NOMEM
  */
 int
 dentry_tree_fix_inodes(struct wim_dentry *root, struct list_head *inode_list)
index 6bdf089f1fb132533147eb30d8d73af5f211acdd..73e0becaa8b2b29215b46c41053f6e0eb6c88fa1 100644 (file)
 
 #include <limits.h>
 #include <string.h>
-
-/* WIM magic characters, translated to a single 64-bit little endian number. */
-#define WIM_MAGIC \
-               cpu_to_le64(((u64)'M' << 0) |           \
-                           ((u64)'S' << 8) |           \
-                           ((u64)'W' << 16) |          \
-                           ((u64)'I' << 24) |          \
-                           ((u64)'M' << 32) |          \
-                           ((u64)'\0' << 40) |         \
-                           ((u64)'\0' << 48) |         \
-                           ((u64)'\0' << 54))
+#include <unistd.h>
+#ifdef HAVE_ALLOCA_H
+#  include <alloca.h>
+#else
+#  include <stdlib.h>
+#endif
 
 /* On-disk format of the WIM header. */
 struct wim_header_disk {
@@ -113,30 +108,72 @@ struct wim_header_disk {
        u8 unused[60];
 } _packed_attribute;
 
-/* Reads the header from a WIM file.  */
+/*
+ * Reads the header from a WIM file.
+ *
+ * @filename
+ *     Name of the WIM file (for error/debug messages only), or NULL if reading
+ *     directly from file descriptor.
+ * @in_fd
+ *     File descriptor, positioned at offset 0, to read the header from.
+ * @hdr
+ *     Structure to read the header into.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_IMAGE_COUNT
+ *     WIMLIB_ERR_INVALID_CHUNK_SIZE
+ *     WIMLIB_ERR_INVALID_PART_NUMBER
+ *     WIMLIB_ERR_NOT_A_WIM_FILE
+ *     WIMLIB_ERR_READ
+ *     WIMLIB_ERR_UNEXPECTED_END_OF_FILE
+ *     WIMLIB_ERR_UNKNOWN_VERSION
+ */
 int
-read_header(const tchar *filename, int in_fd, struct wim_header *hdr)
+read_wim_header(const tchar *filename, struct filedes *in_fd,
+               struct wim_header *hdr)
 {
        struct wim_header_disk disk_hdr _aligned_attribute(8);
+       int ret;
+       tchar *pipe_str;
+
+       wimlib_assert(in_fd->offset == 0);
+
+       if (!filename) {
+               pipe_str = alloca(40);
+               tsprintf(pipe_str, "[fd %d]", in_fd->fd);
+               filename = pipe_str;
+       }
 
        BUILD_BUG_ON(sizeof(struct wim_header_disk) != WIM_HEADER_DISK_SIZE);
 
        DEBUG("Reading WIM header from \"%"TS"\"", filename);
 
-       if (full_pread(in_fd, &disk_hdr, sizeof(disk_hdr), 0) != sizeof(disk_hdr)) {
-               ERROR_WITH_ERRNO("\"%"TS"\": Error reading header", filename);
-               return WIMLIB_ERR_READ;
-       }
+       ret = full_read(in_fd, &disk_hdr, sizeof(disk_hdr));
+       if (ret)
+               goto read_error;
 
        if (disk_hdr.magic != WIM_MAGIC) {
-               ERROR("\"%"TS"\": Invalid magic characters in header", filename);
-               return WIMLIB_ERR_NOT_A_WIM_FILE;
+               if (disk_hdr.magic == PWM_MAGIC) {
+                       /* Pipable WIM:  Use header at end instead, unless
+                        * actually reading from a pipe.  */
+                       if (!in_fd->is_pipe) {
+                               lseek(in_fd->fd, -WIM_HEADER_DISK_SIZE, SEEK_END);
+                               ret = full_read(in_fd, &disk_hdr, sizeof(disk_hdr));
+                               if (ret)
+                                       goto read_error;
+                       }
+               } else {
+                       ERROR("\"%"TS"\": Invalid magic characters in header", filename);
+                       return WIMLIB_ERR_NOT_A_WIM_FILE;
+               }
        }
+       hdr->magic = disk_hdr.magic;
 
        if (le32_to_cpu(disk_hdr.hdr_size) != sizeof(struct wim_header_disk)) {
                ERROR("\"%"TS"\": Header size is invalid (%u bytes)",
                      filename, le32_to_cpu(disk_hdr.hdr_size));
-               return WIMLIB_ERR_INVALID_HEADER_SIZE;
+               return WIMLIB_ERR_INVALID_HEADER;
        }
 
        if (le32_to_cpu(disk_hdr.wim_version) != WIM_VERSION) {
@@ -186,23 +223,27 @@ read_header(const tchar *filename, int in_fd, struct wim_header *hdr)
        hdr->boot_idx = le32_to_cpu(disk_hdr.boot_idx);
        get_resource_entry(&disk_hdr.integrity_table_res_entry, &hdr->integrity);
        return 0;
+
+read_error:
+       ERROR_WITH_ERRNO("\"%"TS"\": Error reading header", filename);
+       return ret;
 }
 
-/*
- * Writes the header for a WIM file.
- *
- * @hdr:       A pointer to a struct wim_header structure that describes the header.
- * @out_fd:    The file descriptor to the WIM file, opened for writing.
- *
- * Returns zero on success, nonzero on failure.
- */
+/* Writes the header for a WIM file at the specified offset.  If the offset
+ * specified is the current one, the position is advanced by the size of the
+ * header.  */
 int
-write_header(const struct wim_header *hdr, int out_fd)
+write_wim_header_at_offset(const struct wim_header *hdr, struct filedes *out_fd,
+                          off_t offset)
 {
        struct wim_header_disk disk_hdr _aligned_attribute(8);
-       DEBUG("Writing WIM header.");
+       int ret;
+
+       DEBUG("Writing %sWIM header at offset %"PRIu64,
+             ((hdr->magic == PWM_MAGIC) ? "pipable " : ""),
+             offset);
 
-       disk_hdr.magic = WIM_MAGIC;
+       disk_hdr.magic = hdr->magic;
        disk_hdr.hdr_size = cpu_to_le32(sizeof(struct wim_header_disk));
        disk_hdr.wim_version = cpu_to_le32(WIM_VERSION);
        disk_hdr.wim_flags = cpu_to_le32(hdr->flags);
@@ -220,51 +261,38 @@ write_header(const struct wim_header *hdr, int out_fd)
        put_resource_entry(&hdr->integrity, &disk_hdr.integrity_table_res_entry);
        memset(disk_hdr.unused, 0, sizeof(disk_hdr.unused));
 
-       if (full_pwrite(out_fd, &disk_hdr, sizeof(disk_hdr), 0) != sizeof(disk_hdr)) {
+       if (offset == out_fd->offset)
+               ret = full_write(out_fd, &disk_hdr, sizeof(disk_hdr));
+       else
+               ret = full_pwrite(out_fd, &disk_hdr, sizeof(disk_hdr), offset);
+       if (ret)
                ERROR_WITH_ERRNO("Failed to write WIM header");
-               return WIMLIB_ERR_WRITE;
-       }
-       DEBUG("Done writing WIM header");
-       return 0;
+       return ret;
 }
 
-/* Update just the wim_flags field. */
+/* Writes the header for a WIM file at the output file descriptor's current
+ * offset.  */
 int
-write_header_flags(u32 hdr_flags, int out_fd)
+write_wim_header(const struct wim_header *hdr, struct filedes *out_fd)
 {
-       le32 flags = cpu_to_le32(hdr_flags);
-       if (full_pwrite(out_fd, &flags, sizeof(flags),
-                       offsetof(struct wim_header_disk, wim_flags)) != sizeof(flags))
-       {
-               return WIMLIB_ERR_WRITE;
-       } else {
-               return 0;
-       }
-
+       return write_wim_header_at_offset(hdr, out_fd, out_fd->offset);
 }
 
-/* Update just the part_number and total_parts fields. */
+/* Update just the wim_flags field. */
 int
-write_header_part_data(u16 part_number, u16 total_parts, int out_fd)
+write_wim_header_flags(u32 hdr_flags, struct filedes *out_fd)
 {
-       le16 part_data[2] = {
-               cpu_to_le16(part_number),
-               cpu_to_le16(total_parts),
-       };
-       if (full_pwrite(out_fd, &part_data, sizeof(part_data),
-                       offsetof(struct wim_header_disk, part_number)) != sizeof(part_data))
-       {
-               return WIMLIB_ERR_WRITE;
-       } else {
-               return 0;
-       }
+       le32 flags = cpu_to_le32(hdr_flags);
+
+       return full_pwrite(out_fd, &flags, sizeof(flags),
+                          offsetof(struct wim_header_disk, wim_flags));
 }
 
 /*
  * Initializes the header for a WIM file.
  */
 int
-init_header(struct wim_header *hdr, int ctype)
+init_wim_header(struct wim_header *hdr, int ctype)
 {
        memset(hdr, 0, sizeof(struct wim_header));
        switch (ctype) {
@@ -286,6 +314,7 @@ init_header(struct wim_header *hdr, int ctype)
        hdr->total_parts = 1;
        hdr->part_number = 1;
        randomize_byte_array(hdr->guid, sizeof(hdr->guid));
+       hdr->magic = WIM_MAGIC;
        return 0;
 }
 
@@ -307,7 +336,7 @@ struct hdr_flag hdr_flags[] = {
        {WIM_HDR_FLAG_COMPRESS_XPRESS,  "COMPRESS_XPRESS"},
 };
 
-/* Prints information from the header of the WIM file associated with @wim. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI void
 wimlib_print_header(const WIMStruct *wim)
 {
index fd656a4e272f1c908dffb522e91884a7da435500..1eeb671aa178e93c1eb1ea44a819bc2500e9d53f 100644 (file)
@@ -55,28 +55,28 @@ struct integrity_table {
 } _packed_attribute;
 
 static int
-calculate_chunk_sha1(int in_fd, size_t this_chunk_size,
+calculate_chunk_sha1(struct filedes *in_fd, size_t this_chunk_size,
                     off_t offset, u8 sha1_md[])
 {
        u8 buf[BUFFER_SIZE];
        SHA_CTX ctx;
        size_t bytes_remaining;
        size_t bytes_to_read;
-       size_t bytes_read;
+       int ret;
 
        bytes_remaining = this_chunk_size;
        sha1_init(&ctx);
        do {
                bytes_to_read = min(bytes_remaining, sizeof(buf));
-               bytes_read = full_pread(in_fd, buf, bytes_to_read, offset);
-               if (bytes_read != bytes_to_read) {
+               ret = full_pread(in_fd, buf, bytes_to_read, offset);
+               if (ret) {
                        ERROR_WITH_ERRNO("Read error while calculating "
                                         "integrity checksums");
-                       return WIMLIB_ERR_READ;
+                       return ret;
                }
-               sha1_update(&ctx, buf, bytes_read);
-               bytes_remaining -= bytes_read;
-               offset += bytes_read;
+               sha1_update(&ctx, buf, bytes_to_read);
+               bytes_remaining -= bytes_to_read;
+               offset += bytes_to_read;
        } while (bytes_remaining);
        sha1_final(sha1_md, &ctx);
        return 0;
@@ -86,118 +86,67 @@ calculate_chunk_sha1(int in_fd, size_t this_chunk_size,
 /*
  * read_integrity_table: -  Reads the integrity table from a WIM file.
  *
- * @res_entry:
- *     The resource entry that specifies the location of the integrity table.
- *     The integrity table must exist (i.e. res_entry->offset must not be 0).
- *
- * @in_fd:
- *     File descriptor to the WIM file, opened for reading.
+ * @wim:
+ *     WIMStruct for the WIM file; @wim->hdr.integrity specifies the location
+ *     of the integrity table.  The integrity table must exist (i.e.
+ *     res_entry->offset must not be 0).  @wim->in_fd is expected to be a
+ *     seekable file descriptor to the WIM file opened for reading.
  *
  * @num_checked_bytes:
- *     Number of bytes of data that should be checked by the integrity table.
- *
- * @table ret:
- *     On success, a pointer to an in-memory structure containing the integrity
- *     information is written to this location.
- *
- * Returns 0 on success; nonzero on failure.  The possible error codes are:
- *
- *     * WIMLIB_ERR_INVALID_INTEGRITY_TABLE:  The integrity table is invalid.
- *     * WIMLIB_ERR_NOMEM:  Could not allocate memory to store the integrity
- *                                 data.
- *     * WIMLIB_ERR_READ:   Could not read the integrity data from the WIM file.
+ *     Number of bytes of data that should be checked by the integrity table.
+ *
+ * @table_ret:
+ *     On success, a pointer to an in-memory structure containing the integrity
+ *     information is written to this location.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_INTEGRITY_TABLE
+ *     WIMLIB_ERR_NOMEM
+ *     WIMLIB_ERR_READ
+ *     WIMLIB_ERR_UNEXPECTED_END_OF_FILE
  */
 static int
-read_integrity_table(const struct resource_entry *res_entry,
-                    int in_fd,
-                    u64 num_checked_bytes,
+read_integrity_table(WIMStruct *wim, u64 num_checked_bytes,
                     struct integrity_table **table_ret)
 {
        struct integrity_table *table;
        int ret;
-       u64 expected_size;
-       u64 expected_num_entries;
 
-       if (resource_is_compressed(res_entry)) {
-               ERROR("Didn't expect a compressed integrity table");
-               return WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
-       }
+       if (wim->hdr.integrity.size < 8)
+               goto invalid;
 
-       if (res_entry->size < 8 || res_entry->size  > 0xffffffff) {
-               ERROR("Integrity table resource header is invalid");
-               return WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
-       }
-
-       /* Read the integrity table into memory. */
-       table = MALLOC((size_t)res_entry->size);
-       if (table == NULL) {
-               ERROR("Can't allocate %zu bytes for integrity table",
-                     (size_t)res_entry->size);
-               return WIMLIB_ERR_NOMEM;
-       }
+       DEBUG("Reading integrity table (offset %"PRIu64", "
+             "original_size %"PRIu64")",
+             wim->hdr.integrity.offset, wim->hdr.integrity.original_size);
 
-       if (full_pread(in_fd, table, res_entry->size,
-                      res_entry->offset) != res_entry->size)
-       {
-               ERROR("Failed to read integrity table (size = %zu, "
-                     " offset = %"PRIu64")",
-                     (size_t)res_entry->size, res_entry->offset);
-               ret = WIMLIB_ERR_READ;
-               goto out_free_table;
-       }
+       ret = res_entry_to_data(&wim->hdr.integrity, wim, (void**)&table);
+       if (ret)
+               return ret;
 
        table->size        = le32_to_cpu(table->size);
        table->num_entries = le32_to_cpu(table->num_entries);
        table->chunk_size  = le32_to_cpu(table->chunk_size);
 
-       if (table->size != res_entry->size) {
-               ERROR("Inconsistent integrity table sizes: Table header says "
-                     "%u bytes but resource entry says %u bytes",
-                     table->size, (unsigned)res_entry->size);
-               ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
-               goto out_free_table;
-       }
-
        DEBUG("table->size = %u, table->num_entries = %u, "
              "table->chunk_size = %u",
              table->size, table->num_entries, table->chunk_size);
 
-       expected_size = (u64)table->num_entries * SHA1_HASH_SIZE + 12;
-
-       if (table->size != expected_size) {
-               ERROR("Integrity table is %u bytes, but expected %"PRIu64" "
-                     "bytes to hold %u entries",
-                     table->size, expected_size, table->num_entries);
-               ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
-               goto out_free_table;
-       }
-
-       if (table->chunk_size == 0) {
-               ERROR("Cannot use integrity chunk size of 0");
-               ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
-               goto out_free_table;
+       if (table->size != wim->hdr.integrity.original_size ||
+           table->size != (u64)table->num_entries * SHA1_HASH_SIZE + 12 ||
+           table->chunk_size == 0 ||
+           table->num_entries != DIV_ROUND_UP(num_checked_bytes, table->chunk_size))
+       {
+               FREE(table);
+               goto invalid;
        }
 
-       expected_num_entries = DIV_ROUND_UP(num_checked_bytes, table->chunk_size);
-
-       if (table->num_entries != expected_num_entries) {
-               ERROR("%"PRIu64" integrity table entries would be required "
-                     "to checksum the %"PRIu64" bytes from the end of the "
-                     "header to the",
-                     expected_num_entries, num_checked_bytes);
-               ERROR("end of the lookup table with a chunk size of %u, but "
-                     "there were only %u entries",
-                     table->chunk_size, table->num_entries);
-               ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
-               goto out_free_table;
-       }
        *table_ret = table;
-       ret = 0;
-       goto out;
-out_free_table:
-       FREE(table);
-out:
-       return ret;
+       return 0;
+
+invalid:
+       ERROR("Integrity table is invalid");
+       return WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
 }
 
 /*
@@ -207,32 +156,36 @@ out:
  * (WIM_HEADER_DISK_SIZE).
  *
  * @in_fd:
- *     File descriptor for the file to be checked, opened for reading.  Does
- *     not need to be at any specific location in the file.
+ *     File descriptor for the file to be checked, opened for reading.  Does
+ *     not need to be at any specific location in the file.
  *
  * @new_check_end:
- *     Offset of byte after the last byte to be checked.
+ *     Offset of byte after the last byte to be checked.
  *
  * @old_table:
- *     If non-NULL, a pointer to the table containing the previously calculated
- *     integrity data for a prefix of this file.
+ *     If non-NULL, a pointer to the table containing the previously calculated
+ *     integrity data for a prefix of this file.
  *
  * @old_check_end:
- *     If @old_table is non-NULL, the byte after the last byte that was checked
- *     in the old table.  Must be less than or equal to new_check_end.
+ *     If @old_table is non-NULL, the byte after the last byte that was checked
+ *     in the old table.  Must be less than or equal to new_check_end.
  *
  * @progress_func:
- *     If non-NULL, a progress function that will be called after every
- *     calculated chunk.
+ *     If non-NULL, a progress function that will be called after every
+ *     calculated chunk.
  *
  * @integrity_table_ret:
- *     On success, a pointer to the calculated integrity table is written into
- *     this location.
- *
- * Returns 0 on success; nonzero on failure.
+ *     On success, a pointer to the calculated integrity table is written into
+ *     this location.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_NOMEM
+ *     WIMLIB_ERR_READ
+ *     WIMLIB_ERR_UNEXPECTED_END_OF_FILE
  */
 static int
-calculate_integrity_table(int in_fd,
+calculate_integrity_table(struct filedes *in_fd,
                          off_t new_check_end,
                          const struct integrity_table *old_table,
                          off_t old_check_end,
@@ -334,37 +287,38 @@ calculate_integrity_table(int in_fd,
  * cannot be read, a warning is printed and the integrity information is
  * re-calculated.
  *
- * @fd:
- *     File descriptor to the WIM file, opened read-write, positioned at the
- *     location at which the integrity table is to be written.
- *
- * @integrity_res_entry:
- *     Resource entry which will be set to point to the integrity table on
- *     success.  In addition, if @old_lookup_table_end != 0, this initially
- *     must point to the resource entry for the old integrity table for the
- *     WIM.
+ * @wim:
+ *     WIMStruct for the WIM file.  @wim->out_fd must be a seekable descriptor
+ *     to the new WIM file, opened read-write, positioned at the location at
+ *     which the integrity table is to be written.  Furthermore,
+ *     @wim->hdr.integrity is expected to be a resource entry which will be set
+ *     to the integrity table information on success.  In addition, if
+ *     @old_lookup_table_end != 0, @wim->hdr.integrity must initially contain
+ *     information about the old integrity table, and @wim->in_fd must be a
+ *     seekable descriptor to the original WIM file opened for reading.
  *
  * @new_lookup_table_end:
- *     The offset of the byte directly following the lookup table in the WIM
- *     being written.
+ *     The offset of the byte directly following the lookup table in the WIM
+ *     being written.
  *
  * @old_lookup_table_end:
- *     If nonzero, the offset of the byte directly following the old lookup
- *     table in the WIM.
+ *     If nonzero, the offset of the byte directly following the old lookup
+ *     table in the WIM.
  *
  * @progress_func
- *     If non-NULL, a progress function that will be called after every
- *     calculated chunk.
- *
- * Returns:
- *     0 on success, nonzero on failure.  The possible error codes are:
- *        * WIMLIB_ERR_WRITE:  Could not write the integrity table.
- *        * WIMLIB_ERR_READ:   Could not read a chunk of data that needed
- *                             to be checked.
+ *     If non-NULL, a progress function that will be called after every
+ *     calculated chunk.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_INTEGRITY_TABLE
+ *     WIMLIB_ERR_NOMEM
+ *     WIMLIB_ERR_READ
+ *     WIMLIB_ERR_UNEXPECTED_END_OF_FILE
+ *     WIMLIB_ERR_WRITE
  */
 int
-write_integrity_table(int fd,
-                     struct resource_entry *integrity_res_entry,
+write_integrity_table(WIMStruct *wim,
                      off_t new_lookup_table_end,
                      off_t old_lookup_table_end,
                      wimlib_progress_func_t progress_func)
@@ -377,14 +331,12 @@ write_integrity_table(int fd,
 
        wimlib_assert(old_lookup_table_end <= new_lookup_table_end);
 
-       cur_offset = filedes_offset(fd);
-       if (cur_offset == -1)
-               return WIMLIB_ERR_WRITE;
+       cur_offset = wim->out_fd.offset;
 
-       if (integrity_res_entry->offset == 0 || old_lookup_table_end == 0) {
+       if (wim->hdr.integrity.offset == 0 || old_lookup_table_end == 0) {
                old_table = NULL;
        } else {
-               ret = read_integrity_table(integrity_res_entry, fd,
+               ret = read_integrity_table(wim,
                                           old_lookup_table_end - WIM_HEADER_DISK_SIZE,
                                           &old_table);
                if (ret == WIMLIB_ERR_INVALID_INTEGRITY_TABLE) {
@@ -396,7 +348,7 @@ write_integrity_table(int fd,
                }
        }
 
-       ret = calculate_integrity_table(fd, new_lookup_table_end,
+       ret = calculate_integrity_table(&wim->out_fd, new_lookup_table_end,
                                        old_table, old_lookup_table_end,
                                        progress_func, &new_table);
        if (ret)
@@ -408,16 +360,14 @@ write_integrity_table(int fd,
        new_table->num_entries = cpu_to_le32(new_table->num_entries);
        new_table->chunk_size  = cpu_to_le32(new_table->chunk_size);
 
-       if (full_write(fd, new_table, new_table_size) != new_table_size) {
-               ERROR_WITH_ERRNO("Failed to write WIM integrity table");
-               ret = WIMLIB_ERR_WRITE;
-       } else {
-               integrity_res_entry->offset        = cur_offset;
-               integrity_res_entry->size          = new_table_size;
-               integrity_res_entry->original_size = new_table_size;
-               integrity_res_entry->flags         = 0;
-               ret = 0;
-       }
+       ret = write_wim_resource_from_buffer(new_table,
+                                            new_table_size,
+                                            0,
+                                            &wim->out_fd,
+                                            WIMLIB_COMPRESSION_TYPE_NONE,
+                                            &wim->hdr.integrity,
+                                            NULL,
+                                            0);
        FREE(new_table);
 out_free_old_table:
        FREE(old_table);
@@ -430,27 +380,27 @@ out_free_old_table:
  * Checks a WIM for consistency with the integrity table.
  *
  * @in_fd:
- *     File descriptor to the WIM file, opened for reading.
+ *     File descriptor to the WIM file, opened for reading.
  *
  * @table:
- *     The integrity table for the WIM, read into memory.
+ *     The integrity table for the WIM, read into memory.
  *
  * @bytes_to_check:
- *     Number of bytes in the WIM that need to be checked (offset of end of the
- *     lookup table minus offset of end of the header).
+ *     Number of bytes in the WIM that need to be checked (offset of end of the
+ *     lookup table minus offset of end of the header).
  *
  * @progress_func
- *     If non-NULL, a progress function that will be called after every
- *     verified chunk.
+ *     If non-NULL, a progress function that will be called after every
+ *     verified chunk.
  *
  * Returns:
- *     > 0 (WIMLIB_ERR_*) on error
- *     0 (WIM_INTEGRITY_OK) if the integrity was checked successfully and there
- *     were no inconsistencies.
- *     -1 (WIM_INTEGRITY_NOT_OK) if the WIM failed the integrity check.
+ *     > 0 (WIMLIB_ERR_READ, WIMLIB_ERR_UNEXPECTED_END_OF_FILE) on error
+ *     0 (WIM_INTEGRITY_OK) if the integrity was checked successfully and there
+ *     were no inconsistencies.
+ *     -1 (WIM_INTEGRITY_NOT_OK) if the WIM failed the integrity check.
  */
 static int
-verify_integrity(int in_fd, const tchar *filename,
+verify_integrity(struct filedes *in_fd, const tchar *filename,
                 const struct integrity_table *table,
                 u64 bytes_to_check,
                 wimlib_progress_func_t progress_func)
@@ -505,19 +455,20 @@ verify_integrity(int in_fd, const tchar *filename,
  * table.
  *
  * @wim:
- *     The WIM, opened for reading, and with the header already read.
+ *     The WIM, opened for reading.
  *
  * @progress_func
- *     If non-NULL, a progress function that will be called after every
- *     verified chunk.
+ *     If non-NULL, a progress function that will be called after every
+ *     verified chunk.
  *
  * Returns:
- *     > 0 (WIMLIB_ERR_*) on error
- *     0 (WIM_INTEGRITY_OK) if the integrity was checked successfully and there
- *     were no inconsistencies.
- *     -1 (WIM_INTEGRITY_NOT_OK) if the WIM failed the integrity check.
- *     -2 (WIM_INTEGRITY_NONEXISTENT) if the WIM contains no integrity
- *     information.
+ *     > 0 (WIMLIB_ERR_INVALID_INTEGRITY_TABLE, WIMLIB_ERR_READ,
+ *          WIMLIB_ERR_UNEXPECTED_END_OF_FILE) on error
+ *     0 (WIM_INTEGRITY_OK) if the integrity was checked successfully and there
+ *     were no inconsistencies.
+ *     -1 (WIM_INTEGRITY_NOT_OK) if the WIM failed the integrity check.
+ *     -2 (WIM_INTEGRITY_NONEXISTENT) if the WIM contains no integrity
+ *     information.
  */
 int
 check_wim_integrity(WIMStruct *wim, wimlib_progress_func_t progress_func)
@@ -542,11 +493,10 @@ check_wim_integrity(WIMStruct *wim, wimlib_progress_func_t progress_func)
 
        bytes_to_check = end_lookup_table_offset - WIM_HEADER_DISK_SIZE;
 
-       ret = read_integrity_table(&wim->hdr.integrity, wim->in_fd,
-                                  bytes_to_check, &table);
+       ret = read_integrity_table(wim, bytes_to_check, &table);
        if (ret)
                return ret;
-       ret = verify_integrity(wim->in_fd, wim->filename, table,
+       ret = verify_integrity(&wim->in_fd, wim->filename, table,
                               bytes_to_check, progress_func);
        FREE(table);
        return ret;
index a3eb07b557db4ad9a05f264b57dc2265b11b1e6e..5cda7739a1f57d2f5a0820b837b01c65a3c5f439 100644 (file)
 #endif
 
 #include "wimlib.h"
-#include "wimlib/lookup_table.h"
-#include "wimlib/metadata.h"
-#include "wimlib/resource.h"
-#include "wimlib/swm.h"
-#include "wimlib/write.h"
-#include "wimlib/xml.h"
+#include "wimlib/types.h"
+#include "wimlib/util.h"
+#include "wimlib/wim.h"
 
-#include <stdlib.h> /* for qsort() */
-
-static int
-join_wims(WIMStruct **swms, unsigned num_swms,
-         WIMStruct *joined_wim, int write_flags,
-         wimlib_progress_func_t progress_func)
-{
-       int ret;
-       unsigned i;
-       union wimlib_progress_info progress;
-       u64 total_bytes = 0;
-       u64 part_bytes;
-       u64 swm_part_sizes[num_swms];
-
-       /* Calculate total size of the streams in the split WIM parts. */
-       for (i = 0; i < num_swms; i++) {
-               part_bytes = lookup_table_total_stream_size(swms[i]->lookup_table);
-               swm_part_sizes[i] = part_bytes;
-               total_bytes += part_bytes;
-       }
-
-       if (progress_func) {
-               progress.join.total_bytes     = total_bytes;
-               progress.join.total_parts     = swms[0]->hdr.total_parts;
-               progress.join.completed_bytes = 0;
-               progress.join.completed_parts = 0;
-               progress_func(WIMLIB_PROGRESS_MSG_JOIN_STREAMS, &progress);
-       }
-
-       /* Write the non-metadata resources from each SWM part */
-       for (i = 0; i < num_swms; i++) {
-               ret = reopen_wim(swms[i]);
-               if (ret)
-                       return ret;
-               swms[i]->out_fd = joined_wim->out_fd;
-               swms[i]->hdr.part_number = 1;
-
-               ret = for_lookup_table_entry_pos_sorted(swms[i]->lookup_table,
-                                                       copy_resource,
-                                                       swms[i]);
-               swms[i]->out_fd = -1;
-               if (i != 0)
-                       close_wim(swms[i]);
-
-               if (ret)
-                       return ret;
-
-               if (progress_func) {
-                       progress.join.completed_bytes += swm_part_sizes[i];
-                       progress.join.completed_parts++;
-                       progress_func(WIMLIB_PROGRESS_MSG_JOIN_STREAMS, &progress);
-               }
-       }
-
-       /* Copy the metadata resources from the first SWM part */
-       joined_wim->hdr.image_count = swms[0]->hdr.image_count;
-       for (i = 0; i < joined_wim->hdr.image_count; i++) {
-               ret = copy_resource(swms[0]->image_metadata[i]->metadata_lte,
-                                   joined_wim);
-               if (ret)
-                       return ret;
-       }
-
-       /* Preserve some of the header flags */
-       joined_wim->hdr.flags |= (swms[0]->hdr.flags &
-                             (WIM_HDR_FLAG_RP_FIX | WIM_HDR_FLAG_READONLY));
-
-       /* Write lookup table, XML data, and optional integrity table */
-       merge_lookup_tables(joined_wim, swms, num_swms);
-       free_wim_info(joined_wim->wim_info);
-       joined_wim->wim_info = swms[0]->wim_info;
-       joined_wim->image_metadata = swms[0]->image_metadata;
-       ret = finish_write(joined_wim, WIMLIB_ALL_IMAGES, write_flags, progress_func);
-       joined_wim->wim_info = NULL;
-       joined_wim->image_metadata = NULL;
-       return ret;
-}
-
-static int
-cmp_swms_by_part_number(const void *swm1, const void *swm2)
-{
-       u16 partno_1 = (*(const WIMStruct**)swm1)->hdr.part_number;
-       u16 partno_2 = (*(const WIMStruct**)swm2)->hdr.part_number;
-       return (int)partno_1 - (int)partno_2;
-}
-
-/*
- * Join a set of split WIMs into a stand-alone WIM.
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_join(const tchar * const *swm_names,
            unsigned num_swms,
@@ -133,47 +42,77 @@ wimlib_join(const tchar * const *swm_names,
            wimlib_progress_func_t progress_func)
 {
        int ret;
-       WIMStruct *joined_wim = NULL;
        unsigned i;
+       unsigned j;
+       WIMStruct *swm0;
+       WIMStruct **swms;
+       unsigned num_additional_swms;
+       WIMStruct *wim;
 
        swm_open_flags |= WIMLIB_OPEN_FLAG_SPLIT_OK;
-       wim_write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
 
        if (num_swms < 1 || num_swms > 0xffff)
                return WIMLIB_ERR_INVALID_PARAM;
+       num_additional_swms = num_swms - 1;
+
+       swms = CALLOC(num_additional_swms, sizeof(swms[0]));
+       if (!swms)
+               return WIMLIB_ERR_NOMEM;
 
-       WIMStruct *swms[num_swms];
-       ZERO_ARRAY(swms);
+       swm0 = NULL;
+       for (i = 0, j = 0; i < num_swms; i++) {
+               WIMStruct *swm;
 
-       for (i = 0; i < num_swms; i++) {
-               ret = wimlib_open_wim(swm_names[i], swm_open_flags, &swms[i],
+               ret = wimlib_open_wim(swm_names[i], swm_open_flags, &swm,
                                      progress_func);
                if (ret)
-                       goto out_free_wims;
-
-               /* Don't open all the parts at the same time, in case there are
-                * a lot of them */
-               close_wim(swms[i]);
+                       goto out_free_swms;
+               if (swm->hdr.part_number == 1 && swm0 == NULL)
+                       swm0 = swm;
+               else
+                       swms[j++] = swm;
        }
 
-       qsort(swms, num_swms, sizeof(swms[0]), cmp_swms_by_part_number);
+       if (!swm0) {
+               ret = WIMLIB_ERR_SPLIT_INVALID;
+               goto out_free_swms;
+       }
 
-       ret = verify_swm_set(swms[0], &swms[1], num_swms - 1);
+       ret = wimlib_create_new_wim(swm0->compression_type, &wim);
        if (ret)
-               goto out_free_wims;
+               goto out_free_swms;
 
-       ret = wimlib_create_new_wim(swms[0]->compression_type, &joined_wim);
+       ret = wimlib_export_image(swm0, WIMLIB_ALL_IMAGES,
+                                 wim, NULL, NULL, 0,
+                                 swms, num_additional_swms,
+                                 progress_func);
        if (ret)
-               goto out_free_wims;
+               goto out_free_wim;
+
+       wim->hdr.flags |= swm0->hdr.flags & (WIM_HDR_FLAG_RP_FIX |
+                                            WIM_HDR_FLAG_READONLY);
+       if (!(wim_write_flags & (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+                                WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY)))
+       {
+               if (wim_has_integrity_table(swm0))
+                       wim_write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
+       }
+       if (!(wim_write_flags & (WIMLIB_WRITE_FLAG_PIPABLE |
+                                WIMLIB_WRITE_FLAG_NOT_PIPABLE)))
+       {
+               if (wim_is_pipable(swm0))
+                       wim_write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+       }
 
-       ret = begin_write(joined_wim, output_path, wim_write_flags);
-       if (ret)
-               goto out_free_wims;
-       ret = join_wims(swms, num_swms, joined_wim, wim_write_flags,
-                       progress_func);
-out_free_wims:
-       for (i = 0; i < num_swms; i++)
+
+       ret = wimlib_write(wim, output_path, WIMLIB_ALL_IMAGES,
+                          wim_write_flags, 1, progress_func);
+out_free_wim:
+       wimlib_free(wim);
+out_free_swms:
+       for (i = 0; i < num_additional_swms; i++)
                wimlib_free(swms[i]);
-       wimlib_free(joined_wim);
+       FREE(swms);
+       wimlib_free(swm0);
        return ret;
 }
index d155ceb0dfece101c11b55654ee57d754d9461a9..3b98553e9bb586d790df154f8de706351bc2ac0e 100644 (file)
@@ -2,7 +2,7 @@
  * lookup_table.c
  *
  * Lookup table, implemented as a hash table, that maps SHA1 message digests to
- * data streams.
+ * data streams; plus code to read and write the corresponding on-disk data.
  */
 
 /*
@@ -36,6 +36,7 @@
 #include "wimlib/paths.h"
 #include "wimlib/resource.h"
 #include "wimlib/util.h"
+#include "wimlib/write.h"
 
 #include <errno.h>
 #include <stdlib.h>
@@ -94,11 +95,9 @@ clone_lookup_table_entry(const struct wim_lookup_table_entry *old)
 
        new->extracted_file = NULL;
        switch (new->resource_location) {
+       case RESOURCE_IN_FILE_ON_DISK:
 #ifdef __WIN32__
-       case RESOURCE_WIN32:
        case RESOURCE_WIN32_ENCRYPTED:
-#else
-       case RESOURCE_IN_FILE_ON_DISK:
 #endif
 #ifdef WITH_FUSE
        case RESOURCE_IN_STAGING_FILE:
@@ -151,11 +150,9 @@ free_lookup_table_entry(struct wim_lookup_table_entry *lte)
 {
        if (lte) {
                switch (lte->resource_location) {
+               case RESOURCE_IN_FILE_ON_DISK:
        #ifdef __WIN32__
-               case RESOURCE_WIN32:
                case RESOURCE_WIN32_ENCRYPTED:
-       #else
-               case RESOURCE_IN_FILE_ON_DISK:
        #endif
        #ifdef WITH_FUSE
                case RESOURCE_IN_STAGING_FILE:
@@ -305,7 +302,6 @@ cmp_streams_by_wim_position(const void *p1, const void *p2)
                return 0;
 }
 
-
 static int
 add_lte_to_array(struct wim_lookup_table_entry *lte,
                 void *_pp)
@@ -367,75 +363,77 @@ struct wim_lookup_table_entry_disk {
 
 #define WIM_LOOKUP_TABLE_ENTRY_DISK_SIZE 50
 
+void
+lte_init_wim(struct wim_lookup_table_entry *lte, WIMStruct *wim)
+{
+       lte->resource_location = RESOURCE_IN_WIM;
+       lte->wim = wim;
+       if (lte->resource_entry.flags & WIM_RESHDR_FLAG_COMPRESSED)
+               lte->compression_type = wim->compression_type;
+       else
+               lte->compression_type = WIMLIB_COMPRESSION_TYPE_NONE;
+
+       if (wim_is_pipable(wim))
+               lte->is_pipable = 1;
+}
+
 /*
  * Reads the lookup table from a WIM file.
  *
  * Saves lookup table entries for non-metadata streams in a hash table, and
  * saves the metadata entry for each image in a special per-image location (the
  * image_metadata array).
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_LOOKUP_TABLE_ENTRY
+ *     WIMLIB_ERR_RESOURCE_NOT_FOUND
  */
 int
-read_lookup_table(WIMStruct *wim)
+read_wim_lookup_table(WIMStruct *wim)
 {
        int ret;
+       size_t i;
        size_t num_entries;
        struct wim_lookup_table *table;
        struct wim_lookup_table_entry *cur_entry, *duplicate_entry;
-       struct wim_lookup_table_entry_disk
-                       table_buf[BUFFER_SIZE / sizeof(struct wim_lookup_table_entry_disk)]
-                               _aligned_attribute(8);
+       struct wim_lookup_table_entry_disk *buf;
 
        BUILD_BUG_ON(sizeof(struct wim_lookup_table_entry_disk) !=
                     WIM_LOOKUP_TABLE_ENTRY_DISK_SIZE);
 
-       off_t offset;
-       size_t buf_entries_remaining;
-       const struct wim_lookup_table_entry_disk *disk_entry;
-
        DEBUG("Reading lookup table: offset %"PRIu64", size %"PRIu64"",
              wim->hdr.lookup_table_res_entry.offset,
-             wim->hdr.lookup_table_res_entry.original_size);
-
-       if (resource_is_compressed(&wim->hdr.lookup_table_res_entry)) {
-               ERROR("Didn't expect a compressed lookup table!");
-               ERROR("Ask the author to implement support for this.");
-               return WIMLIB_ERR_COMPRESSED_LOOKUP_TABLE;
-       }
+             wim->hdr.lookup_table_res_entry.size);
 
+       /* Calculate number of entries in the lookup table.  */
        num_entries = wim->hdr.lookup_table_res_entry.size /
                      sizeof(struct wim_lookup_table_entry_disk);
+
+
+       /* Read the lookup table into a buffer.  */
+       ret = res_entry_to_data(&wim->hdr.lookup_table_res_entry, wim,
+                               (void**)&buf);
+       if (ret)
+               goto out;
+
+       /* Allocate hash table.  */
        table = new_lookup_table(num_entries * 2 + 1);
        if (!table) {
-               ERROR("Failed to allocate stream hash table of size %zu",
-                     num_entries * 2 + 1);
-               return WIMLIB_ERR_NOMEM;
+               ERROR("Not enough memory to read lookup table.");
+               ret = WIMLIB_ERR_NOMEM;
+               goto out_free_buf;
        }
 
+       /* Allocate and initalize `struct wim_lookup_table_entry's from the
+        * on-disk lookup table.  */
        wim->current_image = 0;
-       offset = wim->hdr.lookup_table_res_entry.offset;
-       buf_entries_remaining = 0;
-       for (; num_entries != 0;
-            num_entries--, buf_entries_remaining--, disk_entry++)
-       {
-               if (buf_entries_remaining == 0) {
-                       size_t entries_to_read, bytes_to_read;
-
-                       entries_to_read = min(ARRAY_LEN(table_buf), num_entries);
-                       bytes_to_read = entries_to_read * sizeof(struct wim_lookup_table_entry_disk);
-                       if (full_pread(wim->in_fd, table_buf,
-                                      bytes_to_read, offset) != bytes_to_read)
-                       {
-                               ERROR_WITH_ERRNO("Error reading lookup table "
-                                                "(offset=%"PRIu64")", offset);
-                               ret = WIMLIB_ERR_READ;
-                               goto out_free_lookup_table;
-                       }
-                       offset += bytes_to_read;
-                       disk_entry = table_buf;
-                       buf_entries_remaining = entries_to_read;
-               }
+       for (i = 0; i < num_entries; i++) {
+               const struct wim_lookup_table_entry_disk *disk_entry = &buf[i];
+
                cur_entry = new_lookup_table_entry();
                if (!cur_entry) {
+                       ERROR("Not enough memory to read lookup table.");
                        ret = WIMLIB_ERR_NOMEM;
                        goto out_free_lookup_table;
                }
@@ -446,11 +444,7 @@ read_lookup_table(WIMStruct *wim)
                cur_entry->part_number = le16_to_cpu(disk_entry->part_number);
                cur_entry->refcnt = le32_to_cpu(disk_entry->refcnt);
                copy_hash(cur_entry->hash, disk_entry->hash);
-
-               if (cur_entry->resource_entry.flags & WIM_RESHDR_FLAG_COMPRESSED)
-                       cur_entry->compression_type = wim->compression_type;
-               else
-                       BUILD_BUG_ON(WIMLIB_COMPRESSION_TYPE_NONE != 0);
+               lte_init_wim(cur_entry, wim);
 
                if (cur_entry->part_number != wim->hdr.part_number) {
                        WARNING("A lookup table entry in part %hu of the WIM "
@@ -562,11 +556,13 @@ read_lookup_table(WIMStruct *wim)
        DEBUG("Done reading lookup table.");
        wim->lookup_table = table;
        ret = 0;
-       goto out;
+       goto out_free_buf;
 out_free_cur_entry:
        FREE(cur_entry);
 out_free_lookup_table:
        free_lookup_table(table);
+out_free_buf:
+       FREE(buf);
 out:
        wim->current_image = 0;
        return ret;
@@ -574,8 +570,8 @@ out:
 
 
 static void
-write_lookup_table_entry(const struct wim_lookup_table_entry *lte,
-                        struct wim_lookup_table_entry_disk *disk_entry)
+write_wim_lookup_table_entry(const struct wim_lookup_table_entry *lte,
+                            struct wim_lookup_table_entry_disk *disk_entry)
 {
        put_resource_entry(&lte->output_resource_entry, &disk_entry->resource_entry);
        disk_entry->part_number = cpu_to_le16(lte->part_number);
@@ -583,57 +579,47 @@ write_lookup_table_entry(const struct wim_lookup_table_entry *lte,
        copy_hash(disk_entry->hash, lte->hash);
 }
 
-int
-write_lookup_table_from_stream_list(struct list_head *stream_list,
-                                   int out_fd,
-                                   struct resource_entry *out_res_entry)
+static int
+write_wim_lookup_table_from_stream_list(struct list_head *stream_list,
+                                       struct filedes *out_fd,
+                                       struct resource_entry *out_res_entry,
+                                       int write_resource_flags)
 {
-       int ret;
-       off_t start_offset;
-       struct wim_lookup_table_entry_disk
-                       table_buf[BUFFER_SIZE / sizeof(struct wim_lookup_table_entry_disk)]
-                               _aligned_attribute(8);
        size_t table_size;
-       size_t bytes_to_write;
        struct wim_lookup_table_entry *lte;
-       size_t cur_idx;
-
-       start_offset = filedes_offset(out_fd);
-       if (start_offset == -1)
-               goto write_error;
+       struct wim_lookup_table_entry_disk *table_buf;
+       struct wim_lookup_table_entry_disk *table_buf_ptr;
+       int ret;
 
        table_size = 0;
-       cur_idx = 0;
-       list_for_each_entry(lte, stream_list, lookup_table_list) {
-               if (cur_idx == ARRAY_LEN(table_buf)) {
-                       bytes_to_write = sizeof(table_buf);
-                       if (full_write(out_fd, table_buf,
-                                      bytes_to_write) != bytes_to_write)
-                               goto write_error;
-                       table_size += bytes_to_write;
-                       cur_idx = 0;
-               }
-               write_lookup_table_entry(lte, &table_buf[cur_idx]);
-               cur_idx++;
-       }
-       if (cur_idx != 0) {
-               bytes_to_write = cur_idx * sizeof(struct wim_lookup_table_entry_disk);
-               if (full_write(out_fd, table_buf,
-                              bytes_to_write) != bytes_to_write)
-                       goto write_error;
-               table_size += bytes_to_write;
+       list_for_each_entry(lte, stream_list, lookup_table_list)
+               table_size += sizeof(struct wim_lookup_table_entry_disk);
+
+       DEBUG("Writing WIM lookup table (size=%zu, offset=%"PRIu64")",
+             table_size, out_fd->offset);
+
+       table_buf = MALLOC(table_size);
+       if (!table_buf) {
+               ERROR("Failed to allocate %zu bytes for temporary lookup table",
+                     table_size);
+               return WIMLIB_ERR_NOMEM;
        }
-       out_res_entry->offset        = start_offset;
-       out_res_entry->size          = table_size;
-       out_res_entry->original_size = table_size;
-       out_res_entry->flags         = WIM_RESHDR_FLAG_METADATA;
-       ret = 0;
-out:
+       table_buf_ptr = table_buf;
+       list_for_each_entry(lte, stream_list, lookup_table_list)
+               write_wim_lookup_table_entry(lte, table_buf_ptr++);
+
+       /* Write the lookup table uncompressed.  Although wimlib can handle a
+        * compressed lookup table, MS software cannot.  */
+       ret = write_wim_resource_from_buffer(table_buf,
+                                            table_size,
+                                            WIM_RESHDR_FLAG_METADATA,
+                                            out_fd,
+                                            WIMLIB_COMPRESSION_TYPE_NONE,
+                                            out_res_entry,
+                                            NULL,
+                                            write_resource_flags);
+       FREE(table_buf);
        return ret;
-write_error:
-       ERROR_WITH_ERRNO("Failed to write lookup table");
-       ret = WIMLIB_ERR_WRITE;
-       goto out;
 }
 
 static int
@@ -644,38 +630,65 @@ append_lookup_table_entry(struct wim_lookup_table_entry *lte, void *_list)
        return 0;
 }
 
-/* Writes the WIM lookup table to the output file. */
 int
-write_lookup_table(WIMStruct *wim, int image, struct resource_entry *out_res_entry)
+write_wim_lookup_table(WIMStruct *wim, int image, int write_flags,
+                      struct resource_entry *out_res_entry,
+                      struct list_head *stream_list_override)
 {
-       LIST_HEAD(stream_list);
-       int start_image;
-       int end_image;
+       int write_resource_flags;
+       struct list_head _stream_list;
+       struct list_head *stream_list;
 
-       if (image == WIMLIB_ALL_IMAGES) {
-               start_image = 1;
-               end_image = wim->hdr.image_count;
+       if (stream_list_override) {
+               stream_list = stream_list_override;
        } else {
-               start_image = image;
-               end_image = image;
+               stream_list = &_stream_list;
+               INIT_LIST_HEAD(stream_list);
        }
 
-       for (int i = start_image; i <= end_image; i++) {
-               struct wim_lookup_table_entry *metadata_lte;
+       if (!(write_flags & WIMLIB_WRITE_FLAG_NO_METADATA)) {
+               int start_image;
+               int end_image;
 
-               metadata_lte = wim->image_metadata[i - 1]->metadata_lte;
-               metadata_lte->out_refcnt = 1;
-               metadata_lte->output_resource_entry.flags |= WIM_RESHDR_FLAG_METADATA;
-               append_lookup_table_entry(metadata_lte, &stream_list);
+               if (image == WIMLIB_ALL_IMAGES) {
+                       start_image = 1;
+                       end_image = wim->hdr.image_count;
+               } else {
+                       start_image = image;
+                       end_image = image;
+               }
+
+               /* Push metadata resource lookup table entries onto the front of
+                * the list in reverse order, so that they're written in order.
+                */
+               for (int i = end_image; i >= start_image; i--) {
+                       struct wim_lookup_table_entry *metadata_lte;
+
+                       metadata_lte = wim->image_metadata[i - 1]->metadata_lte;
+                       metadata_lte->out_refcnt = 1;
+                       metadata_lte->part_number = wim->hdr.part_number;
+                       metadata_lte->output_resource_entry.flags |= WIM_RESHDR_FLAG_METADATA;
+
+                       list_add(&metadata_lte->lookup_table_list, stream_list);
+               }
        }
-       for_lookup_table_entry(wim->lookup_table,
-                              append_lookup_table_entry,
-                              &stream_list);
-       return write_lookup_table_from_stream_list(&stream_list,
-                                                  wim->out_fd,
-                                                  out_res_entry);
+
+       /* Append additional lookup table entries that have out_refcnt != 0.  */
+       if (!stream_list_override) {
+               for_lookup_table_entry(wim->lookup_table,
+                                      append_lookup_table_entry, stream_list);
+       }
+
+       write_resource_flags = 0;
+       if (write_flags & WIMLIB_WRITE_FLAG_PIPABLE)
+               write_resource_flags |= WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE;
+       return write_wim_lookup_table_from_stream_list(stream_list,
+                                                      &wim->out_fd,
+                                                      out_res_entry,
+                                                      write_resource_flags);
 }
 
+
 int
 lte_zero_real_refcnt(struct wim_lookup_table_entry *lte, void *_ignore)
 {
@@ -747,11 +760,9 @@ print_lookup_table_entry(const struct wim_lookup_table_entry *lte, FILE *out)
                }
                break;
 #ifdef __WIN32__
-       case RESOURCE_WIN32:
        case RESOURCE_WIN32_ENCRYPTED:
-#else
-       case RESOURCE_IN_FILE_ON_DISK:
 #endif
+       case RESOURCE_IN_FILE_ON_DISK:
                tfprintf(out, T("File on Disk      = `%"TS"'\n"),
                         lte->file_on_disk);
                break;
@@ -798,6 +809,7 @@ do_iterate_lte(struct wim_lookup_table_entry *lte, void *_ctx)
        return (*ctx->cb)(&entry, ctx->user_ctx);
 }
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_iterate_lookup_table(WIMStruct *wim, int flags,
                            wimlib_iterate_lookup_table_callback_t cb,
@@ -826,9 +838,7 @@ do_print_lookup_table_entry(struct wim_lookup_table_entry *lte, void *fp)
        return 0;
 }
 
-/*
- * Deprecated
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI void
 wimlib_print_lookup_table(WIMStruct *wim)
 {
@@ -897,7 +907,7 @@ lookup_resource(WIMStruct *wim,
        inode = dentry->d_inode;
 
        if (!inode->i_resolved)
-               if (inode_resolve_ltes(inode, wim->lookup_table))
+               if (inode_resolve_ltes(inode, wim->lookup_table, false))
                        return -EIO;
 
        if (!(lookup_flags & LOOKUP_FLAG_DIRECTORY_OK)
@@ -931,17 +941,23 @@ out:
 }
 #endif
 
-/* Resolve an inode's lookup table entries
+/*
+ * Resolve an inode's lookup table entries.
  *
  * This replaces the SHA1 hash fields (which are used to lookup an entry in the
- * lookup table) with pointers directly to the lookup table entries.  A circular
- * linked list of streams sharing the same lookup table entry is created.
+ * lookup table) with pointers directly to the lookup table entries.
  *
- * This function always succeeds; unresolved lookup table entries are given a
- * NULL pointer.
+ * If @force is %false:
+ *     If any needed SHA1 message digests are not found in the lookup table,
+ *     WIMLIB_ERR_RESOURCE_NOT_FOUND is returned and the inode is left
+ *     unmodified.
+ * If @force is %true:
+ *     If any needed SHA1 message digests are not found in the lookup table,
+ *     new entries are allocated and inserted into the lookup table.
  */
 int
-inode_resolve_ltes(struct wim_inode *inode, struct wim_lookup_table *table)
+inode_resolve_ltes(struct wim_inode *inode, struct wim_lookup_table *table,
+                  bool force)
 {
        const u8 *hash;
 
@@ -953,8 +969,17 @@ inode_resolve_ltes(struct wim_inode *inode, struct wim_lookup_table *table)
                hash = inode->i_hash;
                if (!is_zero_hash(hash)) {
                        lte = __lookup_resource(table, hash);
-                       if (unlikely(!lte))
-                               goto resource_not_found;
+                       if (!lte) {
+                               if (force) {
+                                       lte = new_lookup_table_entry();
+                                       if (!lte)
+                                               return WIMLIB_ERR_NOMEM;
+                                       copy_hash(lte->hash, hash);
+                                       lookup_table_insert(table, lte);
+                               } else {
+                                       goto resource_not_found;
+                               }
+                       }
                }
 
                /* Resolve the alternate data streams */
@@ -967,8 +992,17 @@ inode_resolve_ltes(struct wim_inode *inode, struct wim_lookup_table *table)
                        hash = cur_entry->hash;
                        if (!is_zero_hash(hash)) {
                                ads_lte = __lookup_resource(table, hash);
-                               if (unlikely(!ads_lte))
-                                       goto resource_not_found;
+                               if (!ads_lte) {
+                                       if (force) {
+                                               ads_lte = new_lookup_table_entry();
+                                               if (!ads_lte)
+                                                       return WIMLIB_ERR_NOMEM;
+                                               copy_hash(ads_lte->hash, hash);
+                                               lookup_table_insert(table, ads_lte);
+                                       } else {
+                                               goto resource_not_found;
+                                       }
+                               }
                        }
                        ads_ltes[i] = ads_lte;
                }
index 61c484318179a383b2f5f1af4dc9bc58b10d67cd..c876a182dd677be651cffd09b5d96937e283a59e 100644 (file)
@@ -646,7 +646,7 @@ static const struct lz_params lzx_lz_params = {
        .too_far        = 4096,
 };
 
-/* Documented in wimlib.h */
+/* API function documented in wimlib.h  */
 WIMLIBAPI unsigned
 wimlib_lzx_compress(const void *_uncompressed_data, unsigned uncompressed_len,
                    void *compressed_data)
index 1d0d04dfaf9b103b5ea356480f06c0e434aa59ac..99502d47edf451a2a3c295593d484bbe61bb98c7 100644 (file)
@@ -336,7 +336,7 @@ lzx_read_block_header(struct input_bitstream *istream,
 
        ret = bitstream_ensure_bits(istream, 4);
        if (ret) {
-               ERROR("LZX input stream overrun");
+               DEBUG("LZX input stream overrun");
                return ret;
        }
 
@@ -379,7 +379,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                                                tables->alignedtree_lens,
                                                8);
                if (ret) {
-                       ERROR("lzx_decompress(): Failed to make the decode "
+                       DEBUG("lzx_decompress(): Failed to make the decode "
                              "table for the aligned offset tree");
                        return ret;
                }
@@ -397,7 +397,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                ret = lzx_read_code_lens(istream, tables->maintree_lens,
                                         LZX_NUM_CHARS);
                if (ret) {
-                       ERROR("lzx_decompress(): Failed to read the code "
+                       DEBUG("lzx_decompress(): Failed to read the code "
                              "lengths for the first 256 elements of the "
                              "main tree");
                        return ret;
@@ -412,7 +412,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                                         tables->maintree_lens + LZX_NUM_CHARS,
                                         LZX_MAINTREE_NUM_SYMBOLS - LZX_NUM_CHARS);
                if (ret) {
-                       ERROR("lzx_decompress(): Failed to read the path "
+                       DEBUG("lzx_decompress(): Failed to read the path "
                              "lengths for the remaining elements of the main "
                              "tree");
                        return ret;
@@ -427,7 +427,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                                                tables->maintree_lens,
                                                LZX_MAX_CODEWORD_LEN);
                if (ret) {
-                       ERROR("lzx_decompress(): Failed to make the decode "
+                       DEBUG("lzx_decompress(): Failed to make the decode "
                              "table for the main tree");
                        return ret;
                }
@@ -436,7 +436,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                ret = lzx_read_code_lens(istream, tables->lentree_lens,
                                         LZX_LENTREE_NUM_SYMBOLS);
                if (ret) {
-                       ERROR("lzx_decompress(): Failed to read the path "
+                       DEBUG("lzx_decompress(): Failed to read the path "
                              "lengths for the length tree");
                        return ret;
                }
@@ -448,7 +448,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                                                tables->lentree_lens,
                                                LZX_MAX_CODEWORD_LEN);
                if (ret) {
-                       ERROR("lzx_decompress(): Failed to build the length "
+                       DEBUG("lzx_decompress(): Failed to build the length "
                              "Huffman tree");
                        return ret;
                }
@@ -465,7 +465,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                 * the next 16 bits. */
                if (istream->bitsleft == 0) {
                        if (istream->data_bytes_left < 14) {
-                               ERROR("lzx_decompress(): Insufficient length in "
+                               DEBUG("lzx_decompress(): Insufficient length in "
                                      "uncompressed block");
                                return -1;
                        }
@@ -473,7 +473,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                        istream->data_bytes_left -= 2;
                } else {
                        if (istream->data_bytes_left < 12) {
-                               ERROR("lzx_decompress(): Insufficient length in "
+                               DEBUG("lzx_decompress(): Insufficient length in "
                                      "uncompressed block");
                                return -1;
                        }
@@ -489,7 +489,7 @@ lzx_read_block_header(struct input_bitstream *istream,
                 * be read in lzx_decompress(). */
                break;
        default:
-               ERROR("lzx_decompress(): Found invalid block");
+               DEBUG("lzx_decompress(): Found invalid block");
                return -1;
        }
        *block_type_ret = block_type;
@@ -659,13 +659,13 @@ lzx_decode_match(unsigned main_element, int block_type,
        match_src = match_dest - match_offset;
 
        if (match_len > bytes_remaining) {
-               ERROR("lzx_decode_match(): Match of length %u bytes overflows "
+               DEBUG("lzx_decode_match(): Match of length %u bytes overflows "
                      "uncompressed block size", match_len);
                return -1;
        }
 
        if (match_src < window) {
-               ERROR("lzx_decode_match(): Match of length %u bytes references "
+               DEBUG("lzx_decode_match(): Match of length %u bytes references "
                      "data before window (match_offset = %u, window_pos = %u)",
                      match_len, match_offset, window_pos);
                return -1;
@@ -801,7 +801,7 @@ lzx_decompress_block(int block_type, unsigned block_size,
        return 0;
 }
 
-/* Documented in wimlib.h */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_lzx_decompress(const void *compressed_data, unsigned compressed_len,
                      void *uncompressed_data, unsigned uncompressed_len)
@@ -851,7 +851,7 @@ wimlib_lzx_decompress(const void *compressed_data, unsigned compressed_len,
                          block_size, window_pos);
 
                if (block_size > uncompressed_len - window_pos) {
-                       ERROR("lzx_decompress(): Expected a block size of at "
+                       DEBUG("lzx_decompress(): Expected a block size of at "
                              "most %u bytes (found %u bytes)",
                              uncompressed_len - window_pos, block_size);
                        return -1;
@@ -879,7 +879,7 @@ wimlib_lzx_decompress(const void *compressed_data, unsigned compressed_len,
                case LZX_BLOCKTYPE_UNCOMPRESSED:
                        LZX_DEBUG("LZX_BLOCKTYPE_UNCOMPRESSED");
                        if (istream.data_bytes_left < block_size) {
-                               ERROR("Unexpected end of input when "
+                               DEBUG("Unexpected end of input when "
                                      "reading %u bytes from LZX bitstream "
                                      "(only have %u bytes left)",
                                      block_size, istream.data_bytes_left);
index 37206d6743f3d77c20edcdb8fef6acab51389810..e4eb78ab6417e8613b6bd26a98ed9a014f628df3 100644 (file)
  * end-of-directory is signaled by a directory entry of length '0', really of
  * length 8, because that's how long the 'length' field is.
  *
- * @wim:               Pointer to the WIMStruct for the WIM file.
+ * @wim:
+ *     Pointer to the WIMStruct for the WIM file.
  *
- * @imd:       Pointer to the image metadata structure for the image whose
- *             metadata resource we are reading.  Its `metadata_lte' member
- *             specifies the lookup table entry for the metadata resource.  The
- *             rest of the image metadata entry will be filled in by this
- *             function.
+ * @imd:
+ *     Pointer to the image metadata structure for the image whose metadata
+ *     resource we are reading.  Its `metadata_lte' member specifies the lookup
+ *     table entry for the metadata resource.  The rest of the image metadata
+ *     entry will be filled in by this function.
  *
- * Returns:    Zero on success, nonzero on failure.
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_METADATA_RESOURCE
+ *     WIMLIB_ERR_NOMEM
  */
 int
 read_metadata_resource(WIMStruct *wim, struct wim_image_metadata *imd)
@@ -79,50 +83,26 @@ read_metadata_resource(WIMStruct *wim, struct wim_image_metadata *imd)
        if (metadata_len < 8 + WIM_DENTRY_DISK_SIZE) {
                ERROR("Expected at least %u bytes for the metadata resource",
                      8 + WIM_DENTRY_DISK_SIZE);
-               return WIMLIB_ERR_INVALID_RESOURCE_SIZE;
-       }
-
-       if (sizeof(size_t) < 8 && metadata_len > 0xffffffff) {
-               ERROR("Metadata resource is too large (%"PRIu64" bytes",
-                     metadata_len);
-               return WIMLIB_ERR_INVALID_RESOURCE_SIZE;
-       }
-
-       /* Allocate memory for the uncompressed metadata resource. */
-       buf = MALLOC(metadata_len);
-
-       if (!buf) {
-               ERROR("Failed to allocate %"PRIu64" bytes for uncompressed "
-                     "metadata resource", metadata_len);
-               return WIMLIB_ERR_NOMEM;
+               return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
        }
 
        /* Read the metadata resource into memory.  (It may be compressed.) */
-       ret = read_full_resource_into_buf(metadata_lte, buf);
+       ret = read_full_resource_into_alloc_buf(metadata_lte, (void**)&buf);
        if (ret)
-               goto out_free_buf;
-
-       sha1_buffer(buf, metadata_len, hash);
-       if (!hashes_equal(metadata_lte->hash, hash)) {
-               ERROR("Metadata resource is corrupted (invalid SHA-1 message digest)!");
-               ret = WIMLIB_ERR_INVALID_RESOURCE_HASH;
-               goto out_free_buf;
+               return ret;
+
+       if (!metadata_lte->dont_check_metadata_hash) {
+               sha1_buffer(buf, metadata_len, hash);
+               if (!hashes_equal(metadata_lte->hash, hash)) {
+                       ERROR("Metadata resource is corrupted "
+                             "(invalid SHA-1 message digest)!");
+                       ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+                       goto out_free_buf;
+               }
        }
 
        DEBUG("Finished reading metadata resource into memory.");
 
-       /* The root directory entry starts after security data, aligned on an
-        * 8-byte boundary within the metadata resource.
-        *
-        * The security data starts with a 4-byte integer giving its total
-        * length, so if we round that up to an 8-byte boundary that gives us
-        * the offset of the root dentry.
-        *
-        * Here we read the security data into a wim_security_data structure,
-        * which takes case of rouding total_length.  If successful, go ahead
-        * and calculate the offset in the metadata resource of the root dentry.
-        * */
-
        ret = read_wim_security_data(buf, metadata_len, &security_data);
        if (ret)
                goto out_free_buf;
@@ -136,6 +116,11 @@ read_metadata_resource(WIMStruct *wim, struct wim_image_metadata *imd)
                goto out_free_security_data;
        }
 
+       /* The root directory entry starts after security data, aligned on an
+        * 8-byte boundary within the metadata resource.  Since
+        * security_data->total_length was already rounded up to an 8-byte
+        * boundary, its value can be used as the offset of the root directory
+        * entry.  */
        ret = read_dentry(buf, metadata_len,
                          security_data->total_length, root);
 
@@ -162,6 +147,13 @@ read_metadata_resource(WIMStruct *wim, struct wim_image_metadata *imd)
                root->short_name_nbytes = 0;
        }
 
+       if (!dentry_is_directory(root)) {
+               ERROR("Root of the WIM image must be a directory!");
+               FREE(root);
+               ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
+               goto out_free_security_data;
+       }
+
        /* This is the root dentry, so set its parent to itself. */
        root->parent = root;
 
@@ -210,57 +202,32 @@ recalculate_security_data_length(struct wim_security_data *sd)
        sd->total_length = (total_length + 7) & ~7;
 }
 
-/* Like write_wim_resource(), but the resource is specified by a buffer of
- * uncompressed data rather a lookup table entry; also writes the SHA1 hash of
- * the buffer to @hash.  */
 static int
-write_wim_resource_from_buffer(const void *buf, size_t buf_size,
-                              int out_fd, int out_ctype,
-                              struct resource_entry *out_res_entry,
-                              u8 hash[SHA1_HASH_SIZE])
-{
-       /* Set up a temporary lookup table entry to provide to
-        * write_wim_resource(). */
-       struct wim_lookup_table_entry lte;
-       int ret;
-       lte.resource_location            = RESOURCE_IN_ATTACHED_BUFFER;
-       lte.attached_buffer              = (void*)buf;
-       lte.resource_entry.original_size = buf_size;
-       lte.resource_entry.flags         = 0;
-       lte.unhashed                     = 1;
-       ret = write_wim_resource(&lte, out_fd, out_ctype, out_res_entry, 0);
-       if (ret == 0)
-               copy_hash(hash, lte.hash);
-       return ret;
-}
-
-/* Write the metadata resource for the current WIM image. */
-int
-write_metadata_resource(WIMStruct *wim)
+prepare_metadata_resource(WIMStruct *wim, int image,
+                         u8 **buf_ret, size_t *len_ret)
 {
        u8 *buf;
        u8 *p;
        int ret;
        u64 subdir_offset;
        struct wim_dentry *root;
-       struct wim_lookup_table_entry *lte;
-       u64 metadata_original_size;
+       u64 len;
        struct wim_security_data *sd;
        struct wim_image_metadata *imd;
 
-       wimlib_assert(wim->out_fd != -1);
-       wimlib_assert(wim->current_image != WIMLIB_NO_IMAGE);
+       DEBUG("Preparing metadata resource for image %d", image);
 
-       DEBUG("Writing metadata resource for image %d (offset = %"PRIu64")",
-             wim->current_image, filedes_offset(wim->out_fd));
+       ret = select_wim_image(wim, image);
+       if (ret)
+               return ret;
 
-       imd = wim->image_metadata[wim->current_image - 1];
+       imd = wim->image_metadata[image - 1];
 
        root = imd->root_dentry;
        sd = imd->security_data;
 
        if (!root) {
-               /* Empty image; create a dummy root. */
+               /* Empty image; create a dummy root.  */
                ret = new_filler_directory(T(""), &root);
                if (ret)
                        return ret;
@@ -278,49 +245,62 @@ write_metadata_resource(WIMStruct *wim)
        subdir_offset = (((u64)sd->total_length + 7) & ~7) +
                        dentry_correct_total_length(root) + 8;
 
-       /* Calculate the subdirectory offsets for the entire dentry tree. */
+       /* Calculate the subdirectory offsets for the entire dentry tree.  */
        calculate_subdir_offsets(root, &subdir_offset);
 
-       /* Total length of the metadata resource (uncompressed) */
-       metadata_original_size = subdir_offset;
+       /* Total length of the metadata resource (uncompressed) */
+       len = subdir_offset;
 
-       /* Allocate a buffer to contain the uncompressed metadata resource */
-       buf = MALLOC(metadata_original_size);
+       /* Allocate a buffer to contain the uncompressed metadata resource */
+       buf = MALLOC(len);
        if (!buf) {
                ERROR("Failed to allocate %"PRIu64" bytes for "
-                     "metadata resource", metadata_original_size);
+                     "metadata resource", len);
                return WIMLIB_ERR_NOMEM;
        }
 
-       /* Write the security data into the resource buffer */
+       /* Write the security data into the resource buffer */
        p = write_wim_security_data(sd, buf);
 
-       /* Write the dentry tree into the resource buffer */
+       /* Write the dentry tree into the resource buffer */
        p = write_dentry_tree(root, p);
 
        /* We MUST have exactly filled the buffer; otherwise we calculated its
-        * size incorrectly or wrote the data incorrectly. */
-       wimlib_assert(p - buf == metadata_original_size);
+        * size incorrectly or wrote the data incorrectly.  */
+       wimlib_assert(p - buf == len);
+
+       *buf_ret = buf;
+       *len_ret = len;
+       return 0;
+}
+
+int
+write_metadata_resource(WIMStruct *wim, int image, int write_resource_flags)
+{
+       int ret;
+       u8 *buf;
+       size_t len;
+       struct wim_image_metadata *imd;
 
-       /* Get the lookup table entry for the metadata resource so we can update
-        * it. */
-       lte = wim_get_current_image_metadata(wim)->metadata_lte;
+       ret = prepare_metadata_resource(wim, image, &buf, &len);
+       if (ret)
+               return ret;
+
+       imd = wim->image_metadata[image - 1];
 
        /* Write the metadata resource to the output WIM using the proper
-        * compression type.  The lookup table entry for the metadata resource
-        * is updated. */
-       ret = write_wim_resource_from_buffer(buf, metadata_original_size,
-                                            wim->out_fd,
+        * compression type, in the process updating the lookup table entry for
+        * the metadata resource.  */
+       ret = write_wim_resource_from_buffer(buf, len, WIM_RESHDR_FLAG_METADATA,
+                                            &wim->out_fd,
                                             wim->compression_type,
-                                            &lte->output_resource_entry,
-                                            lte->hash);
-       /* Note that although the SHA1 message digest of the metadata resource
-        * is very likely to have changed, the corresponding lookup table entry
-        * is not actually located in the hash table, so it need not be
-        * re-inserted in the hash table. */
-
-       /* All the data has been written to the new WIM; no need for the buffer
-        * anymore */
+                                            &imd->metadata_lte->output_resource_entry,
+                                            imd->metadata_lte->hash,
+                                            write_resource_flags);
+
+       /* Original checksum was overridden; set a flag so it isn't used.  */
+       imd->metadata_lte->dont_check_metadata_hash = 1;
+
        FREE(buf);
        return ret;
 }
index f1baebba1338516e788d5edc00b539aa1ffa4723..2f9f440efb86eab662780cca5d16fb2b39e9d582 100644 (file)
@@ -80,7 +80,7 @@
 struct wimfs_fd {
        struct wim_inode *f_inode;
        struct wim_lookup_table_entry *f_lte;
-       int staging_fd;
+       struct filedes staging_fd;
        u16 idx;
        u32 stream_id;
 };
@@ -229,13 +229,13 @@ alloc_wimfs_fd(struct wim_inode *inode,
                                ret = -ENOMEM;
                                break;
                        }
-                       fd->f_inode    = inode;
-                       fd->f_lte      = lte;
-                       fd->staging_fd = -1;
-                       fd->idx        = i;
-                       fd->stream_id  = stream_id;
-                       *fd_ret        = fd;
-                       inode->i_fds[i]  = fd;
+                       fd->f_inode     = inode;
+                       fd->f_lte       = lte;
+                       filedes_invalidate(&fd->staging_fd);
+                       fd->idx         = i;
+                       fd->stream_id   = stream_id;
+                       *fd_ret         = fd;
+                       inode->i_fds[i] = fd;
                        inode->i_num_opened_fds++;
                        if (lte && !readonly)
                                lte->num_opened_fds++;
@@ -279,9 +279,9 @@ lte_put_fd(struct wim_lookup_table_entry *lte, struct wimfs_fd *fd)
        /* Close staging file descriptor if needed. */
 
        if (lte->resource_location == RESOURCE_IN_STAGING_FILE
-            && fd->staging_fd != -1)
+            && filedes_valid(&fd->staging_fd))
        {
-               if (close(fd->staging_fd) != 0) {
+               if (filedes_close(&fd->staging_fd)) {
                        ERROR_WITH_ERRNO("Failed to close staging file");
                        return -errno;
                }
@@ -567,8 +567,10 @@ extract_resource_to_staging_dir(struct wim_inode *inode,
 
        /* Extract the stream to the staging file (possibly truncated) */
        if (old_lte) {
+               struct filedes wimlib_fd = {.fd = fd};
                extract_size = min(wim_resource_size(old_lte), size);
-               ret = extract_wim_resource_to_fd(old_lte, fd, extract_size);
+               ret = extract_wim_resource_to_fd(old_lte, &wimlib_fd,
+                                                extract_size);
        } else {
                ret = 0;
                extract_size = 0;
@@ -637,15 +639,18 @@ extract_resource_to_staging_dir(struct wim_inode *inode,
                        struct wimfs_fd *fd = inode->i_fds[i];
                        if (fd) {
                                if (fd->stream_id == stream_id) {
+                                       int raw_fd;
+
                                        wimlib_assert(fd->f_lte == old_lte);
-                                       wimlib_assert(fd->staging_fd == -1);
+                                       wimlib_assert(!filedes_valid(&fd->staging_fd));
                                        fd->f_lte = new_lte;
                                        new_lte->num_opened_fds++;
-                                       fd->staging_fd = open(staging_file_name, O_RDONLY);
-                                       if (fd->staging_fd == -1) {
+                                       raw_fd = open(staging_file_name, O_RDONLY);
+                                       if (raw_fd < 0) {
                                                ret = -errno;
                                                goto out_revert_fd_changes;
                                        }
+                                       filedes_init(&fd->staging_fd, raw_fd);
                                }
                                j++;
                        }
@@ -673,9 +678,9 @@ out_revert_fd_changes:
                struct wimfs_fd *fd = inode->i_fds[i];
                if (fd && fd->stream_id == stream_id && fd->f_lte == new_lte) {
                        fd->f_lte = old_lte;
-                       if (fd->staging_fd != -1) {
-                               close(fd->staging_fd);
-                               fd->staging_fd = -1;
+                       if (filedes_valid(&fd->staging_fd)) {
+                               filedes_close(&fd->staging_fd);
+                               filedes_invalidate(&fd->staging_fd);
                        }
                        j++;
                }
@@ -1597,7 +1602,7 @@ static int
 wimfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi)
 {
        struct wimfs_fd *fd = (struct wimfs_fd*)(uintptr_t)fi->fh;
-       int ret = ftruncate(fd->staging_fd, size);
+       int ret = ftruncate(fd->staging_fd.fd, size);
        if (ret)
                return -errno;
        touch_inode(fd->f_inode);
@@ -1663,7 +1668,7 @@ wimfs_getxattr(const char *path, const char *name, char *value,
 
        ret = read_full_resource_into_buf(lte, value);
        if (ret)
-               return -EIO;
+               return -errno;
 
        return res_size;
 }
@@ -1858,12 +1863,15 @@ wimfs_open(const char *path, struct fuse_file_info *fi)
                return ret;
 
        if (lte && lte->resource_location == RESOURCE_IN_STAGING_FILE) {
-               fd->staging_fd = open(lte->staging_file_name, fi->flags);
-               if (fd->staging_fd == -1) {
+               int raw_fd;
+
+               raw_fd = open(lte->staging_file_name, fi->flags);
+               if (raw_fd < 0) {
                        int errno_save = errno;
                        close_wimfs_fd(fd);
                        return -errno_save;
                }
+               filedes_init(&fd->staging_fd, raw_fd);
        }
        fi->fh = (uintptr_t)fd;
        return 0;
@@ -1921,8 +1929,8 @@ wimfs_read(const char *path, char *buf, size_t size,
 
        switch (fd->f_lte->resource_location) {
        case RESOURCE_IN_STAGING_FILE:
-               ret = full_pread(fd->staging_fd, buf, size, offset);
-               if (ret != size)
+               ret = raw_pread(&fd->staging_fd, buf, size, offset);
+               if (ret == -1)
                        ret = -errno;
                break;
        case RESOURCE_IN_WIM:
@@ -2009,7 +2017,7 @@ wimfs_readlink(const char *path, char *buf, size_t buf_len)
                return -EINVAL;
        if (buf_len == 0)
                return -ENAMETOOLONG;
-       ret = wim_inode_readlink(inode, buf, buf_len - 1);
+       ret = wim_inode_readlink(inode, buf, buf_len - 1, NULL);
        if (ret >= 0) {
                wimlib_assert(ret <= buf_len - 1);
                buf[ret] = '\0';
@@ -2300,12 +2308,12 @@ wimfs_write(const char *path, const char *buf, size_t size,
 
        wimlib_assert(fd->f_lte != NULL);
        wimlib_assert(fd->f_lte->staging_file_name != NULL);
-       wimlib_assert(fd->staging_fd != -1);
+       wimlib_assert(filedes_valid(&fd->staging_fd));
        wimlib_assert(fd->f_inode != NULL);
 
        /* Write the data. */
-       ret = full_pwrite(fd->staging_fd, buf, size, offset);
-       if (ret != size)
+       ret = raw_pwrite(&fd->staging_fd, buf, size, offset);
+       if (ret == -1)
                return -errno;
 
        /* Update file size */
@@ -2375,7 +2383,7 @@ static struct fuse_operations wimfs_operations = {
 };
 
 
-/* Mounts an image from a WIM file. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_mount_image(WIMStruct *wim, int image, const char *dir,
                   int mount_flags, WIMStruct **additional_swms,
@@ -2434,7 +2442,7 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir,
        }
 
        if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) {
-               ret = lock_wim(wim, wim->in_fd);
+               ret = lock_wim(wim, wim->in_fd.fd);
                if (ret)
                        goto out_restore_lookup_table;
        }
@@ -2567,10 +2575,7 @@ out:
        return ret;
 }
 
-/*
- * Unmounts the WIM file that was previously mounted on @dir by using
- * wimlib_mount_image().
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_unmount_image(const char *dir, int unmount_flags,
                     wimlib_progress_func_t progress_func)
index a8292c7e2432c90da4b8d6c919e1a50fbf9c6dfa..39509fc1d5157f5ae338237d6864596c28b33177 100644 (file)
 #include <time.h> /* NTFS-3g headers are missing <time.h> include */
 
 #include <ntfs-3g/attrib.h>
-#include <ntfs-3g/endians.h>
 #include <ntfs-3g/reparse.h>
 #include <ntfs-3g/security.h>
-#include <ntfs-3g/types.h>
-#include <ntfs-3g/xattrs.h>
+#include <errno.h>
 
 #include "wimlib/apply.h"
-#include "wimlib/compiler.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/reparse.h"
-#include "wimlib/security.h"
+#include "wimlib/paths.h"
+#include "wimlib/resource.h"
 
-struct ntfs_attr_extract_ctx {
-       u64 offset;
-       ntfs_attr *na;
-};
+static ntfs_volume *
+ntfs_3g_apply_ctx_get_volume(struct apply_ctx *ctx)
+{
+       return (ntfs_volume*)ctx->private[0];
+}
 
-static int
-extract_wim_chunk_to_ntfs_attr(const void *buf, size_t len, void *_ctx)
+static void
+ntfs_3g_apply_ctx_set_volume(struct apply_ctx *ctx, ntfs_volume *vol)
 {
-       struct ntfs_attr_extract_ctx *ctx = _ctx;
-       if (ntfs_attr_pwrite(ctx->na, ctx->offset, len, buf) == len) {
-               ctx->offset += len;
-               return 0;
-       } else {
-               ERROR_WITH_ERRNO("Error extracting WIM resource to NTFS attribute");
-               return WIMLIB_ERR_WRITE;
-       }
+       ctx->private[0] = (intptr_t)vol;
 }
 
-/*
- * Extracts a WIM resource to a NTFS attribute.
- */
-static int
-extract_wim_resource_to_ntfs_attr(const struct wim_lookup_table_entry *lte,
-                                 ntfs_attr *na)
+static ntfs_inode *
+ntfs_3g_apply_pathname_to_inode(const char *path, struct apply_ctx *ctx)
 {
-       struct ntfs_attr_extract_ctx ctx;
-       ctx.na = na;
-       ctx.offset = 0;
-       return extract_wim_resource(lte, wim_resource_size(lte),
-                                   extract_wim_chunk_to_ntfs_attr, &ctx);
+       ntfs_volume *vol = ntfs_3g_apply_ctx_get_volume(ctx);
+       return ntfs_pathname_to_inode(vol, NULL, path);
 }
 
-/* Writes the data streams of a WIM inode to the data attributes of a NTFS
- * inode.
- *
- * @ni:             The NTFS inode to which the streams are to be extracted.
- *
- * @dentry:  The WIM dentry being extracted.  The @d_inode member points to the
- *          corresponding WIM inode that contains the streams being extracted.
- *          The WIM dentry itself is only needed to provide a file path for
- *          better error messages.
- *
- * @progress_info:  Progress information for the image application.  The number
- *                 of extracted bytes will be incremented by the uncompressed
- *                 size of each stream extracted.
- *
- * Returns 0 on success, nonzero on failure.
- */
+struct ntfs_attr_extract_ctx {
+       u64 offset;
+       ntfs_attr *na;
+};
+
 static int
-write_ntfs_data_streams(ntfs_inode *ni, struct wim_dentry *dentry,
-                       union wimlib_progress_info *progress_info)
+ntfs_3g_extract_wim_chunk(const void *buf, size_t len, void *_ctx)
 {
-       int ret = 0;
-       unsigned stream_idx = 0;
-       ntfschar *stream_name = AT_UNNAMED;
-       u32 stream_name_nbytes = 0;
-       const struct wim_inode *inode = dentry->d_inode;
-       struct wim_lookup_table_entry *lte;
-
-       lte = inode->i_lte;
-
-       /* For directories, skip unnamed streams; just extract alternate data
-        * streams. */
-       if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
-               goto cont;
-
-       DEBUG("Writing %u NTFS data stream%s for `%s'",
-             inode->i_num_ads + 1,
-             (inode->i_num_ads == 0 ? "" : "s"),
-             dentry->_full_path);
-
-       for (;;) {
-               if (stream_name_nbytes) {
-                       /* Skip special UNIX data entries (see documentation for
-                        * WIMLIB_ADD_FLAG_UNIX_DATA) */
-                       if (stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES
-                           && !memcmp(stream_name,
-                                      WIMLIB_UNIX_DATA_TAG_UTF16LE,
-                                      WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES))
-                               goto cont;
-
-                       /* Create an empty named stream. */
-                       ret = ntfs_attr_add(ni, AT_DATA, stream_name,
-                                           stream_name_nbytes / 2, NULL, 0);
-                       if (ret) {
-                               ERROR_WITH_ERRNO("Failed to create named data "
-                                                "stream for extracted file "
-                                                "`%s'",
-                                                dentry->_full_path);
-                               ret = WIMLIB_ERR_NTFS_3G;
-                               break;
-
-                       }
-               }
-
-               /* If there's no lookup table entry, it's an empty stream.
-                * Otherwise, open the attribute and extract the data. */
-               if (lte) {
-                       ntfs_attr *na;
-
-                       na = ntfs_attr_open(ni, AT_DATA, stream_name,
-                                           stream_name_nbytes / 2);
-                       if (!na) {
-                               ERROR_WITH_ERRNO("Failed to open a data stream of "
-                                                "extracted file `%s'",
-                                                dentry->_full_path);
-                               ret = WIMLIB_ERR_NTFS_3G;
-                               break;
-                       }
-
-                       /* The WIM lookup table entry provides the stream
-                        * length, so the NTFS attribute should be resized to
-                        * this length before starting to extract the data. */
-                       ret = ntfs_attr_truncate_solid(na, wim_resource_size(lte));
-                       if (ret) {
-                               ntfs_attr_close(na);
-                               break;
-                       }
-
-                       /* Actually extract the stream */
-                       ret = extract_wim_resource_to_ntfs_attr(lte, na);
-
-                       /* Close the attribute */
-                       ntfs_attr_close(na);
-                       if (ret)
-                               break;
-
-                       /* Record the number of bytes of uncompressed data that
-                        * have been extracted. */
-                       progress_info->extract.completed_bytes += wim_resource_size(lte);
-               }
-       cont:
-               if (stream_idx == inode->i_num_ads) /* Has the last stream been extracted? */
-                       break;
-
-               /* Get the name and lookup table entry for the next stream. */
-               stream_name = inode->i_ads_entries[stream_idx].stream_name;
-               stream_name_nbytes = inode->i_ads_entries[stream_idx].stream_name_nbytes;
-               lte = inode->i_ads_entries[stream_idx].lte;
-               stream_idx++;
-       }
-       return ret;
+       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;
+       return 0;
 }
 
-/* Open the NTFS inode that corresponds to the parent of a WIM dentry.  Returns
- * the opened inode, or NULL on failure. */
 static ntfs_inode *
-dentry_open_parent_ni(struct wim_dentry *dentry, ntfs_volume *vol)
+ntfs_3g_open_parent_inode(const char *path, ntfs_volume *vol)
 {
-       char *p;
-       const char *dir_name;
+       char orig, *p;
        ntfs_inode *dir_ni;
-       char orig;
 
-       p = dentry->_full_path + dentry->full_path_nbytes;
+       p = strchr(path, '\0');
        do {
                p--;
        } while (*p != '/');
 
        orig = *p;
        *p = '\0';
-       dir_name = dentry->_full_path;
-       dir_ni = ntfs_pathname_to_inode(vol, NULL, dir_name);
-       if (!dir_ni) {
-               ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
-                                dir_name);
-       }
+       dir_ni = ntfs_pathname_to_inode(vol, NULL, path);
        *p = orig;
        return dir_ni;
 }
 
-/*
- * Makes a NTFS hard link.
- *
- * The hard link is named @from_dentry->file_name and is located under the
- * directory specified by @dir_ni, and it is made to point to the previously
- * extracted file located at @inode->i_extracted_file.
- *
- * Or, in other words, this adds a new name @from_dentry->full_path to an
- * existing NTFS inode which already has a name @inode->i_extracted_file.
- *
- * The new name is made in the POSIX namespace (this is the behavior of
- * ntfs_link()).
- *
- * Return 0 on success, nonzero on failure.  dir_ni is closed either way.
- */
 static int
-apply_ntfs_hardlink(struct wim_dentry *from_dentry,
-                   const struct wim_inode *inode,
-                   ntfs_inode *dir_ni)
+ntfs_3g_create(const char *path, struct apply_ctx *ctx, mode_t mode)
 {
-       int ret;
-       ntfs_inode *to_ni;
        ntfs_volume *vol;
+       ntfs_inode *dir_ni, *ni;
+       const char *name;
+       utf16lechar *name_utf16le;
+       size_t name_utf16le_nbytes;
+       int ret;
 
-       vol = dir_ni->vol;
-       ret = ntfs_inode_close(dir_ni);
-       if (ret != 0) {
-               ERROR_WITH_ERRNO("Error closing directory");
-               return WIMLIB_ERR_NTFS_3G;
-       }
-
-       DEBUG("Extracting NTFS hard link `%s' => `%s'",
-             from_dentry->_full_path, inode->i_extracted_file);
+       vol = ntfs_3g_apply_ctx_get_volume(ctx);
 
-       to_ni = ntfs_pathname_to_inode(vol, NULL, inode->i_extracted_file);
-       if (!to_ni) {
-               ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
-                                inode->i_extracted_file);
-               return WIMLIB_ERR_NTFS_3G;
-       }
+       ret = WIMLIB_ERR_NTFS_3G;
+       dir_ni = ntfs_3g_open_parent_inode(path, vol);
+       if (!dir_ni)
+               goto out;
 
-       dir_ni = dentry_open_parent_ni(from_dentry, vol);
-       if (!dir_ni) {
-               ntfs_inode_close(to_ni);
-               return WIMLIB_ERR_NTFS_3G;
-       }
+       name = path_basename(path);
+       ret = tstr_to_utf16le(name, strlen(name),
+                             &name_utf16le, &name_utf16le_nbytes);
+       if (ret)
+               goto out_close_dir_ni;
 
-       ret = ntfs_link(to_ni, dir_ni,
-                       from_dentry->file_name,
-                       from_dentry->file_name_nbytes / 2);
-       ret |= ntfs_inode_close(dir_ni);
-       ret |= ntfs_inode_close(to_ni);
-       if (ret) {
-               ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
-                                from_dentry->_full_path,
-                                inode->i_extracted_file);
+       ret = 0;
+       ni = ntfs_create(dir_ni, 0, name_utf16le,
+                        name_utf16le_nbytes / 2, mode);
+       if (!ni || ntfs_inode_close_in_dir(ni, dir_ni))
                ret = WIMLIB_ERR_NTFS_3G;
-       }
+       FREE(name_utf16le);
+out_close_dir_ni:
+       if (ntfs_inode_close(dir_ni))
+               ret = WIMLIB_ERR_NTFS_3G;
+out:
        return ret;
 }
 
-/* Transfers file attributes and possibly a security descriptor from a WIM inode
- * to a NTFS inode.
- *
- * @ni:             The NTFS inode to apply the metadata to.
- * @dir_ni:  The NTFS inode for a directory containing @ni.
- * @dentry:  The WIM dentry whose inode contains the metadata to apply.
- * @wim:       The WIMStruct for the WIM, through which the table of security
- *             descriptors can be accessed.
- *
- * Returns 0 on success, nonzero on failure.
- */
 static int
-apply_file_attributes_and_security_data(ntfs_inode *ni,
-                                       ntfs_inode *dir_ni,
-                                       struct wim_dentry *dentry,
-                                       const WIMStruct *wim,
-                                       int extract_flags)
+ntfs_3g_create_file(const char *path, struct apply_ctx *ctx)
 {
+       return ntfs_3g_create(path, ctx, S_IFREG);
+}
+
+static int
+ntfs_3g_create_directory(const char *path, struct apply_ctx *ctx)
+{
+       return ntfs_3g_create(path, ctx, 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;
-       struct SECURITY_CONTEXT ctx;
-       le32 attributes;
-       const struct wim_inode *inode;
-
-       inode = dentry->d_inode;
-
-       DEBUG("Setting NTFS file attributes on `%s' to %#"PRIx32,
-             dentry->_full_path, inode->i_attributes);
-
-       attributes = cpu_to_le32(inode->i_attributes);
-       memset(&ctx, 0, sizeof(ctx));
-       ctx.vol = ni->vol;
-       ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ATTRIB,
-                                        ni, dir_ni,
-                                        (char*)&attributes,
-                                        sizeof(attributes), 0);
-       if (ret) {
-               ERROR("Failed to set NTFS file attributes on `%s'",
-                     dentry->_full_path);
+
+       vol = ntfs_3g_apply_ctx_get_volume(ctx);
+
+       ret = WIMLIB_ERR_NTFS_3G;
+       ni = ntfs_pathname_to_inode(vol, NULL, oldpath);
+       if (!ni)
+               goto out;
+
+       ret = WIMLIB_ERR_NTFS_3G;
+       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_NTFS_3G;
-       } else if (inode->i_security_id != -1 &&
-                  !(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS))
-       {
-               const char *desc;
-               const struct wim_security_data *sd;
-
-               sd = wim_const_security_data(wim);
-               wimlib_assert(inode->i_security_id < sd->num_entries);
-               desc = (const char *)sd->descriptors[inode->i_security_id];
-               DEBUG("Applying security descriptor %d to `%s'",
-                     inode->i_security_id, dentry->_full_path);
-
-               ret = ntfs_xattr_system_setxattr(&ctx, XATTR_NTFS_ACL,
-                                                ni, dir_ni, desc,
-                                                sd->sizes[inode->i_security_id], 0);
-
-               if (ret) {
-                       ERROR_WITH_ERRNO("Failed to set security data on `%s'",
-                                       dentry->_full_path);
-                       ret = WIMLIB_ERR_NTFS_3G;
-               }
-       }
+       FREE(name_utf16le);
+out_close_dir_ni:
+       if (ntfs_inode_close(dir_ni))
+               ret = WIMLIB_ERR_NTFS_3G;
+out_close_ni:
+       if (ntfs_inode_close(ni))
+               ret = WIMLIB_ERR_NTFS_3G;
+out:
        return ret;
 }
 
 /*
- * Transfers the reparse data from a WIM inode (which must represent a reparse
- * point) to a NTFS inode.
+ * Extract a stream (default or alternate data) to an attribute of a NTFS file.
  */
 static int
-apply_reparse_data(ntfs_inode *ni, struct wim_dentry *dentry,
-                  union wimlib_progress_info *progress_info)
+ntfs_3g_extract_stream(const char *path, const utf16lechar *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;
-       u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8);
-       u16 rpbuflen;
+       struct ntfs_attr_extract_ctx extract_ctx;
+       utf16lechar stream_name_copy[stream_name_nchars + 1];
 
-       DEBUG("Applying reparse data to `%s'", dentry->_full_path);
+       memcpy(stream_name_copy, stream_name,
+              stream_name_nchars * sizeof(utf16lechar));
+       stream_name_copy[stream_name_nchars] = 0;
 
-       ret = wim_inode_get_reparse_data(dentry->d_inode, rpbuf, &rpbuflen);
-       if (ret)
-               return ret;
+       ret = 0;
+       if (!stream_name_nchars && !lte)
+               goto out;
 
-       ret = ntfs_set_ntfs_reparse_data(ni, rpbuf, rpbuflen, 0);
-       if (ret) {
-               ERROR_WITH_ERRNO("Failed to set NTFS reparse data on `%s'",
-                                dentry->_full_path);
+       /* Look up NTFS inode to which to extract the stream.  */
+       ret = WIMLIB_ERR_NTFS_3G;
+       ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
+       if (!ni)
+               goto out;
+
+       /* Add the stream if it's not the default (unnamed) stream.  */
+       ret = WIMLIB_ERR_NTFS_3G;
+       if (stream_name_nchars)
+               if (ntfs_attr_add(ni, AT_DATA, stream_name_copy,
+                                 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_NTFS_3G;
+       na = ntfs_attr_open(ni, AT_DATA, stream_name_copy, stream_name_nchars);
+       if (!na)
+               goto out_close;
+
+       /* (Optional) Immediately resize attribute to size of stream.  */
+       ret = WIMLIB_ERR_NTFS_3G;
+       if (ntfs_attr_truncate_solid(na, wim_resource_size(lte)))
+               goto out_attr_close;
+
+       /* Extract stream data to the NTFS attribute.  */
+       extract_ctx.na = na;
+       extract_ctx.offset = 0;
+       ret = extract_wim_resource(lte, wim_resource_size(lte),
+                                  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_NTFS_3G;
+out:
+       if (ret && !errno)
+               errno = -1;
+       return ret;
+}
+
+static int
+ntfs_3g_extract_unnamed_stream(const char *path,
+                              struct wim_lookup_table_entry *lte,
+                              struct apply_ctx *ctx)
+{
+       return ntfs_3g_extract_stream(path, NULL, 0, lte, ctx);
+}
+
+static int
+ntfs_3g_extract_named_stream(const char *path, const utf16lechar *stream_name,
+                            size_t stream_name_nchars,
+                            struct wim_lookup_table_entry *lte, struct apply_ctx *ctx)
+{
+       return ntfs_3g_extract_stream(path, stream_name,
+                                     stream_name_nchars, lte, ctx);
+}
+
+static int
+ntfs_3g_set_file_attributes(const char *path, u32 attributes,
+                           struct apply_ctx *ctx)
+{
+       ntfs_inode *ni;
+       int ret = 0;
+
+       ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
+       if (!ni)
                return WIMLIB_ERR_NTFS_3G;
-       }
+       if (ntfs_set_ntfs_attrib(ni, (const char*)&attributes, sizeof(u32), 0))
+               ret = WIMLIB_ERR_NTFS_3G;
+       if (ntfs_inode_close(ni))
+               ret = WIMLIB_ERR_NTFS_3G;
+       return ret;
+}
 
-       progress_info->extract.completed_bytes += rpbuflen - 8;
-       return 0;
+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_NTFS_3G;
+       if (ntfs_set_ntfs_reparse_data(ni, rpbuf, rpbuflen, 0))
+               ret = WIMLIB_ERR_NTFS_3G;
+       if (ntfs_inode_close(ni))
+               ret = WIMLIB_ERR_NTFS_3G;
+       return ret;
 }
 
-/*
- * Applies a WIM dentry to a NTFS filesystem.
- *
- * @dentry:  The WIM dentry to apply
- * @dir_ni:  The NTFS inode for the parent directory
- *
- * @return:  0 on success; nonzero on failure.
- */
 static int
-do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni,
-                    struct apply_args *args)
+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;
-       mode_t type;
-       ntfs_inode *ni = NULL;
-       struct wim_inode *inode = dentry->d_inode;
-       dentry->needs_extraction = 0;
-
-       if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
-               type = S_IFDIR;
-       } else {
-               type = S_IFREG;
-               if (inode->i_nlink > 1) {
-                       /* Inode has multiple dentries referencing it. */
-                       if (inode->i_extracted_file) {
-                               /* Already extracted another dentry in the hard
-                                * link group.  Make a hard link instead of
-                                * extracting the file data. */
-                               ret = apply_ntfs_hardlink(dentry, inode, dir_ni);
-                               /* dir_ni was closed */
-                               goto out;
-                       } else {
-                               /* None of the dentries of this inode have been
-                                * extracted yet, so go ahead and extract the
-                                * first one. */
-                               FREE(inode->i_extracted_file);
-                               if (!(inode->i_extracted_file = STRDUP(dentry->_full_path)))
-                               {
-                                       ret = WIMLIB_ERR_NOMEM;
-                                       goto out_close_dir_ni;
-                               }
-                       }
-               }
-       }
+       char *dosname = NULL;
+       size_t dosname_nbytes;
 
-       /* Create a NTFS directory or file.
-        *
-        * Note: For symbolic links that are not directory junctions, S_IFREG is
-        * passed here, since the reparse data and file attributes are set
-        * later. */
-       ni = ntfs_create(dir_ni, 0, dentry->file_name,
-                        dentry->file_name_nbytes / 2, type);
-
-       if (!ni) {
-               ERROR_WITH_ERRNO("Could not create NTFS inode for `%s'",
-                                dentry->_full_path);
-               ret = WIMLIB_ERR_NTFS_3G;
-               goto out_close_dir_ni;
-       }
+       ret = 0;
+       if (short_name_nchars == 0)
+               goto out;
 
-       /* Write the data streams, unless this is reparse point. */
-       if (!(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
-               ret = write_ntfs_data_streams(ni, dentry, &args->progress);
-               if (ret)
-                       goto out_close_dir_ni;
-       }
+       vol = ntfs_3g_apply_ctx_get_volume(ctx);
 
-       ret = apply_file_attributes_and_security_data(ni, dir_ni, dentry,
-                                                     args->wim,
-                                                     args->extract_flags);
-       if (ret)
+       ret = WIMLIB_ERR_NTFS_3G;
+       dir_ni = ntfs_3g_open_parent_inode(path, vol);
+       if (!dir_ni)
+               goto out;
+
+       ret = WIMLIB_ERR_NTFS_3G;
+       ni = ntfs_pathname_to_inode(vol, NULL, path);
+       if (!ni)
                goto out_close_dir_ni;
 
-       if (inode->i_attributes & FILE_ATTR_REPARSE_POINT) {
-               ret = apply_reparse_data(ni, dentry, &args->progress);
-               if (ret)
-                       goto out_close_dir_ni;
-       }
+       ret = utf16le_to_tstr(short_name, short_name_nchars * 2,
+                             &dosname, &dosname_nbytes);
+       if (ret)
+               goto out_close_ni;
 
-       /* Set DOS (short) name if given */
-       if (dentry_has_short_name(dentry) && !dentry->dos_name_invalid)
-       {
-               char *short_name_mbs;
-               size_t short_name_mbs_nbytes;
-               ret = utf16le_to_tstr(dentry->short_name,
-                                     dentry->short_name_nbytes,
-                                     &short_name_mbs,
-                                     &short_name_mbs_nbytes);
-               if (ret)
-                       goto out_close_dir_ni;
-
-               DEBUG("Setting short (DOS) name of `%s' to %s",
-                     dentry->_full_path, short_name_mbs);
-
-               ret = ntfs_set_ntfs_dos_name(ni, dir_ni, short_name_mbs,
-                                            short_name_mbs_nbytes, 0);
-               FREE(short_name_mbs);
-               if (ret) {
-                       WARNING_WITH_ERRNO("Could not set DOS (short) name for `%s'",
-                                          dentry->_full_path);
-                       ret = 0;
-               }
-               /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
-               goto out;
-       }
+       ret = 0;
+       if (ntfs_set_ntfs_dos_name(ni, dir_ni, dosname,
+                                  dosname_nbytes, 0))
+               ret = WIMLIB_ERR_NTFS_3G;
+       /* 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_NTFS_3G;
 out_close_dir_ni:
-       if (dir_ni) {
-               if (ni) {
-                       if (ntfs_inode_close_in_dir(ni, dir_ni)) {
-                               if (ret == 0)
-                                       ret = WIMLIB_ERR_NTFS_3G;
-                               ERROR_WITH_ERRNO("Failed to close inode for `%s'",
-                                                dentry->_full_path);
-                       }
-               }
-               if (ntfs_inode_close(dir_ni)) {
-                       if (ret == 0)
-                               ret = WIMLIB_ERR_NTFS_3G;
-                       ERROR_WITH_ERRNO("Failed to close inode of directory "
-                                        "containing `%s'",
-                                        dentry->_full_path);
-               }
-       }
+       if (ntfs_inode_close(dir_ni))
+               ret = WIMLIB_ERR_NTFS_3G;
 out:
        return ret;
 }
 
 static int
-apply_root_dentry_ntfs(struct wim_dentry *dentry,
-                      ntfs_volume *vol, const WIMStruct *wim,
-                      int extract_flags)
+ntfs_3g_set_security_descriptor(const char *path, const u8 *desc, size_t desc_size,
+                               struct apply_ctx *ctx, bool strict)
 {
+       ntfs_volume *vol;
        ntfs_inode *ni;
+       struct SECURITY_CONTEXT sec_ctx;
        int ret = 0;
 
-       ret = calculate_dentry_full_path(dentry);
-       if (ret)
-               return ret;
+       vol = ntfs_3g_apply_ctx_get_volume(ctx);
 
-       ni = ntfs_pathname_to_inode(vol, NULL, "/");
-       if (!ni) {
-               ERROR_WITH_ERRNO("Could not find root NTFS inode");
+       ni = ntfs_pathname_to_inode(vol, NULL, path);
+       if (!ni)
                return WIMLIB_ERR_NTFS_3G;
-       }
-       ret = apply_file_attributes_and_security_data(ni, ni, dentry, wim,
-                                                     extract_flags);
-       if (ntfs_inode_close(ni) != 0) {
-               ERROR_WITH_ERRNO("Failed to close NTFS inode for root "
-                                "directory");
+
+       memset(&sec_ctx, 0, sizeof(sec_ctx));
+       sec_ctx.vol = vol;
+
+       if (ntfs_set_ntfs_acl(&sec_ctx, ni, desc, desc_size, 0))
+               ret = WIMLIB_ERR_NTFS_3G;
+       if (ntfs_inode_close(ni))
                ret = WIMLIB_ERR_NTFS_3G;
-       }
        return ret;
 }
 
-/* Applies a WIM dentry to the NTFS volume */
-int
-apply_dentry_ntfs(struct wim_dentry *dentry, void *arg)
+static int
+ntfs_3g_set_timestamps(const char *path, u64 creation_time,
+                      u64 last_write_time, u64 last_access_time,
+                      struct apply_ctx *ctx)
 {
-       struct apply_args *args = arg;
-       ntfs_volume *vol = args->vol;
-       WIMStruct *wim = args->wim;
-       struct wim_dentry *orig_dentry;
-       struct wim_dentry *other;
-       int ret;
+       u64 ntfs_timestamps[3];
+       ntfs_inode *ni;
+       int ret = 0;
 
-       /* Treat the root dentry specially. */
-       if (dentry_is_root(dentry))
-               return apply_root_dentry_ntfs(dentry, vol, wim,
-                                             args->extract_flags);
-
-       /* NTFS filename namespaces need careful consideration.  A name for a
-        * NTFS file may be in either the POSIX, Win32, DOS, or Win32+DOS
-        * namespaces.  A NTFS file (a.k.a. inode) may have multiple names in
-        * multiple directories (i.e. hard links); however, a NTFS file can have
-        * at most 1 DOS name total.  Furthermore, a Win32 name is always
-        * associated with a DOS name (either as a Win32+DOS name, or a Win32
-        * name and a DOS name separately), which implies that a NTFS file can
-        * have at most 1 Win32 name.
-        *
-        * A WIM dentry just contains a "long name", which wimlib makes sure is
-        * non-empty, and a "short name", which may be empty.  So, wimlib must
-        * map these to the correct NTFS names.  wimlib collects all WIM
-        * dentries that map to the same NTFS inode and factors out the common
-        * information into a 'struct wim_inode', so this should make the
-        * mapping a little more obvious.  As a NTFS file can have at most 1 DOS
-        * name, a WIM inode cannot have more than 1 dentry with a non-empty
-        * short name, and this is checked in the verify_inode() function in
-        * verify.c.  Furthermore, a WIM dentry, if any, that has a DOS name
-        * must have a long name that corresponds to a Win32 name or Win32+DOS
-        * name.
-        *
-        * WIM dentries that have a long name but no associated short name are
-        * assumed to be in the POSIX namespace.
-        *
-        * So, given a WIM inode that is to map to a NTFS inode, we must apply
-        * the Win32 and DOS or Win32+DOS names, if they exist, then any
-        * additional (POSIX) names.  A caveat when actually doing this:  as
-        * confirmed by the libntfs-3g authors, ntfs_set_ntfs_dos_name() is only
-        * guaranteed to associate a DOS name with the appropriate long name if
-        * it's called when that long name is the only one in existence for that
-        * file.  So, this implies that the correct ordering of function calls
-        * to extract a NTFS file are:
-        *
-        *      if (file has a DOS name) {
-        *              - Call ntfs_create() to create long name associated with
-        *              the DOS name (this initially creates a POSIX name)
-        *              - Call ntfs_set_ntfs_dos_name() to associate a DOS name
-        *              with the long name just created.  This either changes
-        *              the POSIX name to Win32+DOS, or changes the POSIX name
-        *              to Win32 and creates a separate DOS name.
-        *      } else {
-        *              - Call ntfs_create() to create the first link to the
-        *              file in the POSIX namespace
-        *      }
-        *      - Call ntfs_link() to create the other names of the file, in the
-        *      POSIX namespace.
-        */
-again:
-       orig_dentry = NULL;
-       if (!dentry->d_inode->i_dos_name_extracted &&
-           (!dentry_has_short_name(dentry) || dentry->dos_name_invalid))
-       {
-               inode_for_each_dentry(other, dentry->d_inode) {
-                       if (dentry_has_short_name(other) && !other->dos_name_invalid) {
-                               orig_dentry = dentry;
-                               dentry = other;
-                               break;
-                       }
-               }
-       }
-       dentry->d_inode->i_dos_name_extracted = 1;
+       ni = ntfs_3g_apply_pathname_to_inode(path, ctx);
+       if (!ni)
+               return WIMLIB_ERR_NTFS_3G;
 
-       ret = calculate_dentry_full_path(dentry);
-       if (ret)
-               return ret;
-
-       ntfs_inode *dir_ni = dentry_open_parent_ni(dentry, vol);
-       if (dir_ni) {
-               ret = do_apply_dentry_ntfs(dentry, dir_ni, arg);
-               if (ret == 0 && orig_dentry != NULL) {
-                       dentry = orig_dentry;
-                       goto again;
-               }
-       } else {
+       /* 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_NTFS_3G;
+       if (ntfs_inode_close(ni))
                ret = WIMLIB_ERR_NTFS_3G;
-       }
        return ret;
 }
 
-/* Transfers the 100-nanosecond precision timestamps from a WIM dentry to a NTFS
- * inode */
-int
-apply_dentry_timestamps_ntfs(struct wim_dentry *dentry, void *arg)
+static bool
+ntfs_3g_target_is_root(const char *target)
 {
-       struct apply_args *args = arg;
-       ntfs_volume *vol = args->vol;
-       u64 ntfs_timestamps[3];
-       ntfs_inode *ni;
-       int ret;
+       /* We always extract to the root of the NTFS volume.  */
+       return true;
+}
 
-       DEBUG("Setting timestamps on `%s'", dentry->_full_path);
+static int
+ntfs_3g_start_extract(const char *path, struct apply_ctx *ctx)
+{
+       ntfs_volume *vol;
 
-       ni = ntfs_pathname_to_inode(vol, NULL, dentry->_full_path);
-       if (!ni) {
-               ERROR_WITH_ERRNO("Could not find NTFS inode for `%s'",
-                                dentry->_full_path);
+       vol = ntfs_mount(ctx->target, 0);
+       if (!vol) {
+               ERROR_WITH_ERRNO("Failed to mount \"%"TS"\" with NTFS-3g", ctx->target);
                return WIMLIB_ERR_NTFS_3G;
        }
+       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;
+}
 
-       /* Note: ntfs_inode_set_times() expects the times in native byte order,
-        * not little endian. */
-       ntfs_timestamps[0] = dentry->d_inode->i_creation_time;
-       ntfs_timestamps[1] = dentry->d_inode->i_last_write_time;
-       ntfs_timestamps[2] = dentry->d_inode->i_last_access_time;
-       ret = ntfs_inode_set_times(ni, (const char*)ntfs_timestamps,
-                                  sizeof(ntfs_timestamps), 0);
-       if (ret != 0) {
-               ERROR_WITH_ERRNO("Failed to set NTFS timestamps on `%s'",
-                                dentry->_full_path);
-               ret = WIMLIB_ERR_NTFS_3G;
-       }
+static int
+ntfs_3g_finish_or_abort_extract(struct apply_ctx *ctx)
+{
+       ntfs_volume *vol;
 
-       if (ntfs_inode_close(ni) != 0) {
-               if (ret == 0)
-                       ret = WIMLIB_ERR_NTFS_3G;
-               ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
-                                dentry->_full_path);
+       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_NTFS_3G;
        }
-       return ret;
+       return 0;
 }
 
 void
@@ -675,4 +465,31 @@ libntfs3g_global_init(void)
        ntfs_set_char_encoding(setlocale(LC_ALL, ""));
 }
 
+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,
+
+       .supports_case_sensitive_filenames = 1,
+       .root_directory_is_special = 1,
+};
+
 #endif /* WITH_NTFS_3G */
index 9becb2128185d6dddc06c5251332c9ca4bf53bca..85fb6bc082130656713e32f46ba84884880616ad 100644 (file)
@@ -87,7 +87,7 @@ canonicalize_fs_path(const tchar *fs_path)
        return TSTRDUP(fs_path);
 }
 
-/* 
+/*
  * canonicalize_wim_path - Given a user-provided path to a file within a WIM
  * image, translate it into a "canonical" path.
  *
index 3b36548c3c63951c858c37970a8dde011a70c13d..9003766df8adf4c86044224d85ba82a8cbac08b4 100644 (file)
@@ -272,7 +272,8 @@ make_reparse_buffer(const struct reparse_data * restrict rpdata,
 int
 wim_inode_get_reparse_data(const struct wim_inode * restrict inode,
                           u8 * restrict rpbuf,
-                          u16 * restrict rpbuflen_ret)
+                          u16 * restrict rpbuflen_ret,
+                          struct wim_lookup_table_entry *lte_override)
 {
        struct wim_lookup_table_entry *lte;
        int ret;
@@ -281,10 +282,14 @@ wim_inode_get_reparse_data(const struct wim_inode * restrict inode,
 
        wimlib_assert(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT);
 
-       lte = inode_unnamed_lte_resolved(inode);
-       if (!lte) {
-               ERROR("Reparse point has no reparse data!");
-               return WIMLIB_ERR_INVALID_REPARSE_DATA;
+       if (!lte_override) {
+               lte = inode_unnamed_lte_resolved(inode);
+               if (!lte) {
+                       ERROR("Reparse point has no reparse data!");
+                       return WIMLIB_ERR_INVALID_REPARSE_DATA;
+               }
+       } else {
+               lte = lte_override;
        }
 
        if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE - 8) {
@@ -327,7 +332,8 @@ wim_inode_get_reparse_data(const struct wim_inode * restrict inode,
  * on failure a negated error code is returned rather than -1 with errno set.  */
 ssize_t
 wim_inode_readlink(const struct wim_inode * restrict inode,
-                  char * restrict buf, size_t bufsize)
+                  char * restrict buf, size_t bufsize,
+                  struct wim_lookup_table_entry *lte_override)
 {
        int ret;
        struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8);
@@ -339,7 +345,8 @@ wim_inode_readlink(const struct wim_inode * restrict inode,
 
        wimlib_assert(inode_is_symlink(inode));
 
-       if (wim_inode_get_reparse_data(inode, (u8*)&rpbuf_disk, &rpbuflen))
+       if (wim_inode_get_reparse_data(inode, (u8*)&rpbuf_disk, &rpbuflen,
+                                      lte_override))
                return -EIO;
 
        if (parse_reparse_data((const u8*)&rpbuf_disk, rpbuflen, &rpdata))
index 8705cb1d582d01e19741052fb8e6154216f77ddc..03d6e0c47d35aeaef0d3160b3be5115c8ec5c965 100644 (file)
 #include <unistd.h>
 
 /*
- * Reads all or part of a compressed WIM resource.
+ *                            Compressed resources
  *
- * Returns zero on success, nonzero on failure.
+ * A compressed resource in a WIM consists of a number of consecutive LZX or
+ * XPRESS-compressed chunks, each of which decompresses to 32768 bytes of data,
+ * except possibly the last, which always decompresses to any remaining bytes.
+ * In addition, immediately before the chunks, a table (the "chunk table")
+ * provides the offset, in bytes relative to the end of the chunk table, of the
+ * start of each compressed chunk, except for the first chunk which is omitted
+ * as it always has an offset of 0.  Therefore, a compressed resource with N
+ * chunks will have a chunk table with N - 1 entries.
+ *
+ * Additional information:
+ *
+ * - Entries in the chunk table are 4 bytes each, except if the uncompressed
+ *   size of the resource is greater than 4 GiB, in which case the entries in
+ *   the chunk table are 8 bytes each.  In either case, the entries are unsigned
+ *   little-endian integers.
+ *
+ * - The chunk table is included in the compressed size of the resource provided
+ *   in the corresponding entry in the WIM's stream lookup table.
+ *
+ * - The compressed size of a chunk is never greater than the uncompressed size.
+ *   From the compressor's point of view, chunks that would have compressed to a
+ *   size greater than or equal to their original size are in fact stored
+ *   uncompressed.  From the decompresser's point of view, chunks with
+ *   compressed size equal to their uncompressed size are in fact uncompressed.
+ *
+ * Furthermore, wimlib supports its own "pipable" WIM format, and for this the
+ * structure of compressed resources was modified to allow piped reading and
+ * writing.  To make sequential writing possible, the chunk table is placed
+ * after the chunks rather than before the chunks, and to make sequential
+ * reading possible, each chunk is prefixed with a 4-byte header giving its
+ * compressed size as a 32-bit, unsigned, little-endian integer (less than or
+ * equal to 32768).  Otherwise the details are the same.
+ */
+
+typedef int (*decompress_func_t)(const void *, unsigned, void *, unsigned);
+
+static decompress_func_t
+get_decompress_func(int ctype)
+{
+       if (ctype == WIMLIB_COMPRESSION_TYPE_LZX)
+               return wimlib_lzx_decompress;
+       else
+               return wimlib_xpress_decompress;
+}
+
+/*
+ * read_compressed_resource()-
+ *
+ * Read data from a compressed resource being read from a seekable WIM file.
+ * The resource may be either pipable or non-pipable.
+ *
+ * @flags may be:
+ *
+ * 0:
+ *     Just do a normal read, decompressing the data if necessary.
+ *
+ * WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS:
+ *     Read the raw contents of the compressed chunks of the compressed
+ *     resource.  For pipable resources, this does *not* include the chunk
+ *     headers.  If a callback function is being used, it will be called once
+ *     for each compressed chunk.  For non-pipable resources, this mode
+ *     excludes the chunk table.  For pipable resources, this mode excludes the
+ *     stream and chunk headers.
  */
 static int
-read_compressed_resource(int in_fd,
-                        u64 resource_compressed_size,
-                        u64 resource_uncompressed_size,
-                        u64 resource_offset,
-                        int resource_ctype,
-                        u64 len,
-                        u64 offset,
-                        consume_data_callback_t cb,
-                        void *ctx_or_buf)
+read_compressed_resource(const struct wim_lookup_table_entry *lte,
+                        u64 size, consume_data_callback_t cb,
+                        void *ctx_or_buf, int flags, u64 offset)
 {
        int ret;
 
-       /* Trivial case */
-       if (len == 0)
-               return 0;
+       /* Currently, reading raw compressed chunks is only guaranteed to work
+        * correctly when the full resource is requested.  Furthermore, in such
+        * cases the requested size is specified as the compressed size, but
+        * here we change it to an uncompressed size to avoid confusing the rest
+        * of this function.  */
+       if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS) {
+               wimlib_assert(offset == 0);
+               wimlib_assert(size == lte->resource_entry.size);
+               size = wim_resource_size(lte);
+       }
 
-       int (*decompress)(const void *, unsigned, void *, unsigned);
-       /* Set the appropriate decompress function. */
-       if (resource_ctype == WIMLIB_COMPRESSION_TYPE_LZX)
-               decompress = wimlib_lzx_decompress;
-       else
-               decompress = wimlib_xpress_decompress;
-
-       /* The structure of a compressed resource consists of a table of chunk
-        * offsets followed by the chunks themselves.  Each chunk consists of
-        * compressed data, and there is one chunk for each WIM_CHUNK_SIZE =
-        * 32768 bytes of the uncompressed file, with the last chunk having any
-        * remaining bytes.
-        *
-        * The chunk offsets are measured relative to the end of the chunk
-        * table.  The first chunk is omitted from the table in the WIM file
-        * because its offset is implicitly given by the fact that it directly
-        * follows the chunk table and therefore must have an offset of 0.
-        */
+       wimlib_assert(offset + size <= wim_resource_size(lte));
 
-       /* Calculate how many chunks the resource consists of in its entirety.
-        * */
-       u64 num_chunks = DIV_ROUND_UP(resource_uncompressed_size, WIM_CHUNK_SIZE);
+       /* Handle the trivial case.  */
+       if (size == 0)
+               return 0;
 
-       /* As mentioned, the first chunk has no entry in the chunk table. */
-       u64 num_chunk_entries = num_chunks - 1;
+       /* Get the appropriate decompression function.  */
+       decompress_func_t decompress =
+                       get_decompress_func(wim_resource_compression_type(lte));
 
+       /* Get the file descriptor for the WIM.  */
+       struct filedes *in_fd = &lte->wim->in_fd;
 
-       /* The index of the chunk that the read starts at. */
-       u64 start_chunk = offset / WIM_CHUNK_SIZE;
-       /* The byte offset at which the read starts, within the start chunk. */
-       u64 start_chunk_offset = offset % WIM_CHUNK_SIZE;
+       /* Calculate the number of chunks the resource is divided into.  */
+       u64 num_chunks = wim_resource_chunks(lte);
 
-       /* The index of the chunk that contains the last byte of the read. */
-       u64 end_chunk   = (offset + len - 1) / WIM_CHUNK_SIZE;
-       /* The byte offset of the last byte of the read, within the end chunk */
-       u64 end_chunk_offset = (offset + len - 1) % WIM_CHUNK_SIZE;
+       /* Calculate the number of entries in the chunk table; it's one less
+        * than the number of chunks, since the first chunk has no entry.  */
+       u64 num_chunk_entries = num_chunks - 1;
 
-       /* Number of chunks that are actually needed to read the requested part
-        * of the file. */
-       u64 num_needed_chunks = end_chunk - start_chunk + 1;
+       /* Calculate the 0-based index of the chunk at which the read starts.
+        */
+       u64 start_chunk = offset / WIM_CHUNK_SIZE;
 
-       /* If the end chunk is not the last chunk, an extra chunk entry is
-        * needed because we need to know the offset of the chunk after the last
-        * chunk read to figure out the size of the last read chunk. */
+       /* Calculate the offset, within the start chunk, of the first byte of
+        * the read.  */
+       u64 start_offset_in_chunk = offset % WIM_CHUNK_SIZE;
+
+       /* Calculate the index of the chunk that contains the last byte of the
+        * read.  */
+       u64 end_chunk = (offset + size - 1) / WIM_CHUNK_SIZE;
+
+       /* Calculate the offset, within the end chunk, of the last byte of the
+        * read.  */
+       u64 end_offset_in_chunk = (offset + size - 1) % WIM_CHUNK_SIZE;
+
+       /* Calculate the number of chunk entries are actually needed to read the
+        * requested part of the resource.  Include an entry for the first chunk
+        * even though that doesn't exist in the on-disk table, but take into
+        * account that if the last chunk required for the read is not the last
+        * chunk of the resource, an extra chunk entry is needed so that the
+        * compressed size of the last chunk of the read can be determined.  */
+       u64 num_alloc_chunk_entries = end_chunk - start_chunk + 1;
        if (end_chunk != num_chunks - 1)
-               num_needed_chunks++;
+               num_alloc_chunk_entries++;
 
-       /* According to M$'s documentation, if the uncompressed size of
-        * the file is greater than 4 GB, the chunk entries are 8-byte
-        * integers.  Otherwise, they are 4-byte integers. */
-       u64 chunk_entry_size = (resource_uncompressed_size >
-                               (u64)1 << 32) ?  8 : 4;
+       /* Set the size of each chunk table entry based on the resource's
+        * uncompressed size.  */
+       u64 chunk_entry_size = (wim_resource_size(lte) > ((u64)1 << 32)) ? 8 : 4;
 
-       /* Size of the full chunk table in the WIM file. */
-       u64 chunk_table_size = chunk_entry_size * num_chunk_entries;
+       /* Calculate the size, in bytes, of the full chunk table.  */
+       u64 chunk_table_size = num_chunk_entries * chunk_entry_size;
 
-       /* Allocate the chunk table.  It will only contain offsets for the
-        * chunks that are actually needed for this read. */
+       /* Allocate a buffer to hold a subset of the chunk table.  It will only
+        * contain offsets for the chunks that are actually needed for this
+        * read.  For speed, allocate the buffer on the stack unless it's too
+        * large.  */
        u64 *chunk_offsets;
        bool chunk_offsets_malloced;
-       if (num_needed_chunks < 1024) {
-               chunk_offsets = alloca(num_needed_chunks * sizeof(u64));
+       if (num_alloc_chunk_entries < 1024) {
+               chunk_offsets = alloca(num_alloc_chunk_entries * sizeof(u64));
                chunk_offsets_malloced = false;
        } else {
-               chunk_offsets = malloc(num_needed_chunks * sizeof(u64));
+               chunk_offsets = malloc(num_alloc_chunk_entries * sizeof(u64));
                if (!chunk_offsets) {
                        ERROR("Failed to allocate chunk table "
-                             "with %"PRIu64" entries", num_needed_chunks);
+                             "with %"PRIu64" entries", num_alloc_chunk_entries);
                        return WIMLIB_ERR_NOMEM;
                }
                chunk_offsets_malloced = true;
        }
 
-       /* Set the implicit offset of the first chunk if it is included in the
-        * needed chunks.
-        *
-        * Note: M$'s documentation includes a picture that shows the first
-        * chunk starting right after the chunk entry table, labeled as offset
-        * 0x10.  However, in the actual file format, the offset is measured
-        * from the end of the chunk entry table, so the first chunk has an
-        * offset of 0. */
+       /* Set the implicit offset of the first chunk if it's included in the
+        * needed chunks.  */
        if (start_chunk == 0)
                chunk_offsets[0] = 0;
 
-
-       /* Read the needed chunk offsets from the table in the WIM file. */
-
-       /* Index, in the WIM file, of the first needed entry in the
-        * chunk table. */
+       /* Calculate the index of the first needed entry in the chunk table.  */
        u64 start_table_idx = (start_chunk == 0) ? 0 : start_chunk - 1;
 
-       /* Number of entries we need to actually read from the chunk
-        * table (excludes the implicit first chunk). */
+       /* Calculate the number of entries that need to be read from the chunk
+        * table */
        u64 num_needed_chunk_entries = (start_chunk == 0) ?
-                               num_needed_chunks - 1 : num_needed_chunks;
-
-       /* Skip over unneeded chunk table entries. */
-       u64 file_offset_of_needed_chunk_entries = resource_offset +
-                               start_table_idx * chunk_entry_size;
-
-       /* Allocate a buffer into which to read the raw chunk entries. */
-       void *chunk_tab_buf;
-       bool chunk_tab_buf_malloced = false;
-
-       /* Number of bytes we need to read from the chunk table. */
-       size_t size = num_needed_chunk_entries * chunk_entry_size;
-       if ((u64)size != num_needed_chunk_entries * chunk_entry_size) {
+                               num_alloc_chunk_entries - 1 : num_alloc_chunk_entries;
+
+       /* Calculate the number of bytes of data that need to be read from the
+        * chunk table.  */
+       size_t chunk_table_needed_size =
+                               num_needed_chunk_entries * chunk_entry_size;
+       if ((u64)chunk_table_needed_size !=
+           num_needed_chunk_entries * chunk_entry_size)
+       {
                ERROR("Compressed read request too large to fit into memory!");
                ret = WIMLIB_ERR_NOMEM;
-               goto out;
+               goto out_free_chunk_offsets;
        }
 
-       if (size < 4096) {
-               chunk_tab_buf = alloca(size);
-       } else {
-               chunk_tab_buf = malloc(size);
-               if (!chunk_tab_buf) {
-                       ERROR("Failed to allocate chunk table buffer of "
-                             "size %zu bytes", size);
-                       ret = WIMLIB_ERR_NOMEM;
-                       goto out;
-               }
-               chunk_tab_buf_malloced = true;
-       }
-
-       if (full_pread(in_fd, chunk_tab_buf, size,
-                      file_offset_of_needed_chunk_entries) != size)
+       /* Calculate the byte offset, in the WIM file, of the first chunk table
+        * entry to read.  Take into account that if the WIM file is in the
+        * special "pipable" format, then the chunk table is at the end of the
+        * resource, not the beginning.  */
+       u64 file_offset_of_needed_chunk_entries =
+                       lte->resource_entry.offset + (start_table_idx *
+                                                     chunk_entry_size);
+       if (lte->is_pipable)
+               file_offset_of_needed_chunk_entries += lte->resource_entry.size -
+                                                      chunk_table_size;
+
+       /* Read the needed chunk table entries into the end of the chunk_offsets
+        * buffer.  */
+       void *chunk_tab_data = (u8*)&chunk_offsets[num_alloc_chunk_entries] -
+                               chunk_table_needed_size;
+       ret = full_pread(in_fd, chunk_tab_data, chunk_table_needed_size,
+                        file_offset_of_needed_chunk_entries);
+       if (ret)
                goto read_error;
 
        /* Now fill in chunk_offsets from the entries we have read in
-        * chunk_tab_buf. */
-
-       u64 *chunk_tab_p = chunk_offsets;
-       if (start_chunk == 0)
-               chunk_tab_p++;
-
-       if (chunk_entry_size == 4) {
-               le32 *entries = (le32*)chunk_tab_buf;
-               while (num_needed_chunk_entries--)
-                       *chunk_tab_p++ = le32_to_cpu(*entries++);
-       } else {
-               le64 *entries = (le64*)chunk_tab_buf;
-               while (num_needed_chunk_entries--)
-                       *chunk_tab_p++ = le64_to_cpu(*entries++);
+        * chunk_tab_data.  Careful: chunk_offsets aliases chunk_tab_data, which
+        * breaks C's aliasing rules when we read 32-bit integers and store
+        * 64-bit integers.  But since the operations are safe as long as the
+        * compiler doesn't mess with their order, we use the gcc may_alias
+        * extension to tell the compiler that loads from the 32-bit integers
+        * may alias stores to the 64-bit integers.  */
+       {
+               typedef le64 __attribute__((may_alias)) aliased_le64_t;
+               typedef le32 __attribute__((may_alias)) aliased_le32_t;
+               u64 *chunk_offsets_p = chunk_offsets;
+               u64 i;
+
+               if (start_chunk == 0)
+                       chunk_offsets_p++;
+
+               if (chunk_entry_size == 4) {
+                       aliased_le32_t *raw_entries = (aliased_le32_t*)chunk_tab_data;
+                       for (i = 0; i < num_needed_chunk_entries; i++)
+                               chunk_offsets_p[i] = le32_to_cpu(raw_entries[i]);
+               } else {
+                       aliased_le64_t *raw_entries = (aliased_le64_t*)chunk_tab_data;
+                       for (i = 0; i < num_needed_chunk_entries; i++)
+                               chunk_offsets_p[i] = le64_to_cpu(raw_entries[i]);
+               }
        }
 
-       /* Done reading the chunk table now.  Now calculate the file offset for
-        * the first byte of compressed data we need to read. */
-
-       u64 cur_read_offset = resource_offset + chunk_table_size + chunk_offsets[0];
+       /* Calculate file offset of the first chunk that needs to be read.  N.B.
+        * if the resource is pipable, the entries in the chunk table do *not*
+        * include the chunk headers.  */
+       u64 cur_read_offset = lte->resource_entry.offset + chunk_offsets[0];
+       if (!lte->is_pipable)
+               cur_read_offset += chunk_table_size;
+       else
+               cur_read_offset += start_chunk *
+                                  sizeof(struct pwm_chunk_hdr);
 
-       /* Pointer to current position in the output buffer for uncompressed
-        * data.  Alternatively, if using a callback function, we repeatedly
-        * fill a temporary buffer to feed data into the callback function.  */
+       /* If using a callback function, allocate a temporary buffer that will
+        * be used to pass data to it.  If writing directly to a buffer instead,
+        * arrange to write data directly into it.  */
        u8 *out_p;
        if (cb)
                out_p = alloca(WIM_CHUNK_SIZE);
        else
                out_p = ctx_or_buf;
 
-       /* Buffer for compressed data.  While most compressed chunks will have a
-        * size much less than WIM_CHUNK_SIZE, WIM_CHUNK_SIZE - 1 is the maximum
-        * size in the worst-case.  This assumption is valid only if chunks that
-        * happen to compress to more than the uncompressed size (i.e. a
-        * sequence of random bytes) are always stored uncompressed. But this seems
-        * to be the case in M$'s WIM files, even though it is undocumented. */
-       void *compressed_buf = alloca(WIM_CHUNK_SIZE - 1);
-
-       /* Decompress all the chunks. */
+       /* Unless the raw compressed data was requested, allocate a temporary
+        * buffer for reading compressed chunks, each of which can be at most
+        * WIM_CHUNK_SIZE - 1 bytes.  This excludes compressed chunks that are a
+        * full WIM_CHUNK_SIZE bytes, which are handled separately.  */
+       void *compressed_buf;
+       if (!(flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS))
+               compressed_buf = alloca(WIM_CHUNK_SIZE - 1);
+
+       /* Read, and possibly decompress, each needed chunk, either writing the
+        * data directly into the @ctx_or_buf buffer or passing it to the @cb
+        * callback function.  */
        for (u64 i = start_chunk; i <= end_chunk; i++) {
 
+               /* If the resource is pipable, skip the chunk header.  */
+               if (lte->is_pipable)
+                       cur_read_offset += sizeof(struct pwm_chunk_hdr);
+
                /* Calculate the sizes of the compressed chunk and of the
-                * uncompressed chunk. */
+                * uncompressed chunk.  */
                unsigned compressed_chunk_size;
                unsigned uncompressed_chunk_size;
                if (i != num_chunks - 1) {
-                       /* All the chunks except the last one in the resource
-                        * expand to WIM_CHUNK_SIZE uncompressed, and the amount
-                        * of compressed data for the chunk is given by the
-                        * difference of offsets in the chunk offset table. */
+                       /* Not the last chunk.  Compressed size is given by
+                        * difference of chunk table entries; uncompressed size
+                        * is always 32768 bytes.  */
                        compressed_chunk_size = chunk_offsets[i + 1 - start_chunk] -
                                                chunk_offsets[i - start_chunk];
                        uncompressed_chunk_size = WIM_CHUNK_SIZE;
                } else {
-                       /* The last compressed chunk consists of the remaining
-                        * bytes in the file resource, and the last uncompressed
-                        * chunk has size equal to however many bytes are left-
-                        * that is, the remainder of the uncompressed size when
-                        * divided by WIM_CHUNK_SIZE.
-                        *
-                        * Note that the resource_compressed_size includes the
-                        * chunk table, so the size of it must be subtracted. */
-                       compressed_chunk_size = resource_compressed_size -
+                       /* Last chunk.  Compressed size is the remaining size in
+                        * the compressed resource; uncompressed size is the
+                        * remaining size in the uncompressed resource.  */
+                       compressed_chunk_size = lte->resource_entry.size -
                                                chunk_table_size -
                                                chunk_offsets[i - start_chunk];
+                       if (lte->is_pipable)
+                               compressed_chunk_size -= num_chunks *
+                                                        sizeof(struct pwm_chunk_hdr);
 
-                       uncompressed_chunk_size = resource_uncompressed_size %
-                                                               WIM_CHUNK_SIZE;
-
-                       /* If the remainder is 0, the last chunk actually
-                        * uncompresses to a full WIM_CHUNK_SIZE bytes. */
-                       if (uncompressed_chunk_size == 0)
+                       if (wim_resource_size(lte) % WIM_CHUNK_SIZE == 0)
                                uncompressed_chunk_size = WIM_CHUNK_SIZE;
+                       else
+                               uncompressed_chunk_size = wim_resource_size(lte) %
+                                                         WIM_CHUNK_SIZE;
                }
 
-               /* Figure out how much of this chunk we actually need to read */
-               u64 start_offset;
-               if (i == start_chunk)
-                       start_offset = start_chunk_offset;
-               else
-                       start_offset = 0;
-               u64 end_offset;
-               if (i == end_chunk)
-                       end_offset = end_chunk_offset;
-               else
-                       end_offset = WIM_CHUNK_SIZE - 1;
-
-               unsigned partial_chunk_size = end_offset + 1 - start_offset;
-               bool is_partial_chunk = (partial_chunk_size != uncompressed_chunk_size);
-
-               /* This is undocumented, but chunks can be uncompressed.  This
-                * appears to always be the case when the compressed chunk size
-                * is equal to the uncompressed chunk size. */
-               if (compressed_chunk_size == uncompressed_chunk_size) {
-                       /* Uncompressed chunk */
-                       if (full_pread(in_fd,
-                                      cb ? out_p + start_offset : out_p,
-                                      partial_chunk_size,
-                                      cur_read_offset + start_offset) != partial_chunk_size)
-                       {
+               /* Calculate how much of this chunk needs to be read.  */
+
+               unsigned partial_chunk_size;
+               u64 start_offset = 0;
+               u64 end_offset = WIM_CHUNK_SIZE - 1;
+
+               if (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS) {
+                       partial_chunk_size = compressed_chunk_size;
+               } else {
+                       if (i == start_chunk)
+                               start_offset = start_offset_in_chunk;
+
+                       if (i == end_chunk)
+                               end_offset = end_offset_in_chunk;
+
+                       partial_chunk_size = end_offset + 1 - start_offset;
+               }
+
+               if (compressed_chunk_size == uncompressed_chunk_size ||
+                   (flags & WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS))
+               {
+                       /* Chunk stored uncompressed, or reading raw chunk data.  */
+                       ret = full_pread(in_fd,
+                                        cb ? out_p + start_offset : out_p,
+                                        partial_chunk_size,
+                                        cur_read_offset + start_offset);
+                       if (ret)
                                goto read_error;
-                       }
                } else {
-                       /* Compressed chunk */
+                       /* Compressed chunk and not doing raw read.  */
 
-                       /* Read the compressed data into compressed_buf. */
-                       if (full_pread(in_fd,
-                                      compressed_buf,
-                                      compressed_chunk_size,
-                                      cur_read_offset) != compressed_chunk_size)
-                       {
+                       /* Read the compressed data into compressed_buf.  */
+                       ret = full_pread(in_fd,
+                                        compressed_buf,
+                                        compressed_chunk_size,
+                                        cur_read_offset);
+                       if (ret)
                                goto read_error;
-                       }
 
                        /* For partial chunks and when writing directly to a
                         * buffer, we must buffer the uncompressed data because
-                        * we don't need all of it. */
-                       if (is_partial_chunk && !cb) {
+                        * we don't need all of it.  */
+                       if (partial_chunk_size != uncompressed_chunk_size &&
+                           cb == NULL)
+                       {
                                u8 uncompressed_buf[uncompressed_chunk_size];
 
-                               ret = decompress(compressed_buf,
-                                                compressed_chunk_size,
-                                                uncompressed_buf,
-                                                uncompressed_chunk_size);
+                               ret = (*decompress)(compressed_buf,
+                                                   compressed_chunk_size,
+                                                   uncompressed_buf,
+                                                   uncompressed_chunk_size);
                                if (ret) {
                                        ret = WIMLIB_ERR_DECOMPRESSION;
-                                       goto out;
+                                       errno = EINVAL;
+                                       goto out_free_chunk_offsets;
                                }
                                memcpy(out_p, uncompressed_buf + start_offset,
                                       partial_chunk_size);
                        } else {
-                               ret = decompress(compressed_buf,
-                                                compressed_chunk_size,
-                                                out_p,
-                                                uncompressed_chunk_size);
+                               ret = (*decompress)(compressed_buf,
+                                                   compressed_chunk_size,
+                                                   out_p,
+                                                   uncompressed_chunk_size);
                                if (ret) {
                                        ret = WIMLIB_ERR_DECOMPRESSION;
-                                       goto out;
+                                       errno = EINVAL;
+                                       goto out_free_chunk_offsets;
                                }
                        }
                }
                if (cb) {
-                       /* Feed the data to the callback function */
+                       /* Feed the data to the callback function */
                        ret = cb(out_p + start_offset,
                                 partial_chunk_size, ctx_or_buf);
                        if (ret)
-                               goto out;
+                               goto out_free_chunk_offsets;
                } else {
                        /* No callback function provided; we are writing
                         * directly to a buffer.  Advance the pointer into this
@@ -364,140 +435,282 @@ read_compressed_resource(int in_fd,
        }
 
        ret = 0;
-out:
+out_free_chunk_offsets:
        if (chunk_offsets_malloced)
                FREE(chunk_offsets);
-       if (chunk_tab_buf_malloced)
-               FREE(chunk_tab_buf);
        return ret;
 
 read_error:
        ERROR_WITH_ERRNO("Error reading compressed file resource");
-       ret = WIMLIB_ERR_READ;
-       goto out;
+       goto out_free_chunk_offsets;
 }
 
-/* Translates a WIM resource entry from the on-disk format to an in-memory
- * format. */
-void
-get_resource_entry(const struct resource_entry_disk *disk_entry,
-                  struct resource_entry *entry)
+/* Skip over the chunk table at the end of pipable, compressed resource being
+ * read from a pipe.  */
+static int
+skip_chunk_table(const struct wim_lookup_table_entry *lte,
+                struct filedes *in_fd)
 {
-       /* Note: disk_entry may not be 8 byte aligned--- in that case, the
-        * offset and original_size members will be unaligned.  (This should be
-        * okay since `struct resource_entry_disk' is declared as packed.) */
-
-       /* Read the size and flags into a bitfield portably... */
-       entry->size = (((u64)disk_entry->size[0] <<  0) |
-                      ((u64)disk_entry->size[1] <<  8) |
-                      ((u64)disk_entry->size[2] << 16) |
-                      ((u64)disk_entry->size[3] << 24) |
-                      ((u64)disk_entry->size[4] << 32) |
-                      ((u64)disk_entry->size[5] << 40) |
-                      ((u64)disk_entry->size[6] << 48));
-       entry->flags = disk_entry->flags;
-       entry->offset = le64_to_cpu(disk_entry->offset);
-       entry->original_size = le64_to_cpu(disk_entry->original_size);
+       u64 num_chunk_entries = wim_resource_chunks(lte) - 1;
+       u64 chunk_entry_size = (wim_resource_size(lte) > ((u64)1 << 32)) ? 8 : 4;
+       u64 chunk_table_size = num_chunk_entries * chunk_entry_size;
+       int ret;
 
-       /* offset and original_size are truncated to 62 bits to avoid possible
-        * overflows, when converting to a signed 64-bit integer (off_t) or when
-        * adding size or original_size.  This is okay since no one would ever
-        * actually have a WIM bigger than 4611686018427387903 bytes... */
-       if (entry->offset & 0xc000000000000000ULL) {
-               WARNING("Truncating offset in resource entry");
-               entry->offset &= 0x3fffffffffffffffULL;
-       }
-       if (entry->original_size & 0xc000000000000000ULL) {
-               WARNING("Truncating original_size in resource entry");
-               entry->original_size &= 0x3fffffffffffffffULL;
+       if (num_chunk_entries != 0) {
+               u8 dummy;
+               ret = full_pread(in_fd, &dummy, 1,
+                                in_fd->offset + chunk_table_size - 1);
+               if (ret)
+                       return ret;
        }
+       return 0;
 }
 
-/* Translates a WIM resource entry from an in-memory format into the on-disk
- * format. */
-void
-put_resource_entry(const struct resource_entry *entry,
-                  struct resource_entry_disk *disk_entry)
+/* Read and decompress data from a compressed, pipable resource being read from
+ * a pipe.  */
+static int
+read_pipable_resource(const struct wim_lookup_table_entry *lte,
+                     u64 size, consume_data_callback_t cb,
+                     void *ctx_or_buf, int flags, u64 offset)
 {
-       /* Note: disk_entry may not be 8 byte aligned--- in that case, the
-        * offset and original_size members will be unaligned.  (This should be
-        * okay since `struct resource_entry_disk' is declared as packed.) */
-       u64 size = entry->size;
+       struct filedes *in_fd;
+       decompress_func_t decompress;
+       int ret;
+       u8 chunk[WIM_CHUNK_SIZE];
+       u8 cchunk[WIM_CHUNK_SIZE - 1];
+
+       /* Get pointers to appropriate decompression function and the input file
+        * descriptor.  */
+       decompress = get_decompress_func(wim_resource_compression_type(lte));
+       in_fd = &lte->wim->in_fd;
+
+       /* This function currently assumes the entire resource is being read at
+        * once and that the raw compressed data isn't being requested.  This is
+        * based on the fact that this function currently only gets called
+        * during the operation of wimlib_extract_image_from_pipe().  */
+       wimlib_assert(!(flags & WIMLIB_READ_RESOURCE_FLAG_RAW));
+       wimlib_assert(offset == 0);
+       wimlib_assert(size == wim_resource_size(lte));
+       wimlib_assert(in_fd->offset == lte->resource_entry.offset);
+
+       for (offset = 0; offset < size; offset += WIM_CHUNK_SIZE) {
+               struct pwm_chunk_hdr chunk_hdr;
+               u32 chunk_size;
+               u32 cchunk_size;
+               u8 *res_chunk;
+               u32 res_chunk_size;
+
+               /* Calculate uncompressed size of next chunk.  */
+               chunk_size = min(WIM_CHUNK_SIZE, size - offset);
+
+               /* Read the compressed size of the next chunk from the chunk
+                * header.  */
+               ret = full_read(in_fd, &chunk_hdr, sizeof(chunk_hdr));
+               if (ret)
+                       goto read_error;
 
-       disk_entry->size[0] = size >>  0;
-       disk_entry->size[1] = size >>  8;
-       disk_entry->size[2] = size >> 16;
-       disk_entry->size[3] = size >> 24;
-       disk_entry->size[4] = size >> 32;
-       disk_entry->size[5] = size >> 40;
-       disk_entry->size[6] = size >> 48;
-       disk_entry->flags = entry->flags;
-       disk_entry->offset = cpu_to_le64(entry->offset);
-       disk_entry->original_size = cpu_to_le64(entry->original_size);
+               cchunk_size = le32_to_cpu(chunk_hdr.compressed_size);
+
+               if (cchunk_size > WIM_CHUNK_SIZE) {
+                       errno = EINVAL;
+                       ret = WIMLIB_ERR_INVALID_PIPABLE_WIM;
+                       goto invalid;
+               }
+
+               /* Read chunk data.  */
+               ret = full_read(in_fd, cchunk, cchunk_size);
+               if (ret)
+                       goto read_error;
+
+               if (flags & WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY)
+                       continue;
+
+               /* Decompress chunk if needed.  Uncompressed size same
+                * as compressed size means the chunk is uncompressed.
+                */
+               res_chunk_size = chunk_size;
+               if (cchunk_size == chunk_size) {
+                       res_chunk = cchunk;
+               } else {
+                       ret = (*decompress)(cchunk, cchunk_size,
+                                           chunk, chunk_size);
+                       if (ret) {
+                               errno = EINVAL;
+                               ret = WIMLIB_ERR_DECOMPRESSION;
+                               goto invalid;
+                       }
+                       res_chunk = chunk;
+               }
+
+               /* Feed the uncompressed data into the callback function or copy
+                * it into the provided buffer.  */
+               if (cb) {
+                       ret = cb(res_chunk, res_chunk_size, ctx_or_buf);
+                       if (ret)
+                               return ret;
+               } else {
+                       ctx_or_buf = mempcpy(ctx_or_buf, res_chunk,
+                                            res_chunk_size);
+               }
+       }
+
+       ret = skip_chunk_table(lte, in_fd);
+       if (ret)
+               goto read_error;
+       return 0;
+
+read_error:
+       ERROR_WITH_ERRNO("Error reading compressed file resource");
+       return ret;
+
+invalid:
+       ERROR("Compressed file resource is invalid");
+       return ret;
 }
 
-static int
+/*
+ * read_partial_wim_resource()-
+ *
+ * Read a range of data from a uncompressed or compressed resource in a WIM
+ * file.  Data is written into a buffer or fed into a callback function, as
+ * documented in read_resource_prefix().
+ *
+ * @flags can be:
+ *
+ * 0:
+ *     Just do a normal read, decompressing the data if necessary.  @size and
+ *     @offset are interpreted relative to the uncompressed contents of the
+ *     stream.
+ *
+ * WIMLIB_READ_RESOURCE_FLAG_RAW_FULL:
+ *     Only valid when the resource is compressed:  Read the raw contents of
+ *     the compressed resource.  If the resource is non-pipable, this includes
+ *     the chunk table as well as the compressed chunks.  If the resource is
+ *     pipable, this includes the compressed chunks--- including the chunk
+ *     headers--- and the chunk table.  The stream header is still *not*
+ *     included.
+ *
+ *     In this mode, @offset is relative to the beginning of the raw contents
+ *     of the compressed resource--- that is, the chunk table if the resource
+ *     is non-pipable, or the header for the first compressed chunk if the
+ *     resource is pipable.  @size is the number of raw bytes to read, which
+ *     must not overrun the end of the resource.  For example, if @offset is 0,
+ *     then @size can be at most the raw size of the compressed resource
+ *     (@lte->resource_entry.size).
+ *
+ * WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS:
+ *     Only valid when the resource is compressed and is not being read from a
+ *     pipe:  Read the raw contents of the compressed chunks of the compressed
+ *     resource.  For pipable resources, this does *not* include the chunk
+ *     headers.  If a callback function is being used, it will be called once
+ *     for each compressed chunk.  The chunk table is excluded.  Also, for
+ *     pipable resources, the stream and chunk headers are excluded.  In this
+ *     mode, @size must be exactly the raw size of the compressed resource
+ *     (@lte->resource_entry.size) and @offset must be 0.
+ *
+ * WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY:
+ *     Only valid when the resource is being read from a pipe:  Skip over the
+ *     requested data rather than feed it to the callback function or write it
+ *     into the buffer.  No decompression is done.
+ *     WIMLIB_READ_RESOURCE_FLAG_RAW_* may not be combined with this flag.
+ *     @offset must be 0 and @size must be the uncompressed size of the
+ *     resource.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_READ                 (errno set)
+ *     WIMLIB_ERR_NOMEM                (errno set to ENOMEM)
+ *     WIMLIB_ERR_DECOMPRESSION        (errno set to EINVAL)
+ *     WIMLIB_ERR_INVALID_PIPABLE_WIM  (errno set to EINVAL)
+ *     
+ *     or other error code returned by the @cb function.
+ */
+int
 read_partial_wim_resource(const struct wim_lookup_table_entry *lte,
-                         u64 size,
-                         consume_data_callback_t cb,
-                         void *ctx_or_buf,
-                         int flags,
-                         u64 offset)
+                         u64 size, consume_data_callback_t cb,
+                         void *ctx_or_buf, int flags, u64 offset)
 {
-       WIMStruct *wim;
-       int in_fd;
+       struct filedes *in_fd;
        int ret;
 
+       /* Make sure the resource is actually located in a WIM file and is not
+        * somewhere else.  */
        wimlib_assert(lte->resource_location == RESOURCE_IN_WIM);
 
-       wim = lte->wim;
-       in_fd = wim->in_fd;
-
-       if (lte->resource_entry.flags & WIM_RESHDR_FLAG_COMPRESSED &&
-           !(flags & WIMLIB_RESOURCE_FLAG_RAW))
+       /* Retrieve input file descriptor for the WIM file.  */
+       in_fd = &lte->wim->in_fd;
+
+       /* Don't allow raw reads (either full or chunks) of uncompressed
+        * resources.  */
+       wimlib_assert(!(flags & WIMLIB_READ_RESOURCE_FLAG_RAW) ||
+                     resource_is_compressed(&lte->resource_entry));
+
+       /* Don't allow seek-only reads unless reading from a pipe; also don't
+        * allow combining SEEK_ONLY with either RAW flag.  */
+       wimlib_assert(!(flags & WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY) ||
+                     (!filedes_is_seekable(in_fd) &&
+                      !(flags & WIMLIB_READ_RESOURCE_FLAG_RAW)));
+
+       DEBUG("Reading WIM resource: %"PRIu64" @ +%"PRIu64" "
+             "from %"PRIu64" @ +%"PRIu64" (readflags 0x%08x, resflags 0x%02x%s)",
+             size, offset,
+             lte->resource_entry.original_size, lte->resource_entry.offset,
+             flags, lte->resource_entry.flags,
+             (lte->is_pipable ? ", pipable" : ""));
+
+       if ((flags & WIMLIB_READ_RESOURCE_FLAG_RAW_FULL) ||
+           !resource_is_compressed(&lte->resource_entry))
        {
-               ret = read_compressed_resource(in_fd,
-                                              lte->resource_entry.size,
-                                              lte->resource_entry.original_size,
-                                              lte->resource_entry.offset,
-                                              wim->compression_type,
-                                              size,
-                                              offset,
-                                              cb,
-                                              ctx_or_buf);
-       } else {
+               /* Reading raw resource contents or reading uncompressed
+                * resource.  */
+               wimlib_assert(offset + size <= lte->resource_entry.size);
                offset += lte->resource_entry.offset;
-               if (cb) {
+               if (flags & WIMLIB_READ_RESOURCE_FLAG_SEEK_ONLY) {
+                       if (lte->resource_entry.size != 0) {
+                               u8 dummy;
+                               ret = full_pread(in_fd, &dummy, 1,
+                                                offset + lte->resource_entry.size - 1);
+                               if (ret)
+                                       goto read_error;
+                       }
+               } else if (cb) {
                        /* Send data to callback function */
                        u8 buf[min(WIM_CHUNK_SIZE, size)];
                        while (size) {
-                               size_t bytes_to_read = min(WIM_CHUNK_SIZE, size);
-                               size_t bytes_read = full_pread(in_fd, buf,
-                                                              bytes_to_read, offset);
-                               if (bytes_read != bytes_to_read)
+                               size_t bytes_to_read = min(WIM_CHUNK_SIZE,
+                                                          size);
+                               ret = full_pread(in_fd, buf, bytes_to_read,
+                                                offset);
+                               if (ret)
                                        goto read_error;
-                               ret = cb(buf, bytes_read, ctx_or_buf);
+                               ret = cb(buf, bytes_to_read, ctx_or_buf);
                                if (ret)
                                        goto out;
-                               size -= bytes_read;
-                               offset += bytes_read;
+                               size -= bytes_to_read;
+                               offset += bytes_to_read;
                        }
                } else {
                        /* Send data directly to a buffer */
-                       if (full_pread(in_fd, ctx_or_buf, size, offset) != size)
+                       ret = full_pread(in_fd, ctx_or_buf, size, offset);
+                       if (ret)
                                goto read_error;
                }
                ret = 0;
+       } else if (lte->is_pipable && !filedes_is_seekable(in_fd)) {
+               /* Reading compressed, pipable resource from pipe.  */
+               ret = read_pipable_resource(lte, size, cb,
+                                           ctx_or_buf, flags, offset);
+       } else {
+               /* Reading compressed, possibly pipable resource from seekable
+                * file.  */
+               ret = read_compressed_resource(lte, size, cb,
+                                              ctx_or_buf, flags, offset);
        }
        goto out;
+
 read_error:
        ERROR_WITH_ERRNO("Error reading data from WIM");
-       ret = WIMLIB_ERR_READ;
 out:
-       if (ret) {
-               if (errno == 0)
-                       errno = EIO;
-       }
        return ret;
 }
 
@@ -530,41 +743,45 @@ read_file_on_disk_prefix(const struct wim_lookup_table_entry *lte,
 {
        const tchar *filename = lte->file_on_disk;
        int ret;
-       int fd;
-       size_t bytes_read;
+       struct filedes fd;
+       int raw_fd;
+
+       DEBUG("Reading %"PRIu64" bytes from \"%"TS"\"",
+             size, lte->file_on_disk);
 
-       fd = open(filename, O_RDONLY);
-       if (fd < 0) {
+       raw_fd = open(filename, O_RDONLY);
+       if (raw_fd < 0) {
                ERROR_WITH_ERRNO("Can't open \"%"TS"\"", filename);
                return WIMLIB_ERR_OPEN;
        }
+       filedes_init(&fd, raw_fd);
        if (cb) {
                /* Send data to callback function */
                u8 buf[min(WIM_CHUNK_SIZE, size)];
                size_t bytes_to_read;
                while (size) {
                        bytes_to_read = min(WIM_CHUNK_SIZE, size);
-                       bytes_read = full_read(fd, buf, bytes_to_read);
-                       if (bytes_read != bytes_to_read)
+                       ret = full_read(&fd, buf, bytes_to_read);
+                       if (ret)
                                goto read_error;
-                       ret = cb(buf, bytes_read, ctx_or_buf);
+                       ret = cb(buf, bytes_to_read, ctx_or_buf);
                        if (ret)
                                goto out_close;
-                       size -= bytes_read;
+                       size -= bytes_to_read;
                }
        } else {
                /* Send data directly to a buffer */
-               bytes_read = full_read(fd, ctx_or_buf, size);
-               if (bytes_read != size)
+               ret = full_read(&fd, ctx_or_buf, size);
+               if (ret)
                        goto read_error;
        }
        ret = 0;
        goto out_close;
+
 read_error:
        ERROR_WITH_ERRNO("Error reading \"%"TS"\"", filename);
-       ret = WIMLIB_ERR_READ;
 out_close:
-       close(fd);
+       filedes_close(&fd);
        return ret;
 }
 #endif /* !__WIN32__ */
@@ -599,6 +816,8 @@ typedef int (*read_resource_prefix_handler_t)(const struct wim_lookup_table_entr
                                              int flags);
 
 /*
+ * read_resource_prefix()-
+ *
  * Read the first @size bytes from a generic "resource", which may be located in
  * the WIM (compressed or uncompressed), in an external file, or directly in an
  * in-memory buffer.
@@ -610,10 +829,8 @@ typedef int (*read_resource_prefix_handler_t)(const struct wim_lookup_table_entr
  * When using a callback function, it is called with chunks up to 32768 bytes in
  * size until the resource is exhausted.
  *
- * If the resource is located in a WIM file, @flags can be:
- *   * WIMLIB_RESOURCE_FLAG_RAW if the raw compressed data is to be supplied
- *     instead of the uncompressed data.
- * Otherwise, the @flags are ignored.
+ * If the resource is located in a WIM file, @flags can be set as documented in
+ * read_partial_wim_resource().  Otherwise @flags are ignored.
  */
 int
 read_resource_prefix(const struct wim_lookup_table_entry *lte,
@@ -622,7 +839,9 @@ read_resource_prefix(const struct wim_lookup_table_entry *lte,
 {
        static const read_resource_prefix_handler_t handlers[] = {
                [RESOURCE_IN_WIM]             = read_wim_resource_prefix,
-       #ifndef __WIN32__
+       #ifdef __WIN32__
+               [RESOURCE_IN_FILE_ON_DISK]    = read_win32_file_prefix,
+       #else
                [RESOURCE_IN_FILE_ON_DISK]    = read_file_on_disk_prefix,
        #endif
                [RESOURCE_IN_ATTACHED_BUFFER] = read_buffer_prefix,
@@ -633,7 +852,6 @@ read_resource_prefix(const struct wim_lookup_table_entry *lte,
                [RESOURCE_IN_NTFS_VOLUME]     = read_ntfs_file_prefix,
        #endif
        #ifdef __WIN32__
-               [RESOURCE_WIN32]              = read_win32_file_prefix,
                [RESOURCE_WIN32_ENCRYPTED]    = read_win32_encrypted_file_prefix,
        #endif
        };
@@ -649,6 +867,56 @@ read_full_resource_into_buf(const struct wim_lookup_table_entry *lte,
        return read_resource_prefix(lte, wim_resource_size(lte), NULL, buf, 0);
 }
 
+int
+read_full_resource_into_alloc_buf(const struct wim_lookup_table_entry *lte,
+                                 void **buf_ret)
+{
+       int ret;
+       void *buf;
+
+       if ((size_t)lte->resource_entry.original_size !=
+           lte->resource_entry.original_size)
+       {
+               ERROR("Can't read %"PRIu64" byte resource into "
+                     "memory", lte->resource_entry.original_size);
+               return WIMLIB_ERR_NOMEM;
+       }
+
+       buf = MALLOC(lte->resource_entry.original_size);
+       if (!buf)
+               return WIMLIB_ERR_NOMEM;
+
+       ret = read_full_resource_into_buf(lte, buf);
+       if (ret) {
+               FREE(buf);
+               return ret;
+       }
+
+       *buf_ret = buf;
+       return 0;
+}
+
+int
+res_entry_to_data(const struct resource_entry *res_entry,
+                 WIMStruct *wim, void **buf_ret)
+{
+       int ret;
+       struct wim_lookup_table_entry *lte;
+
+       lte = new_lookup_table_entry();
+       if (!lte)
+               return WIMLIB_ERR_NOMEM;
+
+       copy_resource_entry(&lte->resource_entry, res_entry);
+       lte->unhashed = 1;
+       lte->part_number = wim->hdr.part_number;
+       lte_init_wim(lte, wim);
+
+       ret = read_full_resource_into_alloc_buf(lte, buf_ret);
+       free_lookup_table_entry(lte);
+       return ret;
+}
+
 struct extract_ctx {
        SHA_CTX sha_ctx;
        consume_data_callback_t extract_chunk;
@@ -712,21 +980,18 @@ extract_wim_resource(const struct wim_lookup_table_entry *lte,
 static int
 extract_wim_chunk_to_fd(const void *buf, size_t len, void *_fd_p)
 {
-       int fd = *(int*)_fd_p;
-       ssize_t ret = full_write(fd, buf, len);
-       if (ret < len) {
+       struct filedes *fd = _fd_p;
+       int ret = full_write(fd, buf, len);
+       if (ret)
                ERROR_WITH_ERRNO("Error writing to file descriptor");
-               return WIMLIB_ERR_WRITE;
-       } else {
-               return 0;
-       }
+       return ret;
 }
 
 int
 extract_wim_resource_to_fd(const struct wim_lookup_table_entry *lte,
-                          int fd, u64 size)
+                          struct filedes *fd, u64 size)
 {
-       return extract_wim_resource(lte, size, extract_wim_chunk_to_fd, &fd);
+       return extract_wim_resource(lte, size, extract_wim_chunk_to_fd, fd);
 }
 
 
@@ -752,28 +1017,61 @@ sha1_resource(struct wim_lookup_table_entry *lte)
        return ret;
 }
 
-/*
- * Copies the file resource specified by the lookup table entry @lte from the
- * input WIM to the output WIM that has its output file descriptor given by
- * ((WIMStruct*)_wim)->out_fd.
- *
- * The output_resource_entry, out_refcnt, and part_number fields of @lte are
- * updated.
- *
- * (This function is confusing and should be refactored somehow.)
- */
-int
-copy_resource(struct wim_lookup_table_entry *lte, void *_wim)
+/* Translates a WIM resource entry from the on-disk format to an in-memory
+ * format. */
+void
+get_resource_entry(const struct resource_entry_disk *disk_entry,
+                  struct resource_entry *entry)
 {
-       WIMStruct *wim = _wim;
-       int ret;
+       /* Note: disk_entry may not be 8 byte aligned--- in that case, the
+        * offset and original_size members will be unaligned.  (This should be
+        * okay since `struct resource_entry_disk' is declared as packed.) */
+
+       /* Read the size and flags into a bitfield portably... */
+       entry->size = (((u64)disk_entry->size[0] <<  0) |
+                      ((u64)disk_entry->size[1] <<  8) |
+                      ((u64)disk_entry->size[2] << 16) |
+                      ((u64)disk_entry->size[3] << 24) |
+                      ((u64)disk_entry->size[4] << 32) |
+                      ((u64)disk_entry->size[5] << 40) |
+                      ((u64)disk_entry->size[6] << 48));
+       entry->flags = disk_entry->flags;
+       entry->offset = le64_to_cpu(disk_entry->offset);
+       entry->original_size = le64_to_cpu(disk_entry->original_size);
 
-       ret = write_wim_resource(lte, wim->out_fd,
-                                wim_resource_compression_type(lte),
-                                &lte->output_resource_entry, 0);
-       if (ret == 0) {
-               lte->out_refcnt = lte->refcnt;
-               lte->part_number = wim->hdr.part_number;
+       /* offset and original_size are truncated to 62 bits to avoid possible
+        * overflows, when converting to a signed 64-bit integer (off_t) or when
+        * adding size or original_size.  This is okay since no one would ever
+        * actually have a WIM bigger than 4611686018427387903 bytes... */
+       if (entry->offset & 0xc000000000000000ULL) {
+               WARNING("Truncating offset in resource entry");
+               entry->offset &= 0x3fffffffffffffffULL;
        }
-       return ret;
+       if (entry->original_size & 0xc000000000000000ULL) {
+               WARNING("Truncating original_size in resource entry");
+               entry->original_size &= 0x3fffffffffffffffULL;
+       }
+}
+
+/* Translates a WIM resource entry from an in-memory format into the on-disk
+ * format. */
+void
+put_resource_entry(const struct resource_entry *entry,
+                  struct resource_entry_disk *disk_entry)
+{
+       /* Note: disk_entry may not be 8 byte aligned--- in that case, the
+        * offset and original_size members will be unaligned.  (This should be
+        * okay since `struct resource_entry_disk' is declared as packed.) */
+       u64 size = entry->size;
+
+       disk_entry->size[0] = size >>  0;
+       disk_entry->size[1] = size >>  8;
+       disk_entry->size[2] = size >> 16;
+       disk_entry->size[3] = size >> 24;
+       disk_entry->size[4] = size >> 32;
+       disk_entry->size[5] = size >> 40;
+       disk_entry->size[6] = size >> 48;
+       disk_entry->flags = entry->flags;
+       disk_entry->offset = cpu_to_le64(entry->offset);
+       disk_entry->original_size = cpu_to_le64(entry->original_size);
 }
index 788426c25dd7446d8afefb4a903ae3b2e01f9538..6ed83cd7e952a75cc50f13b091414f34cb444506 100644 (file)
@@ -181,9 +181,10 @@ new_wim_security_data(void)
  * Note: There is no `offset' argument because the security data is located at
  * the beginning of the metadata resource.
  *
- * Possible errors include:
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
+ *     WIMLIB_ERR_INVALID_METADATA_RESOURCE
  *     WIMLIB_ERR_NOMEM
- *     WIMLIB_ERR_INVALID_SECURITY_DATA
  */
 int
 read_wim_security_data(const u8 metadata_resource[], size_t metadata_resource_len,
@@ -291,7 +292,7 @@ out_align_total_length:
        goto out;
 out_invalid_sd:
        ERROR("WIM security data is invalid!");
-       ret = WIMLIB_ERR_INVALID_SECURITY_DATA;
+       ret = WIMLIB_ERR_INVALID_METADATA_RESOURCE;
        goto out_free_sd;
 out_of_memory:
        ERROR("Out of memory while reading WIM security data!");
index 5174625033c1d299dce7a93a5ad9b084425993e3..afb4365cd7fec7f4e24c832518319e90dbe4aac8 100644 (file)
 #endif
 
 #include "wimlib.h"
-#include "wimlib/endianness.h"
 #include "wimlib/error.h"
-#include "wimlib/file_io.h"
+#include "wimlib/list.h"
 #include "wimlib/lookup_table.h"
 #include "wimlib/metadata.h"
-#include "wimlib/types.h"
+#include "wimlib/resource.h"
+#include "wimlib/wim.h"
 #include "wimlib/write.h"
-#include "wimlib/list.h"
 
-#include <fcntl.h> /* for open() */
-#include <unistd.h> /* for close() */
+#ifdef HAVE_ALLOCA_H
+#  include <alloca.h>
+#else
+#  include <stdlib.h>
+#endif
 
-struct split_args {
-       WIMStruct *wim;
-       tchar *swm_base_name;
-       size_t swm_base_name_len;
-       const tchar *swm_suffix;
-       struct list_head lte_list;
-       int cur_part_number;
-       int write_flags;
-       s64 size_remaining;
-       size_t part_size;
-       wimlib_progress_func_t progress_func;
-       union wimlib_progress_info progress;
+struct swm_part_info {
+       struct list_head stream_list;
+       u64 size;
 };
 
-static int
-finish_swm(WIMStruct *wim, struct list_head *lte_list,
-          int write_flags, wimlib_progress_func_t progress_func)
+static void
+copy_part_info(struct swm_part_info *dst, struct swm_part_info *src)
 {
-       int ret;
-
-       ret = write_lookup_table_from_stream_list(lte_list, wim->out_fd,
-                                                 &wim->hdr.lookup_table_res_entry);
-       if (ret)
-               return ret;
-       return finish_write(wim, WIMLIB_ALL_IMAGES,
-                           write_flags | WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE,
-                           progress_func);
+       list_transfer(&src->stream_list, &dst->stream_list);
+       dst->size = src->size;
 }
 
+struct swm_info {
+       struct swm_part_info *parts;
+       unsigned num_parts;
+       unsigned num_alloc_parts;
+       u64 total_bytes;
+       u64 max_part_size;
+};
+
 static int
-copy_resource_to_swm(struct wim_lookup_table_entry *lte, void *_args)
+write_split_wim(WIMStruct *orig_wim, const tchar *swm_name,
+               struct swm_info *swm_info, int write_flags,
+               wimlib_progress_func_t progress_func)
 {
-       struct split_args *args = (struct split_args*)_args;
-       WIMStruct *wim = args->wim;
-       int ret;
-
-       if (args->size_remaining < 0 ||
-                       (u64)args->size_remaining < lte->resource_entry.size) {
+       size_t swm_name_len;
+       tchar *swm_name_buf;
+       const tchar *dot;
+       tchar *swm_suffix;
+       size_t swm_base_name_len;
 
-               /* No space for this resource.  Finish the previous swm and
-                * start a new one. */
+       union wimlib_progress_info progress;
+       unsigned part_number;
+       int ret;
+       u8 guid[WIMLIB_GUID_LEN];
 
-               ret = finish_swm(wim, &args->lte_list, args->write_flags,
-                                args->progress_func);
-               if (ret)
-                       return ret;
+       swm_name_len = tstrlen(swm_name);
+       swm_name_buf = alloca((swm_name_len + 20) * sizeof(tchar));
+       tstrcpy(swm_name_buf, swm_name);
+       dot = tstrchr(swm_name_buf, T('.'));
+       if (dot) {
+               swm_base_name_len = dot - swm_name_buf;
+               swm_suffix = alloca((tstrlen(dot) + 1) * sizeof(tchar));
+               tstrcpy(swm_suffix, dot);
+       } else {
+               swm_base_name_len = swm_name_len;
+               swm_suffix = alloca(1 * sizeof(tchar));
+               swm_suffix[0] = T('\0');
+       }
 
-               if (args->progress_func) {
-                       args->progress_func(WIMLIB_PROGRESS_MSG_SPLIT_END_PART,
-                                           &args->progress);
-               }
+       progress.split.completed_bytes = 0;
+       progress.split.total_bytes = 0;
+       for (part_number = 1; part_number <= swm_info->num_parts; part_number++)
+               progress.split.total_bytes += swm_info->parts[part_number - 1].size;
+       progress.split.total_parts = swm_info->num_parts;
+       progress.split.part_name = swm_name_buf;
 
-               INIT_LIST_HEAD(&args->lte_list);
-               args->cur_part_number++;
+       randomize_byte_array(guid, WIMLIB_GUID_LEN);
 
-               tsprintf(args->swm_base_name + args->swm_base_name_len, T("%d%"TS),
-                        args->cur_part_number, args->swm_suffix);
+       for (part_number = 1; part_number <= swm_info->num_parts; part_number++) {
+               int part_write_flags;
 
-               wim->hdr.part_number = args->cur_part_number;
+               if (part_number != 1) {
+                       tsprintf(swm_name_buf + swm_base_name_len,
+                                T("%u%"TS), part_number, swm_suffix);
+               }
 
-               if (args->progress_func) {
-                       args->progress.split.cur_part_number = args->cur_part_number;
-                       args->progress_func(WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART,
-                                           &args->progress);
+               progress.split.cur_part_number = part_number;
+               if (progress_func) {
+                       progress_func(WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART,
+                                     &progress);
                }
 
-               ret = begin_write(wim, args->swm_base_name, args->write_flags);
+               part_write_flags = write_flags & WIMLIB_WRITE_MASK_PUBLIC;
+               part_write_flags |= WIMLIB_WRITE_FLAG_USE_EXISTING_TOTALBYTES;
+               if (part_number != 1)
+                       part_write_flags |= WIMLIB_WRITE_FLAG_NO_METADATA;
+
+               ret = write_wim_part(orig_wim,
+                                    swm_name_buf,
+                                    WIMLIB_ALL_IMAGES,
+                                    part_write_flags,
+                                    1,
+                                    NULL,
+                                    part_number,
+                                    swm_info->num_parts,
+                                    &swm_info->parts[part_number - 1].stream_list,
+                                    guid);
                if (ret)
                        return ret;
-               args->size_remaining = args->part_size;
+
+               progress.split.completed_bytes += swm_info->parts[part_number - 1].size;
+               if (progress_func) {
+                       progress_func(WIMLIB_PROGRESS_MSG_SPLIT_END_PART,
+                                     &progress);
+               }
        }
-       args->size_remaining -= lte->resource_entry.size;
-       args->progress.split.completed_bytes += lte->resource_entry.size;
-       list_add_tail(&lte->swm_stream_list, &args->lte_list);
-       return copy_resource(lte, wim);
+       return 0;
 }
 
-/* Splits the WIM file @wim into multiple parts prefixed by @swm_name with size
- * at most @part_size bytes. */
+static int
+add_stream_to_swm(struct wim_lookup_table_entry *lte, void *_swm_info)
+{
+       struct swm_info *swm_info = _swm_info;
+       u64 stream_size;
+
+       stream_size = lte->resource_entry.size;
+
+       /* - Start first part if no parts have been started so far;
+        * - Start next part if adding this stream exceeds maximum part size,
+        *   UNLESS the stream is metadata or if no streams at all have been
+        *   added to the current part.
+        */
+       if (swm_info->num_parts == 0 ||
+           ((swm_info->parts[swm_info->num_parts - 1].size +
+                       stream_size >= swm_info->max_part_size)
+            && !((lte->resource_entry.flags & WIM_RESHDR_FLAG_METADATA) ||
+                  swm_info->parts[swm_info->num_parts - 1].size == 0)))
+       {
+               if (swm_info->num_parts == swm_info->num_alloc_parts) {
+                       struct swm_part_info *parts;
+                       size_t num_alloc_parts = swm_info->num_alloc_parts;
+
+                       num_alloc_parts += 8;
+                       parts = MALLOC(num_alloc_parts * sizeof(parts[0]));
+                       if (!parts)
+                               return WIMLIB_ERR_NOMEM;
+
+                       for (unsigned i = 0; i < swm_info->num_parts; i++)
+                               copy_part_info(&parts[i], &swm_info->parts[i]);
+
+                       FREE(swm_info->parts);
+                       swm_info->parts = parts;
+                       swm_info->num_alloc_parts = num_alloc_parts;
+               }
+               swm_info->num_parts++;
+               INIT_LIST_HEAD(&swm_info->parts[swm_info->num_parts - 1].stream_list);
+               swm_info->parts[swm_info->num_parts - 1].size = 0;
+       }
+       swm_info->parts[swm_info->num_parts - 1].size += stream_size;
+       if (!(lte->resource_entry.flags & WIM_RESHDR_FLAG_METADATA)) {
+               list_add_tail(&lte->write_streams_list,
+                             &swm_info->parts[swm_info->num_parts - 1].stream_list);
+       }
+       swm_info->total_bytes += stream_size;
+       return 0;
+}
+
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_split(WIMStruct *wim, const tchar *swm_name,
-            size_t part_size, int write_flags,
+            u64 part_size, int write_flags,
             wimlib_progress_func_t progress_func)
 {
+       struct swm_info swm_info;
+       unsigned i;
        int ret;
-       struct wim_header hdr_save;
-       struct split_args args;
-       const tchar *swm_suffix;
-       size_t swm_name_len;
-       size_t swm_base_name_len;
 
-       if (!swm_name || part_size == 0)
+       if (swm_name == NULL || swm_name[0] == T('\0') || part_size == 0)
                return WIMLIB_ERR_INVALID_PARAM;
 
        if (wim->hdr.total_parts != 1)
                return WIMLIB_ERR_SPLIT_UNSUPPORTED;
 
-       write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
-
-       swm_name_len = tstrlen(swm_name);
-       tchar swm_base_name[swm_name_len + 20];
-
-       memcpy(&hdr_save, &wim->hdr, sizeof(struct wim_header));
-       wim->hdr.flags |= WIM_HDR_FLAG_SPANNED;
-       wim->hdr.boot_idx = 0;
-       randomize_byte_array(wim->hdr.guid, WIM_GID_LEN);
-       ret = begin_write(wim, swm_name, write_flags);
-       if (ret)
-               goto out;
-
-       tmemcpy(swm_base_name, swm_name, swm_name_len + 1);
-
-       swm_suffix = tstrchr(swm_name, T('.'));
-       if (swm_suffix) {
-               swm_base_name_len = swm_suffix - swm_name;
-       } else {
-               swm_base_name_len = swm_name_len;
-               swm_base_name[ARRAY_LEN(swm_base_name) - 1] = T('\0');
-               swm_suffix = &swm_base_name[ARRAY_LEN(swm_base_name) - 1];
-       }
-
-       args.wim                              = wim;
-       args.swm_base_name                  = swm_base_name;
-       args.swm_base_name_len              = swm_base_name_len;
-       args.swm_suffix                     = swm_suffix;
-       INIT_LIST_HEAD(&args.lte_list);
-       args.cur_part_number                = 1;
-       args.write_flags                    = write_flags;
-       args.size_remaining                 = part_size;
-       args.part_size                      = part_size;
-       args.progress_func                  = progress_func;
-       args.progress.split.total_bytes     = lookup_table_total_stream_size(wim->lookup_table);
-       args.progress.split.cur_part_number = 1;
-       args.progress.split.completed_bytes = 0;
-       args.progress.split.part_name       = swm_base_name;
-
-       if (progress_func) {
-               progress_func(WIMLIB_PROGRESS_MSG_SPLIT_BEGIN_PART,
-                             &args.progress);
-       }
+       memset(&swm_info, 0, sizeof(swm_info));
+       swm_info.max_part_size = part_size;
 
-       for (int i = 0; i < wim->hdr.image_count; i++) {
-               struct wim_lookup_table_entry *metadata_lte;
-               metadata_lte = wim->image_metadata[i]->metadata_lte;
-               ret = copy_resource(metadata_lte, wim);
+       for (i = 0; i < wim->hdr.image_count; i++) {
+               ret = add_stream_to_swm(wim->image_metadata[i]->metadata_lte,
+                                       &swm_info);
                if (ret)
-                       goto out;
-               args.size_remaining -= metadata_lte->resource_entry.size;
-               args.progress.split.completed_bytes += metadata_lte->resource_entry.size;
-               /* Careful: The metadata lookup table entries must be added in
-                * order of the images. */
-               list_add_tail(&metadata_lte->swm_stream_list, &args.lte_list);
+                       goto out_free_swm_info;
        }
 
        ret = for_lookup_table_entry_pos_sorted(wim->lookup_table,
-                                               copy_resource_to_swm,
-                                               &args);
+                                               add_stream_to_swm,
+                                               &swm_info);
        if (ret)
-               goto out;
-
-       ret = finish_swm(wim, &args.lte_list, write_flags, progress_func);
-       if (ret)
-               goto out;
-
-       if (progress_func) {
-               progress_func(WIMLIB_PROGRESS_MSG_SPLIT_END_PART,
-                             &args.progress);
-       }
-
-       /* The swms are all ready now, except the total_parts and part_number
-        * fields in their headers are wrong (since we don't know the total
-        * parts until they are all written).  Fix them. */
-       int total_parts = args.cur_part_number;
-       for (int i = 1; i <= total_parts; i++) {
-               const tchar *part_name;
-               int part_fd;
-               int ret2;
-
-               if (i == 1) {
-                       part_name = swm_name;
-               } else {
-                       tsprintf(swm_base_name + swm_base_name_len, T("%d%"TS),
-                                i, swm_suffix);
-                       part_name = swm_base_name;
-               }
+               goto out_free_swm_info;
 
-               part_fd = topen(part_name, O_WRONLY | O_BINARY);
-               if (part_fd == -1) {
-                       ERROR_WITH_ERRNO("Failed to open `%"TS"'", part_name);
-                       ret = WIMLIB_ERR_OPEN;
-                       goto out;
-               }
-
-               ret = write_header_part_data(i, total_parts, part_fd);
-               ret2 = close(part_fd);
-               if (ret == 0 && ret2 != 0)
-                       ret = WIMLIB_ERR_WRITE;
-               if (ret) {
-                       ERROR_WITH_ERRNO("Error updating header of `%"TS"'",
-                                        part_name);
-                       goto out;
-               }
-       }
-       ret = 0;
-out:
-       close_wim_writable(wim);
-       memcpy(&wim->hdr, &hdr_save, sizeof(struct wim_header));
+       ret = write_split_wim(wim, swm_name, &swm_info, write_flags,
+                             progress_func);
+out_free_swm_info:
+       FREE(swm_info.parts);
        return ret;
 }
index b1d914827beec90c9ac3f6e69067cd19161eaaa9..e27accff294769c9e95b2114ce0b963d81e3a9f7 100644 (file)
 #  include "config.h"
 #endif
 
-#include <dirent.h>
+#include "wimlib/apply.h"
+#include "wimlib/error.h"
+#include "wimlib/lookup_table.h"
+#include "wimlib/resource.h"
+#include "wimlib/timestamp.h"
+
 #include <errno.h>
 #include <fcntl.h>
-#include <string.h>
+#include <limits.h>
 #include <sys/stat.h>
-#include <sys/time.h>
+#include <sys/types.h>
 #include <unistd.h>
+
 #ifdef HAVE_UTIME_H
 #  include <utime.h>
 #endif
 
-#include "wimlib/apply.h"
-#include "wimlib/error.h"
-#include "wimlib/lookup_table.h"
-#include "wimlib/reparse.h"
-#include "wimlib/timestamp.h"
-
-/* Returns the number of components of @path.  */
-static unsigned
-get_num_path_components(const char *path)
-{
-       unsigned num_components = 0;
-       while (*path) {
-               while (*path == '/')
-                       path++;
-               if (*path)
-                       num_components++;
-               while (*path && *path != '/')
-                       path++;
-       }
-       return num_components;
-}
-
-static const char *
-path_next_part(const char *path)
-{
-       while (*path && *path != '/')
-               path++;
-       while (*path && *path == '/')
-               path++;
-       return path;
-}
-
 static int
-unix_extract_regular_file_linked(struct wim_dentry *dentry,
-                                const char *output_path,
-                                struct apply_args *args,
-                                struct wim_lookup_table_entry *lte)
+unix_start_extract(const char *target, struct apply_ctx *ctx)
 {
-       /* This mode overrides the normal hard-link extraction and
-        * instead either symlinks or hardlinks *all* identical files in
-        * the WIM, even if they are in a different image (in the case
-        * of a multi-image extraction) */
-
-       if (args->extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) {
-               if (link(lte->extracted_file, output_path) != 0) {
-                       ERROR_WITH_ERRNO("Failed to hard link "
-                                        "`%s' to `%s'",
-                                        output_path, lte->extracted_file);
-                       return WIMLIB_ERR_LINK;
-               }
-       } else {
-               int num_path_components;
-               int num_output_dir_path_components;
-               size_t extracted_file_len;
-               char *p;
-               const char *p2;
-               size_t i;
-               const struct wim_dentry *d;
-
-               num_path_components = 0;
-               for (d = dentry; d != args->extract_root; d = d->parent)
-                       num_path_components++;
-               wimlib_assert(num_path_components > 0);
-               num_path_components--;
-               num_output_dir_path_components = get_num_path_components(args->target);
-
-               if (args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) {
-                       num_path_components++;
-                       num_output_dir_path_components--;
-               }
-               extracted_file_len = strlen(lte->extracted_file);
-
-               char buf[extracted_file_len + 3 * num_path_components + 1];
-               p = &buf[0];
-
-               for (i = 0; i < num_path_components; i++) {
-                       *p++ = '.';
-                       *p++ = '.';
-                       *p++ = '/';
-               }
-               p2 = lte->extracted_file;
-               while (*p2 == '/')
-                       p2++;
-               while (num_output_dir_path_components > 0) {
-                       p2 = path_next_part(p2);
-                       num_output_dir_path_components--;
-               }
-               strcpy(p, p2);
-               if (symlink(buf, output_path) != 0) {
-                       ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'",
-                                        buf, lte->extracted_file);
-                       return WIMLIB_ERR_LINK;
-               }
-       }
+       ctx->supported_features.hard_links = 1;
+       ctx->supported_features.symlink_reparse_points = 1;
+       ctx->supported_features.unix_data = 1;
        return 0;
 }
 
 static int
-symlink_apply_unix_data(const char *link,
-                       const struct wimlib_unix_data *unix_data)
+unix_create_file(const char *path, struct apply_ctx *ctx)
 {
-       if (lchown(link, unix_data->uid, unix_data->gid)) {
-               if (errno == EPERM) {
-                       /* Ignore */
-                       WARNING_WITH_ERRNO("failed to set symlink UNIX "
-                                          "owner/group on \"%s\"", link);
-               } else {
-                       ERROR_WITH_ERRNO("failed to set symlink UNIX "
-                                        "owner/group on \"%s\"", link);
-                       return WIMLIB_ERR_INVALID_DENTRY;
-               }
-       }
+       int fd = open(path, O_TRUNC | O_CREAT | O_WRONLY, 0644);
+       if (fd < 0)
+               return WIMLIB_ERR_OPEN;
+       close(fd);
        return 0;
 }
 
 static int
-fd_apply_unix_data(int fd, const char *path,
-                  const struct wimlib_unix_data *unix_data,
-                  int extract_flags)
+unix_create_directory(const tchar *path, struct apply_ctx *ctx)
 {
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
-               return 0;
-
-       if (fchown(fd, unix_data->uid, unix_data->gid)) {
-               if (errno == EPERM &&
-                   !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
-               {
-                       WARNING_WITH_ERRNO("failed to set file UNIX "
-                                          "owner/group on \"%s\"", path);
-               } else {
-                       ERROR_WITH_ERRNO("failed to set file UNIX "
-                                        "owner/group on \"%s\"", path);
-                       return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT :
-                               WIMLIB_ERR_WRITE;
-               }
-       }
+       struct stat stbuf;
 
-       if (fchmod(fd, unix_data->mode)) {
-               if (errno == EPERM &&
-                   !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
-               {
-                       WARNING_WITH_ERRNO("failed to set UNIX file mode "
-                                          "on \"%s\"", path);
-               } else {
-                       ERROR_WITH_ERRNO("failed to set UNIX file mode "
-                                        "on \"%s\"", path);
-                       return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT :
-                               WIMLIB_ERR_WRITE;
-               }
+       if (mkdir(path, 0755)) {
+               if (errno != EEXIST)
+                       return WIMLIB_ERR_MKDIR;
+               if (lstat(path, &stbuf))
+                       return WIMLIB_ERR_STAT;
+               errno = EEXIST;
+               if (!S_ISDIR(stbuf.st_mode))
+                       return WIMLIB_ERR_NOTDIR;
        }
        return 0;
 }
 
 static int
-dir_apply_unix_data(const char *dir, const struct wimlib_unix_data *unix_data,
-                   int extract_flags)
-{
-       int dfd = open(dir, O_RDONLY);
-       int ret;
-       if (dfd >= 0) {
-               ret = fd_apply_unix_data(dfd, dir, unix_data, extract_flags);
-               if (close(dfd) && ret == 0) {
-                       ERROR_WITH_ERRNO("can't close directory `%s'", dir);
-                       ret = WIMLIB_ERR_WRITE;
-               }
-       } else {
-               ERROR_WITH_ERRNO("can't open directory `%s'", dir);
-               ret = WIMLIB_ERR_OPENDIR;
-       }
-       return ret;
-}
-
-static int
-unix_extract_regular_file_unlinked(struct wim_dentry *dentry,
-                                  struct apply_args *args,
-                                  const char *output_path,
-                                  struct wim_lookup_table_entry *lte)
+unix_create_hardlink(const tchar *oldpath, const tchar *newpath,
+                    struct apply_ctx *ctx)
 {
-       /* Normal mode of extraction.  Regular files and hard links are
-        * extracted in the way that they appear in the WIM. */
-
-       int out_fd;
-       int ret;
-       struct wim_inode *inode = dentry->d_inode;
-
-       if (!((args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE)
-               && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
-                                    WIMLIB_EXTRACT_FLAG_HARDLINK))))
-       {
-               /* If the dentry is part of a hard link set of at least 2
-                * dentries and one of the other dentries has already been
-                * extracted, make a hard link to the file corresponding to this
-                * already-extracted directory.  Otherwise, extract the file and
-                * set the inode->i_extracted_file field so that other dentries
-                * in the hard link group can link to it. */
-               if (inode->i_nlink > 1) {
-                       if (inode->i_extracted_file) {
-                               DEBUG("Extracting hard link `%s' => `%s'",
-                                     output_path, inode->i_extracted_file);
-                               if (link(inode->i_extracted_file, output_path) != 0) {
-                                       ERROR_WITH_ERRNO("Failed to hard link "
-                                                        "`%s' to `%s'",
-                                                        output_path,
-                                                        inode->i_extracted_file);
-                                       return WIMLIB_ERR_LINK;
-                               }
-                               return 0;
-                       }
-                       FREE(inode->i_extracted_file);
-                       inode->i_extracted_file = STRDUP(output_path);
-                       if (!inode->i_extracted_file) {
-                               ERROR("Failed to allocate memory for filename");
-                               return WIMLIB_ERR_NOMEM;
-                       }
-               }
-       }
-
-       /* Extract the contents of the file to @output_path. */
-
-       out_fd = open(output_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
-       if (out_fd == -1) {
-               ERROR_WITH_ERRNO("Failed to open the file `%s' for writing",
-                                output_path);
-               return WIMLIB_ERR_OPEN;
-       }
-
-       if (!lte) {
-               /* Empty file with no lookup table entry */
-               DEBUG("Empty file `%s'.", output_path);
-               ret = 0;
-               goto out_extract_unix_data;
-       }
-
-       ret = extract_wim_resource_to_fd(lte, out_fd, wim_resource_size(lte));
-       if (ret) {
-               ERROR("Failed to extract resource to `%s'", output_path);
-               goto out;
-       }
-
-out_extract_unix_data:
-       if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
-               struct wimlib_unix_data unix_data;
-               ret = inode_get_unix_data(inode, &unix_data, NULL);
-               if (ret > 0)
-                       ;
-               else if (ret < 0)
-                       ret = 0;
-               else
-                       ret = fd_apply_unix_data(out_fd, output_path, &unix_data,
-                                                args->extract_flags);
-               if (ret)
-                       goto out;
-       }
-       if (lte)
-               args->progress.extract.completed_bytes += wim_resource_size(lte);
-out:
-       if (close(out_fd) != 0) {
-               ERROR_WITH_ERRNO("Failed to close file `%s'", output_path);
-               if (ret == 0)
-                       ret = WIMLIB_ERR_WRITE;
-       }
-       return ret;
-}
-
-static int
-unix_extract_regular_file(struct wim_dentry *dentry,
-                         struct apply_args *args,
-                         const char *output_path)
-{
-       struct wim_lookup_table_entry *lte;
-       const struct wim_inode *inode = dentry->d_inode;
-
-       lte = inode_unnamed_lte_resolved(inode);
-
-       if (lte && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK |
-                                          WIMLIB_EXTRACT_FLAG_HARDLINK)))
-       {
-               if (lte->extracted_file) {
-                       return unix_extract_regular_file_linked(dentry,
-                                                               output_path,
-                                                               args, lte);
-               } else {
-                       lte->extracted_file = STRDUP(output_path);
-                       if (!lte->extracted_file)
-                               return WIMLIB_ERR_NOMEM;
-               }
-       }
-       return unix_extract_regular_file_unlinked(dentry, args, output_path, lte);
+       if (link(oldpath, newpath))
+               return WIMLIB_ERR_LINK;
+       return 0;
 }
 
 static int
-unix_extract_symlink(struct wim_dentry *dentry,
-                    struct apply_args *args,
-                    const char *output_path)
+unix_create_symlink(const tchar *oldpath, const tchar *newpath,
+                   struct apply_ctx *ctx)
 {
-       char target[4096 + args->target_realpath_len];
-       char *fixed_target;
-       const struct wim_inode *inode = dentry->d_inode;
-
-       ssize_t ret = wim_inode_readlink(inode,
-                                        target + args->target_realpath_len,
-                                        sizeof(target) - args->target_realpath_len - 1);
-       struct wim_lookup_table_entry *lte;
-
-       if (ret <= 0) {
-               ERROR("Could not read the symbolic link from dentry `%s'",
-                     dentry_full_path(dentry));
-               return WIMLIB_ERR_INVALID_DENTRY;
-       }
-       target[args->target_realpath_len + ret] = '\0';
-       if (target[args->target_realpath_len] == '/' &&
-           args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX)
-       {
-               /* Fix absolute symbolic link target to point into the actual
-                * extraction destination */
-               memcpy(target, args->target_realpath,
-                      args->target_realpath_len);
-               fixed_target = target;
-       } else {
-               /* Keep same link target */
-               fixed_target = target + args->target_realpath_len;
-       }
-       ret = symlink(fixed_target, output_path);
-       if (ret) {
-               ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'",
-                                output_path, fixed_target);
+       if (symlink(oldpath, newpath))
                return WIMLIB_ERR_LINK;
-       }
-       if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
-               struct wimlib_unix_data unix_data;
-               ret = inode_get_unix_data(inode, &unix_data, NULL);
-               if (ret > 0)
-                       ;
-               else if (ret < 0)
-                       ret = 0;
-               else
-                       ret = symlink_apply_unix_data(output_path, &unix_data);
-               if (ret)
-                       return ret;
-       }
-       lte = inode_unnamed_lte_resolved(inode);
-       wimlib_assert(lte != NULL);
-       args->progress.extract.completed_bytes += wim_resource_size(lte);
        return 0;
 }
 
 static int
-unix_extract_directory(struct wim_dentry *dentry, const char *output_path,
-                      int extract_flags)
+unix_extract_unnamed_stream(const tchar *path,
+                           struct wim_lookup_table_entry *lte,
+                           struct apply_ctx *ctx)
 {
+       struct filedes fd;
+       int raw_fd;
        int ret;
-       struct stat stbuf;
 
-       ret = stat(output_path, &stbuf);
-       if (ret == 0) {
-               if (S_ISDIR(stbuf.st_mode)) {
-                       goto dir_exists;
-               } else {
-                       ERROR("\"%s\" is not a directory", output_path);
-                       return WIMLIB_ERR_MKDIR;
-               }
-       } else {
-               if (errno != ENOENT) {
-                       ERROR_WITH_ERRNO("Failed to stat \"%s\"", output_path);
-                       return WIMLIB_ERR_STAT;
-               }
-       }
-
-       if (mkdir(output_path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {
-               ERROR_WITH_ERRNO("Cannot create directory \"%s\"", output_path);
-               return WIMLIB_ERR_MKDIR;
-       }
-dir_exists:
-       ret = 0;
-       if (extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
-               struct wimlib_unix_data unix_data;
-               ret = inode_get_unix_data(dentry->d_inode, &unix_data, NULL);
-               if (ret > 0)
-                       ;
-               else if (ret < 0)
-                       ret = 0;
-               else
-                       ret = dir_apply_unix_data(output_path, &unix_data,
-                                                 extract_flags);
-       }
+       raw_fd = open(path, O_WRONLY | O_TRUNC);
+       if (raw_fd < 0)
+               return WIMLIB_ERR_OPEN;
+       filedes_init(&fd, raw_fd);
+       ret = extract_wim_resource_to_fd(lte, &fd, wim_resource_size(lte));
+       if (filedes_close(&fd) && !ret)
+               ret = WIMLIB_ERR_WRITE;
        return ret;
 }
 
-int
-unix_do_apply_dentry(const char *output_path, size_t output_path_len,
-                    struct wim_dentry *dentry, struct apply_args *args)
+static int
+unix_set_unix_data(const tchar *path, const struct wimlib_unix_data *data,
+                  struct apply_ctx *ctx)
 {
-       const struct wim_inode *inode = dentry->d_inode;
+       struct stat stbuf;
 
-       if (inode_is_symlink(inode))
-               return unix_extract_symlink(dentry, args, output_path);
-       else if (inode_is_directory(inode))
-               return unix_extract_directory(dentry, output_path, args->extract_flags);
-       else
-               return unix_extract_regular_file(dentry, args, output_path);
+       if (lstat(path, &stbuf))
+               return WIMLIB_ERR_STAT;
+       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;
+       return 0;
 }
 
-int
-unix_do_apply_dentry_timestamps(const char *output_path,
-                               size_t output_path_len,
-                               struct wim_dentry *dentry,
-                               struct apply_args *args)
+static int
+unix_set_timestamps(const tchar *path, u64 creation_time, u64 last_write_time,
+                   u64 last_access_time, struct apply_ctx *ctx)
 {
        int ret;
-       const struct wim_inode *inode = dentry->d_inode;
 
 #ifdef HAVE_UTIMENSAT
        /* Convert the WIM timestamps, which are accurate to 100 nanoseconds,
@@ -455,9 +145,9 @@ unix_do_apply_dentry_timestamps(const char *output_path,
         * to 1 nanosecond. */
 
        struct timespec ts[2];
-       ts[0] = wim_timestamp_to_timespec(inode->i_last_access_time);
-       ts[1] = wim_timestamp_to_timespec(inode->i_last_write_time);
-       ret = utimensat(AT_FDCWD, output_path, ts, AT_SYMLINK_NOFOLLOW);
+       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);
        if (ret)
                ret = errno;
 #else
@@ -471,9 +161,9 @@ unix_do_apply_dentry_timestamps(const char *output_path,
                 * 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(inode->i_last_access_time);
-               tv[1] = wim_timestamp_to_timeval(inode->i_last_write_time);
-               ret = lutimes(output_path, tv);
+               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
@@ -487,17 +177,33 @@ unix_do_apply_dentry_timestamps(const char *output_path,
                 * 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(inode->i_last_access_time);
-               buf.modtime = wim_timestamp_to_unix(inode->i_last_write_time);
-               ret = utime(output_path, &buf);
+               buf.actime = wim_timestamp_to_unix(last_access_time);
+               buf.modtime = wim_timestamp_to_unix(last_write_time);
+               ret = utime(path, &buf);
        #endif
        }
-       if (ret && args->num_utime_warnings < 10) {
-               WARNING_WITH_ERRNO("Failed to set timestamp on file `%s'",
-                                   output_path);
-               args->num_utime_warnings++;
-       }
+       if (ret)
+               return WIMLIB_ERR_SET_TIMESTAMPS;
        return 0;
 }
 
+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,
+};
+
 #endif /* !__WIN32__ */
index f1776ec93cfb192cf1f5064c7a9a839f73eae915..3f9e632eed259bbb407c336393ac3df6fc900766 100644 (file)
@@ -704,9 +704,7 @@ err:
        goto out;
 }
 
-/*
- * Entry point for making a series of updates to a WIM image.
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_update_image(WIMStruct *wim,
                    int image,
index 08bcb39b70f4037211fa19e88a6b0526144c158f..7a101f377ee62b594e5d802882c3dd36e0951561 100644 (file)
@@ -232,6 +232,15 @@ void wimlib_debug(const tchar *file, int line, const char *func,
        va_list va;
        tchar buf[tstrlen(file) + strlen(func) + 30];
 
+       static bool debug_enabled = false;
+       if (!debug_enabled) {
+               char *value = getenv("WIMLIB_DEBUG");
+               if (!value || strcmp(value, "0"))
+                       debug_enabled = true;
+               else
+                       return;
+       }
+
        tsprintf(buf, T("[%"TS" %d] %s(): "), file, line, func);
 
        va_start(va, format);
@@ -240,6 +249,7 @@ void wimlib_debug(const tchar *file, int line, const char *func,
 }
 #endif
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_set_print_errors(bool show_error_messages)
 {
@@ -259,8 +269,6 @@ static const tchar *error_strings[] = {
                = T("Success"),
        [WIMLIB_ERR_ALREADY_LOCKED]
                = T("The WIM is already locked for writing"),
-       [WIMLIB_ERR_COMPRESSED_LOOKUP_TABLE]
-               = T("Lookup table is compressed"),
        [WIMLIB_ERR_DECOMPRESSION]
                = T("Failed to decompress compressed data"),
        [WIMLIB_ERR_DELETE_STAGING_DIR]
@@ -280,8 +288,6 @@ static const tchar *error_strings[] = {
        [WIMLIB_ERR_IMAGE_COUNT]
                = T("Inconsistent image count among the metadata "
                        "resources, the WIM header, and/or the XML data"),
-       [WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT]
-               = T("User does not have sufficient privileges to correctly extract the data"),
        [WIMLIB_ERR_IMAGE_NAME_COLLISION]
                = T("Tried to add an image with a name that is already in use"),
        [WIMLIB_ERR_INTEGRITY]
@@ -294,10 +300,8 @@ static const tchar *error_strings[] = {
        [WIMLIB_ERR_INVALID_COMPRESSION_TYPE]
                = T("The WIM is compressed, but is not marked as having LZX or "
                        "XPRESS compression"),
-       [WIMLIB_ERR_INVALID_DENTRY]
-               = T("A directory entry in the WIM was invalid"),
-       [WIMLIB_ERR_INVALID_HEADER_SIZE]
-               = T("The WIM header was not 208 bytes"),
+       [WIMLIB_ERR_INVALID_HEADER]
+               = T("The WIM header was invalid"),
        [WIMLIB_ERR_INVALID_IMAGE]
                = T("Tried to select an image that does not exist in the WIM"),
        [WIMLIB_ERR_INVALID_INTEGRITY_TABLE]
@@ -312,14 +316,14 @@ static const tchar *error_strings[] = {
                = T("An invalid parameter was given"),
        [WIMLIB_ERR_INVALID_PART_NUMBER]
                = T("The part number or total parts of the WIM is invalid"),
+       [WIMLIB_ERR_INVALID_PIPABLE_WIM]
+               = T("The pipable WIM is invalid"),
        [WIMLIB_ERR_INVALID_REPARSE_DATA]
                = T("The reparse data of a reparse point was invalid"),
        [WIMLIB_ERR_INVALID_RESOURCE_HASH]
                = T("The SHA1 message digest of a WIM resource did not match the expected value"),
-       [WIMLIB_ERR_INVALID_RESOURCE_SIZE]
-               = T("A resource entry in the WIM has an invalid size"),
-       [WIMLIB_ERR_INVALID_SECURITY_DATA]
-               = T("The table of security descriptors in the WIM is invalid"),
+       [WIMLIB_ERR_INVALID_METADATA_RESOURCE]
+               = T("The metadata resource is invalid"),
        [WIMLIB_ERR_INVALID_UNMOUNT_MESSAGE]
                = T("The version of wimlib that has mounted a WIM image is incompatible with the "
                  "version being used to unmount it"),
@@ -353,6 +357,9 @@ static const tchar *error_strings[] = {
                    "correspond to a regular file"),
        [WIMLIB_ERR_NO_FILENAME]
                = T("The WIM is not identified with a filename"),
+       [WIMLIB_ERR_NOT_PIPABLE]
+               = T("The WIM was not captured such that it can be "
+                   "applied from a pipe"),
        [WIMLIB_ERR_NTFS_3G]
                = T("NTFS-3g encountered an error (check errno)"),
        [WIMLIB_ERR_OPEN]
@@ -373,6 +380,12 @@ static const tchar *error_strings[] = {
                = T("A file resource needed to complete the operation was missing from the WIM"),
        [WIMLIB_ERR_RESOURCE_ORDER]
                = T("The components of the WIM were arranged in an unexpected order"),
+       [WIMLIB_ERR_SET_ATTRIBUTES]
+               = T("Failed to set file attributes"),
+       [WIMLIB_ERR_SET_SECURITY]
+               = T("Failed to set file owner, group, or other permissions"),
+       [WIMLIB_ERR_SET_TIMESTAMPS]
+               = T("Failed to set file timestamps"),
        [WIMLIB_ERR_SPECIAL_FILE]
                = T("Encountered a special file that cannot be archived"),
        [WIMLIB_ERR_SPLIT_INVALID]
@@ -383,6 +396,8 @@ static const tchar *error_strings[] = {
                = T("Could not read the metadata for a file or directory"),
        [WIMLIB_ERR_TIMEOUT]
                = T("Timed out while waiting for a message to arrive from another process"),
+       [WIMLIB_ERR_UNEXPECTED_END_OF_FILE]
+               = T("Unexpectedly reached the end of the file"),
        [WIMLIB_ERR_UNICODE_STRING_NOT_REPRESENTABLE]
                = T("A Unicode string could not be represented in the current locale's encoding"),
        [WIMLIB_ERR_UNKNOWN_VERSION]
@@ -399,10 +414,11 @@ static const tchar *error_strings[] = {
                = T("The XML data of the WIM is invalid"),
 };
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI const tchar *
 wimlib_get_error_string(enum wimlib_error_code code)
 {
-       if (code < 0 || code >= ARRAY_LEN(error_strings))
+       if ((int)code < 0 || code >= ARRAY_LEN(error_strings))
                return NULL;
        else
                return error_strings[code];
@@ -418,8 +434,10 @@ static void *(*wimlib_realloc_func)(void *, size_t) = realloc;
 void *
 wimlib_malloc(size_t size)
 {
+       if (size == 0)
+               size = 1;
        void *ptr = (*wimlib_malloc_func)(size);
-       if (ptr == NULL && size != 0)
+       if (ptr == NULL)
                ERROR("memory exhausted");
        return ptr;
 }
@@ -433,8 +451,10 @@ wimlib_free_memory(void *ptr)
 void *
 wimlib_realloc(void *ptr, size_t size)
 {
+       if (size == 0)
+               size = 1;
        ptr = (*wimlib_realloc_func)(ptr, size);
-       if (ptr == NULL && size != 0)
+       if (ptr == NULL)
                ERROR("memory exhausted");
        return ptr;
 }
@@ -488,6 +508,7 @@ memdup(const void *mem, size_t size)
        return ptr;
 }
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_set_memory_allocator(void *(*malloc_func)(size_t),
                            void (*free_func)(void *),
index 3702ee7e995fa15af359f0a457c6dde174c5a7c3..10db163550dfe8cea3381ec7936a2d9a768ff376 100644 (file)
@@ -39,6 +39,9 @@
  * - Check to make sure the security ID is valid
  * - Check to make sure there is at most one unnamed stream
  * - Check to make sure there is at most one DOS name.
+ *
+ * Return values:
+ *     WIMLIB_ERR_SUCCESS (0)
  */
 int
 verify_inode(struct wim_inode *inode, const struct wim_security_data *sd)
@@ -86,7 +89,7 @@ verify_inode(struct wim_inode *inode, const struct wim_security_data *sd)
                                      "both `%"TS"' and `%"TS"'",
                                      dentry_full_path(dentry_with_dos_name),
                                      dentry_full_path(dentry));
-                               return WIMLIB_ERR_INVALID_DENTRY;
+                               return WIMLIB_ERR_INVALID_METADATA_RESOURCE;
                        #else
                                dentry->dos_name_invalid = 1;
                        #endif
index 43a0e3491680feafc723b0576333545c665d5a17..9ddaddbeab35cdee1e0a8a2f7d90def0c84dec68 100644 (file)
--- a/src/wim.c
+++ b/src/wim.c
@@ -75,8 +75,8 @@ new_wim_struct(void)
 {
        WIMStruct *wim = CALLOC(1, sizeof(WIMStruct));
        if (wim) {
-               wim->in_fd = -1;
-               wim->out_fd = -1;
+               wim->in_fd.fd = -1;
+               wim->out_fd.fd = -1;
        }
        return wim;
 }
@@ -115,9 +115,7 @@ for_image(WIMStruct *wim, int image, int (*visitor)(WIMStruct *))
        return 0;
 }
 
-/*
- * Creates a WIMStruct for a new WIM file.
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_create_new_wim(int ctype, WIMStruct **wim_ret)
 {
@@ -135,7 +133,7 @@ wimlib_create_new_wim(int ctype, WIMStruct **wim_ret)
        if (!wim)
                return WIMLIB_ERR_NOMEM;
 
-       ret = init_header(&wim->hdr, ctype);
+       ret = init_wim_header(&wim->hdr, ctype);
        if (ret != 0)
                goto out_free;
 
@@ -209,6 +207,7 @@ select_wim_image(WIMStruct *wim, int image)
 }
 
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI const tchar *
 wimlib_get_compression_type_string(int ctype)
 {
@@ -224,11 +223,7 @@ wimlib_get_compression_type_string(int ctype)
        }
 }
 
-/*
- * Returns the number of an image in the WIM file, given a string that is either
- * the number of the image, or the name of the image.  The images are numbered
- * starting at 1.
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_resolve_image(WIMStruct *wim, const tchar *image_name_or_num)
 {
@@ -257,7 +252,7 @@ wimlib_resolve_image(WIMStruct *wim, const tchar *image_name_or_num)
        }
 }
 
-/* Prints some basic information about a WIM file. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI void
 wimlib_print_wim_information(const WIMStruct *wim)
 {
@@ -284,6 +279,7 @@ wimlib_print_wim_information(const WIMStruct *wim)
        tputchar(T('\n'));
 }
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI void
 wimlib_print_available_images(const WIMStruct *wim, int image)
 {
@@ -312,8 +308,7 @@ wimlib_print_available_images(const WIMStruct *wim, int image)
 }
 
 
-/* Prints the metadata for the specified image, which may be WIMLIB_ALL_IMAGES, but
- * not WIMLIB_NO_IMAGE. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_print_metadata(WIMStruct *wim, int image)
 {
@@ -326,6 +321,7 @@ wimlib_print_metadata(WIMStruct *wim, int image)
        return for_image(wim, image, image_print_metadata);
 }
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_print_files(WIMStruct *wim, int image)
 {
@@ -338,6 +334,7 @@ wimlib_print_files(WIMStruct *wim, int image)
        return for_image(wim, image, image_print_files);
 }
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_get_wim_info(WIMStruct *wim, struct wimlib_wim_info *info)
 {
@@ -350,11 +347,8 @@ wimlib_get_wim_info(WIMStruct *wim, struct wimlib_wim_info *info)
        info->part_number = wim->hdr.part_number;
        info->total_parts = wim->hdr.total_parts;
        info->compression_type = wim->compression_type;
-       if (wim->wim_info)
-               info->total_bytes = wim->wim_info->total_bytes;
-       else
-               info->total_bytes = 0;
-       info->has_integrity_table = wim->hdr.integrity.offset != 0;
+       info->total_bytes = wim_info_get_total_bytes(wim->wim_info);
+       info->has_integrity_table = wim_has_integrity_table(wim);
        info->opened_from_file = (wim->filename != NULL);
        info->is_readonly = (wim->hdr.flags & WIM_HDR_FLAG_READONLY) ||
                             (wim->hdr.total_parts != 1) ||
@@ -365,11 +359,12 @@ wimlib_get_wim_info(WIMStruct *wim, struct wimlib_wim_info *info)
        info->metadata_only = (wim->hdr.flags & WIM_HDR_FLAG_METADATA_ONLY) != 0;
        info->resource_only = (wim->hdr.flags & WIM_HDR_FLAG_RESOURCE_ONLY) != 0;
        info->spanned = (wim->hdr.flags & WIM_HDR_FLAG_SPANNED) != 0;
+       info->pipable = wim_is_pipable(wim);
        return 0;
 }
 
 
-/* Deprecated */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_get_boot_idx(const WIMStruct *wim)
 {
@@ -379,7 +374,7 @@ wimlib_get_boot_idx(const WIMStruct *wim)
        return info.boot_index;
 }
 
-/* Deprecated */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_get_compression_type(const WIMStruct *wim)
 {
@@ -389,7 +384,7 @@ wimlib_get_compression_type(const WIMStruct *wim)
        return info.compression_type;
 }
 
-/* Deprecated */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_get_num_images(const WIMStruct *wim)
 {
@@ -399,7 +394,7 @@ wimlib_get_num_images(const WIMStruct *wim)
        return info.image_count;
 }
 
-/* Deprecated */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_get_part_number(const WIMStruct *wim, int *total_parts_ret)
 {
@@ -411,7 +406,7 @@ wimlib_get_part_number(const WIMStruct *wim, int *total_parts_ret)
        return info.part_number;
 }
 
-/* Deprecated */
+/* API function documented in wimlib.h  */
 WIMLIBAPI bool
 wimlib_has_integrity_table(const WIMStruct *wim)
 {
@@ -421,6 +416,7 @@ wimlib_has_integrity_table(const WIMStruct *wim)
        return info.has_integrity_table;
 }
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_set_wim_info(WIMStruct *wim, const struct wimlib_wim_info *info, int which)
 {
@@ -444,8 +440,7 @@ wimlib_set_wim_info(WIMStruct *wim, const struct wimlib_wim_info *info, int whic
                memcpy(wim->hdr.guid, info->guid, WIM_GID_LEN);
 
        if (which & WIMLIB_CHANGE_BOOT_INDEX) {
-               if (info->boot_index < 0 || info->boot_index > wim->hdr.image_count)
-               {
+               if (info->boot_index > wim->hdr.image_count) {
                        ERROR("%u is not 0 or a valid image in the WIM to mark as bootable",
                              info->boot_index);
                        return WIMLIB_ERR_INVALID_IMAGE;
@@ -462,7 +457,7 @@ wimlib_set_wim_info(WIMStruct *wim, const struct wimlib_wim_info *info, int whic
        return 0;
 }
 
-/* Deprecated */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_set_boot_idx(WIMStruct *wim, int boot_idx)
 {
@@ -473,32 +468,32 @@ wimlib_set_boot_idx(WIMStruct *wim, int boot_idx)
 }
 
 static int
-do_open_wim(const tchar *filename, int *fd_ret)
+do_open_wim(const tchar *filename, struct filedes *fd_ret)
 {
-       int fd;
+       int raw_fd;
 
-       fd = topen(filename, O_RDONLY | O_BINARY);
-       if (fd == -1) {
+       raw_fd = topen(filename, O_RDONLY | O_BINARY);
+       if (raw_fd < 0) {
                ERROR_WITH_ERRNO("Can't open \"%"TS"\" read-only", filename);
                return WIMLIB_ERR_OPEN;
        }
-       *fd_ret = fd;
+       filedes_init(fd_ret, raw_fd);
        return 0;
 }
 
 int
 reopen_wim(WIMStruct *wim)
 {
-       wimlib_assert(wim->in_fd == -1);
+       wimlib_assert(!filedes_valid(&wim->in_fd));
        return do_open_wim(wim->filename, &wim->in_fd);
 }
 
 int
 close_wim(WIMStruct *wim)
 {
-       if (wim->in_fd != -1) {
-               close(wim->in_fd);
-               wim->in_fd = -1;
+       if (filedes_valid(&wim->in_fd)) {
+               filedes_close(&wim->in_fd);
+               filedes_invalidate(&wim->in_fd);
        }
        return 0;
 }
@@ -508,47 +503,54 @@ close_wim(WIMStruct *wim)
  * lookup table, and optionally checks the integrity.
  */
 static int
-begin_read(WIMStruct *wim, const tchar *in_wim_path, int open_flags,
-          wimlib_progress_func_t progress_func)
+begin_read(WIMStruct *wim, const void *wim_filename_or_fd,
+          int open_flags, wimlib_progress_func_t progress_func)
 {
        int ret;
        int xml_num_images;
+       const tchar *wimfile;
 
-       DEBUG("Reading the WIM file `%"TS"'", in_wim_path);
-
-       ret = do_open_wim(in_wim_path, &wim->in_fd);
-       if (ret)
-               return ret;
+       if (open_flags & WIMLIB_OPEN_FLAG_FROM_PIPE) {
+               wimfile = NULL;
+               filedes_init(&wim->in_fd, *(const int*)wim_filename_or_fd);
+               wim->in_fd.is_pipe = 1;
+       } else {
+               wimfile = wim_filename_or_fd;
+               DEBUG("Reading the WIM file `%"TS"'", wimfile);
+               ret = do_open_wim(wimfile, &wim->in_fd);
+               if (ret)
+                       return ret;
 
-       /* The absolute path to the WIM is requested so that wimlib_overwrite()
-        * still works even if the process changes its working directory.  This
-        * actually happens if a WIM is mounted read-write, since the FUSE
-        * thread changes directory to "/", and it needs to be able to find the
-        * WIM file again.
-        *
-        * This will break if the full path to the WIM changes in the
-        * intervening time...
-        *
-        * Warning: in Windows native builds, realpath() calls the replacement
-        * function in win32.c.
-        */
-       wim->filename = realpath(in_wim_path, NULL);
-       if (!wim->filename) {
-               ERROR_WITH_ERRNO("Failed to resolve WIM filename");
-               if (errno == ENOMEM)
-                       return WIMLIB_ERR_NOMEM;
-               else
-                       return WIMLIB_ERR_OPEN;
+               /* The absolute path to the WIM is requested so that
+                * wimlib_overwrite() still works even if the process changes
+                * its working directory.  This actually happens if a WIM is
+                * mounted read-write, since the FUSE thread changes directory
+                * to "/", and it needs to be able to find the WIM file again.
+                *
+                * This will break if the full path to the WIM changes in the
+                * intervening time...
+                *
+                * Warning: in Windows native builds, realpath() calls the
+                * replacement function in win32.c.
+                */
+               wim->filename = realpath(wimfile, NULL);
+               if (!wim->filename) {
+                       ERROR_WITH_ERRNO("Failed to resolve WIM filename");
+                       if (errno == ENOMEM)
+                               return WIMLIB_ERR_NOMEM;
+                       else
+                               return WIMLIB_ERR_OPEN;
+               }
        }
 
-       ret = read_header(wim->filename, wim->in_fd, &wim->hdr);
+       ret = read_wim_header(wim->filename, &wim->in_fd, &wim->hdr);
        if (ret)
                return ret;
 
        if (wim->hdr.flags & WIM_HDR_FLAG_WRITE_IN_PROGRESS) {
                WARNING("The WIM_HDR_FLAG_WRITE_IN_PROGRESS is set in the header of \"%"TS"\".\n"
                        "          It may be being changed by another process, or a process\n"
-                       "          may have crashed while writing the WIM.", in_wim_path);
+                       "          may have crashed while writing the WIM.", wimfile);
        }
 
        if (open_flags & WIMLIB_OPEN_FLAG_WRITE_ACCESS) {
@@ -559,7 +561,7 @@ begin_read(WIMStruct *wim, const tchar *in_wim_path, int open_flags,
 
        if (wim->hdr.total_parts != 1 && !(open_flags & WIMLIB_OPEN_FLAG_SPLIT_OK)) {
                ERROR("\"%"TS"\": This WIM is part %u of a %u-part WIM",
-                     in_wim_path, wim->hdr.part_number, wim->hdr.total_parts);
+                     wimfile, wim->hdr.part_number, wim->hdr.total_parts);
                return WIMLIB_ERR_SPLIT_UNSUPPORTED;
        }
 
@@ -569,7 +571,7 @@ begin_read(WIMStruct *wim, const tchar *in_wim_path, int open_flags,
        if (wim->hdr.boot_idx > wim->hdr.image_count) {
                WARNING("In `%"TS"', image %u is marked as bootable, "
                        "but there are only %u images in the WIM",
-                       in_wim_path, wim->hdr.boot_idx, wim->hdr.image_count);
+                       wimfile, wim->hdr.boot_idx, wim->hdr.image_count);
                wim->hdr.boot_idx = 0;
        }
 
@@ -578,7 +580,7 @@ begin_read(WIMStruct *wim, const tchar *in_wim_path, int open_flags,
                if (wim->hdr.flags & WIM_HDR_FLAG_COMPRESS_LZX) {
                        if (wim->hdr.flags & WIM_HDR_FLAG_COMPRESS_XPRESS) {
                                ERROR("Multiple compression flags are set in \"%"TS"\"",
-                                     in_wim_path);
+                                     wimfile);
                                return WIMLIB_ERR_INVALID_COMPRESSION_TYPE;
                        }
                        wim->compression_type = WIMLIB_COMPRESSION_TYPE_LZX;
@@ -587,18 +589,18 @@ begin_read(WIMStruct *wim, const tchar *in_wim_path, int open_flags,
                } else {
                        ERROR("The compression flag is set on \"%"TS"\", but "
                              "neither the XPRESS nor LZX flag is set",
-                             in_wim_path);
+                             wimfile);
                        return WIMLIB_ERR_INVALID_COMPRESSION_TYPE;
                }
        } else {
-               BUILD_BUG_ON(WIMLIB_COMPRESSION_TYPE_NONE != 0);
+               wim->compression_type = WIMLIB_COMPRESSION_TYPE_NONE;
        }
 
        if (open_flags & WIMLIB_OPEN_FLAG_CHECK_INTEGRITY) {
                ret = check_wim_integrity(wim, progress_func);
                if (ret == WIM_INTEGRITY_NONEXISTENT) {
                        WARNING("No integrity information for `%"TS"'; skipping "
-                               "integrity check.", in_wim_path);
+                               "integrity check.", wimfile);
                } else if (ret == WIM_INTEGRITY_NOT_OK) {
                        ERROR("WIM is not intact! (Failed integrity check)");
                        return WIMLIB_ERR_INTEGRITY;
@@ -613,34 +615,35 @@ begin_read(WIMStruct *wim, const tchar *in_wim_path, int open_flags,
                        return WIMLIB_ERR_NOMEM;
        }
 
-       ret = read_lookup_table(wim);
-       if (ret)
-               return ret;
+       if (open_flags & WIMLIB_OPEN_FLAG_FROM_PIPE) {
+               wim->lookup_table = new_lookup_table(9001);
+               if (!wim->lookup_table)
+                       return WIMLIB_ERR_NOMEM;
+       } else {
+               ret = read_wim_lookup_table(wim);
+               if (ret)
+                       return ret;
 
-       ret = read_xml_data(wim->in_fd, &wim->hdr.xml_res_entry, &wim->wim_info);
-       if (ret)
-               return ret;
+               ret = read_wim_xml_data(wim);
+               if (ret)
+                       return ret;
 
-       xml_num_images = wim_info_get_num_images(wim->wim_info);
-       if (xml_num_images != wim->hdr.image_count) {
-               ERROR("In the file `%"TS"', there are %u <IMAGE> elements "
-                     "in the XML data,", in_wim_path, xml_num_images);
-               ERROR("but %u images in the WIM!  There must be exactly one "
-                     "<IMAGE> element per image.", wim->hdr.image_count);
-               return WIMLIB_ERR_IMAGE_COUNT;
+               xml_num_images = wim_info_get_num_images(wim->wim_info);
+               if (xml_num_images != wim->hdr.image_count) {
+                       ERROR("In the file `%"TS"', there are %u <IMAGE> elements "
+                             "in the XML data,", wimfile, xml_num_images);
+                       ERROR("but %u images in the WIM!  There must be exactly one "
+                             "<IMAGE> element per image.", wim->hdr.image_count);
+                       return WIMLIB_ERR_IMAGE_COUNT;
+               }
+               DEBUG("Done beginning read of WIM file `%"TS"'.", wimfile);
        }
-
-       DEBUG("Done beginning read of WIM file `%"TS"'.", in_wim_path);
        return 0;
 }
 
-/*
- * Opens a WIM file and creates a WIMStruct for it.
- */
-WIMLIBAPI int
-wimlib_open_wim(const tchar *wim_file, int open_flags,
-               WIMStruct **wim_ret,
-               wimlib_progress_func_t progress_func)
+int
+open_wim_as_WIMStruct(const void *wim_filename_or_fd, int open_flags,
+                     WIMStruct **wim_ret, wimlib_progress_func_t progress_func)
 {
        WIMStruct *wim;
        int ret;
@@ -648,7 +651,7 @@ wimlib_open_wim(const tchar *wim_file, int open_flags,
        wimlib_global_init(WIMLIB_INIT_FLAG_ASSUME_UTF8);
 
        ret = WIMLIB_ERR_INVALID_PARAM;
-       if (!wim_file || !wim_ret)
+       if (!wim_ret)
                goto out;
 
        ret = WIMLIB_ERR_NOMEM;
@@ -656,7 +659,7 @@ wimlib_open_wim(const tchar *wim_file, int open_flags,
        if (!wim)
                goto out;
 
-       ret = begin_read(wim, wim_file, open_flags, progress_func);
+       ret = begin_read(wim, wim_filename_or_fd, open_flags, progress_func);
        if (ret)
                goto out_wimlib_free;
 
@@ -669,6 +672,16 @@ out:
        return ret;
 }
 
+/* API function documented in wimlib.h  */
+WIMLIBAPI int
+wimlib_open_wim(const tchar *wimfile, int open_flags,
+               WIMStruct **wim_ret, wimlib_progress_func_t progress_func)
+{
+       open_flags &= WIMLIB_OPEN_MASK_PUBLIC;
+       return open_wim_as_WIMStruct(wimfile, open_flags, wim_ret,
+                                    progress_func);
+}
+
 void
 destroy_image_metadata(struct wim_image_metadata *imd,
                       struct wim_lookup_table *table,
@@ -848,8 +861,7 @@ can_delete_from_wim(WIMStruct *wim)
        return 0;
 }
 
-/* Frees the memory for the WIMStruct, including all internal memory; also
- * closes all files associated with the WIMStruct.  */
+/* API function documented in wimlib.h  */
 WIMLIBAPI void
 wimlib_free(WIMStruct *wim)
 {
@@ -857,10 +869,10 @@ wimlib_free(WIMStruct *wim)
 
        if (!wim)
                return;
-       if (wim->in_fd != -1)
-               close(wim->in_fd);
-       if (wim->out_fd != -1)
-               close(wim->out_fd);
+       if (wim->in_fd.fd != -1)
+               close(wim->in_fd.fd);
+       if (wim->out_fd.fd != -1)
+               close(wim->out_fd.fd);
 
        free_lookup_table(wim->lookup_table);
 
@@ -890,6 +902,7 @@ test_locale_ctype_utf8(void)
 #endif
 }
 
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_global_init(int init_flags)
 {
@@ -912,8 +925,7 @@ wimlib_global_init(int init_flags)
        return 0;
 }
 
-/* Free global memory allocations.  Not strictly necessary if the process using
- * wimlib is just about to exit (as is the case for 'imagex'). */
+/* API function documented in wimlib.h  */
 WIMLIBAPI void
 wimlib_global_cleanup(void)
 {
index 70aeff75744784123b30d4a373f92f7e00281def..b59f7a8a0b6d795b273150e2cfe0ad3fc70f4da4 100644 (file)
 #include "wimlib/win32_common.h"
 
 #include "wimlib/apply.h"
-#include "wimlib/dentry.h"
-#include "wimlib/endianness.h"
 #include "wimlib/error.h"
 #include "wimlib/lookup_table.h"
-#include "wimlib/metadata.h"
-#include "wimlib/reparse.h"
-#include "wimlib/security.h"
-
-#define MAX_CREATE_HARD_LINK_WARNINGS 5
-#define MAX_CREATE_SOFT_LINK_WARNINGS 5
-
-#define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1
-#define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1
-
-static const wchar_t *apply_access_denied_msg =
-L"If you are not running this program as the administrator, you may\n"
- "          need to do so, so that all data and metadata can be extracted\n"
- "          exactly as the origignal copy.  However, if you do not care that\n"
- "          the security descriptors are extracted correctly, you could run\n"
- "          `wimlib-imagex apply' with the --no-acls flag instead.\n"
- ;
-
 
 static int
-win32_extract_try_rpfix(u8 *rpbuf,
-                       u16 *rpbuflen_p,
-                       const wchar_t *extract_root_realpath,
-                       unsigned extract_root_realpath_nchars)
+win32_start_extract(const wchar_t *path, struct apply_ctx *ctx)
 {
-       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;
+       unsigned vol_flags;
+       bool supports_SetFileShortName;
 
-       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);
+       ret = win32_get_vol_flags(path, &vol_flags, &supports_SetFileShortName);
        if (ret)
                return ret;
 
-       if (extract_root_realpath[0] == L'\0' ||
-           extract_root_realpath[1] != L':' ||
-           extract_root_realpath[2] != L'\\')
-       {
-               ERROR("Can't understand full path format \"%ls\".  "
-                     "Try turning reparse point fixups off...",
-                     extract_root_realpath);
-               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)
-       {
-               ERROR("Path names too long to do reparse point fixup!");
-               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);
+       ctx->supported_features.archive_files             = 1;
+       ctx->supported_features.hidden_files              = 1;
+       ctx->supported_features.system_files              = 1;
+       ctx->supported_features.compressed_files          = !!(vol_flags & FILE_FILE_COMPRESSION);
+       ctx->supported_features.encrypted_files           = !!(vol_flags & FILE_SUPPORTS_ENCRYPTION);
+       ctx->supported_features.not_context_indexed_files = 1;
+       ctx->supported_features.sparse_files              = !!(vol_flags & FILE_SUPPORTS_SPARSE_FILES);
+       ctx->supported_features.named_data_streams        = !!(vol_flags & FILE_NAMED_STREAMS);
+       ctx->supported_features.hard_links                = !!(vol_flags & FILE_SUPPORTS_HARD_LINKS);
+       ctx->supported_features.reparse_points            = !!(vol_flags & FILE_SUPPORTS_REPARSE_POINTS);
+       ctx->supported_features.security_descriptors      = !!(vol_flags & FILE_PERSISTENT_ACLS);
+       ctx->supported_features.short_names               = !!supports_SetFileShortName;
+       return 0;
 }
 
-/* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on
- * an extracted reparse point. */
 static int
-win32_set_reparse_data(HANDLE h,
-                      const struct wim_inode *inode,
-                      const struct wim_lookup_table_entry *lte,
-                      const wchar_t *path,
-                      struct apply_args *args)
+win32_create_file(const wchar_t *path, struct apply_ctx *ctx)
 {
-       int ret;
-       u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8);
-       DWORD bytesReturned;
-       u16 rpbuflen;
-
-       DEBUG("Setting reparse data on \"%ls\"", path);
+       HANDLE h;
 
-       ret = wim_inode_get_reparse_data(inode, rpbuf, &rpbuflen);
-       if (ret)
-               return ret;
+       h = CreateFile(path, 0, 0, NULL, CREATE_ALWAYS,
+                      FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL);
+       if (h == INVALID_HANDLE_VALUE)
+               goto error;
+       CloseHandle(h);
+       return 0;
 
-       if (args->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 = win32_extract_try_rpfix(rpbuf,
-                                             &rpbuflen,
-                                             args->target_realpath,
-                                             args->target_realpath_len);
-               if (ret)
-                       return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
-       }
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_OPEN;
+}
 
-       /* Set the reparse data on the open file using the
-        * FSCTL_SET_REPARSE_POINT ioctl.
-        *
-        * There are contradictions in Microsoft's documentation for this:
-        *
-        * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED,
-        * lpOverlapped is ignored."
-        *
-        * --- So setting lpOverlapped to NULL is okay since it's ignored.
-        *
-        * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an
-        * operation returns no output data and lpOutBuffer is NULL,
-        * DeviceIoControl makes use of lpBytesReturned. After such an
-        * operation, the value of lpBytesReturned is meaningless."
-        *
-        * --- So lpOverlapped not really ignored, as it affects another
-        *  parameter.  This is the actual behavior: lpBytesReturned must be
-        *  specified, even though lpBytesReturned is documented as:
-        *
-        *  "Not used with this operation; set to NULL."
-        */
-       if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf,
-                            rpbuflen,
-                            NULL, 0,
-                            &bytesReturned /* lpBytesReturned */,
-                            NULL /* lpOverlapped */))
-       {
-               DWORD err = GetLastError();
-               if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
-               {
-                       args->num_soft_links_failed++;
-                       if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) {
-                               WARNING("Can't set reparse data on \"%ls\": 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);
-                       }
-                       if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
-                               WARNING("Suppressing further warnings regarding failure to extract\n"
-                                       "          reparse points due to insufficient privileges...");
-                       }
-               } else {
-                       ERROR("Failed to set reparse data on \"%ls\"", path);
-                       win32_error(err);
-                       if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
-                           inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
-                               return WIMLIB_ERR_LINK;
-                       else
-                               return WIMLIB_ERR_WRITE;
-               }
-       }
+static int
+win32_create_directory(const wchar_t *path, struct apply_ctx *ctx)
+{
+       if (!CreateDirectory(path, NULL))
+               if (GetLastError() != ERROR_ALREADY_EXISTS)
+                       goto error;
        return 0;
+
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_MKDIR;
 }
 
-/* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the
- * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */
 static int
-win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path)
+win32_create_hardlink(const wchar_t *oldpath, const wchar_t *newpath,
+                     struct apply_ctx *ctx)
 {
-       DWORD bytesReturned;
-       if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION,
-                            &format, sizeof(USHORT),
-                            NULL, 0,
-                            &bytesReturned, NULL))
-       {
-               /* Could be a warning only, but we only call this if the volume
-                * supports compression.  So I'm calling this an error. */
-               DWORD err = GetLastError();
-               ERROR("Failed to set compression flag on \"%ls\"", path);
-               win32_error(err);
-               if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
-                       return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
-               else
-                       return WIMLIB_ERR_WRITE;
-       }
+       if (!CreateHardLink(newpath, oldpath, NULL))
+               goto error;
        return 0;
+
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_LINK;
 }
 
-/* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */
 static int
-win32_set_sparse(HANDLE hFile, const wchar_t *path)
+win32_extract_wim_chunk(const void *buf, size_t len, void *arg)
 {
-       DWORD bytesReturned;
-       if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE,
-                            NULL, 0,
-                            NULL, 0,
-                            &bytesReturned, NULL))
-       {
-               /* Could be a warning only, but we only call this if the volume
-                * supports sparse files.  So I'm calling this an error. */
-               DWORD err = GetLastError();
-               WARNING("Failed to set sparse flag on \"%ls\"", path);
-               win32_error(err);
-               if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
-                       return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
-               else
-                       return WIMLIB_ERR_WRITE;
-       }
+       HANDLE h = (HANDLE)arg;
+       DWORD nbytes_written;
+
+       if (unlikely(!WriteFile(h, buf, len, &nbytes_written, NULL)))
+               goto error;
+       if (unlikely(nbytes_written != len))
+               goto error;
        return 0;
+
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_WRITE;
 }
 
-/*
- * Sets the security descriptor on an extracted file.
- */
 static int
-win32_set_security_data(const struct wim_inode *inode,
-                       HANDLE hFile,
-                       const wchar_t *path,
-                       struct apply_args *args)
+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)
 {
-       PSECURITY_DESCRIPTOR descriptor;
-       unsigned long n;
-       DWORD err;
-       const struct wim_security_data *sd;
-       SECURITY_INFORMATION securityInformation;
+       DWORD creationDisposition = OPEN_EXISTING;
+       wchar_t *stream_path = (wchar_t*)path;
+       HANDLE h;
+       int ret;
 
-       securityInformation = OWNER_SECURITY_INFORMATION |
-                             GROUP_SECURITY_INFORMATION |
-                             DACL_SECURITY_INFORMATION  |
-                             SACL_SECURITY_INFORMATION;
+       if (stream_name_nchars) {
+               creationDisposition = CREATE_ALWAYS;
+               stream_path = alloca(sizeof(wchar_t) *
+                                    (wcslen(path) + 1 +
+                                     wcslen(stream_name) + 1));
+               swprintf(stream_path, L"%ls:%ls", path, stream_name);
+       }
 
-       sd = wim_const_security_data(args->wim);
-       descriptor = sd->descriptors[inode->i_security_id];
+       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)
+               goto error;
 
-again:
-       if (SetFileSecurity(path, securityInformation, descriptor))
-               return 0;
-       err = GetLastError();
-       if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)
-               goto fail;
-       switch (err) {
-       case ERROR_PRIVILEGE_NOT_HELD:
-               if (securityInformation & SACL_SECURITY_INFORMATION) {
-                       n = args->num_set_sacl_priv_notheld++;
-                       securityInformation &= ~SACL_SECURITY_INFORMATION;
-                       if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
-                               WARNING(
-"We don't have enough privileges to set the full security\n"
-"          descriptor on \"%ls\"!\n", path);
-                               if (args->num_set_sd_access_denied +
-                                   args->num_set_sacl_priv_notheld == 1)
-                               {
-                                       WARNING("%ls", apply_access_denied_msg);
-                               }
-                               WARNING("Re-trying with SACL omitted.\n", path);
-                       } else if (n == MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) {
-                               WARNING(
-"Suppressing further 'privileges not held' error messages when setting\n"
-"          security descriptors.");
-                       }
-                       goto again;
-               }
-               /* Fall through */
-       case ERROR_INVALID_OWNER:
-       case ERROR_ACCESS_DENIED:
-               n = args->num_set_sd_access_denied++;
-               if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
-                       WARNING("Failed to set security descriptor on \"%ls\": "
-                               "Access denied!\n", path);
-                       if (args->num_set_sd_access_denied +
-                           args->num_set_sacl_priv_notheld == 1)
-                       {
-                               WARNING("%ls", apply_access_denied_msg);
-                       }
-               } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) {
-                       WARNING(
-"Suppressing further access denied error messages when setting\n"
-"          security descriptors");
-               }
-               return 0;
-       default:
-fail:
-               ERROR("Failed to set security descriptor on \"%ls\"", path);
-               win32_error(err);
-               if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD)
-                       return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT;
-               else
-                       return WIMLIB_ERR_WRITE;
-       }
-}
+       ret = 0;
+       if (!lte)
+               goto out_close_handle;
+       ret = extract_wim_resource(lte, wim_resource_size(lte),
+                                  win32_extract_wim_chunk, h);
+out_close_handle:
+       if (!CloseHandle(h))
+               goto error;
+       if (ret && !errno)
+               errno = -1;
+       return ret;
 
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_WRITE;
+}
 
 static int
-win32_extract_chunk(const void *buf, size_t len, void *arg)
+win32_extract_unnamed_stream(const wchar_t *path,
+                            struct wim_lookup_table_entry *lte,
+                            struct apply_ctx *ctx)
 {
-       HANDLE hStream = arg;
-
-       DWORD nbytes_written;
-       wimlib_assert(len <= 0xffffffff);
-
-       if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) ||
-           nbytes_written != len)
-       {
-               DWORD err = GetLastError();
-               ERROR("WriteFile(): write error");
-               win32_error(err);
-               return WIMLIB_ERR_WRITE;
-       }
-       return 0;
+       return win32_extract_stream(path, NULL, 0, lte, ctx);
 }
 
 static int
-do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte)
+win32_extract_named_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)
 {
-       return extract_wim_resource(lte, wim_resource_size(lte),
-                                   win32_extract_chunk, hStream);
+       return win32_extract_stream(path, stream_name,
+                                   stream_name_nchars, lte, ctx);
 }
 
 struct win32_encrypted_extract_ctx {
@@ -385,909 +184,332 @@ struct win32_encrypted_extract_ctx {
 };
 
 static DWORD WINAPI
-win32_encrypted_import_cb(unsigned char *data, void *_ctx,
+win32_encrypted_import_cb(unsigned char *data, void *_import_ctx,
                          unsigned long *len_p)
 {
-       struct win32_encrypted_extract_ctx *ctx = _ctx;
+       struct win32_encrypted_extract_ctx *import_ctx = _import_ctx;
        unsigned long len = *len_p;
-       const struct wim_lookup_table_entry *lte = ctx->lte;
+       const struct wim_lookup_table_entry *lte = import_ctx->lte;
 
-       len = min(len, wim_resource_size(lte) - ctx->offset);
+       len = min(len, wim_resource_size(lte) - import_ctx->offset);
 
-       if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data))
+       if (read_partial_wim_resource_into_buf(lte, len, import_ctx->offset, data))
                return ERROR_READ_FAULT;
 
-       ctx->offset += len;
+       import_ctx->offset += len;
        *len_p = len;
        return ERROR_SUCCESS;
 }
 
-/* Create an encrypted file and extract the raw encrypted data to it.
- *
- * @path:  Path to encrypted file to create.
- * @lte:   WIM lookup_table entry for the raw encrypted data.
- *
- * This is separate from do_win32_extract_stream() because the WIM is supposed
- * to contain the *raw* encrypted data, which needs to be extracted ("imported")
- * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and
- * CloseEncryptedFileRaw().
- *
- * Returns 0 on success; nonzero on failure.
- */
 static int
-do_win32_extract_encrypted_stream(const wchar_t *path,
-                                 const struct wim_lookup_table_entry *lte)
+win32_extract_encrypted_stream(const wchar_t *path,
+                              struct wim_lookup_table_entry *lte,
+                              struct apply_ctx *ctx)
 {
        void *file_ctx;
+       DWORD err;
        int ret;
+       struct win32_encrypted_extract_ctx extract_ctx;
 
-       DEBUG("Opening file \"%ls\" to extract raw encrypted data", path);
-
-       ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx);
-       if (ret) {
-               ERROR("Failed to open \"%ls\" to write raw encrypted data", path);
-               win32_error(ret);
-               return WIMLIB_ERR_OPEN;
+       err = OpenEncryptedFileRaw(path, CREATE_FOR_IMPORT, &file_ctx);
+       if (err != ERROR_SUCCESS) {
+               errno = win32_error_to_errno(err);
+               ret = WIMLIB_ERR_OPEN;
+               goto out;
        }
 
-       if (lte) {
-               struct win32_encrypted_extract_ctx ctx;
-
-               ctx.lte = lte;
-               ctx.offset = 0;
-               ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx);
-               if (ret == ERROR_SUCCESS) {
-                       ret = 0;
-               } else {
-                       ret = WIMLIB_ERR_WRITE;
-                       ERROR("Failed to extract encrypted file \"%ls\"", path);
-               }
+       extract_ctx.lte = lte;
+       extract_ctx.offset = 0;
+       err = WriteEncryptedFileRaw(win32_encrypted_import_cb, &extract_ctx,
+                                   file_ctx);
+       if (err != ERROR_SUCCESS) {
+               errno = win32_error_to_errno(err);
+               ret = WIMLIB_ERR_WRITE;
+               goto out_close;
        }
+
+       ret = 0;
+out_close:
        CloseEncryptedFileRaw(file_ctx);
+out:
        return ret;
 }
 
-static inline DWORD
-win32_mask_attributes(DWORD i_attributes)
-{
-       return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE |
-                               FILE_ATTRIBUTE_COMPRESSED |
-                               FILE_ATTRIBUTE_REPARSE_POINT |
-                               FILE_ATTRIBUTE_DIRECTORY |
-                               FILE_ATTRIBUTE_ENCRYPTED |
-                               FILE_FLAG_DELETE_ON_CLOSE |
-                               FILE_FLAG_NO_BUFFERING |
-                               FILE_FLAG_OPEN_NO_RECALL |
-                               FILE_FLAG_OVERLAPPED |
-                               FILE_FLAG_RANDOM_ACCESS |
-                               /*FILE_FLAG_SESSION_AWARE |*/
-                               FILE_FLAG_SEQUENTIAL_SCAN |
-                               FILE_FLAG_WRITE_THROUGH);
-}
-
-static inline DWORD
-win32_get_create_flags_and_attributes(DWORD i_attributes)
-{
-       /*
-        * Some attributes cannot be set by passing them to CreateFile().  In
-        * particular:
-        *
-        * FILE_ATTRIBUTE_DIRECTORY:
-        *   CreateDirectory() must be called instead of CreateFile().
-        *
-        * FILE_ATTRIBUTE_SPARSE_FILE:
-        *   Needs an ioctl.
-        *   See: win32_set_sparse().
-        *
-        * FILE_ATTRIBUTE_COMPRESSED:
-        *   Not clear from the documentation, but apparently this needs an
-        *   ioctl as well.
-        *   See: win32_set_compressed().
-        *
-        * FILE_ATTRIBUTE_REPARSE_POINT:
-        *   Needs an ioctl, with the reparse data specified.
-        *   See: win32_set_reparse_data().
-        *
-        * In addition, clear any file flags in the attributes that we don't
-        * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and
-        * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application.
-        */
-       return win32_mask_attributes(i_attributes) |
-               FILE_FLAG_OPEN_REPARSE_POINT |
-               FILE_FLAG_BACKUP_SEMANTICS;
-}
-
-/* Set compression and/or sparse attributes on a stream, if supported by the
- * volume. */
-static int
-win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode,
-                                   struct wim_lookup_table_entry *unnamed_stream_lte,
-                                   const wchar_t *path, unsigned vol_flags)
-{
-       int ret;
-
-       if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) {
-               if (vol_flags & FILE_FILE_COMPRESSION) {
-                       ret = win32_set_compression_state(hFile,
-                                                         COMPRESSION_FORMAT_DEFAULT,
-                                                         path);
-                       if (ret)
-                               return ret;
-               } else {
-                       DEBUG("Cannot set compression attribute on \"%ls\": "
-                             "volume does not support transparent compression",
-                             path);
-               }
-       }
-
-       if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
-               if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) {
-                       DEBUG("Setting sparse flag on \"%ls\"", path);
-                       ret = win32_set_sparse(hFile, path);
-                       if (ret)
-                               return ret;
-               } else {
-                       DEBUG("Cannot set sparse attribute on \"%ls\": "
-                             "volume does not support sparse files",
-                             path);
-               }
-       }
-       return 0;
-}
-
-/* Pre-create directories; extract encrypted streams */
-static int
-win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
-                                  const struct wim_lookup_table_entry *lte,
-                                  const wchar_t *path,
-                                  DWORD *creationDisposition_ret,
-                                  unsigned int vol_flags)
+static BOOL
+win32_set_special_file_attributes(const wchar_t *path, u32 attributes)
 {
+       HANDLE h;
        DWORD err;
-       int ret;
-
-       /* Directories must be created with CreateDirectoryW().  Then the call
-        * to CreateFileW() will merely open the directory that was already
-        * created rather than creating a new file. */
-       if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
-               if (!win32_path_is_root_of_drive(path)) {
-                       if (!CreateDirectoryW(path, NULL)) {
-                               err = GetLastError();
-                               if (err != ERROR_ALREADY_EXISTS) {
-                                       ERROR("Failed to create directory \"%ls\"",
-                                             path);
-                                       win32_error(err);
-                                       return WIMLIB_ERR_MKDIR;
-                               }
-                       }
-                       DEBUG("Created directory \"%ls\"", path);
-               }
-               *creationDisposition_ret = OPEN_EXISTING;
-       }
-       if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
-           vol_flags & FILE_SUPPORTS_ENCRYPTION)
-       {
-               if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
-                       unsigned remaining_sharing_violations = 100;
-                       while (!EncryptFile(path)) {
-                               if (remaining_sharing_violations &&
-                                   err == ERROR_SHARING_VIOLATION)
-                               {
-                                       WARNING("Couldn't encrypt directory \"%ls\" "
-                                               "due to sharing violation; re-trying "
-                                               "after 100 ms", path);
-                                       Sleep(100);
-                                       remaining_sharing_violations--;
-                               } else {
-                                       err = GetLastError();
-                                       ERROR("Failed to encrypt directory \"%ls\"",
-                                             path);
-                                       win32_error(err);
-                                       return WIMLIB_ERR_WRITE;
-                               }
-                       }
-               } else {
-                       ret = do_win32_extract_encrypted_stream(path, lte);
-                       if (ret)
-                               return ret;
-                       DEBUG("Extracted encrypted file \"%ls\"", path);
-               }
-               *creationDisposition_ret = OPEN_EXISTING;
-       }
-
-       /* Set file attributes if we created the file.  Otherwise, we haven't
-        * created the file set and we will set the attributes in the call to
-        * CreateFileW().
-        *
-        * The FAT filesystem does not let you change the attributes of the root
-        * directory, so treat that as a special case and do not set attributes.
-        * */
-       if (*creationDisposition_ret == OPEN_EXISTING &&
-           !win32_path_is_root_of_drive(path))
-       {
-               if (!SetFileAttributesW(path,
-                                       win32_mask_attributes(inode->i_attributes)))
-               {
-                       err = GetLastError();
-                       ERROR("Failed to set attributes on \"%ls\"", path);
-                       win32_error(err);
-                       return WIMLIB_ERR_WRITE;
-               }
-       }
-       return 0;
+       USHORT compression_format = COMPRESSION_FORMAT_DEFAULT;
+       DWORD bytes_returned;
+
+       h = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+                      OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS |
+                                     FILE_FLAG_OPEN_REPARSE_POINT,
+                      NULL);
+       if (h == INVALID_HANDLE_VALUE)
+               goto error;
+
+       if (attributes & FILE_ATTRIBUTE_SPARSE_FILE)
+               if (!DeviceIoControl(h, FSCTL_SET_SPARSE,
+                                    NULL, 0,
+                                    NULL, 0,
+                                    &bytes_returned, NULL))
+                       goto error_close_handle;
+
+       if (attributes & FILE_ATTRIBUTE_COMPRESSED)
+               if (!DeviceIoControl(h, FSCTL_SET_COMPRESSION,
+                                    &compression_format, sizeof(USHORT),
+                                    NULL, 0,
+                                    &bytes_returned, NULL))
+                       goto error_close_handle;
+
+       if (!CloseHandle(h))
+               goto error;
+
+       if (attributes & FILE_ATTRIBUTE_ENCRYPTED)
+               if (!EncryptFile(path))
+                       goto error;
+
+       return TRUE;
+
+error_close_handle:
+       err = GetLastError();
+       CloseHandle(h);
+       SetLastError(err);
+error:
+       return FALSE;
 }
 
-/* Extract stream data or reparse data (skip the unnamed data stream of
- * encrypted files, which was already extracted) and set short file name. */
 static int
-win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry,
-                           const struct wim_lookup_table_entry *lte,
-                           const wchar_t *stream_path,
-                           const wchar_t *stream_name_utf16,
-                           struct apply_args *args)
+win32_set_file_attributes(const wchar_t *path, u32 attributes,
+                         struct apply_ctx *ctx)
 {
-       int ret = 0;
-       const struct wim_inode *inode = dentry->d_inode;
-       if (stream_name_utf16 == NULL) {
-               /* Unnamed stream. */
-
-               /* Handle reparse points.  The data for them needs to be set
-                * using a special ioctl.  Note that the reparse point may have
-                * been created using CreateFileW() in the case of
-                * non-directories or CreateDirectoryW() in the case of
-                * directories; but the ioctl works either way.  Also, it is
-                * only this step that actually sets the
-                * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it
-                * using SetFileAttributesW() or CreateFileW().
-                *
-                * If the volume does not support reparse points we simply
-                * ignore the reparse data.  (N.B. the code currently doesn't
-                * actually reach this case because reparse points are skipped
-                * entirely on such volumes.) */
-               if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
-                       if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) {
-                               ret = win32_set_reparse_data(h, inode,
-                                                            lte, stream_path,
-                                                            args);
-                               if (ret)
-                                       return ret;
-                       } else {
-                               DEBUG("Cannot set reparse data on \"%ls\": volume "
-                                     "does not support reparse points", stream_path);
-                       }
-               } else if (lte != NULL &&
-                          !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION &&
-                            inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
+       u32 special_attributes =
+               FILE_ATTRIBUTE_REPARSE_POINT |
+               FILE_ATTRIBUTE_DIRECTORY |
+               FILE_ATTRIBUTE_SPARSE_FILE |
+               FILE_ATTRIBUTE_COMPRESSED |
+               FILE_ATTRIBUTE_ENCRYPTED;
+       u32 actual_attributes;
+
+       if (!SetFileAttributes(path, attributes & ~special_attributes))
+               goto error;
+
+       if (attributes & (FILE_ATTRIBUTE_SPARSE_FILE |
+                         FILE_ATTRIBUTE_ENCRYPTED |
+                         FILE_ATTRIBUTE_COMPRESSED))
+               if (!win32_set_special_file_attributes(path, attributes))
+                       goto error;
+
+       /* If file is not supposed to be encrypted or compressed, remove
+        * defaulted encrypted or compressed attributes (from creating file in
+        * encrypted or compressed directory).  */
+       actual_attributes = GetFileAttributes(path);
+       if (actual_attributes == INVALID_FILE_ATTRIBUTES)
+               goto error;
+
+       if ((actual_attributes & FILE_ATTRIBUTE_ENCRYPTED) &&
+           !(attributes & FILE_ATTRIBUTE_ENCRYPTED))
+               if (!DecryptFile(path, 0))
+                       goto error;
+       if ((actual_attributes & FILE_ATTRIBUTE_COMPRESSED) &&
+           !(attributes & FILE_ATTRIBUTE_COMPRESSED))
+       {
+               HANDLE h;
+               DWORD bytes_returned;
+               USHORT compression_format = COMPRESSION_FORMAT_NONE;
+
+               h = CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+                              OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS |
+                                             FILE_FLAG_OPEN_REPARSE_POINT,
+                              NULL);
+               if (h == INVALID_HANDLE_VALUE)
+                       goto error;
+
+               if (!DeviceIoControl(h, FSCTL_SET_COMPRESSION,
+                                    &compression_format, sizeof(USHORT),
+                                    NULL, 0,
+                                    &bytes_returned, NULL))
                {
-                       /* Extract the data of the unnamed stream, unless the
-                        * lookup table entry is NULL (indicating an empty
-                        * stream for which no data needs to be extracted), or
-                        * the stream is encrypted and therefore was already
-                        * extracted as a special case. */
-                       ret = do_win32_extract_stream(h, lte);
-                       if (ret)
-                               return ret;
+                       DWORD err = GetLastError();
+                       CloseHandle(h);
+                       SetLastError(err);
+                       goto error;
                }
 
-               if (dentry_has_short_name(dentry) && !dentry->dos_name_invalid)
-                       SetFileShortNameW(h, dentry->short_name);
-               else if (running_on_windows_7_or_later())
-                       SetFileShortNameW(h, L"");
-       } else {
-               /* Extract the data for a named data stream. */
-               if (lte != NULL) {
-                       DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")",
-                             stream_path, wim_resource_size(lte));
-                       ret = do_win32_extract_stream(h, lte);
-               }
+               if (!CloseHandle(h))
+                       goto error;
        }
-       return ret;
-}
 
-static int
-win32_decrypt_file(HANDLE open_handle, const wchar_t *path)
-{
-       DWORD err;
-       /* We cannot call DecryptFileW() while there is an open handle to the
-        * file.  So close it first. */
-       if (!CloseHandle(open_handle)) {
-               err = GetLastError();
-               ERROR("Failed to close handle for \"%ls\"", path);
-               win32_error(err);
-               return WIMLIB_ERR_WRITE;
-       }
-       if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) {
-               err = GetLastError();
-               ERROR("Failed to decrypt file \"%ls\"", path);
-               win32_error(err);
-               return WIMLIB_ERR_WRITE;
-       }
+success:
        return 0;
+
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_SET_ATTRIBUTES;
 }
 
-/*
- * Create and extract a stream to a file, or create a directory, using the
- * Windows API.
- *
- * This handles reparse points, directories, alternate data streams, encrypted
- * files, compressed files, etc.
- *
- * @dentry: WIM dentry for the file or directory being extracted.
- *
- * @path:  Path to extract the file to.
- *
- * @stream_name_utf16:
- *        Name of the stream, or NULL if the stream is unnamed.  This will
- *        be called with a NULL stream_name_utf16 before any non-NULL
- *        stream_name_utf16's.
- *
- * @lte:   WIM lookup table entry for the stream.  May be NULL to indicate
- *         a stream of length 0.
- *
- * @args:  Additional apply context, including flags indicating supported
- *         volume features.
- *
- * Returns 0 on success; nonzero on failure.
- */
 static int
-win32_extract_stream(const struct wim_dentry *dentry,
-                    const wchar_t *path,
-                    const wchar_t *stream_name_utf16,
-                    struct wim_lookup_table_entry *lte,
-                    struct apply_args *args)
+win32_set_reparse_data(const wchar_t *path, const u8 *rpbuf, u16 rpbuflen,
+                      struct apply_ctx *ctx)
 {
-       wchar_t *stream_path;
        HANDLE h;
-       int ret;
        DWORD err;
-       DWORD creationDisposition = CREATE_ALWAYS;
-       DWORD requestedAccess;
-       BY_HANDLE_FILE_INFORMATION file_info;
-       unsigned remaining_sharing_violations = 1000;
-       const struct wim_inode *inode = dentry->d_inode;
-
-       if (stream_name_utf16) {
-               /* Named stream.  Create a buffer that contains the UTF-16LE
-                * string [.\]path:stream_name_utf16.  This is needed to
-                * create and open the stream using CreateFileW().  I'm not
-                * aware of any other APIs to do this.  Note: the '$DATA' suffix
-                * seems to be unneeded.  Additional note: a ".\" prefix needs
-                * to be added when the path is a 1-character long relative path
-                * to avoid ambiguity with drive letters. */
-               size_t stream_path_nchars;
-               size_t path_nchars;
-               size_t stream_name_nchars;
-               const wchar_t *prefix;
-
-               path_nchars = wcslen(path);
-               stream_name_nchars = wcslen(stream_name_utf16);
-               stream_path_nchars = path_nchars + 1 + stream_name_nchars;
-               if (path_nchars == 1 && !is_any_path_separator(path[0])) {
-                       static const wchar_t _prefix[] =
-                               {L'.', OS_PREFERRED_PATH_SEPARATOR, L'\0'};
-                       prefix = _prefix;
-                       stream_path_nchars += 2;
-               } else {
-                       prefix = L"";
-               }
-               stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
-               swprintf(stream_path, L"%ls%ls:%ls",
-                        prefix, path, stream_name_utf16);
-       } else {
-               /* Unnamed stream; its path is just the path to the file itself.
-                * */
-               stream_path = (wchar_t*)path;
-
-               ret = win32_begin_extract_unnamed_stream(inode, lte, path,
-                                                        &creationDisposition,
-                                                        args->vol_flags);
-               if (ret)
-                       goto fail;
-       }
+       DWORD bytes_returned;
 
-       DEBUG("Opening \"%ls\"", stream_path);
-       requestedAccess = GENERIC_READ | GENERIC_WRITE;
-       /* DELETE access is needed for SetFileShortNameW(), for some reason.
-        * But don't request it for the extraction root, for the following
-        * reasons:
-        *
-        * - Requesting DELETE access on the extraction root will cause a
-        *   sharing violation if the extraction root is the current working
-        *   directory (".").
-        * - The extraction root may be extracted to a different name than given
-        *   in the WIM file, in which case the DOS name, if given, would not be
-        *   meaningful.
-        * - For full-image extractions, the root dentry is supposed to be
-        *   unnamed anyway.
-        * - Microsoft's ImageX does not extract the root directory.
-        */
-       if (dentry != args->extract_root)
-               requestedAccess |= DELETE;
-
-try_open_again:
-       /* Open the stream to be extracted.  Depending on what we have set
-        * creationDisposition to, we may be creating this for the first time,
-        * or we may be opening on existing stream we already created using
-        * CreateDirectoryW() or OpenEncryptedFileRawW(). */
-       h = CreateFileW(stream_path,
-                       requestedAccess,
-                       FILE_SHARE_READ,
-                       NULL,
-                       creationDisposition,
-                       win32_get_create_flags_and_attributes(inode->i_attributes),
-                       NULL);
-       if (h == INVALID_HANDLE_VALUE) {
-               err = GetLastError();
-               if (err == ERROR_ACCESS_DENIED &&
-                   win32_path_is_root_of_drive(stream_path))
-               {
-                       ret = 0;
-                       goto out;
-               }
-               if ((err == ERROR_PRIVILEGE_NOT_HELD ||
-                    err == ERROR_ACCESS_DENIED) &&
-                   (requestedAccess & ACCESS_SYSTEM_SECURITY))
-               {
-                       /* Try opening the file again without privilege to
-                        * modify SACL. */
-                       requestedAccess &= ~ACCESS_SYSTEM_SECURITY;
-                       goto try_open_again;
-               }
-               if (err == ERROR_SHARING_VIOLATION &&
-                   (inode->i_attributes & (FILE_ATTRIBUTE_ENCRYPTED |
-                                           FILE_ATTRIBUTE_DIRECTORY)) ==
-                       (FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_DIRECTORY))
-               {
-                       if (remaining_sharing_violations) {
-                               --remaining_sharing_violations;
-                               /* This can happen when restoring encrypted directories
-                                * for some reason.  Probably a bug in EncryptFile(). */
-                               WARNING("Couldn't open \"%ls\" due to sharing violation; "
-                                       "re-trying after 100ms", stream_path);
-                               Sleep(100);
-                               goto try_open_again;
-                       } else {
-                               ERROR("Too many sharing violations; giving up...");
-                       }
-               }
-               if (creationDisposition == OPEN_EXISTING)
-                       ERROR("Failed to open \"%ls\"", stream_path);
-               else
-                       ERROR("Failed to create \"%ls\"", stream_path);
-               win32_error(err);
-               ret = WIMLIB_ERR_OPEN;
-               goto fail;
-       }
+       h = CreateFile(path, GENERIC_WRITE, 0, NULL,
+                      OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS |
+                                     FILE_FLAG_OPEN_REPARSE_POINT,
+                      NULL);
+       if (h == INVALID_HANDLE_VALUE)
+               goto error;
 
-       /* Check the attributes of the file we just opened, and remove
-        * encryption or compression if either was set by default but is not
-        * supposed to be set based on the WIM inode attributes. */
-       if (!GetFileInformationByHandle(h, &file_info)) {
-               err = GetLastError();
-               ERROR("Failed to get attributes of \"%ls\"", stream_path);
-               win32_error(err);
-               ret = WIMLIB_ERR_STAT;
-               goto fail_close_handle;
-       }
+       if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT,
+                            (void*)rpbuf, rpbuflen,
+                            NULL, 0, &bytes_returned, NULL))
+               goto error_close_handle;
 
-       /* Remove encryption? */
-       if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED &&
-           !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED))
-       {
-               /* File defaulted to encrypted due to being in an encrypted
-                * directory, but is not actually supposed to be encrypted.
-                *
-                * This is a workaround, because I'm not aware of any way to
-                * directly (e.g. with CreateFileW()) create an unencrypted file
-                * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */
-               ret = win32_decrypt_file(h, stream_path);
-               if (ret)
-                       goto fail; /* win32_decrypt_file() closed the handle. */
-               creationDisposition = OPEN_EXISTING;
-               goto try_open_again;
-       }
-
-       /* Remove compression? */
-       if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED &&
-           !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED))
-       {
-               /* Similar to the encrypted case, above, if the file defaulted
-                * to compressed due to being in an compressed directory, but is
-                * not actually supposed to be compressed, explicitly set the
-                * compression format to COMPRESSION_FORMAT_NONE. */
-               ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE,
-                                                 stream_path);
-               if (ret)
-                       goto fail_close_handle;
-       }
+       if (!CloseHandle(h))
+               goto error;
 
-       /* Set compression and/or sparse attributes if needed */
-       ret = win32_set_special_stream_attributes(h, inode, lte, path,
-                                                 args->vol_flags);
+       return 0;
 
-       if (ret)
-               goto fail_close_handle;
-
-       /* At this point we have at least created the needed stream with the
-        * appropriate attributes.  We have yet to set the appropriate security
-        * descriptor and actually extract the stream data (other than for
-        * extracted files, which were already extracted).
-        * win32_finish_extract_stream() handles these additional steps, except
-        * the security descriptor. */
-       ret = win32_finish_extract_stream(h, dentry, lte, stream_path,
-                                         stream_name_utf16, args);
-       if (ret)
-               goto fail_close_handle;
-
-       /* Done extracting the stream.  Close the handle and return. */
-       DEBUG("Closing \"%ls\"", stream_path);
-       if (!CloseHandle(h)) {
-               err = GetLastError();
-               ERROR("Failed to close \"%ls\"", stream_path);
-               win32_error(err);
-               ret = WIMLIB_ERR_WRITE;
-               goto fail;
-       }
-       ret = 0;
-       goto out;
-fail_close_handle:
+error_close_handle:
+       err = GetLastError();
        CloseHandle(h);
-fail:
-       ERROR("Error extracting \"%ls\"", stream_path);
-out:
-       return ret;
+       SetLastError(err);
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_WRITE; /* XXX: need better error code */
 }
 
-/*
- * Creates a file, directory, or reparse point and extracts all streams to it
- * (unnamed data stream and/or reparse point stream, plus any alternate data
- * streams).  Handles sparse, compressed, and/or encrypted files.
- *
- * @dentry:    WIM dentry for this file or directory.
- * @path:      UTF-16LE external path to extract the inode to.
- * @args:      Additional extraction context.
- *
- * Returns 0 on success; nonzero on failure.
- */
 static int
-win32_extract_streams(const struct wim_dentry *dentry,
-                     const wchar_t *path, struct apply_args *args)
+win32_set_short_name(const wchar_t *path, const wchar_t *short_name,
+                    size_t short_name_nchars, struct apply_ctx *ctx)
 {
-       struct wim_lookup_table_entry *unnamed_lte;
-       int ret;
-       const struct wim_inode *inode = dentry->d_inode;
-
-       /* First extract the unnamed stream. */
-
-       unnamed_lte = inode_unnamed_lte_resolved(inode);
-       ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args);
-       if (ret)
-               goto out;
+       HANDLE h;
+       DWORD err;
 
-       /* Extract any named streams, if supported by the volume. */
+       h = CreateFile(path, GENERIC_WRITE | DELETE, 0, NULL,
+                      OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS |
+                                     FILE_FLAG_OPEN_REPARSE_POINT,
+                      NULL);
+       if (h == INVALID_HANDLE_VALUE)
+               goto error;
 
-       if (!(args->vol_flags & FILE_NAMED_STREAMS))
-               goto out;
-       for (u16 i = 0; i < inode->i_num_ads; i++) {
-               const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i];
-
-               /* Skip the unnamed stream if it's in the ADS entries (we
-                * already extracted it...) */
-               if (ads_entry->stream_name_nbytes == 0)
-                       continue;
-
-               /* Skip special UNIX data entries (see documentation for
-                * WIMLIB_ADD_FLAG_UNIX_DATA) */
-               if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES
-                   && !memcmp(ads_entry->stream_name,
-                              WIMLIB_UNIX_DATA_TAG_UTF16LE,
-                              WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES))
-                       continue;
-
-               /* Extract the named stream */
-               ret = win32_extract_stream(dentry,
-                                          path,
-                                          ads_entry->stream_name,
-                                          ads_entry->lte,
-                                          args);
-               if (ret)
-                       break;
+       if (short_name_nchars) {
+               if (!SetFileShortName(h, short_name))
+                       goto error_close_handle;
+       } else if (running_on_windows_7_or_later()) {
+               if (!SetFileShortName(h, L""))
+                       goto error_close_handle;
        }
-out:
-       return ret;
-}
 
-static int
-dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore)
-{
-       dentry->d_inode->i_visited = 0;
-       return 0;
-}
+       if (!CloseHandle(h))
+               goto error;
 
-static int
-dentry_get_features(struct wim_dentry *dentry, void *_features_p)
-{
-       DWORD features = 0;
-       DWORD *features_p = _features_p;
-       struct wim_inode *inode = dentry->d_inode;
-
-       if (inode->i_visited) {
-               features |= FILE_SUPPORTS_HARD_LINKS;
-       } else {
-               inode->i_visited = 1;
-               if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE)
-                       features |= FILE_SUPPORTS_SPARSE_FILES;
-               if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
-                       features |= FILE_SUPPORTS_REPARSE_POINTS;
-               for (unsigned i = 0; i < inode->i_num_ads; i++)
-                       if (inode->i_ads_entries[i].stream_name_nbytes)
-                               features |= FILE_NAMED_STREAMS;
-               if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
-                       features |= FILE_SUPPORTS_ENCRYPTION;
-               if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
-                       features |= FILE_FILE_COMPRESSION;
-               if (inode->i_security_id != -1)
-                       features |= FILE_PERSISTENT_ACLS;
-       }
-       *features_p |= features;
        return 0;
+
+error_close_handle:
+       err = GetLastError();
+       CloseHandle(h);
+       SetLastError(err);
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_WRITE; /* XXX: need better error code */
 }
 
-/* If not done already, load the supported feature flags for the volume onto
- * which the image is being extracted, and warn the user about any missing
- * features that could be important. */
 static int
-win32_check_vol_flags(const wchar_t *output_path,
-                     struct wim_dentry *root, struct apply_args *args)
+win32_set_security_descriptor(const wchar_t *path, const u8 *desc, size_t desc_size,
+                             struct apply_ctx *ctx, bool strict)
 {
-       DWORD dentry_features = 0;
-       DWORD missing_features;
-
-       if (args->have_vol_flags)
-               return 0;
-
-       for_dentry_in_tree(root, dentry_clear_inode_visited, NULL);
-       for_dentry_in_tree(root, dentry_get_features, &dentry_features);
-
-       win32_get_vol_flags(output_path, &args->vol_flags);
-       args->have_vol_flags = true;
-
-       missing_features = dentry_features & ~args->vol_flags;
-
-       /* Warn the user about data that may not be extracted. */
-       if (missing_features & FILE_SUPPORTS_SPARSE_FILES)
-               WARNING("Volume does not support sparse files!\n"
-                       "          Sparse files will be extracted as non-sparse.");
-       if (missing_features & FILE_SUPPORTS_REPARSE_POINTS)
-               WARNING("Volume does not support reparse points!\n"
-                       "          Reparse point data will not be extracted.");
-       if (missing_features & FILE_NAMED_STREAMS) {
-               WARNING("Volume does not support named data streams!\n"
-                       "          Named data streams will not be extracted.");
-       }
-       if (missing_features & FILE_SUPPORTS_ENCRYPTION) {
-               WARNING("Volume does not support encryption!\n"
-                       "          Encrypted files will be extracted as raw data.");
-       }
-       if (missing_features & FILE_FILE_COMPRESSION) {
-               WARNING("Volume does not support transparent compression!\n"
-                       "          Compressed files will be extracted as non-compressed.");
-       }
-       if (missing_features & FILE_PERSISTENT_ACLS) {
-               if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
-                       ERROR("Strict ACLs requested, but the volume does not "
-                             "support ACLs!");
-                       return WIMLIB_ERR_VOLUME_LACKS_FEATURES;
-               } else {
-                       WARNING("Volume does not support persistent ACLS!\n"
-                               "          File permissions will not be extracted.");
+       SECURITY_INFORMATION info;
+
+       info = OWNER_SECURITY_INFORMATION |
+               GROUP_SECURITY_INFORMATION |
+               DACL_SECURITY_INFORMATION  |
+               SACL_SECURITY_INFORMATION;
+retry:
+       if (!SetFileSecurity(path, info, (PSECURITY_DESCRIPTOR)desc)) {
+               if (!strict && GetLastError() == ERROR_PRIVILEGE_NOT_HELD &&
+                   (info & SACL_SECURITY_INFORMATION))
+               {
+                       info &= ~SACL_SECURITY_INFORMATION;
+                       goto retry;
                }
-       }
-       if (running_on_windows_7_or_later() &&
-           (missing_features & FILE_SUPPORTS_HARD_LINKS))
-       {
-               WARNING("Volume does not support hard links!\n"
-                       "          Hard links will be extracted as duplicate files.");
+               goto error;
        }
        return 0;
+
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_SET_SECURITY;
 }
 
-/*
- * Try extracting a hard link.
- *
- * @output_path:  Path to link to be extracted.
- *
- * @inode:        WIM inode that the link is to; inode->i_extracted_file
- *               the path to a name of the file that has already been
- *               extracted (we use this to create the hard link).
- *
- * @args:         Additional apply context, used here to keep track of
- *                the number of times creating a hard link failed due to
- *                ERROR_INVALID_FUNCTION.  This error should indicate that hard
- *                links are not supported by the volume, and we would like to
- *                warn the user a few times, but not too many times.
- *
- * Returns 0 if the hard link was successfully extracted.  Returns
- * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly
- * being unsupported by the volume.  Returns a negative value if creating the
- * hard link failed due to ERROR_INVALID_FUNCTION.
- */
 static int
-win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
-                   struct apply_args *args)
+win32_set_timestamps(const wchar_t *path, u64 creation_time,
+                    u64 last_write_time, u64 last_access_time,
+                    struct apply_ctx *ctx)
 {
+       HANDLE h;
        DWORD err;
+       FILETIME creationTime = {.dwLowDateTime = creation_time & 0xffffffff,
+                                .dwHighDateTime = creation_time >> 32};
+       FILETIME lastAccessTime = {.dwLowDateTime = last_access_time & 0xffffffff,
+                                 .dwHighDateTime = last_access_time >> 32};
+       FILETIME lastWriteTime = {.dwLowDateTime = last_write_time & 0xffffffff,
+                                 .dwHighDateTime = last_write_time >> 32};
+
+       h = CreateFile(path, FILE_WRITE_ATTRIBUTES, 0, NULL,
+                      OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS |
+                                     FILE_FLAG_OPEN_REPARSE_POINT,
+                      NULL);
+       if (h == INVALID_HANDLE_VALUE)
+               goto error;
+
+       if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime))
+               goto error_close_handle;
+
+       if (!CloseHandle(h))
+               goto error;
 
-       /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
-        * but it's only available on Windows 7 and later.
-        *
-        * Otherwise, CreateHardLinkW() will apparently return
-        * ERROR_INVALID_FUNCTION if the volume does not support hard links. */
-
-       DEBUG("Creating hard link \"%ls => %ls\"",
-             output_path, inode->i_extracted_file);
-
-       if (running_on_windows_7_or_later() &&
-           !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS))
-               goto hard_links_unsupported;
-
-       if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
-               return 0;
+       return 0;
 
+error_close_handle:
        err = GetLastError();
-       if (err != ERROR_INVALID_FUNCTION) {
-               ERROR("Can't create hard link \"%ls => %ls\"",
-                     output_path, inode->i_extracted_file);
-               win32_error(err);
-               return WIMLIB_ERR_LINK;
-       }
-hard_links_unsupported:
-       args->num_hard_links_failed++;
-       if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
-               if (running_on_windows_7_or_later())
-               {
-                       WARNING("Extracting duplicate copy of \"%ls\" "
-                               "rather than hard link", output_path);
-               } else {
-                       WARNING("Can't create hard link \"%ls\" => \"%ls\":\n"
-                               "          Volume does not support hard links!\n"
-                               "          Falling back to extracting a copy of the file.",
-                               output_path, inode->i_extracted_file);
-               }
-       }
-       if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS)
-               WARNING("Suppressing further hard linking warnings...");
-       return -1;
-}
-
-/* Extract a file, directory, reparse point, or hard link to an
- * already-extracted file using the Win32 API */
-int
-win32_do_apply_dentry(const wchar_t *output_path,
-                     size_t output_path_num_chars,
-                     struct wim_dentry *dentry,
-                     struct apply_args *args)
-{
-       int ret;
-       struct wim_inode *inode = dentry->d_inode;
-
-       ret = win32_check_vol_flags(output_path, dentry, args);
-       if (ret)
-               return ret;
-       if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
-               /* Linked file, with another name already extracted.  Create a
-                * hard link. */
-               ret = win32_try_hard_link(output_path, inode, args);
-               if (ret >= 0)
-                       return ret;
-               /* Negative return value from win32_try_hard_link() indicates
-                * that hard links are probably not supported by the volume.
-                * Fall back to extracting a copy of the file. */
-       }
-
-       /* If this is a reparse point and the volume does not support reparse
-        * points, just skip it completely. */
-       if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
-           !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
-       {
-               WARNING("Not extracting reparse point \"%ls\"", output_path);
-               dentry->not_extracted = 1;
-       } else {
-               /* Create the file, directory, or reparse point, and extract the
-                * data streams. */
-               ret = win32_extract_streams(dentry, output_path, args);
-               if (ret)
-                       return ret;
-       }
-       if (inode->i_extracted_file == NULL) {
-               const struct wim_lookup_table_entry *lte;
-
-               /* Tally bytes extracted, including all alternate data streams,
-                * unless we extracted a hard link (or, at least extracted a
-                * name that was supposed to be a hard link) */
-               for (unsigned i = 0; i <= inode->i_num_ads; i++) {
-                       lte = inode_stream_lte_resolved(inode, i);
-                       if (lte)
-                               args->progress.extract.completed_bytes +=
-                                                       wim_resource_size(lte);
-               }
-               if (inode->i_nlink > 1) {
-                       /* Save extracted path for a later call to
-                        * CreateHardLinkW() if this inode has multiple links.
-                        * */
-                       inode->i_extracted_file = WCSDUP(output_path);
-                       if (!inode->i_extracted_file)
-                               return WIMLIB_ERR_NOMEM;
-               }
-       }
-       return 0;
+       CloseHandle(h);
+       SetLastError(err);
+error:
+       set_errno_from_GetLastError();
+       return WIMLIB_ERR_SET_TIMESTAMPS;
 }
 
-/* Set timestamps on an extracted file using the Win32 API */
-int
-win32_do_apply_dentry_timestamps(const wchar_t *path,
-                                size_t path_num_chars,
-                                struct wim_dentry *dentry,
-                                struct apply_args *args)
-{
-       DWORD err;
-       HANDLE h;
-       const struct wim_inode *inode = dentry->d_inode;
-
-       /* On Windows we also use the timestamps pass to apply security
-        * descriptors.  This is because it seems we really need the depth-first
-        * behavior: in particular, Windows contains files like
-        * \Windows\Registration\CRMLog that have funny permissions and don't
-        * even let the administrator with SE_RESTORE_NAME open (at least, with
-        * privileges other than FILE_WRITE_ATTRIBUTES as we do below) after the
-        * security descriptor has been applied.  */
-       if (inode->i_security_id >= 0 &&
-               !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)
-               && (args->vol_flags & FILE_PERSISTENT_ACLS))
-       {
-               int ret = win32_set_security_data(inode, NULL, path, args);
-               if (ret)
-                       return ret;
-       }
-
-       /* Windows doesn't let you change the timestamps of the root directory
-        * (at least on FAT, which is dumb but expected since FAT doesn't store
-        * any metadata about the root directory...) */
-       if (win32_path_is_root_of_drive(path))
-               return 0;
-
-       DEBUG("Opening \"%ls\" to set timestamps", path);
-       h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES);
-       if (h == INVALID_HANDLE_VALUE) {
-               err = GetLastError();
-               goto fail;
-       }
-
-       FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
-                                .dwHighDateTime = inode->i_creation_time >> 32};
-       FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
-                                 .dwHighDateTime = inode->i_last_access_time >> 32};
-       FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
-                                 .dwHighDateTime = inode->i_last_write_time >> 32};
-
-       DEBUG("Calling SetFileTime() on \"%ls\"", path);
-       if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
-               err = GetLastError();
-               CloseHandle(h);
-               goto fail;
-       }
-       DEBUG("Closing \"%ls\"", path);
-       if (!CloseHandle(h)) {
-               err = GetLastError();
-               goto fail;
-       }
-       goto out;
-fail:
-       /* Only warn if setting timestamps failed; still return 0. */
-       WARNING("Can't set timestamps on \"%ls\"", path);
-       win32_error(err);
-out:
-       return 0;
-}
+const struct apply_operations win32_apply_ops = {
+       .name = L"Win32",
+
+       .target_is_root           = win32_path_is_root_of_drive,
+       .start_extract            = win32_start_extract,
+       .create_file              = win32_create_file,
+       .create_directory         = win32_create_directory,
+       .create_hardlink          = win32_create_hardlink,
+       .extract_unnamed_stream   = win32_extract_unnamed_stream,
+       .extract_named_stream     = win32_extract_named_stream,
+       .extract_encrypted_stream = win32_extract_encrypted_stream,
+       .set_file_attributes      = win32_set_file_attributes,
+       .set_reparse_data         = win32_set_reparse_data,
+       .set_short_name           = win32_set_short_name,
+       .set_security_descriptor  = win32_set_security_descriptor,
+       .set_timestamps           = win32_set_timestamps,
+
+       .path_prefix = L"\\\\?\\",
+       .path_prefix_nchars = 4,
+       .path_separator = L'\\',
+       .path_max = 32768,
+
+       .requires_realtarget_in_paths = 1,
+       .realpath_works_on_nonexisting_files = 1,
+       .root_directory_is_special = 1,
+};
 
 #endif /* __WIN32__ */
index 2e501dba3bd5b48480fc4ee53bf40f28fcb409e3..9b16796a5e4bb6f0ac6e0e9fde4f86bbf339f580 100644 (file)
@@ -787,7 +787,7 @@ win32_capture_stream(const wchar_t *path,
                        goto out_free_spath;
                lte->resource_entry.original_size = encrypted_size;
        } else {
-               lte->resource_location = RESOURCE_WIN32;
+               lte->resource_location = RESOURCE_IN_FILE_ON_DISK;
                lte->resource_entry.original_size = (u64)dat->StreamSize.QuadPart;
        }
 
@@ -1167,7 +1167,7 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        if (ret)
                return ret;
 
-       win32_get_vol_flags(root_disk_path, &vol_flags);
+       win32_get_vol_flags(root_disk_path, &vol_flags, NULL);
 
        /* WARNING: There is no check for overflow later when this buffer is
         * being used!  But it's as long as the maximum path length understood
index e5d6a386f26fd3dfae8595ef3837bfcea580f15b..fc55709a7a7ce42090cc219aac5302772f999323 100644 (file)
@@ -363,6 +363,12 @@ 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);
@@ -374,23 +380,23 @@ win32_path_is_root_of_drive(const wchar_t *path)
                if (!is_any_path_separator(*p))
                        return false;
        return true;
-
-       /* XXX This function does not handle paths like "c:" where the working
-        * directory on "c:" is actually "c:\", or weird paths like "\.".  But
-        * currently the capture and apply code always prefixes the paths with
-        * \\?\ anyway so this is irrelevant... */
 }
 
 
 /* Given a path, which may not yet exist, get a set of flags that describe the
  * features of the volume the path is on. */
 int
-win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret)
+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 (supports_SetFileShortName_ret)
+               *supports_SetFileShortName_ret = false;
 
        drive_spec_len = win32_path_drive_spec_len(path);
 
@@ -412,21 +418,33 @@ win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret)
                volume[drive_spec_len] = L'\\';
                volume[drive_spec_len + 1] = L'\0';
        }
-       bret = GetVolumeInformationW(volume, /* lpRootPathName */
-                                    NULL,  /* lpVolumeNameBuffer */
-                                    0,     /* nVolumeNameSize */
-                                    NULL,  /* lpVolumeSerialNumber */
-                                    NULL,  /* lpMaximumComponentLength */
-                                    &vol_flags, /* lpFileSystemFlags */
-                                    NULL,  /* lpFileSystemNameBuffer */
-                                    0);    /* nFileSystemNameSize */
+       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) {
                DWORD err = GetLastError();
                WARNING("Failed to get volume information for path \"%ls\"", path);
                win32_error(err);
                vol_flags = 0xffffffff;
+               goto out;
+       }
+
+       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;
+
+               if (supports_SetFileShortName_ret)
+                       *supports_SetFileShortName_ret = true;
        }
 
+out:
        DEBUG("using vol_flags = %x", vol_flags);
        *vol_flags_ret = vol_flags;
        return 0;
index 36cc8a357e884a177c41b89eb9eee7c2493c7b02..c9a59bab0a90e3fd4d03aff965821362e552a487 100644 (file)
@@ -177,12 +177,15 @@ do_pread_or_pwrite(int fd, void *buf, size_t count, off_t offset,
        OVERLAPPED overlapped;
        BOOL bret;
 
-       wimlib_assert(count <= 0xffffffff);
-
        h = (HANDLE)_get_osfhandle(fd);
        if (h == INVALID_HANDLE_VALUE)
                goto err;
 
+       if (GetFileType(h) == FILE_TYPE_PIPE) {
+               errno = ESPIPE;
+               goto err;
+       }
+
        /* Get original position */
        relative_offset.QuadPart = 0;
        if (!SetFilePointerEx(h, relative_offset, &orig_offset, FILE_CURRENT))
index 3a5ea5762506f2fbabf75f1cf94456f026bb3c80..96ada4929be1316e353091aa547e64f32b14960a 100644 (file)
 #include <fcntl.h>
 #include <errno.h>
 
-#ifdef WITH_NTFS_3G
-#  include <time.h>
-#  include <ntfs-3g/attrib.h>
-#  include <ntfs-3g/inode.h>
-#  include <ntfs-3g/dir.h>
-#endif
-
 #ifdef HAVE_ALLOCA_H
 #  include <alloca.h>
 #else
@@ -80,7 +73,6 @@
  * the WIM.  (This is not the on-disk format; the on-disk format just has an
  * array of offsets.) */
 struct chunk_table {
-       off_t file_offset;
        u64 original_resource_size;
        u64 num_chunks;
        u64 table_disk_size;
@@ -96,30 +88,32 @@ struct chunk_table {
        u8 offsets[] _aligned_attribute(8);
 };
 
-/*
- * Allocates and initializes a chunk table, and reserves space for it in the
- * output file.
- */
+/* Allocate and initializes a chunk table, then reserve space for it in the
+ * output file unless writing a pipable resource.  */
 static int
 begin_wim_resource_chunk_tab(const struct wim_lookup_table_entry *lte,
-                            int out_fd,
-                            off_t file_offset,
-                            struct chunk_table **chunk_tab_ret)
+                            struct filedes *out_fd,
+                            struct chunk_table **chunk_tab_ret,
+                            int resource_flags)
 {
-       u64 size = wim_resource_size(lte);
-       u64 num_chunks = wim_resource_chunks(lte);
-       unsigned bytes_per_chunk_entry = (size > (1ULL << 32)) ? 8 : 4;
-       size_t alloc_size = sizeof(struct chunk_table) + num_chunks * sizeof(u64);
-       struct chunk_table *chunk_tab = CALLOC(1, alloc_size);
+       u64 size;
+       u64 num_chunks;
+       unsigned bytes_per_chunk_entry;
+       size_t alloc_size;
+       struct chunk_table *chunk_tab;
+       int ret;
 
-       DEBUG("Beginning chunk table for stream with size %"PRIu64, size);
+       size = wim_resource_size(lte);
+       num_chunks = wim_resource_chunks(lte);
+       bytes_per_chunk_entry = (size > (1ULL << 32)) ? 8 : 4;
+       alloc_size = sizeof(struct chunk_table) + num_chunks * sizeof(u64);
+       chunk_tab = CALLOC(1, alloc_size);
 
        if (!chunk_tab) {
                ERROR("Failed to allocate chunk table for %"PRIu64" byte "
                      "resource", size);
                return WIMLIB_ERR_NOMEM;
        }
-       chunk_tab->file_offset = file_offset;
        chunk_tab->num_chunks = num_chunks;
        chunk_tab->original_resource_size = size;
        chunk_tab->bytes_per_chunk_entry = bytes_per_chunk_entry;
@@ -127,16 +121,23 @@ begin_wim_resource_chunk_tab(const struct wim_lookup_table_entry *lte,
                                     (num_chunks - 1);
        chunk_tab->cur_offset_p = chunk_tab->offsets;
 
-       /* We don't know the correct offsets yet; this just writes zeroes to
+       /* We don't know the correct offsets yet; so just write zeroes to
         * reserve space for the table, so we can go back to it later after
-        * we've written the compressed chunks following it. */
-       if (full_write(out_fd, chunk_tab->offsets,
-                      chunk_tab->table_disk_size) != chunk_tab->table_disk_size)
-       {
-               ERROR_WITH_ERRNO("Failed to write chunk table in compressed "
-                                "file resource");
-               FREE(chunk_tab);
-               return WIMLIB_ERR_WRITE;
+        * we've written the compressed chunks following it.
+        *
+        * Special case: if writing a pipable WIM, compressed resources are in a
+        * modified format (see comment above write_pipable_wim()) and do not
+        * have a chunk table at the beginning, so don't reserve any space for
+        * one.  */
+       if (!(resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE)) {
+               ret = full_write(out_fd, chunk_tab->offsets,
+                                chunk_tab->table_disk_size);
+               if (ret) {
+                       ERROR_WITH_ERRNO("Failed to write chunk table in compressed "
+                                        "file resource");
+                       FREE(chunk_tab);
+                       return ret;
+               }
        }
        *chunk_tab_ret = chunk_tab;
        return 0;
@@ -193,100 +194,81 @@ get_compress_func(int out_ctype)
                return wimlib_xpress_compress;
 }
 
-/*
- * Writes a chunk of a WIM resource to an output file.
- *
- * @chunk:       Uncompressed data of the chunk.
- * @chunk_size:          Size of the chunk (<= WIM_CHUNK_SIZE)
- * @out_fd:      File descriptor to write the chunk to.
- * @compress:     Compression function to use (NULL if writing uncompressed
- *                     data).
- * @chunk_tab:   Pointer to chunk table being created.  It is updated with the
- *                     offset of the chunk we write.
- *
- * Returns 0 on success; nonzero on failure.
- */
+/* Finishes a WIM chunk table and writes it to the output file at the correct
+ * offset.  */
 static int
-write_wim_resource_chunk(const void * restrict chunk,
-                        unsigned chunk_size,
-                        int out_fd,
-                        compress_func_t compress,
-                        struct chunk_table * restrict chunk_tab)
+finish_wim_resource_chunk_tab(struct chunk_table *chunk_tab,
+                             struct filedes *out_fd,
+                             off_t res_start_offset,
+                             int write_resource_flags)
 {
-       const void *out_chunk;
-       unsigned out_chunk_size;
-       if (compress) {
-               void *compressed_chunk = alloca(chunk_size);
+       int ret;
 
-               out_chunk_size = (*compress)(chunk, chunk_size, compressed_chunk);
-               if (out_chunk_size) {
-                       /* Write compressed */
-                       out_chunk = compressed_chunk;
-               } else {
-                       /* Write uncompressed */
-                       out_chunk = chunk;
-                       out_chunk_size = chunk_size;
-               }
-               chunk_tab_record_chunk(chunk_tab, out_chunk_size);
+       if (write_resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE) {
+               ret = full_write(out_fd,
+                                chunk_tab->offsets +
+                                        chunk_tab->bytes_per_chunk_entry,
+                                chunk_tab->table_disk_size);
        } else {
-               /* Write uncompressed */
-               out_chunk = chunk;
-               out_chunk_size = chunk_size;
+               ret  = full_pwrite(out_fd,
+                                  chunk_tab->offsets +
+                                          chunk_tab->bytes_per_chunk_entry,
+                                  chunk_tab->table_disk_size,
+                                  res_start_offset);
        }
-       if (full_write(out_fd, out_chunk, out_chunk_size) != out_chunk_size) {
-               ERROR_WITH_ERRNO("Failed to write WIM resource chunk");
-               return WIMLIB_ERR_WRITE;
+       if (ret) {
+               ERROR_WITH_ERRNO("Failed to write chunk table in compressed "
+                                "file resource");
        }
-       return 0;
+       return ret;
 }
 
-/*
- * Finishes a WIM chunk table and writes it to the output file at the correct
- * offset.
- *
- * The final size of the full compressed resource is returned in the
- * @compressed_size_p.
+/* Write the header for a stream in a pipable WIM.
  */
 static int
-finish_wim_resource_chunk_tab(struct chunk_table *chunk_tab,
-                             int out_fd, u64 *compressed_size_p)
+write_pwm_stream_header(const struct wim_lookup_table_entry *lte,
+                       struct filedes *out_fd,
+                       int additional_reshdr_flags)
 {
-       size_t bytes_written;
+       struct pwm_stream_hdr stream_hdr;
+       u32 reshdr_flags;
+       int ret;
 
-       bytes_written = full_pwrite(out_fd,
-                                   chunk_tab->offsets + chunk_tab->bytes_per_chunk_entry,
-                                   chunk_tab->table_disk_size,
-                                   chunk_tab->file_offset);
-       if (bytes_written != chunk_tab->table_disk_size) {
-               ERROR_WITH_ERRNO("Failed to write chunk table in compressed "
-                                "file resource");
-               return WIMLIB_ERR_WRITE;
+       stream_hdr.magic = PWM_STREAM_MAGIC;
+       stream_hdr.uncompressed_size = cpu_to_le64(lte->resource_entry.original_size);
+       if (additional_reshdr_flags & PWM_RESHDR_FLAG_UNHASHED) {
+               zero_out_hash(stream_hdr.hash);
+       } else {
+               wimlib_assert(!lte->unhashed);
+               copy_hash(stream_hdr.hash, lte->hash);
        }
-       if (chunk_tab->bytes_per_chunk_entry == 4)
-               *compressed_size_p = chunk_tab->cur_offset_u32 + chunk_tab->table_disk_size;
-       else
-               *compressed_size_p = chunk_tab->cur_offset_u64 + chunk_tab->table_disk_size;
-       return 0;
+
+       reshdr_flags = lte->resource_entry.flags & ~WIM_RESHDR_FLAG_COMPRESSED;
+       reshdr_flags |= additional_reshdr_flags;
+       stream_hdr.flags = cpu_to_le32(reshdr_flags);
+       ret = full_write(out_fd, &stream_hdr, sizeof(stream_hdr));
+       if (ret)
+               ERROR_WITH_ERRNO("Error writing stream header");
+       return ret;
 }
 
 static int
-seek_and_truncate(int out_fd, off_t offset)
+seek_and_truncate(struct filedes *out_fd, off_t offset)
 {
-       if (lseek(out_fd, offset, SEEK_SET) == -1 ||
-           ftruncate(out_fd, offset))
+       if (filedes_seek(out_fd, offset) == -1 ||
+           ftruncate(out_fd->fd, offset))
        {
                ERROR_WITH_ERRNO("Failed to truncate output WIM file");
                return WIMLIB_ERR_WRITE;
-       } else {
-               return 0;
        }
+       return 0;
 }
 
 static int
-finalize_and_check_sha1(SHA_CTX * restrict sha_ctx,
-                       struct wim_lookup_table_entry * restrict lte)
+finalize_and_check_sha1(SHA_CTX *sha_ctx, struct wim_lookup_table_entry *lte)
 {
        u8 md[SHA1_HASH_SIZE];
+
        sha1_final(md, sha_ctx);
        if (lte->unhashed) {
                copy_hash(lte->hash, md);
@@ -302,45 +284,99 @@ finalize_and_check_sha1(SHA_CTX * restrict sha_ctx,
        return 0;
 }
 
-
 struct write_resource_ctx {
        compress_func_t compress;
        struct chunk_table *chunk_tab;
-       int out_fd;
+       struct filedes *out_fd;
        SHA_CTX sha_ctx;
        bool doing_sha;
+       int resource_flags;
 };
 
 static int
-write_resource_cb(const void *restrict chunk, size_t chunk_size,
-                 void *restrict _ctx)
+write_resource_cb(const void *chunk, size_t chunk_size, void *_ctx)
 {
        struct write_resource_ctx *ctx = _ctx;
+       const void *out_chunk;
+       unsigned out_chunk_size;
+       int ret;
 
        if (ctx->doing_sha)
                sha1_update(&ctx->sha_ctx, chunk, chunk_size);
-       return write_wim_resource_chunk(chunk, chunk_size,
-                                       ctx->out_fd, ctx->compress,
-                                       ctx->chunk_tab);
+
+       out_chunk = chunk;
+       out_chunk_size = chunk_size;
+       if (ctx->compress) {
+               void *compressed_chunk;
+               unsigned compressed_size;
+
+               /* Compress the chunk.  */
+               compressed_chunk = alloca(chunk_size);
+               compressed_size = (*ctx->compress)(chunk, chunk_size,
+                                                  compressed_chunk);
+
+               /* Use compressed data if compression to less than input size
+                * was successful.  */
+               if (compressed_size) {
+                       out_chunk = compressed_chunk;
+                       out_chunk_size = compressed_size;
+               }
+       }
+
+       if (ctx->chunk_tab) {
+               /* Update chunk table accounting.  */
+               chunk_tab_record_chunk(ctx->chunk_tab, out_chunk_size);
+
+               /* If writing compressed chunks to a pipable WIM, before the
+                * chunk data write a chunk header that provides the compressed
+                * chunk size.  */
+               if (ctx->resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE) {
+                       struct pwm_chunk_hdr chunk_hdr = {
+                               .compressed_size = cpu_to_le32(out_chunk_size),
+                       };
+                       ret = full_write(ctx->out_fd, &chunk_hdr,
+                                        sizeof(chunk_hdr));
+                       if (ret)
+                               goto error;
+               }
+       }
+
+       /* Write the chunk data.  */
+       ret = full_write(ctx->out_fd, out_chunk, out_chunk_size);
+       if (ret)
+               goto error;
+       return 0;
+
+error:
+       ERROR_WITH_ERRNO("Failed to write WIM resource chunk");
+       return ret;
 }
 
 /*
+ * write_wim_resource()-
+ *
  * Write a resource to an output WIM.
  *
- * @lte:  Lookup table entry for the resource, which could be in another WIM,
- *        in an external file, or in another location.
+ * @lte:
+ *     Lookup table entry for the resource, which could be in another WIM, in
+ *     an external file, or in another location.
  *
- * @out_fd:  File descriptor opened to the output WIM.
+ * @out_fd:
+ *     File descriptor opened to the output WIM.
  *
- * @out_ctype:  One of the WIMLIB_COMPRESSION_TYPE_* constants to indicate
- *              which compression algorithm to use.
+ * @out_ctype:
+ *     One of the WIMLIB_COMPRESSION_TYPE_* constants to indicate which
+ *     compression algorithm to use.
  *
- * @out_res_entry:  On success, this is filled in with the offset, flags,
- *                  compressed size, and uncompressed size of the resource
- *                  in the output WIM.
+ * @out_res_entry:
+ *     On success, this is filled in with the offset, flags, compressed size,
+ *     and uncompressed size of the resource in the output WIM.
  *
- * @flags:  WIMLIB_RESOURCE_FLAG_RECOMPRESS to force data to be recompressed
- *          even if it could otherwise be copied directly from the input.
+ * @write_resource_flags:
+ *     * WIMLIB_WRITE_RESOURCE_FLAG_RECOMPRESS to force data to be recompressed even
+ *       if it could otherwise be copied directly from the input;
+ *     * WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE if writing a resource for a pipable WIM
+ *       (and the output file descriptor may be a pipe).
  *
  * Additional notes:  The SHA1 message digest of the uncompressed data is
  * calculated (except when doing a raw copy --- see below).  If the @unhashed
@@ -350,36 +386,43 @@ write_resource_cb(const void *restrict chunk, size_t chunk_size,
  */
 int
 write_wim_resource(struct wim_lookup_table_entry *lte,
-                  int out_fd, int out_ctype,
+                  struct filedes *out_fd, int out_ctype,
                   struct resource_entry *out_res_entry,
-                  int flags)
+                  int resource_flags)
 {
        struct write_resource_ctx write_ctx;
+       off_t res_start_offset;
        u64 read_size;
-       u64 new_size;
-       off_t offset;
        int ret;
 
-       flags &= ~WIMLIB_RESOURCE_FLAG_RECOMPRESS;
+       /* Mask out any irrelevant flags, since this function also uses this
+        * variable to store WIMLIB_READ_RESOURCE flags.  */
+       resource_flags &= WIMLIB_WRITE_RESOURCE_MASK;
 
-       /* Get current position in output WIM */
-       offset = filedes_offset(out_fd);
-       if (offset == -1) {
-               ERROR_WITH_ERRNO("Can't get position in output WIM");
-               return WIMLIB_ERR_WRITE;
-       }
+       /* Get current position in output WIM.  */
+       res_start_offset = out_fd->offset;
 
        /* If we are not forcing the data to be recompressed, and the input
         * resource is located in a WIM with the same compression type as that
         * desired other than no compression, we can simply copy the compressed
         * data without recompressing it.  This also means we must skip
-        * calculating the SHA1, as we never will see the uncompressed data. */
-       if (!(flags & WIMLIB_RESOURCE_FLAG_RECOMPRESS) &&
-           lte->resource_location == RESOURCE_IN_WIM &&
+        * calculating the SHA1, as we never will see the uncompressed data.  */
+       if (lte->resource_location == RESOURCE_IN_WIM &&
+           out_ctype == wim_resource_compression_type(lte) &&
            out_ctype != WIMLIB_COMPRESSION_TYPE_NONE &&
-           lte->wim->compression_type == out_ctype)
+           !(resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_RECOMPRESS))
        {
-               flags |= WIMLIB_RESOURCE_FLAG_RAW;
+               /* Normally we can request a RAW_FULL read, but if we're reading
+                * from a pipable resource and writing a non-pipable resource or
+                * vice versa, then a RAW_CHUNKS read needs to be requested so
+                * that the written resource can be appropriately formatted.
+                * However, in neither case is any actual decompression needed.
+                */
+               if (lte->is_pipable == !!(resource_flags &
+                                         WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE))
+                       resource_flags |= WIMLIB_READ_RESOURCE_FLAG_RAW_FULL;
+               else
+                       resource_flags |= WIMLIB_READ_RESOURCE_FLAG_RAW_CHUNKS;
                write_ctx.doing_sha = false;
                read_size = lte->resource_entry.size;
        } else {
@@ -388,27 +431,43 @@ write_wim_resource(struct wim_lookup_table_entry *lte,
                read_size = lte->resource_entry.original_size;
        }
 
-       /* Initialize the chunk table and set the compression function if
-        * compressing the resource. */
-       if (out_ctype == WIMLIB_COMPRESSION_TYPE_NONE ||
-           (flags & WIMLIB_RESOURCE_FLAG_RAW)) {
-               write_ctx.compress = NULL;
-               write_ctx.chunk_tab = NULL;
-       } else {
-               write_ctx.compress = get_compress_func(out_ctype);
-               ret = begin_wim_resource_chunk_tab(lte, out_fd,
-                                                  offset,
-                                                  &write_ctx.chunk_tab);
+       /* If the output resource is to be compressed, initialize the chunk
+        * table and set the function to use for chunk compression.  Exceptions:
+        * no compression function is needed if doing a raw copy; also, no chunk
+        * table is needed if doing a *full* (not per-chunk) raw copy.  */
+       write_ctx.compress = NULL;
+       write_ctx.chunk_tab = NULL;
+       if (out_ctype != WIMLIB_COMPRESSION_TYPE_NONE) {
+               if (!(resource_flags & WIMLIB_READ_RESOURCE_FLAG_RAW))
+                       write_ctx.compress = get_compress_func(out_ctype);
+               if (!(resource_flags & WIMLIB_READ_RESOURCE_FLAG_RAW_FULL)) {
+                       ret = begin_wim_resource_chunk_tab(lte, out_fd,
+                                                          &write_ctx.chunk_tab,
+                                                          resource_flags);
+                       if (ret)
+                               goto out;
+               }
+       }
+
+       /* If writing a pipable resource, write the stream header and update
+        * @res_start_offset to be the end of the stream header.  */
+       if (resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE) {
+               int reshdr_flags = 0;
+               if (out_ctype != WIMLIB_COMPRESSION_TYPE_NONE)
+                       reshdr_flags |= WIM_RESHDR_FLAG_COMPRESSED;
+               ret = write_pwm_stream_header(lte, out_fd, reshdr_flags);
                if (ret)
-                       return ret;
+                       goto out_free_chunk_tab;
+               res_start_offset = out_fd->offset;
        }
 
        /* Write the entire resource by reading the entire resource and feeding
         * the data through the write_resource_cb function. */
        write_ctx.out_fd = out_fd;
+       write_ctx.resource_flags = resource_flags;
 try_write_again:
        ret = read_resource_prefix(lte, read_size,
-                                  write_resource_cb, &write_ctx, flags);
+                                  write_resource_cb, &write_ctx, resource_flags);
        if (ret)
                goto out_free_chunk_tab;
 
@@ -420,49 +479,107 @@ try_write_again:
                        goto out_free_chunk_tab;
        }
 
-       out_res_entry->flags = lte->resource_entry.flags;
-       out_res_entry->original_size = wim_resource_size(lte);
-       out_res_entry->offset = offset;
-       if (flags & WIMLIB_RESOURCE_FLAG_RAW) {
-               /* Doing a raw write:  The new compressed size is the same as
-                * the compressed size in the other WIM. */
-               new_size = lte->resource_entry.size;
-       } else if (out_ctype == WIMLIB_COMPRESSION_TYPE_NONE) {
-               /* Using WIMLIB_COMPRESSION_TYPE_NONE:  The new compressed size
-                * is the original size. */
-               new_size = lte->resource_entry.original_size;
-               out_res_entry->flags &= ~WIM_RESHDR_FLAG_COMPRESSED;
-       } else {
-               /* Using a different compression type:  Call
-                * finish_wim_resource_chunk_tab() and it will provide the new
-                * compressed size. */
-               ret = finish_wim_resource_chunk_tab(write_ctx.chunk_tab, out_fd,
-                                                   &new_size);
+       /* Write chunk table if needed.  */
+       if (write_ctx.chunk_tab) {
+               ret = finish_wim_resource_chunk_tab(write_ctx.chunk_tab,
+                                                   out_fd,
+                                                   res_start_offset,
+                                                   resource_flags);
                if (ret)
                        goto out_free_chunk_tab;
-               if (new_size >= wim_resource_size(lte)) {
-                       /* Oops!  We compressed the resource to larger than the original
-                        * size.  Write the resource uncompressed instead. */
-                       DEBUG("Compressed %"PRIu64" => %"PRIu64" bytes; "
-                             "writing uncompressed instead",
-                             wim_resource_size(lte), new_size);
-                       ret = seek_and_truncate(out_fd, offset);
-                       if (ret)
-                               goto out_free_chunk_tab;
-                       write_ctx.compress = NULL;
-                       write_ctx.doing_sha = false;
-                       out_ctype = WIMLIB_COMPRESSION_TYPE_NONE;
-                       goto try_write_again;
-               }
+       }
+
+       /* Fill in out_res_entry with information about the newly written
+        * resource.  */
+       out_res_entry->size          = out_fd->offset - res_start_offset;
+       out_res_entry->flags         = lte->resource_entry.flags;
+       if (out_ctype == WIMLIB_COMPRESSION_TYPE_NONE)
+               out_res_entry->flags &= ~WIM_RESHDR_FLAG_COMPRESSED;
+       else
                out_res_entry->flags |= WIM_RESHDR_FLAG_COMPRESSED;
+       out_res_entry->offset        = res_start_offset;
+       out_res_entry->original_size = wim_resource_size(lte);
+
+       /* Check for resources compressed to greater than their original size
+        * and write them uncompressed instead.  (But never do this if writing
+        * to a pipe, and don't bother if we did a raw copy.)  */
+       if (out_res_entry->size > out_res_entry->original_size &&
+           !(resource_flags & (WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE |
+                               WIMLIB_READ_RESOURCE_FLAG_RAW)))
+       {
+               DEBUG("Compressed %"PRIu64" => %"PRIu64" bytes; "
+                     "writing uncompressed instead",
+                     out_res_entry->original_size, out_res_entry->size);
+               ret = seek_and_truncate(out_fd, res_start_offset);
+               if (ret)
+                       goto out_free_chunk_tab;
+               out_ctype = WIMLIB_COMPRESSION_TYPE_NONE;
+               FREE(write_ctx.chunk_tab);
+               write_ctx.compress = NULL;
+               write_ctx.chunk_tab = NULL;
+               write_ctx.doing_sha = false;
+               goto try_write_again;
+       }
+       if (resource_flags & (WIMLIB_READ_RESOURCE_FLAG_RAW)) {
+               DEBUG("Copied raw compressed data "
+                     "(%"PRIu64" => %"PRIu64" bytes @ +%"PRIu64", flags=0x%02x)",
+                     out_res_entry->original_size, out_res_entry->size,
+                     out_res_entry->offset, out_res_entry->flags);
+       } else if (out_ctype != WIMLIB_COMPRESSION_TYPE_NONE) {
+               DEBUG("Wrote compressed resource "
+                     "(%"PRIu64" => %"PRIu64" bytes @ +%"PRIu64", flags=0x%02x)",
+                     out_res_entry->original_size, out_res_entry->size,
+                     out_res_entry->offset, out_res_entry->flags);
+       } else {
+               DEBUG("Wrote uncompressed resource "
+                     "(%"PRIu64" bytes @ +%"PRIu64", flags=0x%02x)",
+                     out_res_entry->original_size,
+                     out_res_entry->offset, out_res_entry->flags);
        }
-       out_res_entry->size = new_size;
        ret = 0;
 out_free_chunk_tab:
        FREE(write_ctx.chunk_tab);
+out:
        return ret;
 }
 
+/* Like write_wim_resource(), but the resource is specified by a buffer of
+ * uncompressed data rather a lookup table entry; also writes the SHA1 hash of
+ * the buffer to @hash_ret.  */
+int
+write_wim_resource_from_buffer(const void *buf, size_t buf_size,
+                              int reshdr_flags, struct filedes *out_fd,
+                              int out_ctype,
+                              struct resource_entry *out_res_entry,
+                              u8 *hash_ret, int write_resource_flags)
+{
+       /* Set up a temporary lookup table entry to provide to
+        * write_wim_resource(). */
+       struct wim_lookup_table_entry lte;
+       int ret;
+
+       lte.resource_location            = RESOURCE_IN_ATTACHED_BUFFER;
+       lte.attached_buffer              = (void*)buf;
+       lte.resource_entry.original_size = buf_size;
+       lte.resource_entry.flags         = reshdr_flags;
+
+       if (write_resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE) {
+               sha1_buffer(buf, buf_size, lte.hash);
+               lte.unhashed = 0;
+       } else {
+               lte.unhashed = 1;
+       }
+
+       ret = write_wim_resource(&lte, out_fd, out_ctype, out_res_entry,
+                                write_resource_flags);
+       if (ret)
+               return ret;
+       if (hash_ret)
+               copy_hash(hash_ret, lte.hash);
+       return 0;
+}
+
+
 #ifdef ENABLE_MULTITHREADED_COMPRESSION
 
 /* Blocking shared queue (solves the producer-consumer problem) */
@@ -656,7 +773,7 @@ do_write_streams_progress(union wimlib_progress_info *progress,
 }
 
 struct serial_write_stream_ctx {
-       int out_fd;
+       struct filedes *out_fd;
        int out_ctype;
        int write_resource_flags;
 };
@@ -755,7 +872,7 @@ do_write_stream_list(struct list_head *stream_list,
 static int
 do_write_stream_list_serial(struct list_head *stream_list,
                            struct wim_lookup_table *lookup_table,
-                           int out_fd,
+                           struct filedes *out_fd,
                            int out_ctype,
                            int write_resource_flags,
                            wimlib_progress_func_t progress_func,
@@ -780,20 +897,23 @@ write_flags_to_resource_flags(int write_flags)
        int resource_flags = 0;
 
        if (write_flags & WIMLIB_WRITE_FLAG_RECOMPRESS)
-               resource_flags |= WIMLIB_RESOURCE_FLAG_RECOMPRESS;
+               resource_flags |= WIMLIB_WRITE_RESOURCE_FLAG_RECOMPRESS;
+       if (write_flags & WIMLIB_WRITE_FLAG_PIPABLE)
+               resource_flags |= WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE;
        return resource_flags;
 }
 
 static int
 write_stream_list_serial(struct list_head *stream_list,
                         struct wim_lookup_table *lookup_table,
-                        int out_fd,
+                        struct filedes *out_fd,
                         int out_ctype,
                         int write_resource_flags,
                         wimlib_progress_func_t progress_func,
                         union wimlib_progress_info *progress)
 {
-       DEBUG("Writing stream list (serial version)");
+       DEBUG("Writing stream list of size %"PRIu64" (serial version)",
+             progress->write_streams.total_streams);
        progress->write_streams.num_threads = 1;
        if (progress_func)
                progress_func(WIMLIB_PROGRESS_MSG_WRITE_STREAMS, progress);
@@ -808,24 +928,51 @@ write_stream_list_serial(struct list_head *stream_list,
 
 #ifdef ENABLE_MULTITHREADED_COMPRESSION
 static int
-write_wim_chunks(struct message *msg, int out_fd,
-                struct chunk_table *chunk_tab)
+write_wim_chunks(struct message *msg, struct filedes *out_fd,
+                struct chunk_table *chunk_tab,
+                int write_resource_flags)
 {
+       struct iovec *vecs;
+       struct pwm_chunk_hdr *chunk_hdrs;
+       unsigned nvecs;
+       size_t nbytes;
+       int ret;
+
        for (unsigned i = 0; i < msg->num_chunks; i++)
                chunk_tab_record_chunk(chunk_tab, msg->out_chunks[i].iov_len);
-       if (full_writev(out_fd, msg->out_chunks,
-                       msg->num_chunks) != msg->total_out_bytes)
-       {
-               ERROR_WITH_ERRNO("Failed to write WIM chunks");
-               return WIMLIB_ERR_WRITE;
+
+       if (!(write_resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE)) {
+               nvecs = msg->num_chunks;
+               vecs = msg->out_chunks;
+               nbytes = msg->total_out_bytes;
+       } else {
+               /* Special case:  If writing a compressed resource to a pipable
+                * WIM, prefix each compressed chunk with a header that gives
+                * its compressed size.  */
+               nvecs = msg->num_chunks * 2;
+               vecs = alloca(nvecs * sizeof(vecs[0]));
+               chunk_hdrs = alloca(msg->num_chunks * sizeof(chunk_hdrs[0]));
+
+               for (unsigned i = 0; i < msg->num_chunks; i++) {
+                       chunk_hdrs[i].compressed_size = cpu_to_le32(msg->out_chunks[i].iov_len);
+                       vecs[i * 2].iov_base = &chunk_hdrs[i];
+                       vecs[i * 2].iov_len = sizeof(chunk_hdrs[i]);
+                       vecs[i * 2 + 1].iov_base = msg->out_chunks[i].iov_base;
+                       vecs[i * 2 + 1].iov_len = msg->out_chunks[i].iov_len;
+               }
+               nbytes = msg->total_out_bytes + msg->num_chunks * sizeof(chunk_hdrs[0]);
        }
-       return 0;
+       ret = full_writev(out_fd, vecs, nvecs);
+       if (ret)
+               ERROR_WITH_ERRNO("Failed to write WIM chunks");
+       return ret;
 }
 
 struct main_writer_thread_ctx {
        struct list_head *stream_list;
        struct wim_lookup_table *lookup_table;
-       int out_fd;
+       struct filedes *out_fd;
+       off_t res_start_offset;
        int out_ctype;
        int write_resource_flags;
        struct shared_queue *res_to_compress_queue;
@@ -979,21 +1126,35 @@ receive_compressed_chunks(struct main_writer_thread_ctx *ctx)
        {
                list_move(&msg->list, &ctx->available_msgs);
                if (msg->begin_chunk == 0) {
-                       /* This is the first set of chunks.  Leave space
-                        * for the chunk table in the output file. */
-                       off_t cur_offset = filedes_offset(ctx->out_fd);
-                       if (cur_offset == -1)
-                               return WIMLIB_ERR_WRITE;
+                       /* First set of chunks.  */
+
+                       /* Write pipable WIM stream header if needed.  */
+                       if (ctx->write_resource_flags &
+                           WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE)
+                       {
+                               ret = write_pwm_stream_header(cur_lte, ctx->out_fd,
+                                                             WIM_RESHDR_FLAG_COMPRESSED);
+                               if (ret)
+                                       return ret;
+                       }
+
+                       /* Save current offset.  */
+                       ctx->res_start_offset = ctx->out_fd->offset;
+
+                       /* Begin building the chunk table, and leave space for
+                        * it if needed.  */
                        ret = begin_wim_resource_chunk_tab(cur_lte,
                                                           ctx->out_fd,
-                                                          cur_offset,
-                                                          &ctx->cur_chunk_tab);
+                                                          &ctx->cur_chunk_tab,
+                                                          ctx->write_resource_flags);
                        if (ret)
                                return ret;
+
                }
 
                /* Write the compressed chunks from the message. */
-               ret = write_wim_chunks(msg, ctx->out_fd, ctx->cur_chunk_tab);
+               ret = write_wim_chunks(msg, ctx->out_fd, ctx->cur_chunk_tab,
+                                      ctx->write_resource_flags);
                if (ret)
                        return ret;
 
@@ -1003,31 +1164,32 @@ receive_compressed_chunks(struct main_writer_thread_ctx *ctx)
                    msg->begin_chunk + msg->num_chunks == ctx->cur_chunk_tab->num_chunks)
                {
                        u64 res_csize;
-                       off_t offset;
 
                        ret = finish_wim_resource_chunk_tab(ctx->cur_chunk_tab,
                                                            ctx->out_fd,
-                                                           &res_csize);
+                                                           ctx->res_start_offset,
+                                                           ctx->write_resource_flags);
                        if (ret)
                                return ret;
 
                        list_del(&cur_lte->being_compressed_list);
 
-                       /* Grab the offset of this stream in the output file
-                        * from the chunk table before we free it. */
-                       offset = ctx->cur_chunk_tab->file_offset;
+                       res_csize = ctx->out_fd->offset - ctx->res_start_offset;
 
                        FREE(ctx->cur_chunk_tab);
                        ctx->cur_chunk_tab = NULL;
 
-                       if (res_csize >= wim_resource_size(cur_lte)) {
-                               /* Oops!  We compressed the resource to
-                                * larger than the original size.  Write
-                                * the resource uncompressed instead. */
+                       /* Check for resources compressed to greater than or
+                        * equal to their original size and write them
+                        * uncompressed instead.  (But never do this if writing
+                        * to a pipe.)  */
+                       if (res_csize >= wim_resource_size(cur_lte) &&
+                           !(ctx->write_resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE))
+                       {
                                DEBUG("Compressed %"PRIu64" => %"PRIu64" bytes; "
                                      "writing uncompressed instead",
                                      wim_resource_size(cur_lte), res_csize);
-                               ret = seek_and_truncate(ctx->out_fd, offset);
+                               ret = seek_and_truncate(ctx->out_fd, ctx->res_start_offset);
                                if (ret)
                                        return ret;
                                ret = write_wim_resource(cur_lte,
@@ -1045,11 +1207,18 @@ receive_compressed_chunks(struct main_writer_thread_ctx *ctx)
                                        cur_lte->resource_entry.original_size;
 
                                cur_lte->output_resource_entry.offset =
-                                       offset;
+                                       ctx->res_start_offset;
 
                                cur_lte->output_resource_entry.flags =
                                        cur_lte->resource_entry.flags |
                                                WIM_RESHDR_FLAG_COMPRESSED;
+
+                               DEBUG("Wrote compressed resource "
+                                     "(%"PRIu64" => %"PRIu64" bytes @ +%"PRIu64", flags=0x%02x)",
+                                     cur_lte->output_resource_entry.original_size,
+                                     cur_lte->output_resource_entry.size,
+                                     cur_lte->output_resource_entry.offset,
+                                     cur_lte->output_resource_entry.flags);
                        }
 
                        do_write_streams_progress(ctx->progress,
@@ -1190,11 +1359,10 @@ submit_stream_for_compression(struct wim_lookup_table_entry *lte,
        list_add_tail(&lte->being_compressed_list, &ctx->outstanding_streams);
        ret = read_resource_prefix(lte, wim_resource_size(lte),
                                   main_writer_thread_cb, ctx, 0);
-       if (ret == 0) {
-               wimlib_assert(ctx->next_chunk == ctx->next_num_chunks);
-               ret = finalize_and_check_sha1(&ctx->next_sha_ctx, lte);
-       }
-       return ret;
+       if (ret)
+               return ret;
+       wimlib_assert(ctx->next_chunk == ctx->next_num_chunks);
+       return finalize_and_check_sha1(&ctx->next_sha_ctx, lte);
 }
 
 static int
@@ -1206,7 +1374,7 @@ main_thread_process_next_stream(struct wim_lookup_table_entry *lte, void *_ctx)
        if (wim_resource_size(lte) < 1000 ||
            ctx->out_ctype == WIMLIB_COMPRESSION_TYPE_NONE ||
            (lte->resource_location == RESOURCE_IN_WIM &&
-            !(ctx->write_resource_flags & WIMLIB_RESOURCE_FLAG_RECOMPRESS) &&
+            !(ctx->write_resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_RECOMPRESS) &&
             lte->wim->compression_type == ctx->out_ctype))
        {
                /* Stream is too small or isn't being compressed.  Process it by
@@ -1262,7 +1430,7 @@ get_default_num_threads(void)
 static int
 write_stream_list_parallel(struct list_head *stream_list,
                           struct wim_lookup_table *lookup_table,
-                          int out_fd,
+                          struct filedes *out_fd,
                           int out_ctype,
                           int write_resource_flags,
                           wimlib_progress_func_t progress_func,
@@ -1286,8 +1454,9 @@ write_stream_list_parallel(struct list_head *stream_list,
                }
        }
 
-       DEBUG("Writing stream list (parallel version, num_threads=%u)",
-             num_threads);
+       DEBUG("Writing stream list of size %"PRIu64" "
+             "(parallel version, num_threads=%u)",
+             progress->write_streams.total_streams, num_threads);
 
        progress->write_streams.num_threads = num_threads;
 
@@ -1400,7 +1569,7 @@ out_serial_quiet:
 static int
 write_stream_list(struct list_head *stream_list,
                  struct wim_lookup_table *lookup_table,
-                 int out_fd, int out_ctype, int write_flags,
+                 struct filedes *out_fd, int out_ctype, int write_flags,
                  unsigned num_threads, wimlib_progress_func_t progress_func)
 {
        struct wim_lookup_table_entry *lte;
@@ -1416,6 +1585,8 @@ write_stream_list(struct list_head *stream_list,
 
        write_resource_flags = write_flags_to_resource_flags(write_flags);
 
+       DEBUG("write_resource_flags=0x%08x", write_resource_flags);
+
        /* Calculate the total size of the streams to be written.  Note: this
         * will be the uncompressed size, as we may not know the compressed size
         * yet, and also this will assume that every unhashed stream will be
@@ -1425,7 +1596,7 @@ write_stream_list(struct list_head *stream_list,
                total_bytes += wim_resource_size(lte);
                if (out_ctype != WIMLIB_COMPRESSION_TYPE_NONE
                       && (wim_resource_compression_type(lte) != out_ctype ||
-                          (write_resource_flags & WIMLIB_RESOURCE_FLAG_RECOMPRESS)))
+                          (write_resource_flags & WIMLIB_WRITE_RESOURCE_FLAG_RECOMPRESS)))
                {
                        total_compression_bytes += wim_resource_size(lte);
                }
@@ -1457,6 +1628,10 @@ write_stream_list(struct list_head *stream_list,
                                               write_resource_flags,
                                               progress_func,
                                               &progress);
+       if (ret == 0)
+               DEBUG("Successfully wrote stream list.");
+       else
+               DEBUG("Failed to write stream list.");
        return ret;
 }
 
@@ -1491,7 +1666,7 @@ stream_size_table_insert(struct wim_lookup_table_entry *lte, void *_tab)
        struct wim_lookup_table_entry *same_size_lte;
        struct hlist_node *tmp;
 
-       pos = hash_u64(wim_resource_size(lte)) % tab->capacity;
+       pos = hash_u64(wim_resource_size(lte)) % tab->capacity;
        lte->unique_size = 1;
        hlist_for_each_entry(same_size_lte, tmp, &tab->array[pos], hash_list_2) {
                if (wim_resource_size(same_size_lte) == wim_resource_size(lte)) {
@@ -1592,7 +1767,7 @@ prepare_streams_for_overwrite(WIMStruct *wim, off_t end_offset,
                struct wim_image_metadata *imd;
                struct wim_lookup_table_entry *lte;
 
-               imd = wim->image_metadata[i];
+               imd = wim->image_metadata[i];
                image_for_each_unhashed_stream(lte, imd)
                        lte_overwrite_prepare(lte, &args);
        }
@@ -1647,7 +1822,7 @@ image_find_streams_to_write(WIMStruct *wim)
        struct wim_lookup_table_entry *lte;
 
        ctx = wim->private;
-       imd = wim_get_current_image_metadata(wim);
+       imd = wim_get_current_image_metadata(wim);
 
        image_for_each_unhashed_stream(lte, imd)
                lte->out_refcnt = 0;
@@ -1681,6 +1856,8 @@ prepare_stream_list(WIMStruct *wim, int image, struct list_head *stream_list)
        int ret;
        struct find_streams_ctx ctx;
 
+       DEBUG("Preparing list of streams to write for image %d.", image);
+
        for_lookup_table_entry(wim->lookup_table, lte_zero_out_refcnt, NULL);
        ret = init_stream_size_table(&ctx.stream_size_tab,
                                     wim->lookup_table->capacity);
@@ -1692,158 +1869,243 @@ prepare_stream_list(WIMStruct *wim, int image, struct list_head *stream_list)
        wim->private = &ctx;
        ret = for_image(wim, image, image_find_streams_to_write);
        destroy_stream_size_table(&ctx.stream_size_tab);
-       if (ret == 0)
-               list_transfer(&ctx.stream_list, stream_list);
-       return ret;
+       if (ret)
+               return ret;
+       list_transfer(&ctx.stream_list, stream_list);
+       return 0;
 }
 
 /* Writes the streams for the specified @image in @wim to @wim->out_fd.
- */
+ * Alternatively, if @stream_list_override is specified, it is taken to be the
+ * list of streams to write (connected with 'write_streams_list') and @image is
+ * ignored.  */
 static int
 write_wim_streams(WIMStruct *wim, int image, int write_flags,
                  unsigned num_threads,
-                 wimlib_progress_func_t progress_func)
+                 wimlib_progress_func_t progress_func,
+                 struct list_head *stream_list_override)
 {
        int ret;
-       struct list_head stream_list;
+       struct list_head _stream_list;
+       struct list_head *stream_list;
+       struct wim_lookup_table_entry *lte;
 
-       ret = prepare_stream_list(wim, image, &stream_list);
-       if (ret)
-               return ret;
-       return write_stream_list(&stream_list,
+       if (stream_list_override) {
+               stream_list = stream_list_override;
+               list_for_each_entry(lte, stream_list, write_streams_list) {
+                       if (lte->refcnt)
+                               lte->out_refcnt = lte->refcnt;
+                       else
+                               lte->out_refcnt = 1;
+               }
+       } else {
+               stream_list = &_stream_list;
+               ret = prepare_stream_list(wim, image, stream_list);
+               if (ret)
+                       return ret;
+       }
+       list_for_each_entry(lte, stream_list, write_streams_list)
+               lte->part_number = wim->hdr.part_number;
+       return write_stream_list(stream_list,
                                 wim->lookup_table,
-                                wim->out_fd,
+                                &wim->out_fd,
                                 wim->compression_type,
                                 write_flags,
                                 num_threads,
                                 progress_func);
 }
 
+static int
+write_wim_metadata_resources(WIMStruct *wim, int image, int write_flags,
+                            wimlib_progress_func_t progress_func)
+{
+       int ret;
+       int start_image;
+       int end_image;
+       int write_resource_flags;
+
+       if (write_flags & WIMLIB_WRITE_FLAG_NO_METADATA)
+               return 0;
+
+       write_resource_flags = write_flags_to_resource_flags(write_flags);
+
+       DEBUG("Writing metadata resources (offset=%"PRIu64")",
+             wim->out_fd.offset);
+
+       if (progress_func)
+               progress_func(WIMLIB_PROGRESS_MSG_WRITE_METADATA_BEGIN, NULL);
+
+       if (image == WIMLIB_ALL_IMAGES) {
+               start_image = 1;
+               end_image = wim->hdr.image_count;
+       } else {
+               start_image = image;
+               end_image = image;
+       }
+
+       for (int i = start_image; i <= end_image; i++) {
+               struct wim_image_metadata *imd;
+
+               imd = wim->image_metadata[i - 1];
+               if (imd->modified) {
+                       ret = write_metadata_resource(wim, i,
+                                                     write_resource_flags);
+               } else {
+                       ret = write_wim_resource(imd->metadata_lte,
+                                                &wim->out_fd,
+                                                wim->compression_type,
+                                                &imd->metadata_lte->output_resource_entry,
+                                                write_resource_flags);
+               }
+               if (ret)
+                       return ret;
+       }
+       if (progress_func)
+               progress_func(WIMLIB_PROGRESS_MSG_WRITE_METADATA_END, NULL);
+       return 0;
+}
+
 /*
  * Finish writing a WIM file: write the lookup table, xml data, and integrity
- * table (optional), then overwrite the WIM header.
+ * table, then overwrite the WIM header.  Always closes the WIM file descriptor
+ * (wim->out_fd).
  *
  * write_flags is a bitwise OR of the following:
  *
- *     (public)  WIMLIB_WRITE_FLAG_CHECK_INTEGRITY:
- *             Include an integrity table.
+ *     (public) WIMLIB_WRITE_FLAG_CHECK_INTEGRITY:
+ *             Include an integrity table.
  *
- *     (private) WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE:
- *             Don't write the lookup table.
+ *     (public) WIMLIB_WRITE_FLAG_FSYNC:
+ *             fsync() the output file before closing it.
  *
- *     (private) WIMLIB_WRITE_FLAG_REUSE_INTEGRITY_TABLE:
- *             When (if) writing the integrity table, re-use entries from the
- *             existing integrity table, if possible.
+ *     (public)  WIMLIB_WRITE_FLAG_PIPABLE:
+ *             Writing a pipable WIM, possibly to a pipe; include pipable WIM
+ *             stream headers before the lookup table and XML data, and also
+ *             write the WIM header at the end instead of seeking to the
+ *             beginning.  Can't be combined with
+ *             WIMLIB_WRITE_FLAG_CHECK_INTEGRITY.
  *
- *     (private) WIMLIB_WRITE_FLAG_CHECKPOINT_AFTER_XML:
- *             After writing the XML data but before writing the integrity
- *             table, write a temporary WIM header and flush the stream so that
- *             the WIM is less likely to become corrupted upon abrupt program
- *             termination.
+ *     (private) WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE:
+ *             Don't write the lookup table.
  *
- *     (private) WIMLIB_WRITE_FLAG_FSYNC:
- *             fsync() the output file before closing it.
+ *     (private) WIMLIB_WRITE_FLAG_REUSE_INTEGRITY_TABLE:
+ *             When (if) writing the integrity table, re-use entries from the
+ *             existing integrity table, if possible.
  *
+ *     (private) WIMLIB_WRITE_FLAG_CHECKPOINT_AFTER_XML:
+ *             After writing the XML data but before writing the integrity
+ *             table, write a temporary WIM header and flush the stream so that
+ *             the WIM is less likely to become corrupted upon abrupt program
+ *             termination.
+ *     (private) WIMLIB_WRITE_FLAG_HEADER_AT_END:
+ *             Instead of overwriting the WIM header at the beginning of the
+ *             file, simply append it to the end of the file.  (Used when
+ *             writing to pipe.)
+ *     (private) WIMLIB_WRITE_FLAG_USE_EXISTING_TOTALBYTES:
+ *             Use the existing <TOTALBYTES> stored in the in-memory XML
+ *             information, rather than setting it to the offset of the XML
+ *             data being written.
  */
-int
+static int
 finish_write(WIMStruct *wim, int image, int write_flags,
-            wimlib_progress_func_t progress_func)
+            wimlib_progress_func_t progress_func,
+            struct list_head *stream_list_override)
 {
        int ret;
-       struct wim_header hdr;
-
-       /* @hdr will be the header for the new WIM.  First copy all the data
-        * from the header in the WIMStruct; then set all the fields that may
-        * have changed, including the resource entries, boot index, and image
-        * count.  */
-       memcpy(&hdr, &wim->hdr, sizeof(struct wim_header));
+       off_t hdr_offset;
+       int write_resource_flags;
+       off_t old_lookup_table_end;
+       off_t new_lookup_table_end;
+       u64 xml_totalbytes;
 
-       /* Set image count and boot index correctly for single image writes */
-       if (image != WIMLIB_ALL_IMAGES) {
-               hdr.image_count = 1;
-               if (hdr.boot_idx == image)
-                       hdr.boot_idx = 1;
-               else
-                       hdr.boot_idx = 0;
-       }
+       write_resource_flags = write_flags_to_resource_flags(write_flags);
 
        /* In the WIM header, there is room for the resource entry for a
         * metadata resource labeled as the "boot metadata".  This entry should
         * be zeroed out if there is no bootable image (boot_idx 0).  Otherwise,
         * it should be a copy of the resource entry for the image that is
         * marked as bootable.  This is not well documented...  */
-       if (hdr.boot_idx == 0) {
-               zero_resource_entry(&hdr.boot_metadata_res_entry);
+       if (wim->hdr.boot_idx == 0) {
+               zero_resource_entry(&wim->hdr.boot_metadata_res_entry);
        } else {
-               copy_resource_entry(&hdr.boot_metadata_res_entry,
-                           &wim->image_metadata[ hdr.boot_idx- 1
+               copy_resource_entry(&wim->hdr.boot_metadata_res_entry,
+                           &wim->image_metadata[wim->hdr.boot_idx- 1
                                        ]->metadata_lte->output_resource_entry);
        }
 
+       /* Write lookup table.  (Save old position first.)  */
+       old_lookup_table_end = wim->hdr.lookup_table_res_entry.offset +
+                              wim->hdr.lookup_table_res_entry.size;
        if (!(write_flags & WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE)) {
-               ret = write_lookup_table(wim, image, &hdr.lookup_table_res_entry);
+               ret = write_wim_lookup_table(wim, image, write_flags,
+                                            &wim->hdr.lookup_table_res_entry,
+                                            stream_list_override);
                if (ret)
                        goto out_close_wim;
        }
 
-       ret = write_xml_data(wim->wim_info, image, wim->out_fd,
-                            (write_flags & WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE) ?
-                             wim_info_get_total_bytes(wim->wim_info) : 0,
-                            &hdr.xml_res_entry);
+       /* Write XML data.  */
+       xml_totalbytes = wim->out_fd.offset;
+       if (write_flags & WIMLIB_WRITE_FLAG_USE_EXISTING_TOTALBYTES)
+               xml_totalbytes = WIM_TOTALBYTES_USE_EXISTING;
+       ret = write_wim_xml_data(wim, image, xml_totalbytes,
+                                &wim->hdr.xml_res_entry,
+                                write_resource_flags);
        if (ret)
                goto out_close_wim;
 
        if (write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) {
                if (write_flags & WIMLIB_WRITE_FLAG_CHECKPOINT_AFTER_XML) {
                        struct wim_header checkpoint_hdr;
-                       memcpy(&checkpoint_hdr, &hdr, sizeof(struct wim_header));
+                       memcpy(&checkpoint_hdr, &wim->hdr, sizeof(struct wim_header));
                        zero_resource_entry(&checkpoint_hdr.integrity);
                        checkpoint_hdr.flags |= WIM_HDR_FLAG_WRITE_IN_PROGRESS;
-                       ret = write_header(&checkpoint_hdr, wim->out_fd);
+                       ret = write_wim_header_at_offset(&checkpoint_hdr,
+                                                        &wim->out_fd, 0);
                        if (ret)
                                goto out_close_wim;
                }
 
-               off_t old_lookup_table_end;
-               off_t new_lookup_table_end;
-               if (write_flags & WIMLIB_WRITE_FLAG_REUSE_INTEGRITY_TABLE) {
-                       old_lookup_table_end = wim->hdr.lookup_table_res_entry.offset +
-                                              wim->hdr.lookup_table_res_entry.size;
-               } else {
+               if (!(write_flags & WIMLIB_WRITE_FLAG_REUSE_INTEGRITY_TABLE))
                        old_lookup_table_end = 0;
-               }
-               new_lookup_table_end = hdr.lookup_table_res_entry.offset +
-                                      hdr.lookup_table_res_entry.size;
 
-               ret = write_integrity_table(wim->out_fd,
-                                           &hdr.integrity,
+               new_lookup_table_end = wim->hdr.lookup_table_res_entry.offset +
+                                      wim->hdr.lookup_table_res_entry.size;
+
+               ret = write_integrity_table(wim,
                                            new_lookup_table_end,
                                            old_lookup_table_end,
                                            progress_func);
                if (ret)
                        goto out_close_wim;
        } else {
-               zero_resource_entry(&hdr.integrity);
+               zero_resource_entry(&wim->hdr.integrity);
        }
 
-       hdr.flags &= ~WIM_HDR_FLAG_WRITE_IN_PROGRESS;
-       ret = write_header(&hdr, wim->out_fd);
+       wim->hdr.flags &= ~WIM_HDR_FLAG_WRITE_IN_PROGRESS;
+       hdr_offset = 0;
+       if (write_flags & WIMLIB_WRITE_FLAG_HEADER_AT_END)
+               hdr_offset = wim->out_fd.offset;
+       ret = write_wim_header_at_offset(&wim->hdr, &wim->out_fd, hdr_offset);
        if (ret)
                goto out_close_wim;
 
        if (write_flags & WIMLIB_WRITE_FLAG_FSYNC) {
-               if (fsync(wim->out_fd)) {
+               if (fsync(wim->out_fd.fd)) {
                        ERROR_WITH_ERRNO("Error syncing data to WIM file");
                        ret = WIMLIB_ERR_WRITE;
+                       goto out_close_wim;
                }
        }
+
+       ret = 0;
 out_close_wim:
-       if (close(wim->out_fd)) {
+       if (filedes_close(&wim->out_fd)) {
                ERROR_WITH_ERRNO("Failed to close the output WIM file");
                if (ret == 0)
                        ret = WIMLIB_ERR_WRITE;
        }
-       wim->out_fd = -1;
+       filedes_invalidate(&wim->out_fd);
        return ret;
 }
 
@@ -1876,102 +2138,415 @@ lock_wim(WIMStruct *wim, int fd)
 static int
 open_wim_writable(WIMStruct *wim, const tchar *path, int open_flags)
 {
-       wim->out_fd = topen(path, open_flags | O_BINARY, 0644);
-       if (wim->out_fd == -1) {
-               ERROR_WITH_ERRNO("Failed to open `%"TS"' for writing", path);
+       int raw_fd;
+       DEBUG("Opening \"%"TS"\" for writing.", path);
+
+       raw_fd = topen(path, open_flags | O_BINARY, 0644);
+       if (raw_fd < 0) { 
+               ERROR_WITH_ERRNO("Failed to open \"%"TS"\" for writing", path);
                return WIMLIB_ERR_OPEN;
        }
+       filedes_init(&wim->out_fd, raw_fd);
        return 0;
 }
 
 
-void
+static void
 close_wim_writable(WIMStruct *wim)
 {
-       if (wim->out_fd != -1) {
-               if (close(wim->out_fd))
+       if (filedes_valid(&wim->out_fd)) {
+               if (filedes_close(&wim->out_fd))
                        WARNING_WITH_ERRNO("Failed to close output WIM");
-               wim->out_fd = -1;
+               filedes_invalidate(&wim->out_fd);
        }
 }
 
-/* Open file stream and write dummy header for WIM. */
-int
-begin_write(WIMStruct *wim, const tchar *path, int write_flags)
+/*
+ * Perform the intermediate stages of creating a "pipable" WIM (i.e. a WIM
+ * capable of being applied from a pipe).  Such a WIM looks like:
+ *
+ * Pipable WIMs are a wimlib-specific modification of the WIM format such that
+ * images can be applied from them sequentially when the file data is sent over
+ * a pipe.  In addition, a pipable WIM can be written sequentially to a pipe.
+ * The modifications made to the WIM format for pipable WIMs are:
+ *
+ * - Magic characters in header are "WLPWM\0\0\0" (wimlib pipable WIM) instead
+ *   of "MSWIM\0\0\0".  This lets wimlib know that the WIM is pipable and also
+ *   should stop other software from trying to read the file as a normal WIM.
+ *
+ * - The header at the beginning of the file does not contain all the normal
+ *   information; in particular it will have all 0's for the lookup table and
+ *   XML data resource entries.  This is because this information cannot be
+ *   determined until the lookup table and XML data have been written.
+ *   Consequently, wimlib will write the full header at the very end of the
+ *   file.  The header at the end, however, is only used when reading the WIM
+ *   from a seekable file (not a pipe).
+ *
+ * - An extra copy of the XML data is placed directly after the header.  This
+ *   allows image names and sizes to be determined at an appropriate time when
+ *   reading the WIM from a pipe.  This copy of the XML data is ignored if the
+ *   WIM is read from a seekable file (not a pipe).
+ *
+ * - The format of resources, or streams, has been modified to allow them to be
+ *   used before the "lookup table" has been read.  Each stream is prefixed with
+ *   a `struct pwm_stream_hdr' that is basically an abbreviated form of `struct
+ *   wim_lookup_table_entry_disk' that only contains the SHA1 message digest,
+ *   uncompressed stream size, and flags that indicate whether the stream is
+ *   compressed.  The data of uncompressed streams then follows literally, while
+ *   the data of compressed streams follows in a modified format.  Compressed
+ *   streams have no chunk table, since the chunk table cannot be written until
+ *   all chunks have been compressed; instead, each compressed chunk is prefixed
+ *   by a `struct pwm_chunk_hdr' that gives its size.  However, the offsets are
+ *   given in the chunk table as if these chunk headers were not present.
+ *
+ * - Metadata resources always come before other file resources (streams).
+ *   (This does not by itself constitute an incompatibility with normal WIMs,
+ *   since this is valid in normal WIMs.)
+ *
+ * - At least up to the end of the file resources, all components must be packed
+ *   as tightly as possible; there cannot be any "holes" in the WIM.  (This does
+ *   not by itself consititute an incompatibility with normal WIMs, since this
+ *   is valid in normal WIMs.)
+ *
+ * Note: the lookup table, XML data, and header at the end are not used when
+ * applying from a pipe.  They exist to support functionality such as image
+ * application and export when the WIM is *not* read from a pipe.
+ *
+ *   Layout of pipable WIM:
+ *
+ * ----------+----------+--------------------+----------------+--------------+------------+--------+
+ * | Header  | XML data | Metadata resources | File resources | Lookup table | XML data   | Header |
+ * ----------+----------+--------------------+----------------+--------------+------------+--------+
+ *
+ *   Layout of normal WIM:
+ *
+ * +---------+--------------------+----------------+--------------+----------+
+ * | Header  | Metadata resources | File resources | Lookup table | XML data |
+ * +---------+--------------------+----------------+--------------+----------+
+ *
+ * Do note that since pipable WIMs are not supported by Microsoft's software,
+ * wimlib does not create them unless explicitly requested (with
+ * WIMLIB_WRITE_FLAG_PIPABLE) and as stated above they use different magic
+ * characters to identify the file.
+ */
+static int
+write_pipable_wim(WIMStruct *wim, int image, int write_flags,
+                 unsigned num_threads, wimlib_progress_func_t progress_func,
+                 struct list_head *stream_list_override)
 {
        int ret;
-       int open_flags = O_TRUNC | O_CREAT;
-       if (write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY)
-               open_flags |= O_RDWR;
-       else
-               open_flags |= O_WRONLY;
-       ret = open_wim_writable(wim, path, open_flags);
+       struct resource_entry xml_res_entry;
+
+       WARNING("Creating a pipable WIM, which will "
+               "be incompatible\n"
+               "          with Microsoft's software (wimgapi/imagex/Dism).");
+
+       /* At this point, the header at the beginning of the file has already
+        * been written.  */
+
+       /* For efficiency, when wimlib adds an image to the WIM with
+        * wimlib_add_image(), the SHA1 message digests of files is not
+        * calculated; instead, they are calculated while the files are being
+        * written.  However, this does not work when writing a pipable WIM,
+        * since when writing a stream to a pipable WIM, its SHA1 message digest
+        * needs to be known before the stream data is written.  Therefore,
+        * before getting much farther, we need to pre-calculate the SHA1
+        * message digests of all streams that will be written.  */
+       ret = wim_checksum_unhashed_streams(wim);
        if (ret)
                return ret;
-       /* Write dummy header. It will be overwritten later. */
-       wim->hdr.flags |= WIM_HDR_FLAG_WRITE_IN_PROGRESS;
-       ret = write_header(&wim->hdr, wim->out_fd);
-       wim->hdr.flags &= ~WIM_HDR_FLAG_WRITE_IN_PROGRESS;
+
+       /* Write extra copy of the XML data.  */
+       ret = write_wim_xml_data(wim, image, WIM_TOTALBYTES_OMIT,
+                                &xml_res_entry,
+                                WIMLIB_WRITE_RESOURCE_FLAG_PIPABLE);
        if (ret)
                return ret;
-       if (lseek(wim->out_fd, WIM_HEADER_DISK_SIZE, SEEK_SET) == -1) {
-               ERROR_WITH_ERRNO("Failed to seek to end of WIM header");
-               return WIMLIB_ERR_WRITE;
-       }
-       return 0;
+
+       /* Write metadata resources for the image(s) being included in the
+        * output WIM.  */
+       ret = write_wim_metadata_resources(wim, image, write_flags,
+                                          progress_func);
+       if (ret)
+               return ret;
+
+       /* Write streams needed for the image(s) being included in the output
+        * WIM, or streams needed for the split WIM part.  */
+       return write_wim_streams(wim, image, write_flags, num_threads,
+                                progress_func, stream_list_override);
+
+       /* The lookup table, XML data, and header at end are handled by
+        * finish_write().  */
 }
 
-/* Writes a stand-alone WIM to a file.  */
-WIMLIBAPI int
-wimlib_write(WIMStruct *wim, const tchar *path,
-            int image, int write_flags, unsigned num_threads,
-            wimlib_progress_func_t progress_func)
+/* Write a standalone WIM or split WIM (SWM) part to a new file or to a file
+ * descriptor.  */
+int
+write_wim_part(WIMStruct *wim,
+              const void *path_or_fd,
+              int image,
+              int write_flags,
+              unsigned num_threads,
+              wimlib_progress_func_t progress_func,
+              unsigned part_number,
+              unsigned total_parts,
+              struct list_head *stream_list_override,
+              const u8 *guid)
 {
        int ret;
+       struct wim_header hdr_save;
+       struct list_head lt_stream_list_override;
 
-       if (!path)
-               return WIMLIB_ERR_INVALID_PARAM;
+       if (total_parts == 1)
+               DEBUG("Writing standalone WIM.");
+       else
+               DEBUG("Writing split WIM part %u/%u", part_number, total_parts);
+       if (image == WIMLIB_ALL_IMAGES)
+               DEBUG("Including all images.");
+       else
+               DEBUG("Including image %d only.", image);
+       if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR)
+               DEBUG("File descriptor: %d", *(const int*)path_or_fd);
+       else
+               DEBUG("Path: \"%"TS"\"", (const tchar*)path_or_fd);
+       DEBUG("Write flags: 0x%08x", write_flags);
+       if (write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY)
+               DEBUG("\tCHECK_INTEGRITY");
+       if (write_flags & WIMLIB_WRITE_FLAG_REBUILD)
+               DEBUG("\tREBUILD");
+       if (write_flags & WIMLIB_WRITE_FLAG_RECOMPRESS)
+               DEBUG("\tRECOMPRESS");
+       if (write_flags & WIMLIB_WRITE_FLAG_FSYNC)
+               DEBUG("\tFSYNC");
+       if (write_flags & WIMLIB_WRITE_FLAG_SOFT_DELETE)
+               DEBUG("\tFSYNC");
+       if (write_flags & WIMLIB_WRITE_FLAG_IGNORE_READONLY_FLAG)
+               DEBUG("\tIGNORE_READONLY_FLAG");
+       if (write_flags & WIMLIB_WRITE_FLAG_PIPABLE)
+               DEBUG("\tPIPABLE");
+       if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR)
+               DEBUG("\tFILE_DESCRIPTOR");
+       if (write_flags & WIMLIB_WRITE_FLAG_NO_METADATA)
+               DEBUG("\tNO_METADATA");
+       if (write_flags & WIMLIB_WRITE_FLAG_USE_EXISTING_TOTALBYTES)
+               DEBUG("\tUSE_EXISTING_TOTALBYTES");
+       if (num_threads == 0)
+               DEBUG("Number of threads: autodetect");
+       else
+               DEBUG("Number of threads: %u", num_threads);
+       DEBUG("Progress function: %s", (progress_func ? "yes" : "no"));
+       DEBUG("Stream list:       %s", (stream_list_override ? "specified" : "autodetect"));
+       DEBUG("GUID:              %s", (guid ? "specified" : "generate new"));
 
-       write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
+       /* Internally, this is always called with a valid part number and total
+        * parts.  */
+       wimlib_assert(total_parts >= 1);
+       wimlib_assert(part_number >= 1 && part_number <= total_parts);
 
+       /* A valid image (or all images) must be specified.  */
        if (image != WIMLIB_ALL_IMAGES &&
             (image < 1 || image > wim->hdr.image_count))
                return WIMLIB_ERR_INVALID_IMAGE;
 
-       if (wim->hdr.total_parts != 1) {
-               ERROR("Cannot call wimlib_write() on part of a split WIM");
+       /* @wim must specify a standalone WIM.  */
+       if (wim->hdr.total_parts != 1)
                return WIMLIB_ERR_SPLIT_UNSUPPORTED;
+
+       /* Check for contradictory flags.  */
+       if ((write_flags & (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+                           WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY))
+                               == (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+                                   WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY))
+               return WIMLIB_ERR_INVALID_PARAM;
+
+       if ((write_flags & (WIMLIB_WRITE_FLAG_PIPABLE |
+                           WIMLIB_WRITE_FLAG_NOT_PIPABLE))
+                               == (WIMLIB_WRITE_FLAG_PIPABLE |
+                                   WIMLIB_WRITE_FLAG_NOT_PIPABLE))
+               return WIMLIB_ERR_INVALID_PARAM;
+
+       /* Save previous header, then start initializing the new one.  */
+       memcpy(&hdr_save, &wim->hdr, sizeof(struct wim_header));
+
+       /* Set default integrity and pipable flags.  */
+       if (!(write_flags & (WIMLIB_WRITE_FLAG_PIPABLE |
+                            WIMLIB_WRITE_FLAG_NOT_PIPABLE)))
+               if (wim_is_pipable(wim))
+                       write_flags |= WIMLIB_WRITE_FLAG_PIPABLE;
+
+       if (!(write_flags & (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+                            WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY)))
+               if (wim_has_integrity_table(wim))
+                       write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
+
+       /* Set appropriate magic number.  */
+       if (write_flags & WIMLIB_WRITE_FLAG_PIPABLE)
+               wim->hdr.magic = PWM_MAGIC;
+       else
+               wim->hdr.magic = WIM_MAGIC;
+
+       /* Clear header flags that will be set automatically.  */
+       wim->hdr.flags &= ~(WIM_HDR_FLAG_METADATA_ONLY          |
+                           WIM_HDR_FLAG_RESOURCE_ONLY          |
+                           WIM_HDR_FLAG_SPANNED                |
+                           WIM_HDR_FLAG_WRITE_IN_PROGRESS);
+
+       /* Set SPANNED header flag if writing part of a split WIM.  */
+       if (total_parts != 1)
+               wim->hdr.flags |= WIM_HDR_FLAG_SPANNED;
+
+       /* Set part number and total parts of split WIM.  This will be 1 and 1
+        * if the WIM is standalone.  */
+       wim->hdr.part_number = part_number;
+       wim->hdr.total_parts = total_parts;
+
+       /* Use GUID if specified; otherwise generate a new one.  */
+       if (guid)
+               memcpy(wim->hdr.guid, guid, WIMLIB_GUID_LEN);
+       else
+               randomize_byte_array(wim->hdr.guid, WIMLIB_GUID_LEN);
+
+       /* Clear references to resources that have not been written yet.  */
+       zero_resource_entry(&wim->hdr.lookup_table_res_entry);
+       zero_resource_entry(&wim->hdr.xml_res_entry);
+       zero_resource_entry(&wim->hdr.boot_metadata_res_entry);
+       zero_resource_entry(&wim->hdr.integrity);
+
+       /* Set image count and boot index correctly for single image writes.  */
+       if (image != WIMLIB_ALL_IMAGES) {
+               wim->hdr.image_count = 1;
+               if (wim->hdr.boot_idx == image)
+                       wim->hdr.boot_idx = 1;
+               else
+                       wim->hdr.boot_idx = 0;
+       }
+
+       /* Split WIMs can't be bootable.  */
+       if (total_parts != 1)
+               wim->hdr.boot_idx = 0;
+
+       /* Initialize output file descriptor.  */
+       if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR) {
+               /* File descriptor was explicitly provided.  Return error if
+                * file descriptor is not seekable, unless writing a pipable WIM
+                * was requested.  */
+               wim->out_fd.fd = *(const int*)path_or_fd;
+               wim->out_fd.offset = 0;
+               if (!filedes_is_seekable(&wim->out_fd)) {
+                       ret = WIMLIB_ERR_INVALID_PARAM;
+                       if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE))
+                               goto out_restore_hdr;
+                       if (write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY) {
+                               ERROR("Can't include integrity check when "
+                                     "writing pipable WIM to pipe!");
+                               goto out_restore_hdr;
+                       }
+               }
+
+       } else {
+               /* Filename of WIM to write was provided; open file descriptor
+                * to it.  */
+               ret = open_wim_writable(wim, (const tchar*)path_or_fd,
+                                       O_TRUNC | O_CREAT | O_RDWR);
+               if (ret)
+                       goto out_restore_hdr;
        }
 
-       ret = begin_write(wim, path, write_flags);
+       /* Write initial header.  This is merely a "dummy" header since it
+        * doesn't have all the information yet, so it will be overwritten later
+        * (unless writing a pipable WIM).  */
+       if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE))
+               wim->hdr.flags |= WIM_HDR_FLAG_WRITE_IN_PROGRESS;
+       ret = write_wim_header(&wim->hdr, &wim->out_fd);
+       wim->hdr.flags &= ~WIM_HDR_FLAG_WRITE_IN_PROGRESS;
        if (ret)
-               goto out_close_wim;
+               goto out_restore_hdr;
 
-       ret = write_wim_streams(wim, image, write_flags, num_threads,
-                               progress_func);
-       if (ret)
-               goto out_close_wim;
+       if (stream_list_override) {
+               struct wim_lookup_table_entry *lte;
+               INIT_LIST_HEAD(&lt_stream_list_override);
+               list_for_each_entry(lte, stream_list_override,
+                                   write_streams_list)
+               {
+                       list_add_tail(&lte->lookup_table_list,
+                                     &lt_stream_list_override);
+               }
+       }
 
-       if (progress_func)
-               progress_func(WIMLIB_PROGRESS_MSG_WRITE_METADATA_BEGIN, NULL);
+       /* Write metadata resources and streams.  */
+       if (!(write_flags & WIMLIB_WRITE_FLAG_PIPABLE)) {
+               /* Default case: create a normal (non-pipable) WIM.  */
+               ret = write_wim_streams(wim, image, write_flags, num_threads,
+                                       progress_func, stream_list_override);
+               if (ret)
+                       goto out_restore_hdr;
 
-       ret = for_image(wim, image, write_metadata_resource);
-       if (ret)
-               goto out_close_wim;
+               ret = write_wim_metadata_resources(wim, image, write_flags,
+                                                  progress_func);
+               if (ret)
+                       goto out_restore_hdr;
+       } else {
+               /* Non-default case: create pipable WIM.  */
+               ret = write_pipable_wim(wim, image, write_flags, num_threads,
+                                       progress_func, stream_list_override);
+               if (ret)
+                       goto out_restore_hdr;
+               write_flags |= WIMLIB_WRITE_FLAG_HEADER_AT_END;
+       }
 
-       if (progress_func)
-               progress_func(WIMLIB_PROGRESS_MSG_WRITE_METADATA_END, NULL);
+       if (stream_list_override)
+               stream_list_override = &lt_stream_list_override;
 
-       ret = finish_write(wim, image, write_flags, progress_func);
-       /* finish_write() closed the WIM for us */
-       goto out;
-out_close_wim:
+       /* Write lookup table, XML data, and (optional) integrity table.  */
+       ret = finish_write(wim, image, write_flags, progress_func,
+                          stream_list_override);
+out_restore_hdr:
+       memcpy(&wim->hdr, &hdr_save, sizeof(struct wim_header));
        close_wim_writable(wim);
-out:
-       DEBUG("wimlib_write(path=%"TS") = %d", path, ret);
        return ret;
 }
 
+/* Write a standalone WIM to a file or file descriptor.  */
+static int
+write_standalone_wim(WIMStruct *wim, const void *path_or_fd,
+                    int image, int write_flags, unsigned num_threads,
+                    wimlib_progress_func_t progress_func)
+{
+       return write_wim_part(wim, path_or_fd, image, write_flags,
+                             num_threads, progress_func, 1, 1, NULL, NULL);
+}
+
+/* API function documented in wimlib.h  */
+WIMLIBAPI int
+wimlib_write(WIMStruct *wim, const tchar *path,
+            int image, int write_flags, unsigned num_threads,
+            wimlib_progress_func_t progress_func)
+{
+       if (!path)
+               return WIMLIB_ERR_INVALID_PARAM;
+
+       write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
+
+       return write_standalone_wim(wim, path, image, write_flags,
+                                   num_threads, progress_func);
+}
+
+/* API function documented in wimlib.h  */
+WIMLIBAPI int
+wimlib_write_to_fd(WIMStruct *wim, int fd,
+                  int image, int write_flags, unsigned num_threads,
+                  wimlib_progress_func_t progress_func)
+{
+       if (fd < 0)
+               return WIMLIB_ERR_INVALID_PARAM;
+
+       write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
+       write_flags |= WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR;
+
+       return write_standalone_wim(wim, &fd, image, write_flags,
+                                   num_threads, progress_func);
+}
+
 static bool
 any_images_modified(WIMStruct *wim)
 {
@@ -2047,10 +2622,16 @@ overwrite_wim_inplace(WIMStruct *wim, int write_flags,
        struct list_head stream_list;
        off_t old_wim_end;
        u64 old_lookup_table_end, old_xml_begin, old_xml_end;
-       int open_flags;
+
 
        DEBUG("Overwriting `%"TS"' in-place", wim->filename);
 
+       /* Set default integrity flag.  */
+       if (!(write_flags & (WIMLIB_WRITE_FLAG_CHECK_INTEGRITY |
+                            WIMLIB_WRITE_FLAG_NO_CHECK_INTEGRITY)))
+               if (wim_has_integrity_table(wim))
+                       write_flags |= WIMLIB_WRITE_FLAG_CHECK_INTEGRITY;
+
        /* Make sure that the integrity table (if present) is after the XML
         * data, and that there are no stream resources, metadata resources, or
         * lookup tables after the XML data.  Otherwise, these data would be
@@ -2098,31 +2679,26 @@ overwrite_wim_inplace(WIMStruct *wim, int write_flags,
        if (ret)
                return ret;
 
-       open_flags = 0;
-       if (write_flags & WIMLIB_WRITE_FLAG_CHECK_INTEGRITY)
-               open_flags |= O_RDWR;
-       else
-               open_flags |= O_WRONLY;
-       ret = open_wim_writable(wim, wim->filename, open_flags);
+       ret = open_wim_writable(wim, wim->filename, O_RDWR);
        if (ret)
                return ret;
 
-       ret = lock_wim(wim, wim->out_fd);
+       ret = lock_wim(wim, wim->out_fd.fd);
        if (ret) {
                close_wim_writable(wim);
                return ret;
        }
 
        /* Set WIM_HDR_FLAG_WRITE_IN_PROGRESS flag in header. */
-       ret = write_header_flags(wim->hdr.flags | WIM_HDR_FLAG_WRITE_IN_PROGRESS,
-                                wim->out_fd);
+       ret = write_wim_header_flags(wim->hdr.flags | WIM_HDR_FLAG_WRITE_IN_PROGRESS,
+                                    &wim->out_fd);
        if (ret) {
                ERROR_WITH_ERRNO("Error updating WIM header flags");
                close_wim_writable(wim);
                goto out_unlock_wim;
        }
 
-       if (lseek(wim->out_fd, old_wim_end, SEEK_SET) == -1) {
+       if (filedes_seek(&wim->out_fd, old_wim_end) == -1) {
                ERROR_WITH_ERRNO("Can't seek to end of WIM");
                close_wim_writable(wim);
                ret = WIMLIB_ERR_WRITE;
@@ -2133,7 +2709,7 @@ overwrite_wim_inplace(WIMStruct *wim, int write_flags,
              old_wim_end);
        ret = write_stream_list(&stream_list,
                                wim->lookup_table,
-                               wim->out_fd,
+                               &wim->out_fd,
                                wim->compression_type,
                                write_flags,
                                num_threads,
@@ -2141,17 +2717,16 @@ overwrite_wim_inplace(WIMStruct *wim, int write_flags,
        if (ret)
                goto out_truncate;
 
-       for (int i = 0; i < wim->hdr.image_count; i++) {
-               if (wim->image_metadata[i]->modified) {
-                       select_wim_image(wim, i + 1);
-                       ret = write_metadata_resource(wim);
+       for (unsigned i = 1; i <= wim->hdr.image_count; i++) {
+               if (wim->image_metadata[i - 1]->modified) {
+                       ret = write_metadata_resource(wim, i, 0);
                        if (ret)
                                goto out_truncate;
                }
        }
        write_flags |= WIMLIB_WRITE_FLAG_REUSE_INTEGRITY_TABLE;
        ret = finish_write(wim, WIMLIB_ALL_IMAGES, write_flags,
-                          progress_func);
+                          progress_func, NULL);
 out_truncate:
        close_wim_writable(wim);
        if (ret != 0 && !(write_flags & WIMLIB_WRITE_FLAG_NO_LOOKUP_TABLE)) {
@@ -2187,10 +2762,8 @@ overwrite_wim_via_tmpfile(WIMStruct *wim, int write_flags,
        ret = wimlib_write(wim, tmpfile, WIMLIB_ALL_IMAGES,
                           write_flags | WIMLIB_WRITE_FLAG_FSYNC,
                           num_threads, progress_func);
-       if (ret) {
-               ERROR("Failed to write the WIM file `%"TS"'", tmpfile);
+       if (ret)
                goto out_unlink;
-       }
 
        close_wim(wim);
 
@@ -2209,18 +2782,15 @@ overwrite_wim_via_tmpfile(WIMStruct *wim, int write_flags,
                progress.rename.to = wim->filename;
                progress_func(WIMLIB_PROGRESS_MSG_RENAME, &progress);
        }
-       goto out;
+       return 0;
+
 out_unlink:
        /* Remove temporary file. */
-       if (tunlink(tmpfile) != 0)
-               WARNING_WITH_ERRNO("Failed to remove `%"TS"'", tmpfile);
-out:
+       tunlink(tmpfile);
        return ret;
 }
 
-/*
- * Writes a WIM file to the original file that it was read from, overwriting it.
- */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_overwrite(WIMStruct *wim, int write_flags,
                 unsigned num_threads,
@@ -2231,6 +2801,9 @@ wimlib_overwrite(WIMStruct *wim, int write_flags,
 
        write_flags &= WIMLIB_WRITE_MASK_PUBLIC;
 
+       if (write_flags & WIMLIB_WRITE_FLAG_FILE_DESCRIPTOR)
+               return WIMLIB_ERR_INVALID_PARAM;
+
        if (!wim->filename)
                return WIMLIB_ERR_NO_FILENAME;
 
@@ -2243,15 +2816,15 @@ wimlib_overwrite(WIMStruct *wim, int write_flags,
                return ret;
 
        if ((!wim->deletion_occurred || (write_flags & WIMLIB_WRITE_FLAG_SOFT_DELETE))
-           && !(write_flags & WIMLIB_WRITE_FLAG_REBUILD))
+           && !(write_flags & (WIMLIB_WRITE_FLAG_REBUILD |
+                               WIMLIB_WRITE_FLAG_PIPABLE))
+           && !(wim_is_pipable(wim)))
        {
-               int ret;
                ret = overwrite_wim_inplace(wim, write_flags, num_threads,
                                            progress_func);
-               if (ret == WIMLIB_ERR_RESOURCE_ORDER)
-                       WARNING("Falling back to re-building entire WIM");
-               else
+               if (ret != WIMLIB_ERR_RESOURCE_ORDER)
                        return ret;
+               WARNING("Falling back to re-building entire WIM");
        }
        return overwrite_wim_via_tmpfile(wim, write_flags, num_threads,
                                         progress_func);
index db0b3b4daefe8fbde7268fa9a2c490869a48c304..52cd441fe2ad38008c4f643b2020ef181a841c73 100644 (file)
--- a/src/xml.c
+++ b/src/xml.c
@@ -43,6 +43,7 @@
 #include <libxml/xmlwriter.h>
 #include <limits.h>
 #include <string.h>
+#include <unistd.h>
 
 /* Structures used to form an in-memory representation of the XML data (other
  * than the raw parse tree from libxml). */
@@ -89,6 +90,15 @@ struct image_info {
        struct wim_lookup_table *lookup_table; /* temporary field */
 };
 
+/* A struct wim_info structure corresponds to the entire XML data for a WIM file. */
+struct wim_info {
+       u64 total_bytes;
+       int num_images;
+       /* Array of `struct image_info's, one for each image in the WIM that is
+        * mentioned in the XML data. */
+       struct image_info *images;
+};
+
 struct xml_string_spec {
        const char *name;
        size_t offset;
@@ -119,6 +129,25 @@ windows_info_xml_string_specs[] = {
 };
 #undef ELEM
 
+u64
+wim_info_get_total_bytes(const struct wim_info *info)
+{
+       if (!info)
+               return 0;
+       return info->total_bytes;
+}
+
+u64
+wim_info_get_image_total_bytes(const struct wim_info *info, int image)
+{
+       return info->images[image - 1].total_bytes;
+}
+
+unsigned
+wim_info_get_num_images(const struct wim_info *info)
+{
+       return info->num_images;
+}
 
 /* Returns a statically allocated string that is a string representation of the
  * architecture number. */
@@ -1017,13 +1046,8 @@ size_t
 xml_get_max_image_name_len(const WIMStruct *wim)
 {
        size_t max_len = 0;
-       if (wim->wim_info) {
-               for (int i = 0; i < wim->wim_info->num_images; i++) {
-                       size_t len = tstrlen(wim->wim_info->images[i].name);
-                       if (len > max_len)
-                               max_len = len;
-               }
-       }
+       for (u32 i = 0; i < wim->hdr.image_count; i++)
+               max_len = max(max_len, tstrlen(wim->wim_info->images[i].name));
        return max_len;
 }
 
@@ -1242,81 +1266,42 @@ libxml_global_cleanup(void)
        xmlCleanupCharEncodingHandlers();
 }
 
-/*
- * Reads the XML data from a WIM file.
- */
+/* Reads the XML data from a WIM file.  */
 int
-read_xml_data(int in_fd,
-             const struct resource_entry *res_entry,
-             struct wim_info **info_ret)
+read_wim_xml_data(WIMStruct *wim)
 {
-       utf16lechar *xml_data;
+       u8 *xml_data;
        xmlDoc *doc;
        xmlNode *root;
        int ret;
+       const struct resource_entry *res_entry;
 
-       DEBUG("XML data is %"PRIu64" bytes at offset %"PRIu64"",
-             (u64)res_entry->size, res_entry->offset);
+       res_entry = &wim->hdr.xml_res_entry;
 
-       if (resource_is_compressed(res_entry)) {
-               ERROR("XML data is supposed to be uncompressed");
-               ret = WIMLIB_ERR_XML;
-               goto out;
-       }
-
-       if (res_entry->size < 2) {
-               ERROR("XML data must be at least 2 bytes long");
-               ret = WIMLIB_ERR_XML;
-               goto out;
-       }
+       DEBUG("Reading XML data: %"PRIu64" bytes at offset %"PRIu64"",
+             (u64)res_entry->size, res_entry->offset);
 
-       xml_data = MALLOC(res_entry->size + 3);
-       if (!xml_data) {
-               ret = WIMLIB_ERR_NOMEM;
+       ret = res_entry_to_data(res_entry, wim, (void**)&xml_data);
+       if (ret)
                goto out;
-       }
-
-       if (full_pread(in_fd, xml_data,
-                      res_entry->size, res_entry->offset) != res_entry->size)
-       {
-               ERROR_WITH_ERRNO("Error reading XML data");
-               ret = WIMLIB_ERR_READ;
-               goto out_free_xml_data;
-       }
-
-       /* Null-terminate just in case */
-       ((u8*)xml_data)[res_entry->size] = 0;
-       ((u8*)xml_data)[res_entry->size + 1] = 0;
-       ((u8*)xml_data)[res_entry->size + 2] = 0;
-
-       DEBUG("Parsing XML using libxml2 to create XML tree");
-
-       doc = xmlReadMemory((const char *)xml_data,
-                           res_entry->size, "noname.xml", "UTF-16", 0);
 
+       doc = xmlReadMemory((const char *)xml_data, res_entry->original_size,
+                           NULL, "UTF-16LE", 0);
        if (!doc) {
                ERROR("Failed to parse XML data");
                ret = WIMLIB_ERR_XML;
                goto out_free_xml_data;
        }
 
-       DEBUG("Constructing WIM information structure from XML tree.");
-
        root = xmlDocGetRootElement(doc);
-       if (!root) {
-               ERROR("WIM XML data is an empty XML document");
+       if (!root || !node_is_element(root) || !node_name_is(root, "WIM")) {
+               ERROR("WIM XML data is invalid");
                ret = WIMLIB_ERR_XML;
                goto out_free_doc;
        }
 
-       if (!node_is_element(root) || !node_name_is(root, "WIM")) {
-               ERROR("Expected <WIM> for the root XML element");
-               ret = WIMLIB_ERR_XML;
-               goto out_free_doc;
-       }
-       ret = xml_read_wim_info(root, info_ret);
+       ret = xml_read_wim_info(root, &wim->wim_info);
 out_free_doc:
-       DEBUG("Freeing XML tree.");
        xmlFreeDoc(doc);
 out_free_xml_data:
        FREE(xml_data);
@@ -1324,57 +1309,30 @@ out:
        return ret;
 }
 
-/*
- * Writes XML data to a WIM file.
+/* Prepares an in-memory buffer containing the UTF-16LE XML data for a WIM file.
  *
- * If @total_bytes is non-zero, it specifies what to write to the TOTALBYTES
- * element in the XML data.  If zero, TOTALBYTES is given the default value of
- * the offset of the XML data.
+ * total_bytes is the number to write in <TOTALBYTES>, or
+ * WIM_TOTALBYTES_USE_EXISTING to use the existing value in memory, or
+ * WIM_TOTALBYTES_OMIT to omit <TOTALBYTES> entirely.
  */
-int
-write_xml_data(const struct wim_info *wim_info, int image, int out_fd,
-              u64 total_bytes, struct resource_entry *out_res_entry)
+static int
+prepare_wim_xml_data(WIMStruct *wim, int image, u64 total_bytes,
+                    u8 **xml_data_ret, size_t *xml_len_ret)
 {
        xmlCharEncodingHandler *encoding_handler;
-       xmlOutputBuffer *out_buffer;
+       xmlBuffer *buf;
+       xmlOutputBuffer *outbuf;
        xmlTextWriter *writer;
        int ret;
-       off_t start_offset;
-       off_t end_offset;
-
-       wimlib_assert(image == WIMLIB_ALL_IMAGES ||
-                       (wim_info != NULL && image >= 1 &&
-                        image <= wim_info->num_images));
-
-       start_offset = filedes_offset(out_fd);
-       if (start_offset == -1)
-               return WIMLIB_ERR_WRITE;
-
-       DEBUG("Writing XML data for image %d at offset %"PRIu64,
-             image, start_offset);
-
-       /* 2 bytes endianness marker for UTF-16LE.  This is _required_ for WIM
-        * XML data. */
-       static u8 bom[2] = {0xff, 0xfe};
-       if (full_write(out_fd, bom, 2) != 2) {
-               ERROR_WITH_ERRNO("Error writing XML data");
-               return WIMLIB_ERR_WRITE;
-       }
+       int first, last;
+       const xmlChar *content;
+       int len;
+       u8 *xml_data;
+       size_t xml_len;
 
-       /* The contents of the <TOTALBYTES> element in the XML data, under the
-        * <WIM> element (not the <IMAGE> element), is for non-split WIMs the
-        * size of the WIM file excluding the XML data and integrity table.
-        * This should be equal to the current position in the output stream,
-        * since the XML data and integrity table are the last elements of the
-        * WIM.
-        *
-        * For split WIMs, <TOTALBYTES> takes into account the entire WIM, not
-        * just the current part.  In that case, @total_bytes should be passed
-        * in to this function. */
-       if (total_bytes == 0)
-               total_bytes = start_offset;
+       /* Open an xmlTextWriter that writes to an in-memory buffer using
+        * UTF-16LE encoding.  */
 
-       /* The encoding of the XML data must be UTF-16LE. */
        encoding_handler = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF16LE);
        if (!encoding_handler) {
                ERROR("Failed to get XML character encoding handler for UTF-16LE");
@@ -1382,48 +1340,65 @@ write_xml_data(const struct wim_info *wim_info, int image, int out_fd,
                goto out;
        }
 
-       out_buffer = xmlOutputBufferCreateFd(out_fd, encoding_handler);
-       if (!out_buffer) {
-               ERROR("Failed to allocate xmlOutputBuffer");
+       buf = xmlBufferCreate();
+       if (!buf) {
+               ERROR("Failed to create xmlBuffer");
                ret = WIMLIB_ERR_NOMEM;
                goto out;
        }
 
-       writer = xmlNewTextWriter(out_buffer);
+       outbuf = xmlOutputBufferCreateBuffer(buf, encoding_handler);
+       if (!outbuf) {
+               ERROR("Failed to allocate xmlOutputBuffer");
+               ret = WIMLIB_ERR_NOMEM;
+               goto out_buffer_free;
+       }
+
+       writer = xmlNewTextWriter(outbuf);
        if (!writer) {
                ERROR("Failed to allocate xmlTextWriter");
                ret = WIMLIB_ERR_NOMEM;
                goto out_output_buffer_close;
        }
 
-       DEBUG("Writing <WIM> element");
+       /* Write the XML document.  */
 
        ret = xmlTextWriterStartElement(writer, "WIM");
        if (ret < 0)
                goto out_write_error;
 
-       ret = xmlTextWriterWriteFormatElement(writer, "TOTALBYTES", "%"PRIu64,
-                                             total_bytes);
-       if (ret < 0)
-               goto out_write_error;
-
-       if (wim_info != NULL) {
-               int first, last;
-               if (image == WIMLIB_ALL_IMAGES) {
-                       first = 1;
-                       last = wim_info->num_images;
-               } else {
-                       first = image;
-                       last = image;
+       /* The contents of the <TOTALBYTES> element in the XML data, under the
+        * <WIM> element (not the <IMAGE> element), is for non-split WIMs the
+        * size of the WIM file excluding the XML data and integrity table.
+        * For split WIMs, <TOTALBYTES> takes into account the entire WIM, not
+        * just the current part.  */
+       if (total_bytes != WIM_TOTALBYTES_OMIT) {
+               if (total_bytes == WIM_TOTALBYTES_USE_EXISTING) {
+                       if (wim->wim_info)
+                               total_bytes = wim->wim_info->total_bytes;
+                       else
+                               total_bytes = 0;
                }
-               DEBUG("Writing %d <IMAGE> elements", last - first + 1);
-               for (int i = first; i <= last; i++) {
-                       ret = xml_write_image_info(writer, &wim_info->images[i - 1]);
-                       if (ret) {
-                               if (ret < 0)
-                                       goto out_write_error;
-                               goto out_free_text_writer;
-                       }
+               ret = xmlTextWriterWriteFormatElement(writer, "TOTALBYTES",
+                                                     "%"PRIu64, total_bytes);
+               if (ret < 0)
+                       goto out_write_error;
+       }
+
+       if (image == WIMLIB_ALL_IMAGES) {
+               first = 1;
+               last = wim->hdr.image_count;
+       } else {
+               first = image;
+               last = image;
+       }
+
+       for (int i = first; i <= last; i++) {
+               ret = xml_write_image_info(writer, &wim->wim_info->images[i - 1]);
+               if (ret) {
+                       if (ret < 0)
+                               goto out_write_error;
+                       goto out_free_text_writer;
                }
        }
 
@@ -1439,35 +1414,79 @@ write_xml_data(const struct wim_info *wim_info, int image, int out_fd,
        if (ret < 0)
                goto out_write_error;
 
-       DEBUG("Ended XML document");
+       /* Retrieve the buffer into which the document was written.  */
 
-       end_offset = filedes_offset(out_fd);
-       if (end_offset == -1) {
-               ret = WIMLIB_ERR_WRITE;
-       } else {
-               ret = 0;
-               out_res_entry->offset        = start_offset;
-               out_res_entry->size          = end_offset - start_offset;
-               out_res_entry->original_size = end_offset - start_offset;
-               out_res_entry->flags         = WIM_RESHDR_FLAG_METADATA;
+       content = xmlBufferContent(buf);
+       len = xmlBufferLength(buf);
+
+       /* Copy the data into a new buffer, and prefix it with the UTF-16LE BOM
+        * (byte order mark), which is required by MS's software to understand
+        * the data.  */
+
+       xml_len = len + 2;
+       xml_data = MALLOC(xml_len);
+       if (!xml_data) {
+               ret = WIMLIB_ERR_NOMEM;
+               goto out_free_text_writer;
        }
+       xml_data[0] = 0xff;
+       xml_data[1] = 0xfe;
+       memcpy(&xml_data[2], content, len);
+
+       /* Clean up libxml objects and return success.  */
+       *xml_data_ret = xml_data;
+       *xml_len_ret = xml_len;
+       ret = 0;
 out_free_text_writer:
-       /* xmlFreeTextWriter will free the attached xmlOutputBuffer. */
+       /* xmlFreeTextWriter will free the attached xmlOutputBuffer.  */
        xmlFreeTextWriter(writer);
-       goto out;
+       goto out_buffer_free;
 out_output_buffer_close:
-       xmlOutputBufferClose(out_buffer);
+       xmlOutputBufferClose(outbuf);
+out_buffer_free:
+       xmlBufferFree(buf);
 out:
-       if (ret == 0)
-               DEBUG("Successfully wrote XML data");
        return ret;
+
 out_write_error:
        ERROR("Error writing XML data");
        ret = WIMLIB_ERR_WRITE;
        goto out_free_text_writer;
 }
 
-/* Returns the name of the specified image. */
+/* Writes the XML data to a WIM file.  */
+int
+write_wim_xml_data(WIMStruct *wim, int image, u64 total_bytes,
+                  struct resource_entry *out_res_entry,
+                  int write_resource_flags)
+{
+       int ret;
+       u8 *xml_data;
+       size_t xml_len;
+
+       DEBUG("Writing WIM XML data (image=%d, offset=%"PRIu64")",
+             image, total_bytes, wim->out_fd.offset);
+
+       ret = prepare_wim_xml_data(wim, image, total_bytes,
+                                  &xml_data, &xml_len);
+       if (ret)
+               return ret;
+
+       /* Write the XML data uncompressed.  Although wimlib can handle
+        * compressed XML data, MS software cannot.  */
+       ret = write_wim_resource_from_buffer(xml_data,
+                                            xml_len,
+                                            WIM_RESHDR_FLAG_METADATA,
+                                            &wim->out_fd,
+                                            WIMLIB_COMPRESSION_TYPE_NONE,
+                                            out_res_entry,
+                                            NULL,
+                                            write_resource_flags);
+       FREE(xml_data);
+       return ret;
+}
+
+/* API function documented in wimlib.h  */
 WIMLIBAPI const tchar *
 wimlib_get_image_name(const WIMStruct *wim, int image)
 {
@@ -1476,7 +1495,7 @@ wimlib_get_image_name(const WIMStruct *wim, int image)
        return wim->wim_info->images[image - 1].name;
 }
 
-/* Returns the description of the specified image. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI const tchar *
 wimlib_get_image_description(const WIMStruct *wim, int image)
 {
@@ -1485,7 +1504,7 @@ wimlib_get_image_description(const WIMStruct *wim, int image)
        return wim->wim_info->images[image - 1].description;
 }
 
-/* Determines if an image name is already used by some image in the WIM. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI bool
 wimlib_image_name_in_use(const WIMStruct *wim, const tchar *name)
 {
@@ -1498,7 +1517,7 @@ wimlib_image_name_in_use(const WIMStruct *wim, const tchar *name)
 }
 
 
-/* Extracts the raw XML data to a file stream. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_extract_xml_data(WIMStruct *wim, FILE *fp)
 {
@@ -1506,37 +1525,25 @@ wimlib_extract_xml_data(WIMStruct *wim, FILE *fp)
        void *buf;
        int ret;
 
-       size = wim->hdr.xml_res_entry.size;
-       if (sizeof(size_t) < sizeof(u64))
-               if (size != wim->hdr.xml_res_entry.size)
-                       return WIMLIB_ERR_INVALID_PARAM;
-
-       buf = MALLOC(size);
-       if (!buf)
-               return WIMLIB_ERR_NOMEM;
-
-       if (full_pread(wim->in_fd,
-                      buf,
-                      wim->hdr.xml_res_entry.size,
-                      wim->hdr.xml_res_entry.offset) != wim->hdr.xml_res_entry.size)
-       {
-               ERROR_WITH_ERRNO("Error reading XML data");
-               ret = WIMLIB_ERR_READ;
-               goto out_free_buf;
-       }
+       ret = res_entry_to_data(&wim->hdr.xml_res_entry, wim, &buf);
+       if (ret)
+               goto out;
 
+       size = wim->hdr.xml_res_entry.original_size;
        if (fwrite(buf, 1, size, fp) != size) {
                ERROR_WITH_ERRNO("Failed to extract XML data");
                ret = WIMLIB_ERR_WRITE;
-       } else {
-               ret = 0;
+               goto out_free_buf;
        }
+
+       ret = 0;
 out_free_buf:
        FREE(buf);
+out:
        return ret;
 }
 
-/* Sets the name of an image in the WIM. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_set_image_name(WIMStruct *wim, int image, const tchar *name)
 {
@@ -1607,7 +1614,7 @@ do_set_image_info_str(WIMStruct *wim, int image, const tchar *tstr,
        return 0;
 }
 
-/* Sets the description of an image in the WIM. */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_set_image_descripton(WIMStruct *wim, int image,
                            const tchar *description)
@@ -1616,7 +1623,7 @@ wimlib_set_image_descripton(WIMStruct *wim, int image,
                                     offsetof(struct image_info, description));
 }
 
-/* Set the <FLAGS> element of a WIM image */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_set_image_flags(WIMStruct *wim, int image, const tchar *flags)
 {
index 0957e27b960adaf82c45ff8a9156c689a2c29654..6b90ba9e889d547492f03ad2eea07f8169784ddc 100644 (file)
@@ -150,7 +150,7 @@ static const struct lz_params xpress_lz_params = {
        .too_far        = 4096,
 };
 
-/* Documented in wimlib.h */
+/* API function documented in wimlib.h  */
 WIMLIBAPI unsigned
 wimlib_xpress_compress(const void * restrict _uncompressed_data,
                       unsigned uncompressed_len,
index 5586c15ce006118a2dade85c0a4f7d45a5d6176c..e9aec1361bee7ed61664053995e63c37d09f462f 100644 (file)
@@ -147,13 +147,13 @@ xpress_decode_match(unsigned huffsym, unsigned window_pos,
        match_src = match_dest - match_offset;
 
        if (window_pos + match_len > window_len) {
-               ERROR("XPRESS decompression error: match of length %u "
+               DEBUG("XPRESS decompression error: match of length %u "
                      "bytes overflows window", match_len);
                return -1;
        }
 
        if (match_src < window) {
-               ERROR("XPRESS decompression error: match of length %u bytes "
+               DEBUG("XPRESS decompression error: match of length %u bytes "
                      "references data before window (match_offset = %u, "
                      "window_pos = %u)", match_len, match_offset, window_pos);
                return -1;
@@ -204,7 +204,7 @@ xpress_decompress_block(struct input_bitstream * restrict istream,
 }
 
 
-/* Documented in wimlib.h */
+/* API function documented in wimlib.h  */
 WIMLIBAPI int
 wimlib_xpress_decompress(const void * restrict _compressed_data, unsigned compressed_len,
                         void * restrict uncompressed_data, unsigned uncompressed_len)
@@ -229,7 +229,7 @@ wimlib_xpress_decompress(const void * restrict _compressed_data, unsigned compre
         * in the first 256 bytes of the compressed data.
         */
        if (compressed_len < XPRESS_NUM_SYMBOLS / 2) {
-               ERROR("xpress_decompress(): Compressed length too short!");
+               DEBUG("xpress_decompress(): Compressed length too short!");
                return -1;
        }
 
index 7d7b144e7697853032f01fd6acc934e7297bce5a..c523b12d24f2e26f3b390d52e5e1efd4df17bd3c 100755 (executable)
@@ -190,7 +190,7 @@ if ! test "`imagex info dir.wim | grep Integrity | awk '{print $3}'`" = "yes"; t
        error "Integrity table not set correctly on image append"
 fi
 echo "Testing appending WIM image with no integrity check"
-if ! imagex append dir2 dir.wim "newname3"; then
+if ! imagex append dir2 dir.wim "newname3" --nocheck; then
        error "Appending WIM image failed"
 fi
 if ! test "`imagex info dir.wim | grep Integrity | awk '{print $3}'`" = "no"; then
index d4333486d13ad09f3d27b1d0984bb9becd3d18f5..105d0f3955bc22d4be247a47786a8454bf68dd05 100755 (executable)
@@ -84,6 +84,41 @@ do_test() {
                        error "Failed to apply exported WIM image"
                fi
                do_tree_cmp
+               rm -rf out.dir/*
+
+               # Try pipable WIM (don't bother testing all compression types
+               # though, it shouldn't make a difference).
+               if [ "$ctype" = "None" ]; then
+                       # Capture pipable WIM (not writing to pipe)
+                       if ! imagex capture in.dir test.wim \
+                                       --compress=$ctype --norpfix --pipable; then
+                               error "Failed to capture directory tree into a pipable WIM"
+                       fi
+
+                       # Apply pipable WIM (reading from pipe)
+                       if ! cat test.wim | imagex apply - 1 out.dir; then
+                               error "Failed to apply pipable WIM to directory (from pipe)"
+                       fi
+                       do_tree_cmp
+                       rm -rf out.dir/*
+
+                       # Apply pipable WIM (not reading from pipe)
+                       if ! imagex apply test.wim 1 out.dir; then
+                               error "Failed to apply pipable WIM to directory (not from pipe)"
+                       fi
+                       do_tree_cmp
+                       rm -rf out.dir/*
+
+                       # Capture pipable WIM (writing to pipe) and read pipable
+                       # WIM (reading from pipe)
+                       if ! imagex_raw capture --pipable --compress=$ctype \
+                                               --norpfix --pipable         \
+                                           in.dir - | imagex apply - 1 out.dir; then
+                               error "Failed to capture directory tree into a pipable WIM"
+                       fi
+                       do_tree_cmp
+                       rm -rf out.dir/*
+               fi
 
                rm -rf out.dir/* in.dir/* test.wim test*.swm
 
index 79a4a2f9ea4819178ba08523e2ad94ae7906d390..cd353efe72acc1889e09a7ac302ec5b3565860c9 100644 (file)
@@ -41,6 +41,11 @@ imagex()
        fi
 }
 
+imagex_raw()
+{
+       ../../imagex "$@"
+}
+
 wim_ctype()
 {
        imagex info $1 | grep Compression | awk '{print $2}'