summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFabricio Voznika <fvoznika@google.com>2018-10-11 11:55:45 -0700
committerShentubot <shentubot@google.com>2018-10-11 11:56:54 -0700
commitf413e4b11794cd71cc3b2b64c8f6861f5394a3f1 (patch)
treeff9af373751ee40db971ae6dd23a6c752e8d43fe
parent0bfa03d61c7791aad03da5ac021bc60e4578858e (diff)
Add bare bones unsupported syscall logging
This change introduces a new flags to create/run called --user-log. Logs to this files are visible to users and are meant to help debugging problems with their images and containers. For now only unsupported syscalls are sent to this log, and only minimum support was added. We can build more infrastructure around it as needed. PiperOrigin-RevId: 216735977 Change-Id: I54427ca194604991c407d49943ab3680470de2d0
-rw-r--r--runsc/boot/BUILD6
-rw-r--r--runsc/boot/compat.go76
-rw-r--r--runsc/boot/loader.go6
-rw-r--r--runsc/cmd/boot.go5
-rw-r--r--runsc/cmd/capability_test.go2
-rw-r--r--runsc/cmd/checkpoint.go2
-rw-r--r--runsc/cmd/create.go10
-rw-r--r--runsc/cmd/run.go2
-rw-r--r--runsc/container/container.go8
-rw-r--r--runsc/container/container_test.go93
-rw-r--r--runsc/container/multi_container_test.go2
-rw-r--r--runsc/container/test_app.go36
-rw-r--r--runsc/sandbox/sandbox.go18
13 files changed, 223 insertions, 43 deletions
diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD
index c1e035d3b..f8f848ebf 100644
--- a/runsc/boot/BUILD
+++ b/runsc/boot/BUILD
@@ -5,6 +5,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "boot",
srcs = [
+ "compat.go",
"config.go",
"controller.go",
"debug.go",
@@ -21,12 +22,15 @@ go_library(
"//runsc:__subpackages__",
],
deps = [
+ "//pkg/abi",
"//pkg/abi/linux",
"//pkg/control/server",
"//pkg/cpuid",
+ "//pkg/eventchannel",
"//pkg/log",
"//pkg/rand",
"//pkg/sentry/arch",
+ "//pkg/sentry/arch:registers_go_proto",
"//pkg/sentry/context",
"//pkg/sentry/control",
"//pkg/sentry/fs",
@@ -55,6 +59,7 @@ go_library(
"//pkg/sentry/socket/unix",
"//pkg/sentry/state",
"//pkg/sentry/strace",
+ "//pkg/sentry/syscalls:unimplemented_syscall_go_proto",
"//pkg/sentry/syscalls/linux",
"//pkg/sentry/time",
"//pkg/sentry/usage",
@@ -74,6 +79,7 @@ go_library(
"//pkg/urpc",
"//runsc/boot/filter",
"//runsc/specutils",
+ "@com_github_golang_protobuf//proto:go_default_library",
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
],
)
diff --git a/runsc/boot/compat.go b/runsc/boot/compat.go
new file mode 100644
index 000000000..3250cdcdc
--- /dev/null
+++ b/runsc/boot/compat.go
@@ -0,0 +1,76 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package boot
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/golang/protobuf/proto"
+ "gvisor.googlesource.com/gvisor/pkg/abi"
+ "gvisor.googlesource.com/gvisor/pkg/eventchannel"
+ "gvisor.googlesource.com/gvisor/pkg/log"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/arch"
+ rpb "gvisor.googlesource.com/gvisor/pkg/sentry/arch/registers_go_proto"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/strace"
+ spb "gvisor.googlesource.com/gvisor/pkg/sentry/syscalls/unimplemented_syscall_go_proto"
+)
+
+func initCompatLogs(fd int) error {
+ ce, err := newCompatEmitter(fd)
+ if err != nil {
+ return err
+ }
+ eventchannel.AddEmitter(ce)
+ return nil
+}
+
+type compatEmitter struct {
+ sink *log.BasicLogger
+ nameMap strace.SyscallMap
+}
+
+func newCompatEmitter(logFD int) (*compatEmitter, error) {
+ // Always logs to default logger.
+ nameMap, ok := strace.Lookup(abi.Linux, arch.AMD64)
+ if !ok {
+ return nil, fmt.Errorf("amd64 Linux syscall table not found")
+ }
+ c := &compatEmitter{sink: log.Log(), nameMap: nameMap}
+
+ if logFD > 0 {
+ f := os.NewFile(uintptr(logFD), "user log file")
+ target := log.MultiEmitter{c.sink, log.GoogleEmitter{&log.Writer{Next: f}}}
+ c.sink = &log.BasicLogger{Level: log.Info, Emitter: target}
+ }
+ return c, nil
+}
+
+// Emit implements eventchannel.Emitter.
+func (c *compatEmitter) Emit(msg proto.Message) (hangup bool, err error) {
+ // Only interested in UnimplementedSyscall, skip the rest.
+ if us, ok := msg.(*spb.UnimplementedSyscall); ok {
+ regs := us.Registers.GetArch().(*rpb.Registers_Amd64).Amd64
+ sysnr := regs.OrigRax
+ c.sink.Infof("Unsupported syscall: %s, regs: %+v", c.nameMap.Name(uintptr(sysnr)), regs)
+ }
+ return false, nil
+}
+
+// Close implements eventchannel.Emitter.
+func (c *compatEmitter) Close() error {
+ c.sink = nil
+ return nil
+}
diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go
index 60b278295..0a3f67774 100644
--- a/runsc/boot/loader.go
+++ b/runsc/boot/loader.go
@@ -163,6 +163,8 @@ type Args struct {
// TotalMem is the initial amount of total memory to report back to the
// container.
TotalMem uint64
+ // UserLogFD is the file descriptor to write user logs to.
+ UserLogFD int
}
// New initializes a new kernel loader configured by spec.
@@ -313,6 +315,10 @@ func New(args Args) (*Loader, error) {
return nil, fmt.Errorf("failed to create root process: %v", err)
}
+ if err := initCompatLogs(args.UserLogFD); err != nil {
+ return nil, fmt.Errorf("init compat logs: %v", err)
+ }
+
l := &Loader{
k: k,
ctrl: ctrl,
diff --git a/runsc/cmd/boot.go b/runsc/cmd/boot.go
index d26e92bcd..023b63dc0 100644
--- a/runsc/cmd/boot.go
+++ b/runsc/cmd/boot.go
@@ -66,6 +66,9 @@ type Boot struct {
// totalMem sets the initial amount of total memory to report back to the
// container.
totalMem uint64
+
+ // userLogFD is the file descriptor to write user logs to.
+ userLogFD int
}
// Name implements subcommands.Command.Name.
@@ -95,6 +98,7 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
f.BoolVar(&b.applyCaps, "apply-caps", false, "if true, apply capabilities defined in the spec to the process")
f.IntVar(&b.cpuNum, "cpu-num", 0, "number of CPUs to create inside the sandbox")
f.Uint64Var(&b.totalMem, "total-memory", 0, "sets the initial amount of total memory to report back to the container")
+ f.IntVar(&b.userLogFD, "user-log-fd", 0, "file descriptor to write user logs to. 0 means no logging.")
}
// Execute implements subcommands.Command.Execute. It starts a sandbox in a
@@ -163,6 +167,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
Console: b.console,
NumCPU: b.cpuNum,
TotalMem: b.totalMem,
+ UserLogFD: b.userLogFD,
}
l, err := boot.New(bootArgs)
if err != nil {
diff --git a/runsc/cmd/capability_test.go b/runsc/cmd/capability_test.go
index be9ef2e7b..3329b308d 100644
--- a/runsc/cmd/capability_test.go
+++ b/runsc/cmd/capability_test.go
@@ -97,7 +97,7 @@ func TestCapabilities(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- c, err := container.Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ c, err := container.Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
diff --git a/runsc/cmd/checkpoint.go b/runsc/cmd/checkpoint.go
index d074b8617..023ab2455 100644
--- a/runsc/cmd/checkpoint.go
+++ b/runsc/cmd/checkpoint.go
@@ -133,7 +133,7 @@ func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...interfa
Fatalf("error destroying container: %v", err)
}
- cont, err = container.Create(id, spec, conf, bundleDir, "", "")
+ cont, err = container.Create(id, spec, conf, bundleDir, "", "", "")
if err != nil {
Fatalf("error restoring container: %v", err)
}
diff --git a/runsc/cmd/create.go b/runsc/cmd/create.go
index 38ae03e7a..ecd76ee93 100644
--- a/runsc/cmd/create.go
+++ b/runsc/cmd/create.go
@@ -41,6 +41,13 @@ type Create struct {
// pseudoterminal. This is ignored unless spec.Process.Terminal is
// true.
consoleSocket string
+
+ // userLog is the path to send user-visible logs to. This log is different
+ // from debug logs. The former is meant to be consumed by the users and should
+ // contain only information that is relevant to the person running the
+ // container, e.g. unsuported syscalls, while the later is more verbose and
+ // consumed by developers.
+ userLog string
}
// Name implements subcommands.Command.Name.
@@ -64,6 +71,7 @@ func (c *Create) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.bundleDir, "bundle", "", "path to the root of the bundle directory, defaults to the current directory")
f.StringVar(&c.consoleSocket, "console-socket", "", "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal")
f.StringVar(&c.pidFile, "pid-file", "", "filename that the container pid will be written to")
+ f.StringVar(&c.userLog, "user-log", "", "filename to send user-visible logs to. Empty means no logging.")
}
// Execute implements subcommands.Command.Execute.
@@ -90,7 +98,7 @@ func (c *Create) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}
// Create the container. A new sandbox will be created for the
// container unless the metadata specifies that it should be run in an
// existing container.
- if _, err := container.Create(id, spec, conf, bundleDir, c.consoleSocket, c.pidFile); err != nil {
+ if _, err := container.Create(id, spec, conf, bundleDir, c.consoleSocket, c.pidFile, c.userLog); err != nil {
Fatalf("error creating container: %v", err)
}
return subcommands.ExitSuccess
diff --git a/runsc/cmd/run.go b/runsc/cmd/run.go
index 92aa6bc40..826e6e875 100644
--- a/runsc/cmd/run.go
+++ b/runsc/cmd/run.go
@@ -75,7 +75,7 @@ func (r *Run) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) s
specutils.LogSpec(spec)
conf.SpecFile = filepath.Join(bundleDir, "config.json")
- ws, err := container.Run(id, spec, conf, bundleDir, r.consoleSocket, r.pidFile)
+ ws, err := container.Run(id, spec, conf, bundleDir, r.consoleSocket, r.pidFile, r.userLog)
if err != nil {
Fatalf("error running container: %v", err)
}
diff --git a/runsc/container/container.go b/runsc/container/container.go
index eaa62daf1..10108db5a 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -230,7 +230,7 @@ func List(rootDir string) ([]string, error) {
// Create creates the container in a new Sandbox process, unless the metadata
// indicates that an existing Sandbox should be used. The caller must call
// Destroy() on the container.
-func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, pidFile string) (*Container, error) {
+func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, pidFile, userLog string) (*Container, error) {
log.Debugf("Create container %q in root dir: %s", id, conf.RootDir)
if err := validateID(id); err != nil {
return nil, err
@@ -278,7 +278,7 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo
// Start a new sandbox for this container. Any errors after this point
// must destroy the container.
- c.Sandbox, err = sandbox.Create(id, spec, conf, bundleDir, consoleSocket, ioFiles)
+ c.Sandbox, err = sandbox.Create(id, spec, conf, bundleDir, consoleSocket, userLog, ioFiles)
if err != nil {
return nil, err
}
@@ -396,9 +396,9 @@ func (c *Container) Restore(spec *specs.Spec, conf *boot.Config, restoreFile str
}
// Run is a helper that calls Create + Start + Wait.
-func Run(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, pidFile string) (syscall.WaitStatus, error) {
+func Run(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, pidFile, userLog string) (syscall.WaitStatus, error) {
log.Debugf("Run container %q in root dir: %s", id, conf.RootDir)
- c, err := Create(id, spec, conf, bundleDir, consoleSocket, pidFile)
+ c, err := Create(id, spec, conf, bundleDir, consoleSocket, pidFile, userLog)
if err != nil {
return 0, fmt.Errorf("error creating container: %v", err)
}
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 84b59ffd8..7ea99d06b 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -210,18 +210,9 @@ func run(spec *specs.Spec, conf *boot.Config) error {
defer os.RemoveAll(bundleDir)
// Create, start and wait for the container.
- c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ ws, err := Run(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
- return fmt.Errorf("error creating container: %v", err)
- }
- defer c.Destroy()
- if err := c.Start(conf); err != nil {
- return fmt.Errorf("error starting container: %v", err)
- }
-
- ws, err := c.Wait()
- if err != nil {
- return fmt.Errorf("error waiting on container: %v", err)
+ return fmt.Errorf("running container: %v", err)
}
if !ws.Exited() || ws.ExitStatus() != 0 {
return fmt.Errorf("container failed, waitStatus: %v", ws)
@@ -299,7 +290,7 @@ func TestLifecycle(t *testing.T) {
}
// Create the container.
id := testutil.UniqueContainerID()
- c, err := Create(id, spec, conf, bundleDir, "", "")
+ c, err := Create(id, spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -420,7 +411,7 @@ func TestExePath(t *testing.T) {
t.Fatalf("exec: %s, error setting up container: %v", test.path, err)
}
- ws, err := Run(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ ws, err := Run(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
os.RemoveAll(rootDir)
os.RemoveAll(bundleDir)
@@ -453,7 +444,7 @@ func TestAppExitStatus(t *testing.T) {
defer os.RemoveAll(rootDir)
defer os.RemoveAll(bundleDir)
- ws, err := Run(testutil.UniqueContainerID(), succSpec, conf, bundleDir, "", "")
+ ws, err := Run(testutil.UniqueContainerID(), succSpec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error running container: %v", err)
}
@@ -472,7 +463,7 @@ func TestAppExitStatus(t *testing.T) {
defer os.RemoveAll(rootDir2)
defer os.RemoveAll(bundleDir2)
- ws, err = Run(testutil.UniqueContainerID(), succSpec, conf, bundleDir2, "", "")
+ ws, err = Run(testutil.UniqueContainerID(), succSpec, conf, bundleDir2, "", "", "")
if err != nil {
t.Fatalf("error running container: %v", err)
}
@@ -497,7 +488,7 @@ func TestExec(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -603,7 +594,7 @@ func TestCheckpointRestore(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -649,7 +640,7 @@ func TestCheckpointRestore(t *testing.T) {
defer outputFile2.Close()
// Restore into a new container.
- cont2, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ cont2, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -688,7 +679,7 @@ func TestCheckpointRestore(t *testing.T) {
defer outputFile3.Close()
// Restore into a new container.
- cont3, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ cont3, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -767,7 +758,7 @@ func TestUnixDomainSockets(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -814,7 +805,7 @@ func TestUnixDomainSockets(t *testing.T) {
defer outputFile2.Close()
// Restore into a new container.
- contRestore, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ contRestore, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -868,7 +859,7 @@ func TestPauseResume(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -973,7 +964,7 @@ func TestPauseResumeStatus(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1037,7 +1028,7 @@ func TestCapabilities(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1138,7 +1129,7 @@ func TestConsoleSocket(t *testing.T) {
// Create the container and pass the socket name.
id := testutil.UniqueContainerID()
- c, err := Create(id, spec, conf, bundleDir, socketRelPath, "")
+ c, err := Create(id, spec, conf, bundleDir, socketRelPath, "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1262,7 +1253,7 @@ func TestReadonlyRoot(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create, start and wait for the container.
- c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1306,7 +1297,7 @@ func TestReadonlyMount(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create, start and wait for the container.
- c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1349,7 +1340,7 @@ func TestAbbreviatedIDs(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- cont, err := Create(cid, spec, conf, bundleDir, "", "")
+ cont, err := Create(cid, spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1409,7 +1400,7 @@ func TestContainerVolumeContentsShared(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1531,7 +1522,7 @@ func TestGoferExits(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1591,7 +1582,7 @@ func TestJobControlSignalExec(t *testing.T) {
defer os.RemoveAll(bundleDir)
// Create and start the container.
- c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ c, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
if err != nil {
t.Fatalf("error creating container: %v", err)
}
@@ -1694,6 +1685,46 @@ func TestJobControlSignalExec(t *testing.T) {
}
}
+func TestUserLog(t *testing.T) {
+ app, err := testutil.FindFile("runsc/container/test_app")
+ if err != nil {
+ t.Fatal("error finding test_app:", err)
+ }
+
+ // sched_rr_get_interval = 148 - not implemented in gvisor.
+ spec := testutil.NewSpecWithArgs(app, "syscall", "--syscall=148")
+ conf := testutil.TestConfig()
+ rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer os.RemoveAll(rootDir)
+ defer os.RemoveAll(bundleDir)
+
+ dir, err := ioutil.TempDir(testutil.TmpDir(), "user_log_test")
+ if err != nil {
+ t.Fatalf("error creating tmp dir: %v", err)
+ }
+ userLog := filepath.Join(dir, "user.log")
+
+ // Create, start and wait for the container.
+ ws, err := Run(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", userLog)
+ if err != nil {
+ t.Fatalf("error running container: %v", err)
+ }
+ if !ws.Exited() || ws.ExitStatus() != 0 {
+ t.Fatalf("container failed, waitStatus: %v", ws)
+ }
+
+ out, err := ioutil.ReadFile(userLog)
+ if err != nil {
+ t.Fatalf("error opening user log file %q: %v", userLog, err)
+ }
+ if want := "Unsupported syscall: sched_rr_get_interval"; !strings.Contains(string(out), want) {
+ t.Errorf("user log file doesn't contain %q, out: %s", want, string(out))
+ }
+}
+
// executeSync synchronously executes a new process.
func (cont *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) {
pid, err := cont.Execute(args)
diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go
index d23d36c37..77f8da8b0 100644
--- a/runsc/container/multi_container_test.go
+++ b/runsc/container/multi_container_test.go
@@ -83,7 +83,7 @@ func startContainers(conf *boot.Config, specs []*specs.Spec, ids []string) ([]*C
}
bundles = append(bundles, bundleDir)
- cont, err := Create(ids[i], spec, conf, bundleDir, "", "")
+ cont, err := Create(ids[i], spec, conf, bundleDir, "", "", "")
if err != nil {
cleanup()
return nil, nil, fmt.Errorf("error creating container: %v", err)
diff --git a/runsc/container/test_app.go b/runsc/container/test_app.go
index f69cfdf83..9e4b5326d 100644
--- a/runsc/container/test_app.go
+++ b/runsc/container/test_app.go
@@ -24,6 +24,7 @@ import (
"os"
"os/exec"
"strconv"
+ sys "syscall"
"time"
"flag"
@@ -38,6 +39,7 @@ func main() {
subcommands.Register(new(taskTree), "")
subcommands.Register(new(forkBomb), "")
subcommands.Register(new(reaper), "")
+ subcommands.Register(new(syscall), "")
flag.Parse()
@@ -241,3 +243,37 @@ func (c *reaper) Execute(ctx context.Context, f *flag.FlagSet, args ...interface
defer stop()
select {}
}
+
+type syscall struct {
+ sysno uint64
+}
+
+// Name implements subcommands.Command.
+func (*syscall) Name() string {
+ return "syscall"
+}
+
+// Synopsis implements subcommands.Command.
+func (*syscall) Synopsis() string {
+ return "syscall makes a syscall"
+}
+
+// Usage implements subcommands.Command.
+func (*syscall) Usage() string {
+ return "syscall <flags>"
+}
+
+// SetFlags implements subcommands.Command.
+func (s *syscall) SetFlags(f *flag.FlagSet) {
+ f.Uint64Var(&s.sysno, "syscall", 0, "syscall to call")
+}
+
+// Execute implements subcommands.Command.
+func (s *syscall) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
+ if _, _, errno := sys.Syscall(uintptr(s.sysno), 0, 0, 0); errno != 0 {
+ fmt.Printf("syscall(%d, 0, 0...) failed: %v\n", s.sysno, errno)
+ } else {
+ fmt.Printf("syscall(%d, 0, 0...) success\n", s.sysno)
+ }
+ return subcommands.ExitSuccess
+}
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index 7f1afc34b..37a3efd09 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -66,7 +66,7 @@ type Sandbox struct {
// Create creates the sandbox process. The caller must call Destroy() on the
// sandbox.
-func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket string, ioFiles []*os.File) (*Sandbox, error) {
+func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, userLog string, ioFiles []*os.File) (*Sandbox, error) {
s := &Sandbox{ID: id}
c := specutils.MakeCleanup(func() { s.destroy() })
defer c.Clean()
@@ -81,7 +81,7 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo
}
// Create the sandbox process.
- if err := s.createSandboxProcess(spec, conf, bundleDir, consoleSocket, ioFiles); err != nil {
+ if err := s.createSandboxProcess(spec, conf, bundleDir, consoleSocket, userLog, ioFiles); err != nil {
return nil, err
}
@@ -266,7 +266,7 @@ func (s *Sandbox) connError(err error) error {
// createSandboxProcess starts the sandbox as a subprocess by running the "boot"
// command, passing in the bundle dir.
-func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket string, ioFiles []*os.File) error {
+func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket, userLog string, ioFiles []*os.File) error {
// nextFD is used to get unused FDs that we can pass to the sandbox. It
// starts at 3 because 0, 1, and 2 are taken by stdin/out/err.
nextFD := 3
@@ -525,6 +525,18 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund
}
}
+ if userLog != "" {
+ f, err := os.OpenFile(userLog, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664)
+ if err != nil {
+ return fmt.Errorf("opening compat log file: %v", err)
+ }
+ defer f.Close()
+
+ cmd.ExtraFiles = append(cmd.ExtraFiles, f)
+ cmd.Args = append(cmd.Args, "--user-log-fd", strconv.Itoa(nextFD))
+ nextFD++
+ }
+
// Add container as the last argument.
cmd.Args = append(cmd.Args, s.ID)