/**
* mft.c - NTFS kernel mft record operations. Part of the Linux-NTFS project.
*
- * Copyright (c) 2001-2003 Anton Altaparmakov
+ * Copyright (c) 2001-2004 Anton Altaparmakov
* Copyright (c) 2002 Richard Russon
*
* This program/include file is free software; you can redistribute it and/or
* by the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
- * This program/include file is distributed in the hope that it will be
- * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * This program/include file is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program (in the main directory of the Linux-NTFS
+ * along with this program (in the main directory of the Linux-NTFS
* distribution in the file COPYING); if not, write to the Free Software
* Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
*/
extern int ntfs_readpage(struct file *, struct page *);
+#ifdef NTFS_RW
+/**
+ * ntfs_mft_writepage - forward declaration, function is further below
+ */
+static int ntfs_mft_writepage(struct page *page, struct writeback_control *wbc);
+#endif /* NTFS_RW */
+
/**
* ntfs_mft_aops - address space operations for access to $MFT
*
.readpage = ntfs_readpage, /* Fill page with data. */
.sync_page = block_sync_page, /* Currently, just unplugs the
disk request queue. */
+#ifdef NTFS_RW
+ .writepage = ntfs_mft_writepage, /* Write out the dirty mft
+ records in a page. */
+#endif /* NTFS_RW */
};
/**
return m;
}
+#ifdef NTFS_RW
+
+/**
+ * __mark_mft_record_dirty - set the mft record and the page containing it dirty
+ * @ni: ntfs inode describing the mapped mft record
+ *
+ * Internal function. Users should call mark_mft_record_dirty() instead.
+ *
+ * Set the mapped (extent) mft record of the (base or extent) ntfs inode @ni,
+ * as well as the page containing the mft record, dirty. Also, mark the base
+ * vfs inode dirty. This ensures that any changes to the mft record are
+ * written out to disk.
+ *
+ * NOTE: We only set I_DIRTY_SYNC and I_DIRTY_DATASYNC (and not I_DIRTY_PAGES)
+ * on the base vfs inode, because even though file data may have been modified,
+ * it is dirty in the inode meta data rather than the data page cache of the
+ * inode, and thus there are no data pages that need writing out. Therefore, a
+ * full mark_inode_dirty() is overkill. A mark_inode_dirty_sync(), on the
+ * other hand, is not sufficient, because I_DIRTY_DATASYNC needs to be set to
+ * ensure ->write_inode is called from generic_osync_inode() and this needs to
+ * happen or the file data would not necessarily hit the device synchronously,
+ * even though the vfs inode has the O_SYNC flag set. Also, I_DIRTY_DATASYNC
+ * simply "feels" better than just I_DIRTY_SYNC, since the file data has not
+ * actually hit the block device yet, which is not what I_DIRTY_SYNC on its own
+ * would suggest.
+ */
+void __mark_mft_record_dirty(ntfs_inode *ni)
+{
+ struct page *page = ni->page;
+ ntfs_inode *base_ni;
+
+ ntfs_debug("Entering for inode 0x%lx.", ni->mft_no);
+ BUG_ON(!page);
+ BUG_ON(NInoAttr(ni));
+
+ /*
+ * Set the page containing the mft record dirty. This also marks the
+ * $MFT inode dirty (I_DIRTY_PAGES).
+ */
+ __set_page_dirty_nobuffers(page);
+
+ /* Determine the base vfs inode and mark it dirty, too. */
+ down(&ni->extent_lock);
+ if (likely(ni->nr_extents >= 0))
+ base_ni = ni;
+ else
+ base_ni = ni->ext.base_ntfs_ino;
+ up(&ni->extent_lock);
+ __mark_inode_dirty(VFS_I(base_ni), I_DIRTY_SYNC | I_DIRTY_DATASYNC);
+}
+
+static const char *ntfs_please_email = "Please email "
+ "linux-ntfs-dev@lists.sourceforge.net and say that you saw "
+ "this message. Thank you.";
+
+/**
+ * sync_mft_mirror_umount - synchronise an mft record to the mft mirror
+ * @ni: ntfs inode whose mft record to synchronize
+ * @m: mapped, mst protected (extent) mft record to synchronize
+ *
+ * Write the mapped, mst protected (extent) mft record @m described by the
+ * (regular or extent) ntfs inode @ni to the mft mirror ($MFTMirr) bypassing
+ * the page cache and the $MFTMirr inode itself.
+ *
+ * This function is only for use at umount time when the mft mirror inode has
+ * already been disposed off. We BUG() if we are called while the mft mirror
+ * inode is still attached to the volume.
+ *
+ * On success return 0. On error return -errno.
+ *
+ * NOTE: This function is not implemented yet as I am not convinced it can
+ * actually be triggered considering the sequence of commits we do in super.c::
+ * ntfs_put_super(). But just in case we provide this place holder as the
+ * alternative would be either to BUG() or to get a NULL pointer dereference
+ * and Oops.
+ */
+static int sync_mft_mirror_umount(ntfs_inode *ni, MFT_RECORD *m)
+{
+ ntfs_volume *vol = ni->vol;
+
+ BUG_ON(vol->mftmirr_ino);
+ ntfs_error(vol->sb, "Umount time mft mirror syncing is not "
+ "implemented yet. %s", ntfs_please_email);
+ return -EOPNOTSUPP;
+}
+
+/**
+ * sync_mft_mirror - synchronize an mft record to the mft mirror
+ * @ni: ntfs inode whose mft record to synchronize
+ * @m: mapped, mst protected (extent) mft record to synchronize
+ * @sync: if true, wait for i/o completion
+ *
+ * Write the mapped, mst protected (extent) mft record @m described by the
+ * (regular or extent) ntfs inode @ni to the mft mirror ($MFTMirr).
+ *
+ * On success return 0. On error return -errno and set the volume errors flag
+ * in the ntfs_volume to which @ni belongs.
+ *
+ * NOTE: We always perform synchronous i/o and ignore the @sync parameter.
+ *
+ * TODO: If @sync is false, want to do truly asynchronous i/o, i.e. just
+ * schedule i/o via ->writepage or do it via kntfsd or whatever.
+ */
+static int sync_mft_mirror(ntfs_inode *ni, MFT_RECORD *m, int sync)
+{
+ ntfs_volume *vol = ni->vol;
+ struct page *page;
+ unsigned int blocksize = vol->sb->s_blocksize;
+ int max_bhs = vol->mft_record_size / blocksize;
+ struct buffer_head *bhs[max_bhs];
+ struct buffer_head *bh, *head;
+ u8 *kmirr;
+ unsigned int block_start, block_end, m_start, m_end;
+ int i_bhs, nr_bhs, err = 0;
+
+ ntfs_debug("Entering for inode 0x%lx.", ni->mft_no);
+ BUG_ON(!max_bhs);
+ if (unlikely(!vol->mftmirr_ino)) {
+ /* This could happen during umount... */
+ err = sync_mft_mirror_umount(ni, m);
+ if (likely(!err))
+ return err;
+ goto err_out;
+ }
+ /* Get the page containing the mirror copy of the mft record @m. */
+ page = ntfs_map_page(vol->mftmirr_ino->i_mapping, ni->mft_no >>
+ (PAGE_CACHE_SHIFT - vol->mft_record_size_bits));
+ if (unlikely(IS_ERR(page))) {
+ ntfs_error(vol->sb, "Failed to map mft mirror page.");
+ err = PTR_ERR(page);
+ goto err_out;
+ }
+ /*
+ * Exclusion against other writers. This should never be a problem
+ * since the page in which the mft record @m resides is also locked and
+ * hence any other writers would be held up there but it is better to
+ * make sure no one is writing from elsewhere.
+ */
+ lock_page(page);
+ /* The address in the page of the mirror copy of the mft record @m. */
+ kmirr = page_address(page) + ((ni->mft_no << vol->mft_record_size_bits)
+ & ~PAGE_CACHE_MASK);
+ /* Copy the mst protected mft record to the mirror. */
+ memcpy(kmirr, m, vol->mft_record_size);
+ /* Make sure we have mapped buffers. */
+ if (!page_has_buffers(page)) {
+no_buffers_err_out:
+ ntfs_error(vol->sb, "Writing mft mirror records without "
+ "existing buffers is not implemented yet. %s",
+ ntfs_please_email);
+ err = -EOPNOTSUPP;
+ goto unlock_err_out;
+ }
+ bh = head = page_buffers(page);
+ if (!bh)
+ goto no_buffers_err_out;
+ nr_bhs = 0;
+ block_start = 0;
+ m_start = kmirr - (u8*)page_address(page);
+ m_end = m_start + vol->mft_record_size;
+ do {
+ block_end = block_start + blocksize;
+ /*
+ * If the buffer is outside the mft record, just skip it,
+ * clearing it if it is dirty to make sure it is not written
+ * out. It should never be marked dirty but better be safe.
+ */
+ if ((block_end <= m_start) || (block_start >= m_end)) {
+ if (buffer_dirty(bh)) {
+ ntfs_warning(vol->sb, "Clearing dirty mft "
+ "record page buffer. %s",
+ ntfs_please_email);
+ clear_buffer_dirty(bh);
+ }
+ continue;
+ }
+ if (!buffer_mapped(bh)) {
+ ntfs_error(vol->sb, "Writing mft mirror records "
+ "without existing mapped buffers is "
+ "not implemented yet. %s",
+ ntfs_please_email);
+ err = -EOPNOTSUPP;
+ continue;
+ }
+ if (!buffer_uptodate(bh)) {
+ ntfs_error(vol->sb, "Writing mft mirror records "
+ "without existing uptodate buffers is "
+ "not implemented yet. %s",
+ ntfs_please_email);
+ err = -EOPNOTSUPP;
+ continue;
+ }
+ BUG_ON(!nr_bhs && (m_start != block_start));
+ BUG_ON(nr_bhs >= max_bhs);
+ bhs[nr_bhs++] = bh;
+ BUG_ON((nr_bhs >= max_bhs) && (m_end != block_end));
+ } while (block_start = block_end, (bh = bh->b_this_page) != head);
+ if (likely(!err)) {
+ /* Lock buffers and start synchronous write i/o on them. */
+ for (i_bhs = 0; i_bhs < nr_bhs; i_bhs++) {
+ struct buffer_head *tbh = bhs[i_bhs];
+
+ if (unlikely(test_set_buffer_locked(tbh)))
+ BUG();
+ BUG_ON(!buffer_uptodate(tbh));
+ if (buffer_dirty(tbh))
+ clear_buffer_dirty(tbh);
+ get_bh(tbh);
+ tbh->b_end_io = end_buffer_write_sync;
+ submit_bh(WRITE, tbh);
+ }
+ /* Wait on i/o completion of buffers. */
+ for (i_bhs = 0; i_bhs < nr_bhs; i_bhs++) {
+ struct buffer_head *tbh = bhs[i_bhs];
+
+ wait_on_buffer(tbh);
+ if (unlikely(!buffer_uptodate(tbh))) {
+ err = -EIO;
+ /*
+ * Set the buffer uptodate so the page & buffer
+ * states don't become out of sync.
+ */
+ if (PageUptodate(page))
+ set_buffer_uptodate(tbh);
+ }
+ }
+ } else /* if (unlikely(err)) */ {
+ /* Clean the buffers. */
+ for (i_bhs = 0; i_bhs < nr_bhs; i_bhs++)
+ clear_buffer_dirty(bhs[i_bhs]);
+ }
+unlock_err_out:
+ /* Current state: all buffers are clean, unlocked, and uptodate. */
+ /* Remove the mst protection fixups again. */
+ post_write_mst_fixup((NTFS_RECORD*)kmirr);
+ flush_dcache_page(page);
+ unlock_page(page);
+ ntfs_unmap_page(page);
+ if (unlikely(err)) {
+ /* I/O error during writing. This is really bad! */
+ ntfs_error(vol->sb, "I/O error while writing mft mirror "
+ "record 0x%lx! You should unmount the volume "
+ "and run chkdsk or ntfsfix.", ni->mft_no);
+ goto err_out;
+ }
+ ntfs_debug("Done.");
+ return 0;
+err_out:
+ ntfs_error(vol->sb, "Failed to synchronize $MFTMirr (error code %i). "
+ "Volume will be left marked dirty on umount. Run "
+ "ntfsfix on the partition after umounting to correct "
+ "this.", -err);
+ /* We don't want to clear the dirty bit on umount. */
+ NVolSetErrors(vol);
+ return err;
+}
+
+/**
+ * write_mft_record_nolock - write out a mapped (extent) mft record
+ * @ni: ntfs inode describing the mapped (extent) mft record
+ * @m: mapped (extent) mft record to write
+ * @sync: if true, wait for i/o completion
+ *
+ * Write the mapped (extent) mft record @m described by the (regular or extent)
+ * ntfs inode @ni to backing store. If the mft record @m has a counterpart in
+ * the mft mirror, that is also updated.
+ *
+ * On success, clean the mft record and return 0. On error, leave the mft
+ * record dirty and return -errno. The caller should call make_bad_inode() on
+ * the base inode to ensure no more access happens to this inode. We do not do
+ * it here as the caller may want to finish writing other extent mft records
+ * first to minimize on-disk metadata inconsistencies.
+ *
+ * NOTE: We always perform synchronous i/o and ignore the @sync parameter.
+ * However, if the mft record has a counterpart in the mft mirror and @sync is
+ * true, we write the mft record, wait for i/o completion, and only then write
+ * the mft mirror copy. This ensures that if the system crashes either the mft
+ * or the mft mirror will contain a self-consistent mft record @m. If @sync is
+ * false on the other hand, we start i/o on both and then wait for completion
+ * on them. This provides a speedup but no longer guarantees that you will end
+ * up with a self-consistent mft record in the case of a crash but if you asked
+ * for asynchronous writing you probably do not care about that anyway.
+ *
+ * TODO: If @sync is false, want to do truly asynchronous i/o, i.e. just
+ * schedule i/o via ->writepage or do it via kntfsd or whatever.
+ */
+int write_mft_record_nolock(ntfs_inode *ni, MFT_RECORD *m, int sync)
+{
+ ntfs_volume *vol = ni->vol;
+ struct page *page = ni->page;
+ unsigned int blocksize = vol->sb->s_blocksize;
+ int max_bhs = vol->mft_record_size / blocksize;
+ struct buffer_head *bhs[max_bhs];
+ struct buffer_head *bh, *head;
+ unsigned int block_start, block_end, m_start, m_end;
+ int i_bhs, nr_bhs, err = 0;
+
+ ntfs_debug("Entering for inode 0x%lx.", ni->mft_no);
+ BUG_ON(NInoAttr(ni));
+ BUG_ON(!max_bhs);
+ BUG_ON(!page);
+ BUG_ON(!PageLocked(page));
+ /*
+ * If the ntfs_inode is clean no need to do anything. If it is dirty,
+ * mark it as clean now so that it can be redirtied later on if needed.
+ * There is no danger of races as as long as the caller is holding the
+ * locks for the mft record @m and the page it is in.
+ */
+ if (!NInoTestClearDirty(ni))
+ goto done;
+ /* Make sure we have mapped buffers. */
+ if (!page_has_buffers(page)) {
+no_buffers_err_out:
+ ntfs_error(vol->sb, "Writing mft records without existing "
+ "buffers is not implemented yet. %s",
+ ntfs_please_email);
+ err = -EOPNOTSUPP;
+ goto err_out;
+ }
+ bh = head = page_buffers(page);
+ if (!bh)
+ goto no_buffers_err_out;
+ nr_bhs = 0;
+ block_start = 0;
+ m_start = ni->page_ofs;
+ m_end = m_start + vol->mft_record_size;
+ do {
+ block_end = block_start + blocksize;
+ /*
+ * If the buffer is outside the mft record, just skip it,
+ * clearing it if it is dirty to make sure it is not written
+ * out. It should never be marked dirty but better be safe.
+ */
+ if ((block_end <= m_start) || (block_start >= m_end)) {
+ if (buffer_dirty(bh)) {
+ ntfs_warning(vol->sb, "Clearing dirty mft "
+ "record page buffer. %s",
+ ntfs_please_email);
+ clear_buffer_dirty(bh);
+ }
+ continue;
+ }
+ if (!buffer_mapped(bh)) {
+ ntfs_error(vol->sb, "Writing mft records without "
+ "existing mapped buffers is not "
+ "implemented yet. %s",
+ ntfs_please_email);
+ err = -EOPNOTSUPP;
+ continue;
+ }
+ if (!buffer_uptodate(bh)) {
+ ntfs_error(vol->sb, "Writing mft records without "
+ "existing uptodate buffers is not "
+ "implemented yet. %s",
+ ntfs_please_email);
+ err = -EOPNOTSUPP;
+ continue;
+ }
+ BUG_ON(!nr_bhs && (m_start != block_start));
+ BUG_ON(nr_bhs >= max_bhs);
+ bhs[nr_bhs++] = bh;
+ BUG_ON((nr_bhs >= max_bhs) && (m_end != block_end));
+ } while (block_start = block_end, (bh = bh->b_this_page) != head);
+ if (unlikely(err))
+ goto cleanup_out;
+ /* Apply the mst protection fixups. */
+ err = pre_write_mst_fixup((NTFS_RECORD*)m, vol->mft_record_size);
+ if (err) {
+ ntfs_error(vol->sb, "Failed to apply mst fixups!");
+ goto cleanup_out;
+ }
+ flush_dcache_mft_record_page(ni);
+ /* Lock buffers and start synchronous write i/o on them. */
+ for (i_bhs = 0; i_bhs < nr_bhs; i_bhs++) {
+ struct buffer_head *tbh = bhs[i_bhs];
+
+ if (unlikely(test_set_buffer_locked(tbh)))
+ BUG();
+ BUG_ON(!buffer_uptodate(tbh));
+ if (buffer_dirty(tbh))
+ clear_buffer_dirty(tbh);
+ get_bh(tbh);
+ tbh->b_end_io = end_buffer_write_sync;
+ submit_bh(WRITE, tbh);
+ }
+ /* Synchronize the mft mirror now if not @sync. */
+ if (!sync && ni->mft_no < vol->mftmirr_size)
+ sync_mft_mirror(ni, m, sync);
+ /* Wait on i/o completion of buffers. */
+ for (i_bhs = 0; i_bhs < nr_bhs; i_bhs++) {
+ struct buffer_head *tbh = bhs[i_bhs];
+
+ wait_on_buffer(tbh);
+ if (unlikely(!buffer_uptodate(tbh))) {
+ err = -EIO;
+ /*
+ * Set the buffer uptodate so the page & buffer states
+ * don't become out of sync.
+ */
+ if (PageUptodate(page))
+ set_buffer_uptodate(tbh);
+ }
+ }
+ /* If @sync, now synchronize the mft mirror. */
+ if (sync && ni->mft_no < vol->mftmirr_size)
+ sync_mft_mirror(ni, m, sync);
+ /* Remove the mst protection fixups again. */
+ post_write_mst_fixup((NTFS_RECORD*)m);
+ flush_dcache_mft_record_page(ni);
+ if (unlikely(err)) {
+ /* I/O error during writing. This is really bad! */
+ ntfs_error(vol->sb, "I/O error while writing mft record "
+ "0x%lx! Marking base inode as bad. You "
+ "should unmount the volume and run chkdsk.",
+ ni->mft_no);
+ goto err_out;
+ }
+done:
+ ntfs_debug("Done.");
+ return 0;
+cleanup_out:
+ /* Clean the buffers. */
+ for (i_bhs = 0; i_bhs < nr_bhs; i_bhs++)
+ clear_buffer_dirty(bhs[i_bhs]);
+err_out:
+ /*
+ * Current state: all buffers are clean, unlocked, and uptodate.
+ * The caller should mark the base inode as bad so that no more i/o
+ * happens. ->clear_inode() will still be invoked so all extent inodes
+ * and other allocated memory will be freed.
+ */
+ if (err == -ENOMEM) {
+ ntfs_error(vol->sb, "Not enough memory to write mft record. "
+ "Redirtying so the write is retried later.");
+ mark_mft_record_dirty(ni);
+ err = 0;
+ }
+ return err;
+}
+
+/**
+ * ntfs_mft_writepage - check if a metadata page contains dirty mft records
+ * @page: metadata page possibly containing dirty mft records
+ * @wbc: writeback control structure
+ *
+ * This is called from the VM when it wants to have a dirty $MFT/$DATA metadata
+ * page cache page cleaned. The VM has already locked the page and marked it
+ * clean. Instead of writing the page as a conventional ->writepage function
+ * would do, we check if the page still contains any dirty mft records (it must
+ * have done at some point in the past since the page was marked dirty) and if
+ * none are found, i.e. all mft records are clean, we unlock the page and
+ * return. The VM is then free to do with the page as it pleases. If on the
+ * other hand we do find any dirty mft records in the page, we redirty the page
+ * before unlocking it and returning so the VM knows that the page is still
+ * busy and cannot be thrown out.
+ *
+ * Note, we do not actually write any dirty mft records here because they are
+ * dirty inodes and hence will be written by the VFS inode dirty code paths.
+ * There is no need to write them from the VM page dirty code paths, too and in
+ * fact once we implement journalling it would be a complete nightmare having
+ * two code paths leading to mft record writeout.
+ */
+static int ntfs_mft_writepage(struct page *page, struct writeback_control *wbc)
+{
+ struct inode *mft_vi = page->mapping->host;
+ struct super_block *sb = mft_vi->i_sb;
+ ntfs_volume *vol = NTFS_SB(sb);
+ u8 *maddr;
+ MFT_RECORD *m;
+ ntfs_inode **extent_nis;
+ unsigned long mft_no;
+ int nr, i, j;
+ BOOL is_dirty = FALSE;
+
+ BUG_ON(mft_vi != vol->mft_ino);
+ /* The first mft record number in the page. */
+ mft_no = page->index << (PAGE_CACHE_SHIFT - vol->mft_record_size_bits);
+ /* Number of mft records in the page. */
+ nr = PAGE_CACHE_SIZE >> vol->mft_record_size_bits;
+ BUG_ON(!nr);
+ ntfs_debug("Entering for %i inodes starting at 0x%lx.", nr, mft_no);
+ /* Iterate over the mft records in the page looking for a dirty one. */
+ maddr = (u8*)kmap(page);
+ for (i = 0; i < nr; ++i, ++mft_no, maddr += vol->mft_record_size) {
+ struct inode *vi;
+ ntfs_inode *ni, *eni;
+ ntfs_attr na;
+
+ na.mft_no = mft_no;
+ na.name = NULL;
+ na.name_len = 0;
+ na.type = AT_UNUSED;
+ /*
+ * Check if the inode corresponding to this mft record is in
+ * the VFS inode cache and obtain a reference to it if it is.
+ */
+ ntfs_debug("Looking for inode 0x%lx in icache.", mft_no);
+ /*
+ * For inode 0, i.e. $MFT itself, we cannot use ilookup5() from
+ * here or we deadlock because the inode is already locked by
+ * the kernel (fs/fs-writeback.c::__sync_single_inode()) and
+ * ilookup5() waits until the inode is unlocked before
+ * returning it and it never gets unlocked because
+ * ntfs_mft_writepage() never returns. )-: Fortunately, we
+ * have inode 0 pinned in icache for the duration of the mount
+ * so we can access it directly.
+ */
+ if (!mft_no) {
+ /* Balance the below iput(). */
+ vi = igrab(mft_vi);
+ BUG_ON(vi != mft_vi);
+ } else
+ vi = ilookup5(sb, mft_no, (test_t)ntfs_test_inode, &na);
+ if (vi) {
+ ntfs_debug("Inode 0x%lx is in icache.", mft_no);
+ /* The inode is in icache. Check if it is dirty. */
+ ni = NTFS_I(vi);
+ if (!NInoDirty(ni)) {
+ /* The inode is not dirty, skip this record. */
+ ntfs_debug("Inode 0x%lx is not dirty, "
+ "continuing search.", mft_no);
+ iput(vi);
+ continue;
+ }
+ ntfs_debug("Inode 0x%lx is dirty, aborting search.",
+ mft_no);
+ /* The inode is dirty, no need to search further. */
+ iput(vi);
+ is_dirty = TRUE;
+ break;
+ }
+ ntfs_debug("Inode 0x%lx is not in icache.", mft_no);
+ /* The inode is not in icache. */
+ /* Skip the record if it is not a mft record (type "FILE"). */
+ if (!ntfs_is_mft_recordp(maddr)) {
+ ntfs_debug("Mft record 0x%lx is not a FILE record, "
+ "continuing search.", mft_no);
+ continue;
+ }
+ m = (MFT_RECORD*)maddr;
+ /*
+ * Skip the mft record if it is not in use. FIXME: What about
+ * deleted/deallocated (extent) inodes? (AIA)
+ */
+ if (!(m->flags & MFT_RECORD_IN_USE)) {
+ ntfs_debug("Mft record 0x%lx is not in use, "
+ "continuing search.", mft_no);
+ continue;
+ }
+ /* Skip the mft record if it is a base inode. */
+ if (!m->base_mft_record) {
+ ntfs_debug("Mft record 0x%lx is a base record, "
+ "continuing search.", mft_no);
+ continue;
+ }
+ /*
+ * This is an extent mft record. Check if the inode
+ * corresponding to its base mft record is in icache.
+ */
+ na.mft_no = MREF_LE(m->base_mft_record);
+ ntfs_debug("Mft record 0x%lx is an extent record. Looking "
+ "for base inode 0x%lx in icache.", mft_no,
+ na.mft_no);
+ vi = ilookup5(sb, na.mft_no, (test_t)ntfs_test_inode,
+ &na);
+ if (!vi) {
+ /*
+ * The base inode is not in icache. Skip this extent
+ * mft record.
+ */
+ ntfs_debug("Base inode 0x%lx is not in icache, "
+ "continuing search.", na.mft_no);
+ continue;
+ }
+ ntfs_debug("Base inode 0x%lx is in icache.", na.mft_no);
+ /*
+ * The base inode is in icache. Check if it has the extent
+ * inode corresponding to this extent mft record attached.
+ */
+ ni = NTFS_I(vi);
+ down(&ni->extent_lock);
+ if (ni->nr_extents <= 0) {
+ /*
+ * The base inode has no attached extent inodes. Skip
+ * this extent mft record.
+ */
+ up(&ni->extent_lock);
+ iput(vi);
+ continue;
+ }
+ /* Iterate over the attached extent inodes. */
+ extent_nis = ni->ext.extent_ntfs_inos;
+ for (eni = NULL, j = 0; j < ni->nr_extents; ++j) {
+ if (mft_no == extent_nis[j]->mft_no) {
+ /*
+ * Found the extent inode corresponding to this
+ * extent mft record.
+ */
+ eni = extent_nis[j];
+ break;
+ }
+ }
+ /*
+ * If the extent inode was not attached to the base inode, skip
+ * this extent mft record.
+ */
+ if (!eni) {
+ up(&ni->extent_lock);
+ iput(vi);
+ continue;
+ }
+ /*
+ * Found the extent inode corrsponding to this extent mft
+ * record. If it is dirty, no need to search further.
+ */
+ if (NInoDirty(eni)) {
+ up(&ni->extent_lock);
+ iput(vi);
+ is_dirty = TRUE;
+ break;
+ }
+ /* The extent inode is not dirty, so do the next record. */
+ up(&ni->extent_lock);
+ iput(vi);
+ }
+ kunmap(page);
+ /* If a dirty mft record was found, redirty the page. */
+ if (is_dirty) {
+ ntfs_debug("Inode 0x%lx is dirty. Redirtying the page "
+ "starting at inode 0x%lx.", mft_no,
+ page->index << (PAGE_CACHE_SHIFT -
+ vol->mft_record_size_bits));
+ redirty_page_for_writepage(wbc, page);
+ unlock_page(page);
+ } else {
+ /*
+ * Keep the VM happy. This must be done otherwise the
+ * radix-tree tag PAGECACHE_TAG_DIRTY remains set even though
+ * the page is clean.
+ */
+ BUG_ON(PageWriteback(page));
+ set_page_writeback(page);
+ unlock_page(page);
+ end_page_writeback(page);
+ }
+ ntfs_debug("Done.");
+ return 0;
+}
+
+#endif /* NTFS_RW */