*/
#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.",
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;
}
*
* 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
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.",
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);
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;
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)) {
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;
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);
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,
+ };
}
/**
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
* 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 and $EA are 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 || type == AT_EA)
- 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;
}
/**
*/
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;
}
/**
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
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;
ntfs_debug("Done.");
return 0;
}
+
+#endif /* NTFS_RW */