Loading fs/ntfs/ChangeLog +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading fs/ntfs/attrib.c +300 −0 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -1226,6 +1228,304 @@ int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 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 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 Loading fs/ntfs/attrib.h +2 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading
fs/ntfs/ChangeLog +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
fs/ntfs/attrib.c +300 −0 Original line number Diff line number Diff line Loading @@ -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" Loading Loading @@ -1226,6 +1228,304 @@ int ntfs_attr_record_resize(MFT_RECORD *m, ATTR_RECORD *a, u32 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 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 Loading
fs/ntfs/attrib.h +2 −0 Original line number Diff line number Diff line Loading @@ -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); Loading