]> 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 db289684589ef4b78f1b05107dc53a0ae75c9237..27b0ab90a260969cd351db0b352f6a7a112ba765 100644 (file)
@@ -3,68 +3,70 @@
  *
  * 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
+ * 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, 2014 Eric Biggers
+ * Copyright (C) 2012-2016 Eric Biggers
  *
- * This file is part of wimlib, a library for working with WIM files.
+ * 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 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.
- *
- * 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
 
+#include <errno.h>
 #include <locale.h>
 #include <string.h>
 
 #include <ntfs-3g/attrib.h>
+#include <ntfs-3g/object_id.h>
 #include <ntfs-3g/reparse.h>
 #include <ntfs-3g/security.h>
 
 #include "wimlib/assert.h"
 #include "wimlib/apply.h"
+#include "wimlib/blob_table.h"
 #include "wimlib/dentry.h"
 #include "wimlib/encoding.h"
 #include "wimlib/error.h"
 #include "wimlib/metadata.h"
 #include "wimlib/ntfs_3g.h"
+#include "wimlib/object_id.h"
 #include "wimlib/reparse.h"
 #include "wimlib/security.h"
-#include "wimlib/security_descriptor.h"
 
 static int
 ntfs_3g_get_supported_features(const char *target,
                               struct wim_features *supported_features)
 {
-       supported_features->archive_files             = 1;
+       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->encrypted_directories     = 1;
        supported_features->not_context_indexed_files = 1;
        supported_features->named_data_streams        = 1;
        supported_features->hard_links                = 1;
        supported_features->reparse_points            = 1;
        supported_features->security_descriptors      = 1;
        supported_features->short_names               = 1;
+       supported_features->object_ids                = 1;
        supported_features->timestamps                = 1;
        supported_features->case_sensitive_filenames  = 1;
        return 0;
@@ -77,152 +79,19 @@ struct ntfs_3g_apply_ctx {
        /* Pointer to the open NTFS volume  */
        ntfs_volume *vol;
 
-       ntfs_attr *open_attrs[MAX_OPEN_STREAMS];
+       ntfs_attr *open_attrs[MAX_OPEN_FILES];
        unsigned num_open_attrs;
-       ntfs_inode *open_inodes[MAX_OPEN_STREAMS];
+       ntfs_inode *open_inodes[MAX_OPEN_FILES];
        unsigned num_open_inodes;
 
        struct reparse_buffer_disk rpbuf;
        u8 *reparse_ptr;
 
-       /* Offset in the stream currently being read  */
-       u64 offset;
-
        unsigned num_reparse_inodes;
-       ntfs_inode *ntfs_reparse_inodes[MAX_OPEN_STREAMS];
-       struct wim_inode *wim_reparse_inodes[MAX_OPEN_STREAMS];
+       ntfs_inode *ntfs_reparse_inodes[MAX_OPEN_FILES];
+       struct wim_inode *wim_reparse_inodes[MAX_OPEN_FILES];
 };
 
-static size_t
-sid_size(const wimlib_SID *sid)
-{
-       return offsetof(wimlib_SID, sub_authority) +
-               sizeof(le32) * sid->sub_authority_count;
-}
-
-/*
- * sd_fixup - Fix up a Windows NT security descriptor for libntfs-3g.
- *
- * libntfs-3g validates security descriptors before setting them, but old
- * versions contain bugs causing it to reject unusual but valid security
- * descriptors:
- *
- * - Versions before 2013.1.13 reject security descriptors ending with an empty
- *   SACL (System Access Control List).  This bug can be worked around either by
- *   moving the empty SACL earlier in the security descriptor or by removing the
- *   SACL entirely.  The latter work-around is valid because an empty SACL is
- *   equivalent to a "null", or non-existent, SACL.
- * - Versions up to and including 2013.1.13 reject security descriptors ending
- *   with an empty DACL (Discretionary Access Control List).  This is very
- *   similar to the SACL bug and should be fixed in the next release after
- *   2013.1.13.  However, removing the DACL is not a valid workaround because
- *   this changes the meaning of the security descriptor--- an empty DACL allows
- *   no access, whereas a "null" DACL allows all access.
- *
- * If the security descriptor was fixed, this function returns an allocated
- * buffer containing the fixed security descriptor, and its size is updated.
- * Otherwise (or if no memory is available) the original descriptor is returned.
- */
-static u8 *
-sd_fixup(const u8 *_desc, size_t *size_p)
-{
-       u32 owner_offset, group_offset, dacl_offset, sacl_offset;
-       bool owner_valid, group_valid;
-       size_t size = *size_p;
-       const wimlib_SECURITY_DESCRIPTOR_RELATIVE *desc =
-                       (const wimlib_SECURITY_DESCRIPTOR_RELATIVE*)_desc;
-       wimlib_SECURITY_DESCRIPTOR_RELATIVE *desc_new;
-       const wimlib_SID *owner, *group, *sid;
-
-       /* Don't attempt to fix clearly invalid security descriptors.  */
-       if (size < sizeof(wimlib_SECURITY_DESCRIPTOR_RELATIVE))
-               return (u8*)_desc;
-
-       if (le16_to_cpu(desc->control) & wimlib_SE_DACL_PRESENT)
-               dacl_offset = le32_to_cpu(desc->dacl_offset);
-       else
-               dacl_offset = 0;
-
-       if (le16_to_cpu(desc->control) & wimlib_SE_SACL_PRESENT)
-               sacl_offset = le32_to_cpu(desc->sacl_offset);
-       else
-               sacl_offset = 0;
-
-       /* Check if the security descriptor will be affected by one of the bugs.
-        * If not, do nothing and return.
-        *
-        * Note: HAVE_NTFS_MNT_RDONLY is defined if libntfs-3g is
-        * version 2013.1.13 or later.  */
-       if (!(
-       #if !defined(HAVE_NTFS_MNT_RDONLY)
-           (sacl_offset != 0 && sacl_offset == size - sizeof(wimlib_ACL)) ||
-       #endif
-           (dacl_offset != 0 && dacl_offset == size - sizeof(wimlib_ACL))))
-               return (u8*)_desc;
-
-       owner_offset = le32_to_cpu(desc->owner_offset);
-       group_offset = le32_to_cpu(desc->group_offset);
-       owner = (const wimlib_SID*)((const u8*)desc + owner_offset);
-       group = (const wimlib_SID*)((const u8*)desc + group_offset);
-
-       /* We'll try to move the owner or group SID to the end of the security
-        * descriptor to avoid the bug.  This is only possible if at least one
-        * is valid.  */
-       owner_valid = (owner_offset != 0) &&
-                       (owner_offset % 4 == 0) &&
-                       (owner_offset <= size - sizeof(SID)) &&
-                       (owner_offset + sid_size(owner) <= size) &&
-                       (owner_offset >= sizeof(wimlib_SECURITY_DESCRIPTOR_RELATIVE));
-       group_valid = (group_offset != 0) &&
-                       (group_offset % 4 == 0) &&
-                       (group_offset <= size - sizeof(SID)) &&
-                       (group_offset + sid_size(group) <= size) &&
-                       (group_offset >= sizeof(wimlib_SECURITY_DESCRIPTOR_RELATIVE));
-       if (owner_valid) {
-               sid = owner;
-       } else if (group_valid) {
-               sid = group;
-       } else {
-               return (u8*)_desc;
-       }
-
-       desc_new = MALLOC(size + sid_size(sid));
-       if (!desc_new)
-               return (u8*)_desc;
-
-       memcpy(desc_new, desc, size);
-       if (owner_valid)
-               desc_new->owner_offset = cpu_to_le32(size);
-       else if (group_valid)
-               desc_new->group_offset = cpu_to_le32(size);
-       memcpy((u8*)desc_new + size, sid, sid_size(sid));
-       *size_p = size + sid_size(sid);
-       return (u8*)desc_new;
-}
-
-/* Set the security descriptor @desc of size @desc_size on the NTFS inode @ni.
-  */
-static int
-ntfs_3g_set_security_descriptor(ntfs_inode *ni, const void *desc, size_t desc_size)
-{
-       struct SECURITY_CONTEXT sec_ctx;
-       u8 *desc_fixed;
-       int ret = 0;
-
-       memset(&sec_ctx, 0, sizeof(sec_ctx));
-       sec_ctx.vol = ni->vol;
-
-       desc_fixed = sd_fixup(desc, &desc_size);
-
-       if (ntfs_set_ntfs_acl(&sec_ctx, ni, desc_fixed, desc_size, 0))
-               ret = WIMLIB_ERR_SET_SECURITY;
-
-       if (desc_fixed != desc)
-               FREE(desc_fixed);
-
-       return ret;
-}
-
 static int
 ntfs_3g_set_timestamps(ntfs_inode *ni, const struct wim_inode *inode)
 {
@@ -277,7 +146,7 @@ ntfs_3g_restore_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
         * UTF-16LE internally... which is annoying because we currently have
         * the UTF-16LE string but not the multibyte string.  */
 
-       ret = utf16le_get_tstr(dentry->short_name, dentry->short_name_nbytes,
+       ret = utf16le_get_tstr(dentry->d_short_name, dentry->d_short_name_nbytes,
                               &dos_name, &dos_name_nbytes);
        if (ret)
                goto out_close;
@@ -295,14 +164,36 @@ ntfs_3g_restore_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
                ret = -1;
        }
        utf16le_put_tstr(dos_name);
-       if (ret) {
+       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()
+       /* 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);
@@ -317,37 +208,84 @@ out_close:
        return ret;
 }
 
-/* Create empty named data streams.
+static int
+ntfs_3g_restore_reparse_point(ntfs_inode *ni, const struct wim_inode *inode,
+                             unsigned blob_size, struct ntfs_3g_apply_ctx *ctx)
+{
+       complete_reparse_point(&ctx->rpbuf, inode, blob_size);
+
+       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;
+       }
+
+       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;
+       }
+       return false;
+}
+
+/*
+ * Create empty attributes (named data streams and potentially a reparse point)
+ * for the specified file, if there are any.
+ *
+ * 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.
  *
- * Since these won't have 'struct wim_lookup_table_entry's, they won't show up
- * in the call to extract_stream_list().  Hence the need for the special case.
+ * Keep this in sync with ntfs_3g_has_empty_attributes()!
  */
 static int
-ntfs_3g_create_any_empty_ads(ntfs_inode *ni, const struct wim_inode *inode,
-                            const struct ntfs_3g_apply_ctx *ctx)
+ntfs_3g_create_empty_attributes(ntfs_inode *ni,
+                               const struct wim_inode *inode,
+                               struct ntfs_3g_apply_ctx *ctx)
 {
-       for (u16 i = 0; i < inode->i_num_ads; i++) {
-               const struct wim_ads_entry *entry;
+       for (unsigned i = 0; i < inode->i_num_streams; i++) {
 
-               entry = &inode->i_ads_entries[i];
-
-               /* Not named?  */
-               if (!entry->stream_name_nbytes)
-                       continue;
+               const struct wim_inode_stream *strm = &inode->i_streams[i];
+               int ret;
 
-               /* Not empty?  */
-               if (entry->lte)
+               if (stream_blob_resolved(strm) != NULL)
                        continue;
 
-               if (ntfs_attr_add(ni, AT_DATA, entry->stream_name,
-                                 entry->stream_name_nbytes /
-                                       sizeof(utf16lechar),
-                                 NULL, 0))
-               {
-                       ERROR_WITH_ERRNO("Failed to create named data stream "
-                                        "of \"%s\"", dentry_full_path(
-                                               inode_first_extraction_dentry(inode)));
-                       return WIMLIB_ERR_NTFS_3G;
+               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;
@@ -368,6 +306,25 @@ ntfs_3g_set_metadata(ntfs_inode *ni, const struct wim_inode *inode,
        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;
+                       }
+               }
+       }
+
        /* Attributes  */
        if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) {
                u32 attrib = inode->i_attributes;
@@ -386,27 +343,36 @@ ntfs_3g_set_metadata(ntfs_inode *ni, const struct wim_inode *inode,
        }
 
        /* Security descriptor  */
-       if ((inode->i_security_id >= 0)
+       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_3g_set_security_descriptor(ni, desc, desc_size);
-               if (ret) {
-                       if (wimlib_print_errors) {
-                               ERROR_WITH_ERRNO("Failed to set security descriptor "
-                                                "on \"%s\" in NTFS volume",
-                                                dentry_full_path(one_dentry));
+               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");
+                               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 ret;
+                       return WIMLIB_ERR_SET_SECURITY;
                }
        }
 
@@ -451,8 +417,6 @@ ntfs_3g_create_dirs_recursive(ntfs_inode *dir_ni, struct wim_dentry *dir,
                ret = report_file_created(&ctx->common);
                if (!ret)
                        ret = ntfs_3g_set_metadata(ni, child->d_inode, ctx);
-               if (!ret)
-                       ret = ntfs_3g_create_any_empty_ads(ni, child->d_inode, ctx);
                if (!ret)
                        ret = ntfs_3g_create_dirs_recursive(ni, child, ctx);
 
@@ -488,7 +452,9 @@ ntfs_3g_create_directories(struct wim_dentry *root,
 
        root->d_inode->i_mft_no = FILE_root;
 
-       ret = ntfs_3g_create_dirs_recursive(root_ni, root, ctx);
+       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");
@@ -497,18 +463,45 @@ ntfs_3g_create_directories(struct wim_dentry *root,
        if (ret)
                return ret;
 
-       /* Set the DOS name of any directory that has one.  */
+       /* 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) {
-               if (!(dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))
-                       continue;
-               if (!dentry_has_short_name(dentry))
+               const struct wim_inode *inode = dentry->d_inode;
+
+               if (!(inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY))
                        continue;
-               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 (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;
+
+                       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;
 }
@@ -519,17 +512,12 @@ ntfs_3g_create_directories(struct wim_dentry *root,
 static struct wim_dentry *
 ntfs_3g_first_extraction_alias(struct wim_inode *inode)
 {
-       struct list_head *next = inode->i_extraction_aliases.next;
        struct wim_dentry *dentry;
 
-       do {
-               dentry = list_entry(next, struct wim_dentry,
-                                   d_extraction_alias_node);
+       inode_for_each_extraction_alias(dentry, inode)
                if (dentry_has_short_name(dentry))
-                       break;
-               next = next->next;
-       } while (next != &inode->i_extraction_aliases);
-       return dentry;
+                       return dentry;
+       return inode_first_extraction_dentry(inode);
 }
 
 /*
@@ -569,12 +557,11 @@ fail:
 
 static int
 ntfs_3g_create_nondirectory(struct wim_inode *inode,
-                           const struct ntfs_3g_apply_ctx *ctx)
+                           struct ntfs_3g_apply_ctx *ctx)
 {
        struct wim_dentry *first_dentry;
        ntfs_inode *dir_ni;
        ntfs_inode *ni;
-       struct list_head *next;
        struct wim_dentry *dentry;
        int ret;
 
@@ -630,24 +617,20 @@ ntfs_3g_create_nondirectory(struct wim_inode *inode,
        }
 
        /* Create additional links if present.  */
-       next = inode->i_extraction_aliases.next;
-       do {
-               dentry = list_entry(next, struct wim_dentry,
-                                   d_extraction_alias_node);
+       inode_for_each_extraction_alias(dentry, inode) {
                if (dentry != first_dentry) {
                        ret = ntfs_3g_add_link(ni, dentry);
                        if (ret)
                                goto out_close_ni;
                }
-               next = next->next;
-       } while (next != &inode->i_extraction_aliases);
+       }
 
        /* Set metadata.  */
        ret = ntfs_3g_set_metadata(ni, inode, ctx);
        if (ret)
                goto out_close_ni;
 
-       ret = ntfs_3g_create_any_empty_ads(ni, inode, ctx);
+       ret = ntfs_3g_create_empty_attributes(ni, inode, ctx);
 
 out_close_ni:
        /* Close the inode.  */
@@ -688,52 +671,57 @@ ntfs_3g_create_nondirectories(struct list_head *dentry_list,
 }
 
 static int
-ntfs_3g_begin_extract_stream_to_attr(struct wim_lookup_table_entry *stream,
-                                    ntfs_inode *ni,
-                                    struct wim_inode *inode,
-                                    ntfschar *stream_name,
-                                    struct ntfs_3g_apply_ctx *ctx)
+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);
-       size_t stream_name_nchars = 0;
+       ntfschar *stream_name;
+       size_t stream_name_nchars;
        ntfs_attr *attr;
 
-       if (stream_name)
-               for (const ntfschar *p = stream_name; *p; p++)
-                       stream_name_nchars++;
+       if (unlikely(strm->stream_type == STREAM_TYPE_REPARSE_POINT)) {
 
-       if (stream_name_nchars == 0)
-               stream_name = AT_UNNAMED;
-       if ((inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
-           && (stream_name_nchars == 0))
-       {
-               if (stream->size > REPARSE_DATA_MAX_SIZE) {
+               if (blob->size > REPARSE_DATA_MAX_SIZE) {
                        ERROR("Reparse data of \"%s\" has size "
                              "%"PRIu64" bytes (exceeds %u bytes)",
                              dentry_full_path(one_dentry),
-                             stream->size, REPARSE_DATA_MAX_SIZE);
+                             blob->size, REPARSE_DATA_MAX_SIZE);
                        return WIMLIB_ERR_INVALID_REPARSE_DATA;
                }
                ctx->reparse_ptr = ctx->rpbuf.rpdata;
-               ctx->rpbuf.rpdatalen = cpu_to_le16(stream->size);
-               ctx->rpbuf.rpreserved = cpu_to_le16(0);
                ctx->ntfs_reparse_inodes[ctx->num_reparse_inodes] = ni;
                ctx->wim_reparse_inodes[ctx->num_reparse_inodes] = inode;
                ctx->num_reparse_inodes++;
                return 0;
        }
 
-       if (stream_name_nchars &&
-           (ntfs_attr_add(ni, AT_DATA, stream_name,
-                          stream_name_nchars, NULL, 0)))
-       {
-               ERROR_WITH_ERRNO("Failed to create named data stream of \"%s\"",
-                                dentry_full_path(one_dentry));
-               return WIMLIB_ERR_NTFS_3G;
+       /* 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;
        }
 
-       /* This should be ensured by extract_stream_list()  */
-       wimlib_assert(ctx->num_open_attrs < MAX_OPEN_STREAMS);
+       /* 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) {
@@ -742,12 +730,12 @@ ntfs_3g_begin_extract_stream_to_attr(struct wim_lookup_table_entry *stream,
                return WIMLIB_ERR_NTFS_3G;
        }
        ctx->open_attrs[ctx->num_open_attrs++] = attr;
-       ntfs_attr_truncate_solid(attr, stream->size);
+       ntfs_attr_truncate_solid(attr, blob->size);
        return 0;
 }
 
 static int
-ntfs_3g_cleanup_stream_extract(struct ntfs_3g_apply_ctx *ctx)
+ntfs_3g_cleanup_blob_extract(struct ntfs_3g_apply_ctx *ctx)
 {
        int ret = 0;
 
@@ -765,7 +753,6 @@ ntfs_3g_cleanup_stream_extract(struct ntfs_3g_apply_ctx *ctx)
        }
        ctx->num_open_inodes = 0;
 
-       ctx->offset = 0;
        ctx->reparse_ptr = NULL;
        ctx->num_reparse_inodes = 0;
        return ret;
@@ -774,50 +761,47 @@ ntfs_3g_cleanup_stream_extract(struct ntfs_3g_apply_ctx *ctx)
 static ntfs_inode *
 ntfs_3g_open_inode(struct wim_inode *inode, struct ntfs_3g_apply_ctx *ctx)
 {
-       ntfs_inode *ni = NULL;
+       ntfs_inode *ni;
 
-       if (inode->i_visited) {
-               for (u32 i = 0; i < ctx->num_open_inodes; i++) {
+       /* 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) {
-                               ni = ctx->open_inodes[i];
-                               break;
+                               return ctx->open_inodes[i];
                        }
                }
        }
-       if (!ni) {
-               ni = ntfs_inode_open(ctx->vol, inode->i_mft_no);
-               ctx->open_inodes[ctx->num_open_inodes++] = ni;
-               inode->i_visited = 1;
-       }
 
-       if (!ni) {
+       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_stream(struct wim_lookup_table_entry *stream, void *_ctx)
+ntfs_3g_begin_extract_blob(struct blob_descriptor *blob, void *_ctx)
 {
        struct ntfs_3g_apply_ctx *ctx = _ctx;
-       const struct stream_owner *owners = stream_owners(stream);
+       const struct blob_extraction_target *targets = blob_extraction_targets(blob);
        int ret;
+       ntfs_inode *ni;
 
-       for (u32 i = 0; i < stream->out_refcnt; i++) {
-               struct wim_inode *inode = owners[i].inode;
-               ntfschar *stream_name = (ntfschar *)owners[i].stream_name;
-               ntfs_inode *ni;
-
+       for (u32 i = 0; i < blob->out_refcnt; i++) {
                ret = WIMLIB_ERR_NTFS_3G;
-               ni = ntfs_3g_open_inode(inode, ctx);
+               ni = ntfs_3g_open_inode(targets[i].inode, ctx);
                if (!ni)
                        goto out_cleanup;
 
-               ret = ntfs_3g_begin_extract_stream_to_attr(stream, ni, inode,
-                                                          stream_name, ctx);
+               ret = ntfs_3g_begin_extract_blob_instance(blob, ni,
+                                                         targets[i].inode,
+                                                         targets[i].stream, ctx);
                if (ret)
                        goto out_cleanup;
        }
@@ -825,36 +809,55 @@ ntfs_3g_begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx)
        goto out;
 
 out_cleanup:
-       ntfs_3g_cleanup_stream_extract(ctx);
+       ntfs_3g_cleanup_blob_extract(ctx);
 out:
-       for (u32 i = 0; i < stream->out_refcnt; i++)
-               owners[i].inode->i_visited = 0;
        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;
+}
+
 static int
-ntfs_3g_extract_chunk(const void *chunk, size_t size, void *_ctx)
+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;
-       s64 res;
 
        for (unsigned i = 0; i < ctx->num_open_attrs; i++) {
-               res = ntfs_attr_pwrite(ctx->open_attrs[i],
-                                      ctx->offset, size, chunk);
-               if (res != size) {
+               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;
                }
        }
        if (ctx->reparse_ptr)
                ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size);
-       ctx->offset += size;
        return 0;
 }
 
 static int
-ntfs_3g_end_extract_stream(struct wim_lookup_table_entry *stream,
-                          int status, void *_ctx)
+ntfs_3g_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
 {
        struct ntfs_3g_apply_ctx *ctx = _ctx;
        int ret;
@@ -865,37 +868,26 @@ ntfs_3g_end_extract_stream(struct wim_lookup_table_entry *stream,
        }
 
        for (u32 i = 0; i < ctx->num_reparse_inodes; i++) {
-               struct wim_inode *inode = ctx->wim_reparse_inodes[i];
-
-               ctx->rpbuf.rptag = cpu_to_le32(inode->i_reparse_tag);
-
-               if (ntfs_set_ntfs_reparse_data(ctx->ntfs_reparse_inodes[i],
-                                              (const char *)&ctx->rpbuf,
-                                              stream->size + REPARSE_DATA_OFFSET,
-                                              0))
-               {
-                       ERROR_WITH_ERRNO("Failed to set reparse "
-                                        "data on \"%s\"",
-                                        dentry_full_path(
-                                               inode_first_extraction_dentry(inode)));
-                       ret = WIMLIB_ERR_NTFS_3G;
+               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_stream_extract(ctx) && !ret) {
+       if (ntfs_3g_cleanup_blob_extract(ctx) && !ret) {
                ERROR_WITH_ERRNO("Error writing data to NTFS volume");
                ret = WIMLIB_ERR_NTFS_3G;
        }
        return ret;
 }
 
-static uint64_t
+static u64
 ntfs_3g_count_dentries(const struct list_head *dentry_list)
 {
        const struct wim_dentry *dentry;
-       uint64_t count = 0;
+       u64 count = 0;
 
        list_for_each_entry(dentry, dentry_list, d_extraction_list_node) {
                count++;
@@ -904,7 +896,6 @@ ntfs_3g_count_dentries(const struct list_head *dentry_list)
                {
                        count++;
                }
-
        }
 
        return count;
@@ -918,7 +909,7 @@ ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
        struct wim_dentry *root;
        int ret;
 
-       /* For NTFS-3g extraction mode we require that the dentries to extract
+       /* 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);
@@ -926,12 +917,22 @@ ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
        /* Mount the NTFS volume.  */
        vol = ntfs_mount(ctx->common.target, 0);
        if (!vol) {
-               ERROR_WITH_ERRNO("Failed to mount \"%s\" with NTFS-3g",
+               ERROR_WITH_ERRNO("Failed to mount \"%s\" with NTFS-3G",
                                 ctx->common.target);
                return WIMLIB_ERR_NTFS_3G;
        }
        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;
+       }
+
        /* Create all inodes and aliases, including short names, and set
         * metadata (attributes, security descriptors, and timestamps).  */
 
@@ -952,24 +953,33 @@ ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
        if (ret)
                goto out_unmount;
 
-       /* Extract streams.  */
-       struct read_stream_list_callbacks cbs = {
-               .begin_stream      = ntfs_3g_begin_extract_stream,
-               .begin_stream_ctx  = ctx,
-               .consume_chunk     = ntfs_3g_extract_chunk,
-               .consume_chunk_ctx = ctx,
-               .end_stream        = ntfs_3g_end_extract_stream,
-               .end_stream_ctx    = ctx,
+       /* 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_stream_list(&ctx->common, &cbs);
+       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;
+               }
+               vol->secure_ni = NULL;
+       }
        if (ntfs_umount(ctx->vol, FALSE) && !ret) {
-               ERROR_WITH_ERRNO("Failed to unmount \"%s\" with NTFS-3g",
+               ERROR_WITH_ERRNO("Failed to unmount \"%s\" with NTFS-3G",
                                 ctx->common.target);
                ret = WIMLIB_ERR_NTFS_3G;
        }
@@ -977,15 +987,9 @@ out_unmount:
 }
 
 const struct apply_operations ntfs_3g_apply_ops = {
-       .name                   = "NTFS-3g",
+       .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,
 };
-
-void
-libntfs3g_global_init(void)
-{
-       ntfs_set_char_encoding(setlocale(LC_ALL, ""));
-}