]> wimlib.net Git - wimlib/blobdiff - src/extract.c
mount_image.c: add fallback definitions of RENAME_* constants
[wimlib] / src / extract.c
index 706282e00306dc73e465cca403c9268758cd95e0..24b53cbea10a2e9253acac22fe8867cd5929501f 100644 (file)
@@ -6,7 +6,7 @@
  */
 
 /*
- * Copyright (C) 2012, 2013, 2014, 2015 Eric Biggers
+ * Copyright (C) 2012-2018 Eric Biggers
  *
  * This file is free software; you can redistribute it and/or modify it under
  * the terms of the GNU Lesser General Public License as published by the Free
@@ -19,7 +19,7 @@
  * details.
  *
  * You should have received a copy of the GNU Lesser General Public License
- * along with this file; if not, see http://www.gnu.org/licenses/.
+ * along with this file; if not, see https://www.gnu.org/licenses/.
  */
 
 /*
@@ -28,7 +28,7 @@
  * wimlib_extract_pathlist().  Internally, all end up calling
  * do_wimlib_extract_paths() and extract_trees().
  *
- * Although wimlib supports multiple extraction modes/backends (NTFS-3g, UNIX,
+ * 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
@@ -41,6 +41,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <stdlib.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -52,6 +53,7 @@
 #include "wimlib/endianness.h"
 #include "wimlib/error.h"
 #include "wimlib/metadata.h"
+#include "wimlib/object_id.h"
 #include "wimlib/pathlist.h"
 #include "wimlib/paths.h"
 #include "wimlib/pattern.h"
@@ -61,6 +63,7 @@
 #include "wimlib/unix_data.h"
 #include "wimlib/wim.h"
 #include "wimlib/win32.h" /* for realpath() equivalent */
+#include "wimlib/xattr.h"
 #include "wimlib/xml.h"
 
 #define WIMLIB_EXTRACT_FLAG_FROM_PIPE   0x80000000
@@ -69,6 +72,7 @@
 /* Keep in sync with wimlib.h  */
 #define WIMLIB_EXTRACT_MASK_PUBLIC                             \
        (WIMLIB_EXTRACT_FLAG_NTFS                       |       \
+        WIMLIB_EXTRACT_FLAG_RECOVER_DATA               |       \
         WIMLIB_EXTRACT_FLAG_UNIX_DATA                  |       \
         WIMLIB_EXTRACT_FLAG_NO_ACLS                    |       \
         WIMLIB_EXTRACT_FLAG_STRICT_ACLS                |       \
         WIMLIB_EXTRACT_FLAG_STRICT_GLOB                |       \
         WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES              |       \
         WIMLIB_EXTRACT_FLAG_NO_PRESERVE_DIR_STRUCTURE  |       \
-        WIMLIB_EXTRACT_FLAG_WIMBOOT)
+        WIMLIB_EXTRACT_FLAG_WIMBOOT                    |       \
+        WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K           |       \
+        WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K           |       \
+        WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K          |       \
+        WIMLIB_EXTRACT_FLAG_COMPACT_LZX                        \
+        )
 
 /* Send WIMLIB_PROGRESS_MSG_EXTRACT_FILE_STRUCTURE or
  * WIMLIB_PROGRESS_MSG_EXTRACT_METADATA.  */
@@ -134,6 +143,70 @@ end_file_metadata_phase(struct apply_ctx *ctx)
        return end_file_phase(ctx, WIMLIB_PROGRESS_MSG_EXTRACT_METADATA);
 }
 
