Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 451544b

Browse files
committedMay 29, 2025·
Option --overlay-size
1 parent 90f75e2 commit 451544b

File tree

10 files changed

+197
-17
lines changed

10 files changed

+197
-17
lines changed
 

‎g3doc/user_guide/filesystem.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ Self-backed rootfs overlay (`--overlay2=root:self`) is enabled by default in
5858
runsc for performance. If you need to propagate rootfs changes to the host
5959
filesystem, then disable it with `--overlay2=none`.
6060

61+
Overlay has `size=` option which is passed as `size=` tmpfs mount option.
62+
For example, `--overlay2=root:memory,size=2g`.
63+
6164
## Directfs
6265

6366
Directfs is a feature that allows the sandbox process to directly access the

‎runsc/boot/gofer_conf.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,19 +124,24 @@ func (l *GoferMountConfLowerType) Set(v string) error {
124124

125125
// GoferMountConf describes how a gofer mount is configured in the sandbox.
126126
type GoferMountConf struct {
127-
Upper GoferMountConfUpperType `json:"upper"`
128127
Lower GoferMountConfLowerType `json:"lower"`
128+
Upper GoferMountConfUpperType `json:"upper"`
129+
Size string `json:"size,omitempty"`
129130
}
130131

131132
// String returns a human-readable string representing the gofer mount config.
132133
func (g GoferMountConf) String() string {
133-
return fmt.Sprintf("%s:%s", g.Lower, g.Upper)
134+
res := fmt.Sprintf("%s:%s", g.Lower, g.Upper)
135+
if g.Size != "" {
136+
res += ":size=" + g.Size
137+
}
138+
return res
134139
}
135140

136141
// Set sets the value. Set(String()) should be idempotent.
137142
func (g *GoferMountConf) Set(v string) error {
138143
parts := strings.Split(v, ":")
139-
if len(parts) != 2 {
144+
if len(parts) < 2 || len(parts) > 3 {
140145
return fmt.Errorf("invalid gofer mount config format: %q", v)
141146
}
142147
if err := g.Lower.Set(parts[0]); err != nil {
@@ -145,8 +150,17 @@ func (g *GoferMountConf) Set(v string) error {
145150
if err := g.Upper.Set(parts[1]); err != nil {
146151
return err
147152
}
153+
g.Size = ""
154+
if len(parts) >= 3 {
155+
sizeArg := parts[2]
156+
size, cut := strings.CutPrefix(sizeArg, "size=")
157+
if !cut {
158+
return fmt.Errorf("invalid gofer mount config format: %q", v)
159+
}
160+
g.Size = size
161+
}
148162
if !g.valid() {
149-
return fmt.Errorf("invalid gofer mount config: %+v", g)
163+
return fmt.Errorf("invalid gofer mount config: %q", v)
150164
}
151165
return nil
152166
}

‎runsc/boot/gofer_conf_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,28 @@ func TestGoferConfFlags(t *testing.T) {
175175
}
176176
}
177177
}
178+
179+
func TestGoferMountConfSetGet(t *testing.T) {
180+
t.Run("Without size", func(t *testing.T) {
181+
conf := GoferMountConf{}
182+
err := conf.Set("lisafs:anon")
183+
if err != nil {
184+
t.Fatalf("Expect success: %v", err)
185+
}
186+
s := conf.String()
187+
if s != "lisafs:anon" {
188+
t.Fatalf("Expected lisafs:anon, got %s", s)
189+
}
190+
})
191+
t.Run("With size", func(t *testing.T) {
192+
conf := GoferMountConf{}
193+
err := conf.Set("lisafs:anon:size=1719")
194+
if err != nil {
195+
t.Fatalf("Expect success: %v", err)
196+
}
197+
s := conf.String()
198+
if s != "lisafs:anon:size=1719" {
199+
t.Fatalf("Expected lisafs:anon:size=1719, got %s", s)
200+
}
201+
})
202+
}

‎runsc/boot/mount_hints.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ func (p *PodMountHints) FindMount(mountSrc string) *MountHint {
238238
type RootfsHint struct {
239239
Mount specs.Mount
240240
Overlay config.OverlayMedium
241+
// Size of overlay tmpfs. Passed as `size={Size}` to tmpfs mount.
242+
// Use default if unspecified.
243+
Size string
241244
}
242245

243246
func (r *RootfsHint) setSource(val string) error {
@@ -258,6 +261,30 @@ func (r *RootfsHint) setType(val string) error {
258261
return nil
259262
}
260263

264+
func (r *RootfsHint) setOption(key, val string) error {
265+
switch key {
266+
case "size":
267+
r.Size = val
268+
default:
269+
return fmt.Errorf("invalid rootfs option: %s=%s", key, val)
270+
}
271+
return nil
272+
}
273+
274+
func (r *RootfsHint) setOptions(val string) error {
275+
for _, option := range strings.Split(val, ",") {
276+
parts := strings.SplitN(option, "=", 2)
277+
if len(parts) != 2 {
278+
return fmt.Errorf("rootfs options must be key=value: %s", option)
279+
}
280+
key, val := parts[0], parts[1]
281+
if err := r.setOption(key, val); err != nil {
282+
return err
283+
}
284+
}
285+
return nil
286+
}
287+
261288
func (r *RootfsHint) setField(key, val string) error {
262289
switch key {
263290
case "source":
@@ -266,6 +293,8 @@ func (r *RootfsHint) setField(key, val string) error {
266293
return r.setType(val)
267294
case "overlay":
268295
return r.Overlay.Set(val)
296+
case "options":
297+
return r.setOptions(val)
269298
default:
270299
return fmt.Errorf("invalid rootfs annotation: %s=%s", key, val)
271300
}

‎runsc/boot/mount_hints_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ func TestRootfsHintHappy(t *testing.T) {
258258
RootfsPrefix + "source": imagePath,
259259
RootfsPrefix + "type": erofs.Name,
260260
RootfsPrefix + "overlay": config.MemoryOverlay.String(),
261+
RootfsPrefix + "options": "size=100m",
261262
},
262263
}
263264
hint, err := NewRootfsHint(spec)
@@ -275,6 +276,9 @@ func TestRootfsHintHappy(t *testing.T) {
275276
if hint.Overlay != config.MemoryOverlay {
276277
t.Errorf("rootfs overlay, want: %q, got: %q", config.MemoryOverlay, hint.Overlay)
277278
}
279+
if hint.Size != "100m" {
280+
t.Errorf("rootfs size, want: 100m, got: %q", hint.Size)
281+
}
278282
}
279283

280284
// TestRootfsHintErrors tests that proper errors will be returned when parsing

‎runsc/boot/vfs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,12 @@ func (c *containerMounter) configureOverlay(ctx context.Context, conf *config.Co
585585
// filesystem specific options.
586586
upperOpts := *lowerOpts
587587
upperOpts.GetFilesystemOptions = vfs.GetFilesystemOptions{InternalMount: true}
588+
if mountConf.Size != "" {
589+
if upperOpts.GetFilesystemOptions.Data != "" {
590+
upperOpts.GetFilesystemOptions.Data += ","
591+
}
592+
upperOpts.GetFilesystemOptions.Data += "size=" + mountConf.Size
593+
}
588594

589595
overlayOpts := *lowerOpts
590596
overlayOpts.GetFilesystemOptions = vfs.GetFilesystemOptions{InternalMount: true}

‎runsc/config/config.go

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -906,24 +906,42 @@ type Overlay2 struct {
906906
rootMount bool
907907
subMounts bool
908908
medium OverlayMedium
909+
// Size of overlay upper layer.
910+
// Passed as is to tmpfs mount, as `size={size}`.
911+
// Empty means use default.
912+
// Size is applied to each overlay independently and not shared by overlays.
913+
size string
909914
}
910915

911916
func defaultOverlay2() *Overlay2 {
912917
// Rootfs overlay is enabled by default and backed by a file in rootfs itself.
913918
return &Overlay2{rootMount: true, subMounts: false, medium: SelfOverlay}
914919
}
915920

921+
func setOverlay2Err(v string) error {
922+
return fmt.Errorf("expected format is --overlay2={mount}:{medium}[,size={size}], got %q", v)
923+
}
924+
925+
// `--overlay2=...` param `size=`.
926+
const overlay2SizeEq = "size="
927+
916928
// Set implements flag.Value. Set(String()) should be idempotent.
917929
func (o *Overlay2) Set(v string) error {
918930
if v == "none" {
919931
o.rootMount = false
920932
o.subMounts = false
921933
o.medium = NoOverlay
934+
o.size = ""
922935
return nil
923936
}
924-
vs := strings.Split(v, ":")
937+
parts := strings.Split(v, ",")
938+
if len(parts) < 1 {
939+
return setOverlay2Err(v)
940+
}
941+
942+
vs := strings.Split(parts[0], ":")
925943
if len(vs) != 2 {
926-
return fmt.Errorf("expected format is --overlay2={mount}:{medium}, got %q", v)
944+
return setOverlay2Err(v)
927945
}
928946

929947
switch mount := vs[0]; mount {
@@ -936,7 +954,25 @@ func (o *Overlay2) Set(v string) error {
936954
return fmt.Errorf("unexpected mount specifier for --overlay2: %q", mount)
937955
}
938956

939-
return o.medium.Set(vs[1])
957+
err := o.medium.Set(vs[1])
958+
if err != nil {
959+
return err
960+
}
961+
962+
if len(parts) == 1 {
963+
o.size = ""
964+
} else if len(parts) == 2 {
965+
sizeArg := parts[1]
966+
size, cut := strings.CutPrefix(sizeArg, overlay2SizeEq)
967+
if !cut {
968+
return setOverlay2Err(v)
969+
}
970+
o.size = size
971+
} else {
972+
return setOverlay2Err(v)
973+
}
974+
975+
return nil
940976
}
941977

942978
// Get implements flag.Value.
@@ -958,7 +994,13 @@ func (o Overlay2) String() string {
958994
default:
959995
panic("invalid state of subMounts = true and rootMount = false")
960996
}
961-
return res + ":" + o.medium.String()
997+
998+
var sizeSuffix string
999+
if o.size != "" {
1000+
sizeSuffix = fmt.Sprintf(",%s%s", overlay2SizeEq, o.size)
1001+
}
1002+
1003+
return res + ":" + o.medium.String() + sizeSuffix
9621004
}
9631005

9641006
// Enabled returns true if the overlay option is enabled for any mounts.
@@ -974,6 +1016,13 @@ func (o *Overlay2) RootOverlayMedium() OverlayMedium {
9741016
return o.medium
9751017
}
9761018

1019+
func (o *Overlay2) RootOverlaySize() string {
1020+
if !o.rootMount {
1021+
return ""
1022+
}
1023+
return o.size
1024+
}
1025+
9771026
// SubMountOverlayMedium returns the overlay medium config of submounts.
9781027
func (o *Overlay2) SubMountOverlayMedium() OverlayMedium {
9791028
if !o.subMounts {
@@ -982,6 +1031,13 @@ func (o *Overlay2) SubMountOverlayMedium() OverlayMedium {
9821031
return o.medium
9831032
}
9841033

1034+
func (o *Overlay2) SubMountOverlaySize() string {
1035+
if !o.subMounts {
1036+
return ""
1037+
}
1038+
return o.size
1039+
}
1040+
9851041
// Medium returns the overlay medium config.
9861042
func (o Overlay2) Medium() OverlayMedium {
9871043
return o.medium

‎runsc/config/config_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ func TestInvalidFlags(t *testing.T) {
231231
value: "root:dir=tmp",
232232
error: "overlay host file directory should be an absolute path, got \"tmp\"",
233233
},
234+
{
235+
name: "overlay2",
236+
value: "root:memory,sz=sdg",
237+
error: "expected format is --overlay2",
238+
},
234239
} {
235240
t.Run(tc.name, func(t *testing.T) {
236241
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
@@ -781,3 +786,32 @@ root = "%s"
781786
}
782787

783788
}
789+
790+
func TestParseSerializeOverlay2(t *testing.T) {
791+
t.Run("Without size", func(t *testing.T) {
792+
o := Overlay2{}
793+
err := o.Set("all:memory")
794+
if err != nil {
795+
t.Fatalf("Set failed: %v", err)
796+
}
797+
if o.RootOverlaySize() != "" || o.RootOverlayMedium() != "" {
798+
t.Fatalf("Size mismatch, expecting empty, got %q, %q", o.RootOverlaySize(), o.SubMountOverlaySize())
799+
}
800+
if o.String() != "all:memory" {
801+
t.Fatalf("String mismatch, expecting all:memory, got %q", o.String())
802+
}
803+
})
804+
t.Run("With size", func(t *testing.T) {
805+
o := Overlay2{}
806+
err := o.Set("root:memory,size=1g")
807+
if err != nil {
808+
t.Fatalf("Set failed: %v", err)
809+
}
810+
if o.RootOverlaySize() != "1g" || o.SubMountOverlaySize() != "" {
811+
t.Fatalf("Size mismatch, expecting 1g, empty, got %q, %q", o.RootOverlaySize(), o.SubMountOverlaySize())
812+
}
813+
if o.String() != "all:memory,size=1g" {
814+
t.Fatalf("String mismatch, expecting all:memory, got %q", o.String())
815+
}
816+
})
817+
}

‎runsc/config/flags.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,12 @@ func RegisterFlags(flagSet *flag.FlagSet) {
119119
flagSet.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.")
120120
flagSet.Var(fileAccessTypePtr(FileAccessShared), "file-access-mounts", "specifies which filesystem validation to use for volumes other than the root mount: shared (default), exclusive.")
121121
flagSet.Bool("overlay", false, "DEPRECATED: use --overlay2=all:memory to achieve the same effect")
122-
flagSet.Var(defaultOverlay2(), flagOverlay2, "wrap mounts with overlayfs. Format is {mount}:{medium}, where 'mount' can be 'root' or 'all' and medium can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created. 'none' will turn overlay mode off.")
122+
flagSet.Var(defaultOverlay2(), flagOverlay2, "wrap mounts with overlayfs. Format is\n"+
123+
"* 'none' to turn overlay mode off\n"+
124+
"* {mount}:{medium}[,size={size}], where\n"+
125+
" 'mount' can be 'root' or 'all'\n"+
126+
" 'medium' can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created\n"+
127+
" 'size' optional parameter overrides default overlay upper layer size\n")
123128
flagSet.Bool("fsgofer-host-uds", false, "DEPRECATED: use host-uds=all")
124129
flagSet.Var(hostUDSPtr(HostUDSNone), flagHostUDS, "controls permission to access host Unix-domain sockets. Values: none|open|create|all, default: none")
125130
flagSet.Var(hostFifoPtr(HostFifoNone), "host-fifo", "controls permission to access host FIFOs (or named pipes). Values: none|open, default: none")
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.