github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/fsutil/inode_cached_test.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fsutil
    16  
    17  import (
    18  	"bytes"
    19  	"io"
    20  	"testing"
    21  
    22  	"github.com/SagerNet/gvisor/pkg/context"
    23  	"github.com/SagerNet/gvisor/pkg/hostarch"
    24  	"github.com/SagerNet/gvisor/pkg/safemem"
    25  	"github.com/SagerNet/gvisor/pkg/sentry/contexttest"
    26  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    27  	ktime "github.com/SagerNet/gvisor/pkg/sentry/kernel/time"
    28  	"github.com/SagerNet/gvisor/pkg/sentry/memmap"
    29  	"github.com/SagerNet/gvisor/pkg/syserror"
    30  	"github.com/SagerNet/gvisor/pkg/usermem"
    31  )
    32  
    33  type noopBackingFile struct{}
    34  
    35  func (noopBackingFile) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) {
    36  	return dsts.NumBytes(), nil
    37  }
    38  
    39  func (noopBackingFile) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
    40  	return srcs.NumBytes(), nil
    41  }
    42  
    43  func (noopBackingFile) SetMaskedAttributes(context.Context, fs.AttrMask, fs.UnstableAttr, bool) error {
    44  	return nil
    45  }
    46  
    47  func (noopBackingFile) Sync(context.Context) error {
    48  	return nil
    49  }
    50  
    51  func (noopBackingFile) FD() int {
    52  	return -1
    53  }
    54  
    55  func (noopBackingFile) Allocate(ctx context.Context, offset int64, length int64) error {
    56  	return nil
    57  }
    58  
    59  func TestSetPermissions(t *testing.T) {
    60  	ctx := contexttest.Context(t)
    61  
    62  	uattr := fs.WithCurrentTime(ctx, fs.UnstableAttr{
    63  		Perms: fs.FilePermsFromMode(0444),
    64  	})
    65  	iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
    66  	defer iops.Release()
    67  
    68  	perms := fs.FilePermsFromMode(0777)
    69  	if !iops.SetPermissions(ctx, nil, perms) {
    70  		t.Fatalf("SetPermissions failed, want success")
    71  	}
    72  
    73  	// Did permissions change?
    74  	if iops.attr.Perms != perms {
    75  		t.Fatalf("got perms +%v, want +%v", iops.attr.Perms, perms)
    76  	}
    77  
    78  	// Did status change time change?
    79  	if !iops.dirtyAttr.StatusChangeTime {
    80  		t.Fatalf("got status change time not dirty, want dirty")
    81  	}
    82  	if iops.attr.StatusChangeTime.Equal(uattr.StatusChangeTime) {
    83  		t.Fatalf("got status change time unchanged")
    84  	}
    85  }
    86  
    87  func TestSetTimestamps(t *testing.T) {
    88  	ctx := contexttest.Context(t)
    89  	for _, test := range []struct {
    90  		desc        string
    91  		ts          fs.TimeSpec
    92  		wantChanged fs.AttrMask
    93  	}{
    94  		{
    95  			desc: "noop",
    96  			ts: fs.TimeSpec{
    97  				ATimeOmit: true,
    98  				MTimeOmit: true,
    99  			},
   100  			wantChanged: fs.AttrMask{},
   101  		},
   102  		{
   103  			desc: "access time only",
   104  			ts: fs.TimeSpec{
   105  				ATime:     ktime.NowFromContext(ctx),
   106  				MTimeOmit: true,
   107  			},
   108  			wantChanged: fs.AttrMask{
   109  				AccessTime: true,
   110  			},
   111  		},
   112  		{
   113  			desc: "modification time only",
   114  			ts: fs.TimeSpec{
   115  				ATimeOmit: true,
   116  				MTime:     ktime.NowFromContext(ctx),
   117  			},
   118  			wantChanged: fs.AttrMask{
   119  				ModificationTime: true,
   120  			},
   121  		},
   122  		{
   123  			desc: "access and modification time",
   124  			ts: fs.TimeSpec{
   125  				ATime: ktime.NowFromContext(ctx),
   126  				MTime: ktime.NowFromContext(ctx),
   127  			},
   128  			wantChanged: fs.AttrMask{
   129  				AccessTime:       true,
   130  				ModificationTime: true,
   131  			},
   132  		},
   133  		{
   134  			desc: "system time access and modification time",
   135  			ts: fs.TimeSpec{
   136  				ATimeSetSystemTime: true,
   137  				MTimeSetSystemTime: true,
   138  			},
   139  			wantChanged: fs.AttrMask{
   140  				AccessTime:       true,
   141  				ModificationTime: true,
   142  			},
   143  		},
   144  	} {
   145  		t.Run(test.desc, func(t *testing.T) {
   146  			ctx := contexttest.Context(t)
   147  
   148  			epoch := ktime.ZeroTime
   149  			uattr := fs.UnstableAttr{
   150  				AccessTime:       epoch,
   151  				ModificationTime: epoch,
   152  				StatusChangeTime: epoch,
   153  			}
   154  			iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
   155  			defer iops.Release()
   156  
   157  			if err := iops.SetTimestamps(ctx, nil, test.ts); err != nil {
   158  				t.Fatalf("SetTimestamps got error %v, want nil", err)
   159  			}
   160  			if test.wantChanged.AccessTime {
   161  				if !iops.attr.AccessTime.After(uattr.AccessTime) {
   162  					t.Fatalf("diritied access time did not advance, want %v > %v", iops.attr.AccessTime, uattr.AccessTime)
   163  				}
   164  				if !iops.dirtyAttr.StatusChangeTime {
   165  					t.Fatalf("dirty access time requires dirty status change time")
   166  				}
   167  				if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
   168  					t.Fatalf("dirtied status change time did not advance")
   169  				}
   170  			}
   171  			if test.wantChanged.ModificationTime {
   172  				if !iops.attr.ModificationTime.After(uattr.ModificationTime) {
   173  					t.Fatalf("diritied modification time did not advance")
   174  				}
   175  				if !iops.dirtyAttr.StatusChangeTime {
   176  					t.Fatalf("dirty modification time requires dirty status change time")
   177  				}
   178  				if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
   179  					t.Fatalf("dirtied status change time did not advance")
   180  				}
   181  			}
   182  		})
   183  	}
   184  }
   185  
   186  func TestTruncate(t *testing.T) {
   187  	ctx := contexttest.Context(t)
   188  
   189  	uattr := fs.UnstableAttr{
   190  		Size: 0,
   191  	}
   192  	iops := NewCachingInodeOperations(ctx, noopBackingFile{}, uattr, CachingInodeOperationsOptions{})
   193  	defer iops.Release()
   194  
   195  	if err := iops.Truncate(ctx, nil, uattr.Size); err != nil {
   196  		t.Fatalf("Truncate got error %v, want nil", err)
   197  	}
   198  	var size int64 = 4096
   199  	if err := iops.Truncate(ctx, nil, size); err != nil {
   200  		t.Fatalf("Truncate got error %v, want nil", err)
   201  	}
   202  	if iops.attr.Size != size {
   203  		t.Fatalf("Truncate got %d, want %d", iops.attr.Size, size)
   204  	}
   205  	if !iops.dirtyAttr.ModificationTime || !iops.dirtyAttr.StatusChangeTime {
   206  		t.Fatalf("Truncate did not dirty modification and status change time")
   207  	}
   208  	if !iops.attr.ModificationTime.After(uattr.ModificationTime) {
   209  		t.Fatalf("dirtied modification time did not change")
   210  	}
   211  	if !iops.attr.StatusChangeTime.After(uattr.StatusChangeTime) {
   212  		t.Fatalf("dirtied status change time did not change")
   213  	}
   214  }
   215  
   216  type sliceBackingFile struct {
   217  	data []byte
   218  }
   219  
   220  func newSliceBackingFile(data []byte) *sliceBackingFile {
   221  	return &sliceBackingFile{data}
   222  }
   223  
   224  func (f *sliceBackingFile) ReadToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offset uint64) (uint64, error) {
   225  	r := safemem.BlockSeqReader{safemem.BlockSeqOf(safemem.BlockFromSafeSlice(f.data)).DropFirst64(offset)}
   226  	return r.ReadToBlocks(dsts)
   227  }
   228  
   229  func (f *sliceBackingFile) WriteFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
   230  	w := safemem.BlockSeqWriter{safemem.BlockSeqOf(safemem.BlockFromSafeSlice(f.data)).DropFirst64(offset)}
   231  	return w.WriteFromBlocks(srcs)
   232  }
   233  
   234  func (*sliceBackingFile) SetMaskedAttributes(context.Context, fs.AttrMask, fs.UnstableAttr, bool) error {
   235  	return nil
   236  }
   237  
   238  func (*sliceBackingFile) Sync(context.Context) error {
   239  	return nil
   240  }
   241  
   242  func (*sliceBackingFile) FD() int {
   243  	return -1
   244  }
   245  
   246  func (f *sliceBackingFile) Allocate(ctx context.Context, offset int64, length int64) error {
   247  	return syserror.EOPNOTSUPP
   248  }
   249  
   250  type noopMappingSpace struct{}
   251  
   252  // Invalidate implements memmap.MappingSpace.Invalidate.
   253  func (noopMappingSpace) Invalidate(ar hostarch.AddrRange, opts memmap.InvalidateOpts) {
   254  }
   255  
   256  func anonInode(ctx context.Context) *fs.Inode {
   257  	return fs.NewInode(ctx, &SimpleFileInode{
   258  		InodeSimpleAttributes: NewInodeSimpleAttributes(ctx, fs.FileOwnerFromContext(ctx), fs.FilePermissions{
   259  			User: fs.PermMask{Read: true, Write: true},
   260  		}, 0),
   261  	}, fs.NewPseudoMountSource(ctx), fs.StableAttr{
   262  		Type:      fs.Anonymous,
   263  		BlockSize: hostarch.PageSize,
   264  	})
   265  }
   266  
   267  func pagesOf(bs ...byte) []byte {
   268  	buf := make([]byte, 0, len(bs)*hostarch.PageSize)
   269  	for _, b := range bs {
   270  		buf = append(buf, bytes.Repeat([]byte{b}, hostarch.PageSize)...)
   271  	}
   272  	return buf
   273  }
   274  
   275  func TestRead(t *testing.T) {
   276  	ctx := contexttest.Context(t)
   277  
   278  	// Construct a 3-page file.
   279  	buf := pagesOf('a', 'b', 'c')
   280  	file := fs.NewFile(ctx, fs.NewDirent(ctx, anonInode(ctx), "anon"), fs.FileFlags{}, nil)
   281  	uattr := fs.UnstableAttr{
   282  		Size: int64(len(buf)),
   283  	}
   284  	iops := NewCachingInodeOperations(ctx, newSliceBackingFile(buf), uattr, CachingInodeOperationsOptions{})
   285  	defer iops.Release()
   286  
   287  	// Expect the cache to be initially empty.
   288  	if cached := iops.cache.Span(); cached != 0 {
   289  		t.Errorf("Span got %d, want 0", cached)
   290  	}
   291  
   292  	// Create a memory mapping of the second page (as CachingInodeOperations
   293  	// expects to only cache mapped pages), then call Translate to force it to
   294  	// be cached.
   295  	var ms noopMappingSpace
   296  	ar := hostarch.AddrRange{hostarch.PageSize, 2 * hostarch.PageSize}
   297  	if err := iops.AddMapping(ctx, ms, ar, hostarch.PageSize, true); err != nil {
   298  		t.Fatalf("AddMapping got %v, want nil", err)
   299  	}
   300  	mr := memmap.MappableRange{hostarch.PageSize, 2 * hostarch.PageSize}
   301  	if _, err := iops.Translate(ctx, mr, mr, hostarch.Read); err != nil {
   302  		t.Fatalf("Translate got %v, want nil", err)
   303  	}
   304  	if cached := iops.cache.Span(); cached != hostarch.PageSize {
   305  		t.Errorf("SpanRange got %d, want %d", cached, hostarch.PageSize)
   306  	}
   307  
   308  	// Try to read 4 pages. The first and third pages should be read directly
   309  	// from the "file", the second page should be read from the cache, and only
   310  	// 3 pages (the size of the file) should be readable.
   311  	rbuf := make([]byte, 4*hostarch.PageSize)
   312  	dst := usermem.BytesIOSequence(rbuf)
   313  	n, err := iops.Read(ctx, file, dst, 0)
   314  	if n != 3*hostarch.PageSize || (err != nil && err != io.EOF) {
   315  		t.Fatalf("Read got (%d, %v), want (%d, nil or EOF)", n, err, 3*hostarch.PageSize)
   316  	}
   317  	rbuf = rbuf[:3*hostarch.PageSize]
   318  
   319  	// Did we get the bytes we expect?
   320  	if !bytes.Equal(rbuf, buf) {
   321  		t.Errorf("Read back bytes %v, want %v", rbuf, buf)
   322  	}
   323  
   324  	// Delete the memory mapping before iops.Release(). The cached page will
   325  	// either be evicted by ctx's pgalloc.MemoryFile, or dropped by
   326  	// iops.Release().
   327  	iops.RemoveMapping(ctx, ms, ar, hostarch.PageSize, true)
   328  }
   329  
   330  func TestWrite(t *testing.T) {
   331  	ctx := contexttest.Context(t)
   332  
   333  	// Construct a 4-page file.
   334  	buf := pagesOf('a', 'b', 'c', 'd')
   335  	orig := append([]byte(nil), buf...)
   336  	inode := anonInode(ctx)
   337  	uattr := fs.UnstableAttr{
   338  		Size: int64(len(buf)),
   339  	}
   340  	iops := NewCachingInodeOperations(ctx, newSliceBackingFile(buf), uattr, CachingInodeOperationsOptions{})
   341  	defer iops.Release()
   342  
   343  	// Expect the cache to be initially empty.
   344  	if cached := iops.cache.Span(); cached != 0 {
   345  		t.Errorf("Span got %d, want 0", cached)
   346  	}
   347  
   348  	// Create a memory mapping of the second and third pages (as
   349  	// CachingInodeOperations expects to only cache mapped pages), then call
   350  	// Translate to force them to be cached.
   351  	var ms noopMappingSpace
   352  	ar := hostarch.AddrRange{hostarch.PageSize, 3 * hostarch.PageSize}
   353  	if err := iops.AddMapping(ctx, ms, ar, hostarch.PageSize, true); err != nil {
   354  		t.Fatalf("AddMapping got %v, want nil", err)
   355  	}
   356  	defer iops.RemoveMapping(ctx, ms, ar, hostarch.PageSize, true)
   357  	mr := memmap.MappableRange{hostarch.PageSize, 3 * hostarch.PageSize}
   358  	if _, err := iops.Translate(ctx, mr, mr, hostarch.Read); err != nil {
   359  		t.Fatalf("Translate got %v, want nil", err)
   360  	}
   361  	if cached := iops.cache.Span(); cached != 2*hostarch.PageSize {
   362  		t.Errorf("SpanRange got %d, want %d", cached, 2*hostarch.PageSize)
   363  	}
   364  
   365  	// Write to the first 2 pages.
   366  	wbuf := pagesOf('e', 'f')
   367  	src := usermem.BytesIOSequence(wbuf)
   368  	n, err := iops.Write(ctx, src, 0)
   369  	if n != 2*hostarch.PageSize || err != nil {
   370  		t.Fatalf("Write got (%d, %v), want (%d, nil)", n, err, 2*hostarch.PageSize)
   371  	}
   372  
   373  	// The first page should have been written directly, since it was not cached.
   374  	want := append([]byte(nil), orig...)
   375  	copy(want, pagesOf('e'))
   376  	if !bytes.Equal(buf, want) {
   377  		t.Errorf("File contents are %v, want %v", buf, want)
   378  	}
   379  
   380  	// Sync back to the "backing file".
   381  	if err := iops.WriteOut(ctx, inode); err != nil {
   382  		t.Errorf("Sync got %v, want nil", err)
   383  	}
   384  
   385  	// Now the second page should have been written as well.
   386  	copy(want[hostarch.PageSize:], pagesOf('f'))
   387  	if !bytes.Equal(buf, want) {
   388  		t.Errorf("File contents are %v, want %v", buf, want)
   389  	}
   390  }