NTFS: Add fs/ntfs/attrib.[hc]::ntfs_attr_make_non_resident().
authorAnton Altaparmakov <aia21@cantab.net>
Wed, 9 Mar 2005 15:15:06 +0000 (15:15 +0000)
committerAnton Altaparmakov <aia21@cantab.net>
Thu, 5 May 2005 10:22:07 +0000 (11:22 +0100)
Signed-off-by: Anton Altaparmakov <aia21@cantab.net>
fs/ntfs/ChangeLog
fs/ntfs/attrib.c
fs/ntfs/attrib.h

index 7507a56..71680a9 100644 (file)
@@ -97,6 +97,7 @@ ToDo/Notes:
          whether someone else did not already do the work we wanted to do.
        - Rename fs/ntfs/attrib.c::ntfs_find_vcn_nolock() to
          ntfs_attr_find_vcn_nolock() and update all callers.
+       - Add fs/ntfs/attrib.[hc]::ntfs_attr_make_non_resident().
 
 2.1.22 - Many bug and race fixes and error handling improvements.
 
index 6de5e04..3b9de40 100644 (file)
@@ -25,6 +25,8 @@
 #include "attrib.h"
 #include "debug.h"
 #include "layout.h"
+#include "lcnalloc.h"
+#include "malloc.h"
 #include "mft.h"
 #include "ntfs.h"
 #include "types.h"
@@ -1227,6 +1229,304 @@ int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size)
 }
 
 /**
+ * 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 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;
+       }
+       /*
+        * 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) {
+               /*
+                * 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.\n", (new_size >>
+                                       vol->cluster_size_bits) > 1 ? "s" : "",
+                                       err);
+                       goto page_err_out;
+               }
+       } else {
+               rl = NULL;
+               page = NULL;
+       }
+       /* Determine the size of the mapping pairs array. */
+       mp_size = ntfs_get_size_for_mapping_pairs(vol, rl, 0);
+       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.
+        * We assume the attribute is not compressed or sparse.
+        */
+       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;
+       /* Setup the in-memory attribute structure to be non-resident. */
+       NInoSetNonResident(ni);
+       ni->runlist.rl = rl;
+       write_lock_irqsave(&ni->size_lock, flags);
+       ni->allocated_size = new_size;
+       write_unlock_irqrestore(&ni->size_lock, flags);
+       /*
+        * FIXME: For now just clear all of these as we do not support them
+        * when writing.
+        */
+       NInoClearCompressed(ni);
+       NInoClearSparse(ni);
+       NInoClearEncrypted(ni);
+       /*
+        * 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);
+       /* Update the flags to match the in-memory ones. */
+       a->flags &= cpu_to_le16(0xffff & ~le16_to_cpu(ATTR_IS_SPARSE |
+                       ATTR_IS_ENCRYPTED | ATTR_COMPRESSION_MASK));
+       /* 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);
+       a->data.non_resident.compression_unit = 0;
+       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);
+       /* 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, NULL);
+       if (unlikely(err)) {
+               ntfs_debug("Failed to build mapping pairs, error code %i.",
+                               err);
+               goto undo_err_out;
+       }
+       /* 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);
+               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);
+       }
+       /* Finally setup the ntfs inode appropriately. */
+       write_lock_irqsave(&ni->size_lock, flags);
+       ni->allocated_size = arec_size - mp_ofs;
+       write_unlock_irqrestore(&ni->size_lock, flags);
+       NInoClearNonResident(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);
+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_free(rl);
+                       ntfs_error(vol->sb, "Failed to release allocated "
+                                       "cluster(s) in error code path.  Run "
+                                       "chkdsk to recover the lost "
+                                       "cluster(s).");
+                       NVolSetErrors(vol);
+               }
+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
  * @ofs:       offset inside the attribute at which to start to fill
index e0a50f1..d733851 100644 (file)
@@ -98,6 +98,8 @@ extern int ntfs_attr_can_be_resident(const ntfs_volume *vol,
 
 extern int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 new_size);
 
+extern int ntfs_attr_make_non_resident(ntfs_inode *ni);
+
 extern int ntfs_attr_set(ntfs_inode *ni, const s64 ofs, const s64 cnt,
                const u8 val);