]> wimlib.net Git - wimlib/blobdiff - src/ntfs-3g_apply.c
resource: pass blob and offset to consume_chunk
[wimlib] / src / ntfs-3g_apply.c
index a8292c7e2432c90da4b8d6c919e1a50fbf9c6dfa..27b0ab90a260969cd351db0b352f6a7a112ba765 100644 (file)
 /*
  * ntfs-3g_apply.c
  *
- * Apply a WIM image directly to a NTFS volume using libntfs-3g.  Restore as
+ * Apply a WIM image directly to an NTFS volume using libntfs-3g.  Restore as
  * much information as possible, including security data, file attributes, DOS
- * names, and alternate data streams.
+ * names, alternate data streams, and object IDs.
+ *
+ * Note: because NTFS-3G offers inode-based interfaces, we actually don't need
+ * to deal with paths at all!  (Other than for error messages.)
  */
 
 /*
- * Copyright (C) 2012, 2013 Eric Biggers
- *
- * This file is part of wimlib, a library for working with WIM files.
+ * Copyright (C) 2012-2016 Eric Biggers
  *
- * wimlib is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free
- * Software Foundation; either version 3 of the License, or (at your option)
- * any later version.
+ * 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
+ * Software Foundation; either version 3 of the License, or (at your option) any
+ * later version.
  *
- * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  * details.
  *
- * You should have received a copy of the GNU General Public License
- * along with wimlib; if not, see http://www.gnu.org/licenses/.
+ * 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/.
  */
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
 
-#ifdef WITH_NTFS_3G
-
+#include <errno.h>
 #include <locale.h>
 #include <string.h>
-#include <time.h> /* NTFS-3g headers are missing <time.h> include */
 
 #include <ntfs-3g/attrib.h>
-#include <ntfs-3g/endians.h>
+#include <ntfs-3g/object_id.h>
 #include <ntfs-3g/reparse.h>
 #include <ntfs-3g/security.h>
-#include <ntfs-3g/types.h>
-#include <ntfs-3g/xattrs.h>
 
+#include "wimlib/assert.h"
 #include "wimlib/apply.h"
-#include "wimlib/compiler.h"
+#include "wimlib/blob_table.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/object_id.h"
 #include "wimlib/reparse.h"
 #include "wimlib/security.h"
 
-struct ntfs_attr_extract_ctx {
-       u64 offset;
-       ntfs_attr *na;
-};
-
 static int
-extract_wim_chunk_to_ntfs_attr(const void *buf, size_t len, void *_ctx)
+ntfs_3g_get_supported_features(const char *target,
+                              struct wim_features *supported_features)
 {
-       struct ntfs_attr_extract_ctx *ctx = _ctx;
-       if (ntfs_attr_pwrite(ctx->na, ctx->offset, len, buf) == len) {
-               ctx->offset += len;
-               return 0;
-       } else {
-               ERROR_WITH_ERRNO("Error extracting WIM resource to NTFS attribute");
-               return WIMLIB_ERR_WRITE;
-       }
+       supported_features->readonly_files            = 1;
+       supported_features->hidden_files              = 1;
+       supported_features->system_files              = 1;
+       supported_features->archive_files             = 1;
+       supported_features->compressed_files          = 1;
+       supported_features->not_context_indexed_files = 1;
+       supported_features->named_data_streams        = 1;
+       supported_features->hard_links                = 1;
+       supported_features->reparse_points            = 1;
+       supported_features->security_descriptors      = 1;
+       supported_features->short_names               = 1;
+       supported_features->object_ids                = 1;
+       supported_features->timestamps                = 1;
+       supported_features->case_sensitive_filenames  = 1;
+       return 0;
 }
 
-/*
- * Extracts a WIM resource to a NTFS attribute.
- */
+struct ntfs_3g_apply_ctx {
+       /* Extract flags, the pointer to the WIMStruct, etc.  */
+       struct apply_ctx common;
+
+       /* Pointer to the open NTFS volume  */
+       ntfs_volume *vol;
+
+       ntfs_attr *open_attrs[MAX_OPEN_FILES];
+       unsigned num_open_attrs;
+       ntfs_inode *open_inodes[MAX_OPEN_FILES];
+       unsigned num_open_inodes;
+
+       struct reparse_buffer_disk rpbuf;
+       u8 *reparse_ptr;
+
+       unsigned num_reparse_inodes;
+       ntfs_inode *ntfs_reparse_inodes[MAX_OPEN_FILES];
+       struct wim_inode *wim_reparse_inodes[MAX_OPEN_FILES];
+};
+
 static int
-extract_wim_resource_to_ntfs_attr(const struct wim_lookup_table_entry *lte,
-                                 ntfs_attr *na)
+ntfs_3g_set_timestamps(ntfs_inode *ni, const struct wim_inode *inode)
 {
-       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);
+       u64 times[3] = {
+               inode->i_creation_time,
+               inode->i_last_write_time,
+               inode->i_last_access_time,
+       };
+
+       if (ntfs_inode_set_times(ni, (const char *)times, sizeof(times), 0))
+               return WIMLIB_ERR_SET_TIMESTAMPS;
+       return 0;
 }
 
-/* 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.
- */
+/* Restore the timestamps on the NTFS inode corresponding to @inode.  */
 static int