+/* Are all bytes in the specified buffer zero? */
+static bool
+is_all_zeroes(const u8 *p, const size_t size)
+{
+       const u8 * const end = p + size;
+
+       for (; (uintptr_t)p % WORDBYTES && p != end; p++)
+               if (*p)
+                       return false;
+
+       for (; end - p >= WORDBYTES; p += WORDBYTES)
+               if (*(const machine_word_t *)p)
+                       return false;
+
+       for (; p != end; p++)
+               if (*p)
+                       return false;
+
+       return true;
+}
+
+/*
+ * Sparse regions should be detected at the granularity of the filesystem block
+ * size.  For now just assume 4096 bytes, which is the default block size on
+ * NTFS and most Linux filesystems.
+ */
+#define SPARSE_UNIT 4096
+
+/*
+ * Detect whether the specified buffer begins with a region of all zero bytes.
+ * Return %true if a zero region was found or %false if a nonzero region was
+ * found, and sets *len_ret to the length of the region.  This operates at a
+ * granularity of SPARSE_UNIT bytes, meaning that to extend a zero region, there
+ * must be SPARSE_UNIT zero bytes with no interruption, but to extend a nonzero
+ * region, just one nonzero byte in the next SPARSE_UNIT bytes is sufficient.
+ *
+ * Note: besides compression, the WIM format doesn't yet have a way to
+ * efficiently represent zero regions, so that's why we need to detect them
+ * ourselves.  Things will still fall apart badly on extremely large sparse
+ * files, but this is a start...
+ */
+bool
+detect_sparse_region(const void *data, size_t size, size_t *len_ret)
+{
+       const void *p = data;
+       const void * const end = data + size;
+       size_t len = 0;
+       bool zeroes = false;
+
+       while (p != end) {
+               size_t n = min(end - p, SPARSE_UNIT);
+               bool z = is_all_zeroes(p, n);
+
+               if (len != 0 && z != zeroes)
+                       break;
+               zeroes = z;
+               len += n;
+               p += n;
+       }
+
+       *len_ret = len;
+       return zeroes;
+}
+
 #define PWM_FOUND_WIM_HDR (-1)
 
 /* Read the header for a blob in a pipable WIM.  If @pwm_hdr_ret is not NULL,
@@ -238,7 +311,9 @@ read_blobs_from_pipe(struct apply_ctx *ctx, const struct read_blob_callbacks *cb
                    && (blob->out_refcnt))
                {
                        wim_reshdr_to_desc_and_blob(&reshdr, ctx->wim, &rdesc, blob);
-                       ret = read_blob_with_sha1(blob, cbs);
+                       ret = read_blob_with_sha1(blob, cbs,
+                                                 ctx->extract_flags &
+                                                 WIMLIB_EXTRACT_FLAG_RECOVER_DATA);
                        blob_unset_is_located_in_wim_resource(blob);
                        if (ret)
                                return ret;
@@ -254,6 +329,55 @@ read_blobs_from_pipe(struct apply_ctx *ctx, const struct read_blob_callbacks *cb
        return 0;
 }
 
+static int
+handle_pwm_metadata_resource(WIMStruct *pwm, int image, bool is_needed)
+{
+       struct blob_descriptor *blob;
+       struct wim_reshdr reshdr;
+       struct wim_resource_descriptor *rdesc;
+       int ret;
+
+       ret = WIMLIB_ERR_NOMEM;
+       blob = new_blob_descriptor();
+       if (!blob)
+               goto out;
+
+       ret = read_pwm_blob_header(pwm, blob->hash, &reshdr, NULL);
+       if (ret)
+               goto out;
+
+       ret = WIMLIB_ERR_INVALID_PIPABLE_WIM;
+       if (!(reshdr.flags & WIM_RESHDR_FLAG_METADATA)) {
+               ERROR("Expected metadata resource, but found non-metadata "
+                     "resource");
+               goto out;
+       }
+
+       ret = WIMLIB_ERR_NOMEM;
+       rdesc = MALLOC(sizeof(*rdesc));
+       if (!rdesc)
+               goto out;
+
+       wim_reshdr_to_desc_and_blob(&reshdr, pwm, rdesc, blob);
+       pwm->refcnt++;
+
+       ret = WIMLIB_ERR_NOMEM;
+       pwm->image_metadata[image - 1] = new_unloaded_image_metadata(blob);
+       if (!pwm->image_metadata[image - 1])
+               goto out;
+       blob = NULL;
+
+       /* If the metadata resource is for the image being extracted, then parse
+        * it and save the metadata in memory.  Otherwise, skip over it.  */
+       if (is_needed)
+               ret = select_wim_image(pwm, image);
+       else
+               ret = skip_wim_resource(rdesc);
+out:
+       free_blob_descriptor(blob);
+       return ret;
+}
+
 /* Creates a temporary file opened for writing.  The open file descriptor is
  * returned in @fd_ret and its name is returned in @name_ret (dynamically
  * allocated).  */
