]> wimlib.net Git - wimlib/blobdiff - src/ntfs-3g_apply.c
ntfs-3g_apply.c: note bugs fixed in NTFS-3G version 2017.3.23
[wimlib] / src / ntfs-3g_apply.c
index acf1b95485ac61d2132897bd6eb6c7434aefb085..c012c8e5b600188d76115949098763eb5b22d6b0 100644 (file)
@@ -10,7 +10,7 @@
  */
 
 /*
- * Copyright (C) 2012-2016 Eric Biggers
+ * Copyright (C) 2012-2017 Eric Biggers
  *
  * This file is free software; you can redistribute it and/or modify it under
  * the terms of the GNU Lesser General Public License as published by the Free
@@ -61,6 +61,7 @@ ntfs_3g_get_supported_features(const char *target,
        supported_features->archive_files             = 1;
        supported_features->compressed_files          = 1;
        supported_features->not_context_indexed_files = 1;
+       supported_features->sparse_files              = 1;
        supported_features->named_data_streams        = 1;
        supported_features->hard_links                = 1;
        supported_features->reparse_points            = 1;
@@ -84,12 +85,16 @@ struct ntfs_3g_apply_ctx {
        ntfs_inode *open_inodes[MAX_OPEN_FILES];
        unsigned num_open_inodes;
 
+       /* For each currently open attribute, whether we're writing to it in
+        * "sparse" mode or not.  */
+       bool is_sparse_attr[MAX_OPEN_FILES];
+
+       /* Whether is_sparse_attr[] is true for any currently open attribute  */
+       bool any_sparse_attrs;
+
        struct reparse_buffer_disk rpbuf;
        u8 *reparse_ptr;
 
-       /* Offset in the blob currently being read  */
-       u64 offset;
-
        unsigned num_reparse_inodes;
        ntfs_inode *ntfs_reparse_inodes[MAX_OPEN_FILES];
        struct wim_inode *wim_reparse_inodes[MAX_OPEN_FILES];
@@ -167,7 +172,7 @@ 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));
@@ -176,8 +181,19 @@ ntfs_3g_restore_dos_name(ntfs_inode *ni, ntfs_inode *dir_ni,
                              "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.");
+                             "was fixed in NTFS-3G version 2017.3.23.");
+               }
+               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.  This bug was fixed in "
+                                     "NTFS-3G version 2017.3.23.");
+                       }
                }
                ret = WIMLIB_ERR_SET_SHORT_NAME;
                goto out_close;
