bk://linux-ntfs.bkbits.net/ntfs-2.6-devel
aia21@cantab.net|ChangeSet|20040601163752|45566 aia21

# This is a BitKeeper generated diff -Nru style patch.
#
# ChangeSet
#   2004/06/01 17:00:58+01:00 aia21@cantab.net 
#   NTFS: - Implement fs/ntfs/mft.[hc]::{,__}mark_mft_record_dirty() and make
#           fs/ntfs/aops.c::ntfs_writepage() and ntfs_commit_write() use it, thus
#           finally enabling resident file overwrite!  (-8  This also includes a
#           placeholder for ->writepage (ntfs_mft_writepage()), which for now
#           just redirties the page and returns.  Also, at umount time, we for
#           now throw away all mft data page cache pages after the last call to
#           ntfs_commit_inode() in the hope that all inodes will have been
#           written out by then and hence no dirty (meta)data will be lost.  We
#           also check for this case and emit an error message telling the user
#           to run chkdsk.
#         - If the user is trying to enable (dir)atime updates, warn about the
#           fact that we are disabling them.
#   
#   Signed-off-by: Anton Altaparmakov <aia21@cantab.net>
# 
# fs/ntfs/super.c
#   2004/06/01 17:00:52+01:00 aia21@cantab.net +27 -2
#   - At umount time, we for now throw away all mft data page cache
#     pages after the last call to ntfs_commit_inode() in the hope
#     that all inodes will have been written out by then and hence
#     no dirty (meta)data will be lost.  We also check for this case
#     and emit an error message telling the user to run chkdsk.
#   - If the user is trying to enable (dir)atime updates, warn about
#     the fact that we are disabling them.
# 
# fs/ntfs/mft.h
#   2004/06/01 17:00:52+01:00 aia21@cantab.net +19 -0
#   Implement {,__}mark_mft_record_dirty().
# 
# fs/ntfs/mft.c
#   2004/06/01 17:00:52+01:00 aia21@cantab.net +77 -0
#   Implement __mark_mft_record_dirty() and a placeholder for
#   ->writepage (ntfs_mft_writepage()), which for now just
#   redirties the page and returns.
# 
# fs/ntfs/aops.c
#   2004/06/01 17:00:52+01:00 aia21@cantab.net +14 -19
#   Use mark_mft_record_dirty() in ntfs_writepage() and ntfs_commit_write(),
#   thus finally enabling resident file overwrite!  (-8
# 
# fs/ntfs/ChangeLog
#   2004/06/01 17:00:52+01:00 aia21@cantab.net +10 -1
#   Update
# 
# ChangeSet
#   2004/05/28 16:24:23+01:00 aia21@cantab.net 
#   NTFS: Implement ->write_inode (fs/ntfs/inode.c::ntfs_write_inode()) for the
#         ntfs super operations.  This gives us inode writing via the VFS inode
#         dirty code paths.  Note:  Access time updates are not implemented yet.
#   
#   Signed-off-by: Anton Altaparmakov <aia21@cantab.net>
# 
# fs/ntfs/super.c
#   2004/05/28 16:24:17+01:00 aia21@cantab.net +2 -2
#   Set ntfs_write_inode() to be our sops->write_inode.
# 
# fs/ntfs/inode.c
#   2004/05/28 16:24:17+01:00 aia21@cantab.net +101 -16
#   Implement ntfs_write_inode().
# 
# fs/ntfs/ChangeLog
#   2004/05/28 16:24:17+01:00 aia21@cantab.net +7 -1
#   Update.
# 
# ChangeSet
#   2004/05/28 12:38:37+01:00 aia21@cantab.net 
#   NTFS: Commit open system inodes at umount time.  This should make it
#         virtually impossible for sync_mft_mirror_umount() to ever be needed.
#   
#   Signed-off-by: Anton Altaparmakov <aia21@cantab.net>
# 
# fs/ntfs/super.c
#   2004/05/28 12:38:31+01:00 aia21@cantab.net +35 -0
#   Commit open system inodes at umount time.
# 
# fs/ntfs/ChangeLog
#   2004/05/28 12:38:31+01:00 aia21@cantab.net +2 -0
#   Update.
# 
# ChangeSet
#   2004/05/28 12:29:35+01:00 aia21@cantab.net 
#   NTFS: Implement writing of mft records (fs/ntfs/mft.[hc]), which includes
#         keeping the mft mirror in sync with the mft when mirrored mft records
#         are written.  The functions are write_mft_record{,_nolock}().  The
#         implementation is quite rudimentary for now with lots of things not
#         implemented yet but I am not sure any of them can actually occur so
#         I will wait for people to hit each one and only then implement it.
#   
#   Signed-off-by: Anton Altaparmakov <aia21@cantab.net>
# 
# fs/ntfs/mft.h
#   2004/05/28 12:29:29+01:00 aia21@cantab.net +35 -0
#   Add write_mft_record{,_nolock}().
# 
# fs/ntfs/mft.c
#   2004/05/28 12:29:29+01:00 aia21@cantab.net +387 -0
#   Add write_mft_record{,_nolock}().
# 
# fs/ntfs/compress.c
#   2004/05/28 12:29:29+01:00 aia21@cantab.net +2 -2
#   Error messages typo fixes.
# 
# fs/ntfs/attrib.c
#   2004/05/28 12:29:29+01:00 aia21@cantab.net +2 -2
#   Debug and error messages typo fixes.
# 
# fs/ntfs/aops.c
#   2004/05/28 12:29:29+01:00 aia21@cantab.net +1 -1
#   Debug message typo fix.
# 
# fs/ntfs/Makefile
#   2004/05/28 12:29:29+01:00 aia21@cantab.net +1 -1
#   Update.
# 
# fs/ntfs/ChangeLog
#   2004/05/28 12:29:29+01:00 aia21@cantab.net +15 -0
#   Update.
# 
diff -Nru a/fs/ntfs/ChangeLog b/fs/ntfs/ChangeLog
--- a/fs/ntfs/ChangeLog	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/ChangeLog	2004-06-02 23:51:00 -07:00
@@ -11,8 +11,10 @@
 	  pages as nothing can dirty a page other than ourselves. Should this
 	  change, we will really need to roll our own ->set_page_dirty().
 	- Implement sops->dirty_inode() to implement {a,m,c}time updates and