-write_ntfs_data_streams(ntfs_inode *ni, struct wim_dentry *dentry,
-                       union wimlib_progress_info *progress_info)
+ntfs_3g_restore_timestamps(ntfs_volume *vol, const struct wim_inode *inode)
 {
-       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;
+       ntfs_inode *ni;
+       int res;
 
-                       }
-               }
+       ni = ntfs_inode_open(vol, inode->i_mft_no);
+       if (!ni)
+               goto fail;
 
-               /* 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;
-                       }
+       res = ntfs_3g_set_timestamps(ni, inode);
 
-                       /* 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;
-                       }
+       if (ntfs_inode_close(ni) || res)
+               goto fail;
 
-                       /* Actually extract the stream */
-                       ret = extract_wim_resource_to_ntfs_attr(lte, na);
+       return 0;
 
-                       /* Close the attribute */
-                       ntfs_attr_close(na);
-                       if (ret)
-                               break;
+fail:
+       ERROR_WITH_ERRNO("Failed to update timestamps of \"%s\" in NTFS volume",
+                        dentry_full_path(inode_first_extraction_dentry(inode)));
+       return WIMLIB_ERR_SET_TIMESTAMPS;
+}
 
-                       /* 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;
+/* Restore the DOS name of the @dentry.
+ * This closes both @ni and @dir_ni.
+ * If either is NULL, then they are opened temporarily.  */
+static int
+ntfs_3g_restore_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
+                        struct wim_dentry *dentry, ntfs_volume *vol)
+{
+       int ret;
+       const char *dos_name;
+       size_t dos_name_nbytes;
 
-               /* 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++;
+       /* Note: ntfs_set_ntfs_dos_name() closes both inodes (even if it fails).
+        * And it takes in a multibyte string, even though it translates it to
+        * UTF-16LE internally... which is annoying because we currently have
+        * the UTF-16LE string but not the multibyte string.  */
+
+       ret = utf16le_get_tstr(dentry->d_short_name, dentry->d_short_name_nbytes,
+                              &dos_name, &dos_name_nbytes);
+       if (ret)
+               goto out_close;
+
+       if (!dir_ni)
+               dir_ni = ntfs_inode_open(vol, dentry->d_parent->d_inode->i_mft_no);
+       if (!ni)
+               ni = ntfs_inode_open(vol, dentry->d_inode->i_mft_no);
+       if (dir_ni && ni) {
+               ret = ntfs_set_ntfs_dos_name(ni, dir_ni,
+                                            dos_name, dos_name_nbytes, 0);
+               dir_ni = NULL;
+               ni = NULL;
+       } else {
+               ret = -1;
+       }
+       utf16le_put_tstr(dos_name);
+       if (unlikely(ret)) {
+               int err = errno;
+               ERROR_WITH_ERRNO("Failed to set DOS name of \"%s\" in NTFS "
+                                "volume", dentry_full_path(dentry));
+               if (err == EILSEQ) {
+                       ERROR("This error may have been caused by a known "
+                             "bug in libntfs-3g where it is unable to set "
+                             "DOS names on files whose long names contain "
+                             "unpaired surrogate characters.  This bug "
+                             "was fixed in the development version of "
+                             "NTFS-3G in June 2016.");
+               }
+               if (err == EINVAL) {
+                       utf16lechar c =
+                               dentry->d_name[dentry->d_name_nbytes / 2 - 1];
+                       if (c == cpu_to_le16('.') || c == cpu_to_le16(' ')) {
+                               ERROR("This error was probably caused by a "
+                                     "known bug in libntfs-3g where it is "
+                                     "unable to set DOS names on files whose "
+                                     "long names end with a dot or space "
+                                     "character.  See "
+                                     "https://wimlib.net/forums/viewtopic.php?f=1&t=294 "
+                                     "for more information.");
+                       }
+               }
+               ret = WIMLIB_ERR_SET_SHORT_NAME;
+               goto out_close;
        }
+
+       /* Unlike most other NTFS-3G functions, ntfs_set_ntfs_dos_name()
+        * changes the directory's last modification timestamp...
+        * Change it back.  */
+       return ntfs_3g_restore_timestamps(vol, dentry->d_parent->d_inode);
+
+out_close:
+       /* ntfs_inode_close() can take a NULL argument, but it's probably best
+        * not to rely on this behavior.  */
+       if (ni)
+               ntfs_inode_close(ni);
+       if (dir_ni)
+               ntfs_inode_close(dir_ni);
        return ret;
 }
 
-/* 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)
+static int
+ntfs_3g_restore_reparse_point(ntfs_inode *ni, const struct wim_inode *inode,
+                             unsigned blob_size, struct ntfs_3g_apply_ctx *ctx)
 {
-       char *p;
-       const char *dir_name;
-       ntfs_inode *dir_ni;
-       char orig;
+       complete_reparse_point(&ctx->rpbuf, inode, blob_size);
 
-       p = dentry->_full_path + dentry->full_path_nbytes;
-       do {
-               p--;
-       } while (*p != '/');
+       if (ntfs_set_ntfs_reparse_data(ni, (const char *)&ctx->rpbuf,
+                                      REPARSE_DATA_OFFSET + blob_size, 0))
+       {
+               int err = errno;
+               ERROR_WITH_ERRNO("Failed to set reparse data on \"%s\"",
+                                dentry_full_path(
+                                       inode_first_extraction_dentry(inode)));
+               if (err == EINVAL && !(inode->i_reparse_tag & 0x80000000)) {
+                       WARNING("This reparse point had a non-Microsoft reparse "
+                               "tag.  The preceding error may have been caused "
+                               "by a known bug in libntfs-3g where it does not "
+                               "correctly validate non-Microsoft reparse "
+                               "points.  This bug was fixed in NTFS-3G version "
+                               "2016.2.22.");
+               }
+               return WIMLIB_ERR_SET_REPARSE_DATA;
+       }
 
-       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);
+       return 0;
+}
+
+static bool
+ntfs_3g_has_empty_attributes(const struct wim_inode *inode)
+{
+       for (unsigned i = 0; i < inode->i_num_streams; i++) {
+               const struct wim_inode_stream *strm = &inode->i_streams[i];
+
+               if (stream_blob_resolved(strm) == NULL &&
+                   (strm->stream_type == STREAM_TYPE_REPARSE_POINT ||
+                    stream_is_named_data_stream(strm)))
+                       return true;
        }
-       *p = orig;
-       return dir_ni;
+       return false;
 }
 
 /*
- * Makes a NTFS hard link.
+ * Create empty attributes (named data streams and potentially a reparse point)
+ * for the specified file, if there are any.
  *
- * 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.
+ * Since these won't have blob descriptors, they won't show up in the call to
+ * extract_blob_list().  Hence the need for the special case.
  *
- * 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.
+ * Keep this in sync with ntfs_3g_has_empty_attributes()!
  */
 static int
