fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / fs / ntfs / inode.c
index 1abe1b3..f8bf8da 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * inode.c - NTFS kernel inode handling. Part of the Linux-NTFS project.
  *
- * Copyright (c) 2001-2004 Anton Altaparmakov
+ * Copyright (c) 2001-2007 Anton Altaparmakov
  *
  * This program/include file is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as published
  * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-#include <linux/pagemap.h>
 #include <linux/buffer_head.h>
-#include <linux/smp_lock.h>
-#include <linux/quotaops.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
 #include <linux/mount.h>
+#include <linux/mutex.h>
+#include <linux/pagemap.h>
+#include <linux/quotaops.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
 
-#include "ntfs.h"
+#include "aops.h"
+#include "attrib.h"
+#include "bitmap.h"
 #include "dir.h"
+#include "debug.h"
 #include "inode.h"
 #include "attrib.h"
+#include "lcnalloc.h"
+#include "malloc.h"
+#include "mft.h"
 #include "time.h"
+#include "ntfs.h"
 
 /**
  * ntfs_test_inode - compare two (possibly fake) inodes for equality
@@ -84,7 +95,7 @@ int ntfs_test_inode(struct inode *vi, ntfs_attr *na)
  * If initializing the normal file/directory inode, set @na->type to AT_UNUSED.
  * In that case, @na->name and @na->name_len should be set to NULL and 0,
  * respectively. Although that is not strictly necessary as
- * ntfs_read_inode_locked() will fill them in later.
+ * ntfs_read_locked_inode() will fill them in later.
  *
  * Return 0 on success and -errno on error.
  *
@@ -105,8 +116,11 @@ static int ntfs_init_locked_inode(struct inode *vi, ntfs_attr *na)
        ni->name_len = na->name_len;
 
        /* If initializing a normal inode, we are done. */
-       if (likely(na->type == AT_UNUSED))
+       if (likely(na->type == AT_UNUSED)) {
+               BUG_ON(na->name);
+               BUG_ON(na->name_len);
                return 0;
+       }
 
        /* It is a fake inode. */
        NInoSetAttr(ni);
@@ -118,15 +132,16 @@ static int ntfs_init_locked_inode(struct inode *vi, ntfs_attr *na)
         * thus the fraction of named attributes with name != I30 is actually
         * absolutely tiny.
         */
-       if (na->name && na->name_len && na->name != I30) {
+       if (na->name_len && na->name != I30) {
                unsigned int i;
 
+               BUG_ON(!na->name);
                i = na->name_len * sizeof(ntfschar);
-               ni->name = (ntfschar*)kmalloc(i + sizeof(ntfschar), GFP_ATOMIC);
+               ni->name = kmalloc(i + sizeof(ntfschar), GFP_ATOMIC);
                if (!ni->name)
                        return -ENOMEM;
                memcpy(ni->name, na->name, i);
-               ni->name[i] = cpu_to_le16('\0');
+               ni->name[i] = 0;
        }
        return 0;
 }
@@ -134,6 +149,8 @@ static int ntfs_init_locked_inode(struct inode *vi, ntfs_attr *na)
 typedef int (*set_t)(struct inode *, void *);
 static int ntfs_read_locked_inode(struct inode *vi);
 static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi);
+static int ntfs_read_locked_index_inode(struct inode *base_vi,
+               struct inode *vi);
 
 /**
  * ntfs_iget - obtain a struct inode corresponding to a specific normal inode
@@ -154,8 +171,8 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi);
 struct inode *ntfs_iget(struct super_block *sb, unsigned long mft_no)
 {
        struct inode *vi;
-       ntfs_attr na;
        int err;
+       ntfs_attr na;
 
        na.mft_no = mft_no;
        na.type = AT_UNUSED;
@@ -164,7 +181,7 @@ struct inode *ntfs_iget(struct super_block *sb, unsigned long mft_no)
 
        vi = iget5_locked(sb, mft_no, (test_t)ntfs_test_inode,
                        (set_t)ntfs_init_locked_inode, &na);
-       if (!vi)
+       if (unlikely(!vi))
                return ERR_PTR(-ENOMEM);
 
        err = 0;
@@ -178,7 +195,7 @@ struct inode *ntfs_iget(struct super_block *sb, unsigned long mft_no)
         * There is no point in keeping bad inodes around if the failure was
         * due to ENOMEM. We want to be able to retry again later.
         */