-	  such things.
-	- Implement sops->write_inode().
+	  such things.  This should probably just flag the ntfs inode such that
+	  sops->write_inode(), i.e. ntfs_write_inode(), will copy the times
+	  when it is invoked rather than having to update the mft record
+	  every time.
 	- In between ntfs_prepare/commit_write, need exclusion between
 	  simultaneous file extensions. Need perhaps an NInoResizeUnderway()
 	  flag which we can set in ntfs_prepare_write() and clear again in
@@ -24,6 +26,36 @@
 	  OTOH, perhaps i_sem, which is held accross generic_file_write is
 	  sufficient for synchronisation here. We then just need to make sure
 	  ntfs_readpage/writepage/truncate interoperate properly with us.
+	- Implement mft.c::sync_mft_mirror_umount().  We currently will just
+	  leave the volume dirty on umount if the final iput(vol->mft_ino)
+	  causes a write of any mirrored mft records due to the mft mirror
+	  inode having been discarded already.  Whether this can actually ever
+	  happen is unclear however so it is worth waiting until someone hits
+	  the problem.
+
+2.1.13 - WIP.
+
+	- Implement writing of mft records (fs/ntfs/mft.[hc]), which includes
+	  keeping the mft mirror in sync with the mft when mirrored mft records
+	  are written.  The functions are write_mft_record{,_nolock}().  The
+	  implementation is quite rudimentary for now with lots of things not
+	  implemented yet but I am not sure any of them can actually occur so
+	  I will wait for people to hit each one and only then implement it.
+	- Commit open system inodes at umount time.  This should make it
+	  virtually impossible for sync_mft_mirror_umount() to ever be needed.
+	- Implement ->write_inode (fs/ntfs/inode.c::ntfs_write_inode()) for the
+	  ntfs super operations.  This gives us inode writing via the VFS inode
+	  dirty code paths.  Note:  Access time updates are not implemented yet.
+	- Implement fs/ntfs/mft.[hc]::{,__}mark_mft_record_dirty() and make
+	  fs/ntfs/aops.c::ntfs_writepage() and ntfs_commit_write() use it, thus
+	  finally enabling resident file overwrite!  (-8  This also includes a
+	  placeholder for ->writepage (ntfs_mft_writepage()), which for now
+	  just redirties the page and returns.  Also, at umount time, we for
+	  now throw away all mft data page cache pages after the last call to
+	  ntfs_commit_inode() in the hope that all inodes will have been
+	  written out by then and hence no dirty (meta)data will be lost.  We
+	  also check for this case and emit an error message telling the user
+	  to run chkdsk.
 
 2.1.12 - Fix the second fix to the decompression engine and some cleanups.
 
diff -Nru a/fs/ntfs/Makefile b/fs/ntfs/Makefile
--- a/fs/ntfs/Makefile	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/Makefile	2004-06-02 23:51:00 -07:00
@@ -5,7 +5,7 @@
 ntfs-objs := aops.o attrib.o compress.o debug.o dir.o file.o inode.o mft.o \
 	     mst.o namei.o super.o sysctl.o unistr.o upcase.o
 
-EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.12\"
+EXTRA_CFLAGS = -DNTFS_VERSION=\"2.1.13-WIP\"
 
 ifeq ($(CONFIG_NTFS_DEBUG),y)
 EXTRA_CFLAGS += -DDEBUG
diff -Nru a/fs/ntfs/aops.c b/fs/ntfs/aops.c
--- a/fs/ntfs/aops.c	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/aops.c	2004-06-02 23:51:00 -07:00
@@ -479,7 +479,7 @@
 	vol = ni->vol;
 
 	ntfs_debug("Entering for inode %li, attribute type 0x%x, page index "
-			"0x%lx.\n", vi->i_ino, ni->type, page->index);
+			"0x%lx.", vi->i_ino, ni->type, page->index);
 
 	BUG_ON(!NInoNonResident(ni));
 	BUG_ON(NInoMstProtected(ni));
@@ -778,9 +778,8 @@
  *
  * For resident attributes, OTOH, ntfs_writepage() writes the @page by copying
  * the data to the mft record (which at this stage is most likely in memory).
- * Thus, in this case, I/O is synchronous, as even if the mft record is not
- * cached at this point in time, we need to wait for it to be read in before we
- * can do the copy.
+ * The mft record is then marked dirty and written out asynchronously via the
+ * vfs inode dirty code path.
  *
  * Note the caller clears the page dirty flag before calling ntfs_writepage().
  *
@@ -875,16 +874,6 @@
 	BUG_ON(page_has_buffers(page));
 	BUG_ON(!PageUptodate(page));
 
-	// TODO: Consider using PageWriteback() + unlock_page() in 2.5 once the
-	// "VM fiddling has ended". Note, don't forget to replace all the
-	// unlock_page() calls further below with end_page_writeback() ones.
-	// FIXME: Make sure it is ok to SetPageError() on unlocked page under
-	// writeback before doing the change!
-#if 0
-	set_page_writeback(page);
-	unlock_page(page);
-#endif
-
 	if (!NInoAttr(ni))
 		base_ni = ni;
 	else
@@ -934,6 +923,14 @@
 	if (unlikely(bytes > PAGE_CACHE_SIZE))
 		bytes = PAGE_CACHE_SIZE;
 
+	// TODO: Consider using PageWriteback() + unlock_page() in 2.6 once the
+	// "VM fiddling has ended". Note, don't forget to replace all the
+	// unlock_page() calls further below with end_page_writeback() ones.
+#if 0
+	set_page_writeback(page);
+	unlock_page(page);
+#endif
+
 	/*
 	 * Here, we don't need to zero the out of bounds area everytime because
 	 * the below memcpy() already takes care of the mmap-at-end-of-file
@@ -968,9 +965,8 @@
 
 	unlock_page(page);
 
-	// TODO: Mark mft record dirty so it gets written back.
-	ntfs_error(vi->i_sb, "Writing to resident files is not supported yet. "
-			"Wrote to memory only...");
+	/* Mark the mft record dirty, so it gets written back. */
+	mark_mft_record_dirty(ctx->ntfs_ino);
 
 	put_attr_search_ctx(ctx);
 	unmap_mft_record(base_ni);
@@ -1734,9 +1730,8 @@
 	}
 	kunmap_atomic(kaddr, KM_USER0);
 
-	// TODO: Mark mft record dirty so it gets written back.
-	ntfs_error(vi->i_sb, "Writing to resident files is not supported yet. "
-			"Wrote to memory only...");
+	/* Mark the mft record dirty, so it gets written back. */
+	mark_mft_record_dirty(ctx->ntfs_ino);
 
 	put_attr_search_ctx(ctx);
 	unmap_mft_record(base_ni);
diff -Nru a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
--- a/fs/ntfs/attrib.c	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/attrib.c	2004-06-02 23:51:00 -07:00
@@ -624,7 +624,7 @@
 
 			if (drl[ds].vcn == marker_vcn) {
 				ntfs_debug("Old marker = 0x%llx, replacing "
-						"with LCN_ENOENT.\n",
+						"with LCN_ENOENT.",
 						(unsigned long long)
 						drl[ds].lcn);
 				drl[ds].lcn = (LCN)LCN_ENOENT;
@@ -1565,7 +1565,7 @@
 		goto do_next_attr_loop;
 	}
 	ntfs_error(base_ni->vol->sb, "Inode contains corrupt attribute list "
-			"attribute.\n");
+			"attribute.");
 	if (ni != base_ni) {
 		unmap_extent_mft_record(ni);
 		ctx->ntfs_ino = base_ni;
diff -Nru a/fs/ntfs/compress.c b/fs/ntfs/compress.c
--- a/fs/ntfs/compress.c	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/compress.c	2004-06-02 23:51:00 -07:00
@@ -433,7 +433,7 @@
 	goto do_next_tag;
 
 return_overflow:
-	ntfs_error(NULL, "Failed. Returning -EOVERFLOW.\n");
+	ntfs_error(NULL, "Failed. Returning -EOVERFLOW.");
 	goto return_error;
 }
 