@@ -261,27 +385,33 @@ static int
 create_temporary_file(struct filedes *fd_ret, tchar **name_ret)
 {
        tchar *name;
-       int open_flags;
        int raw_fd;
 
+#ifdef _WIN32
 retry:
-       name = ttempnam(NULL, T("wimlib"));
+       name = _wtempnam(NULL, L"wimlib");
        if (!name) {
                ERROR_WITH_ERRNO("Failed to create temporary filename");
                return WIMLIB_ERR_NOMEM;
        }
-
-       open_flags = O_WRONLY | O_CREAT | O_EXCL | O_BINARY;
-#ifdef __WIN32__
-       open_flags |= _O_SHORT_LIVED;
-#endif
-       raw_fd = topen(name, open_flags, 0600);
+       raw_fd = _wopen(name, O_WRONLY | O_CREAT | O_EXCL | O_BINARY |
+                       _O_SHORT_LIVED, 0600);
+       if (raw_fd < 0 && errno == EEXIST) {
+               FREE(name);
+               goto retry;
+       }
+#else /* _WIN32 */
+       const char *tmpdir = getenv("TMPDIR");
+       if (!tmpdir)
+               tmpdir = P_tmpdir;
+       name = MALLOC(strlen(tmpdir) + 1 + 6 + 6 + 1);
+       if (!name)
+               return WIMLIB_ERR_NOMEM;
+       sprintf(name, "%s/wimlibXXXXXX", tmpdir);
+       raw_fd = mkstemp(name);
+#endif /* !_WIN32 */
 
        if (raw_fd < 0) {
-               if (errno == EEXIST) {
-                       FREE(name);
-                       goto retry;
-               }
                ERROR_WITH_ERRNO("Failed to create temporary file "
                                 "\"%"TS"\"", name);
                FREE(name);
@@ -294,13 +424,10 @@ retry:
 }
 
 static int
-begin_extract_blob_wrapper(struct blob_descriptor *blob, void *_ctx)
+begin_extract_blob(struct blob_descriptor *blob, void *_ctx)
 {
        struct apply_ctx *ctx = _ctx;
 
-       ctx->cur_blob = blob;
-       ctx->cur_blob_offset = 0;
-
        if (unlikely(blob->out_refcnt > MAX_OPEN_FILES))
                return create_temporary_file(&ctx->tmpfile_fd, &ctx->tmpfile_name);
 
@@ -308,29 +435,29 @@ begin_extract_blob_wrapper(struct blob_descriptor *blob, void *_ctx)
 }
 
 static int
-extract_chunk_wrapper(const void *chunk, size_t size, void *_ctx)
+extract_chunk(const struct blob_descriptor *blob, u64 offset,
+             const void *chunk, size_t size, void *_ctx)
 {
        struct apply_ctx *ctx = _ctx;
        union wimlib_progress_info *progress = &ctx->progress;
+       bool last = (offset + size == blob->size);
        int ret;
 
-       ctx->cur_blob_offset += size;
-
        if (likely(ctx->supported_features.hard_links)) {
                progress->extract.completed_bytes +=
-                       (u64)size * ctx->cur_blob->out_refcnt;
-               if (ctx->cur_blob_offset == ctx->cur_blob->size)
-                       progress->extract.completed_streams += ctx->cur_blob->out_refcnt;
+                       (u64)size * blob->out_refcnt;
+               if (last)
+                       progress->extract.completed_streams += blob->out_refcnt;
        } else {
                const struct blob_extraction_target *targets =
-                       blob_extraction_targets(ctx->cur_blob);
-               for (u32 i = 0; i < ctx->cur_blob->out_refcnt; i++) {
+                       blob_extraction_targets(blob);
+               for (u32 i = 0; i < blob->out_refcnt; i++) {
                        const struct wim_inode *inode = targets[i].inode;
                        const struct wim_dentry *dentry;
 
                        inode_for_each_extraction_alias(dentry, inode) {
                                progress->extract.completed_bytes += size;
-                               if (ctx->cur_blob_offset == ctx->cur_blob->size)
+                               if (last)
                                        progress->extract.completed_streams++;
                        }
                }
@@ -357,7 +484,7 @@ extract_chunk_wrapper(const void *chunk, size_t size, void *_ctx)
                return ret;
        }
 
-       return call_consume_chunk(chunk, size, ctx->saved_cbs);
+       return call_continue_blob(blob, offset, chunk, size, ctx->saved_cbs);
 }
 
 /* Copy the blob's data from the temporary file to each of its targets.
@@ -380,18 +507,39 @@ extract_from_tmpfile(const tchar *tmpfile_name,
 
        for (u32 i = 0; i < orig_blob->out_refcnt; i++) {
                tmpfile_blob.inline_blob_extraction_targets[0] = targets[i];
-               ret = read_blob_with_cbs(&tmpfile_blob, cbs);
+               ret = read_blob_with_cbs(&tmpfile_blob, cbs, false);
                if (ret)
                        return ret;
        }
        return 0;
 }
 
+static void
+warn_about_corrupted_file(struct wim_dentry *dentry,
+                         const struct wim_inode_stream *stream)
+{
+       WARNING("Corruption in %s\"%"TS"\"!  Extracting anyway since data recovery mode is enabled.",
+               stream_is_unnamed_data_stream(stream) ? "" : "alternate stream of ",
+               dentry_full_path(dentry));
+}
+
 static int
-end_extract_blob_wrapper(struct blob_descriptor *blob, int status, void *_ctx)
+end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
 {
        struct apply_ctx *ctx = _ctx;
 
+       if ((ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RECOVER_DATA) &&
+           !status && blob->corrupted) {
+               const struct blob_extraction_target *targets =
+                       blob_extraction_targets(blob);
+               for (u32 i = 0; i < blob->out_refcnt; i++) {
+                       struct wim_dentry *dentry =
+                               inode_first_extraction_dentry(targets[i].inode);
+
+                       warn_about_corrupted_file(dentry, targets[i].stream);
+               }
+       }
+
        if (unlikely(filedes_valid(&ctx->tmpfile_fd))) {
                filedes_close(&ctx->tmpfile_fd);
                if (!status)
@@ -427,19 +575,24 @@ int
 extract_blob_list(struct apply_ctx *ctx, const struct read_blob_callbacks *cbs)
 {
        struct read_blob_callbacks wrapper_cbs = {
-               .begin_blob     = begin_extract_blob_wrapper,
-               .consume_chunk  = extract_chunk_wrapper,
-               .end_blob       = end_extract_blob_wrapper,
+               .begin_blob     = begin_extract_blob,
+               .continue_blob  = extract_chunk,
+               .end_blob       = end_extract_blob,
                .ctx            = ctx,
        };
        ctx->saved_cbs = cbs;
        if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_FROM_PIPE) {
                return read_blobs_from_pipe(ctx, &wrapper_cbs);
        } else {
+               int flags = VERIFY_BLOB_HASHES;
+
+               if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_RECOVER_DATA)
+                       flags |= RECOVER_DATA;
+
                return read_blob_list(&ctx->blob_list,
                                      offsetof(struct blob_descriptor,
                                               extraction_list),
-                                     &wrapper_cbs, VERIFY_BLOB_HASHES);
+                                     &wrapper_cbs, flags);
        }
 }
 
@@ -450,11 +603,13 @@ extract_blob_list(struct apply_ctx *ctx, const struct read_blob_callbacks *cbs)
  * unnamed data stream only.  */
 static int
 extract_dentry_to_stdout(struct wim_dentry *dentry,
-                        const struct blob_table *blob_table)
+                        const struct blob_table *blob_table, int extract_flags)
 {
        struct wim_inode *inode = dentry->d_inode;
        struct blob_descriptor *blob;
        struct filedes _stdout;
+       bool recover = (extract_flags & WIMLIB_EXTRACT_FLAG_RECOVER_DATA);
+       int ret;
 
        if (inode->i_attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
                                   FILE_ATTRIBUTE_DIRECTORY |
@@ -474,15 +629,23 @@ extract_dentry_to_stdout(struct wim_dentry *dentry,
        }
 
        filedes_init(&_stdout, STDOUT_FILENO);
-       return extract_blob_to_fd(blob, &_stdout);
+       ret = extract_blob_to_fd(blob, &_stdout, recover);
+       if (ret)
+               return ret;
+       if (recover && blob->corrupted)
+               warn_about_corrupted_file(dentry,
+                                         inode_get_unnamed_data_stream(inode));
+       return 0;
 }
 
 static int
 extract_dentries_to_stdout(struct wim_dentry **dentries, size_t num_dentries,
-                          const struct blob_table *blob_table)
+                          const struct blob_table *blob_table,
+                          int extract_flags)
 {
        for (size_t i = 0; i < num_dentries; i++) {
-               int ret = extract_dentry_to_stdout(dentries[i], blob_table);
+               int ret = extract_dentry_to_stdout(dentries[i], blob_table,
+                                                  extract_flags);
                if (ret)
                        return ret;
        }
@@ -641,7 +804,7 @@ destroy_blob_list(struct list_head *blob_list)
                        FREE(blob->blob_extraction_targets);
 }
 
-#ifdef __WIN32__
+#ifdef _WIN32
 static const utf16lechar replacement_char = cpu_to_le16(0xfffd);
 #else
 static const utf16lechar replacement_char = cpu_to_le16('?');
@@ -655,19 +818,20 @@ file_name_valid(utf16lechar *name, size_t num_chars, bool fix)
        if (num_chars == 0)
                return true;
        for (i = 0; i < num_chars; i++) {
-               switch (name[i]) {
-       #ifdef __WIN32__
-               case cpu_to_le16('\\'):
-               case cpu_to_le16(':'):
-               case cpu_to_le16('*'):
-               case cpu_to_le16('?'):
-               case cpu_to_le16('"'):
-               case cpu_to_le16('<'):
-               case cpu_to_le16('>'):
-               case cpu_to_le16('|'):
+               switch (le16_to_cpu(name[i])) {
+       #ifdef _WIN32
+               case '\x01'...'\x1F':
+               case '\\':
+               case ':':
+               case '*':
+               case '?':
+               case '"':
+               case '<':
+               case '>':
+               case '|':
        #endif
-               case cpu_to_le16('/'):
-               case cpu_to_le16('\0'):
+               case '/':
+               case '\0':
                        if (fix)
                                name[i] = replacement_char;
                        else
@@ -698,9 +862,7 @@ dentry_calculate_extraction_name(struct wim_dentry *dentry,
 
        if (!ctx->supported_features.case_sensitive_filenames) {
                struct wim_dentry *other;
-               list_for_each_entry(other, &dentry->d_ci_conflict_list,
-                                   d_ci_conflict_list)
-               {
+               dentry_for_each_ci_match(other, dentry) {
                        if (will_extract_dentry(other)) {
                                if (ctx->extract_flags &
                                    WIMLIB_EXTRACT_FLAG_ALL_CASE_CONFLICTS) {
@@ -972,10 +1134,11 @@ ref_stream_if_needed(struct wim_dentry *dentry, struct wim_inode *inode,
                         * - file is a directory
                         * - file is encrypted
                         * - backend needs to create the file as UNIX symlink
-                        * - backend will extract the stream as externally backed
+                        * - backend will extract the stream as externally
+                        *   backed from the WIM archive itself
                         */
-                       if (ctx->apply_ops->will_externally_back) {
-                               int ret = (*ctx->apply_ops->will_externally_back)(dentry, ctx);
+                       if (ctx->apply_ops->will_back_from_wim) {
+                               int ret = (*ctx->apply_ops->will_back_from_wim)(dentry, ctx);
                                if (ret > 0) /* Error?  */
                                        return ret;
                                if (ret < 0) /* Won't externally back?  */
@@ -1066,12 +1229,14 @@ static void
 inode_tally_features(const struct wim_inode *inode,
                     struct wim_features *features)
 {
-       if (inode->i_attributes & FILE_ATTRIBUTE_ARCHIVE)
-               features->archive_files++;
+       if (inode->i_attributes & FILE_ATTRIBUTE_READONLY)
+               features->readonly_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_ARCHIVE)
+               features->archive_files++;
        if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
                features->compressed_files++;
        if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
@@ -1097,6 +1262,10 @@ inode_tally_features(const struct wim_inode *inode,
                features->security_descriptors++;
        if (inode_has_unix_data(inode))
                features->unix_data++;
+       if (inode_has_object_id(inode))
+               features->object_ids++;
+       if (inode_has_xattrs(inode))
+               features->xattrs++;
 }
 
 /* Tally features necessary to extract a dentry and the corresponding inode.  */
@@ -1149,9 +1318,11 @@ do_feature_check(const struct wim_features *required_features,
 
        /* File attributes.  */
        if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) {
-               /* Note: Don't bother the user about FILE_ATTRIBUTE_ARCHIVE.
-                * We're an archive program, so theoretically we can do what we
-                * want with it.  */
+
+               if (required_features->readonly_files &&
+                   !supported_features->readonly_files)
+                       WARNING("Ignoring FILE_ATTRIBUTE_READONLY of %lu files",
+                               required_features->readonly_files);
 
                if (required_features->hidden_files &&
                    !supported_features->hidden_files)
@@ -1163,6 +1334,10 @@ do_feature_check(const struct wim_features *required_features,
                        WARNING("Ignoring FILE_ATTRIBUTE_SYSTEM of %lu files",
                                required_features->system_files);
 
+               /* Note: Don't bother the user about FILE_ATTRIBUTE_ARCHIVE.
+                * We're an archive program, so theoretically we can do what we
+                * want with it.  */
+
                if (required_features->compressed_files &&
                    !supported_features->compressed_files)
                        WARNING("Ignoring FILE_ATTRIBUTE_COMPRESSED of %lu files",
@@ -1228,19 +1403,38 @@ do_feature_check(const struct wim_features *required_features,
                WARNING("Ignoring Windows NT security descriptors of %lu files",
                        required_features->security_descriptors);
 
-       /* UNIX data.  */
-       if ((extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) &&
-           required_features->unix_data && !supported_features->unix_data)
+       /* Standard UNIX metadata */
+       if (required_features->unix_data &&
+           (!supported_features->unix_data ||
+            !(extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA)))
        {
-               ERROR("Extraction backend does not support UNIX data!");
-               return WIMLIB_ERR_UNSUPPORTED;
+               if (extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) {
+                       ERROR("Requested UNIX metadata extraction, but "
+                             "extraction backend does not support it!");
+                       return WIMLIB_ERR_UNSUPPORTED;
+               }
+               WARNING("Ignoring UNIX metadata (uid/gid/mode/rdev) of %lu files%"TS,
+                       required_features->unix_data,
+                       (supported_features->unix_data ?
+                        T("\n          (use --unix-data mode to extract these)") : T("")));
        }
 
-       if (required_features->unix_data &&
-           !(extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA))
+       /* Extended attributes */
+       if (required_features->xattrs &&
+           (!supported_features->xattrs ||
+            (supported_features->unix_data &&
+             !(extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA))))
        {
-               WARNING("Ignoring UNIX metadata of %lu files",
-                       required_features->unix_data);
+               WARNING("Ignoring extended attributes of %lu files%"TS,
+                       required_features->xattrs,
+                       (supported_features->xattrs ?
+                        T("\n          (use --unix-data mode to extract these)") : T("")));
+       }
+
+       /* Object IDs.  */
+       if (required_features->object_ids && !supported_features->object_ids) {
+               WARNING("Ignoring object IDs of %lu files",
+                       required_features->object_ids);
        }
 
        /* DOS Names.  */
@@ -1273,7 +1467,7 @@ select_apply_operations(int extract_flags)
        if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS)
                return &ntfs_3g_apply_ops;
 #endif
-#ifdef __WIN32__
+#ifdef _WIN32
        return &win32_apply_ops;
 #else
        return &unix_apply_ops;
@@ -1291,7 +1485,8 @@ extract_trees(WIMStruct *wim, struct wim_dentry **trees, size_t num_trees,
 
        if (extract_flags & WIMLIB_EXTRACT_FLAG_TO_STDOUT) {
                ret = extract_dentries_to_stdout(trees, num_trees,
-                                                wim->blob_table);
+                                                wim->blob_table,
+                                                extract_flags);
                goto out;
        }
 
@@ -1379,12 +1574,12 @@ extract_trees(WIMStruct *wim, struct wim_dentry **trees, size_t num_trees,
                 * subtract <HARDLINKBYTES> from this if hard links are
                 * supported by the extraction mode.)  */
                ctx->progress.extract.total_bytes =
-                       wim_info_get_image_total_bytes(wim->wim_info,
-                                                      wim->current_image);
+                       xml_get_image_total_bytes(wim->xml_info,
+                                                 wim->current_image);
                if (ctx->supported_features.hard_links) {
                        ctx->progress.extract.total_bytes -=
-                               wim_info_get_image_hard_link_bytes(wim->wim_info,
-                                                                  wim->current_image);
+                               xml_get_image_hard_link_bytes(wim->xml_info,
+                                                             wim->current_image);
                }
        }
 
@@ -1430,7 +1625,7 @@ mkdir_if_needed(const tchar *target)
        if (errno == EEXIST)
                return 0;
 
-#ifdef __WIN32__
+#ifdef _WIN32
        /* _wmkdir() fails with EACCES if called on a drive root directory.  */
        if (errno == EACCES)
                return 0;
@@ -1462,14 +1657,14 @@ check_extract_flags(const WIMStruct *wim, int *extract_flags_p)
 
 #ifndef WITH_NTFS_3G
        if (extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) {
-               ERROR("wimlib was compiled without support for NTFS-3g, so\n"
+               ERROR("wimlib was compiled without support for NTFS-3G, so\n"
                      "        it cannot apply a WIM image directly to an NTFS volume.");
                return WIMLIB_ERR_UNSUPPORTED;
        }
 #endif
 
        if (extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) {
-#ifdef __WIN32__
+#ifdef _WIN32
                if (!wim->filename)
                        return WIMLIB_ERR_NO_FILENAME;
 #else
@@ -1478,6 +1673,34 @@ check_extract_flags(const WIMStruct *wim, int *extract_flags_p)
 #endif
        }
 
+       if (extract_flags & (WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K |
+                            WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K |
+                            WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K |
+                            WIMLIB_EXTRACT_FLAG_COMPACT_LZX))
+       {
+       #ifdef _WIN32
+               int count = 0;
+               count += ((extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K) != 0);
+               count += ((extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K) != 0);
+               count += ((extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K) != 0);
+               count += ((extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_LZX) != 0);
+               if (count != 1) {
+                       ERROR("Only one compression format can be specified "
+                             "for compact-mode extraction!");
+                       return WIMLIB_ERR_INVALID_PARAM;
+               }
+               if (extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) {
+                       ERROR("Compact-mode extraction and WIMBoot-mode "
+                             "extraction are mutually exclusive!");
+                       return WIMLIB_ERR_INVALID_PARAM;
+               }
+       #else
+               ERROR("Compact-mode extraction (System Compression) "
+                     "is only supported on Windows!");
+               return WIMLIB_ERR_UNSUPPORTED;
+       #endif
+       }
+
 
        if ((extract_flags & (WIMLIB_EXTRACT_FLAG_RPFIX |
                              WIMLIB_EXTRACT_FLAG_NORPFIX |
@@ -1648,13 +1871,11 @@ extract_single_image(WIMStruct *wim, int image,
 }
 
 static const tchar * const filename_forbidden_chars =
-T(
-#ifdef __WIN32__
-"<>:\"/\\|?*"
+#ifdef _WIN32
+T("<>:\"/\\|?*");
 #else
-"/"
+T("/");
 #endif
-);
 
 /* This function checks if it is okay to use a WIM image's name as a directory
  * name.  */
@@ -1664,7 +1885,8 @@ image_name_ok_as_dir(const tchar *image_name)
        return image_name && *image_name &&
                !tstrpbrk(image_name, filename_forbidden_chars) &&
                tstrcmp(image_name, T(".")) &&
-               tstrcmp(image_name, T(".."));
+               tstrcmp(image_name, T("..")) &&
+               tstrlen(image_name) <= 128;
 }
 
 /* Extracts all images from the WIM to the directory @target, with the images
@@ -1672,9 +1894,8 @@ image_name_ok_as_dir(const tchar *image_name)
 static int
 extract_all_images(WIMStruct *wim, const tchar *target, int extract_flags)
 {
-       size_t image_name_max_len = max(xml_get_max_image_name_len(wim), 20);
        size_t output_path_len = tstrlen(target);
-       tchar buf[output_path_len + 1 + image_name_max_len + 1];
+       tchar buf[output_path_len + 1 + 128 + 1];
        int ret;
        int image;
        const tchar *image_name;
@@ -1749,7 +1970,7 @@ wimlib_extract_pathlist(WIMStruct *wim, int image, const tchar *target,
        ret = read_path_list_file(path_list_file, &paths, &num_paths, &mem);
        if (ret) {
                ERROR("Failed to read path list file \"%"TS"\"",
-                     path_list_file);
+                     path_list_file ? path_list_file : T("<stdin>"));
                return ret;
        }
 
@@ -1830,7 +2051,7 @@ wimlib_extract_image_from_pipe_with_progress(int pipe_fd,
                if (ret)
                        goto out_wimlib_free;
 
-               if (wim_info_get_num_images(pwm->wim_info) != pwm->hdr.image_count) {
+               if (xml_get_image_count(pwm->xml_info) != pwm->hdr.image_count) {
                        ERROR("Image count in XML data is not the same as in WIM header.");
                        ret = WIMLIB_ERR_IMAGE_COUNT;
                        goto out_wimlib_free;
@@ -1863,52 +2084,9 @@ wimlib_extract_image_from_pipe_with_progress(int pipe_fd,
 
        /* Load the needed metadata resource.  */
        for (i = 1; i <= pwm->hdr.image_count; i++) {
-               struct wim_image_metadata *imd;
-               struct wim_reshdr reshdr;
-               struct wim_resource_descriptor *metadata_rdesc;
-
-               imd = pwm->image_metadata[i - 1];
-
-               ret = WIMLIB_ERR_NOMEM;
-               imd->metadata_blob = new_blob_descriptor();
-               if (!imd->metadata_blob)
-                       goto out_wimlib_free;
-
-               imd->metadata_blob->is_metadata = 1;
-
-               ret = read_pwm_blob_header(pwm, imd->metadata_blob->hash,
-                                          &reshdr, NULL);
+               ret = handle_pwm_metadata_resource(pwm, i, i == image);
                if (ret)
                        goto out_wimlib_free;
-
-               if (!(reshdr.flags & WIM_RESHDR_FLAG_METADATA)) {
-                       ERROR("Expected metadata resource, but found "
-                             "non-metadata resource");
-                       ret = WIMLIB_ERR_INVALID_PIPABLE_WIM;
-                       goto out_wimlib_free;
-               }
-
-               ret = WIMLIB_ERR_NOMEM;
-               metadata_rdesc = MALLOC(sizeof(struct wim_resource_descriptor));
-               if (!metadata_rdesc)
-                       goto out_wimlib_free;
-               wim_reshdr_to_desc_and_blob(&reshdr, pwm, metadata_rdesc,
-                                           imd->metadata_blob);
-
-               if (i == image) {
-                       /* Metadata resource is for the image being extracted.
-                        * Parse it and save the metadata in memory.  */
-                       ret = read_metadata_resource(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_wim_resource(metadata_rdesc);
-                       if (ret)
-                               goto out_wimlib_free;
-               }
        }
        /* Extract the image.  */
        extract_flags |= WIMLIB_EXTRACT_FLAG_FROM_PIPE;