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 }