@@ -851,7 +851,7 @@
 		if (err) {
 			ntfs_error(vol->sb, "ntfs_decompress() failed in inode "
 					"0x%lx with error code %i. Skipping "
-					"this compression block.\n",
+					"this compression block.",
 					ni->mft_no, -err);
 			/* Release the unfinished pages. */
 			for (; prev_cur_page < cur_page; prev_cur_page++) {
diff -Nru a/fs/ntfs/inode.c b/fs/ntfs/inode.c
--- a/fs/ntfs/inode.c	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/inode.c	2004-06-02 23:51:00 -07:00
@@ -1960,49 +1960,134 @@
 	return err;
 }
 
+/**
+ * ntfs_write_inode - write out a dirty inode
+ * @vi:		inode to write out
+ * @sync:	if true, write out synchronously
+ *
+ * Write out a dirty inode to disk including any extent inodes if present.
+ *
+ * If @sync is true, commit the inode to disk and wait for io completion.  This
+ * is done using write_mft_record().
+ *
+ * If @sync is false, just schedule the write to happen but do not wait for i/o
+ * completion.  In 2.6 kernels, scheduling usually happens just by virtue of
+ * marking the page (and in this case mft record) dirty but we do not implement
+ * this yet as write_mft_record() largely ignores the @sync parameter and
+ * always performs synchronous writes.
+ */
 void ntfs_write_inode(struct inode *vi, int sync)
 {
 	ntfs_inode *ni = NTFS_I(vi);
+#if 0
+	attr_search_context *ctx;
+#endif
+	MFT_RECORD *m;
+	int err = 0;
 
 	ntfs_debug("Entering for %sinode 0x%lx.", NInoAttr(ni) ? "attr " : "",
 			vi->i_ino);
-
 	/*
 	 * Dirty attribute inodes are written via their real inodes so just
-	 * clean them here.
+	 * clean them here.  TODO:  Take care of access time updates.
 	 */
 	if (NInoAttr(ni)) {
 		NInoClearDirty(ni);
 		return;
 	}
-
-	/* Write this base mft record. */
-	if (NInoDirty(ni)) {
-		ntfs_warning(vi->i_sb, "Cleaning dirty inode 0x%lx without "
-				"writing to disk as this is not yet "
-				"implemented.", vi->i_ino);
-		NInoClearDirty(ni);
+	/* Map, pin, and lock the mft record belonging to the inode. */
+	m = map_mft_record(ni);
+	if (unlikely(IS_ERR(m))) {
+		err = PTR_ERR(m);
+		goto err_out;
 	}
-
+#if 0
+	/* Obtain the standard information attribute. */
+	ctx = get_attr_search_ctx(ni, m);
+	if (unlikely(!ctx)) {
+		err = -ENOMEM;
+		goto unm_err_out;
+	}
+	if (unlikely(!lookup_attr(AT_STANDARD_INFORMATION, NULL, 0,
+			IGNORE_CASE, 0, NULL, 0, ctx))) {
+		put_attr_search_ctx(ctx);
+		err = -ENOENT;
+		goto unm_err_out;
+	}
+	// TODO:  Update the access times in the standard information attribute
+	// which is now in ctx->attr.
+	// - Probably want to have use sops->dirty_inode() to set a flag that
+	//   we need to update the times here rather than having to blindly do
+	//   it every time.  Or even don't do it here at all and do it in
+	//   sops->dirty_inode() instead.  Problem with this would be that
+	//   sops->dirty_inode() must be atomic under certain circumstances
+	//   and mapping mft records and such like is not atomic.
+	// - For atime updates also need to check whether they are enabled in
+	//   the superblock flags.
+	ntfs_warning(vi->i_sb, "Access time updates not implement yet.");
+	/*
+	 * We just modified the mft record containing the standard information
+	 * attribute.  So need to mark the mft record dirty, too, but we do it
+	 * manually so that mark_inode_dirty() is not called again.
+	 * TODO:  Only do this if there was a change in any of the times!
+	 */
+	if (!NInoTestSetDirty(ctx->ntfs_ino))
+		__set_page_dirty_nobuffers(ctx->ntfs_ino->page);
+	put_attr_search_ctx(ctx);
+#endif
+	/* Write this base mft record. */
+	if (NInoDirty(ni))
+		err = write_mft_record(ni, m, sync);
 	/* Write all attached extent mft records. */
 	down(&ni->extent_lock);
 	if (ni->nr_extents > 0) {
-		int i;
 		ntfs_inode **extent_nis = ni->ext.extent_ntfs_inos;
+		int i;
 
+		ntfs_debug("Writing %i extent inodes.", ni->nr_extents);
 		for (i = 0; i < ni->nr_extents; i++) {
 			ntfs_inode *tni = extent_nis[i];
 
 			if (NInoDirty(tni)) {
-				ntfs_warning(vi->i_sb, "Cleaning dirty extent "
-						"inode 0x%lx without writing "
-						"to disk as this is not yet "
-						"implemented.", tni->mft_no);
-				NInoClearDirty(tni);
+				MFT_RECORD *tm = map_mft_record(tni);
+				int ret;
+
+				if (unlikely(IS_ERR(tm))) {
+					if (!err || err == -ENOMEM)
+						err = PTR_ERR(tm);
+					continue;
+				}
+				ret = write_mft_record(tni, tm, sync);
+				unmap_mft_record(tni);
+				if (unlikely(ret)) {
+					if (!err || err == -ENOMEM)
+						err = ret;
+				}
 			}
 		}
 	}
 	up(&ni->extent_lock);
+	unmap_mft_record(ni);
+	if (unlikely(err))
+		goto err_out;
+	ntfs_debug("Done.");
+	return;
+#if 0
+unm_err_out:
+	unmap_mft_record(ni);
+#endif
+err_out:
+	if (err == -ENOMEM) {
+		ntfs_warning(vi->i_sb, "Not enough memory to write inode.  "
+				"Marking the inode dirty again, so the VFS "
+				"retries later.");
+		mark_inode_dirty(vi);
+	} else {
+		ntfs_error(vi->i_sb, "Failed (error code %i):  Marking inode "
+				"as bad.  You should run chkdsk.", -err);
+		make_bad_inode(vi);
+	}
+	return;
 }
 
 #endif /* NTFS_RW */
diff -Nru a/fs/ntfs/mft.c b/fs/ntfs/mft.c
--- a/fs/ntfs/mft.c	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/mft.c	2004-06-02 23:51:00 -07:00
@@ -102,6 +102,13 @@
  */
 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
  *
@@ -112,6 +119,10 @@
 	.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 */
 };
 
 /**
@@ -429,3 +440,456 @@
 		ntfs_clear_extent_inode(ni);
 	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. */
+	__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;
+}
+
+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);
+
+	BUG_ON(mft_vi != vol->mft_ino);
+	ntfs_warning(sb, "VM writeback of $MFT is not implemented yet:  "
+			"Redirtying the page.");
+	redirty_page_for_writepage(wbc, page);
+	unlock_page(page);
+	return 0;
+}
+
+#endif /* NTFS_RW */
diff -Nru a/fs/ntfs/mft.h b/fs/ntfs/mft.h
--- a/fs/ntfs/mft.h	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/mft.h	2004-06-02 23:51:00 -07:00
@@ -57,6 +57,60 @@
 	flush_dcache_page(ni->page);
 }
 