-       if (err == -ENOMEM) {
+       if (unlikely(err == -ENOMEM)) {
                iput(vi);
                vi = ERR_PTR(err);
        }
@@ -201,16 +218,22 @@ struct inode *ntfs_iget(struct super_block *sb, unsigned long mft_no)
  * initialized, and finally ntfs_read_locked_attr_inode() is called to read the
  * attribute and fill in the inode structure.
  *
+ * Note, for index allocation attributes, you need to use ntfs_index_iget()
+ * instead of ntfs_attr_iget() as working with indices is a lot more complex.
+ *
  * Return the struct inode of the attribute inode on success. Check the return
  * value with IS_ERR() and if true, the function failed and the error code is
  * obtained from PTR_ERR().
  */
-struct inode *ntfs_attr_iget(struct inode *base_vi, ATTR_TYPES type,
+struct inode *ntfs_attr_iget(struct inode *base_vi, ATTR_TYPE type,
                ntfschar *name, u32 name_len)
 {
        struct inode *vi;
-       ntfs_attr na;
        int err;
+       ntfs_attr na;
+
+       /* Make sure no one calls ntfs_attr_iget() for indices. */
+       BUG_ON(type == AT_INDEX_ALLOCATION);
 
        na.mft_no = base_vi->i_ino;
        na.type = type;
@@ -219,7 +242,7 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, ATTR_TYPES type,
 
        vi = iget5_locked(base_vi->i_sb, na.mft_no, (test_t)ntfs_test_inode,
                        (set_t)ntfs_init_locked_inode, &na);
-       if (!vi)
+       if (unlikely(!vi))
                return ERR_PTR(-ENOMEM);
 
        err = 0;
@@ -234,7 +257,62 @@ struct inode *ntfs_attr_iget(struct inode *base_vi, ATTR_TYPES type,
         * simplifies things in that we never need to check for bad attribute
         * inodes elsewhere.
         */
-       if (err) {
+       if (unlikely(err)) {
+               iput(vi);
+               vi = ERR_PTR(err);
+       }
+       return vi;
+}
+
+/**
+ * ntfs_index_iget - obtain a struct inode corresponding to an index
+ * @base_vi:   vfs base inode containing the index related attributes
+ * @name:      Unicode name of the index
+ * @name_len:  length of @name in Unicode characters
+ *
+ * Obtain the (fake) struct inode corresponding to the index specified by @name
+ * and @name_len, which is present in the base mft record specified by the vfs
+ * inode @base_vi.
+ *
+ * If the index inode is in the cache, it is just returned with an increased
+ * reference count.  Otherwise, a new struct inode is allocated and
+ * initialized, and finally ntfs_read_locked_index_inode() is called to read
+ * the index related attributes and fill in the inode structure.
+ *
+ * Return the struct inode of the index inode on success. Check the return
+ * value with IS_ERR() and if true, the function failed and the error code is
+ * obtained from PTR_ERR().
+ */
+struct inode *ntfs_index_iget(struct inode *base_vi, ntfschar *name,
+               u32 name_len)
+{
+       struct inode *vi;
+       int err;
+       ntfs_attr na;
+
+       na.mft_no = base_vi->i_ino;
+       na.type = AT_INDEX_ALLOCATION;
+       na.name = name;
+       na.name_len = name_len;
+
+       vi = iget5_locked(base_vi->i_sb, na.mft_no, (test_t)ntfs_test_inode,
+                       (set_t)ntfs_init_locked_inode, &na);
+       if (unlikely(!vi))
+               return ERR_PTR(-ENOMEM);
+
+       err = 0;
+
+       /* If this is a freshly allocated inode, need to read it now. */
+       if (vi->i_state & I_NEW) {
+               err = ntfs_read_locked_index_inode(base_vi, vi);
+               unlock_new_inode(vi);
+       }
+       /*
+        * There is no point in keeping bad index inodes around.  This also
+        * simplifies things in that we never need to check for bad index
+        * inodes elsewhere.
+        */
+       if (unlikely(err)) {
                iput(vi);
                vi = ERR_PTR(err);
        }
@@ -246,8 +324,7 @@ struct inode *ntfs_alloc_big_inode(struct super_block *sb)
        ntfs_inode *ni;
 
        ntfs_debug("Entering.");
-       ni = (ntfs_inode *)kmem_cache_alloc(ntfs_big_inode_cache,
-                       SLAB_NOFS);
+       ni = kmem_cache_alloc(ntfs_big_inode_cache, GFP_NOFS);
        if (likely(ni != NULL)) {
                ni->state = 0;
                return VFS_I(ni);
@@ -272,7 +349,7 @@ static inline ntfs_inode *ntfs_alloc_extent_inode(void)
        ntfs_inode *ni;
 
        ntfs_debug("Entering.");
-       ni = (ntfs_inode *)kmem_cache_alloc(ntfs_inode_cache, SLAB_NOFS);
+       ni = kmem_cache_alloc(ntfs_inode_cache, GFP_NOFS);
        if (likely(ni != NULL)) {
                ni->state = 0;
                return ni;
@@ -281,7 +358,7 @@ static inline ntfs_inode *ntfs_alloc_extent_inode(void)
        return NULL;
 }
 
-void ntfs_destroy_extent_inode(ntfs_inode *ni)
+static void ntfs_destroy_extent_inode(ntfs_inode *ni)
 {
        ntfs_debug("Entering.");
        BUG_ON(ni->page);
@@ -290,6 +367,12 @@ void ntfs_destroy_extent_inode(ntfs_inode *ni)
        kmem_cache_free(ntfs_inode_cache, ni);
 }
 
+/*
+ * The attribute runlist lock has separate locking rules from the
+ * normal runlist lock, so split the two lock-classes:
+ */
+static struct lock_class_key attr_list_rl_lock_class;
+
 /**
  * __ntfs_init_inode - initialize ntfs specific part of an inode
  * @sb:                super block of mounted volume
@@ -302,40 +385,39 @@ void ntfs_destroy_extent_inode(ntfs_inode *ni)
  *
  * Return zero on success and -ENOMEM on error.
  */
-static void __ntfs_init_inode(struct super_block *sb, ntfs_inode *ni)
+void __ntfs_init_inode(struct super_block *sb, ntfs_inode *ni)
 {
        ntfs_debug("Entering.");
+       rwlock_init(&ni->size_lock);
        ni->initialized_size = ni->allocated_size = 0;
        ni->seq_no = 0;
        atomic_set(&ni->count, 1);
        ni->vol = NTFS_SB(sb);
-       init_run_list(&ni->run_list);
-       init_MUTEX(&ni->mrec_lock);
+       ntfs_init_runlist(&ni->runlist);
+       mutex_init(&ni->mrec_lock);
        ni->page = NULL;
        ni->page_ofs = 0;
        ni->attr_list_size = 0;
        ni->attr_list = NULL;
-       init_run_list(&ni->attr_list_rl);
-       ni->itype.index.bmp_ino = NULL;
+       ntfs_init_runlist(&ni->attr_list_rl);
+       lockdep_set_class(&ni->attr_list_rl.lock,
+                               &attr_list_rl_lock_class);
        ni->itype.index.block_size = 0;
        ni->itype.index.vcn_size = 0;
+       ni->itype.index.collation_rule = 0;
        ni->itype.index.block_size_bits = 0;
        ni->itype.index.vcn_size_bits = 0;
-       init_MUTEX(&ni->extent_lock);
+       mutex_init(&ni->extent_lock);
        ni->nr_extents = 0;
        ni->ext.base_ntfs_ino = NULL;
-       return;
 }
 
-static inline void ntfs_init_big_inode(struct inode *vi)
-{
-       ntfs_inode *ni = NTFS_I(vi);
-
-       ntfs_debug("Entering.");
-       __ntfs_init_inode(vi->i_sb, ni);
-       ni->mft_no = vi->i_ino;
-       return;
-}
+/*
+ * Extent inodes get MFT-mapped in a nested way, while the base inode
+ * is still mapped. Teach this nesting to the lock validator by creating
+ * a separate class for nested inode's mrec_lock's:
+ */
+static struct lock_class_key extent_inode_mrec_lock_key;
 
 inline ntfs_inode *ntfs_new_extent_inode(struct super_block *sb,
                unsigned long mft_no)
@@ -345,6 +427,7 @@ inline ntfs_inode *ntfs_new_extent_inode(struct super_block *sb,
        ntfs_debug("Entering.");
        if (likely(ni != NULL)) {
                __ntfs_init_inode(sb, ni);
+               lockdep_set_class(&ni->mrec_lock, &extent_inode_mrec_lock_key);
                ni->mft_no = mft_no;
                ni->type = AT_UNUSED;
                ni->name = NULL;
@@ -364,20 +447,21 @@ inline ntfs_inode *ntfs_new_extent_inode(struct super_block *sb,
  * Return values:
  *        1: file is in $Extend directory
  *        0: file is not in $Extend directory
- *     -EIO: file is corrupt
+ *    -errno: failed to determine if the file is in the $Extend directory
  */
-static int ntfs_is_extended_system_file(attr_search_context *ctx)
+static int ntfs_is_extended_system_file(ntfs_attr_search_ctx *ctx)
 {
-       int nr_links;
+       int nr_links, err;
 
        /* Restart search. */
-       reinit_attr_search_ctx(ctx);
+       ntfs_attr_reinit_search_ctx(ctx);
 
        /* Get number of hard links. */
        nr_links = le16_to_cpu(ctx->mrec->link_count);
 
        /* Loop through all hard links. */
-       while (lookup_attr(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, ctx)) {
+       while (!(err = ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0,
+                       ctx))) {
                FILE_NAME_ATTR *file_name_attr;
                ATTR_RECORD *attr = ctx->attr;
                u8 *p, *p2;
@@ -420,7 +504,9 @@ err_corrupt_attr:
                if (MREF_LE(file_name_attr->parent_directory) == FILE_Extend)
                        return 1;       /* YES, it's an extended system file. */
        }
-       if (nr_links) {
+       if (unlikely(err != -ENOENT))
+               return err;
+       if (unlikely(nr_links)) {
                ntfs_error(ctx->ntfs_ino->vol->sb, "Inode hard link count "
                                "doesn't match number of name attributes. You "
                                "should run chkdsk.");
@@ -438,9 +524,7 @@ err_corrupt_attr:
  *
  * The only fields in @vi that we need to/can look at when the function is
  * called are i_sb, pointing to the mounted device's super block, and i_ino,
- * the number of the inode to load. If this is a fake inode, i.e. NInoAttr(),
- * then the fields type, name, and name_len are also valid, and describe the
- * attribute which this fake inode represents.
+ * the number of the inode to load.
  *
  * ntfs_read_locked_inode() maps, pins and locks the mft record number i_ino
  * for reading and sets up the necessary @vi fields as well as initializing
@@ -449,29 +533,29 @@ err_corrupt_attr:
  * Q: What locks are held when the function is called?
  * A: i_state has I_LOCK set, hence the inode is locked, also
  *    i_count is set to 1, so it is not going to go away
- *    i_flags is set to 0 and we have no business touching it. Only an ioctl()
+ *    i_flags is set to 0 and we have no business touching it.  Only an ioctl()
  *    is allowed to write to them. We should of course be honouring them but
  *    we need to do that using the IS_* macros defined in include/linux/fs.h.
  *    In any case ntfs_read_locked_inode() has nothing to do with i_flags.
  *
- * Return 0 on success and -errno on error. In the error case, the inode will
+ * Return 0 on success and -errno on error.  In the error case, the inode will
  * have had make_bad_inode() executed on it.
  */
 static int ntfs_read_locked_inode(struct inode *vi)
 {
        ntfs_volume *vol = NTFS_SB(vi->i_sb);
        ntfs_inode *ni;
+       struct inode *bvi;
        MFT_RECORD *m;
+       ATTR_RECORD *a;
        STANDARD_INFORMATION *si;
-       attr_search_context *ctx;
+       ntfs_attr_search_ctx *ctx;
        int err = 0;
 
        ntfs_debug("Entering for i_ino 0x%lx.", vi->i_ino);
 
        /* Setup the generic vfs inode parts now. */
 
-       /* This is the optimal IO size (for stat), not the fs block size. */
-       vi->i_blksize = PAGE_CACHE_SIZE;
        /*
         * This is for checking whether an inode has changed w.r.t. a file so
         * that the file can be updated if necessary (compare with f_version).
@@ -495,20 +579,18 @@ static int ntfs_read_locked_inode(struct inode *vi)
                err = PTR_ERR(m);
                goto err_out;
        }
-       ctx = get_attr_search_ctx(ni, m);
+       ctx = ntfs_attr_get_search_ctx(ni, m);
        if (!ctx) {
                err = -ENOMEM;
                goto unm_err_out;
        }
 
        if (!(m->flags & MFT_RECORD_IN_USE)) {
-               ntfs_error(vi->i_sb, "Inode is not in use! You should "
-                               "run chkdsk.");
+               ntfs_error(vi->i_sb, "Inode is not in use!");
                goto unm_err_out;
        }
        if (m->base_mft_record) {
-               ntfs_error(vi->i_sb, "Inode is an extent inode! You should "
-                               "run chkdsk.");
+               ntfs_error(vi->i_sb, "Inode is an extent inode!");
                goto unm_err_out;
        }
 
@@ -532,35 +614,52 @@ static int ntfs_read_locked_inode(struct inode *vi)
         * Also if not a directory, it could be something else, rather than
         * a regular file. But again, will do for now.
         */
+       /* Everyone gets all permissions. */
+       vi->i_mode |= S_IRWXUGO;
+       /* If read-only, noone gets write permissions. */
+       if (IS_RDONLY(vi))
+               vi->i_mode &= ~S_IWUGO;
        if (m->flags & MFT_RECORD_IS_DIRECTORY) {
                vi->i_mode |= S_IFDIR;
+               /*
+                * Apply the directory permissions mask set in the mount
+                * options.
+                */
+               vi->i_mode &= ~vol->dmask;
                /* Things break without this kludge! */
                if (vi->i_nlink > 1)
                        vi->i_nlink = 1;
-       } else
+       } else {
                vi->i_mode |= S_IFREG;
-
+               /* Apply the file permissions mask set in the mount options. */
+               vi->i_mode &= ~vol->fmask;
+       }
        /*
         * Find the standard information attribute in the mft record. At this
         * stage we haven't setup the attribute list stuff yet, so this could
         * in fact fail if the standard information is in an extent record, but
         * I don't think this actually ever happens.
         */
-       if (!lookup_attr(AT_STANDARD_INFORMATION, NULL, 0, 0, 0, NULL, 0,
-                       ctx)) {
-               /*
-                * TODO: We should be performing a hot fix here (if the recover
-                * mount option is set) by creating a new attribute.
-                */
-               ntfs_error(vi->i_sb, "$STANDARD_INFORMATION attribute is "
-                               "missing.");
+       err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0, 0, 0, NULL, 0,
+                       ctx);
+       if (unlikely(err)) {
+               if (err == -ENOENT) {
+                       /*
+                        * TODO: We should be performing a hot fix here (if the
+                        * recover mount option is set) by creating a new
+                        * attribute.
+                        */
+                       ntfs_error(vi->i_sb, "$STANDARD_INFORMATION attribute "
+                                       "is missing.");
+               }
                goto unm_err_out;
        }
+       a = ctx->attr;
        /* Get the standard information attribute value. */
-       si = (STANDARD_INFORMATION*)((char*)ctx->attr +
-                       le16_to_cpu(ctx->attr->data.resident.value_offset));
+       si = (STANDARD_INFORMATION*)((u8*)a +
+                       le16_to_cpu(a->data.resident.value_offset));
 
-       /* Transfer information from the standard information into vfs_ino. */
+       /* Transfer information from the standard information into vi. */
        /*
         * Note: The i_?times do not quite map perfectly onto the NTFS times,
         * but they are close enough, and in the end it doesn't really matter
@@ -584,23 +683,44 @@ static int ntfs_read_locked_inode(struct inode *vi)
        vi->i_atime = ntfs2utc(si->last_access_time);
 
        /* Find the attribute list attribute if present. */
-       reinit_attr_search_ctx(ctx);
-       if (lookup_attr(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx)) {
+       ntfs_attr_reinit_search_ctx(ctx);
+       err = ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx);
+       if (err) {
+               if (unlikely(err != -ENOENT)) {
+                       ntfs_error(vi->i_sb, "Failed to lookup attribute list "
+                                       "attribute.");
+                       goto unm_err_out;
+               }
+       } else /* if (!err) */ {
                if (vi->i_ino == FILE_MFT)
                        goto skip_attr_list_load;
                ntfs_debug("Attribute list found in inode 0x%lx.", vi->i_ino);
                NInoSetAttrList(ni);
-               if (ctx->attr->flags & ATTR_IS_ENCRYPTED ||
-                               ctx->attr->flags & ATTR_COMPRESSION_MASK ||
-                               ctx->attr->flags & ATTR_IS_SPARSE) {
+               a = ctx->attr;
+               if (a->flags & ATTR_COMPRESSION_MASK) {
                        ntfs_error(vi->i_sb, "Attribute list attribute is "
-                                       "compressed/encrypted/sparse. Not "
-                                       "allowed. Corrupt inode. You should "
-                                       "run chkdsk.");
+                                       "compressed.");
                        goto unm_err_out;
                }
+               if (a->flags & ATTR_IS_ENCRYPTED ||
+                               a->flags & ATTR_IS_SPARSE) {
+                       if (a->non_resident) {
+                               ntfs_error(vi->i_sb, "Non-resident attribute "
+                                               "list attribute is encrypted/"
+                                               "sparse.");
+                               goto unm_err_out;
+                       }
+                       ntfs_warning(vi->i_sb, "Resident attribute list "
+                                       "attribute in inode 0x%lx is marked "
+                                       "encrypted/sparse which is not true.  "
+                                       "However, Windows allows this and "
+                                       "chkdsk does not detect or correct it "
+                                       "so we will just ignore the invalid "
+                                       "flags and pretend they are not set.",
+                                       vi->i_ino);
+               }
                /* Now allocate memory for the attribute list. */
-               ni->attr_list_size = (u32)attribute_value_length(ctx->attr);
+               ni->attr_list_size = (u32)ntfs_attr_size(a);
                ni->attr_list = ntfs_malloc_nofs(ni->attr_list_size);
                if (!ni->attr_list) {
                        ntfs_error(vi->i_sb, "Not enough memory to allocate "
@@ -608,55 +728,49 @@ static int ntfs_read_locked_inode(struct inode *vi)
                        err = -ENOMEM;
                        goto unm_err_out;
                }
-               if (ctx->attr->non_resident) {
+               if (a->non_resident) {
                        NInoSetAttrListNonResident(ni);
-                       if (ctx->attr->data.non_resident.lowest_vcn) {
+                       if (a->data.non_resident.lowest_vcn) {
                                ntfs_error(vi->i_sb, "Attribute list has non "
-                                               "zero lowest_vcn. Inode is "
-                                               "corrupt. You should run "
-                                               "chkdsk.");
+                                               "zero lowest_vcn.");
                                goto unm_err_out;
                        }
                        /*
-                        * Setup the run list. No need for locking as we have
+                        * Setup the runlist. No need for locking as we have
                         * exclusive access to the inode at this time.
                         */
-                       ni->attr_list_rl.rl = decompress_mapping_pairs(vol,
-                                       ctx->attr, NULL);
+                       ni->attr_list_rl.rl = ntfs_mapping_pairs_decompress(vol,
+                                       a, NULL);
                        if (IS_ERR(ni->attr_list_rl.rl)) {
                                err = PTR_ERR(ni->attr_list_rl.rl);
                                ni->attr_list_rl.rl = NULL;
                                ntfs_error(vi->i_sb, "Mapping pairs "
-                                               "decompression failed with "
-                                               "error code %i. Corrupt "
-                                               "attribute list in inode.",
-                                               -err);
+                                               "decompression failed.");
                                goto unm_err_out;
                        }
                        /* Now load the attribute list. */
                        if ((err = load_attribute_list(vol, &ni->attr_list_rl,
                                        ni->attr_list, ni->attr_list_size,
-                                       sle64_to_cpu(ctx->attr->data.
-                                       non_resident.initialized_size)))) {
+                                       sle64_to_cpu(a->data.non_resident.
+                                       initialized_size)))) {
                                ntfs_error(vi->i_sb, "Failed to load "
                                                "attribute list attribute.");
                                goto unm_err_out;
                        }
-               } else /* if (!ctx.attr->non_resident) */ {
-                       if ((u8*)ctx->attr + le16_to_cpu(
-                                       ctx->attr->data.resident.value_offset) +
-                                       le32_to_cpu(
-                                       ctx->attr->data.resident.value_length) >
+               } else /* if (!a->non_resident) */ {
+                       if ((u8*)a + le16_to_cpu(a->data.resident.value_offset)
+                                       + le32_to_cpu(
+                                       a->data.resident.value_length) >
                                        (u8*)ctx->mrec + vol->mft_record_size) {
                                ntfs_error(vi->i_sb, "Corrupt attribute list "
                                                "in inode.");
                                goto unm_err_out;
                        }
                        /* Now copy the attribute list. */
-                       memcpy(ni->attr_list, (u8*)ctx->attr + le16_to_cpu(
-                                       ctx->attr->data.resident.value_offset),
+                       memcpy(ni->attr_list, (u8*)a + le16_to_cpu(
+                                       a->data.resident.value_offset),
                                        le32_to_cpu(
-                                       ctx->attr->data.resident.value_length));
+                                       a->data.resident.value_length));
                }
        }
 skip_attr_list_load:
@@ -665,25 +779,37 @@ skip_attr_list_load:
         * in ntfs_ino->attr_list and it is ntfs_ino->attr_list_size bytes.
         */
        if (S_ISDIR(vi->i_mode)) {
-               struct inode *bvi;
+               loff_t bvi_size;
                ntfs_inode *bni;
                INDEX_ROOT *ir;
-               char *ir_end, *index_end;
+               u8 *ir_end, *index_end;
 
                /* It is a directory, find index root attribute. */
-               reinit_attr_search_ctx(ctx);
-               if (!lookup_attr(AT_INDEX_ROOT, I30, 4, CASE_SENSITIVE, 0,
-                               NULL, 0, ctx)) {
-                       // FIXME: File is corrupt! Hot-fix with empty index
-                       // root attribute if recovery option is set.
-                       ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is "
-                                       "missing.");
+               ntfs_attr_reinit_search_ctx(ctx);
+               err = ntfs_attr_lookup(AT_INDEX_ROOT, I30, 4, CASE_SENSITIVE,
+                               0, NULL, 0, ctx);
+               if (unlikely(err)) {
+                       if (err == -ENOENT) {
+                               // FIXME: File is corrupt! Hot-fix with empty
+                               // index root attribute if recovery option is
+                               // set.
+                               ntfs_error(vi->i_sb, "$INDEX_ROOT attribute "
+                                               "is missing.");
+                       }
                        goto unm_err_out;
                }
+               a = ctx->attr;
                /* Set up the state. */
-               if (ctx->attr->non_resident) {
-                       ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is "
-                                       "not resident. Not allowed.");
+               if (unlikely(a->non_resident)) {
+                       ntfs_error(vol->sb, "$INDEX_ROOT attribute is not "
+                                       "resident.");
+                       goto unm_err_out;
+               }
+               /* Ensure the attribute name is placed before the value. */
+               if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=
+                               le16_to_cpu(a->data.resident.value_offset)))) {
+                       ntfs_error(vol->sb, "$INDEX_ROOT attribute name is "
+                                       "placed after the attribute value.");
                        goto unm_err_out;
                }
                /*
@@ -692,29 +818,27 @@ skip_attr_list_load:
                 * encrypted. However index root cannot be both compressed and
                 * encrypted.
                 */
-               if (ctx->attr->flags & ATTR_COMPRESSION_MASK)
+               if (a->flags & ATTR_COMPRESSION_MASK)
                        NInoSetCompressed(ni);
-               if (ctx->attr->flags & ATTR_IS_ENCRYPTED) {
-                       if (ctx->attr->flags & ATTR_COMPRESSION_MASK) {
+               if (a->flags & ATTR_IS_ENCRYPTED) {
+                       if (a->flags & ATTR_COMPRESSION_MASK) {
                                ntfs_error(vi->i_sb, "Found encrypted and "
-                                               "compressed attribute. Not "
-                                               "allowed.");
+                                               "compressed attribute.");
                                goto unm_err_out;
                        }
                        NInoSetEncrypted(ni);
                }
-               if (ctx->attr->flags & ATTR_IS_SPARSE)
+               if (a->flags & ATTR_IS_SPARSE)
                        NInoSetSparse(ni);
-               ir = (INDEX_ROOT*)((char*)ctx->attr + le16_to_cpu(
-                               ctx->attr->data.resident.value_offset));
-               ir_end = (char*)ir + le32_to_cpu(
-                               ctx->attr->data.resident.value_length);
-               if (ir_end > (char*)ctx->mrec + vol->mft_record_size) {
+               ir = (INDEX_ROOT*)((u8*)a +
+                               le16_to_cpu(a->data.resident.value_offset));
+               ir_end = (u8*)ir + le32_to_cpu(a->data.resident.value_length);
+               if (ir_end > (u8*)ctx->mrec + vol->mft_record_size) {
                        ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is "
                                        "corrupt.");
                        goto unm_err_out;
                }
-               index_end = (char*)&ir->index +
+               index_end = (u8*)&ir->index +
                                le32_to_cpu(ir->index.index_length);
                if (index_end > ir_end) {
                        ntfs_error(vi->i_sb, "Directory index is corrupt.");
@@ -722,14 +846,15 @@ skip_attr_list_load:
                }
                if (ir->type != AT_FILE_NAME) {
                        ntfs_error(vi->i_sb, "Indexed attribute is not "
-                                       "$FILE_NAME. Not allowed.");
+                                       "$FILE_NAME.");
                        goto unm_err_out;
                }
                if (ir->collation_rule != COLLATION_FILE_NAME) {
                        ntfs_error(vi->i_sb, "Index collation rule is not "
-                                       "COLLATION_FILE_NAME. Not allowed.");
+                                       "COLLATION_FILE_NAME.");
                        goto unm_err_out;
                }
+               ni->itype.index.collation_rule = ir->collation_rule;
                ni->itype.index.block_size = le32_to_cpu(ir->index_block_size);
                if (ni->itype.index.block_size &
                                (ni->itype.index.block_size - 1)) {
@@ -741,7 +866,7 @@ skip_attr_list_load:
                if (ni->itype.index.block_size > PAGE_CACHE_SIZE) {
                        ntfs_error(vi->i_sb, "Index block size (%u) > "
                                        "PAGE_CACHE_SIZE (%ld) is not "
-                                       "supported. Sorry.",
+                                       "supported.  Sorry.",
                                        ni->itype.index.block_size,
                                        PAGE_CACHE_SIZE);
                        err = -EOPNOTSUPP;
@@ -750,7 +875,7 @@ skip_attr_list_load:
                if (ni->itype.index.block_size < NTFS_BLOCK_SIZE) {
                        ntfs_error(vi->i_sb, "Index block size (%u) < "
                                        "NTFS_BLOCK_SIZE (%i) is not "
-                                       "supported. Sorry.",
+                                       "supported.  Sorry.",
                                        ni->itype.index.block_size,
                                        NTFS_BLOCK_SIZE);
                        err = -EOPNOTSUPP;
@@ -778,7 +903,7 @@ skip_attr_list_load:
                        vi->i_size = ni->initialized_size =
                                        ni->allocated_size = 0;
                        /* We are done with the mft record, so we release it. */
-                       put_attr_search_ctx(ctx);
+                       ntfs_attr_put_search_ctx(ctx);
                        unmap_mft_record(ni);
                        m = NULL;
                        ctx = NULL;
@@ -786,96 +911,104 @@ skip_attr_list_load:
                } /* LARGE_INDEX: Index allocation present. Setup state. */
                NInoSetIndexAllocPresent(ni);
                /* Find index allocation attribute. */
-               reinit_attr_search_ctx(ctx);
-               if (!lookup_attr(AT_INDEX_ALLOCATION, I30, 4, CASE_SENSITIVE,
-                               0, NULL, 0, ctx)) {
-                       ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute "
-                                       "is not present but $INDEX_ROOT "
-                                       "indicated it is.");
+               ntfs_attr_reinit_search_ctx(ctx);
+               err = ntfs_attr_lookup(AT_INDEX_ALLOCATION, I30, 4,
+                               CASE_SENSITIVE, 0, NULL, 0, ctx);
+               if (unlikely(err)) {
+                       if (err == -ENOENT)
+                               ntfs_error(vi->i_sb, "$INDEX_ALLOCATION "
+                                               "attribute is not present but "
+                                               "$INDEX_ROOT indicated it is.");
+                       else
+                               ntfs_error(vi->i_sb, "Failed to lookup "
+                                               "$INDEX_ALLOCATION "
+                                               "attribute.");
                        goto unm_err_out;
                }
-               if (!ctx->attr->non_resident) {
+               a = ctx->attr;
+               if (!a->non_resident) {
                        ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute "
                                        "is resident.");
                        goto unm_err_out;
                }
-               if (ctx->attr->flags & ATTR_IS_ENCRYPTED) {
+               /*
+                * Ensure the attribute name is placed before the mapping pairs
+                * array.
+                */
+               if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=
+                               le16_to_cpu(
+                               a->data.non_resident.mapping_pairs_offset)))) {
+                       ntfs_error(vol->sb, "$INDEX_ALLOCATION attribute name "
+                                       "is placed after the mapping pairs "
+                                       "array.");
+                       goto unm_err_out;
+               }
+               if (a->flags & ATTR_IS_ENCRYPTED) {
                        ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute "
                                        "is encrypted.");
                        goto unm_err_out;
                }
-               if (ctx->attr->flags & ATTR_IS_SPARSE) {
+               if (a->flags & ATTR_IS_SPARSE) {
                        ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute "
                                        "is sparse.");
                        goto unm_err_out;
                }
-               if (ctx->attr->flags & ATTR_COMPRESSION_MASK) {
+               if (a->flags & ATTR_COMPRESSION_MASK) {
                        ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute "
                                        "is compressed.");
                        goto unm_err_out;
                }
-               if (ctx->attr->data.non_resident.lowest_vcn) {
+               if (a->data.non_resident.lowest_vcn) {
                        ntfs_error(vi->i_sb, "First extent of "
                                        "$INDEX_ALLOCATION attribute has non "
-                                       "zero lowest_vcn. Inode is corrupt. "
-                                       "You should run chkdsk.");
+                                       "zero lowest_vcn.");
                        goto unm_err_out;
                }
-               vi->i_size = sle64_to_cpu(
-                               ctx->attr->data.non_resident.data_size);
+               vi->i_size = sle64_to_cpu(a->data.non_resident.data_size);
                ni->initialized_size = sle64_to_cpu(
-                               ctx->attr->data.non_resident.initialized_size);
+                               a->data.non_resident.initialized_size);
                ni->allocated_size = sle64_to_cpu(
-                               ctx->attr->data.non_resident.allocated_size);
+                               a->data.non_resident.allocated_size);
                /*
                 * We are done with the mft record, so we release it. Otherwise
                 * we would deadlock in ntfs_attr_iget().
                 */
-               put_attr_search_ctx(ctx);
+               ntfs_attr_put_search_ctx(ctx);
                unmap_mft_record(ni);
                m = NULL;
                ctx = NULL;
                /* Get the index bitmap attribute inode. */
                bvi = ntfs_attr_iget(vi, AT_BITMAP, I30, 4);
-               if (unlikely(IS_ERR(bvi))) {
+               if (IS_ERR(bvi)) {
                        ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
                        err = PTR_ERR(bvi);
                        goto unm_err_out;
                }
-               ni->itype.index.bmp_ino = bvi;
                bni = NTFS_I(bvi);
                if (NInoCompressed(bni) || NInoEncrypted(bni) ||
                                NInoSparse(bni)) {
                        ntfs_error(vi->i_sb, "$BITMAP attribute is compressed "
                                        "and/or encrypted and/or sparse.");
-                       goto unm_err_out;
+                       goto iput_unm_err_out;
                }
                /* Consistency check bitmap size vs. index allocation size. */
-               if ((bvi->i_size << 3) < (vi->i_size >>
+               bvi_size = i_size_read(bvi);
+               if ((bvi_size << 3) < (vi->i_size >>
                                ni->itype.index.block_size_bits)) {
                        ntfs_error(vi->i_sb, "Index bitmap too small (0x%llx) "
                                        "for index allocation (0x%llx).",
-                                       bvi->i_size << 3, vi->i_size);
-                       goto unm_err_out;
+                                       bvi_size << 3, vi->i_size);
+                       goto iput_unm_err_out;
                }
+               /* No longer need the bitmap attribute inode. */
+               iput(bvi);
 skip_large_dir_stuff:
-               /* Everyone gets read and scan permissions. */
-               vi->i_mode |= S_IRUGO | S_IXUGO;
-               /* If not read-only, set write permissions. */
-               if (!IS_RDONLY(vi))
-                       vi->i_mode |= S_IWUGO;
-               /*
-                * Apply the directory permissions mask set in the mount
-                * options.
-                */
-               vi->i_mode &= ~vol->dmask;
                /* Setup the operations for this inode. */
                vi->i_op = &ntfs_dir_inode_ops;
                vi->i_fop = &ntfs_dir_ops;
-               vi->i_mapping->a_ops = &ntfs_mst_aops;
        } else {
                /* It is a file. */
-               reinit_attr_search_ctx(ctx);
+               ntfs_attr_reinit_search_ctx(ctx);
 
                /* Setup the data attribute, even if not present. */
                ni->type = AT_DATA;
@@ -883,9 +1016,15 @@ skip_large_dir_stuff:
                ni->name_len = 0;
 
                /* Find first extent of the unnamed data attribute. */
-               if (!lookup_attr(AT_DATA, NULL, 0, 0, 0, NULL, 0, ctx)) {
+               err = ntfs_attr_lookup(AT_DATA, NULL, 0, 0, 0, NULL, 0, ctx);
+               if (unlikely(err)) {
                        vi->i_size = ni->initialized_size =
-                                       ni->allocated_size = 0LL;
+                                       ni->allocated_size = 0;
+                       if (err != -ENOENT) {
+                               ntfs_error(vi->i_sb, "Failed to lookup $DATA "
+                                               "attribute.");
+                               goto unm_err_out;
+                       }
                        /*
                         * FILE_Secure does not have an unnamed $DATA
                         * attribute, so we special case it here.
@@ -905,113 +1044,121 @@ skip_large_dir_stuff:
                                goto no_data_attr_special_case;
                        // FIXME: File is corrupt! Hot-fix with empty data
                        // attribute if recovery option is set.
-                       ntfs_error(vi->i_sb, "$DATA attribute is "
-                                       "missing.");
+                       ntfs_error(vi->i_sb, "$DATA attribute is missing.");
                        goto unm_err_out;
                }
+               a = ctx->attr;
                /* Setup the state. */
-               if (ctx->attr->non_resident) {
-                       NInoSetNonResident(ni);
-                       if (ctx->attr->flags & ATTR_COMPRESSION_MASK) {
+               if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) {
+                       if (a->flags & ATTR_COMPRESSION_MASK) {
                                NInoSetCompressed(ni);
                                if (vol->cluster_size > 4096) {
                                        ntfs_error(vi->i_sb, "Found "
-                                               "compressed data but "
-                                               "compression is disabled due "
-                                               "to cluster size (%i) > 4kiB.",
-                                               vol->cluster_size);
+                                                       "compressed data but "
+                                                       "compression is "
+                                                       "disabled due to "
+                                                       "cluster size (%i) > "
+                                                       "4kiB.",
+                                                       vol->cluster_size);
                                        goto unm_err_out;
                                }
-                               if ((ctx->attr->flags & ATTR_COMPRESSION_MASK)
+                               if ((a->flags & ATTR_COMPRESSION_MASK)
                                                != ATTR_IS_COMPRESSED) {
-                                       ntfs_error(vi->i_sb, "Found "
-                                               "unknown compression method or "
-                                               "corrupt file.");
+                                       ntfs_error(vi->i_sb, "Found unknown "
+                                                       "compression method "
+                                                       "or corrupt file.");
                                        goto unm_err_out;
                                }
-                               ni->itype.compressed.block_clusters = 1U <<
-                                               ctx->attr->data.non_resident.
-                                               compression_unit;
-                               if (ctx->attr->data.non_resident.
+                       }
+                       if (a->flags & ATTR_IS_SPARSE)
+                               NInoSetSparse(ni);
+               }
+               if (a->flags & ATTR_IS_ENCRYPTED) {
+                       if (NInoCompressed(ni)) {
+                               ntfs_error(vi->i_sb, "Found encrypted and "
+                                               "compressed data.");
+                               goto unm_err_out;
+                       }
+                       NInoSetEncrypted(ni);
+               }
+               if (a->non_resident) {
+                       NInoSetNonResident(ni);
+                       if (NInoCompressed(ni) || NInoSparse(ni)) {
+                               if (NInoCompressed(ni) && a->data.non_resident.
                                                compression_unit != 4) {
                                        ntfs_error(vi->i_sb, "Found "
-                                               "nonstandard compression unit "
-                                               "(%u instead of 4). Cannot "
-                                               "handle this. This might "
-                                               "indicate corruption so you "
-                                               "should run chkdsk.",
-                                               ctx->attr->data.non_resident.
-                                               compression_unit);
+                                                       "non-standard "
+                                                       "compression unit (%u "
+                                                       "instead of 4).  "
+                                                       "Cannot handle this.",
+                                                       a->data.non_resident.
+                                                       compression_unit);
                                        err = -EOPNOTSUPP;
                                        goto unm_err_out;
                                }
-                               ni->itype.compressed.block_size = 1U << (
-                                               ctx->attr->data.non_resident.
-                                               compression_unit +
-                                               vol->cluster_size_bits);
-                               ni->itype.compressed.block_size_bits = ffs(
-                                       ni->itype.compressed.block_size) - 1;
-                       }
-                       if (ctx->attr->flags & ATTR_IS_ENCRYPTED) {
-                               if (ctx->attr->flags & ATTR_COMPRESSION_MASK) {
-                                       ntfs_error(vi->i_sb, "Found encrypted "
-                                                       "and compressed data.");
-                                       goto unm_err_out;
+                               if (a->data.non_resident.compression_unit) {
+                                       ni->itype.compressed.block_size = 1U <<
+                                                       (a->data.non_resident.
+                                                       compression_unit +
+                                                       vol->cluster_size_bits);
+                                       ni->itype.compressed.block_size_bits =
+                                                       ffs(ni->itype.
+                                                       compressed.
+                                                       block_size) - 1;
+                                       ni->itype.compressed.block_clusters =
+                                                       1U << a->data.
+                                                       non_resident.
+                                                       compression_unit;
+                               } else {
+                                       ni->itype.compressed.block_size = 0;
+                                       ni->itype.compressed.block_size_bits =
+                                                       0;
+                                       ni->itype.compressed.block_clusters =
+                                                       0;
                                }
-                               NInoSetEncrypted(ni);
+                               ni->itype.compressed.size = sle64_to_cpu(
+                                               a->data.non_resident.
+                                               compressed_size);
                        }
-                       if (ctx->attr->flags & ATTR_IS_SPARSE)
-                               NInoSetSparse(ni);
-                       if (ctx->attr->data.non_resident.lowest_vcn) {
+                       if (a->data.non_resident.lowest_vcn) {
                                ntfs_error(vi->i_sb, "First extent of $DATA "
                                                "attribute has non zero "
-                                               "lowest_vcn. Inode is corrupt. "
-                                               "You should run chkdsk.");
+                                               "lowest_vcn.");
                                goto unm_err_out;
                        }
-                       /* Setup all the sizes. */
                        vi->i_size = sle64_to_cpu(
-                                       ctx->attr->data.non_resident.data_size);
+                                       a->data.non_resident.data_size);
                        ni->initialized_size = sle64_to_cpu(
-                                       ctx->attr->data.non_resident.
-                                       initialized_size);
+                                       a->data.non_resident.initialized_size);
                        ni->allocated_size = sle64_to_cpu(
-                                       ctx->attr->data.non_resident.
-                                       allocated_size);
-                       if (NInoCompressed(ni)) {
-                               ni->itype.compressed.size = sle64_to_cpu(
-                                               ctx->attr->data.non_resident.
-                                               compressed_size);
-                       }
+                                       a->data.non_resident.allocated_size);
                } else { /* Resident attribute. */
-                       /*
-                        * Make all sizes equal for simplicity in read code
-                        * paths. FIXME: Need to keep this in mind when
-                        * converting to non-resident attribute in write code
-                        * path. (Probably only affects truncate().)
-                        */
-                       vi->i_size = ni->initialized_size = ni->allocated_size =
-                                       le32_to_cpu(
-                                       ctx->attr->data.resident.value_length);
+                       vi->i_size = ni->initialized_size = le32_to_cpu(
+                                       a->data.resident.value_length);
+                       ni->allocated_size = le32_to_cpu(a->length) -
+                                       le16_to_cpu(
+                                       a->data.resident.value_offset);
+                       if (vi->i_size > ni->allocated_size) {
+                               ntfs_error(vi->i_sb, "Resident data attribute "
+                                               "is corrupt (size exceeds "
+                                               "allocation).");
+                               goto unm_err_out;
+                       }
                }
 no_data_attr_special_case:
                /* We are done with the mft record, so we release it. */
-               put_attr_search_ctx(ctx);
+               ntfs_attr_put_search_ctx(ctx);
                unmap_mft_record(ni);
                m = NULL;
                ctx = NULL;
-               /* Everyone gets all permissions. */
-               vi->i_mode |= S_IRWXUGO;
-               /* If read-only, noone gets write permissions. */
-               if (IS_RDONLY(vi))
-                       vi->i_mode &= ~S_IWUGO;
-               /* Apply the file permissions mask set in the mount options. */
-               vi->i_mode &= ~vol->fmask;
                /* Setup the operations for this inode. */
                vi->i_op = &ntfs_file_inode_ops;
                vi->i_fop = &ntfs_file_ops;
-               vi->i_mapping->a_ops = &ntfs_aops;
        }
+       if (NInoMstProtected(ni))
+               vi->i_mapping->a_ops = &ntfs_mst_aops;
+       else
+               vi->i_mapping->a_ops = &ntfs_aops;
        /*
         * The number of 512-byte blocks used on disk (for stat). This is in so
         * far inaccurate as it doesn't account for any named streams or other
@@ -1023,25 +1170,27 @@ no_data_attr_special_case:
         * sizes of all non-resident attributes present to give us the Linux
         * correct size that should go into i_blocks (after division by 512).
         */
-       if (S_ISDIR(vi->i_mode) || !NInoCompressed(ni))
-               vi->i_blocks = ni->allocated_size >> 9;
-       else
+       if (S_ISREG(vi->i_mode) && (NInoCompressed(ni) || NInoSparse(ni)))
                vi->i_blocks = ni->itype.compressed.size >> 9;
-
+       else
+               vi->i_blocks = ni->allocated_size >> 9;
        ntfs_debug("Done.");
        return 0;
-
+iput_unm_err_out:
+       iput(bvi);
 unm_err_out:
        if (!err)
                err = -EIO;
        if (ctx)
-               put_attr_search_ctx(ctx);
+               ntfs_attr_put_search_ctx(ctx);
        if (m)
                unmap_mft_record(ni);
 err_out:
-       ntfs_error(vi->i_sb, "Failed with error code %i. Marking inode 0x%lx "
-                       "as bad.", -err, vi->i_ino);
+       ntfs_error(vol->sb, "Failed with error code %i.  Marking corrupt "
+                       "inode 0x%lx as bad.  Run chkdsk.", err, vi->i_ino);
        make_bad_inode(vi);
+       if (err != -EOPNOTSUPP && err != -ENOMEM)
+               NVolSetErrors(vol);
        return err;
 }
 
@@ -1050,8 +1199,8 @@ err_out:
  * @base_vi:   base inode
  * @vi:                attribute inode to read
  *
- * ntfs_read_locked_attr_inode() is called from the ntfs_attr_iget() to read
- * the attribute inode described by @vi into memory from the base mft record
+ * ntfs_read_locked_attr_inode() is called from ntfs_attr_iget() to read the
+ * attribute inode described by @vi into memory from the base mft record
  * described by @base_ni.
  *
  * ntfs_read_locked_attr_inode() maps, pins and locks the base inode for
@@ -1061,13 +1210,19 @@ err_out:
  * Q: What locks are held when the function is called?
  * A: i_state has I_LOCK set, hence the inode is locked, also
  *    i_count is set to 1, so it is not going to go away
+ *
+ * Return 0 on success and -errno on error.  In the error case, the inode will
+ * have had make_bad_inode() executed on it.
+ *
+ * Note this cannot be called for AT_INDEX_ALLOCATION.
  */
 static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
 {
        ntfs_volume *vol = NTFS_SB(vi->i_sb);
        ntfs_inode *ni, *base_ni;
        MFT_RECORD *m;
-       attr_search_context *ctx;
+       ATTR_RECORD *a;
+       ntfs_attr_search_ctx *ctx;
        int err = 0;
 
        ntfs_debug("Entering for i_ino 0x%lx.", vi->i_ino);
@@ -1078,7 +1233,6 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
        base_ni = NTFS_I(base_vi);
 
        /* Just mirror the values from the base inode. */
-       vi->i_blksize   = base_vi->i_blksize;
        vi->i_version   = base_vi->i_version;
        vi->i_uid       = base_vi->i_uid;
        vi->i_gid       = base_vi->i_gid;
@@ -1096,156 +1250,164 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
                err = PTR_ERR(m);
                goto err_out;
        }
-       ctx = get_attr_search_ctx(base_ni, m);
+       ctx = ntfs_attr_get_search_ctx(base_ni, m);
        if (!ctx) {
                err = -ENOMEM;
                goto unm_err_out;
        }
-
        /* Find the attribute. */
-       if (!lookup_attr(ni->type, ni->name, ni->name_len, IGNORE_CASE, 0,
-                       NULL, 0, ctx))
+       err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
+                       CASE_SENSITIVE, 0, NULL, 0, ctx);
+       if (unlikely(err))
                goto unm_err_out;
-
-       if (!ctx->attr->non_resident) {
-               if (NInoMstProtected(ni) || ctx->attr->flags) {
-                       ntfs_error(vi->i_sb, "Found mst protected attribute "
-                                       "or attribute with non-zero flags but "
-                                       "the attribute is resident (mft_no "
-                                       "0x%lx, type 0x%x, name_len %i). "
-                                       "Please report you saw this message "
-                                       "to linux-ntfs-dev@lists."
-                                       "sourceforge.net",
-                                       vi->i_ino, ni->type, ni->name_len);
-                       goto unm_err_out;
-               }
-               /*
-                * Resident attribute. Make all sizes equal for simplicity in
-                * read code paths.
-                */
-               vi->i_size = ni->initialized_size = ni->allocated_size =
-                       le32_to_cpu(ctx->attr->data.resident.value_length);
-       } else {
-               NInoSetNonResident(ni);
-               if (ctx->attr->flags & ATTR_COMPRESSION_MASK) {
-                       if (NInoMstProtected(ni)) {
-                               ntfs_error(vi->i_sb, "Found mst protected "
-                                               "attribute but the attribute "
-                                               "is compressed (mft_no 0x%lx, "
-                                               "type 0x%x, name_len %i). "
-                                               "Please report you saw this "
-                                               "message to linux-ntfs-dev@"
-                                               "lists.sourceforge.net",
-                                               vi->i_ino, ni->type,
-                                               ni->name_len);
-                               goto unm_err_out;
-                       }
+       a = ctx->attr;
+       if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_SPARSE)) {
+               if (a->flags & ATTR_COMPRESSION_MASK) {
                        NInoSetCompressed(ni);
                        if ((ni->type != AT_DATA) || (ni->type == AT_DATA &&
                                        ni->name_len)) {
-                               ntfs_error(vi->i_sb, "Found compressed non-"
-                                               "data or named data attribute "
-                                               "(mft_no 0x%lx, type 0x%x, "
-                                               "name_len %i). Please report "
+                               ntfs_error(vi->i_sb, "Found compressed "
+                                               "non-data or named data "
+                                               "attribute.  Please report "
                                                "you saw this message to "
                                                "linux-ntfs-dev@lists."
-                                               "sourceforge.net",
-                                               vi->i_ino, ni->type,
-                                               ni->name_len);
+                                               "sourceforge.net");
                                goto unm_err_out;
                        }
                        if (vol->cluster_size > 4096) {
-                               ntfs_error(vi->i_sb, "Found "
-                                       "compressed attribute but "
-                                       "compression is disabled due "
-                                       "to cluster size (%i) > 4kiB.",
-                                       vol->cluster_size);
+                               ntfs_error(vi->i_sb, "Found compressed "
+                                               "attribute but compression is "
+                                               "disabled due to cluster size "
+                                               "(%i) > 4kiB.",
+                                               vol->cluster_size);
                                goto unm_err_out;
                        }
-                       if ((ctx->attr->flags & ATTR_COMPRESSION_MASK)
-                                       != ATTR_IS_COMPRESSED) {
+                       if ((a->flags & ATTR_COMPRESSION_MASK) !=
+                                       ATTR_IS_COMPRESSED) {
                                ntfs_error(vi->i_sb, "Found unknown "
-                                               "compression method or "
-                                               "corrupt file.");
+                                               "compression method.");
                                goto unm_err_out;
                        }
-                       ni->itype.compressed.block_clusters = 1U <<
-                                       ctx->attr->data.non_resident.
-                                       compression_unit;
-                       if (ctx->attr->data.non_resident.compression_unit != 4) {
-                               ntfs_error(vi->i_sb, "Found "
-                                       "nonstandard compression unit "
-                                       "(%u instead of 4). Cannot "
-                                       "handle this. This might "
-                                       "indicate corruption so you "
-                                       "should run chkdsk.",
-                                       ctx->attr->data.non_resident.
-                                       compression_unit);
+               }
+               /*
+                * The compressed/sparse flag set in an index root just means
+                * to compress all files.
+                */
+               if (NInoMstProtected(ni) && ni->type != AT_INDEX_ROOT) {
+                       ntfs_error(vi->i_sb, "Found mst protected attribute "
+                                       "but the attribute is %s.  Please "
+                                       "report you saw this message to "
+                                       "linux-ntfs-dev@lists.sourceforge.net",
+                                       NInoCompressed(ni) ? "compressed" :
+                                       "sparse");
+                       goto unm_err_out;
+               }
+               if (a->flags & ATTR_IS_SPARSE)
+                       NInoSetSparse(ni);
+       }
+       if (a->flags & ATTR_IS_ENCRYPTED) {
+               if (NInoCompressed(ni)) {
+                       ntfs_error(vi->i_sb, "Found encrypted and compressed "
+                                       "data.");
+                       goto unm_err_out;
+               }
+               /*
+                * The encryption flag set in an index root just means to
+                * encrypt all files.
+                */
+               if (NInoMstProtected(ni) && ni->type != AT_INDEX_ROOT) {
+                       ntfs_error(vi->i_sb, "Found mst protected attribute "
+                                       "but the attribute is encrypted.  "
+                                       "Please report you saw this message "
+                                       "to linux-ntfs-dev@lists.sourceforge."
+                                       "net");
+                       goto unm_err_out;
+               }
+               if (ni->type != AT_DATA) {
+                       ntfs_error(vi->i_sb, "Found encrypted non-data "
+                                       "attribute.");
+                       goto unm_err_out;
+               }
+               NInoSetEncrypted(ni);
+       }
+       if (!a->non_resident) {
+               /* Ensure the attribute name is placed before the value. */
+               if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=
+                               le16_to_cpu(a->data.resident.value_offset)))) {
+                       ntfs_error(vol->sb, "Attribute name is placed after "
+                                       "the attribute value.");
+                       goto unm_err_out;
+               }
+               if (NInoMstProtected(ni)) {
+                       ntfs_error(vi->i_sb, "Found mst protected attribute "
+                                       "but the attribute is resident.  "
+                                       "Please report you saw this message to "
+                                       "linux-ntfs-dev@lists.sourceforge.net");
+                       goto unm_err_out;
+               }
+               vi->i_size = ni->initialized_size = le32_to_cpu(
+                               a->data.resident.value_length);
+               ni->allocated_size = le32_to_cpu(a->length) -
+                               le16_to_cpu(a->data.resident.value_offset);
+               if (vi->i_size > ni->allocated_size) {
+                       ntfs_error(vi->i_sb, "Resident attribute is corrupt "
+                                       "(size exceeds allocation).");
+                       goto unm_err_out;
+               }
+       } else {
+               NInoSetNonResident(ni);
+               /*
+                * Ensure the attribute name is placed before the mapping pairs
+                * array.
+                */
+               if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=
+                               le16_to_cpu(
+                               a->data.non_resident.mapping_pairs_offset)))) {
+                       ntfs_error(vol->sb, "Attribute name is placed after "
+                                       "the mapping pairs array.");
+                       goto unm_err_out;
+               }
+               if (NInoCompressed(ni) || NInoSparse(ni)) {
+                       if (NInoCompressed(ni) && a->data.non_resident.
+                                       compression_unit != 4) {
+                               ntfs_error(vi->i_sb, "Found non-standard "
+                                               "compression unit (%u instead "
+                                               "of 4).  Cannot handle this.",
+                                               a->data.non_resident.
+                                               compression_unit);
                                err = -EOPNOTSUPP;
                                goto unm_err_out;
                        }
-                       ni->itype.compressed.block_size = 1U << (
-                                       ctx->attr->data.non_resident.
-                                       compression_unit +
-                                       vol->cluster_size_bits);
-                       ni->itype.compressed.block_size_bits = ffs(
-                               ni->itype.compressed.block_size) - 1;
-               }
-               if (ctx->attr->flags & ATTR_IS_ENCRYPTED) {
-                       if (ctx->attr->flags & ATTR_COMPRESSION_MASK) {
-                               ntfs_error(vi->i_sb, "Found encrypted "
-                                               "and compressed data.");
-                               goto unm_err_out;
-                       }
-                       if (NInoMstProtected(ni)) {
-                               ntfs_error(vi->i_sb, "Found mst protected "
-                                               "attribute but the attribute "
-                                               "is encrypted (mft_no 0x%lx, "
-                                               "type 0x%x, name_len %i). "
-                                               "Please report you saw this "
-                                               "message to linux-ntfs-dev@"
-                                               "lists.sourceforge.net",
-                                               vi->i_ino, ni->type,
-                                               ni->name_len);
-                               goto unm_err_out;
-                       }
-                       NInoSetEncrypted(ni);
-               }
-               if (ctx->attr->flags & ATTR_IS_SPARSE) {
-                       if (NInoMstProtected(ni)) {
-                               ntfs_error(vi->i_sb, "Found mst protected "
-                                               "attribute but the attribute "
-                                               "is sparse (mft_no 0x%lx, "
-                                               "type 0x%x, name_len %i). "
-                                               "Please report you saw this "
-                                               "message to linux-ntfs-dev@"
-                                               "lists.sourceforge.net",
-                                               vi->i_ino, ni->type,
-                                               ni->name_len);
-                               goto unm_err_out;
+                       if (a->data.non_resident.compression_unit) {
+                               ni->itype.compressed.block_size = 1U <<
+                                               (a->data.non_resident.
+                                               compression_unit +
+                                               vol->cluster_size_bits);
+                               ni->itype.compressed.block_size_bits =
+                                               ffs(ni->itype.compressed.
+                                               block_size) - 1;
+                               ni->itype.compressed.block_clusters = 1U <<
+                                               a->data.non_resident.
+                                               compression_unit;
+                       } else {
+                               ni->itype.compressed.block_size = 0;
+                               ni->itype.compressed.block_size_bits = 0;
+                               ni->itype.compressed.block_clusters = 0;
                        }
-                       NInoSetSparse(ni);
+                       ni->itype.compressed.size = sle64_to_cpu(
+                                       a->data.non_resident.compressed_size);
                }
-               if (ctx->attr->data.non_resident.lowest_vcn) {
+               if (a->data.non_resident.lowest_vcn) {
                        ntfs_error(vi->i_sb, "First extent of attribute has "
-                                       "non-zero lowest_vcn. Inode is "
-                                       "corrupt. You should run chkdsk.");
+                                       "non-zero lowest_vcn.");
                        goto unm_err_out;
                }
-               /* Setup all the sizes. */
-               vi->i_size = sle64_to_cpu(
-                               ctx->attr->data.non_resident.data_size);
+               vi->i_size = sle64_to_cpu(a->data.non_resident.data_size);
                ni->initialized_size = sle64_to_cpu(
-                               ctx->attr->data.non_resident.initialized_size);
+                               a->data.non_resident.initialized_size);
                ni->allocated_size = sle64_to_cpu(
-                               ctx->attr->data.non_resident.allocated_size);
-               if (NInoCompressed(ni)) {
-                       ni->itype.compressed.size = sle64_to_cpu(
-                                       ctx->attr->data.non_resident.
-                                       compressed_size);
-               }
+                               a->data.non_resident.allocated_size);
        }
