diff --git a/pkg/sentry/fsimpl/tmpfs/tmpfs.go b/pkg/sentry/fsimpl/tmpfs/tmpfs.go index d535f0b73..432a03490 100644 --- a/pkg/sentry/fsimpl/tmpfs/tmpfs.go +++ b/pkg/sentry/fsimpl/tmpfs/tmpfs.go @@ -202,7 +202,7 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt var maxSizeInPages uint64 if ok { delete(mopts, "size") - maxSizeInBytes, err := strconv.ParseUint(maxSizeStr, 10, 64) + maxSizeInBytes, err := parseSize(maxSizeStr) if err != nil { ctx.Warningf("tmpfs.FilesystemType.GetFilesystem: invalid size: %q", maxSizeStr) return nil, nil, linuxerr.EINVAL @@ -944,3 +944,33 @@ func (fd *fileDescription) RemoveXattr(ctx context.Context, name string) error { func (*fileDescription) Sync(context.Context) error { return nil } + +// parseSize converts size in string to an integer bytes. +// Supported suffixes in string are:K, M, G, T, P, E. +func parseSize(s string) (uint64, error) { + suffix := s[len(s)-1] + count := 1 + switch suffix { + case 'e', 'E': + count = count << 10 + fallthrough + case 'p', 'P': + count = count << 10 + fallthrough + case 't', 'T': + count = count << 10 + fallthrough + case 'g', 'G': + count = count << 10 + fallthrough + case 'm', 'M': + count = count << 10 + fallthrough + case 'k', 'K': + count = count << 10 + s = s[:len(s)-1] + } + bytes, err := strconv.ParseUint(s, 10, 64) + bytes = bytes * uint64(count) + return bytes, err +} diff --git a/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go b/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go index 8da9a6cb5..2aa58c8ec 100644 --- a/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go +++ b/pkg/sentry/fsimpl/tmpfs/tmpfs_test.go @@ -16,6 +16,7 @@ package tmpfs import ( "fmt" + "testing" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/atomicbitops" @@ -155,3 +156,37 @@ func newPipeFD(ctx context.Context, mode linux.FileMode) (*vfs.FileDescription, return fd, cleanup, nil } + +func TestParseSize(t *testing.T) { + var tests = []struct { + s string + want uint64 + wantError bool + }{ + {"500", 500, false}, + {"5k", (5 * 1024), false}, + {"5m", (5 * 1024 * 1024), false}, + {"5G", (5 * 1024 * 1024 * 1024), false}, + {"5t", (5 * 1024 * 1024 * 1024 * 1024), false}, + {"5P", (5 * 1024 * 1024 * 1024 * 1024 * 1024), false}, + {"5e", (5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024), false}, + {"5e3", 0, true}, + } + for _, tt := range tests { + testname := fmt.Sprintf("%s", tt.s) + t.Run(testname, func(t *testing.T) { + size, err := parseSize(tt.s) + if tt.wantError && err == nil { + t.Errorf("Invalid input: %v parsed", tt.s) + } + if !tt.wantError { + if err != nil { + t.Errorf("Couldn't parse size, Error: %v", err) + } + if size != tt.want { + t.Errorf("got: %v, want %v", size, tt.want) + } + } + }) + } +} diff --git a/runsc/boot/fs.go b/runsc/boot/fs.go index c8bc07085..86bf04ca2 100644 --- a/runsc/boot/fs.go +++ b/runsc/boot/fs.go @@ -67,7 +67,7 @@ const ( ) // tmpfs has some extra supported options that we must pass through. -var tmpfsAllowedData = []string{"mode", "uid", "gid"} +var tmpfsAllowedData = []string{"mode", "size", "uid", "gid"} func addOverlay(ctx context.Context, lower *fs.Inode, name string, lowerFlags fs.MountSourceFlags) (*fs.Inode, error) { // Upper layer uses the same flags as lower, but it must be read-write. diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index b6e24bf5d..4f7e3889e 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -911,3 +911,42 @@ func TestRevalidateSymlinkChain(t *testing.T) { t.Fatalf("Read wrong file, want: %q, got: %q", want, got) } } + +// TestTmpMountWithSize checks when 'tmpfs' is mounted +// with size option the limit is not exceeded. +func TestTmpMountWithSize(t *testing.T) { + ctx := context.Background() + d := dockerutil.MakeContainer(ctx, t) + defer d.CleanUp(ctx) + + opts := dockerutil.RunOpts{ + Image: "basic/alpine", + Mounts: []mount.Mount{ + { + Type: mount.TypeTmpfs, + Target: "/tmp/foo", + TmpfsOptions: &mount.TmpfsOptions{ + SizeBytes: 4096, + }, + }, + }, + } + if err := d.Create(ctx, opts, "sleep", "1000"); err != nil { + t.Fatalf("docker create failed: %v", err) + } + if err := d.Start(ctx); err != nil { + t.Fatalf("docker start failed: %v", err) + } + + if _, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo hello > /tmp/foo/test1.txt"); err != nil { + t.Fatalf("docker exec failed: %v", err) + } + echoOutput, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", "echo world > /tmp/foo/test2.txt") + if err == nil { + t.Fatalf("docker exec size check failed: %v", err) + } + wantErr := "No space left on device" + if !strings.Contains(echoOutput, wantErr) { + t.Errorf("unexpected echo error:Expected: %v, Got: %v", wantErr, echoOutput) + } +}