summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--fs/ntfs/ChangeLog3
-rw-r--r--fs/ntfs/aops.c18
-rw-r--r--fs/ntfs/attrib.c41
3 files changed, 46 insertions, 16 deletions
diff --git a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog
index e0b4adf5adc..b29e0618f35 100644
--- a/fs/ntfs/ChangeLog
+++ b/fs/ntfs/ChangeLog
@@ -100,6 +100,9 @@ ToDo/Notes:
- Add fs/ntfs/attrib.[hc]::ntfs_attr_make_non_resident().
- Fix sign of various error return values to be negative in
fs/ntfs/lcnalloc.c.
+ - Modify ->readpage and ->writepage (fs/ntfs/aops.c) so they detect and
+ handle the case where an attribute is converted from resident to
+ non-resident by a concurrent file write.
2.1.22 - Many bug and race fixes and error handling improvements.
diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c
index 2a7cba258cc..6241c4cfbe2 100644
--- a/fs/ntfs/aops.c
+++ b/fs/ntfs/aops.c
@@ -355,6 +355,7 @@ static int ntfs_readpage(struct file *file, struct page *page)
u32 attr_len;
int err = 0;
+retry_readpage:
BUG_ON(!PageLocked(page));
/*
* This can potentially happen because we clear PageUptodate() during
@@ -408,6 +409,14 @@ static int ntfs_readpage(struct file *file, struct page *page)
err = PTR_ERR(mrec);
goto err_out;
}
+ /*
+ * If a parallel write made the attribute non-resident, drop the mft
+ * record and retry the readpage.
+ */
+ if (unlikely(NInoNonResident(ni))) {
+ unmap_mft_record(base_ni);
+ goto retry_readpage;
+ }
ctx = ntfs_attr_get_search_ctx(base_ni, mrec);
if (unlikely(!ctx)) {
err = -ENOMEM;
@@ -1248,6 +1257,7 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
u32 attr_len;
int err;
+retry_writepage:
BUG_ON(!PageLocked(page));
i_size = i_size_read(vi);
/* Is the page fully outside i_size? (truncate in progress) */
@@ -1338,6 +1348,14 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
ctx = NULL;
goto err_out;
}
+ /*
+ * If a parallel write made the attribute non-resident, drop the mft
+ * record and retry the writepage.
+ */
+ if (unlikely(NInoNonResident(ni))) {
+ unmap_mft_record(base_ni);
+ goto retry_writepage;
+ }
ctx = ntfs_attr_get_search_ctx(base_ni, m);
if (unlikely(!ctx)) {
err = -ENOMEM;
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index 3b9de404021..41859343a0c 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -1376,19 +1376,6 @@ int ntfs_attr_make_non_resident(ntfs_inode *ni)
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.
@@ -1399,7 +1386,10 @@ int ntfs_attr_make_non_resident(ntfs_inode *ni)
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. */
+ /*
+ * FIXME: For now just clear all of these as we do not support them
+ * when writing.
+ */
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. */
@@ -1422,6 +1412,25 @@ int ntfs_attr_make_non_resident(ntfs_inode *ni)
err);
goto undo_err_out;
}
+ /* Setup the in-memory attribute structure to be non-resident. */
+ /*
+ * FIXME: For now just clear all of these as we do not support them
+ * when writing.
+ */
+ NInoClearSparse(ni);
+ NInoClearEncrypted(ni);
+ NInoClearCompressed(ni);
+ ni->runlist.rl = rl;
+ write_lock_irqsave(&ni->size_lock, flags);
+ ni->allocated_size = new_size;
+ 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);
@@ -1431,6 +1440,7 @@ int ntfs_attr_make_non_resident(ntfs_inode *ni)
if (page) {
set_page_dirty(page);
unlock_page(page);
+ mark_page_accessed(page);
page_cache_release(page);
}
ntfs_debug("Done.");
@@ -1492,11 +1502,10 @@ undo_err_out:
memcpy((u8*)a + mp_ofs, kaddr, attr_size);
kunmap_atomic(kaddr, KM_USER0);
}
- /* Finally setup the ntfs inode appropriately. */
+ /* 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);
- 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);