[vfs2][gofer] Update file size to 0 on O_TRUNC

Some Open:TruncateXxx syscall tests were failing because the file size was
not being updated when the file was opened with O_TRUNC.

Fixes Truncate tests in test/syscalls:open_test_runsc_ptrace_vfs2.

Updates #2923

PiperOrigin-RevId: 319340127
This commit is contained in:
Ayush Ranjan
2020-07-01 19:49:44 -07:00
committed by gVisor bot
parent 3b26d2121e
commit 52b44719d6
4 changed files with 77 additions and 43 deletions
+32 -3
View File
@@ -869,11 +869,22 @@ func (d *dentry) openLocked(ctx context.Context, rp *vfs.ResolvingPath, opts *vf
if err := d.checkPermissions(rp.Credentials(), ats); err != nil {
return nil, err
}
trunc := opts.Flags&linux.O_TRUNC != 0 && d.fileType() == linux.S_IFREG
if trunc {
// Lock metadataMu *while* we open a regular file with O_TRUNC because
// open(2) will change the file size on server.
d.metadataMu.Lock()
defer d.metadataMu.Unlock()
}
var vfd *vfs.FileDescription
var err error
mnt := rp.Mount()
switch d.fileType() {
case linux.S_IFREG:
if !d.fs.opts.regularFilesUseSpecialFileFD {
if err := d.ensureSharedHandle(ctx, ats&vfs.MayRead != 0, ats&vfs.MayWrite != 0, opts.Flags&linux.O_TRUNC != 0); err != nil {
if err := d.ensureSharedHandle(ctx, ats&vfs.MayRead != 0, ats&vfs.MayWrite != 0, trunc); err != nil {
return nil, err
}
fd := &regularFileFD{}
@@ -883,7 +894,7 @@ func (d *dentry) openLocked(ctx context.Context, rp *vfs.ResolvingPath, opts *vf
}); err != nil {
return nil, err
}
return &fd.vfsfd, nil
vfd = &fd.vfsfd
}
case linux.S_IFDIR:
// Can't open directories with O_CREAT.
@@ -923,7 +934,25 @@ func (d *dentry) openLocked(ctx context.Context, rp *vfs.ResolvingPath, opts *vf
return d.pipe.Open(ctx, mnt, &d.vfsd, opts.Flags, &d.locks)
}
}
return d.openSpecialFileLocked(ctx, mnt, opts)
if vfd == nil {
if vfd, err = d.openSpecialFileLocked(ctx, mnt, opts); err != nil {
return nil, err
}
}
if trunc {
// If no errors occured so far then update file size in memory. This
// step is required even if !d.cachedMetadataAuthoritative() because
// d.mappings has to be updated.
// d.metadataMu has already been acquired if trunc == true.
d.updateFileSizeLocked(0)
if d.cachedMetadataAuthoritative() {
d.touchCMtimeLocked()
}
}
return vfd, err
}
func (d *dentry) connectSocketLocked(ctx context.Context, opts *vfs.OpenOptions) (*vfs.FileDescription, error) {
+37 -34
View File
@@ -794,9 +794,7 @@ func (d *dentry) updateFromP9Attrs(mask p9.AttrMask, attr *p9.Attr) {
atomic.StoreUint32(&d.nlink, uint32(attr.NLink))
}
if mask.Size {
d.dataMu.Lock()
atomic.StoreUint64(&d.size, attr.Size)
d.dataMu.Unlock()
d.updateFileSizeLocked(attr.Size)
}
d.metadataMu.Unlock()
}
@@ -964,41 +962,46 @@ func (d *dentry) setStat(ctx context.Context, creds *auth.Credentials, stat *lin
}
atomic.StoreInt64(&d.ctime, now)
if stat.Mask&linux.STATX_SIZE != 0 {
d.dataMu.Lock()
oldSize := d.size
d.size = stat.Size
// d.dataMu must be unlocked to lock d.mapsMu and invalidate mappings
// below. This allows concurrent calls to Read/Translate/etc. These
// functions synchronize with truncation by refusing to use cache
// contents beyond the new d.size. (We are still holding d.metadataMu,
// so we can't race with Write or another truncate.)
d.dataMu.Unlock()
if d.size < oldSize {
oldpgend, _ := usermem.PageRoundUp(oldSize)
newpgend, _ := usermem.PageRoundUp(d.size)
if oldpgend != newpgend {
d.mapsMu.Lock()
d.mappings.Invalidate(memmap.MappableRange{newpgend, oldpgend}, memmap.InvalidateOpts{
// Compare Linux's mm/truncate.c:truncate_setsize() =>
// truncate_pagecache() =>
// mm/memory.c:unmap_mapping_range(evencows=1).
InvalidatePrivate: true,
})
d.mapsMu.Unlock()
}
// We are now guaranteed that there are no translations of
// truncated pages, and can remove them from the cache. Since
// truncated pages have been removed from the remote file, they
// should be dropped without being written back.
d.dataMu.Lock()
d.cache.Truncate(d.size, d.fs.mfp.MemoryFile())
d.dirty.KeepClean(memmap.MappableRange{d.size, oldpgend})
d.dataMu.Unlock()
}
d.updateFileSizeLocked(stat.Size)
}
return nil
}
// Preconditions: d.metadataMu must be locked.
func (d *dentry) updateFileSizeLocked(newSize uint64) {
d.dataMu.Lock()
oldSize := d.size
d.size = newSize
// d.dataMu must be unlocked to lock d.mapsMu and invalidate mappings
// below. This allows concurrent calls to Read/Translate/etc. These
// functions synchronize with truncation by refusing to use cache
// contents beyond the new d.size. (We are still holding d.metadataMu,
// so we can't race with Write or another truncate.)
d.dataMu.Unlock()
if d.size < oldSize {
oldpgend, _ := usermem.PageRoundUp(oldSize)
newpgend, _ := usermem.PageRoundUp(d.size)
if oldpgend != newpgend {
d.mapsMu.Lock()
d.mappings.Invalidate(memmap.MappableRange{newpgend, oldpgend}, memmap.InvalidateOpts{
// Compare Linux's mm/truncate.c:truncate_setsize() =>
// truncate_pagecache() =>
// mm/memory.c:unmap_mapping_range(evencows=1).
InvalidatePrivate: true,
})
d.mapsMu.Unlock()
}
// We are now guaranteed that there are no translations of
// truncated pages, and can remove them from the cache. Since
// truncated pages have been removed from the remote file, they
// should be dropped without being written back.
d.dataMu.Lock()
d.cache.Truncate(d.size, d.fs.mfp.MemoryFile())
d.dirty.KeepClean(memmap.MappableRange{d.size, oldpgend})
d.dataMu.Unlock()
}
}
func (d *dentry) checkPermissions(creds *auth.Credentials, ats vfs.AccessTypes) error {
return vfs.GenericCheckPermissions(creds, ats, linux.FileMode(atomic.LoadUint32(&d.mode)), auth.KUID(atomic.LoadUint32(&d.uid)), auth.KGID(atomic.LoadUint32(&d.gid)))
}
+7 -5
View File
@@ -36,7 +36,7 @@ func statxTimestampFromDentry(ns int64) linux.StatxTimestamp {
}
}
// Preconditions: fs.interop != InteropModeShared.
// Preconditions: d.cachedMetadataAuthoritative() == true.
func (d *dentry) touchAtime(mnt *vfs.Mount) {
if mnt.Flags.NoATime {
return
@@ -51,8 +51,8 @@ func (d *dentry) touchAtime(mnt *vfs.Mount) {
mnt.EndWrite()
}
// Preconditions: fs.interop != InteropModeShared. The caller has successfully
// called vfs.Mount.CheckBeginWrite().
// Preconditions: d.cachedMetadataAuthoritative() == true. The caller has
// successfully called vfs.Mount.CheckBeginWrite().
func (d *dentry) touchCtime() {
now := d.fs.clock.Now().Nanoseconds()
d.metadataMu.Lock()
@@ -60,8 +60,8 @@ func (d *dentry) touchCtime() {
d.metadataMu.Unlock()
}
// Preconditions: fs.interop != InteropModeShared. The caller has successfully
// called vfs.Mount.CheckBeginWrite().
// Preconditions: d.cachedMetadataAuthoritative() == true. The caller has
// successfully called vfs.Mount.CheckBeginWrite().
func (d *dentry) touchCMtime() {
now := d.fs.clock.Now().Nanoseconds()
d.metadataMu.Lock()
@@ -70,6 +70,8 @@ func (d *dentry) touchCMtime() {
d.metadataMu.Unlock()
}
// Preconditions: d.cachedMetadataAuthoritative() == true. The caller has
// locked d.metadataMu.
func (d *dentry) touchCMtimeLocked() {
now := d.fs.clock.Now().Nanoseconds()
atomic.StoreInt64(&d.mtime, now)
+1 -1
View File
@@ -410,7 +410,7 @@ func (d *dentry) open(ctx context.Context, rp *vfs.ResolvingPath, opts *vfs.Open
if err := fd.vfsfd.Init(&fd, opts.Flags, rp.Mount(), &d.vfsd, &vfs.FileDescriptionOptions{AllowDirectIO: true}); err != nil {
return nil, err
}
if opts.Flags&linux.O_TRUNC != 0 {
if !afterCreate && opts.Flags&linux.O_TRUNC != 0 {
if _, err := impl.truncate(0); err != nil {
return nil, err
}