gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/tmpfs/regular_file_test.go (about)

     1  // Copyright 2019 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 tmpfs
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"testing"
    22  
    23  	"gvisor.dev/gvisor/pkg/abi/linux"
    24  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    25  	"gvisor.dev/gvisor/pkg/sentry/contexttest"
    26  	"gvisor.dev/gvisor/pkg/sentry/fsimpl/lock"
    27  	"gvisor.dev/gvisor/pkg/sentry/vfs"
    28  	"gvisor.dev/gvisor/pkg/usermem"
    29  )
    30  
    31  // Test that we can write some data to a file and read it back.`
    32  func TestSimpleWriteRead(t *testing.T) {
    33  	ctx := contexttest.Context(t)
    34  	fd, cleanup, err := newFileFD(ctx, 0644)
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  	defer cleanup()
    39  
    40  	// Write.
    41  	data := []byte("foobarbaz")
    42  	n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
    43  	if err != nil {
    44  		t.Fatalf("fd.Write failed: %v", err)
    45  	}
    46  	if n != int64(len(data)) {
    47  		t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
    48  	}
    49  	if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want {
    50  		t.Errorf("fd.Write left offset at %d, want %d", got, want)
    51  	}
    52  
    53  	// Seek back to beginning.
    54  	if _, err := fd.Seek(ctx, 0, linux.SEEK_SET); err != nil {
    55  		t.Fatalf("fd.Seek failed: %v", err)
    56  	}
    57  	if got, want := fd.Impl().(*regularFileFD).off, int64(0); got != want {
    58  		t.Errorf("fd.Seek(0) left offset at %d, want %d", got, want)
    59  	}
    60  
    61  	// Read.
    62  	buf := make([]byte, len(data))
    63  	n, err = fd.Read(ctx, usermem.BytesIOSequence(buf), vfs.ReadOptions{})
    64  	if err != nil && err != io.EOF {
    65  		t.Fatalf("fd.Read failed: %v", err)
    66  	}
    67  	if n != int64(len(data)) {
    68  		t.Errorf("fd.Read got short read length %d, want %d", n, len(data))
    69  	}
    70  	if got, want := string(buf), string(data); got != want {
    71  		t.Errorf("Read got %q want %s", got, want)
    72  	}
    73  	if got, want := fd.Impl().(*regularFileFD).off, int64(len(data)); got != want {
    74  		t.Errorf("fd.Write left offset at %d, want %d", got, want)
    75  	}
    76  }
    77  
    78  func TestPWrite(t *testing.T) {
    79  	ctx := contexttest.Context(t)
    80  	fd, cleanup, err := newFileFD(ctx, 0644)
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  	defer cleanup()
    85  
    86  	// Fill file with 1k 'a's.
    87  	data := bytes.Repeat([]byte{'a'}, 1000)
    88  	n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
    89  	if err != nil {
    90  		t.Fatalf("fd.Write failed: %v", err)
    91  	}
    92  	if n != int64(len(data)) {
    93  		t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
    94  	}
    95  
    96  	// Write "gVisor is awesome" at various offsets.
    97  	buf := []byte("gVisor is awesome")
    98  	offsets := []int{0, 1, 2, 10, 20, 50, 100, len(data) - 100, len(data) - 1, len(data), len(data) + 1}
    99  	for _, offset := range offsets {
   100  		name := fmt.Sprintf("PWrite offset=%d", offset)
   101  		t.Run(name, func(t *testing.T) {
   102  			n, err := fd.PWrite(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.WriteOptions{})
   103  			if err != nil {
   104  				t.Errorf("fd.PWrite got err %v want nil", err)
   105  			}
   106  			if n != int64(len(buf)) {
   107  				t.Errorf("fd.PWrite got %d bytes want %d", n, len(buf))
   108  			}
   109  
   110  			// Update data to reflect expected file contents.
   111  			if len(data) < offset+len(buf) {
   112  				data = append(data, make([]byte, (offset+len(buf))-len(data))...)
   113  			}
   114  			copy(data[offset:], buf)
   115  
   116  			// Read the whole file and compare with data.
   117  			readBuf := make([]byte, len(data))
   118  			n, err = fd.PRead(ctx, usermem.BytesIOSequence(readBuf), 0, vfs.ReadOptions{})
   119  			if err != nil {
   120  				t.Fatalf("fd.PRead failed: %v", err)
   121  			}
   122  			if n != int64(len(data)) {
   123  				t.Errorf("fd.PRead got short read length %d, want %d", n, len(data))
   124  			}
   125  			if got, want := string(readBuf), string(data); got != want {
   126  				t.Errorf("PRead got %q want %s", got, want)
   127  			}
   128  
   129  		})
   130  	}
   131  }
   132  
   133  func TestLocks(t *testing.T) {
   134  	ctx := contexttest.Context(t)
   135  	fd, cleanup, err := newFileFD(ctx, 0644)
   136  	if err != nil {
   137  		t.Fatal(err)
   138  	}
   139  	defer cleanup()
   140  
   141  	uid1 := 123
   142  	uid2 := 456
   143  	if err := fd.Impl().LockBSD(ctx, uid1, 0 /* ownerPID */, lock.ReadLock, false /* block */); err != nil {
   144  		t.Fatalf("fd.Impl().LockBSD failed: err = %v", err)
   145  	}
   146  	if err := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, false /* block */); err != nil {
   147  		t.Fatalf("fd.Impl().LockBSD failed: err = %v", err)
   148  	}
   149  	if got, want := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.WriteLock, false /* block */), linuxerr.ErrWouldBlock; got != want {
   150  		t.Fatalf("fd.Impl().LockBSD failed: got = %v, want = %v", got, want)
   151  	}
   152  	if err := fd.Impl().UnlockBSD(ctx, uid1); err != nil {
   153  		t.Fatalf("fd.Impl().UnlockBSD failed: err = %v", err)
   154  	}
   155  	if err := fd.Impl().LockBSD(ctx, uid2, 0 /* ownerPID */, lock.WriteLock, false /* block */); err != nil {
   156  		t.Fatalf("fd.Impl().LockBSD failed: err = %v", err)
   157  	}
   158  
   159  	if err := fd.Impl().LockPOSIX(ctx, uid1, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 0, End: 1}, false /* block */); err != nil {
   160  		t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err)
   161  	}
   162  	if err := fd.Impl().LockPOSIX(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 1, End: 2}, false /* block */); err != nil {
   163  		t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err)
   164  	}
   165  	if err := fd.Impl().LockPOSIX(ctx, uid1, 0 /* ownerPID */, lock.WriteLock, lock.LockRange{Start: 0, End: 1}, false /* block */); err != nil {
   166  		t.Fatalf("fd.Impl().LockPOSIX failed: err = %v", err)
   167  	}
   168  	if got, want := fd.Impl().LockPOSIX(ctx, uid2, 0 /* ownerPID */, lock.ReadLock, lock.LockRange{Start: 0, End: 1}, false /* block */), linuxerr.ErrWouldBlock; got != want {
   169  		t.Fatalf("fd.Impl().LockPOSIX failed: got = %v, want = %v", got, want)
   170  	}
   171  	if err := fd.Impl().UnlockPOSIX(ctx, uid1, lock.LockRange{Start: 0, End: 1}); err != nil {
   172  		t.Fatalf("fd.Impl().UnlockPOSIX failed: err = %v", err)
   173  	}
   174  }
   175  
   176  func TestPRead(t *testing.T) {
   177  	ctx := contexttest.Context(t)
   178  	fd, cleanup, err := newFileFD(ctx, 0644)
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  	defer cleanup()
   183  
   184  	// Write 100 sequences of 'gVisor is awesome'.
   185  	data := bytes.Repeat([]byte("gVisor is awesome"), 100)
   186  	n, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
   187  	if err != nil {
   188  		t.Fatalf("fd.Write failed: %v", err)
   189  	}
   190  	if n != int64(len(data)) {
   191  		t.Errorf("fd.Write got short write length %d, want %d", n, len(data))
   192  	}
   193  
   194  	// Read various sizes from various offsets.
   195  	sizes := []int{0, 1, 2, 10, 20, 50, 100, 1000}
   196  	offsets := []int{0, 1, 2, 10, 20, 50, 100, 1000, len(data) - 100, len(data) - 1, len(data), len(data) + 1}
   197  
   198  	for _, size := range sizes {
   199  		for _, offset := range offsets {
   200  			name := fmt.Sprintf("PRead offset=%d size=%d", offset, size)
   201  			t.Run(name, func(t *testing.T) {
   202  				var (
   203  					wantRead []byte
   204  					wantErr  error
   205  				)
   206  				if offset < len(data) {
   207  					wantRead = data[offset:]
   208  				} else if size > 0 {
   209  					wantErr = io.EOF
   210  				}
   211  				if offset+size < len(data) {
   212  					wantRead = wantRead[:size]
   213  				}
   214  				buf := make([]byte, size)
   215  				n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), int64(offset), vfs.ReadOptions{})
   216  				if err != wantErr {
   217  					t.Errorf("fd.PRead got err %v want %v", err, wantErr)
   218  				}
   219  				if n != int64(len(wantRead)) {
   220  					t.Errorf("fd.PRead got %d bytes want %d", n, len(wantRead))
   221  				}
   222  				if got := string(buf[:n]); got != string(wantRead) {
   223  					t.Errorf("fd.PRead got %q want %q", got, string(wantRead))
   224  				}
   225  			})
   226  		}
   227  	}
   228  }
   229  
   230  func TestTruncate(t *testing.T) {
   231  	ctx := contexttest.Context(t)
   232  	fd, cleanup, err := newFileFD(ctx, 0644)
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	defer cleanup()
   237  
   238  	// Fill the file with some data.
   239  	data := bytes.Repeat([]byte("gVisor is awesome"), 100)
   240  	written, err := fd.Write(ctx, usermem.BytesIOSequence(data), vfs.WriteOptions{})
   241  	if err != nil {
   242  		t.Fatalf("fd.Write failed: %v", err)
   243  	}
   244  
   245  	// Size should be same as written.
   246  	sizeStatOpts := vfs.StatOptions{Mask: linux.STATX_SIZE}
   247  	stat, err := fd.Stat(ctx, sizeStatOpts)
   248  	if err != nil {
   249  		t.Fatalf("fd.Stat failed: %v", err)
   250  	}
   251  	if got, want := int64(stat.Size), written; got != want {
   252  		t.Errorf("fd.Stat got size %d, want %d", got, want)
   253  	}
   254  
   255  	// Truncate down.
   256  	newSize := uint64(10)
   257  	if err := fd.SetStat(ctx, vfs.SetStatOptions{
   258  		Stat: linux.Statx{
   259  			Mask: linux.STATX_SIZE,
   260  			Size: newSize,
   261  		},
   262  	}); err != nil {
   263  		t.Errorf("fd.Truncate failed: %v", err)
   264  	}
   265  	// Size should be updated.
   266  	statAfterTruncateDown, err := fd.Stat(ctx, sizeStatOpts)
   267  	if err != nil {
   268  		t.Fatalf("fd.Stat failed: %v", err)
   269  	}
   270  	if got, want := statAfterTruncateDown.Size, newSize; got != want {
   271  		t.Errorf("fd.Stat got size %d, want %d", got, want)
   272  	}
   273  	// We should only read newSize worth of data.
   274  	buf := make([]byte, 1000)
   275  	if n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0, vfs.ReadOptions{}); err != nil && err != io.EOF {
   276  		t.Fatalf("fd.PRead failed: %v", err)
   277  	} else if uint64(n) != newSize {
   278  		t.Errorf("fd.PRead got size %d, want %d", n, newSize)
   279  	}
   280  	// Mtime and Ctime should be bumped.
   281  	if got := statAfterTruncateDown.Mtime.ToNsec(); got <= stat.Mtime.ToNsec() {
   282  		t.Errorf("fd.Stat got Mtime %v, want > %v", got, stat.Mtime)
   283  	}
   284  	if got := statAfterTruncateDown.Ctime.ToNsec(); got <= stat.Ctime.ToNsec() {
   285  		t.Errorf("fd.Stat got Ctime %v, want > %v", got, stat.Ctime)
   286  	}
   287  
   288  	// Truncate up.
   289  	newSize = 100
   290  	if err := fd.SetStat(ctx, vfs.SetStatOptions{
   291  		Stat: linux.Statx{
   292  			Mask: linux.STATX_SIZE,
   293  			Size: newSize,
   294  		},
   295  	}); err != nil {
   296  		t.Errorf("fd.Truncate failed: %v", err)
   297  	}
   298  	// Size should be updated.
   299  	statAfterTruncateUp, err := fd.Stat(ctx, sizeStatOpts)
   300  	if err != nil {
   301  		t.Fatalf("fd.Stat failed: %v", err)
   302  	}
   303  	if got, want := statAfterTruncateUp.Size, newSize; got != want {
   304  		t.Errorf("fd.Stat got size %d, want %d", got, want)
   305  	}
   306  	// We should read newSize worth of data.
   307  	buf = make([]byte, 1000)
   308  	if n, err := fd.PRead(ctx, usermem.BytesIOSequence(buf), 0, vfs.ReadOptions{}); err != nil && err != io.EOF {
   309  		t.Fatalf("fd.PRead failed: %v", err)
   310  	} else if uint64(n) != newSize {
   311  		t.Errorf("fd.PRead got size %d, want %d", n, newSize)
   312  	}
   313  	// Bytes should be null after 10, since we previously truncated to 10.
   314  	for i := uint64(10); i < newSize; i++ {
   315  		if buf[i] != 0 {
   316  			t.Errorf("fd.PRead got byte %d=%x, want 0", i, buf[i])
   317  			break
   318  		}
   319  	}
   320  	// Mtime and Ctime should be bumped.
   321  	if got := statAfterTruncateUp.Mtime.ToNsec(); got <= statAfterTruncateDown.Mtime.ToNsec() {
   322  		t.Errorf("fd.Stat got Mtime %v, want > %v", got, statAfterTruncateDown.Mtime)
   323  	}
   324  	if got := statAfterTruncateUp.Ctime.ToNsec(); got <= statAfterTruncateDown.Ctime.ToNsec() {
   325  		t.Errorf("fd.Stat got Ctime %v, want > %v", got, stat.Ctime)
   326  	}
   327  
   328  	// Truncate to the current size.
   329  	newSize = statAfterTruncateUp.Size
   330  	if err := fd.SetStat(ctx, vfs.SetStatOptions{
   331  		Stat: linux.Statx{
   332  			Mask: linux.STATX_SIZE,
   333  			Size: newSize,
   334  		},
   335  	}); err != nil {
   336  		t.Errorf("fd.Truncate failed: %v", err)
   337  	}
   338  	statAfterTruncateNoop, err := fd.Stat(ctx, sizeStatOpts)
   339  	if err != nil {
   340  		t.Fatalf("fd.Stat failed: %v", err)
   341  	}
   342  	// Mtime and Ctime should not be bumped, since operation is a noop.
   343  	if got := statAfterTruncateNoop.Mtime.ToNsec(); got != statAfterTruncateUp.Mtime.ToNsec() {
   344  		t.Errorf("fd.Stat got Mtime %v, want %v", got, statAfterTruncateUp.Mtime)
   345  	}
   346  	if got := statAfterTruncateNoop.Ctime.ToNsec(); got != statAfterTruncateUp.Ctime.ToNsec() {
   347  		t.Errorf("fd.Stat got Ctime %v, want %v", got, statAfterTruncateUp.Ctime)
   348  	}
   349  }