| // Copyright 2018 The gVisor Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package seqfile |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "testing" |
| |
| "gvisor.dev/gvisor/pkg/context" |
| "gvisor.dev/gvisor/pkg/sentry/contexttest" |
| "gvisor.dev/gvisor/pkg/sentry/fs" |
| "gvisor.dev/gvisor/pkg/sentry/fs/ramfs" |
| "gvisor.dev/gvisor/pkg/usermem" |
| ) |
| |
| type seqTest struct { |
| actual []SeqData |
| update bool |
| } |
| |
| func (s *seqTest) Init() { |
| var sq []SeqData |
| // Create some SeqData. |
| for i := 0; i < 10; i++ { |
| var b []byte |
| for j := 0; j < 10; j++ { |
| b = append(b, byte(i)) |
| } |
| sq = append(sq, SeqData{ |
| Buf: b, |
| Handle: &testHandle{i: i}, |
| }) |
| } |
| s.actual = sq |
| } |
| |
| // NeedsUpdate reports whether we need to update the data we've previously read. |
| func (s *seqTest) NeedsUpdate(int64) bool { |
| return s.update |
| } |
| |
| // ReadSeqFiledata returns a slice of SeqData which contains elements |
| // greater than the handle. |
| func (s *seqTest) ReadSeqFileData(ctx context.Context, handle SeqHandle) ([]SeqData, int64) { |
| if handle == nil { |
| return s.actual, 0 |
| } |
| h := *handle.(*testHandle) |
| var ret []SeqData |
| for _, b := range s.actual { |
| // We want the next one. |
| h2 := *b.Handle.(*testHandle) |
| if h2.i > h.i { |
| ret = append(ret, b) |
| } |
| } |
| return ret, 0 |
| } |
| |
| // Flatten a slice of slices into one slice. |
| func flatten(buf ...[]byte) []byte { |
| var flat []byte |
| for _, b := range buf { |
| flat = append(flat, b...) |
| } |
| return flat |
| } |
| |
| type testHandle struct { |
| i int |
| } |
| |
| type testTable struct { |
| offset int64 |
| readBufferSize int |
| expectedData []byte |
| expectedError error |
| } |
| |
| func runTableTests(ctx context.Context, table []testTable, dirent *fs.Dirent) error { |
| for _, tt := range table { |
| file, err := dirent.Inode.InodeOperations.GetFile(ctx, dirent, fs.FileFlags{Read: true}) |
| if err != nil { |
| return fmt.Errorf("GetFile returned error: %v", err) |
| } |
| |
| data := make([]byte, tt.readBufferSize) |
| resultLen, err := file.Preadv(ctx, usermem.BytesIOSequence(data), tt.offset) |
| if err != tt.expectedError { |
| return fmt.Errorf("t.Preadv(len: %v, offset: %v) (error) => %v expected %v", tt.readBufferSize, tt.offset, err, tt.expectedError) |
| } |
| expectedLen := int64(len(tt.expectedData)) |
| if resultLen != expectedLen { |
| // We make this just an error so we wall through and print the data below. |
| return fmt.Errorf("t.Preadv(len: %v, offset: %v) (size) => %v expected %v", tt.readBufferSize, tt.offset, resultLen, expectedLen) |
| } |
| if !bytes.Equal(data[:expectedLen], tt.expectedData) { |
| return fmt.Errorf("t.Preadv(len: %v, offset: %v) (data) => %v expected %v", tt.readBufferSize, tt.offset, data[:expectedLen], tt.expectedData) |
| } |
| } |
| return nil |
| } |
| |
| func TestSeqFile(t *testing.T) { |
| testSource := &seqTest{} |
| testSource.Init() |
| |
| // Create a file that can be R/W. |
| ctx := contexttest.Context(t) |
| m := fs.NewPseudoMountSource(ctx) |
| contents := map[string]*fs.Inode{ |
| "foo": NewSeqFileInode(ctx, testSource, m), |
| } |
| root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777)) |
| |
| // How about opening it? |
| inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory}) |
| dirent2, err := root.Lookup(ctx, inode, "foo") |
| if err != nil { |
| t.Fatalf("failed to walk to foo for n2: %v", err) |
| } |
| n2 := dirent2.Inode.InodeOperations |
| file2, err := n2.GetFile(ctx, dirent2, fs.FileFlags{Read: true, Write: true}) |
| if err != nil { |
| t.Fatalf("GetFile returned error: %v", err) |
| } |
| |
| // Writing? |
| if _, err := file2.Writev(ctx, usermem.BytesIOSequence([]byte("test"))); err == nil { |
| t.Fatalf("managed to write to n2: %v", err) |
| } |
| |
| // How about reading? |
| dirent3, err := root.Lookup(ctx, inode, "foo") |
| if err != nil { |
| t.Fatalf("failed to walk to foo: %v", err) |
| } |
| n3 := dirent3.Inode.InodeOperations |
| if n2 != n3 { |
| t.Error("got n2 != n3, want same") |
| } |
| |
| testSource.update = true |
| |
| table := []testTable{ |
| // Read past the end. |
| {100, 4, []byte{}, io.EOF}, |
| {110, 4, []byte{}, io.EOF}, |
| {200, 4, []byte{}, io.EOF}, |
| // Read a truncated first line. |
| {0, 4, testSource.actual[0].Buf[:4], nil}, |
| // Read the whole first line. |
| {0, 10, testSource.actual[0].Buf, nil}, |
| // Read the whole first line + 5 bytes of second line. |
| {0, 15, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:5]), nil}, |
| // First 4 bytes of the second line. |
| {10, 4, testSource.actual[1].Buf[:4], nil}, |
| // Read the two first lines. |
| {0, 20, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf), nil}, |
| // Read three lines. |
| {0, 30, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf), nil}, |
| // Read everything, but use a bigger buffer than necessary. |
| {0, 150, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf, testSource.actual[3].Buf, testSource.actual[4].Buf, testSource.actual[5].Buf, testSource.actual[6].Buf, testSource.actual[7].Buf, testSource.actual[8].Buf, testSource.actual[9].Buf), nil}, |
| // Read the last 3 bytes. |
| {97, 10, testSource.actual[9].Buf[7:], nil}, |
| } |
| if err := runTableTests(ctx, table, dirent2); err != nil { |
| t.Errorf("runTableTest failed with testSource.update = %v : %v", testSource.update, err) |
| } |
| |
| // Disable updates and do it again. |
| testSource.update = false |
| if err := runTableTests(ctx, table, dirent2); err != nil { |
| t.Errorf("runTableTest failed with testSource.update = %v: %v", testSource.update, err) |
| } |
| } |
| |
| // Test that we behave correctly when the file is updated. |
| func TestSeqFileFileUpdated(t *testing.T) { |
| testSource := &seqTest{} |
| testSource.Init() |
| testSource.update = true |
| |
| // Create a file that can be R/W. |
| ctx := contexttest.Context(t) |
| m := fs.NewPseudoMountSource(ctx) |
| contents := map[string]*fs.Inode{ |
| "foo": NewSeqFileInode(ctx, testSource, m), |
| } |
| root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777)) |
| |
| // How about opening it? |
| inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory}) |
| dirent2, err := root.Lookup(ctx, inode, "foo") |
| if err != nil { |
| t.Fatalf("failed to walk to foo for dirent2: %v", err) |
| } |
| |
| table := []testTable{ |
| {0, 16, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:6]), nil}, |
| } |
| if err := runTableTests(ctx, table, dirent2); err != nil { |
| t.Errorf("runTableTest failed: %v", err) |
| } |
| // Delete the first entry. |
| cut := testSource.actual[0].Buf |
| testSource.actual = testSource.actual[1:] |
| |
| table = []testTable{ |
| // Try reading buffer 0 with an offset. This will not delete the old data. |
| {1, 5, cut[1:6], nil}, |
| // Reset our file by reading at offset 0. |
| {0, 10, testSource.actual[0].Buf, nil}, |
| {16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil}, |
| // Read the same data a second time. |
| {16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil}, |
| // Read the following two lines. |
| {30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil}, |
| } |
| if err := runTableTests(ctx, table, dirent2); err != nil { |
| t.Errorf("runTableTest failed after removing first entry: %v", err) |
| } |
| |
| // Add a new duplicate line in the middle (6666...) |
| after := testSource.actual[5:] |
| testSource.actual = testSource.actual[:4] |
| // Note the list must be sorted. |
| testSource.actual = append(testSource.actual, after[0]) |
| testSource.actual = append(testSource.actual, after...) |
| |
| table = []testTable{ |
| {50, 20, flatten(testSource.actual[4].Buf, testSource.actual[5].Buf), nil}, |
| } |
| if err := runTableTests(ctx, table, dirent2); err != nil { |
| t.Errorf("runTableTest failed after adding middle entry: %v", err) |
| } |
| // This will be used in a later test. |
| oldTestData := testSource.actual |
| |
| // Delete everything. |
| testSource.actual = testSource.actual[:0] |
| table = []testTable{ |
| {20, 20, []byte{}, io.EOF}, |
| } |
| if err := runTableTests(ctx, table, dirent2); err != nil { |
| t.Errorf("runTableTest failed after removing all entries: %v", err) |
| } |
| // Restore some of the data. |
| testSource.actual = oldTestData[:1] |
| table = []testTable{ |
| {6, 20, testSource.actual[0].Buf[6:], nil}, |
| } |
| if err := runTableTests(ctx, table, dirent2); err != nil { |
| t.Errorf("runTableTest failed after adding first entry back: %v", err) |
| } |
| |
| // Re-extend the data |
| testSource.actual = oldTestData |
| table = []testTable{ |
| {30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil}, |
| } |
| if err := runTableTests(ctx, table, dirent2); err != nil { |
| t.Errorf("runTableTest failed after extending testSource: %v", err) |
| } |
| } |