Re: [PATCH v6 8/9] vfs: update times after copying data in __generic_file_write_iter

From: Jeff Layton
Date: Mon Oct 03 2022 - 09:01:51 EST


On Sun, 2022-10-02 at 10:08 +0300, Amir Goldstein wrote:
> On Fri, Sep 30, 2022 at 2:30 PM Jeff Layton <jlayton@xxxxxxxxxx> wrote:
> >
> > The c/mtime and i_version currently get updated before the data is
> > copied (or a DIO write is issued), which is problematic for NFS.
> >
> > READ+GETATTR can race with a write (even a local one) in such a way as
> > to make the client associate the state of the file with the wrong change
> > attribute. That association can persist indefinitely if the file sees no
> > further changes.
> >
> > Move the setting of times to the bottom of the function in
> > __generic_file_write_iter and only update it if something was
> > successfully written.
> >
>
> This solution is wrong for several reasons:
>
> 1. There is still file_update_time() in ->page_mkwrite() so you haven't
> solved the problem completely

Right. I don't think there is a way to solve the problem vs. mmap.
Userland can write to a writeable mmap'ed page at any time and we'd
never know. We have to specifically carve out mmap as an exception here.
I'll plan to add something to the manpage patch for this.

> 2. The other side of the coin is that post crash state is more likely to end
> up data changes without mtime/ctime change
>

Is this really something filesystems rely on? I suppose the danger is
that some cached data gets written to disk before the write returns and
the inode on disk never gets updated.

But...isn't that a danger now? Some of the cached data could get written
out and the updated inode just never makes it to disk before a crash
(AFAIU). I'm not sure that this increases our exposure to that problem.


> If I read the problem description correctly, then a solution that invalidates
> the NFS cache before AND after the write would be acceptable. Right?
> Would an extra i_version bump after the write solve the race?
>

I based this patch on Neil's assertion that updating the time before an
operation was pointless if we were going to do it afterward. The NFS
client only really cares about seeing it change after a write.

Doing both would be fine from a correctness standpoint, and in most
cases, the second would be a no-op anyway since a query would have to
race in between the two for that to happen.

FWIW, I think we should update the m/ctime and version at the same time.
If the version changes, then there is always the potential that a timer
tick has occurred. So, that would translate to a second call to
file_update_time in here.

The downside of bumping the times/version both before and after is that
these are hot codepaths, and we'd be adding extra operations there. Even
in the case where nothing has changed, we'd have to call
inode_needs_update_time a second time for every write. Is that worth the
cost?

> > If the time update fails, log a warning once, but don't fail the write.
> > All of the existing callers use update_time functions that don't fail,
> > so we should never trip this.
> >
> > Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx>
> > ---
> > mm/filemap.c | 17 +++++++++++++----
> > 1 file changed, 13 insertions(+), 4 deletions(-)
> >
> > diff --git a/mm/filemap.c b/mm/filemap.c
> > index 15800334147b..72c0ceb75176 100644
> > --- a/mm/filemap.c
> > +++ b/mm/filemap.c
> > @@ -3812,10 +3812,6 @@ ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
> > if (err)
> > goto out;
> >
> > - err = file_update_time(file);
> > - if (err)
> > - goto out;
> > -
> > if (iocb->ki_flags & IOCB_DIRECT) {
> > loff_t pos, endbyte;
> >
> > @@ -3868,6 +3864,19 @@ ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
> > iocb->ki_pos += written;
> > }
> > out:
> > + if (written > 0) {
> > + err = file_update_time(file);
> > + /*
> > + * There isn't much we can do at this point if updating the
> > + * times fails after a successful write. The times and i_version
> > + * should still be updated in the inode, and it should still be
> > + * marked dirty, so hopefully the next inode update will catch it.
> > + * Log a warning once so we have a record that something untoward
> > + * has occurred.
> > + */
> > + WARN_ONCE(err, "Failed to update m/ctime after write: %ld\n", err);
>
> pr_warn_once() please - this is not a programming assertion.
>

ACK. I'll change that.

--
Jeff Layton <jlayton@xxxxxxxxxx>