+extern void __mark_mft_record_dirty(ntfs_inode *ni);
+
+/**
+ * mark_mft_record_dirty - set the mft record and the page containing it dirty
+ * @ni:		ntfs inode describing the mapped mft record
+ *
+ * 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:  Do not do anything if the mft record is already marked dirty.
+ */
+static inline void mark_mft_record_dirty(ntfs_inode *ni)
+{
+	if (!NInoTestSetDirty(ni))
+		__mark_mft_record_dirty(ni);
+}
+
+extern int write_mft_record_nolock(ntfs_inode *ni, MFT_RECORD *m, int sync);
+
+/**
+ * write_mft_record - 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
+ *
+ * This is just a wrapper for write_mft_record_nolock() (see mft.c), which
+ * locks the page for the duration of the write.  This ensures that there are
+ * no race conditions between writing the mft record via the dirty inode code
+ * paths and via the page cache write back code paths or between writing
+ * neighbouring mft records residing in the same page.
+ *
+ * Locking the page also serializes us against ->readpage() if the page is not
+ * uptodate.
+ *
+ * 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.
+ */
+static inline int write_mft_record(ntfs_inode *ni, MFT_RECORD *m, int sync)
+{
+	struct page *page = ni->page;
+	int err;
+
+	BUG_ON(!page);
+	lock_page(page);
+	err = write_mft_record_nolock(ni, m, sync);
+	unlock_page(page);
+	return err;
+}
+
 #endif /* NTFS_RW */
 
 #endif /* _LINUX_NTFS_MFT_H */
diff -Nru a/fs/ntfs/super.c b/fs/ntfs/super.c
--- a/fs/ntfs/super.c	2004-06-02 23:51:00 -07:00
+++ b/fs/ntfs/super.c	2004-06-02 23:51:00 -07:00
@@ -340,6 +340,12 @@
 	}
 	// TODO:  For now we enforce no atime and dir atime updates as they are
 	// not implemented.
+	if ((sb->s_flags & MS_NOATIME) && !(*flags & MS_NOATIME))
+		ntfs_warning(sb, "Atime updates are not implemented yet.  "
+				"Leaving them disabled.");
+	else if ((sb->s_flags & MS_NODIRATIME) && !(*flags & MS_NODIRATIME))
+		ntfs_warning(sb, "Directory atime updates are not implemented "
+				"yet.  Leaving them disabled.");
 	*flags |= MS_NOATIME | MS_NODIRATIME;
 #endif /* ! NTFS_RW */
 