@@ -319,9 +335,6 @@ ntfs_3g_set_metadata(ntfs_inode *ni, const struct wim_inode *inode,
        if (!(extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) {
                u32 attrib = inode->i_attributes;
 
-               attrib &= ~(FILE_ATTRIBUTE_SPARSE_FILE |
-                           FILE_ATTRIBUTE_ENCRYPTED);
-
                if (ntfs_set_ntfs_attrib(ni, (const char *)&attrib,
                                         sizeof(attrib), 0))
                {
@@ -670,7 +683,7 @@ ntfs_3g_begin_extract_blob_instance(struct blob_descriptor *blob,
        struct wim_dentry *one_dentry = inode_first_extraction_dentry(inode);
        ntfschar *stream_name;
        size_t stream_name_nchars;
-       ntfs_attr *attr;
+       ntfs_attr *na;
 
        if (unlikely(strm->stream_type == STREAM_TYPE_REPARSE_POINT)) {
 
@@ -713,14 +726,33 @@ ntfs_3g_begin_extract_blob_instance(struct blob_descriptor *blob,
        /* 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) {
+       na = ntfs_attr_open(ni, AT_DATA, stream_name, stream_name_nchars);
+       if (!na) {
                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);
+
+       /*
+        * Note: there are problems with trying to combine compression with
+        * sparseness when extracting.  For example, doing ntfs_attr_truncate()
+        * at the end to extend the attribute to its final size actually extends
+        * to a compression block size boundary rather than to the requested
+        * size.  Until these problems are solved, we always write the full data
+        * to compressed attributes.  We also don't attempt to preallocate space
+        * for compressed attributes, since we don't know how much space they
+        * are going to actually need.
+        */
+       ctx->is_sparse_attr[ctx->num_open_attrs] = false;
+       if (!(na->data_flags & ATTR_COMPRESSION_MASK)) {
+               if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) {
+                       ctx->is_sparse_attr[ctx->num_open_attrs] = true;
+                       ctx->any_sparse_attrs = true;
+               } else {
+                       ntfs_attr_truncate_solid(na, blob->size);
+               }
+       }
+       ctx->open_attrs[ctx->num_open_attrs++] = na;
        return 0;
 }
 
@@ -743,7 +775,7 @@ ntfs_3g_cleanup_blob_extract(struct ntfs_3g_apply_ctx *ctx)
        }
        ctx->num_open_inodes = 0;
 
-       ctx->offset = 0;
+       ctx->any_sparse_attrs = false;
        ctx->reparse_ptr = NULL;
        ctx->num_reparse_inodes = 0;
        return ret;
@@ -805,11 +837,14 @@ out:
        return ret;
 }
 
-/* Note: contrary to its documentation, ntfs_attr_pwrite() can return a short
- * count in non-error cases --- specifically, when writing to a compressed
- * attribute and the requested count exceeds 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.  */
+/*
+ * 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)
 {
@@ -826,22 +861,39 @@ ntfs_3g_full_pwrite(ntfs_attr *na, u64 offset, size_t size, const u8 *data)
 }
 
 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;
-
-       for (unsigned i = 0; i < ctx->num_open_attrs; i++) {
-               if (!ntfs_3g_full_pwrite(ctx->open_attrs[i],
-                                        ctx->offset, size, chunk))
-               {
-                       ERROR_WITH_ERRNO("Error writing data to NTFS volume");
-                       return WIMLIB_ERR_NTFS_3G;
+       const void * const end = chunk + size;
+       const void *p;
+       bool zeroes;
+       size_t len;
+       unsigned i;
+
+       /*
+        * For sparse attributes, only write nonzero regions.  This lets the
+        * filesystem use holes to represent zero regions.
+        */
+       for (p = chunk; p != end; p += len, offset += len) {
+               zeroes = maybe_detect_sparse_region(p, end - p, &len,
+                                                   ctx->any_sparse_attrs);
+               for (i = 0; i < ctx->num_open_attrs; i++) {
+                       if (!zeroes || !ctx->is_sparse_attr[i]) {
+                               if (!ntfs_3g_full_pwrite(ctx->open_attrs[i],
+                                                        offset, len, p))
+                                       goto err;
+                       }
                }
        }
+
        if (ctx->reparse_ptr)
                ctx->reparse_ptr = mempcpy(ctx->reparse_ptr, chunk, size);
-       ctx->offset += size;
        return 0;
+
+err:
+       ERROR_WITH_ERRNO("Error writing data to NTFS volume");
+       return WIMLIB_ERR_NTFS_3G;
 }
 
 static int
@@ -855,6 +907,21 @@ ntfs_3g_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
                goto out;
        }
 
+       /* Extend sparse attributes to their final size. */
+       if (ctx->any_sparse_attrs) {
+               for (unsigned i = 0; i < ctx->num_open_attrs; i++) {
+                       if (!ctx->is_sparse_attr[i])
+                               continue;
+                       if (ntfs_attr_truncate(ctx->open_attrs[i], blob->size))
+                       {
+                               ERROR_WITH_ERRNO("Error extending attribute to "
+                                                "final size");
+                               ret = WIMLIB_ERR_WRITE;
+                               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],
@@ -911,6 +978,16 @@ ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
        }
        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).  */
 
@@ -934,7 +1011,7 @@ ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
        /* Extract blobs.  */
        struct read_blob_callbacks cbs = {
                .begin_blob     = ntfs_3g_begin_extract_blob,
-               .consume_chunk  = ntfs_3g_extract_chunk,
+               .continue_blob  = ntfs_3g_extract_chunk,
                .end_blob       = ntfs_3g_end_extract_blob,
                .ctx            = ctx,
        };
@@ -945,6 +1022,17 @@ ntfs_3g_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
         * 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",
                                 ctx->common.target);