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 # # 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 # # 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 # # 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 # # 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. */