-apply_ntfs_hardlink(struct wim_dentry *from_dentry,
-                   const struct wim_inode *inode,
-                   ntfs_inode *dir_ni)
+ntfs_3g_create_empty_attributes(ntfs_inode *ni,
+                               const struct wim_inode *inode,
+                               struct ntfs_3g_apply_ctx *ctx)
 {
+       for (unsigned i = 0; i < inode->i_num_streams; i++) {
+
+               const struct wim_inode_stream *strm = &inode->i_streams[i];
+               int ret;
+
+               if (stream_blob_resolved(strm) != NULL)
+                       continue;
+
+               if (strm->stream_type == STREAM_TYPE_REPARSE_POINT) {
+                       ret = ntfs_3g_restore_reparse_point(ni, inode, 0, ctx);
+                       if (ret)
+                               return ret;
+               } else if (stream_is_named_data_stream(strm)) {
+                       if (ntfs_attr_add(ni, AT_DATA, strm->stream_name,
+                                         utf16le_len_chars(strm->stream_name),
+                                         NULL, 0))
+                       {
+                               ERROR_WITH_ERRNO("Failed to create named data "
+                                                "stream of \"%s\"",
+                                                dentry_full_path(
+                                       inode_first_extraction_dentry(inode)));
+                               return WIMLIB_ERR_NTFS_3G;
+                       }
+               }
+       }
+       return 0;
+}
+
+/* Set attributes, security descriptor, and timestamps on the NTFS inode @ni.
+ */
+static int
+ntfs_3g_set_metadata(ntfs_inode *ni, const struct wim_inode *inode,
+                    const struct ntfs_3g_apply_ctx *ctx)
+{
+       int extract_flags;
+       const struct wim_security_data *sd;
+       struct wim_dentry *one_dentry;
        int ret;
-       ntfs_inode *to_ni;
-       ntfs_volume *vol;
 
-       vol = dir_ni->vol;
-       ret = ntfs_inode_close(dir_ni);
-       if (ret != 0) {
-               ERROR_WITH_ERRNO("Error closing directory");
-               return WIMLIB_ERR_NTFS_3G;
+       extract_flags = ctx->common.extract_flags;
+       sd = wim_get_current_security_data(ctx->common.wim);
+       one_dentry = inode_first_extraction_dentry(inode);
+
+       /* Object ID */
+       {
+               u32 len;
+               const void *object_id = inode_get_object_id(inode, &len);
+               if (unlikely(object_id != NULL) &&
+                   ntfs_set_ntfs_object_id(ni, object_id, len, 0))
+               {
+                       if (errno == EEXIST) {
+                               WARNING("Duplicate object ID on file \"%s\"",
+                                       dentry_full_path(one_dentry));
+                       } else {
+                               ERROR_WITH_ERRNO("Failed to set object ID on "
+                                                "\"%s\" in NTFS volume",
+                                                dentry_full_path(one_dentry));
+                               return WIMLIB_ERR_NTFS_3G;
+                       }
+               }
        }
 
-       DEBUG("Extracting NTFS hard link `%s' => `%s'",
-             from_dentry->_full_path, inode->i_extracted_file);
+       /* Attributes  */
+       if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) {
+               u32 attrib = inode->i_attributes;
 
-       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;
+               attrib &= ~(FILE_ATTRIBUTE_SPARSE_FILE |
+                           FILE_ATTRIBUTE_ENCRYPTED);
+
+               if (ntfs_set_ntfs_attrib(ni, (const char *)&attrib,
+                                        sizeof(attrib), 0))
+               {
+                       ERROR_WITH_ERRNO("Failed to set attributes on \"%s\" "
+                                        "in NTFS volume",
+                                        dentry_full_path(one_dentry));
+                       return WIMLIB_ERR_SET_ATTRIBUTES;
+               }
        }
 
-       dir_ni = dentry_open_parent_ni(from_dentry, vol);
-       if (!dir_ni) {
-               ntfs_inode_close(to_ni);
-               return WIMLIB_ERR_NTFS_3G;
+       /* Security descriptor  */
+       if (inode_has_security_descriptor(inode)
+           && !(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS))
+       {
+               struct SECURITY_CONTEXT sec_ctx = { ctx->vol };
+               const void *desc;
+               size_t desc_size;
+
+               desc = sd->descriptors[inode->i_security_id];
+               desc_size = sd->sizes[inode->i_security_id];
+
+               ret = ntfs_set_ntfs_acl(&sec_ctx, ni, desc, desc_size, 0);
+
+               if (unlikely(ret)) {
+                       int err = errno;
+                       ERROR_WITH_ERRNO("Failed to set security descriptor on "
+                                        "\"%s\" in NTFS volume",
+                                        dentry_full_path(one_dentry));
+                       if (err == EINVAL && wimlib_print_errors) {
+                               fprintf(wimlib_error_file,
+                                       "The security descriptor is: ");
+                               print_byte_field(desc, desc_size, wimlib_error_file);
+                               fprintf(wimlib_error_file,
+                                       "\n\nThis error occurred because libntfs-3g thinks "
+                                       "the security descriptor is invalid.  There "
+                                       "are several known bugs with libntfs-3g's "
+                                       "security descriptor validation logic in older "
+                                       "versions.  Please upgrade to NTFS-3G version "
+                                       "2016.2.22 or later if you haven't already.\n");
+                       }
+                       return WIMLIB_ERR_SET_SECURITY;
+               }
        }
 
-       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);
+       /* Timestamps  */
+       ret = ntfs_3g_set_timestamps(ni, inode);
        if (ret) {
-               ERROR_WITH_ERRNO("Could not create hard link `%s' => `%s'",
-                                from_dentry->_full_path,
-                                inode->i_extracted_file);
-               ret = WIMLIB_ERR_NTFS_3G;
+               ERROR_WITH_ERRNO("Failed to set timestamps on \"%s\" "
+                                "in NTFS volume",
+                                dentry_full_path(one_dentry));
+               return ret;
        }
-       return ret;
+       return 0;
 }
 
