int ret = 0;
BUG_ON(!PageLocked(page));
- if (PageDirty(page) || PageWriteback(page))
+ if (PageWriteback(page))
return 0;
if (mapping == NULL) { /* can this still happen? */
spin_lock(&mapping->private_lock);
ret = drop_buffers(page, &buffers_to_free);
spin_unlock(&mapping->private_lock);
+
+ /*
+ * If the filesystem writes its buffers by hand (eg ext3)
+ * then we can have clean buffers against a dirty page. We
+ * clean the page here; otherwise the VM will never notice
+ * that the filesystem did any IO at all.
+ *
+ * Also, during truncate, discard_buffer will have marked all
+ * the page's buffers clean. We discover that here and clean
+ * the page also.
+ */
+ if (ret)
+ cancel_dirty_page(page, PAGE_CACHE_SIZE);
out:
if (buffers_to_free) {
struct buffer_head *bh = buffers_to_free;
do_invalidatepage(page, partial);
}
+/*
+ * This cancels just the dirty bit on the kernel page itself, it
+ * does NOT actually remove dirty bits on any mmap's that may be
+ * around. It also leaves the page tagged dirty, so any sync
+ * activity will still find it on the dirty lists, and in particular,
+ * clear_page_dirty_for_io() will still look at the dirty bits in
+ * the VM.
+ *
+ * Doing this should *normally* only ever be done when a page
+ * is truncated, and is not actually mapped anywhere at all. However,
+ * fs/buffer.c does this when it notices that somebody has cleaned
+ * out all the buffers on a page without actually doing it through
+ * the VM. Can you say "ext3 is horribly ugly"? Tought you could.
+ */
void cancel_dirty_page(struct page *page, unsigned int account_size)
{
- /* If we're cancelling the page, it had better not be mapped any more */
- if (page_mapped(page)) {
- static unsigned int warncount;
-
- WARN_ON(++warncount < 5);
- }
-
if (TestClearPageDirty(page)) {
struct address_space *mapping = page->mapping;
if (mapping && mapping_cap_account_dirty(mapping)) {