From a277f53f250910d23534a3fc4401840008296f4d Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Fri, 22 Sep 2023 10:32:39 -0700 Subject: [PATCH] Fix atomicOTrunc implementation in fusefs. If FUSE connection does not support atomic O_TRUNC and application calls open(O_TRUNC), FUSE should truncate the file *first* (without a file handle) and then subsequently open the file with O_TRUNC filtered out. We were truncating the file after open(). There were 2 issues with it: 1. We were using the inode's file handle, which may be invalid at this point because it is donated to the first FD (to save an open(2) RPC I believe) and that FD can close it. 2. The truncate() was being done with fhOptions.useFh = true. However, sometimes the file handle might be read-only causing truncate to fail. Fixed both issues by moving the truncate above open() and making it not use any file handles. PiperOrigin-RevId: 567658664 --- pkg/sentry/fsimpl/fuse/inode.go | 39 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/pkg/sentry/fsimpl/fuse/inode.go b/pkg/sentry/fsimpl/fuse/inode.go index 740a568f7..d7c87e227 100644 --- a/pkg/sentry/fsimpl/fuse/inode.go +++ b/pkg/sentry/fsimpl/fuse/inode.go @@ -292,6 +292,19 @@ func (i *inode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentr // FOPEN_KEEP_CACHE is the default flag for noOpen. fd.OpenFlag = linux.FOPEN_KEEP_CACHE + truncateRegFile := opts.Flags&linux.O_TRUNC != 0 && i.filemode().FileType() == linux.S_IFREG + if truncateRegFile && (i.fh.new || !i.fs.conn.atomicOTrunc) { + // If the regular file needs to be truncated, but the connection doesn't + // support O_TRUNC or if we are optimizing away the Open RPC, then manually + // truncate the file *before* Open. As per libfuse, "If [atomic O_TRUNC is] + // disabled, and an application specifies O_TRUNC, fuse first calls + // truncate() and then open() with O_TRUNC filtered out.". + opts := vfs.SetStatOptions{Stat: linux.Statx{Size: 0, Mask: linux.STATX_SIZE}} + if err := i.setAttr(ctx, i.fs.VFSFilesystem(), auth.CredentialsFromContext(ctx), opts, fhOptions{useFh: false}); err != nil { + return nil, err + } + } + if i.fh.new { fd.OpenFlag = i.fh.flags fd.Fh = i.fh.handle @@ -300,7 +313,7 @@ func (i *inode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentr // opening a directory. } else if !i.fs.conn.noOpen || i.filemode().IsDir() { in := linux.FUSEOpenIn{Flags: opts.Flags & ^uint32(linux.O_CREAT|linux.O_EXCL|linux.O_NOCTTY)} - // Truncating with SETATTR instead of O_TRUNC, so clear the flag. + // Clear O_TRUNC if the server doesn't support it. if !i.fs.conn.atomicOTrunc { in.Flags &= ^uint32(linux.O_TRUNC) } @@ -323,6 +336,14 @@ func (i *inode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentr } fd.OpenFlag = out.OpenFlag fd.Fh = out.Fh + // Open was successful. Update inode's size if atomicOTrunc && O_TRUNC. + if truncateRegFile && i.fs.conn.atomicOTrunc { + i.fs.conn.mu.Lock() + i.attrVersion.Store(i.fs.conn.attributeVersion.Add(1)) + i.fs.conn.mu.Unlock() + i.size.Store(0) + i.touchCMtime() + } } } if i.filemode().IsDir() { @@ -338,22 +359,6 @@ func (i *inode) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentr fd.Nonseekable = true } - // If atomicOTrunc and O_TRUNC are set, just update the inode's version number - // and set its size to 0 since the truncation is handled by the FUSE daemon. - // Otherwise send a separate SETATTR to truncate the file size. - if opts.Flags&linux.O_TRUNC != 0 && i.filemode().FileType() == linux.S_IFREG { - if i.fs.conn.atomicOTrunc { - i.fs.conn.mu.Lock() - i.attrVersion.Store(i.fs.conn.attributeVersion.Add(1)) - i.fs.conn.mu.Unlock() - i.size.Store(0) - i.touchCMtime() - } else { - opts := vfs.SetStatOptions{Stat: linux.Statx{Size: 0, Mask: linux.STATX_SIZE}} - i.setAttr(ctx, i.fs.VFSFilesystem(), auth.CredentialsFromContext(ctx), opts, fhOptions{useFh: true, fh: i.fh.handle}) - } - } - if err := fd.vfsfd.Init(fdImpl, opts.Flags, rp.Mount(), d.VFSDentry(), fdOptions); err != nil { return nil, err }