NTFS: Make ntfs_write_block() not instantiate sparse blocks if they are zero.
[powerpc.git] / fs / ntfs / attrib.c
index 7a16f7c..3f9a4ff 100644 (file)
  */
 
 #include <linux/buffer_head.h>
+#include <linux/swap.h>
 
 #include "attrib.h"
 #include "debug.h"
 #include "layout.h"
+#include "lcnalloc.h"
+#include "malloc.h"
 #include "mft.h"
 #include "ntfs.h"
 #include "types.h"
  *
  * Map the part of a runlist containing the @vcn of the ntfs inode @ni.
  *
- * Return 0 on success and -errno on error.
+ * Return 0 on success and -errno on error.  There is one special error code
+ * which is not an error as such.  This is -ENOENT.  It means that @vcn is out
+ * of bounds of the runlist.
+ *
+ * Note the runlist can be NULL after this function returns if @vcn is zero and
+ * the attribute has zero allocated size, i.e. there simply is no runlist.
  *
  * Locking: - The runlist must be locked for writing.
  *         - This function modifies the runlist.
  */
 int ntfs_map_runlist_nolock(ntfs_inode *ni, VCN vcn)
 {
+       VCN end_vcn;
        ntfs_inode *base_ni;
-       MFT_RECORD *mrec;
+       MFT_RECORD *m;
+       ATTR_RECORD *a;
        ntfs_attr_search_ctx *ctx;
        runlist_element *rl;
+       unsigned long flags;
        int err = 0;
 
        ntfs_debug("Mapping runlist part containing vcn 0x%llx.",
@@ -55,26 +66,46 @@ int ntfs_map_runlist_nolock(ntfs_inode *ni, VCN vcn)
                base_ni = ni;
        else
                base_ni = ni->ext.base_ntfs_ino;
-       mrec = map_mft_record(base_ni);
-       if (IS_ERR(mrec))
-               return PTR_ERR(mrec);
-       ctx = ntfs_attr_get_search_ctx(base_ni, mrec);
+       m = map_mft_record(base_ni);
+       if (IS_ERR(m))
+               return PTR_ERR(m);
+       ctx = ntfs_attr_get_search_ctx(base_ni, m);
        if (unlikely(!ctx)) {
                err = -ENOMEM;
                goto err_out;
        }
        err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
                        CASE_SENSITIVE, vcn, NULL, 0, ctx);
-       if (likely(!err)) {
-               rl = ntfs_mapping_pairs_decompress(ni->vol, ctx->attr,
-                               ni->runlist.rl);
-               if (IS_ERR(rl))
-                       err = PTR_ERR(rl);
-               else
-                       ni->runlist.rl = rl;
+       if (unlikely(err)) {
+               if (err == -ENOENT)
+                       err = -EIO;
+               goto err_out;
        }
-       ntfs_attr_put_search_ctx(ctx);
+       a = ctx->attr;
+       /*
+        * Only decompress the mapping pairs if @vcn is inside it.  Otherwise
+        * we get into problems when we try to map an out of bounds vcn because
+        * we then try to map the already mapped runlist fragment and
+        * ntfs_mapping_pairs_decompress() fails.
+        */
+       end_vcn = sle64_to_cpu(a->data.non_resident.highest_vcn) + 1;
+       if (unlikely(!a->data.non_resident.lowest_vcn && end_vcn <= 1)) {
+               read_lock_irqsave(&ni->size_lock, flags);
+               end_vcn = ni->allocated_size >> ni->vol->cluster_size_bits;
+               read_unlock_irqrestore(&ni->size_lock, flags);
+       }
+       if (unlikely(vcn >= end_vcn)) {
+               err = -ENOENT;
+               goto err_out;
+       }
+       rl = ntfs_mapping_pairs_decompress(ni->vol, a, ni->runlist.rl);
+       if (IS_ERR(rl))
+               err = PTR_ERR(rl);
+       else
+               ni->runlist.rl = rl;
 err_out:
+       if (likely(ctx))
+               ntfs_attr_put_search_ctx(ctx);
        unmap_mft_record(base_ni);
        return err;
 }
@@ -86,7 +117,9 @@ err_out:
  *
  * Map the part of a runlist containing the @vcn of the ntfs inode @ni.
  *
- * Return 0 on success and -errno on error.
+ * Return 0 on success and -errno on error.  There is one special error code
+ * which is not an error as such.  This is -ENOENT.  It means that @vcn is out
+ * of bounds of the runlist.
  *
  * Locking: - The runlist must be unlocked on entry and is unlocked on return.
  *         - This function takes the runlist lock for writing and modifies the
@@ -106,19 +139,115 @@ int ntfs_map_runlist(ntfs_inode *ni, VCN vcn)
 }
 
 /**
- * ntfs_find_vcn_nolock - find a vcn in the runlist described by an ntfs inode
+ * ntfs_attr_vcn_to_lcn_nolock - convert a vcn into a lcn given an ntfs inode
+ * @ni:                        ntfs inode of the attribute whose runlist to search
+ * @vcn:               vcn to convert
+ * @write_locked:      true if the runlist is locked for writing
+ *
+ * Find the virtual cluster number @vcn in the runlist of the ntfs attribute
+ * described by the ntfs inode @ni and return the corresponding logical cluster
+ * number (lcn).
+ *
+ * If the @vcn is not mapped yet, the attempt is made to map the attribute
+ * extent containing the @vcn and the vcn to lcn conversion is retried.
+ *
+ * If @write_locked is true the caller has locked the runlist for writing and
+ * if false for reading.
+ *
+ * Since lcns must be >= 0, we use negative return codes with special meaning:
+ *
+ * Return code Meaning / Description
+ * ==========================================
+ *  LCN_HOLE   Hole / not allocated on disk.
+ *  LCN_ENOENT There is no such vcn in the runlist, i.e. @vcn is out of bounds.
+ *  LCN_ENOMEM Not enough memory to map runlist.
+ *  LCN_EIO    Critical error (runlist/file is corrupt, i/o error, etc).
+ *
+ * Locking: - The runlist must be locked on entry and is left locked on return.
+ *         - If @write_locked is FALSE, i.e. the runlist is locked for reading,
+ *           the lock may be dropped inside the function so you cannot rely on
+ *           the runlist still being the same when this function returns.
+ */
+LCN ntfs_attr_vcn_to_lcn_nolock(ntfs_inode *ni, const VCN vcn,
+               const BOOL write_locked)
+{
+       LCN lcn;
+       unsigned long flags;
+       BOOL is_retry = FALSE;
+
+       ntfs_debug("Entering for i_ino 0x%lx, vcn 0x%llx, %s_locked.",
+                       ni->mft_no, (unsigned long long)vcn,
+                       write_locked ? "write" : "read");
+       BUG_ON(!ni);
+       BUG_ON(!NInoNonResident(ni));
+       BUG_ON(vcn < 0);
+       if (!ni->runlist.rl) {
+               read_lock_irqsave(&ni->size_lock, flags);
+               if (!ni->allocated_size) {
+                       read_unlock_irqrestore(&ni->size_lock, flags);
+                       return LCN_ENOENT;
+               }
+               read_unlock_irqrestore(&ni->size_lock, flags);
+       }
+retry_remap:
+       /* Convert vcn to lcn.  If that fails map the runlist and retry once. */
+       lcn = ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn);
+       if (likely(lcn >= LCN_HOLE)) {
+               ntfs_debug("Done, lcn 0x%llx.", (long long)lcn);
+               return lcn;
+       }
+       if (lcn != LCN_RL_NOT_MAPPED) {
+               if (lcn != LCN_ENOENT)
+                       lcn = LCN_EIO;
+       } else if (!is_retry) {
+               int err;
+
+               if (!write_locked) {
+                       up_read(&ni->runlist.lock);
+                       down_write(&ni->runlist.lock);
+                       if (unlikely(ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn) !=
+                                       LCN_RL_NOT_MAPPED)) {
+                               up_write(&ni->runlist.lock);
+                               down_read(&ni->runlist.lock);
+                               goto retry_remap;
+                       }
+               }
+               err = ntfs_map_runlist_nolock(ni, vcn);
+               if (!write_locked) {
+                       up_write(&ni->runlist.lock);
+                       down_read(&ni->runlist.lock);
+               }
+               if (likely(!err)) {
+                       is_retry = TRUE;
+                       goto retry_remap;
+               }
+               if (err == -ENOENT)
+                       lcn = LCN_ENOENT;
+               else if (err == -ENOMEM)
+                       lcn = LCN_ENOMEM;
+               else
+                       lcn = LCN_EIO;
+       }
+       if (lcn != LCN_ENOENT)
+               ntfs_error(ni->vol->sb, "Failed with error code %lli.",
+                               (long long)lcn);
+       return lcn;
+}
+
+/**
+ * ntfs_attr_find_vcn_nolock - find a vcn in the runlist of an ntfs inode
  * @ni:                        ntfs inode describing the runlist to search
  * @vcn:               vcn to find
  * @write_locked:      true if the runlist is locked for writing
  *
  * Find the virtual cluster number @vcn in the runlist described by the ntfs
  * inode @ni and return the address of the runlist element containing the @vcn.
- * The runlist is left locked and the caller has to unlock it.  In the error
- * case, the runlist is left in the same locking state as on entry.
  *
- * Note if @write_locked is FALSE the lock may be dropped inside the function
- * so you cannot rely on the runlist still being the same when this function
- * returns.
+ * If the @vcn is not mapped yet, the attempt is made to map the attribute
+ * extent containing the @vcn and the vcn to lcn conversion is retried.
+ *
+ * If @write_locked is true the caller has locked the runlist for writing and
+ * if false for reading.
  *
  * Note you need to distinguish between the lcn of the returned runlist element
  * being >= 0 and LCN_HOLE.  In the later case you have to return zeroes on
@@ -134,15 +263,15 @@ int ntfs_map_runlist(ntfs_inode *ni, VCN vcn)
  *     -ENOMEM - Not enough memory to map runlist.
  *     -EIO    - Critical error (runlist/file is corrupt, i/o error, etc).
  *
- * Locking: - The runlist must be unlocked on entry.
- *         - On failing return, the runlist is unlocked.
- *         - On successful return, the runlist is locked.  If @need_write us
- *           true, it is locked for writing.  Otherwise is is locked for
- *           reading.
+ * Locking: - The runlist must be locked on entry and is left locked on return.
+ *         - If @write_locked is FALSE, i.e. the runlist is locked for reading,
+ *           the lock may be dropped inside the function so you cannot rely on
+ *           the runlist still being the same when this function returns.
  */