-/* 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.
- */
+/* Recursively creates all the subdirectories of @dir, which has been created as
+ * the NTFS inode @dir_ni.  */
 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_dirs_recursive(ntfs_inode *dir_ni, struct wim_dentry *dir,
+                             struct ntfs_3g_apply_ctx *ctx)
 {
-       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);
-               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);
+       struct wim_dentry *child;
+
+       for_dentry_child(child, dir) {
+               ntfs_inode *ni;
+               int ret;
+
+               if (!(child->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))
+                       continue;
+               if (!will_extract_dentry(child))
+                       continue;
+
+               ni = ntfs_create(dir_ni, 0, child->d_extraction_name,
+                                child->d_extraction_name_nchars, S_IFDIR);
+               if (!ni) {
+                       ERROR_WITH_ERRNO("Error creating \"%s\" in NTFS volume",
+                                        dentry_full_path(child));
+                       return WIMLIB_ERR_NTFS_3G;
+               }
+
+               child->d_inode->i_mft_no = ni->mft_no;
+
+               ret = report_file_created(&ctx->common);
+               if (!ret)
+                       ret = ntfs_3g_set_metadata(ni, child->d_inode, ctx);
+               if (!ret)
+                       ret = ntfs_3g_create_dirs_recursive(ni, child, ctx);
+
+               if (ntfs_inode_close_in_dir(ni, dir_ni) && !ret) {
+                       ERROR_WITH_ERRNO("Error closing \"%s\" in NTFS volume",
+                                        dentry_full_path(child));
                        ret = WIMLIB_ERR_NTFS_3G;
                }
+               if (ret)
+                       return ret;
        }
-       return ret;
+       return 0;
 }
 
-/*
- * Transfers the reparse data from a WIM inode (which must represent a reparse
- * point) to a NTFS inode.
- */
+/* For each WIM dentry in the @root tree that represents a directory, create the
+ * corresponding directory in the NTFS volume @ctx->vol.  */
 static int
-apply_reparse_data(ntfs_inode *ni, struct wim_dentry *dentry,
-                  union wimlib_progress_info *progress_info)
+ntfs_3g_create_directories(struct wim_dentry *root,
+                          struct list_head *dentry_list,
+                          struct ntfs_3g_apply_ctx *ctx)
 {
+       ntfs_inode *root_ni;
        int ret;
-       u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8);
-       u16 rpbuflen;
+       struct wim_dentry *dentry;
+
+       /* Create the directories using POSIX names.  */
 
-       DEBUG("Applying reparse data to `%s'", dentry->_full_path);
+       root_ni = ntfs_inode_open(ctx->vol, FILE_root);
+       if (!root_ni) {
+               ERROR_WITH_ERRNO("Can't open root of NTFS volume");
+               return WIMLIB_ERR_NTFS_3G;
+       }
 
-       ret = wim_inode_get_reparse_data(dentry->d_inode, rpbuf, &rpbuflen);
+       root->d_inode->i_mft_no = FILE_root;
+
+       ret = ntfs_3g_set_metadata(root_ni, root->d_inode, ctx);
+       if (!ret)
+               ret = ntfs_3g_create_dirs_recursive(root_ni, root, ctx);
+
+       if (ntfs_inode_close(root_ni) && !ret) {
+               ERROR_WITH_ERRNO("Error closing root of NTFS volume");
+               ret = WIMLIB_ERR_NTFS_3G;
+       }
        if (ret)
                return ret;
 