@@ -1302,6 +1308,38 @@
 
 	ntfs_debug("Entering.");
 
+#ifdef NTFS_RW
+	/*
+	 * Commit all inodes while they are still open in case some of them
+	 * cause others to be dirtied.
+	 */
+	ntfs_commit_inode(vol->vol_ino);
+
+	/* NTFS 3.0+ specific. */
+	if (vol->major_ver >= 3) {
+		if (vol->secure_ino)
+			ntfs_commit_inode(vol->secure_ino);
+	}
+
+	ntfs_commit_inode(vol->root_ino);
+
+	down_write(&vol->lcnbmp_lock);
+	ntfs_commit_inode(vol->lcnbmp_ino);
+	up_write(&vol->lcnbmp_lock);
+
+	down_write(&vol->mftbmp_lock);
+	ntfs_commit_inode(vol->mftbmp_ino);
+	up_write(&vol->mftbmp_lock);
+
+	if (vol->logfile_ino)
+		ntfs_commit_inode(vol->logfile_ino);
+
+	if (vol->mftmirr_ino)
+		ntfs_commit_inode(vol->mftmirr_ino);
+
+	ntfs_commit_inode(vol->mft_ino);
+#endif /* NTFS_RW */
+
 	iput(vol->vol_ino);
 	vol->vol_ino = NULL;
 
@@ -1333,9 +1371,27 @@
 	}
 
 	if (vol->mftmirr_ino) {
+		/* Re-commit the mft mirror and mft just in case. */
+		ntfs_commit_inode(vol->mftmirr_ino);
+		ntfs_commit_inode(vol->mft_ino);
 		iput(vol->mftmirr_ino);
 		vol->mftmirr_ino = NULL;
 	}
+	/*
+	 * Throw away all mft data page cache pages to allow a clean umount.
+	 * All inodes should by now be written out and clean so this should not
+	 * loose any data while removing all the pages which have the dirty bit
+	 * set.
+	 */
+	ntfs_commit_inode(vol->mft_ino);
+	down(&vol->mft_ino->i_sem);
+	truncate_inode_pages(vol->mft_ino->i_mapping, 0);
+	up(&vol->mft_ino->i_sem);
+	write_inode_now(vol->mft_ino, 1);
+	if (!list_empty(&vfs_sb->s_dirty) || !list_empty(&vfs_sb->s_io))
+		ntfs_error(vfs_sb, "Dirty inodes found at umount time.  "
+				"They have been thrown away and their changes "
+				"have been lost.  You should run chkdsk.");
 #endif /* NTFS_RW */
 
 	iput(vol->mft_ino);
@@ -1629,8 +1685,8 @@
 #ifdef NTFS_RW
 	//.dirty_inode	= NULL,			/* VFS: Called from
 	//					   __mark_inode_dirty(). */
-	//.write_inode	= NULL,			/* VFS: Write dirty inode to
-	//					   disk. */
+	.write_inode	= ntfs_write_inode,	/* VFS: Write dirty inode to
+						   disk. */
 	//.drop_inode	= NULL,			/* VFS: Called just after the
 	//					   inode reference count has
 	//					   been decreased to zero.
@@ -1719,8 +1775,12 @@
 #ifndef NTFS_RW
 	sb->s_flags |= MS_RDONLY | MS_NOATIME | MS_NODIRATIME;
 #else
-	// TODO:  For now we enforce no atime and dir atime updates as they are
-	// not implemented.
+	if (!(sb->s_flags & MS_NOATIME))
+		ntfs_warning(sb, "Atime updates are not implemented yet.  "
+				"Disabling them.");
+	else if (!(sb->s_flags & MS_NODIRATIME))
+		ntfs_warning(sb, "Directory atime updates are not implemented "
+				"yet.  Disabling them.");
 	sb->s_flags |= MS_NOATIME | MS_NODIRATIME;
 #endif
 	/* Allocate a new ntfs_volume and place it in sb->s_fs_info. */