diff options
Diffstat (limited to 'runsc/boot')
-rw-r--r-- | runsc/boot/BUILD | 6 | ||||
-rw-r--r-- | runsc/boot/controller.go | 197 | ||||
-rw-r--r-- | runsc/boot/events.go | 2 | ||||
-rw-r--r-- | runsc/boot/filter/config.go | 8 | ||||
-rw-r--r-- | runsc/boot/filter/config_amd64.go | 1 | ||||
-rw-r--r-- | runsc/boot/filter/config_arm64.go | 1 | ||||
-rw-r--r-- | runsc/boot/filter/config_profile.go | 3 | ||||
-rw-r--r-- | runsc/boot/filter/extra_filters.go | 1 | ||||
-rw-r--r-- | runsc/boot/filter/extra_filters_msan.go | 1 | ||||
-rw-r--r-- | runsc/boot/filter/extra_filters_race.go | 1 | ||||
-rw-r--r-- | runsc/boot/fs.go | 16 | ||||
-rw-r--r-- | runsc/boot/loader.go | 134 | ||||
-rw-r--r-- | runsc/boot/loader_test.go | 6 | ||||
-rw-r--r-- | runsc/boot/network.go | 22 | ||||
-rw-r--r-- | runsc/boot/pprof/pprof.go | 3 | ||||
-rw-r--r-- | runsc/boot/profile.go | 95 | ||||
-rw-r--r-- | runsc/boot/strace.go | 9 | ||||
-rw-r--r-- | runsc/boot/vfs.go | 18 |
18 files changed, 351 insertions, 173 deletions
diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index a79afbdc4..ff7a5a44b 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -15,6 +15,7 @@ go_library( "limits.go", "loader.go", "network.go", + "profile.go", "strace.go", "vfs.go", ], @@ -32,6 +33,7 @@ go_library( "//pkg/control/server", "//pkg/coverage", "//pkg/cpuid", + "//pkg/errors/linuxerr", "//pkg/eventchannel", "//pkg/fd", "//pkg/flipcall", @@ -44,6 +46,7 @@ go_library( "//pkg/sentry/arch", "//pkg/sentry/arch:registers_go_proto", "//pkg/sentry/control", + "//pkg/sentry/control:control_go_proto", "//pkg/sentry/devices/memdev", "//pkg/sentry/devices/ttydev", "//pkg/sentry/devices/tundev", @@ -94,11 +97,10 @@ go_library( "//pkg/sentry/vfs", "//pkg/sentry/watchdog", "//pkg/sync", - "//pkg/syserror", "//pkg/tcpip", + "//pkg/tcpip/link/ethernet", "//pkg/tcpip/link/fdbased", "//pkg/tcpip/link/loopback", - "//pkg/tcpip/link/packetsocket", "//pkg/tcpip/link/qdisc/fifo", "//pkg/tcpip/link/sniffer", "//pkg/tcpip/network/arp", diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go index 9b270cbf2..76e1f596b 100644 --- a/runsc/boot/controller.go +++ b/runsc/boot/controller.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "os" + gtime "time" specs "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" @@ -25,6 +26,7 @@ import ( "gvisor.dev/gvisor/pkg/fd" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/control" + controlpb "gvisor.dev/gvisor/pkg/sentry/control/control_go_proto" "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/socket/netstack" @@ -40,80 +42,89 @@ import ( ) const ( - // ContainerCheckpoint checkpoints a container. - ContainerCheckpoint = "containerManager.Checkpoint" + // ContMgrCheckpoint checkpoints a container. + ContMgrCheckpoint = "containerManager.Checkpoint" - // ContainerCreate creates a container. - ContainerCreate = "containerManager.Create" + // ContMgrCreateSubcontainer creates a sub-container. + ContMgrCreateSubcontainer = "containerManager.CreateSubcontainer" - // ContainerDestroy is used to stop a non-root container and free all + // ContMgrDestroySubcontainer is used to stop a sub-container and free all // associated resources in the sandbox. - ContainerDestroy = "containerManager.Destroy" + ContMgrDestroySubcontainer = "containerManager.DestroySubcontainer" - // ContainerEvent is the URPC endpoint for getting stats about the - // container used by "runsc events". - ContainerEvent = "containerManager.Event" + // ContMgrEvent gets stats about the container used by "runsc events". + ContMgrEvent = "containerManager.Event" - // ContainerExecuteAsync is the URPC endpoint for executing a command in a - // container. - ContainerExecuteAsync = "containerManager.ExecuteAsync" + // ContMgrExecuteAsync executes a command in a container. + ContMgrExecuteAsync = "containerManager.ExecuteAsync" - // ContainerPause pauses the container. - ContainerPause = "containerManager.Pause" + // ContMgrProcesses lists processes running in a container. + ContMgrProcesses = "containerManager.Processes" - // ContainerProcesses is the URPC endpoint for getting the list of - // processes running in a container. - ContainerProcesses = "containerManager.Processes" + // ContMgrRestore restores a container from a statefile. + ContMgrRestore = "containerManager.Restore" - // ContainerRestore restores a container from a statefile. - ContainerRestore = "containerManager.Restore" + // ContMgrSignal sends a signal to a container. + ContMgrSignal = "containerManager.Signal" - // ContainerResume unpauses the paused container. - ContainerResume = "containerManager.Resume" + // ContMgrStartSubcontainer starts a sub-container inside a running sandbox. + ContMgrStartSubcontainer = "containerManager.StartSubcontainer" - // ContainerSignal is used to send a signal to a container. - ContainerSignal = "containerManager.Signal" + // ContMgrWait waits on the init process of the container and returns its + // ExitStatus. + ContMgrWait = "containerManager.Wait" - // ContainerSignalProcess is used to send a signal to a particular - // process in a container. - ContainerSignalProcess = "containerManager.SignalProcess" + // ContMgrWaitPID waits on a process with a certain PID in the sandbox and + // return its ExitStatus. + ContMgrWaitPID = "containerManager.WaitPID" - // ContainerStart is the URPC endpoint for running a non-root container - // within a sandbox. - ContainerStart = "containerManager.Start" - - // ContainerWait is used to wait on the init process of the container - // and return its ExitStatus. - ContainerWait = "containerManager.Wait" - - // ContainerWaitPID is used to wait on a process with a certain PID in - // the sandbox and return its ExitStatus. - ContainerWaitPID = "containerManager.WaitPID" + // ContMgrRootContainerStart starts a new sandbox with a root container. + ContMgrRootContainerStart = "containerManager.StartRoot" +) - // NetworkCreateLinksAndRoutes is the URPC endpoint for creating links - // and routes in a network stack. +const ( + // NetworkCreateLinksAndRoutes creates links and routes in a network stack. NetworkCreateLinksAndRoutes = "Network.CreateLinksAndRoutes" - // RootContainerStart is the URPC endpoint for starting a new sandbox - // with root container. - RootContainerStart = "containerManager.StartRoot" - - // SandboxStacks collects sandbox stacks for debugging. - SandboxStacks = "debug.Stacks" + // DebugStacks collects sandbox stacks for debugging. + DebugStacks = "debug.Stacks" ) // Profiling related commands (see pprof.go for more details). const ( - CPUProfile = "Profile.CPU" - HeapProfile = "Profile.Heap" - BlockProfile = "Profile.Block" - MutexProfile = "Profile.Mutex" - Trace = "Profile.Trace" + ProfileCPU = "Profile.CPU" + ProfileHeap = "Profile.Heap" + ProfileBlock = "Profile.Block" + ProfileMutex = "Profile.Mutex" + ProfileTrace = "Profile.Trace" ) // Logging related commands (see logging.go for more details). const ( - ChangeLogging = "Logging.Change" + LoggingChange = "Logging.Change" +) + +// Lifecycle related commands (see lifecycle.go for more details). +const ( + LifecyclePause = "Lifecycle.Pause" + LifecycleResume = "Lifecycle.Resume" +) + +// Filesystem related commands (see fs.go for more details). +const ( + FsCat = "Fs.Cat" +) + +// Usage related commands (see usage.go for more details). +const ( + UsageCollect = "Usage.Collect" + UsageUsageFD = "Usage.UsageFD" + UsageReduce = "Usage.Reduce" +) + +// Events related commands (see events.go for more details). +const ( + EventsAttachDebugEmitter = "Events.AttachDebugEmitter" ) // ControlSocketAddr generates an abstract unix socket name for the given ID. @@ -155,18 +166,41 @@ func newController(fd int, l *Loader) (*controller, error) { ctrl.srv.Register(net) } - ctrl.srv.Register(&debug{}) - ctrl.srv.Register(&control.Logging{}) - - if l.root.conf.ProfileEnable { - ctrl.srv.Register(control.NewProfile(l.k)) + if l.root.conf.Controls.Controls != nil { + for _, c := range l.root.conf.Controls.Controls.AllowedControls { + switch c { + case controlpb.ControlConfig_EVENTS: + ctrl.srv.Register(&control.Events{}) + case controlpb.ControlConfig_FS: + ctrl.srv.Register(&control.Fs{Kernel: l.k}) + case controlpb.ControlConfig_LIFECYCLE: + ctrl.srv.Register(&control.Lifecycle{Kernel: l.k}) + case controlpb.ControlConfig_LOGGING: + ctrl.srv.Register(&control.Logging{}) + case controlpb.ControlConfig_PROFILE: + if l.root.conf.ProfileEnable { + ctrl.srv.Register(control.NewProfile(l.k)) + } + case controlpb.ControlConfig_USAGE: + ctrl.srv.Register(&control.Usage{Kernel: l.k}) + case controlpb.ControlConfig_PROC: + ctrl.srv.Register(&control.Proc{Kernel: l.k}) + case controlpb.ControlConfig_STATE: + ctrl.srv.Register(&control.State{Kernel: l.k}) + case controlpb.ControlConfig_DEBUG: + ctrl.srv.Register(&debug{}) + } + } } return ctrl, nil } +// stopRPCTimeout is the time for clients to complete ongoing RPCs. +const stopRPCTimeout = 15 * gtime.Second + func (c *controller) stop() { - c.srv.Stop() + c.srv.Stop(stopRPCTimeout) } // containerManager manages sandbox containers. @@ -210,9 +244,9 @@ type CreateArgs struct { urpc.FilePayload } -// Create creates a container within a sandbox. -func (cm *containerManager) Create(args *CreateArgs, _ *struct{}) error { - log.Debugf("containerManager.Create: %s", args.CID) +// CreateSubcontainer creates a container within a sandbox. +func (cm *containerManager) CreateSubcontainer(args *CreateArgs, _ *struct{}) error { + log.Debugf("containerManager.CreateSubcontainer: %s", args.CID) if len(args.Files) > 1 { return fmt.Errorf("start arguments must have at most 1 files for TTY") @@ -225,7 +259,7 @@ func (cm *containerManager) Create(args *CreateArgs, _ *struct{}) error { return fmt.Errorf("error dup'ing TTY file: %w", err) } } - return cm.l.createContainer(args.CID, tty) + return cm.l.createSubcontainer(args.CID, tty) } // StartArgs contains arguments to the Start method. @@ -245,13 +279,13 @@ type StartArgs struct { urpc.FilePayload } -// Start runs a created container within a sandbox. -func (cm *containerManager) Start(args *StartArgs, _ *struct{}) error { +// StartSubcontainer runs a created container within a sandbox. +func (cm *containerManager) StartSubcontainer(args *StartArgs, _ *struct{}) error { // Validate arguments. if args == nil { return errors.New("start missing arguments") } - log.Debugf("containerManager.Start, cid: %s, args: %+v", args.CID, args) + log.Debugf("containerManager.StartSubcontainer, cid: %s, args: %+v", args.CID, args) if args.Spec == nil { return errors.New("start arguments missing spec") } @@ -299,19 +333,19 @@ func (cm *containerManager) Start(args *StartArgs, _ *struct{}) error { } }() - if err := cm.l.startContainer(args.Spec, args.Conf, args.CID, stdios, goferFDs); err != nil { - log.Debugf("containerManager.Start failed, cid: %s, args: %+v, err: %v", args.CID, args, err) + if err := cm.l.startSubcontainer(args.Spec, args.Conf, args.CID, stdios, goferFDs); err != nil { + log.Debugf("containerManager.StartSubcontainer failed, cid: %s, args: %+v, err: %v", args.CID, args, err) return err } log.Debugf("Container started, cid: %s", args.CID) return nil } -// Destroy stops a container if it is still running and cleans up its -// filesystem. -func (cm *containerManager) Destroy(cid *string, _ *struct{}) error { - log.Debugf("containerManager.destroy, cid: %s", *cid) - return cm.l.destroyContainer(*cid) +// DestroySubcontainer stops a container if it is still running and cleans up +// its filesystem. +func (cm *containerManager) DestroySubcontainer(cid *string, _ *struct{}) error { + log.Debugf("containerManager.DestroySubcontainer, cid: %s", *cid) + return cm.l.destroySubcontainer(*cid) } // ExecuteAsync starts running a command on a created or running sandbox. It @@ -330,6 +364,11 @@ func (cm *containerManager) ExecuteAsync(args *control.ExecArgs, pid *int32) err // Checkpoint pauses a sandbox and saves its state. func (cm *containerManager) Checkpoint(o *control.SaveOpts, _ *struct{}) error { log.Debugf("containerManager.Checkpoint") + // TODO(gvisor.dev/issues/6243): save/restore not supported w/ hostinet + if cm.l.root.conf.Network == config.NetworkHost { + return errors.New("checkpoint not supported when using hostinet") + } + state := control.State{ Kernel: cm.l.k, Watchdog: cm.l.watchdog, @@ -337,13 +376,6 @@ func (cm *containerManager) Checkpoint(o *control.SaveOpts, _ *struct{}) error { return state.Save(o, nil) } -// Pause suspends a container. -func (cm *containerManager) Pause(_, _ *struct{}) error { - log.Debugf("containerManager.Pause") - cm.l.k.Pause() - return nil -} - // RestoreOpts contains options related to restoring a container's file system. type RestoreOpts struct { // FilePayload contains the state file to be restored, followed by the @@ -439,7 +471,7 @@ func (cm *containerManager) Restore(o *RestoreOpts, _ *struct{}) error { // Load the state. loadOpts := state.LoadOpts{Source: specFile} - if err := loadOpts.Load(ctx, k, networkStack, time.NewCalibratedClocks(), &vfs.CompleteRestoreOptions{}); err != nil { + if err := loadOpts.Load(ctx, k, nil, networkStack, time.NewCalibratedClocks(), &vfs.CompleteRestoreOptions{}); err != nil { return err } @@ -475,13 +507,6 @@ func (cm *containerManager) Restore(o *RestoreOpts, _ *struct{}) error { return nil } -// Resume unpauses a container. -func (cm *containerManager) Resume(_, _ *struct{}) error { - log.Debugf("containerManager.Resume") - cm.l.k.Unpause() - return nil -} - // Wait waits for the init process in the given container. func (cm *containerManager) Wait(cid *string, waitStatus *uint32) error { log.Debugf("containerManager.Wait, cid: %s", *cid) diff --git a/runsc/boot/events.go b/runsc/boot/events.go index 0814b2a69..65137de8a 100644 --- a/runsc/boot/events.go +++ b/runsc/boot/events.go @@ -91,7 +91,7 @@ func (cm *containerManager) Event(_ *struct{}, out *EventOut) error { // Memory usage. // TODO(gvisor.dev/issue/172): Per-container accounting. mem := cm.l.k.MemoryFile() - mem.UpdateUsage() + _ = mem.UpdateUsage() // best effort to update. _, totalUsage := usage.MemoryAccounting.Copy() out.Event.Data.Memory.Usage = MemoryEntry{ Usage: totalUsage, diff --git a/runsc/boot/filter/config.go b/runsc/boot/filter/config.go index 752fea0e1..703f34827 100644 --- a/runsc/boot/filter/config.go +++ b/runsc/boot/filter/config.go @@ -459,6 +459,14 @@ func hostInetFilters() seccomp.SyscallRules { seccomp.MatchAny{}, seccomp.EqualTo(unix.TIOCINQ), }, + { + seccomp.MatchAny{}, + seccomp.EqualTo(unix.SIOCGIFFLAGS), + }, + { + seccomp.MatchAny{}, + seccomp.EqualTo(unix.SIOCGIFCONF), + }, }, unix.SYS_LISTEN: {}, unix.SYS_READV: {}, diff --git a/runsc/boot/filter/config_amd64.go b/runsc/boot/filter/config_amd64.go index 42cb8ed3a..8015a0e52 100644 --- a/runsc/boot/filter/config_amd64.go +++ b/runsc/boot/filter/config_amd64.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build amd64 // +build amd64 package filter diff --git a/runsc/boot/filter/config_arm64.go b/runsc/boot/filter/config_arm64.go index f162f87ff..9f44379b4 100644 --- a/runsc/boot/filter/config_arm64.go +++ b/runsc/boot/filter/config_arm64.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build arm64 // +build arm64 package filter diff --git a/runsc/boot/filter/config_profile.go b/runsc/boot/filter/config_profile.go index 89b66a6da..214bf8b1d 100644 --- a/runsc/boot/filter/config_profile.go +++ b/runsc/boot/filter/config_profile.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.1 +// +build go1.1 + package filter import ( diff --git a/runsc/boot/filter/extra_filters.go b/runsc/boot/filter/extra_filters.go index e28d4b8d6..5442add95 100644 --- a/runsc/boot/filter/extra_filters.go +++ b/runsc/boot/filter/extra_filters.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !msan && !race // +build !msan,!race package filter diff --git a/runsc/boot/filter/extra_filters_msan.go b/runsc/boot/filter/extra_filters_msan.go index 41baa78cd..8873f9cf9 100644 --- a/runsc/boot/filter/extra_filters_msan.go +++ b/runsc/boot/filter/extra_filters_msan.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build msan // +build msan package filter diff --git a/runsc/boot/filter/extra_filters_race.go b/runsc/boot/filter/extra_filters_race.go index 79b2104f0..046b39014 100644 --- a/runsc/boot/filter/extra_filters_race.go +++ b/runsc/boot/filter/extra_filters_race.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build race // +build race package filter diff --git a/runsc/boot/fs.go b/runsc/boot/fs.go index c4590aab1..40cf2a3df 100644 --- a/runsc/boot/fs.go +++ b/runsc/boot/fs.go @@ -25,6 +25,7 @@ import ( "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/fd" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/fs" @@ -41,7 +42,6 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserror" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/specutils" @@ -69,7 +69,7 @@ const ( // tmpfs has some extra supported options that we must pass through. var tmpfsAllowedData = []string{"mode", "uid", "gid"} -func addOverlay(ctx context.Context, conf *config.Config, lower *fs.Inode, name string, lowerFlags fs.MountSourceFlags) (*fs.Inode, error) { +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. upperFlags := lowerFlags upperFlags.ReadOnly = false @@ -744,7 +744,7 @@ func (c *containerMounter) mountSharedMaster(ctx context.Context, conf *config.C if useOverlay { log.Debugf("Adding overlay on top of shared mount %q", hint.name) - inode, err = addOverlay(ctx, conf, inode, hint.mount.Type, mf) + inode, err = addOverlay(ctx, inode, hint.mount.Type, mf) if err != nil { return nil, err } @@ -785,7 +785,7 @@ func (c *containerMounter) createRootMount(ctx context.Context, conf *config.Con if conf.Overlay && !c.root.Readonly { log.Debugf("Adding overlay on top of root mount") // Overlay a tmpfs filesystem on top of the root. - rootInode, err = addOverlay(ctx, conf, rootInode, "root-overlay-upper", mf) + rootInode, err = addOverlay(ctx, rootInode, "root-overlay-upper", mf) if err != nil { return nil, err } @@ -901,7 +901,7 @@ func (c *containerMounter) mountSubmount(ctx context.Context, conf *config.Confi if useOverlay { log.Debugf("Adding overlay on top of mount %q", m.Destination) - inode, err = addOverlay(ctx, conf, inode, m.Type, mf) + inode, err = addOverlay(ctx, inode, m.Type, mf) if err != nil { return err } @@ -1039,8 +1039,8 @@ func (c *containerMounter) mountTmp(ctx context.Context, conf *config.Config, mn maxTraversals := uint(0) tmp, err := mns.FindInode(ctx, root, root, "tmp", &maxTraversals) - switch err { - case nil: + switch { + case err == nil: // Found '/tmp' in filesystem, check if it's empty. defer tmp.DecRef(ctx) f, err := tmp.Inode.GetFile(ctx, tmp, fs.FileFlags{Read: true, Directory: true}) @@ -1061,7 +1061,7 @@ func (c *containerMounter) mountTmp(ctx context.Context, conf *config.Config, mn log.Infof("Mounting internal tmpfs on top of empty %q", "/tmp") fallthrough - case syserror.ENOENT: + case linuxerr.Equals(linuxerr.ENOENT, err): // No '/tmp' found (or fallthrough from above). Safe to mount internal // tmpfs. tmpMount := specs.Mount{ diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index ad4d50008..b46d84e5a 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -58,6 +58,7 @@ import ( "gvisor.dev/gvisor/pkg/sentry/watchdog" "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/link/ethernet" "gvisor.dev/gvisor/pkg/tcpip/link/loopback" "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" "gvisor.dev/gvisor/pkg/tcpip/network/arp" @@ -119,6 +120,10 @@ type Loader struct { // container. It should be called when a sandbox is destroyed. stopSignalForwarding func() + // stopProfiling stops profiling started at container creation. It + // should be called when a sandbox is destroyed. + stopProfiling func() + // restore is set to true if we are restoring a container. restore bool @@ -198,6 +203,21 @@ type Args struct { TotalMem uint64 // UserLogFD is the file descriptor to write user logs to. UserLogFD int + // ProfileBlockFD is the file descriptor to write a block profile to. + // Valid if >=0. + ProfileBlockFD int + // ProfileCPUFD is the file descriptor to write a CPU profile to. + // Valid if >=0. + ProfileCPUFD int + // ProfileHeapFD is the file descriptor to write a heap profile to. + // Valid if >=0. + ProfileHeapFD int + // ProfileMutexFD is the file descriptor to write a mutex profile to. + // Valid if >=0. + ProfileMutexFD int + // TraceFD is the file descriptor to write a Go execution trace to. + // Valid if >=0. + TraceFD int } // make sure stdioFDs are always the same on initial start and on restore @@ -206,6 +226,8 @@ const startingStdioFD = 256 // New initializes a new kernel loader configured by spec. // New also handles setting up a kernel for restoring a container. func New(args Args) (*Loader, error) { + stopProfiling := startProfiling(args) + // We initialize the rand package now to make sure /dev/urandom is pre-opened // on kernels that do not support getrandom(2). if err := rand.Init(); err != nil { @@ -278,19 +300,15 @@ func New(args Args) (*Loader, error) { } // Create timekeeper. - tk, err := kernel.NewTimekeeper(k, vdso.ParamPage.FileRange()) - if err != nil { - return nil, fmt.Errorf("creating timekeeper: %w", err) - } + tk := kernel.NewTimekeeper(k, vdso.ParamPage.FileRange()) tk.SetClocks(time.NewCalibratedClocks()) - k.SetTimekeeper(tk) if err := enableStrace(args.Conf); err != nil { return nil, fmt.Errorf("enabling strace: %w", err) } // Create root network namespace/stack. - netns, err := newRootNetworkNamespace(args.Conf, k, k) + netns, err := newRootNetworkNamespace(args.Conf, tk, k) if err != nil { return nil, fmt.Errorf("creating network: %w", err) } @@ -332,6 +350,7 @@ func New(args Args) (*Loader, error) { // to createVFS in order to mount (among other things) procfs. if err = k.Init(kernel.InitKernelArgs{ FeatureSet: cpuid.HostFeatureSet(), + Timekeeper: tk, RootUserNamespace: creds.UserNamespace, RootNetworkNamespace: netns, ApplicationCores: uint(args.NumCPU), @@ -402,12 +421,13 @@ func New(args Args) (*Loader, error) { eid := execID{cid: args.ID} l := &Loader{ - k: k, - watchdog: dog, - sandboxID: args.ID, - processes: map[execID]*execProcess{eid: {}}, - mountHints: mountHints, - root: info, + k: k, + watchdog: dog, + sandboxID: args.ID, + processes: map[execID]*execProcess{eid: {}}, + mountHints: mountHints, + root: info, + stopProfiling: stopProfiling, } // We don't care about child signals; some platforms can generate a @@ -500,6 +520,8 @@ func (l *Loader) Destroy() { for _, f := range l.root.goferFDs { _ = f.Close() } + + l.stopProfiling() } func createPlatform(conf *config.Config, deviceFile *os.File) (platform.Platform, error) { @@ -636,8 +658,8 @@ func (l *Loader) run() error { return l.k.Start() } -// createContainer creates a new container inside the sandbox. -func (l *Loader) createContainer(cid string, tty *fd.FD) error { +// createSubcontainer creates a new container inside the sandbox. +func (l *Loader) createSubcontainer(cid string, tty *fd.FD) error { l.mu.Lock() defer l.mu.Unlock() @@ -649,10 +671,10 @@ func (l *Loader) createContainer(cid string, tty *fd.FD) error { return nil } -// startContainer starts a child container. It returns the thread group ID of +// startSubcontainer starts a child container. It returns the thread group ID of // the newly created process. Used FDs are either closed or released. It's safe // for the caller to close any remaining files upon return. -func (l *Loader) startContainer(spec *specs.Spec, conf *config.Config, cid string, stdioFDs, goferFDs []*fd.FD) error { +func (l *Loader) startSubcontainer(spec *specs.Spec, conf *config.Config, cid string, stdioFDs, goferFDs []*fd.FD) error { // Create capabilities. caps, err := specutils.Capabilities(conf.EnableRaw, spec.Process.Capabilities) if err != nil { @@ -718,7 +740,7 @@ func (l *Loader) startContainer(spec *specs.Spec, conf *config.Config, cid strin return fmt.Errorf("using TTY, stdios not expected: %d", l) } if ep.hostTTY == nil { - return fmt.Errorf("terminal enabled but no TTY provided (--console-socket possibly passed)") + return fmt.Errorf("terminal enabled but no TTY provided. Did you set --console-socket on create?") } info.stdioFDs = []*fd.FD{ep.hostTTY, ep.hostTTY, ep.hostTTY} ep.hostTTY = nil @@ -737,7 +759,7 @@ func (l *Loader) startContainer(spec *specs.Spec, conf *config.Config, cid strin func (l *Loader) createContainerProcess(root bool, cid string, info *containerInfo) (*kernel.ThreadGroup, *host.TTYFileOperations, *hostvfs2.TTYFileDescription, error) { // Create the FD map, which will set stdin, stdout, and stderr. ctx := info.procArgs.NewContext(l.k) - fdTable, ttyFile, ttyFileVFS2, err := createFDTable(ctx, info.spec.Process.Terminal, info.stdioFDs) + fdTable, ttyFile, ttyFileVFS2, err := createFDTable(ctx, info.spec.Process.Terminal, info.stdioFDs, info.spec.Process.User) if err != nil { return nil, nil, nil, fmt.Errorf("importing fds: %w", err) } @@ -745,8 +767,11 @@ func (l *Loader) createContainerProcess(root bool, cid string, info *containerIn // ours either way. info.procArgs.FDTable = fdTable - // Setup the child container file system. - l.startGoferMonitor(cid, info.goferFDs) + // Gofer FDs must be ordered and the first FD is always the rootfs. + if len(info.goferFDs) < 1 { + return nil, nil, nil, fmt.Errorf("rootfs gofer FD not found") + } + l.startGoferMonitor(cid, int32(info.goferFDs[0].FD())) mntr := newContainerMounter(info, l.k, l.mountHints, kernel.VFS2Enabled) if root { @@ -819,17 +844,21 @@ func (l *Loader) createContainerProcess(root bool, cid string, info *containerIn } // startGoferMonitor runs a goroutine to monitor gofer's health. It polls on -// the gofer FDs looking for disconnects, and kills the container processes if a -// disconnect occurs in any of the gofer FDs. -func (l *Loader) startGoferMonitor(cid string, goferFDs []*fd.FD) { +// the gofer FD looking for disconnects, and kills the container processes if +// the rootfs FD disconnects. +// +// Note that other gofer mounts are allowed to be unmounted and disconnected. +func (l *Loader) startGoferMonitor(cid string, rootfsGoferFD int32) { + if rootfsGoferFD < 0 { + panic(fmt.Sprintf("invalid FD: %d", rootfsGoferFD)) + } go func() { log.Debugf("Monitoring gofer health for container %q", cid) - var events []unix.PollFd - for _, goferFD := range goferFDs { - events = append(events, unix.PollFd{ - Fd: int32(goferFD.FD()), + events := []unix.PollFd{ + { + Fd: rootfsGoferFD, Events: unix.POLLHUP | unix.POLLRDHUP, - }) + }, } _, _, err := specutils.RetryEintr(func() (uintptr, uintptr, error) { // Use ppoll instead of poll because it's already whilelisted in seccomp. @@ -854,9 +883,9 @@ func (l *Loader) startGoferMonitor(cid string, goferFDs []*fd.FD) { }() } -// destroyContainer stops a container if it is still running and cleans up its -// filesystem. -func (l *Loader) destroyContainer(cid string) error { +// destroySubcontainer stops a container if it is still running and cleans up +// its filesystem. +func (l *Loader) destroySubcontainer(cid string) error { l.mu.Lock() defer l.mu.Unlock() @@ -983,7 +1012,7 @@ func (l *Loader) executeAsync(args *control.ExecArgs) (kernel.ThreadID, error) { tty: ttyFile, ttyVFS2: ttyFileVFS2, } - log.Debugf("updated processes: %s", l.processes) + log.Debugf("updated processes: %v", l.processes) return tgid, nil } @@ -1004,7 +1033,7 @@ func (l *Loader) waitContainer(cid string, waitStatus *uint32) error { // Check for leaks and write coverage report after the root container has // exited. This guarantees that the report is written in cases where the - // sandbox is killed by a signal after the ContainerWait request is completed. + // sandbox is killed by a signal after the ContMgrWait request is completed. if l.root.procArgs.ContainerID == cid { // All sentry-created resources should have been released at this point. refsvfs2.DoLeakCheck() @@ -1027,7 +1056,7 @@ func (l *Loader) waitPID(tgid kernel.ThreadID, cid string, waitStatus *uint32) e l.mu.Lock() delete(l.processes, eid) - log.Debugf("updated processes (removal): %s", l.processes) + log.Debugf("updated processes (removal): %v", l.processes) l.mu.Unlock() return nil } @@ -1054,7 +1083,7 @@ func (l *Loader) waitPID(tgid kernel.ThreadID, cid string, waitStatus *uint32) e // to exit. func (l *Loader) wait(tg *kernel.ThreadGroup) uint32 { tg.WaitExited() - return tg.ExitStatus().Status() + return uint32(tg.ExitStatus()) } // WaitForStartSignal waits for a start signal from the control server. @@ -1063,7 +1092,7 @@ func (l *Loader) WaitForStartSignal() { } // WaitExit waits for the root container to exit, and returns its exit status. -func (l *Loader) WaitExit() kernel.ExitStatus { +func (l *Loader) WaitExit() linux.WaitStatus { // Wait for container. l.k.WaitExited() @@ -1084,23 +1113,24 @@ func newRootNetworkNamespace(conf *config.Config, clock tcpip.Clock, uniqueID st return inet.NewRootNamespace(hostinet.NewStack(), nil), nil case config.NetworkNone, config.NetworkSandbox: - s, err := newEmptySandboxNetworkStack(clock, uniqueID) + s, err := newEmptySandboxNetworkStack(clock, uniqueID, conf.AllowPacketEndpointWrite) if err != nil { return nil, err } creator := &sandboxNetstackCreator{ - clock: clock, - uniqueID: uniqueID, + clock: clock, + uniqueID: uniqueID, + allowPacketEndpointWrite: conf.AllowPacketEndpointWrite, } return inet.NewRootNamespace(s, creator), nil default: - panic(fmt.Sprintf("invalid network configuration: %d", conf.Network)) + panic(fmt.Sprintf("invalid network configuration: %v", conf.Network)) } } -func newEmptySandboxNetworkStack(clock tcpip.Clock, uniqueID stack.UniqueID) (inet.Stack, error) { +func newEmptySandboxNetworkStack(clock tcpip.Clock, uniqueID stack.UniqueID, allowPacketEndpointWrite bool) (inet.Stack, error) { netProtos := []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol, arp.NewProtocol} transProtos := []stack.TransportProtocolFactory{ tcp.NewProtocol, @@ -1116,9 +1146,10 @@ func newEmptySandboxNetworkStack(clock tcpip.Clock, uniqueID stack.UniqueID) (in HandleLocal: true, // Enable raw sockets for users with sufficient // privileges. - RawFactory: raw.EndpointFactory{}, - UniqueID: uniqueID, - DefaultIPTables: netfilter.DefaultLinuxTables, + RawFactory: raw.EndpointFactory{}, + AllowPacketEndpointWrite: allowPacketEndpointWrite, + UniqueID: uniqueID, + DefaultIPTables: netfilter.DefaultLinuxTables, })} // Enable SACK Recovery. @@ -1155,13 +1186,14 @@ func newEmptySandboxNetworkStack(clock tcpip.Clock, uniqueID stack.UniqueID) (in // // +stateify savable type sandboxNetstackCreator struct { - clock tcpip.Clock - uniqueID stack.UniqueID + clock tcpip.Clock + uniqueID stack.UniqueID + allowPacketEndpointWrite bool } // CreateStack implements kernel.NetworkStackCreator.CreateStack. func (f *sandboxNetstackCreator) CreateStack() (inet.Stack, error) { - s, err := newEmptySandboxNetworkStack(f.clock, f.uniqueID) + s, err := newEmptySandboxNetworkStack(f.clock, f.uniqueID, f.allowPacketEndpointWrite) if err != nil { return nil, err } @@ -1170,7 +1202,7 @@ func (f *sandboxNetstackCreator) CreateStack() (inet.Stack, error) { n := &Network{Stack: s.(*netstack.Stack).Stack} nicID := tcpip.NICID(f.uniqueID.UniqueID()) link := DefaultLoopbackLink - linkEP := loopback.New() + linkEP := ethernet.New(loopback.New()) if err := n.createNICWithAddrs(nicID, link.Name, linkEP, link.Addresses); err != nil { return nil, err } @@ -1215,7 +1247,7 @@ func (l *Loader) signal(cid string, pid, signo int32, mode SignalDeliveryMode) e return nil default: - panic(fmt.Sprintf("unknown signal delivery mode %s", mode)) + panic(fmt.Sprintf("unknown signal delivery mode %v", mode)) } } @@ -1340,14 +1372,14 @@ func (l *Loader) ttyFromIDLocked(key execID) (*host.TTYFileOperations, *hostvfs2 return ep.tty, ep.ttyVFS2, nil } -func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD) (*kernel.FDTable, *host.TTYFileOperations, *hostvfs2.TTYFileDescription, error) { +func createFDTable(ctx context.Context, console bool, stdioFDs []*fd.FD, user specs.User) (*kernel.FDTable, *host.TTYFileOperations, *hostvfs2.TTYFileDescription, error) { if len(stdioFDs) != 3 { return nil, nil, nil, fmt.Errorf("stdioFDs should contain exactly 3 FDs (stdin, stdout, and stderr), but %d FDs received", len(stdioFDs)) } k := kernel.KernelFromContext(ctx) fdTable := k.NewFDTable() - ttyFile, ttyFileVFS2, err := fdimport.Import(ctx, fdTable, console, stdioFDs) + ttyFile, ttyFileVFS2, err := fdimport.Import(ctx, fdTable, console, auth.KUID(user.UID), auth.KGID(user.GID), stdioFDs) if err != nil { fdTable.DecRef(ctx) return nil, nil, nil, err diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go index 93c476971..ac6c26d25 100644 --- a/runsc/boot/loader_test.go +++ b/runsc/boot/loader_test.go @@ -188,8 +188,8 @@ func doRun(t *testing.T, vfsEnabled bool) { } // Wait for the application to exit. It should succeed. - if status := l.WaitExit(); status.Code != 0 || status.Signo != 0 { - t.Errorf("application exited with status %+v, want 0", status) + if status := l.WaitExit(); !status.Exited() || status.ExitStatus() != 0 { + t.Errorf("application exited with %s, want exit status 0", status) } } @@ -214,7 +214,7 @@ func doStartSignal(t *testing.T, vfsEnabled bool) { // We aren't going to wait on this application, so the control server // needs to be shut down manually. - defer l.ctrl.srv.Stop() + defer l.ctrl.srv.Stop(time.Hour) // Start a goroutine that calls WaitForStartSignal and writes to a // channel when it returns. diff --git a/runsc/boot/network.go b/runsc/boot/network.go index 7e627e4c6..9fb3ebd95 100644 --- a/runsc/boot/network.go +++ b/runsc/boot/network.go @@ -23,9 +23,9 @@ import ( "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/link/ethernet" "gvisor.dev/gvisor/pkg/tcpip/link/fdbased" "gvisor.dev/gvisor/pkg/tcpip/link/loopback" - "gvisor.dev/gvisor/pkg/tcpip/link/packetsocket" "gvisor.dev/gvisor/pkg/tcpip/link/qdisc/fifo" "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" @@ -169,7 +169,7 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct nicID++ nicids[link.Name] = nicID - linkEP := loopback.New() + linkEP := ethernet.New(loopback.New()) log.Infof("Enabling loopback interface %q with id %d on addresses %+v", link.Name, nicID, link.Addresses) if err := n.createNICWithAddrs(nicID, link.Name, linkEP, link.Addresses); err != nil { @@ -209,7 +209,7 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct linkEP, err := fdbased.New(&fdbased.Options{ FDs: FDs, MTU: uint32(link.MTU), - EthernetHeader: true, + EthernetHeader: mac != "", Address: mac, PacketDispatchMode: fdbased.RecvMMsg, GSOMaxSize: link.GSOMaxSize, @@ -228,9 +228,6 @@ func (n *Network) CreateLinksAndRoutes(args *CreateLinksAndRoutesArgs, _ *struct linkEP = fifo.New(linkEP, runtime.GOMAXPROCS(0), 1000) } - // Enable support for AF_PACKET sockets to receive outgoing packets. - linkEP = packetsocket.New(linkEP) - log.Infof("Enabling interface %q with id %d on addresses %+v (%v) w/ %d channels", link.Name, nicID, link.Addresses, mac, link.NumChannels) if err := n.createNICWithAddrs(nicID, link.Name, linkEP, link.Addresses); err != nil { return err @@ -285,12 +282,15 @@ func (n *Network) createNICWithAddrs(id tcpip.NICID, name string, ep stack.LinkE for _, addr := range addrs { proto, tcpipAddr := ipToAddressAndProto(addr.Address) - ap := tcpip.AddressWithPrefix{ - Address: tcpipAddr, - PrefixLen: addr.PrefixLen, + protocolAddr := tcpip.ProtocolAddress{ + Protocol: proto, + AddressWithPrefix: tcpip.AddressWithPrefix{ + Address: tcpipAddr, + PrefixLen: addr.PrefixLen, + }, } - if err := n.Stack.AddAddressWithPrefix(id, proto, ap); err != nil { - return fmt.Errorf("AddAddress(%v, %v, %v) failed: %v", id, proto, tcpipAddr, err) + if err := n.Stack.AddProtocolAddress(id, protocolAddr, stack.AddressProperties{}); err != nil { + return fmt.Errorf("AddProtocolAddress(%d, %+v, {}) failed: %s", id, protocolAddr, err) } } return nil diff --git a/runsc/boot/pprof/pprof.go b/runsc/boot/pprof/pprof.go index 1ded20dee..36b78ad86 100644 --- a/runsc/boot/pprof/pprof.go +++ b/runsc/boot/pprof/pprof.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.1 +// +build go1.1 + // Package pprof provides a stub to initialize custom profilers. package pprof diff --git a/runsc/boot/profile.go b/runsc/boot/profile.go new file mode 100644 index 000000000..3ecd3e532 --- /dev/null +++ b/runsc/boot/profile.go @@ -0,0 +1,95 @@ +// Copyright 2021 The gVisor Authors. +// +// 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 ( + "os" + "runtime" + "runtime/pprof" + "runtime/trace" + + "gvisor.dev/gvisor/pkg/log" + "gvisor.dev/gvisor/pkg/sentry/control" +) + +// startProfiling initiates profiling as defined by the ProfileConfig, and +// returns a function that should be called to stop profiling. +func startProfiling(args Args) func() { + var onStopProfiling []func() + stopProfiling := func() { + for _, f := range onStopProfiling { + f() + } + } + + if args.ProfileBlockFD >= 0 { + file := os.NewFile(uintptr(args.ProfileBlockFD), "profile-block") + + runtime.SetBlockProfileRate(control.DefaultBlockProfileRate) + onStopProfiling = append(onStopProfiling, func() { + if err := pprof.Lookup("block").WriteTo(file, 0); err != nil { + log.Warningf("Error writing block profile: %v", err) + } + file.Close() + runtime.SetBlockProfileRate(0) + }) + } + + if args.ProfileCPUFD >= 0 { + file := os.NewFile(uintptr(args.ProfileCPUFD), "profile-cpu") + + pprof.StartCPUProfile(file) + onStopProfiling = append(onStopProfiling, func() { + pprof.StopCPUProfile() + file.Close() + }) + } + + if args.ProfileHeapFD >= 0 { + file := os.NewFile(uintptr(args.ProfileHeapFD), "profile-heap") + + onStopProfiling = append(onStopProfiling, func() { + if err := pprof.Lookup("heap").WriteTo(file, 0); err != nil { + log.Warningf("Error writing heap profile: %v", err) + } + file.Close() + }) + } + + if args.ProfileMutexFD >= 0 { + file := os.NewFile(uintptr(args.ProfileMutexFD), "profile-mutex") + + prev := runtime.SetMutexProfileFraction(control.DefaultMutexProfileRate) + onStopProfiling = append(onStopProfiling, func() { + if err := pprof.Lookup("mutex").WriteTo(file, 0); err != nil { + log.Warningf("Error writing mutex profile: %v", err) + } + file.Close() + runtime.SetMutexProfileFraction(prev) + }) + } + + if args.TraceFD >= 0 { + file := os.NewFile(uintptr(args.TraceFD), "trace") + + trace.Start(file) + onStopProfiling = append(onStopProfiling, func() { + trace.Stop() + file.Close() + }) + } + + return stopProfiling +} diff --git a/runsc/boot/strace.go b/runsc/boot/strace.go index c21648a32..cf5be34cd 100644 --- a/runsc/boot/strace.go +++ b/runsc/boot/strace.go @@ -35,9 +35,14 @@ func enableStrace(conf *config.Config) error { } strace.LogMaximumSize = max + sink := strace.SinkTypeLog + if conf.StraceEvent { + sink = strace.SinkTypeEvent + } + if len(conf.StraceSyscalls) == 0 { - strace.EnableAll(strace.SinkTypeLog) + strace.EnableAll(sink) return nil } - return strace.Enable(strings.Split(conf.StraceSyscalls, ","), strace.SinkTypeLog) + return strace.Enable(strings.Split(conf.StraceSyscalls, ","), sink) } diff --git a/runsc/boot/vfs.go b/runsc/boot/vfs.go index 52aa33529..346796d9c 100644 --- a/runsc/boot/vfs.go +++ b/runsc/boot/vfs.go @@ -24,6 +24,7 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/cleanup" "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/errors/linuxerr" "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/devices/memdev" @@ -44,7 +45,6 @@ import ( "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" "gvisor.dev/gvisor/pkg/sentry/vfs" - "gvisor.dev/gvisor/pkg/syserror" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/specutils" ) @@ -656,20 +656,20 @@ func (c *containerMounter) mountTmpVFS2(ctx context.Context, conf *config.Config Path: fspath.Parse("/tmp"), } fd, err := c.k.VFS().OpenAt(ctx, creds, &pop, &vfs.OpenOptions{Flags: linux.O_RDONLY | linux.O_DIRECTORY}) - switch err { - case nil: + switch { + case err == nil: defer fd.DecRef(ctx) err := fd.IterDirents(ctx, vfs.IterDirentsCallbackFunc(func(dirent vfs.Dirent) error { if dirent.Name != "." && dirent.Name != ".." { - return syserror.ENOTEMPTY + return linuxerr.ENOTEMPTY } return nil })) - switch err { - case nil: + switch { + case err == nil: log.Infof(`Mounting internal tmpfs on top of empty "/tmp"`) - case syserror.ENOTEMPTY: + case linuxerr.Equals(linuxerr.ENOTEMPTY, err): // If more than "." and ".." is found, skip internal tmpfs to prevent // hiding existing files. log.Infof(`Skipping internal tmpfs mount for "/tmp" because it's not empty`) @@ -679,7 +679,7 @@ func (c *containerMounter) mountTmpVFS2(ctx context.Context, conf *config.Config } fallthrough - case syserror.ENOENT: + case linuxerr.Equals(linuxerr.ENOENT, err): // No '/tmp' found (or fallthrough from above). It's safe to mount internal // tmpfs. tmpMount := specs.Mount{ @@ -692,7 +692,7 @@ func (c *containerMounter) mountTmpVFS2(ctx context.Context, conf *config.Config _, err := c.mountSubmountVFS2(ctx, conf, mns, creds, &mountAndFD{mount: &tmpMount}) return err - case syserror.ENOTDIR: + case linuxerr.Equals(linuxerr.ENOTDIR, err): // Not a dir?! Let it be. return nil |