-       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);
-               return WIMLIB_ERR_NTFS_3G;
-       }
+       /* Set the DOS name of any directory that has one.  In addition, create
+        * empty attributes for directories that have them.  Note that creating
+        * an empty reparse point attribute must happen *after* setting the DOS
+        * name in order to work around a case where ntfs_set_ntfs_dos_name()
+        * fails with EOPNOTSUPP.  This bug was fixed in NTFS-3G version
+        * 2016.2.22.  */
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               const struct wim_inode *inode = dentry->d_inode;
+
+               if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))
+                       continue;
+               if (dentry_has_short_name(dentry)) {
+                       ret = ntfs_3g_restore_dos_name(NULL, NULL, dentry,
+                                                      ctx->vol);
+                       if (ret)
+                               return ret;
+                       ret = report_file_created(&ctx->common);
+                       if (ret)
+                               return ret;
+               }
+               if (ntfs_3g_has_empty_attributes(inode)) {
+                       ntfs_inode *ni;
 
-       progress_info->extract.completed_bytes += rpbuflen - 8;
+                       ret = WIMLIB_ERR_NTFS_3G;
+                       ni = ntfs_inode_open(ctx->vol, inode->i_mft_no);
+                       if (ni) {
+                               ret = ntfs_3g_create_empty_attributes(ni, inode,
+                                                                     ctx);
+                               if (ntfs_inode_close(ni) && !ret)
+                                       ret = WIMLIB_ERR_NTFS_3G;
+                       }
+                       if (ret) {
+                               ERROR_WITH_ERRNO("Failed to create empty "
+                                                "attributes of directory "
+                                                "\"%s\" in NTFS volume",
+                                                dentry_full_path(dentry));
+                               return ret;
+                       }
+               }
+       }
        return 0;
 }
 
+/* When creating an inode that will have a short (DOS) name, we create it using
+ * the long name associated with the short name.  This ensures that the short
+ * name gets associated with the correct long name.  */
+static struct wim_dentry *
+ntfs_3g_first_extraction_alias(struct wim_inode *inode)
+{
+       struct wim_dentry *dentry;
+
+       inode_for_each_extraction_alias(dentry, inode)
+               if (dentry_has_short_name(dentry))
+                       return dentry;
+       return inode_first_extraction_dentry(inode);
+}
+
 /*
- * Applies a WIM dentry to a NTFS filesystem.
+ * Add a hard link for the NTFS inode @ni at the location corresponding to the
+ * WIM dentry @dentry.
  *
- * @dentry:  The WIM dentry to apply
- * @dir_ni:  The NTFS inode for the parent directory
+ * The parent directory must have already been created on the NTFS volume.
  *
- * @return:  0 on success; nonzero on failure.
+ * Returns 0 on success; returns WIMLIB_ERR_NTFS_3G and sets errno on failure.
  */
 static int
-do_apply_dentry_ntfs(struct wim_dentry *dentry, ntfs_inode *dir_ni,
-                    struct apply_args *args)
+ntfs_3g_add_link(ntfs_inode *ni, struct wim_dentry *dentry)
 {
+       ntfs_inode *dir_ni;
+       int res;
+
+       /* Open the inode of the parent directory.  */
+       dir_ni = ntfs_inode_open(ni->vol, dentry->d_parent->d_inode->i_mft_no);
+       if (!dir_ni)
+               goto fail;
+
+       /* Create the link.  */
+       res = ntfs_link(ni, dir_ni, dentry->d_extraction_name,
+                       dentry->d_extraction_name_nchars);
+
+       /* Close the parent directory.  */
+       if (ntfs_inode_close(dir_ni) || res)
+               goto fail;
+
+       return 0;
+
+fail:
+       ERROR_WITH_ERRNO("Can't create link \"%s\" in NTFS volume",
+                        dentry_full_path(dentry));
+       return WIMLIB_ERR_NTFS_3G;
+}
+
+static int
+ntfs_3g_create_nondirectory(struct wim_inode *inode,
+                           struct ntfs_3g_apply_ctx *ctx)
+{
+       struct wim_dentry *first_dentry;
+       ntfs_inode *dir_ni;
+       ntfs_inode *ni;
+       struct wim_dentry *dentry;
        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;
-                               }
-                       }
-               }
+       first_dentry = ntfs_3g_first_extraction_alias(inode);
+
+       /* Create first link.  */
+
+       dir_ni = ntfs_inode_open(ctx->vol, first_dentry->d_parent->d_inode->i_mft_no);
+       if (!dir_ni) {
+               ERROR_WITH_ERRNO("Can't open \"%s\" in NTFS volume",
+                                dentry_full_path(first_dentry->d_parent));
+               return WIMLIB_ERR_NTFS_3G;
        }
 
-       /* 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);
+       ni = ntfs_create(dir_ni, 0, first_dentry->d_extraction_name,
+                        first_dentry->d_extraction_name_nchars, S_IFREG);
 
        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;
+               ERROR_WITH_ERRNO("Can't create \"%s\" in NTFS volume",
+                                dentry_full_path(first_dentry));
+               ntfs_inode_close(dir_ni);
+               return WIMLIB_ERR_NTFS_3G;
        }
 
-       /* 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);
+       inode->i_mft_no = ni->mft_no;
+
+       /* Set short name if present.  */
+       if (dentry_has_short_name(first_dentry)) {
+
+               ret = ntfs_3g_restore_dos_name(ni, dir_ni, first_dentry, ctx->vol);
+
+               /* ntfs_3g_restore_dos_name() closed both 'ni' and 'dir_ni'.  */
+
                if (ret)
-                       goto out_close_dir_ni;
+                       return ret;
+
+               /* Reopen the inode.  */
+               ni = ntfs_inode_open(ctx->vol, inode->i_mft_no);
+               if (!ni) {
+                       ERROR_WITH_ERRNO("Failed to reopen \"%s\" "
+                                        "in NTFS volume",
+                                        dentry_full_path(first_dentry));
+                       return WIMLIB_ERR_NTFS_3G;
+               }
+       } else {
+               /* Close the directory in which the first link was created.  */
+               if (ntfs_inode_close(dir_ni)) {
+                       ERROR_WITH_ERRNO("Failed to close \"%s\" in NTFS volume",
+                                        dentry_full_path(first_dentry->d_parent));
+                       ret = WIMLIB_ERR_NTFS_3G;
+                       goto out_close_ni;
+               }
        }
 