-
        /* Setup the operations for this attribute inode. */
        vi->i_op = NULL;
        vi->i_fop = NULL;
@@ -1253,21 +1415,19 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
                vi->i_mapping->a_ops = &ntfs_mst_aops;
        else
                vi->i_mapping->a_ops = &ntfs_aops;
-
-       if (!NInoCompressed(ni))
-               vi->i_blocks = ni->allocated_size >> 9;
-       else
+       if ((NInoCompressed(ni) || NInoSparse(ni)) && ni->type != AT_INDEX_ROOT)
                vi->i_blocks = ni->itype.compressed.size >> 9;
-
+       else
+               vi->i_blocks = ni->allocated_size >> 9;
        /*
-        * Make sure the base inode doesn't go away and attach it to the
+        * Make sure the base inode does not go away and attach it to the
         * attribute inode.
         */
        igrab(base_vi);
        ni->ext.base_ntfs_ino = base_ni;
        ni->nr_extents = -1;
 
-       put_attr_search_ctx(ctx);
+       ntfs_attr_put_search_ctx(ctx);
        unmap_mft_record(base_ni);
 
        ntfs_debug("Done.");
@@ -1277,40 +1437,336 @@ unm_err_out:
        if (!err)
                err = -EIO;
        if (ctx)
-               put_attr_search_ctx(ctx);
+               ntfs_attr_put_search_ctx(ctx);
        unmap_mft_record(base_ni);
 err_out:
-       ntfs_error(vi->i_sb, "Failed with error code %i while reading "
-                       "attribute inode (mft_no 0x%lx, type 0x%x, name_len "
-                       "%i.", -err, vi->i_ino, ni->type, ni->name_len);
+       ntfs_error(vol->sb, "Failed with error code %i while reading attribute "
+                       "inode (mft_no 0x%lx, type 0x%x, name_len %i).  "
+                       "Marking corrupt inode and base inode 0x%lx as bad.  "
+                       "Run chkdsk.", err, vi->i_ino, ni->type, ni->name_len,
+                       base_vi->i_ino);
        make_bad_inode(vi);
+       if (err != -ENOMEM)
+               NVolSetErrors(vol);
        return err;
 }
 
 /**
- * ntfs_read_inode_mount - special read_inode for mount time use only
- * @vi:                inode to read
+ * ntfs_read_locked_index_inode - read an index inode from its base inode
+ * @base_vi:   base inode
+ * @vi:                index inode to read
  *
- * Read inode FILE_MFT at mount time, only called with super_block lock
- * held from within the read_super() code path.
+ * ntfs_read_locked_index_inode() is called from ntfs_index_iget() to read the
+ * index inode described by @vi into memory from the base mft record described
+ * by @base_ni.
  *
- * This function exists because when it is called the page cache for $MFT/$DATA
- * is not initialized and hence we cannot get at the contents of mft records
- * by calling map_mft_record*().
+ * ntfs_read_locked_index_inode() maps, pins and locks the base inode for
+ * reading and looks up the attributes relating to the index described by @vi
+ * before setting up the necessary fields in @vi as well as initializing the
+ * ntfs inode.
  *
- * Further it needs to cope with the circular references problem, i.e. can't
- * load any attributes other than $ATTRIBUTE_LIST until $DATA is loaded, because
- * we don't know where the other extent mft records are yet and again, because
- * we cannot call map_mft_record*() yet. Obviously this applies only when an
- * attribute list is actually present in $MFT inode.
+ * Note, index inodes are essentially attribute inodes (NInoAttr() is true)
+ * with the attribute type set to AT_INDEX_ALLOCATION.  Apart from that, they
+ * are setup like directory inodes since directories are a special case of
+ * indices ao they need to be treated in much the same way.  Most importantly,
+ * for small indices the index allocation attribute might not actually exist.
+ * However, the index root attribute always exists but this does not need to
+ * have an inode associated with it and this is why we define a new inode type
+ * index.  Also, like for directories, we need to have an attribute inode for
+ * the bitmap attribute corresponding to the index allocation attribute and we
+ * can store this in the appropriate field of the inode, just like we do for
+ * normal directory inodes.
  *
- * We solve these problems by starting with the $DATA attribute before anything
- * else and iterating using lookup_attr($DATA) over all extents. As each extent
- * is found, we decompress_mapping_pairs() including the implied
- * merge_run_lists(). Each step of the iteration necessarily provides
- * sufficient information for the next step to complete.
+ * Q: What locks are held when the function is called?
+ * A: i_state has I_LOCK set, hence the inode is locked, also
+ *    i_count is set to 1, so it is not going to go away
  *
- * This should work but there are two possible pit falls (see inline comments
+ * Return 0 on success and -errno on error.  In the error case, the inode will
+ * have had make_bad_inode() executed on it.
+ */
+static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
+{
+       loff_t bvi_size;
+       ntfs_volume *vol = NTFS_SB(vi->i_sb);
+       ntfs_inode *ni, *base_ni, *bni;
+       struct inode *bvi;
+       MFT_RECORD *m;
+       ATTR_RECORD *a;
+       ntfs_attr_search_ctx *ctx;
+       INDEX_ROOT *ir;
+       u8 *ir_end, *index_end;
+       int err = 0;
+
+       ntfs_debug("Entering for i_ino 0x%lx.", vi->i_ino);
+       ntfs_init_big_inode(vi);
+       ni      = NTFS_I(vi);
+       base_ni = NTFS_I(base_vi);
+       /* Just mirror the values from the base inode. */
+       vi->i_version   = base_vi->i_version;
+       vi->i_uid       = base_vi->i_uid;
+       vi->i_gid       = base_vi->i_gid;
+       vi->i_nlink     = base_vi->i_nlink;
+       vi->i_mtime     = base_vi->i_mtime;
+       vi->i_ctime     = base_vi->i_ctime;
+       vi->i_atime     = base_vi->i_atime;
+       vi->i_generation = ni->seq_no = base_ni->seq_no;
+       /* Set inode type to zero but preserve permissions. */
+       vi->i_mode      = base_vi->i_mode & ~S_IFMT;
+       /* Map the mft record for the base inode. */
+       m = map_mft_record(base_ni);
+       if (IS_ERR(m)) {
+               err = PTR_ERR(m);
+               goto err_out;
+       }
+       ctx = ntfs_attr_get_search_ctx(base_ni, m);
+       if (!ctx) {
+               err = -ENOMEM;
+               goto unm_err_out;
+       }
+       /* Find the index root attribute. */
+       err = ntfs_attr_lookup(AT_INDEX_ROOT, ni->name, ni->name_len,
+                       CASE_SENSITIVE, 0, NULL, 0, ctx);
+       if (unlikely(err)) {
+               if (err == -ENOENT)
+                       ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is "
+                                       "missing.");
+               goto unm_err_out;
+       }
+       a = ctx->attr;
+       /* Set up the state. */
+       if (unlikely(a->non_resident)) {
+               ntfs_error(vol->sb, "$INDEX_ROOT attribute is not resident.");
+               goto unm_err_out;
+       }
+       /* Ensure the attribute name is placed before the value. */
+       if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=
+                       le16_to_cpu(a->data.resident.value_offset)))) {
+               ntfs_error(vol->sb, "$INDEX_ROOT attribute name is placed "
+                               "after the attribute value.");
+               goto unm_err_out;
+       }
+       /*
+        * Compressed/encrypted/sparse index root is not allowed, except for
+        * directories of course but those are not dealt with here.
+        */
+       if (a->flags & (ATTR_COMPRESSION_MASK | ATTR_IS_ENCRYPTED |
+                       ATTR_IS_SPARSE)) {
+               ntfs_error(vi->i_sb, "Found compressed/encrypted/sparse index "
+                               "root attribute.");
+               goto unm_err_out;
+       }
+       ir = (INDEX_ROOT*)((u8*)a + le16_to_cpu(a->data.resident.value_offset));
+       ir_end = (u8*)ir + le32_to_cpu(a->data.resident.value_length);
+       if (ir_end > (u8*)ctx->mrec + vol->mft_record_size) {
+               ntfs_error(vi->i_sb, "$INDEX_ROOT attribute is corrupt.");
+               goto unm_err_out;
+       }
+       index_end = (u8*)&ir->index + le32_to_cpu(ir->index.index_length);
+       if (index_end > ir_end) {
+               ntfs_error(vi->i_sb, "Index is corrupt.");
+               goto unm_err_out;
+       }
+       if (ir->type) {
+               ntfs_error(vi->i_sb, "Index type is not 0 (type is 0x%x).",
+                               le32_to_cpu(ir->type));
+               goto unm_err_out;
+       }
+       ni->itype.index.collation_rule = ir->collation_rule;
+       ntfs_debug("Index collation rule is 0x%x.",
+                       le32_to_cpu(ir->collation_rule));
+       ni->itype.index.block_size = le32_to_cpu(ir->index_block_size);
+       if (ni->itype.index.block_size & (ni->itype.index.block_size - 1)) {
+               ntfs_error(vi->i_sb, "Index block size (%u) is not a power of "
+                               "two.", ni->itype.index.block_size);
+               goto unm_err_out;
+       }
+       if (ni->itype.index.block_size > PAGE_CACHE_SIZE) {
+               ntfs_error(vi->i_sb, "Index block size (%u) > PAGE_CACHE_SIZE "
+                               "(%ld) is not supported.  Sorry.",
+                               ni->itype.index.block_size, PAGE_CACHE_SIZE);
+               err = -EOPNOTSUPP;
+               goto unm_err_out;
+       }
+       if (ni->itype.index.block_size < NTFS_BLOCK_SIZE) {
+               ntfs_error(vi->i_sb, "Index block size (%u) < NTFS_BLOCK_SIZE "
+                               "(%i) is not supported.  Sorry.",
+                               ni->itype.index.block_size, NTFS_BLOCK_SIZE);
+               err = -EOPNOTSUPP;
+               goto unm_err_out;
+       }
+       ni->itype.index.block_size_bits = ffs(ni->itype.index.block_size) - 1;
+       /* Determine the size of a vcn in the index. */
+       if (vol->cluster_size <= ni->itype.index.block_size) {
+               ni->itype.index.vcn_size = vol->cluster_size;
+               ni->itype.index.vcn_size_bits = vol->cluster_size_bits;
+       } else {
+               ni->itype.index.vcn_size = vol->sector_size;
+               ni->itype.index.vcn_size_bits = vol->sector_size_bits;
+       }
+       /* Check for presence of index allocation attribute. */
+       if (!(ir->index.flags & LARGE_INDEX)) {
+               /* No index allocation. */
+               vi->i_size = ni->initialized_size = ni->allocated_size = 0;
+               /* We are done with the mft record, so we release it. */
+               ntfs_attr_put_search_ctx(ctx);
+               unmap_mft_record(base_ni);
+               m = NULL;
+               ctx = NULL;
+               goto skip_large_index_stuff;
+       } /* LARGE_INDEX:  Index allocation present.  Setup state. */
+       NInoSetIndexAllocPresent(ni);
+       /* Find index allocation attribute. */
+       ntfs_attr_reinit_search_ctx(ctx);
+       err = ntfs_attr_lookup(AT_INDEX_ALLOCATION, ni->name, ni->name_len,
+                       CASE_SENSITIVE, 0, NULL, 0, ctx);
+       if (unlikely(err)) {
+               if (err == -ENOENT)
+                       ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is "
+                                       "not present but $INDEX_ROOT "
+                                       "indicated it is.");
+               else
+                       ntfs_error(vi->i_sb, "Failed to lookup "
+                                       "$INDEX_ALLOCATION attribute.");
+               goto unm_err_out;
+       }
+       a = ctx->attr;
+       if (!a->non_resident) {
+               ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is "
+                               "resident.");
+               goto unm_err_out;
+       }
+       /*
+        * Ensure the attribute name is placed before the mapping pairs array.
+        */
+       if (unlikely(a->name_length && (le16_to_cpu(a->name_offset) >=
+                       le16_to_cpu(
+                       a->data.non_resident.mapping_pairs_offset)))) {
+               ntfs_error(vol->sb, "$INDEX_ALLOCATION attribute name is "
+                               "placed after the mapping pairs array.");
+               goto unm_err_out;
+       }
+       if (a->flags & ATTR_IS_ENCRYPTED) {
+               ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is "
+                               "encrypted.");
+               goto unm_err_out;
+       }
+       if (a->flags & ATTR_IS_SPARSE) {
+               ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is sparse.");
+               goto unm_err_out;
+       }
+       if (a->flags & ATTR_COMPRESSION_MASK) {
+               ntfs_error(vi->i_sb, "$INDEX_ALLOCATION attribute is "
+                               "compressed.");
+               goto unm_err_out;
+       }
+       if (a->data.non_resident.lowest_vcn) {
+               ntfs_error(vi->i_sb, "First extent of $INDEX_ALLOCATION "
+                               "attribute has non zero lowest_vcn.");
+               goto unm_err_out;
+       }
+       vi->i_size = sle64_to_cpu(a->data.non_resident.data_size);
+       ni->initialized_size = sle64_to_cpu(
+                       a->data.non_resident.initialized_size);
+       ni->allocated_size = sle64_to_cpu(a->data.non_resident.allocated_size);
+       /*
+        * We are done with the mft record, so we release it.  Otherwise
+        * we would deadlock in ntfs_attr_iget().
+        */
+       ntfs_attr_put_search_ctx(ctx);
+       unmap_mft_record(base_ni);
+       m = NULL;
+       ctx = NULL;
+       /* Get the index bitmap attribute inode. */
+       bvi = ntfs_attr_iget(base_vi, AT_BITMAP, ni->name, ni->name_len);
+       if (IS_ERR(bvi)) {
+               ntfs_error(vi->i_sb, "Failed to get bitmap attribute.");
+               err = PTR_ERR(bvi);
+               goto unm_err_out;
+       }
+       bni = NTFS_I(bvi);
+       if (NInoCompressed(bni) || NInoEncrypted(bni) ||
+                       NInoSparse(bni)) {
+               ntfs_error(vi->i_sb, "$BITMAP attribute is compressed and/or "
+                               "encrypted and/or sparse.");
+               goto iput_unm_err_out;
+       }
+       /* Consistency check bitmap size vs. index allocation size. */
+       bvi_size = i_size_read(bvi);
+       if ((bvi_size << 3) < (vi->i_size >> ni->itype.index.block_size_bits)) {
+               ntfs_error(vi->i_sb, "Index bitmap too small (0x%llx) for "
+                               "index allocation (0x%llx).", bvi_size << 3,
+                               vi->i_size);
+               goto iput_unm_err_out;
+       }
+       iput(bvi);
+skip_large_index_stuff:
+       /* Setup the operations for this index inode. */
+       vi->i_op = NULL;
+       vi->i_fop = NULL;
+       vi->i_mapping->a_ops = &ntfs_mst_aops;
+       vi->i_blocks = ni->allocated_size >> 9;
+       /*
+        * Make sure the base inode doesn't go away and attach it to the
+        * index inode.
+        */
+       igrab(base_vi);
+       ni->ext.base_ntfs_ino = base_ni;
+       ni->nr_extents = -1;
+
+       ntfs_debug("Done.");
+       return 0;
+iput_unm_err_out:
+       iput(bvi);
+unm_err_out:
+       if (!err)
+               err = -EIO;
+       if (ctx)
+               ntfs_attr_put_search_ctx(ctx);
+       if (m)
+               unmap_mft_record(base_ni);
+err_out:
+       ntfs_error(vi->i_sb, "Failed with error code %i while reading index "
+                       "inode (mft_no 0x%lx, name_len %i.", err, vi->i_ino,
+                       ni->name_len);
+       make_bad_inode(vi);
+       if (err != -EOPNOTSUPP && err != -ENOMEM)
+               NVolSetErrors(vol);
+       return err;
+}
+
+/*
+ * The MFT inode has special locking, so teach the lock validator
+ * about this by splitting off the locking rules of the MFT from
+ * the locking rules of other inodes. The MFT inode can never be
+ * accessed from the VFS side (or even internally), only by the
+ * map_mft functions.
+ */
+static struct lock_class_key mft_ni_runlist_lock_key, mft_ni_mrec_lock_key;
+
+/**
+ * ntfs_read_inode_mount - special read_inode for mount time use only
+ * @vi:                inode to read
+ *
+ * Read inode FILE_MFT at mount time, only called with super_block lock
+ * held from within the read_super() code path.
+ *
+ * This function exists because when it is called the page cache for $MFT/$DATA
+ * is not initialized and hence we cannot get at the contents of mft records
+ * by calling map_mft_record*().
+ *
+ * Further it needs to cope with the circular references problem, i.e. cannot
+ * load any attributes other than $ATTRIBUTE_LIST until $DATA is loaded, because
+ * we do not know where the other extent mft records are yet and again, because
+ * we cannot call map_mft_record*() yet.  Obviously this applies only when an
+ * attribute list is actually present in $MFT inode.
+ *
+ * We solve these problems by starting with the $DATA attribute before anything
+ * else and iterating using ntfs_attr_lookup($DATA) over all extents.  As each
+ * extent is found, we ntfs_mapping_pairs_decompress() including the implied
+ * ntfs_runlists_merge().  Each step of the iteration necessarily provides
+ * sufficient information for the next step to complete.
+ *
+ * This should work but there are two possible pit falls (see inline comments
  * below), but only time will tell if they are real pits or just smoke...
  */
 int ntfs_read_inode_mount(struct inode *vi)
