Add NAME_MAX checks and update file times

NAME_MAX should be enforced per filesystem implementation
because other file systems may not have the same restriction.

Gofer filesystem now keeps a reference to the kernel clock to
avoid lookup in the Context on file access to update atime.

Update access, modification, and status change times in tmpfs.

Updates #1197, #1198.

PiperOrigin-RevId: 304527148
This commit is contained in:
Fabricio Voznika 2020-04-02 19:37:41 -07:00 committed by gVisor bot
parent 4582a2f188
commit dd3bc49997
12 changed files with 139 additions and 52 deletions

View File

@ -56,14 +56,19 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
fd.mu.Lock()
defer fd.mu.Unlock()
d := fd.dentry()
if fd.dirents == nil {
ds, err := fd.dentry().getDirents(ctx)
ds, err := d.getDirents(ctx)
if err != nil {
return err
}
fd.dirents = ds
}
if d.fs.opts.interop != InteropModeShared {
d.touchAtime(fd.vfsfd.Mount())
}
for fd.off < int64(len(fd.dirents)) {
if err := cb.Handle(fd.dirents[fd.off]); err != nil {
return err

View File

@ -356,7 +356,9 @@ func (fs *filesystem) doCreateAt(ctx context.Context, rp *vfs.ResolvingPath, dir
if err := create(parent, name); err != nil {
return err
}
parent.touchCMtime(ctx)
if fs.opts.interop != InteropModeShared {
parent.touchCMtime()
}
delete(parent.negativeChildren, name)
parent.dirents = nil
return nil
@ -454,7 +456,7 @@ func (fs *filesystem) unlinkAt(ctx context.Context, rp *vfs.ResolvingPath, dir b
return err
}
if fs.opts.interop != InteropModeShared {
parent.touchCMtime(ctx)
parent.touchCMtime()
if dir {
parent.decLinks()
}
@ -802,7 +804,6 @@ func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.Resolving
d.IncRef() // reference held by child on its parent d
d.vfsd.InsertChild(&child.vfsd, name)
if d.fs.opts.interop != InteropModeShared {
d.touchCMtime(ctx)
delete(d.negativeChildren, name)
d.dirents = nil
}
@ -834,6 +835,9 @@ func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.Resolving
}
childVFSFD = &fd.vfsfd
}
if d.fs.opts.interop != InteropModeShared {
d.touchCMtime()
}
return childVFSFD, nil
}
@ -975,6 +979,9 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
oldParent.decLinks()
newParent.incLinks()
}
oldParent.touchCMtime()
newParent.touchCMtime()
renamed.touchCtime()
}
vfsObj.CommitRenameReplaceDentry(&renamed.vfsd, &newParent.vfsd, newName, replacedVFSD)
return nil

View File