-runlist_element *ntfs_find_vcn_nolock(ntfs_inode *ni, const VCN vcn,
+runlist_element *ntfs_attr_find_vcn_nolock(ntfs_inode *ni, const VCN vcn,
                const BOOL write_locked)
 {
+       unsigned long flags;
        runlist_element *rl;
        int err = 0;
        BOOL is_retry = FALSE;
@@ -153,6 +282,14 @@ runlist_element *ntfs_find_vcn_nolock(ntfs_inode *ni, const VCN vcn,
        BUG_ON(!ni);
        BUG_ON(!NInoNonResident(ni));
        BUG_ON(vcn < 0);
+       if (!ni->runlist.rl) {
+               read_lock_irqsave(&ni->size_lock, flags);
+               if (!ni->allocated_size) {
+                       read_unlock_irqrestore(&ni->size_lock, flags);
+                       return ERR_PTR(-ENOENT);
+               }
+               read_unlock_irqrestore(&ni->size_lock, flags);
+       }
 retry_remap:
        rl = ni->runlist.rl;
        if (likely(rl && vcn >= rl[0].vcn)) {
@@ -181,6 +318,12 @@ retry_remap:
                if (!write_locked) {
                        up_read(&ni->runlist.lock);
                        down_write(&ni->runlist.lock);
+                       if (unlikely(ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn) !=
+                                       LCN_RL_NOT_MAPPED)) {
+                               up_write(&ni->runlist.lock);
+                               down_read(&ni->runlist.lock);
+                               goto retry_remap;
+                       }
                }
                err = ntfs_map_runlist_nolock(ni, vcn);
                if (!write_locked) {
@@ -192,11 +335,11 @@ retry_remap:
                        goto retry_remap;
                }
                /*
-                * -EINVAL and -ENOENT coming from a failed mapping attempt are
-                * equivalent to i/o errors for us as they should not happen in
-                * our code paths.
+                * -EINVAL coming from a failed mapping attempt is equivalent
+                * to i/o error for us as it should not happen in our code
+                * paths.
                 */
-               if (err == -EINVAL || err == -ENOENT)
+               if (err == -EINVAL)
                        err = -EIO;
        } else if (!err)
                err = -EIO;
@@ -410,6 +553,11 @@ int load_attribute_list(ntfs_volume *vol, runlist *runlist, u8 *al_start,
        block_size_bits = sb->s_blocksize_bits;
        down_read(&runlist->lock);
        rl = runlist->rl;
+       if (!rl) {
+               ntfs_error(sb, "Cannot read attribute list since runlist is "
+                               "missing.");
+               goto err_out;   
+       }
        /* Read all clusters specified by the runlist one run at a time. */
        while (rl->length) {
                lcn = ntfs_rl_vcn_to_lcn(rl, rl->vcn);
@@ -887,15 +1035,14 @@ int ntfs_attr_lookup(const ATTR_TYPE type, const ntfschar *name,
 static inline void ntfs_attr_init_search_ctx(ntfs_attr_search_ctx *ctx,
                ntfs_inode *ni, MFT_RECORD *mrec)
 {
-       ctx->mrec = mrec;
-       /* Sanity checks are performed elsewhere. */
-       ctx->attr = (ATTR_RECORD*)((u8*)mrec + le16_to_cpu(mrec->attrs_offset));
-       ctx->is_first = TRUE;
-       ctx->ntfs_ino = ni;
-       ctx->al_entry = NULL;
-       ctx->base_ntfs_ino = NULL;
-       ctx->base_mrec = NULL;
-       ctx->base_attr = NULL;
+       *ctx = (ntfs_attr_search_ctx) {
+               .mrec = mrec,
+               /* Sanity checks are performed elsewhere. */
+               .attr = (ATTR_RECORD*)((u8*)mrec +
+                               le16_to_cpu(mrec->attrs_offset)),
+               .is_first = TRUE,
+               .ntfs_ino = ni,
+       };
 }
 
 /**
@@ -962,6 +1109,8 @@ void ntfs_attr_put_search_ctx(ntfs_attr_search_ctx *ctx)
        return;
 }
 
+#ifdef NTFS_RW
+
 /**
  * ntfs_attr_find_in_attrdef - find an attribute in the $AttrDef system file
  * @vol:       ntfs volume to which the attribute belongs
@@ -1041,27 +1190,21 @@ int ntfs_attr_size_bounds_check(const ntfs_volume *vol, const ATTR_TYPE type,
  * Check whether the attribute of @type on the ntfs volume @vol is allowed to
  * be non-resident.  This information is obtained from $AttrDef system file.
  *
- * Return 0 if the attribute is allowed to be non-resident, -EPERM if not, or
+ * Return 0 if the attribute is allowed to be non-resident, -EPERM if not, and
  * -ENOENT if the attribute is not listed in $AttrDef.
  */
 int ntfs_attr_can_be_non_resident(const ntfs_volume *vol, const ATTR_TYPE type)
 {
        ATTR_DEF *ad;
 
-       /*
-        * $DATA is always allowed to be non-resident even if $AttrDef does not
-        * specify this in the flags of the $DATA attribute definition record.
-        */
-       if (type == AT_DATA)
-               return 0;
        /* Find the attribute definition record in $AttrDef. */
        ad = ntfs_attr_find_in_attrdef(vol, type);
        if (unlikely(!ad))
                return -ENOENT;
        /* Check the flags and return the result. */
-       if (ad->flags & CAN_BE_NON_RESIDENT)
-               return 0;
-       return -EPERM;
+       if (ad->flags & ATTR_DEF_RESIDENT)
+               return -EPERM;
+       return 0;
 }
 
 /**
@@ -1084,9 +1227,9 @@ int ntfs_attr_can_be_non_resident(const ntfs_volume *vol, const ATTR_TYPE type)
  */
 int ntfs_attr_can_be_resident(const ntfs_volume *vol, const ATTR_TYPE type)
 {
-       if (type != AT_INDEX_ALLOCATION && type != AT_EA)
-               return 0;
-       return -EPERM;
+       if (type == AT_INDEX_ALLOCATION || type == AT_EA)
+               return -EPERM;
+       return 0;
 }
 
 /**
@@ -1133,6 +1276,381 @@ int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size)
        return 0;
 }
 
+/**
+ * ntfs_resident_attr_value_resize - resize the value of a resident attribute
+ * @m:         mft record containing attribute record
+ * @a:         attribute record whose value to resize
+ * @new_size:  new size in bytes to which to resize the attribute value of @a
+ *
+ * Resize the value of the attribute @a in the mft record @m to @new_size bytes.
+ * If the value is made bigger, the newly allocated space is cleared.
+ *
+ * Return 0 on success and -errno on error.  The following error codes are
+ * defined:
+ *     -ENOSPC - Not enough space in the mft record @m to perform the resize.
+ *
+ * Note: On error, no modifications have been performed whatsoever.
+ *
+ * Warning: If you make a record smaller without having copied all the data you
+ *         are interested in the data may be overwritten.
+ */
+int ntfs_resident_attr_value_resize(MFT_RECORD *m, ATTR_RECORD *a,
+               const u32 new_size)
+{
+       u32 old_size;
+
+       /* Resize the resident part of the attribute record. */
+       if (ntfs_attr_record_resize(m, a,
+                       le16_to_cpu(a->data.resident.value_offset) + new_size))
+               return -ENOSPC;
+       /*
+        * The resize succeeded!  If we made the attribute value bigger, clear
+        * the area between the old size and @new_size.
+        */
+       old_size = le32_to_cpu(a->data.resident.value_length);
+       if (new_size > old_size)
+               memset((u8*)a + le16_to_cpu(a->data.resident.value_offset) +
+                               old_size, 0, new_size - old_size);
+       /* Finally update the length of the attribute value. */
+       a->data.resident.value_length = cpu_to_le32(new_size);
+       return 0;
+}
+
+/**
+ * ntfs_attr_make_non_resident - convert a resident to a non-resident attribute
+ * @ni:                ntfs inode describing the attribute to convert
+ *
+ * Convert the resident ntfs attribute described by the ntfs inode @ni to a
+ * non-resident one.
+ *
+ * Return 0 on success and -errno on error.  The following error return codes
+ * are defined:
+ *     -EPERM  - The attribute is not allowed to be non-resident.
+ *     -ENOMEM - Not enough memory.
+ *     -ENOSPC - Not enough disk space.
+ *     -EINVAL - Attribute not defined on the volume.
+ *     -EIO    - I/o error or other error.
+ * Note that -ENOSPC is also returned in the case that there is not enough
+ * space in the mft record to do the conversion.  This can happen when the mft
+ * record is already very full.  The caller is responsible for trying to make
+ * space in the mft record and trying again.  FIXME: Do we need a separate
+ * error return code for this kind of -ENOSPC or is it always worth trying
+ * again in case the attribute may then fit in a resident state so no need to
+ * make it non-resident at all?  Ho-hum...  (AIA)
+ *
+ * NOTE to self: No changes in the attribute list are required to move from
+ *              a resident to a non-resident attribute.
+ *
+ * Locking: - The caller must hold i_sem on the inode.
+ */
+int ntfs_attr_make_non_resident(ntfs_inode *ni)
+{
+       s64 new_size;
+       struct inode *vi = VFS_I(ni);
+       ntfs_volume *vol = ni->vol;
+       ntfs_inode *base_ni;
+       MFT_RECORD *m;
+       ATTR_RECORD *a;
+       ntfs_attr_search_ctx *ctx;
+       struct page *page;
+       runlist_element *rl;
+       u8 *kaddr;
+       unsigned long flags;
+       int mp_size, mp_ofs, name_ofs, arec_size, err, err2;
+       u32 attr_size;
+       u8 old_res_attr_flags;
+
+       /* Check that the attribute is allowed to be non-resident. */
+       err = ntfs_attr_can_be_non_resident(vol, ni->type);
+       if (unlikely(err)) {
+               if (err == -EPERM)
+                       ntfs_debug("Attribute is not allowed to be "
+                                       "non-resident.");
+               else
+                       ntfs_debug("Attribute not defined on the NTFS "
+                                       "volume!");
+               return err;
+       }
+       /*
+        * FIXME: Compressed and encrypted attributes are not supported when
+        * writing and we should never have gotten here for them.
+        */
+       BUG_ON(NInoCompressed(ni));
+       BUG_ON(NInoEncrypted(ni));
+       /*
+        * The size needs to be aligned to a cluster boundary for allocation
+        * purposes.
+        */
+       new_size = (i_size_read(vi) + vol->cluster_size - 1) &
+                       ~(vol->cluster_size - 1);
+       if (new_size > 0) {
+               runlist_element *rl2;
+
+               /*
+                * Will need the page later and since the page lock nests
+                * outside all ntfs locks, we need to get the page now.
+                */
+               page = find_or_create_page(vi->i_mapping, 0,
+                               mapping_gfp_mask(vi->i_mapping));
+               if (unlikely(!page))
+                       return -ENOMEM;
+               /* Start by allocating clusters to hold the attribute value. */
+               rl = ntfs_cluster_alloc(vol, 0, new_size >>
+                               vol->cluster_size_bits, -1, DATA_ZONE);
+               if (IS_ERR(rl)) {
+                       err = PTR_ERR(rl);
+                       ntfs_debug("Failed to allocate cluster%s, error code "
+                                       "%i.", (new_size >>
+                                       vol->cluster_size_bits) > 1 ? "s" : "",
+                                       err);
+                       goto page_err_out;
+               }
+               /* Change the runlist terminator to LCN_ENOENT. */
+               rl2 = rl;
+               while (rl2->length)
+                       rl2++;
+               BUG_ON(rl2->lcn != LCN_RL_NOT_MAPPED);
+               rl2->lcn = LCN_ENOENT;
+       } else {
+               rl = NULL;
+               page = NULL;
+       }
+       /* Determine the size of the mapping pairs array. */
+       mp_size = ntfs_get_size_for_mapping_pairs(vol, rl, 0, -1);
+       if (unlikely(mp_size < 0)) {
+               err = mp_size;
+               ntfs_debug("Failed to get size for mapping pairs array, error "
+                               "code %i.", err);
+               goto rl_err_out;
+       }
+       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);
+               m = NULL;
+               ctx = NULL;
+               goto err_out;
+       }
+       ctx = ntfs_attr_get_search_ctx(base_ni, m);
+       if (unlikely(!ctx)) {
+               err = -ENOMEM;
+               goto err_out;
+       }
+       err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
+                       CASE_SENSITIVE, 0, NULL, 0, ctx);
+       if (unlikely(err)) {
+               if (err == -ENOENT)
+                       err = -EIO;
+               goto err_out;
+       }
+       m = ctx->mrec;
+       a = ctx->attr;
+       BUG_ON(NInoNonResident(ni));
+       BUG_ON(a->non_resident);
+       /*
+        * Calculate new offsets for the name and the mapping pairs array.
+        */
+       if (NInoSparse(ni) || NInoCompressed(ni))
+               name_ofs = (offsetof(ATTR_REC,
+                               data.non_resident.compressed_size) +
+                               sizeof(a->data.non_resident.compressed_size) +
+                               7) & ~7;
+       else
+               name_ofs = (offsetof(ATTR_REC,
+                               data.non_resident.compressed_size) + 7) & ~7;
+       mp_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7;
+       /*
+        * Determine the size of the resident part of the now non-resident
+        * attribute record.
+        */
+       arec_size = (mp_ofs + mp_size + 7) & ~7;
+       /*
+        * If the page is not uptodate bring it uptodate by copying from the
+        * attribute value.
+        */
+       attr_size = le32_to_cpu(a->data.resident.value_length);
+       BUG_ON(attr_size != i_size_read(vi));
+       if (page && !PageUptodate(page)) {
+               kaddr = kmap_atomic(page, KM_USER0);
+               memcpy(kaddr, (u8*)a +
+                               le16_to_cpu(a->data.resident.value_offset),
+                               attr_size);
+               memset(kaddr + attr_size, 0, PAGE_CACHE_SIZE - attr_size);
+               kunmap_atomic(kaddr, KM_USER0);
+               flush_dcache_page(page);
+               SetPageUptodate(page);
+       }
+       /* Backup the attribute flag. */
+       old_res_attr_flags = a->data.resident.flags;
+       /* Resize the resident part of the attribute record. */
+       err = ntfs_attr_record_resize(m, a, arec_size);
+       if (unlikely(err))
+               goto err_out;
+       /*
+        * Convert the resident part of the attribute record to describe a
+        * non-resident attribute.
+        */
+       a->non_resident = 1;
+       /* Move the attribute name if it exists and update the offset. */
+       if (a->name_length)
+               memmove((u8*)a + name_ofs, (u8*)a + le16_to_cpu(a->name_offset),
+                               a->name_length * sizeof(ntfschar));
+       a->name_offset = cpu_to_le16(name_ofs);
+       /* Setup the fields specific to non-resident attributes. */
+       a->data.non_resident.lowest_vcn = 0;
+       a->data.non_resident.highest_vcn = cpu_to_sle64((new_size - 1) >>
+                       vol->cluster_size_bits);
+       a->data.non_resident.mapping_pairs_offset = cpu_to_le16(mp_ofs);
+       memset(&a->data.non_resident.reserved, 0,
+                       sizeof(a->data.non_resident.reserved));
+       a->data.non_resident.allocated_size = cpu_to_sle64(new_size);
+       a->data.non_resident.data_size =
+                       a->data.non_resident.initialized_size =
+                       cpu_to_sle64(attr_size);
+       if (NInoSparse(ni) || NInoCompressed(ni)) {
+               a->data.non_resident.compression_unit = 4;
+               a->data.non_resident.compressed_size =
+                               a->data.non_resident.allocated_size;
+       } else
+               a->data.non_resident.compression_unit = 0;
+       /* Generate the mapping pairs array into the attribute record. */
+       err = ntfs_mapping_pairs_build(vol, (u8*)a + mp_ofs,
+                       arec_size - mp_ofs, rl, 0, -1, NULL);
+       if (unlikely(err)) {
+               ntfs_debug("Failed to build mapping pairs, error code %i.",
+                               err);
+               goto undo_err_out;
+       }
+       /* Setup the in-memory attribute structure to be non-resident. */
+       ni->runlist.rl = rl;
+       write_lock_irqsave(&ni->size_lock, flags);
+       ni->allocated_size = new_size;
+       if (NInoSparse(ni) || NInoCompressed(ni)) {
+               ni->itype.compressed.size = ni->allocated_size;
+               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;
+       }
+       write_unlock_irqrestore(&ni->size_lock, flags);
+       /*
+        * This needs to be last since the address space operations ->readpage
+        * and ->writepage can run concurrently with us as they are not
+        * serialized on i_sem.  Note, we are not allowed to fail once we flip
+        * this switch, which is another reason to do this last.
+        */
+       NInoSetNonResident(ni);
+       /* Mark the mft record dirty, so it gets written back. */
+       flush_dcache_mft_record_page(ctx->ntfs_ino);
+       mark_mft_record_dirty(ctx->ntfs_ino);
+       ntfs_attr_put_search_ctx(ctx);
+       unmap_mft_record(base_ni);
+       up_write(&ni->runlist.lock);
+       if (page) {
+               set_page_dirty(page);
+               unlock_page(page);
+               mark_page_accessed(page);
+               page_cache_release(page);
+       }
+       ntfs_debug("Done.");
+       return 0;
+undo_err_out:
+       /* Convert the attribute back into a resident attribute. */
+       a->non_resident = 0;
+       /* Move the attribute name if it exists and update the offset. */
+       name_ofs = (offsetof(ATTR_RECORD, data.resident.reserved) +
+                       sizeof(a->data.resident.reserved) + 7) & ~7;
+       if (a->name_length)
+               memmove((u8*)a + name_ofs, (u8*)a + le16_to_cpu(a->name_offset),
+                               a->name_length * sizeof(ntfschar));
+       mp_ofs = (name_ofs + a->name_length * sizeof(ntfschar) + 7) & ~7;
+       a->name_offset = cpu_to_le16(name_ofs);
+       arec_size = (mp_ofs + attr_size + 7) & ~7;
+       /* Resize the resident part of the attribute record. */
+       err2 = ntfs_attr_record_resize(m, a, arec_size);
+       if (unlikely(err2)) {
+               /*
+                * This cannot happen (well if memory corruption is at work it
+                * could happen in theory), but deal with it as well as we can.
+                * If the old size is too small, truncate the attribute,
+                * otherwise simply give it a larger allocated size.
+                * FIXME: Should check whether chkdsk complains when the
+                * allocated size is much bigger than the resident value size.
+                */
+               arec_size = le32_to_cpu(a->length);
+               if ((mp_ofs + attr_size) > arec_size) {
+                       err2 = attr_size;
+                       attr_size = arec_size - mp_ofs;
+                       ntfs_error(vol->sb, "Failed to undo partial resident "
+                                       "to non-resident attribute "
+                                       "conversion.  Truncating inode 0x%lx, "
+                                       "attribute type 0x%x from %i bytes to "
+                                       "%i bytes to maintain metadata "
+                                       "consistency.  THIS MEANS YOU ARE "
+                                       "LOSING %i BYTES DATA FROM THIS %s.",
+                                       vi->i_ino,
+                                       (unsigned)le32_to_cpu(ni->type),
+                                       err2, attr_size, err2 - attr_size,
+                                       ((ni->type == AT_DATA) &&
+                                       !ni->name_len) ? "FILE": "ATTRIBUTE");
+                       write_lock_irqsave(&ni->size_lock, flags);
+                       ni->initialized_size = attr_size;
+                       i_size_write(vi, attr_size);
+                       write_unlock_irqrestore(&ni->size_lock, flags);
+               }
+       }
+       /* Setup the fields specific to resident attributes. */
+       a->data.resident.value_length = cpu_to_le32(attr_size);
+       a->data.resident.value_offset = cpu_to_le16(mp_ofs);
+       a->data.resident.flags = old_res_attr_flags;
+       memset(&a->data.resident.reserved, 0,
+                       sizeof(a->data.resident.reserved));
+       /* Copy the data from the page back to the attribute value. */
+       if (page) {
+               kaddr = kmap_atomic(page, KM_USER0);
+               memcpy((u8*)a + mp_ofs, kaddr, attr_size);
+               kunmap_atomic(kaddr, KM_USER0);
+       }
+       /* Setup the allocated size in the ntfs inode in case it changed. */
+       write_lock_irqsave(&ni->size_lock, flags);
+       ni->allocated_size = arec_size - mp_ofs;
+       write_unlock_irqrestore(&ni->size_lock, flags);
+       /* Mark the mft record dirty, so it gets written back. */
+       flush_dcache_mft_record_page(ctx->ntfs_ino);
+       mark_mft_record_dirty(ctx->ntfs_ino);
+err_out:
+       if (ctx)
+               ntfs_attr_put_search_ctx(ctx);
+       if (m)
+               unmap_mft_record(base_ni);
+       ni->runlist.rl = NULL;
+       up_write(&ni->runlist.lock);
+rl_err_out:
+       if (rl) {
+               if (ntfs_cluster_free_from_rl(vol, rl) < 0) {
+                       ntfs_error(vol->sb, "Failed to release allocated "
+                                       "cluster(s) in error code path.  Run "
+                                       "chkdsk to recover the lost "
+                                       "cluster(s).");
+                       NVolSetErrors(vol);
+               }
+               ntfs_free(rl);
+page_err_out:
+               unlock_page(page);
+               page_cache_release(page);
+       }
+       if (err == -EINVAL)
+               err = -EIO;
+       return err;
+}
+
 /**
  * ntfs_attr_set - fill (a part of) an attribute with a byte
  * @ni:                ntfs inode describing the attribute to fill
@@ -1168,6 +1686,12 @@ int ntfs_attr_set(ntfs_inode *ni, const s64 ofs, const s64 cnt, const u8 val)
        BUG_ON(cnt < 0);
        if (!cnt)
                goto done;
+       /*
+        * FIXME: Compressed and encrypted attributes are not supported when
+        * writing and we should never have gotten here for them.
+        */
+       BUG_ON(NInoCompressed(ni));
+       BUG_ON(NInoEncrypted(ni));
        mapping = VFS_I(ni)->i_mapping;
        /* Work out the starting index and page offset. */
        idx = ofs >> PAGE_CACHE_SHIFT;
@@ -1277,3 +1801,5 @@ done:
        ntfs_debug("Done.");
        return 0;
 }
+
+#endif /* NTFS_RW */