@@ -1322,8 +1778,8 @@ int ntfs_read_inode_mount(struct inode *vi)
        struct buffer_head *bh;
        ntfs_inode *ni;
        MFT_RECORD *m = NULL;
-       ATTR_RECORD *attr;
-       attr_search_context *ctx;
+       ATTR_RECORD *a;
+       ntfs_attr_search_ctx *ctx;
        unsigned int i, nr_blocks;
        int err;
 
@@ -1337,10 +1793,10 @@ int ntfs_read_inode_mount(struct inode *vi)
        /* Setup the data attribute. It is special as it is mst protected. */
        NInoSetNonResident(ni);
        NInoSetMstProtected(ni);
+       NInoSetSparseDisabled(ni);
        ni->type = AT_DATA;
        ni->name = NULL;
        ni->name_len = 0;
-
        /*
         * This sets up our little cheat allowing us to reuse the async read io
         * completion handler for directories.
@@ -1396,49 +1852,71 @@ int ntfs_read_inode_mount(struct inode *vi)
        vi->i_generation = ni->seq_no = le16_to_cpu(m->sequence_number);
 
        /* Provides readpage() and sync_page() for map_mft_record(). */
-       vi->i_mapping->a_ops = &ntfs_mft_aops;
+       vi->i_mapping->a_ops = &ntfs_mst_aops;
 
-       ctx = get_attr_search_ctx(ni, m);
+       ctx = ntfs_attr_get_search_ctx(ni, m);
        if (!ctx) {
                err = -ENOMEM;
                goto err_out;
        }
 
        /* Find the attribute list attribute if present. */