@ -44,6 +44,7 @@ import (
"gvisor.dev/gvisor/pkg/p9"
"gvisor.dev/gvisor/pkg/sentry/fs/fsutil"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/vfs"
@ -72,6 +73,9 @@ type filesystem struct {
// client is the client used by this filesystem. client is immutable.
client *p9.Client
// clock is a realtime clock used to set timestamps in file operations.
clock ktime.Clock
// uid and gid are the effective KUID and KGID of the filesystem's creator,
// and are used as the owner and group for files that don't specify one.
// uid and gid are immutable.
@ -376,6 +380,7 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
uid: creds.EffectiveKUID,
gid: creds.EffectiveKGID,
client: client,
clock: ktime.RealtimeClockFromContext(ctx),
dentries: make(map[*dentry]struct{}),
specialFileFDs: make(map[*specialFileFD]struct{}),
}
@ -779,10 +784,7 @@ func (d *dentry) setStat(ctx context.Context, creds *auth.Credentials, stat *lin
// data, so there's no cache to truncate either.)
return nil
}
now, haveNow := nowFromContext(ctx)
if !haveNow {
ctx.Warningf("gofer.dentry.setStat: current time not available")
}
now := d.fs.clock.Now().Nanoseconds()
if stat.Mask&linux.STATX_MODE != 0 {
atomic.StoreUint32(&d.mode, d.fileType()|uint32(stat.Mode))
}
@ -794,25 +796,19 @@ func (d *dentry) setStat(ctx context.Context, creds *auth.Credentials, stat *lin
}
if setLocalAtime {
if stat.Atime.Nsec == linux.UTIME_NOW {
if haveNow {
atomic.StoreInt64(&d.atime, now)
}
atomic.StoreInt64(&d.atime, now)
} else {
atomic.StoreInt64(&d.atime, dentryTimestampFromStatx(stat.Atime))
}
}
if setLocalMtime {
if stat.Mtime.Nsec == linux.UTIME_NOW {
if haveNow {
atomic.StoreInt64(&d.mtime, now)
}
atomic.StoreInt64(&d.mtime, now)
} else {
atomic.StoreInt64(&d.mtime, dentryTimestampFromStatx(stat.Mtime))
}
}
if haveNow {
atomic.StoreInt64(&d.ctime, now)
}
atomic.StoreInt64(&d.ctime, now)
if stat.Mask&linux.STATX_SIZE != 0 {
d.dataMu.Lock()
oldSize := d.size

View File

@ -104,7 +104,7 @@ func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offs
putDentryReadWriter(rw)
if d.fs.opts.interop != InteropModeShared {
// Compare Linux's mm/filemap.c:do_generic_file_read() => file_accessed().
d.touchAtime(ctx, fd.vfsfd.Mount())
d.touchAtime(fd.vfsfd.Mount())
}
return n, err
}
@ -139,10 +139,7 @@ func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, off
// Compare Linux's mm/filemap.c:__generic_file_write_iter() =>
// file_update_time(). This is d.touchCMtime(), but without locking
// d.metadataMu (recursively).
if now, ok := nowFromContext(ctx); ok {
atomic.StoreInt64(&d.mtime, now)
atomic.StoreInt64(&d.ctime, now)
}
d.touchCMtimeLocked()
}
if fd.vfsfd.StatusFlags()&linux.O_DIRECT != 0 {
// Write dirty cached pages that will be touched by the write back to

View File

@ -76,7 +76,7 @@ func (fd *specialFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offs
// hold here since specialFileFD doesn't client-cache data. Just buffer the
// read instead.
if d := fd.dentry(); d.fs.opts.interop != InteropModeShared {
d.touchAtime(ctx, fd.vfsfd.Mount())
d.touchAtime(fd.vfsfd.Mount())
}
buf := make([]byte, dst.NumBytes())
n, err := fd.handle.readToBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf)), uint64(offset))
@ -117,7 +117,7 @@ func (fd *specialFileFD) PWrite(ctx context.Context, src usermem.IOSequence, off
// Do a buffered write. See rationale in PRead.
if d := fd.dentry(); d.fs.opts.interop != InteropModeShared {
d.touchCMtime(ctx)
d.touchCMtime()
}
buf := make([]byte, src.NumBytes())
// Don't do partial writes if we get a partial read from src.

View File

@ -27,7 +27,7 @@ func (d *dentry) isSymlink() bool {
// Precondition: d.isSymlink().
func (d *dentry) readlink(ctx context.Context, mnt *vfs.Mount) (string, error) {
if d.fs.opts.interop != InteropModeShared {
d.touchAtime(ctx, mnt)
d.touchAtime(mnt)
d.dataMu.Lock()
if d.haveTarget {
target := d.target

View File

@ -18,8 +18,6 @@ import (
"sync/atomic"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
@ -38,23 +36,12 @@ func statxTimestampFromDentry(ns int64) linux.StatxTimestamp {
}
}
func nowFromContext(ctx context.Context) (int64, bool) {
if clock := ktime.RealtimeClockFromContext(ctx); clock != nil {
return clock.Now().Nanoseconds(), true
}
return 0, false
}
// Preconditions: fs.interop != InteropModeShared.
func (d *dentry) touchAtime(ctx context.Context, mnt *vfs.Mount) {
func (d *dentry) touchAtime(mnt *vfs.Mount) {
if err := mnt.CheckBeginWrite(); err != nil {
return
}
now, ok := nowFromContext(ctx)
if !ok {
mnt.EndWrite()
return
}
now := d.fs.clock.Now().Nanoseconds()
d.metadataMu.Lock()
atomic.StoreInt64(&d.atime, now)
d.metadataMu.Unlock()
@ -63,13 +50,25 @@ func (d *dentry) touchAtime(ctx context.Context, mnt *vfs.Mount) {
// Preconditions: fs.interop != InteropModeShared. The caller has successfully
// called vfs.Mount.CheckBeginWrite().
func (d *dentry) touchCMtime(ctx context.Context) {
now, ok := nowFromContext(ctx)
if !ok {
return
}
func (d *dentry) touchCtime() {
now := d.fs.clock.Now().Nanoseconds()
d.metadataMu.Lock()
atomic.StoreInt64(&d.ctime, now)
d.metadataMu.Unlock()
}
// Preconditions: fs.interop != InteropModeShared. The caller has successfully
// called vfs.Mount.CheckBeginWrite().
func (d *dentry) touchCMtime() {
now := d.fs.clock.Now().Nanoseconds()
d.metadataMu.Lock()
atomic.StoreInt64(&d.mtime, now)
atomic.StoreInt64(&d.ctime, now)
d.metadataMu.Unlock()
}
func (d *dentry) touchCMtimeLocked() {
now := d.fs.clock.Now().Nanoseconds()
atomic.StoreInt64(&d.mtime, now)
atomic.StoreInt64(&d.ctime, now)
}

View File

@ -63,6 +63,9 @@ afterSymlink:
rp.Advance()
return nextVFSD, nil
}
if len(name) > linux.NAME_MAX {
return nil, syserror.ENAMETOOLONG
}
d.dirMu.Lock()
nextVFSD, err := rp.ResolveChild(vfsd, name)
if err != nil {
@ -191,6 +194,9 @@ func checkCreateLocked(ctx context.Context, rp *vfs.ResolvingPath, parentVFSD *v
if pc == "." || pc == ".." {
return "", syserror.EEXIST
}
if len(pc) > linux.NAME_MAX {
return "", syserror.ENAMETOOLONG
}
childVFSD, err := rp.ResolveChild(parentVFSD, pc)
if err != nil {
return "", err
@ -433,6 +439,9 @@ afterTrailingSymlink:
if pc == "." || pc == ".." {
return nil, syserror.EISDIR
}
if len(pc) > linux.NAME_MAX {
return nil, syserror.ENAMETOOLONG
}
// Determine whether or not we need to create a file.
childVFSD, err := rp.ResolveChild(parentVFSD, pc)
if err != nil {

View File

@ -68,6 +68,8 @@ func (fd *directoryFD) IterDirents(ctx context.Context, cb vfs.IterDirentsCallba
fs.mu.Lock()
defer fs.mu.Unlock()
fd.inode().touchAtime(fd.vfsfd.Mount())
if fd.off == 0 {
if err := cb.Handle(vfs.Dirent{
Name: ".",

View File

@ -46,6 +46,9 @@ func stepLocked(rp *vfs.ResolvingPath, d *dentry) (*dentry, error) {
return nil, err
}
afterSymlink:
if len(rp.Component()) > linux.NAME_MAX {
return nil, syserror.ENAMETOOLONG
}
nextVFSD, err := rp.ResolveComponent(&d.vfsd)
if err != nil {
return nil, err
@ -133,6 +136,9 @@ func (fs *filesystem) doCreateAt(rp *vfs.ResolvingPath, dir bool, create func(pa
if name == "." || name == ".." {
return syserror.EEXIST
}
if len(name) > linux.NAME_MAX {
return syserror.ENAMETOOLONG
}
// Call parent.vfsd.Child() instead of stepLocked() or rp.ResolveChild(),
// because if the child exists we want to return EEXIST immediately instead
// of attempting symlink/mount traversal.
@ -153,7 +159,11 @@ func (fs *filesystem) doCreateAt(rp *vfs.ResolvingPath, dir bool, create func(pa
return err
}
defer mnt.EndWrite()
return create(parent, name)
if err := create(parent, name); err != nil {
return err
}
parent.inode.touchCMtime()
return nil
}
// AccessAt implements vfs.Filesystem.Impl.AccessAt.
@ -328,7 +338,12 @@ afterTrailingSymlink:
child := fs.newDentry(fs.newRegularFile(rp.Credentials(), opts.Mode))
parent.vfsd.InsertChild(&child.vfsd, name)
parent.inode.impl.(*directory).childList.PushBack(child)
return child.open(ctx, rp, &opts, true)
fd, err := child.open(ctx, rp, &opts, true)
if err != nil {
return nil, err
}
parent.inode.touchCMtime()
return fd, nil
}
if err != nil {
return nil, err
@ -398,6 +413,7 @@ func (fs *filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (st
if !ok {
return "", syserror.EINVAL
}
symlink.inode.touchAtime(rp.Mount())
return symlink.target, nil
}
@ -515,6 +531,9 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
oldParent.inode.decLinksLocked()
newParent.inode.incLinksLocked()
}
oldParent.inode.touchCMtime()
newParent.inode.touchCMtime()
renamed.inode.touchCtime()
// TODO(gvisor.dev/issue/1197): Update timestamps and parent directory
// sizes.
vfsObj.CommitRenameReplaceDentry(renamedVFSD, &newParent.vfsd, newName, replacedVFSD)
@ -565,6 +584,7 @@ func (fs *filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error
parent.inode.decLinksLocked() // from child's ".."
child.inode.decLinksLocked()
vfsObj.CommitDeleteDentry(childVFSD)
parent.inode.touchCMtime()
return nil
}
@ -654,6 +674,7 @@ func (fs *filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error
parent.inode.impl.(*directory).childList.Remove(child)
child.inode.decLinksLocked()
vfsObj.CommitDeleteDentry(childVFSD)
parent.inode.touchCMtime()
return nil
}

View File

@ -286,7 +286,8 @@ func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offs
rw := getRegularFileReadWriter(f, offset)
n, err := dst.CopyOutFrom(ctx, rw)
putRegularFileReadWriter(rw)
return int64(n), err
fd.inode().touchAtime(fd.vfsfd.Mount())
return n, err
}
// Read implements vfs.FileDescriptionImpl.Read.
@ -323,6 +324,7 @@ func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, off
f.inode.mu.Lock()
rw := getRegularFileReadWriter(f, offset)
n, err := src.CopyInTo(ctx, rw)
fd.inode().touchCMtimeLocked()
f.inode.mu.Unlock()
putRegularFileReadWriter(rw)
return n, err

View File

@ -385,28 +385,41 @@ func (i *inode) setStat(ctx context.Context, creds *auth.Credentials, stat *linu
return syserror.EINVAL
}
}
now := i.clock.Now().Nanoseconds()
if mask&linux.STATX_ATIME != 0 {
atomic.StoreInt64(&i.atime, stat.Atime.ToNsecCapped())
if stat.Atime.Nsec == linux.UTIME_NOW {
atomic.StoreInt64(&i.atime, now)
} else {
atomic.StoreInt64(&i.atime, stat.Atime.ToNsecCapped())
}
needsCtimeBump = true
}
if mask&linux.STATX_MTIME != 0 {
atomic.StoreInt64(&i.mtime, stat.Mtime.ToNsecCapped())
if stat.Mtime.Nsec == linux.UTIME_NOW {
atomic.StoreInt64(&i.mtime, now)
} else {
atomic.StoreInt64(&i.mtime, stat.Mtime.ToNsecCapped())
}
needsCtimeBump = true
// Ignore the mtime bump, since we just set it ourselves.
needsMtimeBump = false
}
if mask&linux.STATX_CTIME != 0 {
atomic.StoreInt64(&i.ctime, stat.Ctime.ToNsecCapped())
if stat.Ctime.Nsec == linux.UTIME_NOW {
atomic.StoreInt64(&i.ctime, now)
} else {
atomic.StoreInt64(&i.ctime, stat.Ctime.ToNsecCapped())
}
// Ignore the ctime bump, since we just set it ourselves.
needsCtimeBump = false
}
now := i.clock.Now().Nanoseconds()
if needsMtimeBump {
atomic.StoreInt64(&i.mtime, now)
}
if needsCtimeBump {
atomic.StoreInt64(&i.ctime, now)
}
i.mu.Unlock()
return nil
}
@ -484,6 +497,42 @@ func (i *inode) isDir() bool {
return linux.FileMode(i.mode).FileType() == linux.S_IFDIR
}
func (i *inode) touchAtime(mnt *vfs.Mount) {
if err := mnt.CheckBeginWrite(); err != nil {
return
}
now := i.clock.Now().Nanoseconds()
i.mu.Lock()
atomic.StoreInt64(&i.atime, now)
i.mu.Unlock()
mnt.EndWrite()
}
// Preconditions: The caller has called vfs.Mount.CheckBeginWrite().
func (i *inode) touchCtime() {
now := i.clock.Now().Nanoseconds()
i.mu.Lock()
atomic.StoreInt64(&i.ctime, now)
i.mu.Unlock()
}
// Preconditions: The caller has called vfs.Mount.CheckBeginWrite().
func (i *inode) touchCMtime() {
now := i.clock.Now().Nanoseconds()
i.mu.Lock()
atomic.StoreInt64(&i.mtime, now)
atomic.StoreInt64(&i.ctime, now)
i.mu.Unlock()
}
// Preconditions: The caller has called vfs.Mount.CheckBeginWrite() and holds
// inode.mu.
func (i *inode) touchCMtimeLocked() {
now := i.clock.Now().Nanoseconds()
atomic.StoreInt64(&i.mtime, now)
atomic.StoreInt64(&i.ctime, now)
}
// fileDescription is embedded by tmpfs implementations of
// vfs.FileDescriptionImpl.
type fileDescription struct {