-       ret = apply_file_attributes_and_security_data(ni, dir_ni, dentry,
-                                                     args->wim,
-                                                     args->extract_flags);
+       /* Create additional links if present.  */
+       inode_for_each_extraction_alias(dentry, inode) {
+               if (dentry != first_dentry) {
+                       ret = ntfs_3g_add_link(ni, dentry);
+                       if (ret)
+                               goto out_close_ni;
+               }
+       }
+
+       /* Set metadata.  */
+       ret = ntfs_3g_set_metadata(ni, inode, ctx);
        if (ret)
-               goto out_close_dir_ni;
+               goto out_close_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 = ntfs_3g_create_empty_attributes(ni, inode, ctx);
+
+out_close_ni:
+       /* Close the inode.  */
+       if (ntfs_inode_close(ni) && !ret) {
+               ERROR_WITH_ERRNO("Error closing \"%s\" in NTFS volume",
+                                dentry_full_path(first_dentry));
+               ret = WIMLIB_ERR_NTFS_3G;
        }
+       return ret;
+}
 
-       /* 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;
+/* For each WIM dentry in the @dentry_list that represents a nondirectory file,
+ * create the corresponding nondirectory file in the NTFS volume.
+ *
+ * Directories must have already been created.  */
+static int
+ntfs_3g_create_nondirectories(struct list_head *dentry_list,
+                             struct ntfs_3g_apply_ctx *ctx)
+{
+       struct wim_dentry *dentry;
+       struct wim_inode *inode;
+       int ret;
+
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               inode = dentry->d_inode;
+               if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
+                       continue;
+               if (dentry == inode_first_extraction_dentry(inode)) {
+                       ret = ntfs_3g_create_nondirectory(inode, ctx);
+                       if (ret)
+                               return ret;
                }
-               /* inodes have been closed by ntfs_set_ntfs_dos_name(). */
-               goto out;
+               ret = report_file_created(&ctx->common);
+               if (ret)
+                       return ret;
        }
-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);
-                       }
+       return 0;
+}
+
+static int
+ntfs_3g_begin_extract_blob_instance(struct blob_descriptor *blob,
+                                   ntfs_inode *ni,
+                                   struct wim_inode *inode,
+                                   const struct wim_inode_stream *strm,
+                                   struct ntfs_3g_apply_ctx *ctx)
+{
+       struct wim_dentry *one_dentry = inode_first_extraction_dentry(inode);
+       ntfschar *stream_name;
+       size_t stream_name_nchars;
+       ntfs_attr *attr;
+
+       if (unlikely(strm->stream_type == STREAM_TYPE_REPARSE_POINT)) {
+
+               if (blob->size > REPARSE_DATA_MAX_SIZE) {
+                       ERROR("Reparse data of \"%s\" has size "
+                             "%"PRIu64" bytes (exceeds %u bytes)",
+                             dentry_full_path(one_dentry),
+                             blob->size, REPARSE_DATA_MAX_SIZE);
+                       return WIMLIB_ERR_INVALID_REPARSE_DATA;
                }
-               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);
+               ctx->reparse_ptr = ctx->rpbuf.rpdata;
+               ctx->ntfs_reparse_inodes[ctx->num_reparse_inodes] = ni;
+               ctx->wim_reparse_inodes[ctx->num_reparse_inodes] = inode;
+               ctx->num_reparse_inodes++;
+               return 0;
+       }
+
+       /* It's a data stream (may be unnamed or named).  */
+       wimlib_assert(strm->stream_type == STREAM_TYPE_DATA);
+
+       if (unlikely(stream_is_named(strm))) {
+               stream_name = strm->stream_name;
+               stream_name_nchars = utf16le_len_chars(stream_name);
+
+               if (ntfs_attr_add(ni, AT_DATA, stream_name,
+                                 stream_name_nchars, NULL, 0))
+               {
+                       ERROR_WITH_ERRNO("Failed to create named data stream of \"%s\"",
+                                        dentry_full_path(one_dentry));
+                       return WIMLIB_ERR_NTFS_3G;
                }
+       } else {
+               /* Don't pass an empty string other than AT_UNNAMED to
+                * ntfs_attr_open() --- it violates assumptions made by
+                * libntfs-3g.  */
+               stream_name = AT_UNNAMED;
+               stream_name_nchars = 0;
        }
-out:
-       return ret;
+
+       /* This should be ensured by extract_blob_list()  */
+       wimlib_assert(ctx->num_open_attrs < MAX_OPEN_FILES);
+
+       attr = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_nchars);
+       if (!attr) {
+               ERROR_WITH_ERRNO("Failed to open data stream of \"%s\"",
+                                dentry_full_path(one_dentry));
+               return WIMLIB_ERR_NTFS_3G;
+       }
+       ctx->open_attrs[ctx->num_open_attrs++] = attr;
+       ntfs_attr_truncate_solid(attr, blob->size);
+       return 0;
 }
 
 static int
