Commit d586a8d9 authored by Anton Altaparmakov's avatar Anton Altaparmakov

NTFS: 2.1.22 - Many bug and race fixes and error handling improvements.

- Cleanup fs/ntfs/aops.c::ntfs_{read,write}page() since we know that a
  resident attribute will be smaller than a page which makes the code
  simpler.  Also make the code more tolerant to concurrent ->truncate.
Signed-off-by: default avatarAnton Altaparmakov <aia21@cantab.net>
parent bf71a676
...@@ -432,6 +432,9 @@ ChangeLog ...@@ -432,6 +432,9 @@ ChangeLog
Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog. Note, a technical ChangeLog aimed at kernel hackers is in fs/ntfs/ChangeLog.
2.1.22:
- Improve handling of ntfs volumes with errors.
- Fix various bugs and race conditions.
2.1.21: 2.1.21:
- Fix several race conditions and various other bugs. - Fix several race conditions and various other bugs.
- Many internal cleanups, code reorganization, optimizations, and mft - Many internal cleanups, code reorganization, optimizations, and mft
......
...@@ -25,7 +25,7 @@ ToDo/Notes: ...@@ -25,7 +25,7 @@ ToDo/Notes:
- Enable the code for setting the NT4 compatibility flag when we start - Enable the code for setting the NT4 compatibility flag when we start
making NTFS 1.2 specific modifications. making NTFS 1.2 specific modifications.
2.1.22-WIP 2.1.22 - Many bug and race fixes and error handling improvements.
- Improve error handling in fs/ntfs/inode.c::ntfs_truncate(). - Improve error handling in fs/ntfs/inode.c::ntfs_truncate().
- Change fs/ntfs/inode.c::ntfs_truncate() to return an error code - Change fs/ntfs/inode.c::ntfs_truncate() to return an error code
...@@ -85,6 +85,9 @@ ToDo/Notes: ...@@ -85,6 +85,9 @@ ToDo/Notes:
complete runlist for the mft mirror is always mapped into memory. complete runlist for the mft mirror is always mapped into memory.
- Add creation of buffers to fs/ntfs/mft.c::ntfs_sync_mft_mirror(). - Add creation of buffers to fs/ntfs/mft.c::ntfs_sync_mft_mirror().
- Improve error handling in fs/ntfs/aops.c::ntfs_{read,write}_block(). - Improve error handling in fs/ntfs/aops.c::ntfs_{read,write}_block().
- Cleanup fs/ntfs/aops.c::ntfs_{read,write}page() since we know that a
resident attribute will be smaller than a page which makes the code
simpler. Also make the code more tolerant to concurrent ->truncate.
2.1.21 - Fix some races and bugs, rewrite mft write code, add mft allocator. 2.1.21 - Fix some races and bugs, rewrite mft write code, add mft allocator.
......
...@@ -6,7 +6,7 @@ ntfs-objs := aops.o attrib.o collate.o compress.o debug.o dir.o file.o \ ...@@ -6,7 +6,7 @@ ntfs-objs := aops.o attrib.o collate.o compress.o debug.o dir.o file.o \
index.o inode.o mft.o mst.o namei.o runlist.o super.o sysctl.o \ index.o inode.o mft.o mst.o namei.o runlist.o super.o sysctl.o \
unistr.o upcase.o unistr.o upcase.o
EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.22-WIP\" EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.22\"
ifeq ($(CONFIG_NTFS_DEBUG),y) ifeq ($(CONFIG_NTFS_DEBUG),y)
EXTRA_CFLAGS += -DDEBUG EXTRA_CFLAGS += -DDEBUG
......
...@@ -341,7 +341,7 @@ static int ntfs_read_block(struct page *page) ...@@ -341,7 +341,7 @@ static int ntfs_read_block(struct page *page)
*/ */
static int ntfs_readpage(struct file *file, struct page *page) static int ntfs_readpage(struct file *file, struct page *page)
{ {
s64 attr_pos; loff_t i_size;
ntfs_inode *ni, *base_ni; ntfs_inode *ni, *base_ni;
u8 *kaddr; u8 *kaddr;
ntfs_attr_search_ctx *ctx; ntfs_attr_search_ctx *ctx;
...@@ -350,7 +350,6 @@ static int ntfs_readpage(struct file *file, struct page *page) ...@@ -350,7 +350,6 @@ static int ntfs_readpage(struct file *file, struct page *page)
int err = 0; int err = 0;
BUG_ON(!PageLocked(page)); BUG_ON(!PageLocked(page));
/* /*
* This can potentially happen because we clear PageUptodate() during * This can potentially happen because we clear PageUptodate() during
* ntfs_writepage() of MstProtected() attributes. * ntfs_writepage() of MstProtected() attributes.
...@@ -359,7 +358,6 @@ static int ntfs_readpage(struct file *file, struct page *page) ...@@ -359,7 +358,6 @@ static int ntfs_readpage(struct file *file, struct page *page)
unlock_page(page); unlock_page(page);
return 0; return 0;
} }
ni = NTFS_I(page->mapping->host); ni = NTFS_I(page->mapping->host);
/* NInoNonResident() == NInoIndexAllocPresent() */ /* NInoNonResident() == NInoIndexAllocPresent() */
...@@ -381,12 +379,23 @@ static int ntfs_readpage(struct file *file, struct page *page) ...@@ -381,12 +379,23 @@ static int ntfs_readpage(struct file *file, struct page *page)
/* Normal data stream. */ /* Normal data stream. */
return ntfs_read_block(page); return ntfs_read_block(page);
} }
/* Attribute is resident, implying it is not compressed or encrypted. */ /*
* Attribute is resident, implying it is not compressed or encrypted.
* This also means the attribute is smaller than an mft record and
* hence smaller than a page, so can simply zero out any pages with
* index above 0. We can also do this if the file size is 0.
*/
if (unlikely(page->index > 0 || !i_size_read(VFS_I(ni)))) {
kaddr = kmap_atomic(page, KM_USER0);
memset(kaddr, 0, PAGE_CACHE_SIZE);
flush_dcache_page(page);
kunmap_atomic(kaddr, KM_USER0);
goto done;
}
if (!NInoAttr(ni)) if (!NInoAttr(ni))
base_ni = ni; base_ni = ni;
else else
base_ni = ni->ext.base_ntfs_ino; base_ni = ni->ext.base_ntfs_ino;
/* Map, pin, and lock the mft record. */ /* Map, pin, and lock the mft record. */
mrec = map_mft_record(base_ni); mrec = map_mft_record(base_ni);
if (IS_ERR(mrec)) { if (IS_ERR(mrec)) {
...@@ -402,35 +411,25 @@ static int ntfs_readpage(struct file *file, struct page *page) ...@@ -402,35 +411,25 @@ static int ntfs_readpage(struct file *file, struct page *page)
CASE_SENSITIVE, 0, NULL, 0, ctx); CASE_SENSITIVE, 0, NULL, 0, ctx);
if (unlikely(err)) if (unlikely(err))
goto put_unm_err_out; goto put_unm_err_out;
/* Starting position of the page within the attribute value. */
attr_pos = page->index << PAGE_CACHE_SHIFT;
/* The total length of the attribute value. */
attr_len = le32_to_cpu(ctx->attr->data.resident.value_length); attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
i_size = i_size_read(VFS_I(ni));
if (unlikely(attr_len > i_size))
attr_len = i_size;
kaddr = kmap_atomic(page, KM_USER0); kaddr = kmap_atomic(page, KM_USER0);
/* Copy over in bounds data, zeroing the remainder of the page. */ /* Copy the data to the page. */
if (attr_pos < attr_len) { memcpy(kaddr, (u8*)ctx->attr +
u32 bytes = attr_len - attr_pos; le16_to_cpu(ctx->attr->data.resident.value_offset),
if (bytes > PAGE_CACHE_SIZE) attr_len);
bytes = PAGE_CACHE_SIZE; /* Zero the remainder of the page. */
else if (bytes < PAGE_CACHE_SIZE) memset(kaddr + attr_len, 0, PAGE_CACHE_SIZE - attr_len);
memset(kaddr + bytes, 0, PAGE_CACHE_SIZE - bytes);
/* Copy the data to the page. */
memcpy(kaddr, attr_pos + (char*)ctx->attr +
le16_to_cpu(
ctx->attr->data.resident.value_offset), bytes);
} else
memset(kaddr, 0, PAGE_CACHE_SIZE);
flush_dcache_page(page); flush_dcache_page(page);
kunmap_atomic(kaddr, KM_USER0); kunmap_atomic(kaddr, KM_USER0);
SetPageUptodate(page);
put_unm_err_out: put_unm_err_out:
ntfs_attr_put_search_ctx(ctx); ntfs_attr_put_search_ctx(ctx);
unm_err_out: unm_err_out:
unmap_mft_record(base_ni); unmap_mft_record(base_ni);
done:
SetPageUptodate(page);
err_out: err_out:
unlock_page(page); unlock_page(page);
return err; return err;
...@@ -1223,21 +1222,22 @@ static int ntfs_write_mst_block(struct page *page, ...@@ -1223,21 +1222,22 @@ static int ntfs_write_mst_block(struct page *page,
*/ */
static int ntfs_writepage(struct page *page, struct writeback_control *wbc) static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
{ {
s64 attr_pos; loff_t i_size;
struct inode *vi; struct inode *vi;
ntfs_inode *ni, *base_ni; ntfs_inode *ni, *base_ni;
char *kaddr; char *kaddr;
ntfs_attr_search_ctx *ctx; ntfs_attr_search_ctx *ctx;
MFT_RECORD *m; MFT_RECORD *m;
u32 attr_len, bytes; u32 attr_len;
int err; int err;
BUG_ON(!PageLocked(page)); BUG_ON(!PageLocked(page));
vi = page->mapping->host; vi = page->mapping->host;
i_size = i_size_read(vi);
/* Is the page fully outside i_size? (truncate in progress) */ /* Is the page fully outside i_size? (truncate in progress) */
if (unlikely(page->index >= (vi->i_size + PAGE_CACHE_SIZE - 1) >> if (unlikely(page->index >= (i_size + PAGE_CACHE_SIZE - 1) >>
PAGE_CACHE_SHIFT)) { PAGE_CACHE_SHIFT)) {
/* /*
* The page may have dirty, unmapped buffers. Make them * The page may have dirty, unmapped buffers. Make them
...@@ -1248,7 +1248,6 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc) ...@@ -1248,7 +1248,6 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
ntfs_debug("Write outside i_size - truncated?"); ntfs_debug("Write outside i_size - truncated?");
return 0; return 0;
} }
ni = NTFS_I(vi); ni = NTFS_I(vi);
/* NInoNonResident() == NInoIndexAllocPresent() */ /* NInoNonResident() == NInoIndexAllocPresent() */
...@@ -1284,9 +1283,9 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc) ...@@ -1284,9 +1283,9 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
} }
} }
/* We have to zero every time due to mmap-at-end-of-file. */ /* We have to zero every time due to mmap-at-end-of-file. */
if (page->index >= (vi->i_size >> PAGE_CACHE_SHIFT)) { if (page->index >= (i_size >> PAGE_CACHE_SHIFT)) {
/* The page straddles i_size. */ /* The page straddles i_size. */
unsigned int ofs = vi->i_size & ~PAGE_CACHE_MASK; unsigned int ofs = i_size & ~PAGE_CACHE_MASK;
kaddr = kmap_atomic(page, KM_USER0); kaddr = kmap_atomic(page, KM_USER0);
memset(kaddr + ofs, 0, PAGE_CACHE_SIZE - ofs); memset(kaddr + ofs, 0, PAGE_CACHE_SIZE - ofs);
flush_dcache_page(page); flush_dcache_page(page);
...@@ -1300,16 +1299,25 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc) ...@@ -1300,16 +1299,25 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
} }
/* /*
* Attribute is resident, implying it is not compressed, encrypted, * Attribute is resident, implying it is not compressed, encrypted,
* sparse, or mst protected. * sparse, or mst protected. This also means the attribute is smaller
* than an mft record and hence smaller than a page, so can simply
* return error on any pages with index above 0.
*/ */
BUG_ON(page_has_buffers(page)); BUG_ON(page_has_buffers(page));
BUG_ON(!PageUptodate(page)); BUG_ON(!PageUptodate(page));
if (unlikely(page->index > 0)) {
ntfs_error(vi->i_sb, "BUG()! page->index (0x%lx) > 0. "
"Aborting write.", page->index);
BUG_ON(PageWriteback(page));
set_page_writeback(page);
unlock_page(page);
end_page_writeback(page);
return -EIO;
}
if (!NInoAttr(ni)) if (!NInoAttr(ni))
base_ni = ni; base_ni = ni;
else else
base_ni = ni->ext.base_ntfs_ino; base_ni = ni->ext.base_ntfs_ino;
/* Map, pin, and lock the mft record. */ /* Map, pin, and lock the mft record. */
m = map_mft_record(base_ni); m = map_mft_record(base_ni);
if (IS_ERR(m)) { if (IS_ERR(m)) {
...@@ -1327,32 +1335,6 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc) ...@@ -1327,32 +1335,6 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
CASE_SENSITIVE, 0, NULL, 0, ctx); CASE_SENSITIVE, 0, NULL, 0, ctx);
if (unlikely(err)) if (unlikely(err))
goto err_out; goto err_out;
/* Starting position of the page within the attribute value. */
attr_pos = page->index << PAGE_CACHE_SHIFT;
/* The total length of the attribute value. */
attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
if (unlikely(vi->i_size != attr_len)) {
ntfs_error(vi->i_sb, "BUG()! i_size (0x%llx) doesn't match "
"attr_len (0x%x). Aborting write.", vi->i_size,
attr_len);
err = -EIO;
goto err_out;
}
if (unlikely(attr_pos >= attr_len)) {
ntfs_error(vi->i_sb, "BUG()! attr_pos (0x%llx) > attr_len "
"(0x%x). Aborting write.",
(unsigned long long)attr_pos, attr_len);
err = -EIO;
goto err_out;
}
bytes = attr_len - attr_pos;
if (unlikely(bytes > PAGE_CACHE_SIZE))
bytes = PAGE_CACHE_SIZE;
/* /*
* Keep the VM happy. This must be done otherwise the radix-tree tag * Keep the VM happy. This must be done otherwise the radix-tree tag
* PAGECACHE_TAG_DIRTY remains set even though the page is clean. * PAGECACHE_TAG_DIRTY remains set even though the page is clean.
...@@ -1384,26 +1366,30 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc) ...@@ -1384,26 +1366,30 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
* TODO: ntfs_truncate(), others? * TODO: ntfs_truncate(), others?
*/ */
attr_len = le32_to_cpu(ctx->attr->data.resident.value_length);
i_size = i_size_read(VFS_I(ni));
kaddr = kmap_atomic(page, KM_USER0); kaddr = kmap_atomic(page, KM_USER0);
if (unlikely(attr_len > i_size)) {
/* Zero out of bounds area in the mft record. */
memset((u8*)ctx->attr + le16_to_cpu(
ctx->attr->data.resident.value_offset) +
i_size, 0, attr_len - i_size);
attr_len = i_size;
}
/* Copy the data from the page to the mft record. */ /* Copy the data from the page to the mft record. */
memcpy((u8*)ctx->attr + le16_to_cpu( memcpy((u8*)ctx->attr +
ctx->attr->data.resident.value_offset) + attr_pos, le16_to_cpu(ctx->attr->data.resident.value_offset),
kaddr, bytes); kaddr, attr_len);
flush_dcache_mft_record_page(ctx->ntfs_ino); flush_dcache_mft_record_page(ctx->ntfs_ino);
#if 0 /* Zero out of bounds area in the page cache page. */
/* Zero out of bounds area. */ memset(kaddr + attr_len, 0, PAGE_CACHE_SIZE - attr_len);
if (likely(bytes < PAGE_CACHE_SIZE)) { flush_dcache_page(page);
memset(kaddr + bytes, 0, PAGE_CACHE_SIZE - bytes);
flush_dcache_page(page);
}
#endif
kunmap_atomic(kaddr, KM_USER0); kunmap_atomic(kaddr, KM_USER0);
end_page_writeback(page); end_page_writeback(page);
/* Mark the mft record dirty, so it gets written back. */ /* Mark the mft record dirty, so it gets written back. */
mark_mft_record_dirty(ctx->ntfs_ino); mark_mft_record_dirty(ctx->ntfs_ino);
ntfs_attr_put_search_ctx(ctx); ntfs_attr_put_search_ctx(ctx);
unmap_mft_record(base_ni); unmap_mft_record(base_ni);
return 0; return 0;
...@@ -1413,13 +1399,13 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc) ...@@ -1413,13 +1399,13 @@ static int ntfs_writepage(struct page *page, struct writeback_control *wbc)
"page so we try again later."); "page so we try again later.");
/* /*
* Put the page back on mapping->dirty_pages, but leave its * Put the page back on mapping->dirty_pages, but leave its
* buffer's dirty state as-is. * buffers' dirty state as-is.
*/ */
redirty_page_for_writepage(wbc, page); redirty_page_for_writepage(wbc, page);
err = 0; err = 0;
} else { } else {
ntfs_error(vi->i_sb, "Resident attribute write failed with " ntfs_error(vi->i_sb, "Resident attribute write failed with "
"error %i. Setting page error flag.", -err); "error %i. Setting page error flag.", err);
SetPageError(page); SetPageError(page);
} }
unlock_page(page); unlock_page(page);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment