container/check: relocate overlay escape
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m12s
Test / Hakurei (push) Successful in 3m8s
Test / Hpkg (push) Successful in 4m9s
Test / Sandbox (race detector) (push) Successful in 4m31s
Test / Hakurei (race detector) (push) Successful in 5m25s
Test / Flake checks (push) Successful in 1m40s

This is used in hst to format strings.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-10-07 23:56:19 +09:00
parent 584ce3da68
commit 1f0226f7e0
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
7 changed files with 71 additions and 60 deletions

View File

@ -0,0 +1,29 @@
package check
import "strings"
const (
// SpecialOverlayEscape is the escape string for overlay mount options.
SpecialOverlayEscape = `\`
// SpecialOverlayOption is the separator string between overlay mount options.
SpecialOverlayOption = ","
// SpecialOverlayPath is the separator string between overlay paths.
SpecialOverlayPath = ":"
)
// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call.
func EscapeOverlayDataSegment(s string) string {
if s == "" {
return ""
}
if f := strings.SplitN(s, "\x00", 2); len(f) > 0 {
s = f[0]
}
return strings.NewReplacer(
SpecialOverlayEscape, SpecialOverlayEscape+SpecialOverlayEscape,
SpecialOverlayOption, SpecialOverlayEscape+SpecialOverlayOption,
SpecialOverlayPath, SpecialOverlayEscape+SpecialOverlayPath,
).Replace(s)
}

View File

@ -0,0 +1,27 @@
package check_test
import (
"testing"
"hakurei.app/container/check"
)
func TestEscapeOverlayDataSegment(t *testing.T) {
testCases := []struct {
name string
s string
want string
}{
{"zero", "", ""},
{"multi", `\\\:,:,\\\`, `\\\\\\\:\,\:\,\\\\\\`},
{"bwrap", `/path :,\`, `/path \:\,\\`},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := check.EscapeOverlayDataSegment(tc.s); got != tc.want {
t.Errorf("escapeOverlayDataSegment: %s, want %s", got, tc.want)
}
})
}
}

View File

@ -139,7 +139,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if v, err := k.evalSymlinks(o.Upper.String()); err != nil { if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
return err return err
} else { } else {
o.upper = EscapeOverlayDataSegment(toHost(v)) o.upper = check.EscapeOverlayDataSegment(toHost(v))
} }
} }
@ -147,7 +147,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if v, err := k.evalSymlinks(o.Work.String()); err != nil { if v, err := k.evalSymlinks(o.Work.String()); err != nil {
return err return err
} else { } else {
o.work = EscapeOverlayDataSegment(toHost(v)) o.work = check.EscapeOverlayDataSegment(toHost(v))
} }
} }
} }
@ -157,7 +157,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if v, err := k.evalSymlinks(a.String()); err != nil { if v, err := k.evalSymlinks(a.String()); err != nil {
return err return err
} else { } else {
o.lower[i] = EscapeOverlayDataSegment(toHost(v)) o.lower[i] = check.EscapeOverlayDataSegment(toHost(v))
} }
} }
return nil return nil
@ -199,10 +199,10 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
OptionOverlayWorkdir+"="+o.work) OptionOverlayWorkdir+"="+o.work)
} }
options = append(options, options = append(options,
OptionOverlayLowerdir+"="+strings.Join(o.lower, SpecialOverlayPath), OptionOverlayLowerdir+"="+strings.Join(o.lower, check.SpecialOverlayPath),
OptionOverlayUserxattr) OptionOverlayUserxattr)
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, SpecialOverlayOption)) return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption))
} }
func (o *MountOverlayOp) Is(op Op) bool { func (o *MountOverlayOp) Is(op Op) bool {

View File

@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"strings"
. "syscall" . "syscall"
"hakurei.app/container/vfs" "hakurei.app/container/vfs"
@ -85,13 +84,6 @@ const (
// OptionOverlayUserxattr represents the userxattr option of the overlay pseudo-filesystem. // OptionOverlayUserxattr represents the userxattr option of the overlay pseudo-filesystem.
// Use the "user.overlay." xattr namespace instead of "trusted.overlay.". // Use the "user.overlay." xattr namespace instead of "trusted.overlay.".
OptionOverlayUserxattr = "userxattr" OptionOverlayUserxattr = "userxattr"
// SpecialOverlayEscape is the escape string for overlay mount options.
SpecialOverlayEscape = `\`
// SpecialOverlayOption is the separator string between overlay mount options.
SpecialOverlayOption = ","
// SpecialOverlayPath is the separator string between overlay paths.
SpecialOverlayPath = ":"
) )
// bindMount mounts source on target and recursively applies flags if MS_REC is set. // bindMount mounts source on target and recursively applies flags if MS_REC is set.
@ -206,20 +198,3 @@ func parentPerm(perm os.FileMode) os.FileMode {
} }
return os.FileMode(pperm) return os.FileMode(pperm)
} }
// EscapeOverlayDataSegment escapes a string for formatting into the data argument of an overlay mount call.
func EscapeOverlayDataSegment(s string) string {
if s == zeroString {
return zeroString
}
if f := strings.SplitN(s, "\x00", 2); len(f) > 0 {
s = f[0]
}
return strings.NewReplacer(
SpecialOverlayEscape, SpecialOverlayEscape+SpecialOverlayEscape,
SpecialOverlayOption, SpecialOverlayEscape+SpecialOverlayOption,
SpecialOverlayPath, SpecialOverlayEscape+SpecialOverlayPath,
).Replace(s)
}

View File

@ -281,23 +281,3 @@ func TestParentPerm(t *testing.T) {
}) })
} }
} }
func TestEscapeOverlayDataSegment(t *testing.T) {
testCases := []struct {
name string
s string
want string
}{
{"zero", zeroString, zeroString},
{"multi", `\\\:,:,\\\`, `\\\\\\\:\,\:\,\\\\\\`},
{"bwrap", `/path :,\`, `/path \:\,\\`},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := EscapeOverlayDataSegment(tc.s); got != tc.want {
t.Errorf("escapeOverlayDataSegment: %s, want %s", got, tc.want)
}
})
}
}

View File

@ -10,6 +10,7 @@ import (
"testing" "testing"
"unsafe" "unsafe"
"hakurei.app/container/check"
"hakurei.app/container/vfs" "hakurei.app/container/vfs"
) )
@ -49,8 +50,8 @@ func TestToHost(t *testing.T) {
} }
} }
// InternalToHostOvlEscape exports toHost passed to EscapeOverlayDataSegment. // InternalToHostOvlEscape exports toHost passed to [check.EscapeOverlayDataSegment].
func InternalToHostOvlEscape(s string) string { return EscapeOverlayDataSegment(toHost(s)) } func InternalToHostOvlEscape(s string) string { return check.EscapeOverlayDataSegment(toHost(s)) }
func TestCreateFile(t *testing.T) { func TestCreateFile(t *testing.T) {
t.Run("nonexistent", func(t *testing.T) { t.Run("nonexistent", func(t *testing.T) {

View File

@ -4,7 +4,6 @@ import (
"encoding/gob" "encoding/gob"
"strings" "strings"
"hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
) )
@ -82,18 +81,18 @@ func (o *FSOverlay) String() string {
lower := make([]string, len(o.Lower)) lower := make([]string, len(o.Lower))
for i, a := range o.Lower { for i, a := range o.Lower {
lower[i] = container.EscapeOverlayDataSegment(a.String()) lower[i] = check.EscapeOverlayDataSegment(a.String())
} }
if o.Upper != nil && o.Work != nil { if o.Upper != nil && o.Work != nil {
return "w*" + strings.Join(append([]string{ return "w*" + strings.Join(append([]string{
container.EscapeOverlayDataSegment(o.Target.String()), check.EscapeOverlayDataSegment(o.Target.String()),
container.EscapeOverlayDataSegment(o.Upper.String()), check.EscapeOverlayDataSegment(o.Upper.String()),
container.EscapeOverlayDataSegment(o.Work.String())}, check.EscapeOverlayDataSegment(o.Work.String())},
lower...), container.SpecialOverlayPath) lower...), check.SpecialOverlayPath)
} else { } else {
return "*" + strings.Join(append([]string{ return "*" + strings.Join(append([]string{
container.EscapeOverlayDataSegment(o.Target.String())}, check.EscapeOverlayDataSegment(o.Target.String())},
lower...), container.SpecialOverlayPath) lower...), check.SpecialOverlayPath)
} }
} }