aboutsummaryrefslogtreecommitdiffhomepage
path: root/container
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-07-28 01:04:29 +0900
committerOphestra <cat@gensokyo.uk>2025-07-28 01:04:29 +0900
commit65fe09caf9bd8cbed9177022c765423885aed5be (patch)
tree68600dafad417f2bc9bd2f246c51fdea4aad3fb3 /container
parenta1e5f020f48a65138c900961f856cf6b04833770 (diff)
container: check cancel signal delivery
This change also makes some parts of the test more robust. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'container')
-rw-r--r--container/container.go4
-rw-r--r--container/container_test.go84
-rw-r--r--container/init.go4
-rw-r--r--container/init_test.go28
4 files changed, 90 insertions, 30 deletions
diff --git a/container/container.go b/container/container.go
index 0b6891b9..941b6631 100644
--- a/container/container.go
+++ b/container/container.go
@@ -71,6 +71,7 @@ type (
Hostname string
// Sequential container setup ops.
*Ops
+
// Seccomp system call filter rules.
SeccompRules []seccomp.NativeRule
// Extra seccomp flags.
@@ -79,6 +80,7 @@ type (
SeccompPresets seccomp.FilterPreset
// Do not load seccomp program.
SeccompDisable bool
+
// Permission bits of newly created parent directories.
// The zero value is interpreted as 0755.
ParentPerm os.FileMode
@@ -121,7 +123,7 @@ func (p *Container) Start() error {
}
p.cmd = exec.CommandContext(ctx, MustExecutable())
- p.cmd.Args = []string{"init"}
+ p.cmd.Args = []string{initName}
p.cmd.Stdin, p.cmd.Stdout, p.cmd.Stderr = p.Stdin, p.Stdout, p.Stderr
p.cmd.WaitDelay = p.WaitDelay
if p.Cancel != nil {
diff --git a/container/container_test.go b/container/container_test.go
index 81b7049d..8e59e4eb 100644
--- a/container/container_test.go
+++ b/container/container_test.go
@@ -12,7 +12,6 @@ import (
"strings"
"syscall"
"testing"
- "time"
"hakurei.app/command"
"hakurei.app/container"
@@ -21,7 +20,6 @@ import (
"hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
- "hakurei.app/ldd"
)
const (
@@ -92,18 +90,54 @@ func TestContainer(t *testing.T) {
t.Cleanup(func() { container.SetOutput(oldOutput) })
}
+ t.Run("cancel", func(t *testing.T) {
+ ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
+
+ c := helperNewContainer(ctx, "block")
+ c.Stdout, c.Stderr = os.Stdout, os.Stderr
+ c.WaitDelay = helperDefaultTimeout
+
+ ready := make(chan struct{})
+ if r, w, err := os.Pipe(); err != nil {
+ t.Fatalf("cannot pipe: %v", err)
+ } else {
+ c.ExtraFiles = append(c.ExtraFiles, w)
+ go func() {
+ defer close(ready)
+ if _, err = r.Read(make([]byte, 1)); err != nil {
+ panic(err.Error())
+ }
+ }()
+ }
+
+ if err := c.Start(); err != nil {
+ hlog.PrintBaseError(err, "start:")
+ t.Fatalf("cannot start container: %v", err)
+ } else if err = c.Serve(); err != nil {
+ hlog.PrintBaseError(err, "serve:")
+ t.Errorf("cannot serve setup params: %v", err)
+ }
+ <-ready
+ cancel()
+ wantErr := context.Canceled
+ if err := c.Wait(); !errors.Is(err, wantErr) {
+ hlog.PrintBaseError(err, "wait:")
+ t.Fatalf("Wait: error = %v, want %v", err, wantErr)
+ }
+ })
+
for i, tc := range containerTestCases {
t.Run(tc.name, func(t *testing.T) {
- ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
+ ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
defer cancel()
- hostname := hostnameFromTestCase(tc.name)
- c := container.New(ctx, os.Args[0], "container", strconv.Itoa(i))
- prepareHelper(c)
+ var libPaths []string
+ c := helperNewContainerLibPaths(ctx, &libPaths, "container", strconv.Itoa(i))
c.Uid = tc.uid
c.Gid = tc.gid
- c.Hostname = hostname
+ c.Hostname = hostnameFromTestCase(tc.name)
c.Stdout, c.Stderr = os.Stdout, os.Stderr
+ c.WaitDelay = helperDefaultTimeout
*c.Ops = append(*c.Ops, *tc.ops...)
c.SeccompRules = tc.rules
c.SeccompFlags = tc.flags | seccomp.AllowMultiarch
@@ -114,38 +148,27 @@ func TestContainer(t *testing.T) {
c.
Tmpfs("/tmp", 0, 0755).
- Bind(os.Args[0], os.Args[0], 0).
- Place("/etc/hostname", []byte(hostname))
- // in case test has cgo enabled
- var libPaths []string
- if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil {
- log.Fatalf("ldd: %v", err)
- } else {
- libPaths = ldd.Path(entries)
- }
- for _, name := range libPaths {
- c.Bind(name, name, 0)
- }
+ Place("/etc/hostname", []byte(c.Hostname))
// needs /proc to check mountinfo
c.Proc("/proc")
// mountinfo cannot be resolved directly by helper due to libPaths nondeterminism
mnt := make([]*vfs.MountInfoEntry, 0, 3+len(libPaths))
- mnt = append(mnt, ent("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore))
- mnt = append(mnt, tc.mnt...)
mnt = append(mnt,
- // Tmpfs("/tmp", 0, 0755)
- ent("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
- // Bind(os.Args[0], os.Args[0], 0)
- ent(ignore, os.Args[0], "ro,nosuid,nodev,relatime", ignore, ignore, ignore),
- // Place("/etc/hostname", []byte(hostname))
- ent(ignore, "/etc/hostname", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
+ ent("/sysroot", "/", "rw,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
+ // Bind(os.Args[0], helperInnerPath, 0)
+ ent(ignore, helperInnerPath, "ro,nosuid,nodev,relatime", ignore, ignore, ignore),
)
for _, name := range libPaths {
// Bind(name, name, 0)
mnt = append(mnt, ent(ignore, name, "ro,nosuid,nodev,relatime", ignore, ignore, ignore))
}
+ mnt = append(mnt, tc.mnt...)
mnt = append(mnt,
+ // Tmpfs("/tmp", 0, 0755)
+ ent("/", "/tmp", "rw,nosuid,nodev,relatime", "tmpfs", "tmpfs", ignore),
+ // Place("/etc/hostname", []byte(hostname))
+ ent(ignore, "/etc/hostname", "ro,nosuid,nodev,relatime", "tmpfs", "rootfs", ignore),
// Proc("/proc")
ent("/", "/proc", "rw,nosuid,nodev,noexec,relatime", "proc", "proc", "rw"),
// Place(pathWantMnt, want.Bytes())
@@ -206,6 +229,13 @@ func TestContainerString(t *testing.T) {
func init() {
helperCommands = append(helperCommands, func(c command.Command) {
+ c.Command("block", command.UsageInternal, func(args []string) error {
+ if _, err := os.NewFile(3, "sync").Write([]byte{0}); err != nil {
+ return fmt.Errorf("write to sync pipe: %v", err)
+ }
+ select {}
+ })
+
c.Command("container", command.UsageInternal, func(args []string) error {
if len(args) != 1 {
return syscall.EINVAL
diff --git a/container/init.go b/container/init.go
index 8853bd8b..757af58c 100644
--- a/container/init.go
+++ b/container/init.go
@@ -367,9 +367,11 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
}
}
+const initName = "init"
+
// TryArgv0 calls [Init] if the last element of argv0 is "init".
func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) {
- if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" {
+ if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {
msg = v
Init(prepare, setVerbose)
msg.BeforeExit()
diff --git a/container/init_test.go b/container/init_test.go
index ad35b4cf..6e99cc09 100644
--- a/container/init_test.go
+++ b/container/init_test.go
@@ -1,18 +1,24 @@
package container_test
import (
+ "context"
"log"
"os"
"testing"
+ "time"
"hakurei.app/command"
"hakurei.app/container"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
+ "hakurei.app/ldd"
)
const (
envDoCheck = "HAKUREI_TEST_DO_CHECK"
+
+ helperDefaultTimeout = 5 * time.Second
+ helperInnerPath = "/usr/bin/helper"
)
var helperCommands []func(c command.Command)
@@ -40,4 +46,24 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
-func prepareHelper(c *container.Container) { c.Env = append(c.Env, envDoCheck+"=1") }
+func helperNewContainerLibPaths(ctx context.Context, libPaths *[]string, args ...string) (c *container.Container) {
+ c = container.New(ctx, helperInnerPath, args...)
+ c.Env = append(c.Env, envDoCheck+"=1")
+ c.Bind(os.Args[0], helperInnerPath, 0)
+
+ // in case test has cgo enabled
+ if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil {
+ log.Fatalf("ldd: %v", err)
+ } else {
+ *libPaths = ldd.Path(entries)
+ }
+ for _, name := range *libPaths {
+ c.Bind(name, name, 0)
+ }
+
+ return
+}
+
+func helperNewContainer(ctx context.Context, args ...string) (c *container.Container) {
+ return helperNewContainerLibPaths(ctx, new([]string), args...)
+}