-       if (lookup_attr(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx)) {
+       err = ntfs_attr_lookup(AT_ATTRIBUTE_LIST, NULL, 0, 0, 0, NULL, 0, ctx);
+       if (err) {
+               if (unlikely(err != -ENOENT)) {
+                       ntfs_error(sb, "Failed to lookup attribute list "
+                                       "attribute. You should run chkdsk.");
+                       goto put_err_out;
+               }
+       } else /* if (!err) */ {
                ATTR_LIST_ENTRY *al_entry, *next_al_entry;
                u8 *al_end;
+               static const char *es = "  Not allowed.  $MFT is corrupt.  "
+                               "You should run chkdsk.";
 
                ntfs_debug("Attribute list attribute found in $MFT.");
                NInoSetAttrList(ni);
-               if (ctx->attr->flags & ATTR_IS_ENCRYPTED ||
-                               ctx->attr->flags & ATTR_COMPRESSION_MASK ||
-                               ctx->attr->flags & ATTR_IS_SPARSE) {
+               a = ctx->attr;
+               if (a->flags & ATTR_COMPRESSION_MASK) {
                        ntfs_error(sb, "Attribute list attribute is "
-                                       "compressed/encrypted/sparse. Not "
-                                       "allowed. $MFT is corrupt. You should "
-                                       "run chkdsk.");
+                                       "compressed.%s", es);
                        goto put_err_out;
                }
+               if (a->flags & ATTR_IS_ENCRYPTED ||
+                               a->flags & ATTR_IS_SPARSE) {
+                       if (a->non_resident) {
+                               ntfs_error(sb, "Non-resident attribute list "
+                                               "attribute is encrypted/"
+                                               "sparse.%s", es);
+                               goto put_err_out;
+                       }
+                       ntfs_warning(sb, "Resident attribute list attribute "
+                                       "in $MFT system file is marked "
+                                       "encrypted/sparse which is not true.  "
+                                       "However, Windows allows this and "
+                                       "chkdsk does not detect or correct it "
+                                       "so we will just ignore the invalid "
+                                       "flags and pretend they are not set.");
+               }
                /* Now allocate memory for the attribute list. */
-               ni->attr_list_size = (u32)attribute_value_length(ctx->attr);
+               ni->attr_list_size = (u32)ntfs_attr_size(a);
                ni->attr_list = ntfs_malloc_nofs(ni->attr_list_size);
                if (!ni->attr_list) {
                        ntfs_error(sb, "Not enough memory to allocate buffer "
                                        "for attribute list.");
                        goto put_err_out;
                }
-               if (ctx->attr->non_resident) {
+               if (a->non_resident) {
                        NInoSetAttrListNonResident(ni);
-                       if (ctx->attr->data.non_resident.lowest_vcn) {
+                       if (a->data.non_resident.lowest_vcn) {
                                ntfs_error(sb, "Attribute list has non zero "
                                                "lowest_vcn. $MFT is corrupt. "
                                                "You should run chkdsk.");
                                goto put_err_out;
                        }
-                       /* Setup the run list. */
-                       ni->attr_list_rl.rl = decompress_mapping_pairs(vol,
-                                       ctx->attr, NULL);
+                       /* Setup the runlist. */
+                       ni->attr_list_rl.rl = ntfs_mapping_pairs_decompress(vol,
+                                       a, NULL);
                        if (IS_ERR(ni->attr_list_rl.rl)) {
                                err = PTR_ERR(ni->attr_list_rl.rl);
                                ni->attr_list_rl.rl = NULL;
@@ -1450,7 +1928,7 @@ int ntfs_read_inode_mount(struct inode *vi)
                        /* Now load the attribute list. */
                        if ((err = load_attribute_list(vol, &ni->attr_list_rl,
                                        ni->attr_list, ni->attr_list_size,
-                                       sle64_to_cpu(ctx->attr->data.
+                                       sle64_to_cpu(a->data.
                                        non_resident.initialized_size)))) {
                                ntfs_error(sb, "Failed to load attribute list "
                                                "attribute with error code %i.",
@@ -1458,20 +1936,20 @@ int ntfs_read_inode_mount(struct inode *vi)
                                goto put_err_out;
                        }
                } else /* if (!ctx.attr->non_resident) */ {
-                       if ((u8*)ctx->attr + le16_to_cpu(
-                                       ctx->attr->data.resident.value_offset) +
+                       if ((u8*)a + le16_to_cpu(
+                                       a->data.resident.value_offset) +
                                        le32_to_cpu(
-                                       ctx->attr->data.resident.value_length) >
+                                       a->data.resident.value_length) >
                                        (u8*)ctx->mrec + vol->mft_record_size) {
                                ntfs_error(sb, "Corrupt attribute list "
                                                "attribute.");
                                goto put_err_out;
                        }
                        /* Now copy the attribute list. */
-                       memcpy(ni->attr_list, (u8*)ctx->attr + le16_to_cpu(
-                                       ctx->attr->data.resident.value_offset),
+                       memcpy(ni->attr_list, (u8*)a + le16_to_cpu(
+                                       a->data.resident.value_offset),
                                        le32_to_cpu(
-                                       ctx->attr->data.resident.value_length));
+                                       a->data.resident.value_length));
                }
                /* The attribute list is now setup in memory. */
                /*
@@ -1534,27 +2012,28 @@ int ntfs_read_inode_mount(struct inode *vi)
                }
        }
 
-       reinit_attr_search_ctx(ctx);
+       ntfs_attr_reinit_search_ctx(ctx);
 
        /* Now load all attribute extents. */
-       attr = NULL;
+       a = NULL;
        next_vcn = last_vcn = highest_vcn = 0;
-       while (lookup_attr(AT_DATA, NULL, 0, 0, next_vcn, NULL, 0, ctx)) {
-               run_list_element *nrl;
+       while (!(err = ntfs_attr_lookup(AT_DATA, NULL, 0, 0, next_vcn, NULL, 0,
+                       ctx))) {
+               runlist_element *nrl;
 
                /* Cache the current attribute. */
-               attr = ctx->attr;
+               a = ctx->attr;
                /* $MFT must be non-resident. */
-               if (!attr->non_resident) {
+               if (!a->non_resident) {
                        ntfs_error(sb, "$MFT must be non-resident but a "
                                        "resident extent was found. $MFT is "
                                        "corrupt. Run chkdsk.");
                        goto put_err_out;
                }
                /* $MFT must be uncompressed and unencrypted. */
-               if (attr->flags & ATTR_COMPRESSION_MASK ||
-                               attr->flags & ATTR_IS_ENCRYPTED ||
-                               attr->flags & ATTR_IS_SPARSE) {
+               if (a->flags & ATTR_COMPRESSION_MASK ||
+                               a->flags & ATTR_IS_ENCRYPTED ||
+                               a->flags & ATTR_IS_SPARSE) {
                        ntfs_error(sb, "$MFT must be uncompressed, "
                                        "non-sparse, and unencrypted but a "
                                        "compressed/sparse/encrypted extent "
@@ -1564,24 +2043,22 @@ int ntfs_read_inode_mount(struct inode *vi)
                }
                /*
                 * Decompress the mapping pairs array of this extent and merge
-                * the result into the existing run list. No need for locking
+                * the result into the existing runlist. No need for locking
                 * as we have exclusive access to the inode at this time and we
                 * are a mount in progress task, too.
                 */
-               nrl = decompress_mapping_pairs(vol, attr, ni->run_list.rl);
+               nrl = ntfs_mapping_pairs_decompress(vol, a, ni->runlist.rl);
                if (IS_ERR(nrl)) {
-                       ntfs_error(sb, "decompress_mapping_pairs() failed with "
-                                       "error code %ld. $MFT is corrupt.",
-                                       PTR_ERR(nrl));
+                       ntfs_error(sb, "ntfs_mapping_pairs_decompress() "
+                                       "failed with error code %ld.  $MFT is "
+                                       "corrupt.", PTR_ERR(nrl));
                        goto put_err_out;
                }
-               ni->run_list.rl = nrl;
+               ni->runlist.rl = nrl;
 
                /* Are we in the first extent? */
                if (!next_vcn) {
-                       u64 ll;
-
-                       if (attr->data.non_resident.lowest_vcn) {
+                       if (a->data.non_resident.lowest_vcn) {
                                ntfs_error(sb, "First extent of $DATA "
                                                "attribute has non zero "
                                                "lowest_vcn. $MFT is corrupt. "
@@ -1590,28 +2067,26 @@ int ntfs_read_inode_mount(struct inode *vi)
                        }
                        /* Get the last vcn in the $DATA attribute. */
                        last_vcn = sle64_to_cpu(
-                                       attr->data.non_resident.allocated_size)
+                                       a->data.non_resident.allocated_size)
                                        >> vol->cluster_size_bits;
                        /* Fill in the inode size. */
                        vi->i_size = sle64_to_cpu(
-                                       attr->data.non_resident.data_size);
-                       ni->initialized_size = sle64_to_cpu(attr->data.
-                                       non_resident.initialized_size);
+                                       a->data.non_resident.data_size);
+                       ni->initialized_size = sle64_to_cpu(
+                                       a->data.non_resident.initialized_size);
                        ni->allocated_size = sle64_to_cpu(
-                                       attr->data.non_resident.allocated_size);
-                       /* Set the number of mft records. */
-                       ll = vi->i_size >> vol->mft_record_size_bits;
+                                       a->data.non_resident.allocated_size);
                        /*
                         * Verify the number of mft records does not exceed
                         * 2^32 - 1.
                         */
-                       if (ll >= (1ULL << 32)) {
+                       if ((vi->i_size >> vol->mft_record_size_bits) >=
+                                       (1ULL << 32)) {
                                ntfs_error(sb, "$MFT is too big! Aborting.");
                                goto put_err_out;
                        }
-                       vol->nr_mft_records = ll;
                        /*
-                        * We have got the first extent of the run_list for
+                        * We have got the first extent of the runlist for
                         * $MFT which means it is now relatively safe to call
                         * the normal ntfs_read_inode() function.
                         * Complete reading the inode, this will actually
@@ -1638,7 +2113,7 @@ int ntfs_read_inode_mount(struct inode *vi)
                                                "saw this message to "
                                                "linux-ntfs-dev@lists."
                                                "sourceforge.net");
-                               put_attr_search_ctx(ctx);
+                               ntfs_attr_put_search_ctx(ctx);
                                /* Revert to the safe super operations. */
                                ntfs_free(m);
                                return -1;
@@ -1654,12 +2129,10 @@ int ntfs_read_inode_mount(struct inode *vi)
                        /* No VFS initiated operations allowed for $MFT. */
                        vi->i_op = &ntfs_empty_inode_ops;
                        vi->i_fop = &ntfs_empty_file_ops;
-                       /* Put back our special address space operations. */
-                       vi->i_mapping->a_ops = &ntfs_mft_aops;
                }
 
                /* Get the lowest vcn for the next extent. */
-               highest_vcn = sle64_to_cpu(attr->data.non_resident.highest_vcn);
+               highest_vcn = sle64_to_cpu(a->data.non_resident.highest_vcn);
                next_vcn = highest_vcn + 1;
 
                /* Only one extent or error, which we catch below. */
@@ -1668,36 +2141,49 @@ int ntfs_read_inode_mount(struct inode *vi)
 
                /* Avoid endless loops due to corruption. */
                if (next_vcn < sle64_to_cpu(
-                               attr->data.non_resident.lowest_vcn)) {
+                               a->data.non_resident.lowest_vcn)) {
                        ntfs_error(sb, "$MFT has corrupt attribute list "
                                        "attribute. Run chkdsk.");
                        goto put_err_out;
                }
        }
-       if (!attr) {
+       if (err != -ENOENT) {
+               ntfs_error(sb, "Failed to lookup $MFT/$DATA attribute extent. "
+                               "$MFT is corrupt. Run chkdsk.");
+               goto put_err_out;
+       }
+       if (!a) {
                ntfs_error(sb, "$MFT/$DATA attribute not found. $MFT is "
                                "corrupt. Run chkdsk.");
                goto put_err_out;
        }
        if (highest_vcn && highest_vcn != last_vcn - 1) {
-               ntfs_error(sb, "Failed to load the complete run list "
-                               "for $MFT/$DATA. Driver bug or "
-                               "corrupt $MFT. Run chkdsk.");
+               ntfs_error(sb, "Failed to load the complete runlist for "
+                               "$MFT/$DATA. Driver bug or corrupt $MFT. "
+                               "Run chkdsk.");
                ntfs_debug("highest_vcn = 0x%llx, last_vcn - 1 = 0x%llx",
                                (unsigned long long)highest_vcn,
                                (unsigned long long)last_vcn - 1);
                goto put_err_out;
        }
-       put_attr_search_ctx(ctx);
+       ntfs_attr_put_search_ctx(ctx);
        ntfs_debug("Done.");
        ntfs_free(m);
+
+       /*
+        * Split the locking rules of the MFT inode from the
+        * locking rules of other inodes:
+        */
+       lockdep_set_class(&ni->runlist.lock, &mft_ni_runlist_lock_key);
+       lockdep_set_class(&ni->mrec_lock, &mft_ni_mrec_lock_key);
+
        return 0;
 
 em_put_err_out:
        ntfs_error(sb, "Couldn't find first extent of $DATA attribute in "
                        "attribute list. $MFT is corrupt. Run chkdsk.");
 put_err_out:
-       put_attr_search_ctx(ctx);
+       ntfs_attr_put_search_ctx(ctx);
 err_out:
        ntfs_error(sb, "Failed. Marking inode as bad.");
        make_bad_inode(vi);
@@ -1705,41 +2191,15 @@ err_out:
        return -1;
 }
 
-/**
- * ntfs_put_inode - handler for when the inode reference count is decremented
- * @vi:                vfs inode
- *
- * The VFS calls ntfs_put_inode() every time the inode reference count (i_count)
- * is about to be decremented (but before the decrement itself.
- *
- * If the inode @vi is a directory with a single reference, we need to put the
- * attribute inode for the directory index bitmap, if it is present, otherwise
- * the directory inode would remain pinned for ever (or rather until umount()
- * time.
- */
-void ntfs_put_inode(struct inode *vi)
-{
-       if (S_ISDIR(vi->i_mode) && (atomic_read(&vi->i_count) == 2)) {
-               ntfs_inode *ni;
-
-               ni = NTFS_I(vi);
-               if (NInoIndexAllocPresent(ni) && ni->itype.index.bmp_ino) {
-                       iput(ni->itype.index.bmp_ino);
-                       ni->itype.index.bmp_ino = NULL;
-               }
-       }
-       return;
-}
-
-void __ntfs_clear_inode(ntfs_inode *ni)
+static void __ntfs_clear_inode(ntfs_inode *ni)
 {
        /* Free all alocated memory. */
-       down_write(&ni->run_list.lock);
-       if (ni->run_list.rl) {
-               ntfs_free(ni->run_list.rl);
-               ni->run_list.rl = NULL;
+       down_write(&ni->runlist.lock);
+       if (ni->runlist.rl) {
+               ntfs_free(ni->runlist.rl);
+               ni->runlist.rl = NULL;
        }
-       up_write(&ni->run_list.lock);
+       up_write(&ni->runlist.lock);
 
        if (ni->attr_list) {
                ntfs_free(ni->attr_list);
@@ -1798,7 +2258,7 @@ void ntfs_clear_big_inode(struct inode *vi)
 
 #ifdef NTFS_RW
        if (NInoDirty(ni)) {
-               BOOL was_bad = (is_bad_inode(vi));
+               bool was_bad = (is_bad_inode(vi));
 
                /* Committing the inode also commits all extent inodes. */
                ntfs_commit_inode(vi);
@@ -1861,6 +2321,8 @@ int ntfs_show_options(struct seq_file *sf, struct vfsmount *mnt)
                seq_printf(sf, ",case_sensitive");
        if (NVolShowSystemFiles(vol))
                seq_printf(sf, ",show_sys_files");
+       if (!NVolSparseEnabled(vol))
+               seq_printf(sf, ",disable_sparse");
        for (i = 0; on_errors_arr[i].val; i++) {
                if (on_errors_arr[i].val & vol->on_errors)
                        seq_printf(sf, ",errors=%s", on_errors_arr[i].str);
@@ -1871,23 +2333,543 @@ int ntfs_show_options(struct seq_file *sf, struct vfsmount *mnt)
 
 #ifdef NTFS_RW
 
+static const char *es = "  Leaving inconsistent metadata.  Unmount and run "
+               "chkdsk.";
+
 /**
  * ntfs_truncate - called when the i_size of an ntfs inode is changed
  * @vi:                inode for which the i_size was changed
  *
- * We don't support i_size changes yet.
+ * We only support i_size changes for normal files at present, i.e. not
+ * compressed and not encrypted.  This is enforced in ntfs_setattr(), see
+ * below.
  *
- * Called with ->i_sem held.
+ * The kernel guarantees that @vi is a regular file (S_ISREG() is true) and
+ * that the change is allowed.
+ *
+ * This implies for us that @vi is a file inode rather than a directory, index,
+ * or attribute inode as well as that @vi is a base inode.
+ *
+ * Returns 0 on success or -errno on error.
+ *
+ * Called with ->i_mutex held.  In all but one case ->i_alloc_sem is held for
+ * writing.  The only case in the kernel where ->i_alloc_sem is not held is
+ * mm/filemap.c::generic_file_buffered_write() where vmtruncate() is called
+ * with the current i_size as the offset.  The analogous place in NTFS is in
+ * fs/ntfs/file.c::ntfs_file_buffered_write() where we call vmtruncate() again
+ * without holding ->i_alloc_sem.
  */
-void ntfs_truncate(struct inode *vi)
+int ntfs_truncate(struct inode *vi)
 {
-       // TODO: Implement...
-       ntfs_warning(vi->i_sb, "Eeek: i_size may have changed! If you see "
-                       "this right after a message from "
-                       "ntfs_{prepare,commit}_{,nonresident_}write() then "
-                       "just ignore it. Otherwise it is bad news.");
-       // TODO: reset i_size now!
-       return;
+       s64 new_size, old_size, nr_freed, new_alloc_size, old_alloc_size;
+       VCN highest_vcn;
+       unsigned long flags;
+       ntfs_inode *base_ni, *ni = NTFS_I(vi);
+       ntfs_volume *vol = ni->vol;
+       ntfs_attr_search_ctx *ctx;
+       MFT_RECORD *m;
+       ATTR_RECORD *a;
+       const char *te = "  Leaving file length out of sync with i_size.";
+       int err, mp_size, size_change, alloc_change;
+       u32 attr_len;
+
+       ntfs_debug("Entering for inode 0x%lx.", vi->i_ino);
+       BUG_ON(NInoAttr(ni));
+       BUG_ON(S_ISDIR(vi->i_mode));
+       BUG_ON(NInoMstProtected(ni));
+       BUG_ON(ni->nr_extents < 0);
+retry_truncate:
+       /*
+        * Lock the runlist for writing and map the mft record to ensure it is
+        * safe to mess with the attribute runlist and sizes.
+        */
+       down_write(&ni->runlist.lock);
+       if (!NInoAttr(ni))
+               base_ni = ni;
+       else
+               base_ni = ni->ext.base_ntfs_ino;
+       m = map_mft_record(base_ni);
+       if (IS_ERR(m)) {
+               err = PTR_ERR(m);
+               ntfs_error(vi->i_sb, "Failed to map mft record for inode 0x%lx "
+                               "(error code %d).%s", vi->i_ino, err, te);
+               ctx = NULL;
+               m = NULL;
+               goto old_bad_out;
+       }
+       ctx = ntfs_attr_get_search_ctx(base_ni, m);
+       if (unlikely(!ctx)) {
+               ntfs_error(vi->i_sb, "Failed to allocate a search context for "
+                               "inode 0x%lx (not enough memory).%s",
+                               vi->i_ino, te);
+               err = -ENOMEM;
+               goto old_bad_out;
+       }
+       err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
+                       CASE_SENSITIVE, 0, NULL, 0, ctx);
+       if (unlikely(err)) {
+               if (err == -ENOENT) {
+                       ntfs_error(vi->i_sb, "Open attribute is missing from "
+                                       "mft record.  Inode 0x%lx is corrupt.  "
+                                       "Run chkdsk.%s", vi->i_ino, te);
+                       err = -EIO;
+               } else
+                       ntfs_error(vi->i_sb, "Failed to lookup attribute in "
+                                       "inode 0x%lx (error code %d).%s",
+                                       vi->i_ino, err, te);
+               goto old_bad_out;
+       }
+       m = ctx->mrec;
+       a = ctx->attr;
+       /*
+        * The i_size of the vfs inode is the new size for the attribute value.
+        */
+       new_size = i_size_read(vi);
+       /* The current size of the attribute value is the old size. */
+       old_size = ntfs_attr_size(a);
+       /* Calculate the new allocated size. */
+       if (NInoNonResident(ni))
+               new_alloc_size = (new_size + vol->cluster_size - 1) &
+                               ~(s64)vol->cluster_size_mask;
+       else
+               new_alloc_size = (new_size + 7) & ~7;
+       /* The current allocated size is the old allocated size. */
+       read_lock_irqsave(&ni->size_lock, flags);
+       old_alloc_size = ni->allocated_size;
+       read_unlock_irqrestore(&ni->size_lock, flags);
+       /*
+        * The change in the file size.  This will be 0 if no change, >0 if the
+        * size is growing, and <0 if the size is shrinking.
+        */
+       size_change = -1;
+       if (new_size - old_size >= 0) {
+               size_change = 1;
+               if (new_size == old_size)
+                       size_change = 0;
+       }
+       /* As above for the allocated size. */
+       alloc_change = -1;
+       if (new_alloc_size - old_alloc_size >= 0) {
+               alloc_change = 1;
+               if (new_alloc_size == old_alloc_size)
+                       alloc_change = 0;
+       }
+       /*
+        * If neither the size nor the allocation are being changed there is
+        * nothing to do.
+        */
+       if (!size_change && !alloc_change)
+               goto unm_done;
+       /* If the size is changing, check if new size is allowed in $AttrDef. */
+       if (size_change) {
+               err = ntfs_attr_size_bounds_check(vol, ni->type, new_size);
+               if (unlikely(err)) {
+                       if (err == -ERANGE) {
+                               ntfs_error(vol->sb, "Truncate would cause the "
+                                               "inode 0x%lx to %simum size "
+                                               "for its attribute type "
+                                               "(0x%x).  Aborting truncate.",
+                                               vi->i_ino,
+                                               new_size > old_size ? "exceed "
+                                               "the max" : "go under the min",
+                                               le32_to_cpu(ni->type));
+                               err = -EFBIG;
+                       } else {
+                               ntfs_error(vol->sb, "Inode 0x%lx has unknown "
+                                               "attribute type 0x%x.  "
+                                               "Aborting truncate.",
+                                               vi->i_ino,
+                                               le32_to_cpu(ni->type));
+                               err = -EIO;
+                       }
+                       /* Reset the vfs inode size to the old size. */
+                       i_size_write(vi, old_size);
+                       goto err_out;
+               }
+       }
+       if (NInoCompressed(ni) || NInoEncrypted(ni)) {
+               ntfs_warning(vi->i_sb, "Changes in inode size are not "
+                               "supported yet for %s files, ignoring.",
+                               NInoCompressed(ni) ? "compressed" :
+                               "encrypted");
+               err = -EOPNOTSUPP;
+               goto bad_out;
+       }
+       if (a->non_resident)
+               goto do_non_resident_truncate;
+       BUG_ON(NInoNonResident(ni));
+       /* Resize the attribute record to best fit the new attribute size. */
+       if (new_size < vol->mft_record_size &&
+                       !ntfs_resident_attr_value_resize(m, a, new_size)) {
+               unsigned long flags;
+
+               /* The resize succeeded! */
+               flush_dcache_mft_record_page(ctx->ntfs_ino);
+               mark_mft_record_dirty(ctx->ntfs_ino);
+               write_lock_irqsave(&ni->size_lock, flags);
+               /* Update the sizes in the ntfs inode and all is done. */
+               ni->allocated_size = le32_to_cpu(a->length) -
+                               le16_to_cpu(a->data.resident.value_offset);
+               /*
+                * Note ntfs_resident_attr_value_resize() has already done any
+                * necessary data clearing in the attribute record.  When the
+                * file is being shrunk vmtruncate() will already have cleared
+                * the top part of the last partial page, i.e. since this is
+                * the resident case this is the page with index 0.  However,
+                * when the file is being expanded, the page cache page data
+                * between the old data_size, i.e. old_size, and the new_size
+                * has not been zeroed.  Fortunately, we do not need to zero it
+                * either since on one hand it will either already be zero due
+                * to both readpage and writepage clearing partial page data
+                * beyond i_size in which case there is nothing to do or in the
+                * case of the file being mmap()ped at the same time, POSIX
+                * specifies that the behaviour is unspecified thus we do not
+                * have to do anything.  This means that in our implementation
+                * in the rare case that the file is mmap()ped and a write
+                * occured into the mmap()ped region just beyond the file size
+                * and writepage has not yet been called to write out the page
+                * (which would clear the area beyond the file size) and we now
+                * extend the file size to incorporate this dirty region
+                * outside the file size, a write of the page would result in
+                * this data being written to disk instead of being cleared.
+                * Given both POSIX and the Linux mmap(2) man page specify that
+                * this corner case is undefined, we choose to leave it like
+                * that as this is much simpler for us as we cannot lock the
+                * relevant page now since we are holding too many ntfs locks
+                * which would result in a lock reversal deadlock.
+                */
+               ni->initialized_size = new_size;
+               write_unlock_irqrestore(&ni->size_lock, flags);
+               goto unm_done;
+       }
+       /* If the above resize failed, this must be an attribute extension. */
+       BUG_ON(size_change < 0);
+       /*
+        * We have to drop all the locks so we can call
+        * ntfs_attr_make_non_resident().  This could be optimised by try-
+        * locking the first page cache page and only if that fails dropping
+        * the locks, locking the page, and redoing all the locking and
+        * lookups.  While this would be a huge optimisation, it is not worth
+        * it as this is definitely a slow code path as it only ever can happen
+        * once for any given file.
+        */
+       ntfs_attr_put_search_ctx(ctx);
+       unmap_mft_record(base_ni);
+       up_write(&ni->runlist.lock);
+       /*
+        * Not enough space in the mft record, try to make the attribute
+        * non-resident and if successful restart the truncation process.
+        */
+       err = ntfs_attr_make_non_resident(ni, old_size);
+       if (likely(!err))
+               goto retry_truncate;
+       /*
+        * Could not make non-resident.  If this is due to this not being
+        * permitted for this attribute type or there not being enough space,
+        * try to make other attributes non-resident.  Otherwise fail.
+        */
+       if (unlikely(err != -EPERM && err != -ENOSPC)) {
+               ntfs_error(vol->sb, "Cannot truncate inode 0x%lx, attribute "
+                               "type 0x%x, because the conversion from "
+                               "resident to non-resident attribute failed "
+                               "with error code %i.", vi->i_ino,
+                               (unsigned)le32_to_cpu(ni->type), err);
+               if (err != -ENOMEM)
+                       err = -EIO;
+               goto conv_err_out;
+       }
+       /* TODO: Not implemented from here, abort. */
+       if (err == -ENOSPC)
+               ntfs_error(vol->sb, "Not enough space in the mft record/on "
+                               "disk for the non-resident attribute value.  "
+                               "This case is not implemented yet.");
+       else /* if (err == -EPERM) */
+               ntfs_error(vol->sb, "This attribute type may not be "
+                               "non-resident.  This case is not implemented "
+                               "yet.");
+       err = -EOPNOTSUPP;
+       goto conv_err_out;
+#if 0
+       // TODO: Attempt to make other attributes non-resident.
+       if (!err)
+               goto do_resident_extend;
+       /*
+        * Both the attribute list attribute and the standard information
+        * attribute must remain in the base inode.  Thus, if this is one of
+        * these attributes, we have to try to move other attributes out into
+        * extent mft records instead.
+        */
+       if (ni->type == AT_ATTRIBUTE_LIST ||
+                       ni->type == AT_STANDARD_INFORMATION) {
+               // TODO: Attempt to move other attributes into extent mft
+               // records.
+               err = -EOPNOTSUPP;
+               if (!err)
+                       goto do_resident_extend;
+               goto err_out;
+       }
+       // TODO: Attempt to move this attribute to an extent mft record, but
+       // only if it is not already the only attribute in an mft record in
+       // which case there would be nothing to gain.
+       err = -EOPNOTSUPP;
+       if (!err)
+               goto do_resident_extend;
+       /* There is nothing we can do to make enough space. )-: */
+       goto err_out;
+#endif
+do_non_resident_truncate:
+       BUG_ON(!NInoNonResident(ni));
+       if (alloc_change < 0) {
+               highest_vcn = sle64_to_cpu(a->data.non_resident.highest_vcn);
+               if (highest_vcn > 0 &&
+                               old_alloc_size >> vol->cluster_size_bits >
+                               highest_vcn + 1) {
+                       /*
+                        * This attribute has multiple extents.  Not yet
+                        * supported.
+                        */
+                       ntfs_error(vol->sb, "Cannot truncate inode 0x%lx, "
+                                       "attribute type 0x%x, because the "
+                                       "attribute is highly fragmented (it "
+                                       "consists of multiple extents) and "
+                                       "this case is not implemented yet.",
+                                       vi->i_ino,
+                                       (unsigned)le32_to_cpu(ni->type));
+                       err = -EOPNOTSUPP;
+                       goto bad_out;
+               }
+       }
+       /*
+        * If the size is shrinking, need to reduce the initialized_size and
+        * the data_size before reducing the allocation.
+        */
+       if (size_change < 0) {
+               /*
+                * Make the valid size smaller (i_size is already up-to-date).
+                */
+               write_lock_irqsave(&ni->size_lock, flags);
+               if (new_size < ni->initialized_size) {
+                       ni->initialized_size = new_size;
+                       a->data.non_resident.initialized_size =
+                                       cpu_to_sle64(new_size);
+               }
+               a->data.non_resident.data_size = cpu_to_sle64(new_size);
+               write_unlock_irqrestore(&ni->size_lock, flags);
+               flush_dcache_mft_record_page(ctx->ntfs_ino);
+               mark_mft_record_dirty(ctx->ntfs_ino);
+               /* If the allocated size is not changing, we are done. */
+               if (!alloc_change)
+                       goto unm_done;
+               /*
+                * If the size is shrinking it makes no sense for the
+                * allocation to be growing.
+                */
+               BUG_ON(alloc_change > 0);
+       } else /* if (size_change >= 0) */ {
+               /*
+                * The file size is growing or staying the same but the
+                * allocation can be shrinking, growing or staying the same.
+                */
+               if (alloc_change > 0) {
+                       /*
+                        * We need to extend the allocation and possibly update
+                        * the data size.  If we are updating the data size,
+                        * since we are not touching the initialized_size we do
+                        * not need to worry about the actual data on disk.
+                        * And as far as the page cache is concerned, there
+                        * will be no pages beyond the old data size and any
+                        * partial region in the last page between the old and
+                        * new data size (or the end of the page if the new
+                        * data size is outside the page) does not need to be
+                        * modified as explained above for the resident
+                        * attribute truncate case.  To do this, we simply drop
+                        * the locks we hold and leave all the work to our
+                        * friendly helper ntfs_attr_extend_allocation().
+                        */
+                       ntfs_attr_put_search_ctx(ctx);
+                       unmap_mft_record(base_ni);
+                       up_write(&ni->runlist.lock);
+                       err = ntfs_attr_extend_allocation(ni, new_size,
+                                       size_change > 0 ? new_size : -1, -1);
+                       /*
+                        * ntfs_attr_extend_allocation() will have done error
+                        * output already.
+                        */
+                       goto done;
+               }
+               if (!alloc_change)
+                       goto alloc_done;
+       }
+       /* alloc_change < 0 */
+       /* Free the clusters. */
+       nr_freed = ntfs_cluster_free(ni, new_alloc_size >>
+                       vol->cluster_size_bits, -1, ctx);
+       m = ctx->mrec;
+       a = ctx->attr;
+       if (unlikely(nr_freed < 0)) {
+               ntfs_error(vol->sb, "Failed to release cluster(s) (error code "
+                               "%lli).  Unmount and run chkdsk to recover "
+                               "the lost cluster(s).", (long long)nr_freed);
+               NVolSetErrors(vol);
+               nr_freed = 0;
+       }
+       /* Truncate the runlist. */
+       err = ntfs_rl_truncate_nolock(vol, &ni->runlist,
+                       new_alloc_size >> vol->cluster_size_bits);
+       /*
+        * If the runlist truncation failed and/or the search context is no
+        * longer valid, we cannot resize the attribute record or build the
+        * mapping pairs array thus we mark the inode bad so that no access to
+        * the freed clusters can happen.
+        */
+       if (unlikely(err || IS_ERR(m))) {
+               ntfs_error(vol->sb, "Failed to %s (error code %li).%s",
+                               IS_ERR(m) ?
+                               "restore attribute search context" :
+                               "truncate attribute runlist",
+                               IS_ERR(m) ? PTR_ERR(m) : err, es);
+               err = -EIO;
+               goto bad_out;
+       }
+       /* Get the size for the shrunk mapping pairs array for the runlist. */
+       mp_size = ntfs_get_size_for_mapping_pairs(vol, ni->runlist.rl, 0, -1);
+       if (unlikely(mp_size <= 0)) {
+               ntfs_error(vol->sb, "Cannot shrink allocation of inode 0x%lx, "
+                               "attribute type 0x%x, because determining the "
+                               "size for the mapping pairs failed with error "
+                               "code %i.%s", vi->i_ino,
+                               (unsigned)le32_to_cpu(ni->type), mp_size, es);
+               err = -EIO;
+               goto bad_out;
+       }
+       /*
+        * Shrink the attribute record for the new mapping pairs array.  Note,
+        * this cannot fail since we are making the attribute smaller thus by
+        * definition there is enough space to do so.
+        */
+       attr_len = le32_to_cpu(a->length);
+       err = ntfs_attr_record_resize(m, a, mp_size +
+                       le16_to_cpu(a->data.non_resident.mapping_pairs_offset));
+       BUG_ON(err);
+       /*
+        * Generate the mapping pairs array directly into the attribute record.
+        */
+       err = ntfs_mapping_pairs_build(vol, (u8*)a +
+                       le16_to_cpu(a->data.non_resident.mapping_pairs_offset),
+                       mp_size, ni->runlist.rl, 0, -1, NULL);
+       if (unlikely(err)) {
+               ntfs_error(vol->sb, "Cannot shrink allocation of inode 0x%lx, "
+                               "attribute type 0x%x, because building the "
+                               "mapping pairs failed with error code %i.%s",
+                               vi->i_ino, (unsigned)le32_to_cpu(ni->type),
+                               err, es);
+               err = -EIO;
+               goto bad_out;
+       }
+       /* Update the allocated/compressed size as well as the highest vcn. */
+       a->data.non_resident.highest_vcn = cpu_to_sle64((new_alloc_size >>
+                       vol->cluster_size_bits) - 1);
+       write_lock_irqsave(&ni->size_lock, flags);
+       ni->allocated_size = new_alloc_size;
+       a->data.non_resident.allocated_size = cpu_to_sle64(new_alloc_size);
+       if (NInoSparse(ni) || NInoCompressed(ni)) {
+               if (nr_freed) {
+                       ni->itype.compressed.size -= nr_freed <<
+                                       vol->cluster_size_bits;
+                       BUG_ON(ni->itype.compressed.size < 0);
+                       a->data.non_resident.compressed_size = cpu_to_sle64(
+                                       ni->itype.compressed.size);
+                       vi->i_blocks = ni->itype.compressed.size >> 9;
+               }
+       } else
+               vi->i_blocks = new_alloc_size >> 9;
+       write_unlock_irqrestore(&ni->size_lock, flags);
+       /*
+        * We have shrunk the allocation.  If this is a shrinking truncate we
+        * have already dealt with the initialized_size and the data_size above
+        * and we are done.  If the truncate is only changing the allocation
+        * and not the data_size, we are also done.  If this is an extending
+        * truncate, need to extend the data_size now which is ensured by the
+        * fact that @size_change is positive.
+        */
+alloc_done:
+       /*
+        * If the size is growing, need to update it now.  If it is shrinking,
+        * we have already updated it above (before the allocation change).
+        */
+       if (size_change > 0)
+               a->data.non_resident.data_size = cpu_to_sle64(new_size);
+       /* Ensure the modified mft record is written out. */
+       flush_dcache_mft_record_page(ctx->ntfs_ino);
+       mark_mft_record_dirty(ctx->ntfs_ino);
+unm_done:
+       ntfs_attr_put_search_ctx(ctx);
+       unmap_mft_record(base_ni);
+       up_write(&ni->runlist.lock);
+done:
+       /* Update the mtime and ctime on the base inode. */
+       /* normally ->truncate shouldn't update ctime or mtime,
+        * but ntfs did before so it got a copy & paste version
+        * of file_update_time.  one day someone should fix this
+        * for real.
+        */
+       if (!IS_NOCMTIME(VFS_I(base_ni)) && !IS_RDONLY(VFS_I(base_ni))) {
+               struct timespec now = current_fs_time(VFS_I(base_ni)->i_sb);
+               int sync_it = 0;
+
+               if (!timespec_equal(&VFS_I(base_ni)->i_mtime, &now) ||
+                   !timespec_equal(&VFS_I(base_ni)->i_ctime, &now))
+                       sync_it = 1;
+               VFS_I(base_ni)->i_mtime = now;
+               VFS_I(base_ni)->i_ctime = now;
+
+               if (sync_it)
+                       mark_inode_dirty_sync(VFS_I(base_ni));
+       }
+
+       if (likely(!err)) {
+               NInoClearTruncateFailed(ni);
+               ntfs_debug("Done.");
+       }
+       return err;
+old_bad_out:
+       old_size = -1;
+bad_out:
+       if (err != -ENOMEM && err != -EOPNOTSUPP)
+               NVolSetErrors(vol);
+       if (err != -EOPNOTSUPP)
+               NInoSetTruncateFailed(ni);
+       else if (old_size >= 0)
+               i_size_write(vi, old_size);
+err_out:
+       if (ctx)
+               ntfs_attr_put_search_ctx(ctx);
+       if (m)
+               unmap_mft_record(base_ni);
+       up_write(&ni->runlist.lock);
+out:
+       ntfs_debug("Failed.  Returning error code %i.", err);
+       return err;
+conv_err_out:
+       if (err != -ENOMEM && err != -EOPNOTSUPP)
+               NVolSetErrors(vol);
+       if (err != -EOPNOTSUPP)
+               NInoSetTruncateFailed(ni);
+       else
+               i_size_write(vi, old_size);
+       goto out;
+}
+
+/**
+ * ntfs_truncate_vfs - wrapper for ntfs_truncate() that has no return value
+ * @vi:                inode for which the i_size was changed
+ *
+ * Wrapper for ntfs_truncate() that has no return value.
+ *
+ * See ntfs_truncate() description above for details.
+ */
+void ntfs_truncate_vfs(struct inode *vi) {
+       ntfs_truncate(vi);
 }
 
 /**
@@ -1896,67 +2878,71 @@ void ntfs_truncate(struct inode *vi)
  * @attr:      structure describing the attributes and the changes
  *
  * We have to trap VFS attempts to truncate the file described by @dentry as
- * soon as possible, because we do not implement changes in i_size yet. So we
+ * soon as possible, because we do not implement changes in i_size yet.  So we
  * abort all i_size changes here.
  *
- * Called with ->i_sem held.
+ * We also abort all changes of user, group, and mode as we do not implement
+ * the NTFS ACLs yet.
+ *
+ * Called with ->i_mutex held.  For the ATTR_SIZE (i.e. ->truncate) case, also
+ * called with ->i_alloc_sem held for writing.
  *
  * Basically this is a copy of generic notify_change() and inode_setattr()
  * functionality, except we intercept and abort changes in i_size.
  */
 int ntfs_setattr(struct dentry *dentry, struct iattr *attr)
 {
-       struct inode *vi;
+       struct inode *vi = dentry->d_inode;
        int err;
        unsigned int ia_valid = attr->ia_valid;
 
-       vi = dentry->d_inode;
-
        err = inode_change_ok(vi, attr);
        if (err)
-               return err;
-
-       if ((ia_valid & ATTR_UID && attr->ia_uid != vi->i_uid) ||
-                       (ia_valid & ATTR_GID && attr->ia_gid != vi->i_gid)) {
-               err = DQUOT_TRANSFER(vi, attr) ? -EDQUOT : 0;
-               if (err)
-                       return err;
+               goto out;
+       /* We do not support NTFS ACLs yet. */
+       if (ia_valid & (ATTR_UID | ATTR_GID | ATTR_MODE)) {
+               ntfs_warning(vi->i_sb, "Changes in user/group/mode are not "
+                               "supported yet, ignoring.");
+               err = -EOPNOTSUPP;
+               goto out;
        }
-
-       lock_kernel();
-
        if (ia_valid & ATTR_SIZE) {
-               ntfs_error(vi->i_sb, "Changes in i_size are not supported "
-                               "yet. Sorry.");
-               // TODO: Implement...
-               // err = vmtruncate(vi, attr->ia_size);
-               err = -EOPNOTSUPP;
-               if (err)
-                       goto trunc_err;
+               if (attr->ia_size != i_size_read(vi)) {
+                       ntfs_inode *ni = NTFS_I(vi);
+                       /*
+                        * FIXME: For now we do not support resizing of
+                        * compressed or encrypted files yet.
+                        */
+                       if (NInoCompressed(ni) || NInoEncrypted(ni)) {
+                               ntfs_warning(vi->i_sb, "Changes in inode size "
+                                               "are not supported yet for "
+                                               "%s files, ignoring.",
+                                               NInoCompressed(ni) ?
+                                               "compressed" : "encrypted");
+                               err = -EOPNOTSUPP;
+                       } else
+                               err = vmtruncate(vi, attr->ia_size);
+                       if (err || ia_valid == ATTR_SIZE)
+                               goto out;
+               } else {
+                       /*
+                        * We skipped the truncate but must still update
+                        * timestamps.
+                        */
+                       ia_valid |= ATTR_MTIME | ATTR_CTIME;
+               }
        }
-
-       if (ia_valid & ATTR_UID)
-               vi->i_uid = attr->ia_uid;
-       if (ia_valid & ATTR_GID)
-               vi->i_gid = attr->ia_gid;
        if (ia_valid & ATTR_ATIME)
-               vi->i_atime = attr->ia_atime;
+               vi->i_atime = timespec_trunc(attr->ia_atime,
+                               vi->i_sb->s_time_gran);
        if (ia_valid & ATTR_MTIME)
-               vi->i_mtime = attr->ia_mtime;
+               vi->i_mtime = timespec_trunc(attr->ia_mtime,
+                               vi->i_sb->s_time_gran);
        if (ia_valid & ATTR_CTIME)
-               vi->i_ctime = attr->ia_ctime;
-       if (ia_valid & ATTR_MODE) {
-               vi->i_mode = attr->ia_mode;
-               if (!in_group_p(vi->i_gid) &&
-                               !capable(CAP_FSETID))
-                       vi->i_mode &= ~S_ISGID;
-       }
+               vi->i_ctime = timespec_trunc(attr->ia_ctime,
+                               vi->i_sb->s_time_gran);
        mark_inode_dirty(vi);
-
-trunc_err:
-
-       unlock_kernel();
-
+out:
        return err;
 }
 
@@ -1975,71 +2961,107 @@ trunc_err:
  * marking the page (and in this case mft record) dirty but we do not implement
  * this yet as write_mft_record() largely ignores the @sync parameter and
  * always performs synchronous writes.
+ *
+ * Return 0 on success and -errno on error.
  */
-void ntfs_write_inode(struct inode *vi, int sync)
+int ntfs_write_inode(struct inode *vi, int sync)
 {
+       sle64 nt;
        ntfs_inode *ni = NTFS_I(vi);
-#if 0
-       attr_search_context *ctx;
-#endif
+       ntfs_attr_search_ctx *ctx;
        MFT_RECORD *m;
+       STANDARD_INFORMATION *si;
        int err = 0;
+       bool modified = false;
 
        ntfs_debug("Entering for %sinode 0x%lx.", NInoAttr(ni) ? "attr " : "",
                        vi->i_ino);
        /*
         * Dirty attribute inodes are written via their real inodes so just
-        * clean them here.  TODO:  Take care of access time updates.
+        * clean them here.  Access time updates are taken care off when the
+        * real inode is written.
         */
        if (NInoAttr(ni)) {
                NInoClearDirty(ni);
-               return;
+               ntfs_debug("Done.");
+               return 0;
        }
        /* Map, pin, and lock the mft record belonging to the inode. */
        m = map_mft_record(ni);
-       if (unlikely(IS_ERR(m))) {
+       if (IS_ERR(m)) {
                err = PTR_ERR(m);
                goto err_out;
        }
-#if 0
-       /* Obtain the standard information attribute. */
-       ctx = get_attr_search_ctx(ni, m);
+       /* Update the access times in the standard information attribute. */
+       ctx = ntfs_attr_get_search_ctx(ni, m);
        if (unlikely(!ctx)) {
                err = -ENOMEM;
                goto unm_err_out;
        }
-       if (unlikely(!lookup_attr(AT_STANDARD_INFORMATION, NULL, 0,
-                       IGNORE_CASE, 0, NULL, 0, ctx))) {
-               put_attr_search_ctx(ctx);
-               err = -ENOENT;
+       err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0,
+                       CASE_SENSITIVE, 0, NULL, 0, ctx);
+       if (unlikely(err)) {
+               ntfs_attr_put_search_ctx(ctx);
                goto unm_err_out;
        }
-       // TODO:  Update the access times in the standard information attribute
-       // which is now in ctx->attr.
-       // - Probably want to have use sops->dirty_inode() to set a flag that
-       //   we need to update the times here rather than having to blindly do
-       //   it every time.  Or even don't do it here at all and do it in
-       //   sops->dirty_inode() instead.  Problem with this would be that
-       //   sops->dirty_inode() must be atomic under certain circumstances
-       //   and mapping mft records and such like is not atomic.
-       // - For atime updates also need to check whether they are enabled in
-       //   the superblock flags.
-       ntfs_warning(vi->i_sb, "Access time updates not implement yet.");
+       si = (STANDARD_INFORMATION*)((u8*)ctx->attr +
+                       le16_to_cpu(ctx->attr->data.resident.value_offset));
+       /* Update the access times if they have changed. */
+       nt = utc2ntfs(vi->i_mtime);
+       if (si->last_data_change_time != nt) {
+               ntfs_debug("Updating mtime for inode 0x%lx: old = 0x%llx, "
+                               "new = 0x%llx", vi->i_ino, (long long)
+                               sle64_to_cpu(si->last_data_change_time),
+                               (long long)sle64_to_cpu(nt));
+               si->last_data_change_time = nt;
+               modified = true;
+       }
+       nt = utc2ntfs(vi->i_ctime);
+       if (si->last_mft_change_time != nt) {
+               ntfs_debug("Updating ctime for inode 0x%lx: old = 0x%llx, "
+                               "new = 0x%llx", vi->i_ino, (long long)
+                               sle64_to_cpu(si->last_mft_change_time),
+                               (long long)sle64_to_cpu(nt));
+               si->last_mft_change_time = nt;
+               modified = true;
+       }
+       nt = utc2ntfs(vi->i_atime);
+       if (si->last_access_time != nt) {
+               ntfs_debug("Updating atime for inode 0x%lx: old = 0x%llx, "
+                               "new = 0x%llx", vi->i_ino,
+                               (long long)sle64_to_cpu(si->last_access_time),
+                               (long long)sle64_to_cpu(nt));
+               si->last_access_time = nt;
+               modified = true;
+       }
        /*
-        * We just modified the mft record containing the standard information
-        * attribute.  So need to mark the mft record dirty, too, but we do it
-        * manually so that mark_inode_dirty() is not called again.
-        * TODO:  Only do this if there was a change in any of the times!
+        * If we just modified the standard information attribute we need to
+        * mark the mft record it is in dirty.  We do this manually so that
+        * mark_inode_dirty() is not called which would redirty the inode and
+        * hence result in an infinite loop of trying to write the inode.
+        * There is no need to mark the base inode nor the base mft record
+        * dirty, since we are going to write this mft record below in any case
+        * and the base mft record may actually not have been modified so it
+        * might not need to be written out.
+        * NOTE: It is not a problem when the inode for $MFT itself is being
+        * written out as mark_ntfs_record_dirty() will only set I_DIRTY_PAGES
+        * on the $MFT inode and hence ntfs_write_inode() will not be
+        * re-invoked because of it which in turn is ok since the dirtied mft
+        * record will be cleaned and written out to disk below, i.e. before
+        * this function returns.
         */
-       if (!NInoTestSetDirty(ctx->ntfs_ino))
-               __set_page_dirty_nobuffers(ctx->ntfs_ino->page);
-       put_attr_search_ctx(ctx);
-#endif
-       /* Write this base mft record. */
+       if (modified) {
+               flush_dcache_mft_record_page(ctx->ntfs_ino);
+               if (!NInoTestSetDirty(ctx->ntfs_ino))
+                       mark_ntfs_record_dirty(ctx->ntfs_ino->page,
+                                       ctx->ntfs_ino->page_ofs);
+       }
+       ntfs_attr_put_search_ctx(ctx);
+       /* Now the access times are updated, write the base mft record. */
        if (NInoDirty(ni))
                err = write_mft_record(ni, m, sync);
        /* Write all attached extent mft records. */
-       down(&ni->extent_lock);
+       mutex_lock(&ni->extent_lock);
        if (ni->nr_extents > 0) {
                ntfs_inode **extent_nis = ni->ext.extent_ntfs_inos;
                int i;
@@ -2052,7 +3074,7 @@ void ntfs_write_inode(struct inode *vi, int sync)
                                MFT_RECORD *tm = map_mft_record(tni);
                                int ret;
 
-                               if (unlikely(IS_ERR(tm))) {
+                               if (IS_ERR(tm)) {
                                        if (!err || err == -ENOMEM)
                                                err = PTR_ERR(tm);
                                        continue;
@@ -2066,16 +3088,14 @@ void ntfs_write_inode(struct inode *vi, int sync)
                        }
                }
        }
-       up(&ni->extent_lock);
+       mutex_unlock(&ni->extent_lock);
        unmap_mft_record(ni);
        if (unlikely(err))
                goto err_out;
        ntfs_debug("Done.");
-       return;
-#if 0
+       return 0;
 unm_err_out:
        unmap_mft_record(ni);
-#endif
 err_out:
        if (err == -ENOMEM) {
                ntfs_warning(vi->i_sb, "Not enough memory to write inode.  "
@@ -2083,11 +3103,10 @@ err_out:
                                "retries later.");
                mark_inode_dirty(vi);
        } else {
-               ntfs_error(vi->i_sb, "Failed (error code %i):  Marking inode "
-                               "as bad.  You should run chkdsk.", -err);
-               make_bad_inode(vi);
+               ntfs_error(vi->i_sb, "Failed (error %i):  Run chkdsk.", -err);
+               NVolSetErrors(ni->vol);
        }
-       return;
+       return err;
 }
 
 #endif /* NTFS_RW */