-apply_root_dentry_ntfs(struct wim_dentry *dentry,
-                      ntfs_volume *vol, const WIMStruct *wim,
-                      int extract_flags)
+ntfs_3g_cleanup_blob_extract(struct ntfs_3g_apply_ctx *ctx)
 {
-       ntfs_inode *ni;
        int ret = 0;
 
-       ret = calculate_dentry_full_path(dentry);
-       if (ret)
-               return ret;
-
-       ni = ntfs_pathname_to_inode(vol, NULL, "/");
-       if (!ni) {
-               ERROR_WITH_ERRNO("Could not find root NTFS inode");
-               return WIMLIB_ERR_NTFS_3G;
+       for (unsigned i = 0; i < ctx->num_open_attrs; i++) {
+               if (ntfs_attr_pclose(ctx->open_attrs[i]))
+                       ret = -1;
+               ntfs_attr_close(ctx->open_attrs[i]);
        }
-       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");
-               ret = WIMLIB_ERR_NTFS_3G;
+
+       ctx->num_open_attrs = 0;
+
+       for (unsigned i = 0; i < ctx->num_open_inodes; i++) {
+               if (ntfs_inode_close(ctx->open_inodes[i]))
+                       ret = -1;
        }
+       ctx->num_open_inodes = 0;
+
+       ctx->reparse_ptr = NULL;
+       ctx->num_reparse_inodes = 0;
        return ret;
 }
 
-/* Applies a WIM dentry to the NTFS volume */
-int
-apply_dentry_ntfs(struct wim_dentry *dentry, void *arg)
+static ntfs_inode *
+ntfs_3g_open_inode(struct wim_inode *inode, struct ntfs_3g_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;
+       ntfs_inode *ni;
 
-       /* 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;
+       /* If the same blob is being extracted to multiple streams of the same
+        * inode, then we must only open the inode once.  */
+       if (unlikely(inode->i_num_streams > 1)) {
+               for (unsigned i = 0; i < ctx->num_open_inodes; i++) {
+                       if (ctx->open_inodes[i]->mft_no == inode->i_mft_no) {
+                               return ctx->open_inodes[i];
                        }
                }
        }
-       dentry->d_inode->i_dos_name_extracted = 1;
 
-       ret = calculate_dentry_full_path(dentry);
-       if (ret)
-               return ret;
+       ni = ntfs_inode_open(ctx->vol, inode->i_mft_no);
+       if (unlikely(!ni)) {
+               ERROR_WITH_ERRNO("Can't open \"%s\" in NTFS volume",
+                                dentry_full_path(
+                                       inode_first_extraction_dentry(inode)));
+               return NULL;
+       }
+
+       ctx->open_inodes[ctx->num_open_inodes++] = ni;
+       return ni;
+}
+
+static int
+ntfs_3g_begin_extract_blob(struct blob_descriptor *blob, void *_ctx)
+{
+       struct ntfs_3g_apply_ctx *ctx = _ctx;
+       const struct blob_extraction_target *targets = blob_extraction_targets(blob);
+       int ret;
+       ntfs_inode *ni;
+
+       for (u32 i = 0; i < blob->out_refcnt; i++) {
+               ret = WIMLIB_ERR_NTFS_3G;
+               ni = ntfs_3g_open_inode(targets[i].inode, ctx);
+               if (!ni)
+                       goto out_cleanup;
+
+               ret = ntfs_3g_begin_extract_blob_instance(blob, ni,
+                                                         targets[i].inode,
+                                                         targets[i].stream, ctx);
+               if (ret)
+                       goto out_cleanup;
+       }
+       ret = 0;
+       goto out;
+
+out_cleanup:
+       ntfs_3g_cleanup_blob_extract(ctx);
+out:
+       return ret;
+}
+
+/*
+ * Note: prior to NTFS-3G version 2016.2.22, ntfs_attr_pwrite() could return a
+ * short count in non-error cases, contrary to its documentation.  Specifically,
+ * a short count could be returned when writing to a compressed attribute and
+ * the requested count exceeded the size of an NTFS "compression block".
+ * Therefore, we must continue calling ntfs_attr_pwrite() until all bytes have
+ * been written or a real error has occurred.
+ */
+static bool
+ntfs_3g_full_pwrite(ntfs_attr *na, u64 offset, size_t size, const u8 *data)
+{
+       while (size) {
+               s64 res = ntfs_attr_pwrite(na, offset, size, data);
+               if (unlikely(res <= 0))
+                       return false;
+               wimlib_assert(res <= size);
+               offset += res;
+               size -= res;
+               data += res;
+       }
+       return true;
+}
 
-       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;
+static int
+ntfs_3g_extract_chunk(const struct blob_descriptor *blob, u64 offset,
+                     const void *chunk, size_t size, void *_ctx)
+{
+       struct ntfs_3g_apply_ctx *ctx = _ctx;
+
+       for (unsigned i = 0; i < ctx->num_open_attrs; i++) {
+               if (!ntfs_3g_full_pwrite(ctx->open_attrs[i], offset,
+                                        size, chunk))
+               {
+                       ERROR_WITH_ERRNO("Error writing data to NTFS volume");
+                       return WIMLIB_ERR_NTFS_3G;
                }
-       } else {
+       }
+       if (ctx->reparse_ptr)
+               ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size);
+       return 0;
+}
+
+static int
+ntfs_3g_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
+{
+       struct ntfs_3g_apply_ctx *ctx = _ctx;
+       int ret;
+
+       if (status) {
+               ret = status;
+               goto out;
+       }
+
+       for (u32 i = 0; i < ctx->num_reparse_inodes; i++) {
+               ret = ntfs_3g_restore_reparse_point(ctx->ntfs_reparse_inodes[i],
+                                                   ctx->wim_reparse_inodes[i],
+                                                   blob->size, ctx);
+               if (ret)
+                       goto out;
+       }
+       ret = 0;
+out:
+       if (ntfs_3g_cleanup_blob_extract(ctx) && !ret) {
+               ERROR_WITH_ERRNO("Error writing data to NTFS volume");
                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 u64
+ntfs_3g_count_dentries(const struct list_head *dentry_list)
 {
-       struct apply_args *args = arg;
-       ntfs_volume *vol = args->vol;
-       u64 ntfs_timestamps[3];
-       ntfs_inode *ni;
+       const struct wim_dentry *dentry;
+       u64 count = 0;
+
+       list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
+               count++;
+               if ((dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) &&
+                   dentry_has_short_name(dentry))
+               {
+                       count++;
+               }
+       }
+
+       return count;
+}
+
+static int
+ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
+{
+       struct ntfs_3g_apply_ctx *ctx = (struct ntfs_3g_apply_ctx *)_ctx;
+       ntfs_volume *vol;
+       struct wim_dentry *root;
        int ret;
 
-       DEBUG("Setting timestamps on `%s'", dentry->_full_path);
+       /* For NTFS-3G extraction mode we require that the dentries to extract
+        * form a single tree.  */
+       root = list_first_entry(dentry_list, struct wim_dentry,
+                               d_extraction_list_node);
 
-       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);
+       /* Mount the NTFS volume.  */
+       vol = ntfs_mount(ctx->common.target, 0);
+       if (!vol) {
+               ERROR_WITH_ERRNO("Failed to mount \"%s\" with NTFS-3G",
+                                ctx->common.target);
                return WIMLIB_ERR_NTFS_3G;
        }
-
-       /* 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);
+       ctx->vol = vol;
+
+       /* Opening $Secure is required to set security descriptors in NTFS v3.0
+        * format, where security descriptors are stored in a per-volume index
+        * rather than being fully specified for each file.  */
+       if (ntfs_open_secure(vol) && vol->major_ver >= 3) {
+               ERROR_WITH_ERRNO("Unable to open security descriptor index of "
+                                "NTFS volume \"%s\"", ctx->common.target);
                ret = WIMLIB_ERR_NTFS_3G;
+               goto out_unmount;
        }
 
-       if (ntfs_inode_close(ni) != 0) {
-               if (ret == 0)
+       /* Create all inodes and aliases, including short names, and set
+        * metadata (attributes, security descriptors, and timestamps).  */
+
+       ret = start_file_structure_phase(&ctx->common,
+                                        ntfs_3g_count_dentries(dentry_list));
+       if (ret)
+               goto out_unmount;
+
+       ret = ntfs_3g_create_directories(root, dentry_list, ctx);
+       if (ret)
+               goto out_unmount;
+
+       ret = ntfs_3g_create_nondirectories(dentry_list, ctx);
+       if (ret)
+               goto out_unmount;
+
+       ret = end_file_structure_phase(&ctx->common);
+       if (ret)
+               goto out_unmount;
+
+       /* Extract blobs.  */
+       struct read_blob_callbacks cbs = {
+               .begin_blob     = ntfs_3g_begin_extract_blob,
+               .continue_blob  = ntfs_3g_extract_chunk,
+               .end_blob       = ntfs_3g_end_extract_blob,
+               .ctx            = ctx,
+       };
+       ret = extract_blob_list(&ctx->common, &cbs);
+
+       /* We do not need a final pass to set timestamps because libntfs-3g does
+        * not update timestamps automatically (exception:
+        * ntfs_set_ntfs_dos_name() does, but we handle this elsewhere).  */
+
+out_unmount:
+       if (vol->secure_ni) {
+               ntfs_index_ctx_put(vol->secure_xsii);
+               ntfs_index_ctx_put(vol->secure_xsdh);
+               if (ntfs_inode_close(vol->secure_ni) && !ret) {
+                       ERROR_WITH_ERRNO("Failed to close security descriptor "
+                                        "index of NTFS volume \"%s\"",
+                                        ctx->common.target);
                        ret = WIMLIB_ERR_NTFS_3G;
-               ERROR_WITH_ERRNO("Failed to close NTFS inode for `%s'",
-                                dentry->_full_path);
+               }
+               vol->secure_ni = NULL;
+       }
+       if (ntfs_umount(ctx->vol, FALSE) && !ret) {
+               ERROR_WITH_ERRNO("Failed to unmount \"%s\" with NTFS-3G",
+                                ctx->common.target);
+               ret = WIMLIB_ERR_NTFS_3G;
        }
        return ret;
 }
 
-void
-libntfs3g_global_init(void)
-{
-       ntfs_set_char_encoding(setlocale(LC_ALL, ""));
-}
-
-#endif /* WITH_NTFS_3G */
+const struct apply_operations ntfs_3g_apply_ops = {
+       .name                   = "NTFS-3G",
+       .get_supported_features = ntfs_3g_get_supported_features,
+       .extract                = ntfs_3g_extract,
+       .context_size           = sizeof(struct ntfs_3g_apply_ctx),
+       .single_tree_only       = true,
+};