diff --git a/cmd/nerdctl/container_restart_linux_test.go b/cmd/nerdctl/container_restart_linux_test.go index 374debc1d83..cd1160a540e 100644 --- a/cmd/nerdctl/container_restart_linux_test.go +++ b/cmd/nerdctl/container_restart_linux_test.go @@ -71,6 +71,33 @@ func TestRestartPIDContainer(t *testing.T) { assert.Equal(t, baseOutput, sharedOutput) } +func TestRestartIPCContainer(t *testing.T) { + t.Parallel() + base := testutil.NewBase(t) + + const shmSize = "32m" + baseContainerName := testutil.Identifier(t) + defer base.Cmd("rm", "-f", baseContainerName).Run() + base.Cmd("run", "-d", "--shm-size", shmSize, "--ipc", "shareable", "--name", baseContainerName, testutil.AlpineImage, "sleep", "infinity").AssertOK() + + sharedContainerName := fmt.Sprintf("%s-shared", baseContainerName) + defer base.Cmd("rm", "-f", sharedContainerName).Run() + base.Cmd("run", "-d", "--name", sharedContainerName, fmt.Sprintf("--ipc=container:%s", baseContainerName), testutil.AlpineImage, "sleep", "infinity").AssertOK() + + base.Cmd("stop", baseContainerName).Run() + base.Cmd("stop", sharedContainerName).Run() + + base.Cmd("restart", baseContainerName).AssertOK() + base.Cmd("restart", sharedContainerName).AssertOK() + + baseShmSizeResult := base.Cmd("exec", baseContainerName, "/bin/grep", "shm", "/proc/self/mounts").Run() + baseOutput := strings.TrimSpace(baseShmSizeResult.Stdout()) + sharedShmSizeResult := base.Cmd("exec", sharedContainerName, "/bin/grep", "shm", "/proc/self/mounts").Run() + sharedOutput := strings.TrimSpace(sharedShmSizeResult.Stdout()) + + assert.Equal(t, baseOutput, sharedOutput) +} + func TestRestartWithTime(t *testing.T) { t.Parallel() base := testutil.NewBase(t) diff --git a/cmd/nerdctl/container_run_linux_test.go b/cmd/nerdctl/container_run_linux_test.go index e5b519c709f..63943258677 100644 --- a/cmd/nerdctl/container_run_linux_test.go +++ b/cmd/nerdctl/container_run_linux_test.go @@ -70,6 +70,64 @@ func TestRunShmSize(t *testing.T) { base.Cmd("run", "--rm", "--shm-size", shmSize, testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k") } +func TestRunShmSizeIPCShareable(t *testing.T) { + t.Parallel() + base := testutil.NewBase(t) + const shmSize = "32m" + + container := testutil.Identifier(t) + base.Cmd("run", "--rm", "--name", container, "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k") + defer base.Cmd("rm", "-f", container) +} + +func TestRunIPCShareableRemoveMount(t *testing.T) { + t.Parallel() + base := testutil.NewBase(t) + container := testutil.Identifier(t) + + base.Cmd("run", "--name", container, "--ipc", "shareable", testutil.AlpineImage, "sleep", "0").AssertOK() + base.Cmd("rm", container).AssertOK() +} + +func TestRunIPCContainerNotExists(t *testing.T) { + t.Parallel() + base := testutil.NewBase(t) + + container := testutil.Identifier(t) + result := base.Cmd("run", "--name", container, "--ipc", "container:abcd1234", testutil.AlpineImage, "sleep", "infinity").Run() + defer base.Cmd("rm", "-f", container) + combined := result.Combined() + if !strings.Contains(strings.ToLower(combined), "no such container: abcd1234") { + t.Fatalf("unexpected output: %s", combined) + } +} + +func TestRunShmSizeIPCContainer(t *testing.T) { + t.Parallel() + base := testutil.NewBase(t) + + const shmSize = "32m" + sharedContainerResult := base.Cmd("run", "-d", "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "sleep", "infinity").Run() + baseContainerID := strings.TrimSpace(sharedContainerResult.Stdout()) + defer base.Cmd("rm", "-f", baseContainerID).Run() + + base.Cmd("run", "--rm", fmt.Sprintf("--ipc=container:%s", baseContainerID), + testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k") +} + +func TestRunIPCContainer(t *testing.T) { + t.Parallel() + base := testutil.NewBase(t) + + const shmSize = "32m" + victimContainerResult := base.Cmd("run", "-d", "--ipc", "shareable", "--shm-size", shmSize, testutil.AlpineImage, "sleep", "infinity").Run() + victimContainerID := strings.TrimSpace(victimContainerResult.Stdout()) + defer base.Cmd("rm", "-f", victimContainerID).Run() + + base.Cmd("run", "--rm", fmt.Sprintf("--ipc=container:%s", victimContainerID), + testutil.AlpineImage, "/bin/grep", "shm", "/proc/self/mounts").AssertOutContains("size=32768k") +} + func TestRunPidHost(t *testing.T) { t.Parallel() base := testutil.NewBase(t) diff --git a/docs/command-reference.md b/docs/command-reference.md index cbd79627998..5ac33d6b565 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -351,7 +351,7 @@ Logging flags: Shared memory flags: -- :whale: `--ipc`: IPC namespace to use +- :whale: `--ipc=(host|private|shareable|container:)`: IPC namespace to use and mount `/dev/shm`. Default: "private". Only implemented on Linux. - :whale: `--shm-size`: Size of `/dev/shm` GPU flags: diff --git a/pkg/cmd/container/create.go b/pkg/cmd/container/create.go index dc5b3491bf7..fb1bf982688 100644 --- a/pkg/cmd/container/create.go +++ b/pkg/cmd/container/create.go @@ -24,7 +24,6 @@ import ( "net/url" "os" "os/exec" - "path" "path/filepath" "runtime" "strconv" @@ -44,6 +43,7 @@ import ( "github.com/containerd/nerdctl/v2/pkg/idgen" "github.com/containerd/nerdctl/v2/pkg/imgutil" "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" + "github.com/containerd/nerdctl/v2/pkg/ipcutil" "github.com/containerd/nerdctl/v2/pkg/labels" "github.com/containerd/nerdctl/v2/pkg/logging" "github.com/containerd/nerdctl/v2/pkg/mountutil" @@ -387,23 +387,6 @@ func generateRootfsOpts(args []string, id string, ensured *imgutil.EnsuredImage, return opts, cOpts, nil } -// withBindMountHostIPC replaces /dev/shm and /dev/mqueue mount with rbind. -// Required for --ipc=host on rootless. -func withBindMountHostIPC(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error { - for i, m := range s.Mounts { - switch p := path.Clean(m.Destination); p { - case "/dev/shm", "/dev/mqueue": - s.Mounts[i] = specs.Mount{ - Destination: p, - Type: "bind", - Source: p, - Options: []string{"rbind", "nosuid", "noexec", "nodev"}, - } - } - } - return nil -} - // GenerateLogURI generates a log URI for the current container store func GenerateLogURI(dataStore string) (*url.URL, error) { selfExe, err := os.Executable() @@ -516,6 +499,8 @@ type internalLabels struct { anonVolumes []string // pid namespace pidContainer string + // ipc namespace & dev/shm + ipc string // log logURI string } @@ -591,6 +576,10 @@ func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerO m[labels.PIDContainer] = internalLabels.pidContainer } + if internalLabels.ipc != "" { + m[labels.IPC] = internalLabels.ipc + } + return containerd.WithAdditionalContainerLabels(m), nil } @@ -717,6 +706,13 @@ func generateGcFunc(ctx context.Context, container containerd.Container, ns, id, } } + ipc, ipcErr := ipcutil.DecodeIPCLabel(internalLabels.ipc) + if ipcErr != nil { + log.G(ctx).WithError(ipcErr).Warnf("failed to decode ipc label for container %q", id) + } + if ipcErr := ipcutil.CleanUp(ipc); ipcErr != nil { + log.G(ctx).WithError(ipcErr).Warnf("failed to clean up ipc for container %q", id) + } if rmErr := os.RemoveAll(internalLabels.stateDir); rmErr != nil { log.G(ctx).WithError(rmErr).Warnf("failed to remove container %q state dir %q", id, internalLabels.stateDir) } diff --git a/pkg/cmd/container/remove.go b/pkg/cmd/container/remove.go index 842f62d7e54..04c022e719b 100644 --- a/pkg/cmd/container/remove.go +++ b/pkg/cmd/container/remove.go @@ -36,6 +36,7 @@ import ( "github.com/containerd/nerdctl/v2/pkg/containerutil" "github.com/containerd/nerdctl/v2/pkg/dnsutil/hostsstore" "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" + "github.com/containerd/nerdctl/v2/pkg/ipcutil" "github.com/containerd/nerdctl/v2/pkg/labels" "github.com/containerd/nerdctl/v2/pkg/namestore" ) @@ -105,6 +106,14 @@ func RemoveContainer(ctx context.Context, c containerd.Container, globalOptions if err != nil { return err } + ipc, err := ipcutil.DecodeIPCLabel(l[labels.IPC]) + if err != nil { + return err + } + err = ipcutil.CleanUp(ipc) + if err != nil { + return err + } stateDir := l[labels.StateDir] name := l[labels.Name] dataStore, err := clientutil.DataStore(globalOptions.DataRoot, globalOptions.Address) diff --git a/pkg/cmd/container/run_linux.go b/pkg/cmd/container/run_linux.go index 06ef8416294..83773477782 100644 --- a/pkg/cmd/container/run_linux.go +++ b/pkg/cmd/container/run_linux.go @@ -30,9 +30,9 @@ import ( "github.com/containerd/nerdctl/v2/pkg/bypass4netnsutil" "github.com/containerd/nerdctl/v2/pkg/containerutil" "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" + "github.com/containerd/nerdctl/v2/pkg/ipcutil" "github.com/containerd/nerdctl/v2/pkg/rootlessutil" "github.com/containerd/nerdctl/v2/pkg/strutil" - "github.com/docker/go-units" "github.com/opencontainers/runtime-spec/specs-go" ) @@ -83,13 +83,6 @@ func setPlatformOptions(ctx context.Context, client *containerd.Client, id, uts return nil, err } opts = append(opts, b4nnOpts...) - if len(options.ShmSize) > 0 { - shmBytes, err := units.RAMInBytes(options.ShmSize) - if err != nil { - return nil, err - } - opts = append(opts, oci.WithDevShmSize(shmBytes/1024)) - } ulimitOpts, err := generateUlimitsOpts(options.Ulimit) if err != nil { @@ -149,15 +142,13 @@ func generateNamespaceOpts( return nil, fmt.Errorf("unknown uts value. valid value(s) are 'host', got: %q", uts) } - switch options.IPC { - case "host": - opts = append(opts, oci.WithHostNamespace(specs.IPCNamespace)) - opts = append(opts, withBindMountHostIPC) - case "private", "": - // If nothing is specified, or if private, default to normal behavior - default: - return nil, fmt.Errorf("unknown ipc value. valid values are 'private' or 'host', got: %q", options.IPC) + stateDir := internalLabels.stateDir + ipcOpts, ipcLabel, err := generateIPCOpts(ctx, client, options.IPC, options.ShmSize, stateDir) + if err != nil { + return nil, err } + internalLabels.ipc = ipcLabel + opts = append(opts, ipcOpts...) pidOpts, pidLabel, err := generatePIDOpts(ctx, client, options.Pid) if err != nil { @@ -169,6 +160,25 @@ func generateNamespaceOpts( return opts, nil } +func generateIPCOpts(ctx context.Context, client *containerd.Client, ipcFlag string, shmSize string, stateDir string) ([]oci.SpecOpts, string, error) { + ipcFlag = strings.ToLower(ipcFlag) + + ipc, err := ipcutil.DetectFlags(ctx, client, stateDir, ipcFlag, shmSize) + if err != nil { + return nil, "", err + } + ipcLabel, err := ipcutil.EncodeIPCLabel(ipc) + if err != nil { + return nil, "", err + } + opts, err := ipcutil.GenerateIPCOpts(ctx, ipc, client) + if err != nil { + return nil, "", err + } + + return opts, ipcLabel, nil +} + func generatePIDOpts(ctx context.Context, client *containerd.Client, pid string) ([]oci.SpecOpts, string, error) { opts := make([]oci.SpecOpts, 0) pid = strings.ToLower(pid) diff --git a/pkg/containerutil/config.go b/pkg/containerutil/config.go index 44bf50286dc..3031921222b 100644 --- a/pkg/containerutil/config.go +++ b/pkg/containerutil/config.go @@ -26,6 +26,7 @@ import ( "github.com/containerd/containerd" "github.com/containerd/containerd/oci" + "github.com/containerd/nerdctl/v2/pkg/ipcutil" "github.com/containerd/nerdctl/v2/pkg/labels" "github.com/containerd/nerdctl/v2/pkg/netutil/nettype" "github.com/opencontainers/runtime-spec/specs-go" @@ -104,3 +105,26 @@ func ReconfigPIDContainer(ctx context.Context, c containerd.Container, client *c } return nil } + +// ReconfigIPCContainer reconfigures the container's spec options for sharing IPC namespace and volumns. +func ReconfigIPCContainer(ctx context.Context, c containerd.Container, client *containerd.Client, lab map[string]string) error { + ipc, err := ipcutil.DecodeIPCLabel(lab[labels.IPC]) + if err != nil { + return err + } + opts, err := ipcutil.GenerateIPCOpts(ctx, ipc, client) + if err != nil { + return err + } + spec, err := c.Spec(ctx) + if err != nil { + return err + } + err = c.Update(ctx, containerd.UpdateContainerOpts( + containerd.WithSpec(spec, oci.Compose(opts...)), + )) + if err != nil { + return err + } + return nil +} diff --git a/pkg/containerutil/containerutil.go b/pkg/containerutil/containerutil.go index ad05e8fab5d..85a4ddc53c9 100644 --- a/pkg/containerutil/containerutil.go +++ b/pkg/containerutil/containerutil.go @@ -38,6 +38,7 @@ import ( "github.com/containerd/nerdctl/v2/pkg/consoleutil" "github.com/containerd/nerdctl/v2/pkg/errutil" "github.com/containerd/nerdctl/v2/pkg/formatter" + "github.com/containerd/nerdctl/v2/pkg/ipcutil" "github.com/containerd/nerdctl/v2/pkg/labels" "github.com/containerd/nerdctl/v2/pkg/nsutil" "github.com/containerd/nerdctl/v2/pkg/portutil" @@ -228,6 +229,10 @@ func Start(ctx context.Context, container containerd.Container, flagA bool, clie return err } + if err := ReconfigIPCContainer(ctx, container, client, lab); err != nil { + return err + } + process, err := container.Spec(ctx) if err != nil { return err @@ -331,6 +336,16 @@ func Stop(ctx context.Context, container containerd.Container, timeout *time.Dur if err != nil { return err } + ipc, err := ipcutil.DecodeIPCLabel(l[labels.IPC]) + if err != nil { + return err + } + // defer umount + defer func() { + if err := ipcutil.CleanUp(ipc); err != nil { + log.G(ctx).Warnf("failed to clean up IPC container %s: %s", container.ID(), err) + } + }() if timeout == nil { t, ok := l[labels.StopTimeout] diff --git a/pkg/ipcutil/ipcutil.go b/pkg/ipcutil/ipcutil.go new file mode 100644 index 00000000000..9a1ac8e9015 --- /dev/null +++ b/pkg/ipcutil/ipcutil.go @@ -0,0 +1,250 @@ +/* + Copyright The containerd 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 ipcutil + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "path" + "path/filepath" + "runtime" + "strings" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" + "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" + "github.com/containerd/nerdctl/v2/pkg/labels" + "github.com/docker/go-units" + "github.com/opencontainers/runtime-spec/specs-go" +) + +type IPCMode string + +type IPC struct { + Mode IPCMode `json:"mode,omitempty"` + // VictimContainer is only used when mode is container + VictimContainerID *string `json:"victimContainerId,omitempty"` + + // HostShmPath is only used when mode is shareable + HostShmPath *string `json:"hostShmPath,omitempty"` + + // ShmSize is only used when mode is private or shareable + // Devshm size in bytes + ShmSize string `json:"shmSize,omitempty"` +} + +const ( + Private IPCMode = "private" + Host IPCMode = "host" + Shareable IPCMode = "shareable" + Container IPCMode = "container" +) + +// DetectFlags detects IPC mode from the given ipc string and shmSize string. +// If ipc is empty, it returns IPC{Mode: Private}. +func DetectFlags(ctx context.Context, client *containerd.Client, stateDir string, ipc string, shmSize string) (IPC, error) { + var res IPC + res.ShmSize = shmSize + switch ipc { + case "", "private": + res.Mode = Private + case "host": + res.Mode = Host + case "shareable": + res.Mode = Shareable + shmPath := filepath.Join(stateDir, "shm") + res.HostShmPath = &shmPath + default: // container: + res.Mode = Container + parsed := strings.Split(ipc, ":") + if len(parsed) < 2 || parsed[0] != "container" { + return res, fmt.Errorf("invalid ipc namespace. Set --ipc=[host|container:") + } + + containerName := parsed[1] + walker := &containerwalker.ContainerWalker{ + Client: client, + OnFound: func(ctx context.Context, found containerwalker.Found) error { + if found.MatchCount > 1 { + return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) + } + victimContainerID := found.Container.ID() + res.VictimContainerID = &victimContainerID + + return nil + }, + } + matchedCount, err := walker.Walk(ctx, containerName) + if err != nil { + return res, err + } + if matchedCount < 1 { + return res, fmt.Errorf("no such container: %s", containerName) + } + } + + return res, nil +} + +// EncodeIPCLabel encodes IPC spec into a label. +func EncodeIPCLabel(ipc IPC) (string, error) { + if ipc.Mode == "" { + return "", nil + } + b, err := json.Marshal(ipc) + if err != nil { + return "", err + } + return string(b), nil +} + +// DecodeIPCLabel decodes IPC spec from a label. +// For backward compatibility, if ipcLabel is empty, it returns IPC{Mode: Private}. +func DecodeIPCLabel(ipcLabel string) (IPC, error) { + if ipcLabel == "" { + return IPC{ + Mode: Private, + }, nil + } + + var ipc IPC + if err := json.Unmarshal([]byte(ipcLabel), &ipc); err != nil { + return IPC{}, err + } + return ipc, nil +} + +// GenerateIPCOpts generates IPC spec opts from the given IPC. +func GenerateIPCOpts(ctx context.Context, ipc IPC, client *containerd.Client) ([]oci.SpecOpts, error) { + opts := make([]oci.SpecOpts, 0) + + switch ipc.Mode { + case Private: + // If nothing is specified, or if private, default to normal behavior + if len(ipc.ShmSize) > 0 { + shmBytes, err := units.RAMInBytes(ipc.ShmSize) + if err != nil { + return nil, err + } + opts = append(opts, oci.WithDevShmSize(shmBytes/1024)) + } + case Host: + opts = append(opts, withBindMountHostIPC) + if runtime.GOOS != "windows" { + opts = append(opts, oci.WithHostNamespace(specs.IPCNamespace)) + } + case Shareable: + if ipc.HostShmPath == nil { + return nil, errors.New("ipc mode is shareable, but host shm path is nil") + } + err := makeShareableDevshm(*ipc.HostShmPath, ipc.ShmSize) + if err != nil { + return nil, err + } + opts = append(opts, withBindMountHostOtherSourceIPC(*ipc.HostShmPath)) + case Container: + if ipc.VictimContainerID == nil { + return nil, errors.New("ipc mode is container, but victim container id is nil") + } + targetCon, err := client.LoadContainer(ctx, *ipc.VictimContainerID) + if err != nil { + return nil, err + } + + task, err := targetCon.Task(ctx, nil) + if err != nil { + return nil, err + } + + status, err := task.Status(ctx) + if err != nil { + return nil, err + } + + if status.Status != containerd.Running { + return nil, fmt.Errorf("shared container is not running") + } + + targetConLabels, err := targetCon.Labels(ctx) + if err != nil { + return nil, err + } + + targetConIPC, err := DecodeIPCLabel(targetConLabels[labels.IPC]) + if err != nil { + return nil, err + } + + if targetConIPC.Mode == Host { + opts = append(opts, oci.WithHostNamespace(specs.IPCNamespace)) + opts = append(opts, withBindMountHostIPC) + return opts, nil + } else if targetConIPC.Mode != Shareable { + return nil, errors.New("victim container's ipc mode is not shareable") + } + + if targetConIPC.HostShmPath == nil { + return nil, errors.New("victim container's host shm path is nil") + } + + opts = append(opts, withBindMountHostOtherSourceIPC(*targetConIPC.HostShmPath)) + } + + return opts, nil +} + +// WithBindMountHostOtherSourceIPC replaces /dev/shm mount with rbind by the given path on host +func withBindMountHostOtherSourceIPC(source string) oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error { + for i, m := range s.Mounts { + p := path.Clean(m.Destination) + if p == "/dev/shm" { + s.Mounts[i] = specs.Mount{ + Type: "bind", + Destination: p, + Source: source, + Options: []string{"rbind", "nosuid", "noexec", "nodev"}, + } + } + } + return nil + } +} + +// WithBindMountHostIPC replaces /dev/shm and /dev/mqueue mount with rbind. +// Required for --ipc=host on rootless. +func withBindMountHostIPC(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error { + for i, m := range s.Mounts { + switch p := path.Clean(m.Destination); p { + case "/dev/shm", "/dev/mqueue": + s.Mounts[i] = specs.Mount{ + Destination: p, + Type: "bind", + Source: p, + Options: []string{"rbind", "nosuid", "noexec", "nodev"}, + } + } + } + return nil +} + +func CleanUp(ipc IPC) error { + return cleanUpPlatformSpecificIPC(ipc) +} diff --git a/pkg/ipcutil/ipcutil_linux.go b/pkg/ipcutil/ipcutil_linux.go new file mode 100644 index 00000000000..1c676abb027 --- /dev/null +++ b/pkg/ipcutil/ipcutil_linux.go @@ -0,0 +1,64 @@ +//go:build linux + +/* + Copyright The containerd 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 ipcutil + +import ( + "fmt" + "os" + + "github.com/docker/go-units" + "golang.org/x/sys/unix" +) + +// makeShareableDevshm returns devshm directory path on host when there is no error. +func makeShareableDevshm(shmPath, shmSize string) error { + shmproperty := "mode=1777" + if len(shmSize) > 0 { + shmBytes, err := units.RAMInBytes(shmSize) + if err != nil { + return err + } + shmproperty = fmt.Sprintf("%s,size=%d", shmproperty, shmBytes) + } + err := os.MkdirAll(shmPath, 0700) + if err != nil { + return err + } + err = unix.Mount("/dev/shm", shmPath, "tmpfs", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), shmproperty) + if err != nil { + return err + } + + return nil +} + +// cleanUpPlatformSpecificIPC cleans up platform specific IPC. +func cleanUpPlatformSpecificIPC(ipc IPC) error { + if ipc.Mode == Shareable && ipc.HostShmPath != nil { + err := unix.Unmount(*ipc.HostShmPath, 0) + if err != nil { + return err + } + err = os.RemoveAll(*ipc.HostShmPath) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/ipcutil/ipcutil_unix.go b/pkg/ipcutil/ipcutil_unix.go new file mode 100644 index 00000000000..c5664fc2732 --- /dev/null +++ b/pkg/ipcutil/ipcutil_unix.go @@ -0,0 +1,34 @@ +//go:build freebsd + +/* + Copyright The containerd 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 ipcutil + +import "fmt" + +// makeShareableDevshm returns devshm directory path on host when there is no error. +func makeShareableDevshm(shmPath, shmSize string) error { + return fmt.Errorf("unix does not support shareable devshm") +} + +// cleanUpPlatformSpecificIPC cleans up platform specific IPC. +func cleanUpPlatformSpecificIPC(ipc IPC) error { + if ipc.Mode == Shareable { + return fmt.Errorf("unix does not support shareable devshm") + } + return nil +} diff --git a/pkg/ipcutil/ipcutil_windows.go b/pkg/ipcutil/ipcutil_windows.go new file mode 100644 index 00000000000..5e9d4aa391d --- /dev/null +++ b/pkg/ipcutil/ipcutil_windows.go @@ -0,0 +1,32 @@ +/* + Copyright The containerd 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 ipcutil + +import "fmt" + +// makeShareableDevshm returns devshm directory path on host when there is no error. +func makeShareableDevshm(shmPath, shmSize string) error { + return fmt.Errorf("windows does not support shareable devshm") +} + +// cleanUpPlatformSpecificIPC cleans up platform specific IPC. +func cleanUpPlatformSpecificIPC(ipc IPC) error { + if ipc.Mode == Shareable { + return fmt.Errorf("windows does not support shareable devshm") + } + return nil +} diff --git a/pkg/labels/labels.go b/pkg/labels/labels.go index fc0b5a574dd..40e82971fd4 100644 --- a/pkg/labels/labels.go +++ b/pkg/labels/labels.go @@ -92,6 +92,10 @@ const ( // PIDContainer is the `nerdctl run --pid` for restarting PIDContainer = Prefix + "pid-container" + // IPC is the `nerectl run --ipc` for restrating + // IPC indicates ipc victim container. + IPC = Prefix + "ipc" + // Error encapsulates a container human-readable string // that describes container error. Error = Prefix + "error"