github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/create.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package container
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"net/url"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"runtime"
    29  	"strconv"
    30  	"strings"
    31  
    32  	"github.com/containerd/containerd"
    33  	"github.com/containerd/containerd/cio"
    34  	"github.com/containerd/containerd/containers"
    35  	"github.com/containerd/containerd/oci"
    36  	gocni "github.com/containerd/go-cni"
    37  	"github.com/containerd/log"
    38  	"github.com/containerd/nerdctl/v2/pkg/annotations"
    39  	"github.com/containerd/nerdctl/v2/pkg/api/types"
    40  	"github.com/containerd/nerdctl/v2/pkg/clientutil"
    41  	"github.com/containerd/nerdctl/v2/pkg/cmd/image"
    42  	"github.com/containerd/nerdctl/v2/pkg/containerutil"
    43  	"github.com/containerd/nerdctl/v2/pkg/flagutil"
    44  	"github.com/containerd/nerdctl/v2/pkg/idgen"
    45  	"github.com/containerd/nerdctl/v2/pkg/imgutil"
    46  	"github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat"
    47  	"github.com/containerd/nerdctl/v2/pkg/ipcutil"
    48  	"github.com/containerd/nerdctl/v2/pkg/labels"
    49  	"github.com/containerd/nerdctl/v2/pkg/logging"
    50  	"github.com/containerd/nerdctl/v2/pkg/maputil"
    51  	"github.com/containerd/nerdctl/v2/pkg/mountutil"
    52  	"github.com/containerd/nerdctl/v2/pkg/namestore"
    53  	"github.com/containerd/nerdctl/v2/pkg/platformutil"
    54  	"github.com/containerd/nerdctl/v2/pkg/referenceutil"
    55  	"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
    56  	"github.com/containerd/nerdctl/v2/pkg/strutil"
    57  	dockercliopts "github.com/docker/cli/opts"
    58  	dockeropts "github.com/docker/docker/opts"
    59  	"github.com/opencontainers/runtime-spec/specs-go"
    60  )
    61  
    62  // Create will create a container.
    63  func Create(ctx context.Context, client *containerd.Client, args []string, netManager containerutil.NetworkOptionsManager, options types.ContainerCreateOptions) (containerd.Container, func(), error) {
    64  	// simulate the behavior of double dash
    65  	newArg := []string{}
    66  	if len(args) >= 2 && args[1] == "--" {
    67  		newArg = append(newArg, args[:1]...)
    68  		newArg = append(newArg, args[2:]...)
    69  		args = newArg
    70  	}
    71  	var internalLabels internalLabels
    72  	internalLabels.platform = options.Platform
    73  	internalLabels.namespace = options.GOptions.Namespace
    74  
    75  	var (
    76  		id    = idgen.GenerateID()
    77  		opts  []oci.SpecOpts
    78  		cOpts []containerd.NewContainerOpts
    79  	)
    80  
    81  	if options.CidFile != "" {
    82  		if err := writeCIDFile(options.CidFile, id); err != nil {
    83  			return nil, nil, err
    84  		}
    85  	}
    86  	dataStore, err := clientutil.DataStore(options.GOptions.DataRoot, options.GOptions.Address)
    87  	if err != nil {
    88  		return nil, nil, err
    89  	}
    90  
    91  	internalLabels.stateDir, err = containerutil.ContainerStateDirPath(options.GOptions.Namespace, dataStore, id)
    92  	if err != nil {
    93  		return nil, nil, err
    94  	}
    95  	if err := os.MkdirAll(internalLabels.stateDir, 0700); err != nil {
    96  		return nil, nil, err
    97  	}
    98  
    99  	opts = append(opts,
   100  		oci.WithDefaultSpec(),
   101  	)
   102  
   103  	platformOpts, err := setPlatformOptions(ctx, client, id, netManager.NetworkOptions().UTSNamespace, &internalLabels, options)
   104  	if err != nil {
   105  		return nil, nil, err
   106  	}
   107  	opts = append(opts, platformOpts...)
   108  
   109  	var ensuredImage *imgutil.EnsuredImage
   110  	if !options.Rootfs {
   111  		var platformSS []string // len: 0 or 1
   112  		if options.Platform != "" {
   113  			platformSS = append(platformSS, options.Platform)
   114  		}
   115  		ocispecPlatforms, err := platformutil.NewOCISpecPlatformSlice(false, platformSS)
   116  		if err != nil {
   117  			return nil, nil, err
   118  		}
   119  		rawRef := args[0]
   120  
   121  		ensuredImage, err = image.EnsureImage(ctx, client, rawRef, ocispecPlatforms, options.Pull, nil, false, options.ImagePullOpt)
   122  		if err != nil {
   123  			return nil, nil, err
   124  		}
   125  	}
   126  
   127  	rootfsOpts, rootfsCOpts, err := generateRootfsOpts(args, id, ensuredImage, options)
   128  	if err != nil {
   129  		return nil, nil, err
   130  	}
   131  	opts = append(opts, rootfsOpts...)
   132  	cOpts = append(cOpts, rootfsCOpts...)
   133  
   134  	if options.Workdir != "" {
   135  		opts = append(opts, oci.WithProcessCwd(options.Workdir))
   136  	}
   137  
   138  	envs, err := flagutil.MergeEnvFileAndOSEnv(options.EnvFile, options.Env)
   139  	if err != nil {
   140  		return nil, nil, err
   141  	}
   142  	opts = append(opts, oci.WithEnv(envs))
   143  
   144  	if options.Interactive {
   145  		if options.Detach {
   146  			return nil, nil, errors.New("currently flag -i and -d cannot be specified together (FIXME)")
   147  		}
   148  	}
   149  
   150  	if options.TTY {
   151  		opts = append(opts, oci.WithTTY)
   152  	}
   153  
   154  	var mountOpts []oci.SpecOpts
   155  	mountOpts, internalLabels.anonVolumes, internalLabels.mountPoints, err = generateMountOpts(ctx, client, ensuredImage, options)
   156  	if err != nil {
   157  		return nil, nil, err
   158  	}
   159  	opts = append(opts, mountOpts...)
   160  
   161  	// Always set internalLabels.logURI
   162  	// to support restart the container that run with "-it", like
   163  	//
   164  	// 1, nerdctl run --name demo -it imagename
   165  	// 2, ctrl + c to stop demo container
   166  	// 3, nerdctl start/restart demo
   167  	logConfig, err := generateLogConfig(dataStore, id, options.LogDriver, options.LogOpt, options.GOptions.Namespace)
   168  	if err != nil {
   169  		return nil, nil, err
   170  	}
   171  	internalLabels.logURI = logConfig.LogURI
   172  
   173  	restartOpts, err := generateRestartOpts(ctx, client, options.Restart, logConfig.LogURI, options.InRun)
   174  	if err != nil {
   175  		return nil, nil, err
   176  	}
   177  	cOpts = append(cOpts, restartOpts...)
   178  
   179  	if err = netManager.VerifyNetworkOptions(ctx); err != nil {
   180  		return nil, nil, fmt.Errorf("failed to verify networking settings: %s", err)
   181  	}
   182  
   183  	netOpts, netNewContainerOpts, err := netManager.ContainerNetworkingOpts(ctx, id)
   184  	if err != nil {
   185  		return nil, nil, fmt.Errorf("failed to generate networking spec options: %s", err)
   186  	}
   187  	opts = append(opts, netOpts...)
   188  	cOpts = append(cOpts, netNewContainerOpts...)
   189  
   190  	netLabelOpts, err := netManager.InternalNetworkingOptionLabels(ctx)
   191  	if err != nil {
   192  		return nil, nil, fmt.Errorf("failed to generate internal networking labels: %s", err)
   193  	}
   194  	// TODO(aznashwan): more formal way to load net opts into internalLabels:
   195  	internalLabels.hostname = netLabelOpts.Hostname
   196  	internalLabels.ports = netLabelOpts.PortMappings
   197  	internalLabels.ipAddress = netLabelOpts.IPAddress
   198  	internalLabels.ip6Address = netLabelOpts.IP6Address
   199  	internalLabels.networks = netLabelOpts.NetworkSlice
   200  	internalLabels.macAddress = netLabelOpts.MACAddress
   201  
   202  	// NOTE: OCI hooks are currently not supported on Windows so we skip setting them altogether.
   203  	// The OCI hooks we define (whose logic can be found in pkg/ocihook) primarily
   204  	// perform network setup and teardown when using CNI networking.
   205  	// On Windows, we are forced to set up and tear down the networking from within nerdctl.
   206  	if runtime.GOOS != "windows" {
   207  		hookOpt, err := withNerdctlOCIHook(options.NerdctlCmd, options.NerdctlArgs)
   208  		if err != nil {
   209  			return nil, nil, err
   210  		}
   211  		opts = append(opts, hookOpt)
   212  	}
   213  
   214  	uOpts, err := generateUserOpts(options.User)
   215  	if err != nil {
   216  		return nil, nil, err
   217  	}
   218  	opts = append(opts, uOpts...)
   219  	gOpts, err := generateGroupsOpts(options.GroupAdd)
   220  	if err != nil {
   221  		return nil, nil, err
   222  	}
   223  	opts = append(opts, gOpts...)
   224  
   225  	umaskOpts, err := generateUmaskOpts(options.Umask)
   226  	if err != nil {
   227  		return nil, nil, err
   228  	}
   229  	opts = append(opts, umaskOpts...)
   230  
   231  	rtCOpts, err := generateRuntimeCOpts(options.GOptions.CgroupManager, options.Runtime)
   232  	if err != nil {
   233  		return nil, nil, err
   234  	}
   235  	cOpts = append(cOpts, rtCOpts...)
   236  
   237  	lCOpts, err := withContainerLabels(options.Label, options.LabelFile)
   238  	if err != nil {
   239  		return nil, nil, err
   240  	}
   241  	cOpts = append(cOpts, lCOpts...)
   242  
   243  	var containerNameStore namestore.NameStore
   244  	if options.Name == "" && !options.NameChanged {
   245  		// Automatically set the container name, unless `--name=""` was explicitly specified.
   246  		var imageRef string
   247  		if ensuredImage != nil {
   248  			imageRef = ensuredImage.Ref
   249  		}
   250  		options.Name = referenceutil.SuggestContainerName(imageRef, id)
   251  	}
   252  	if options.Name != "" {
   253  		containerNameStore, err = namestore.New(dataStore, options.GOptions.Namespace)
   254  		if err != nil {
   255  			return nil, nil, err
   256  		}
   257  		if err := containerNameStore.Acquire(options.Name, id); err != nil {
   258  			return nil, nil, err
   259  		}
   260  	}
   261  	internalLabels.name = options.Name
   262  	internalLabels.pidFile = options.PidFile
   263  	internalLabels.extraHosts = strutil.DedupeStrSlice(netManager.NetworkOptions().AddHost)
   264  	for i, host := range internalLabels.extraHosts {
   265  		if _, err := dockercliopts.ValidateExtraHost(host); err != nil {
   266  			return nil, nil, err
   267  		}
   268  		parts := strings.SplitN(host, ":", 2)
   269  		// If the IP Address is a string called "host-gateway", replace this value with the IP address stored
   270  		// in the daemon level HostGateway IP config variable.
   271  		if parts[1] == dockeropts.HostGatewayName {
   272  			if options.GOptions.HostGatewayIP == "" {
   273  				return nil, nil, fmt.Errorf("unable to derive the IP value for host-gateway")
   274  			}
   275  			parts[1] = options.GOptions.HostGatewayIP
   276  			internalLabels.extraHosts[i] = fmt.Sprintf(`%s:%s`, parts[0], parts[1])
   277  		}
   278  	}
   279  
   280  	// TODO: abolish internal labels and only use annotations
   281  	ilOpt, err := withInternalLabels(internalLabels)
   282  	if err != nil {
   283  		return nil, nil, err
   284  	}
   285  	cOpts = append(cOpts, ilOpt)
   286  
   287  	opts = append(opts, propagateInternalContainerdLabelsToOCIAnnotations(),
   288  		oci.WithAnnotations(strutil.ConvertKVStringsToMap(options.Annotations)))
   289  
   290  	var s specs.Spec
   291  	spec := containerd.WithSpec(&s, opts...)
   292  
   293  	cOpts = append(cOpts, spec)
   294  
   295  	c, containerErr := client.NewContainer(ctx, id, cOpts...)
   296  	var netSetupErr error
   297  	// NOTE: on non-Windows platforms, network setup is performed by OCI hooks.
   298  	// Seeing as though Windows does not currently support OCI hooks, we must explicitly
   299  	// perform network setup/teardown in the main nerdctl executable.
   300  	if containerErr == nil && runtime.GOOS == "windows" {
   301  		netSetupErr = netManager.SetupNetworking(ctx, id)
   302  		if netSetupErr != nil {
   303  			log.G(ctx).WithError(netSetupErr).Warnf("networking setup error has occurred")
   304  		}
   305  	}
   306  
   307  	if containerErr != nil || netSetupErr != nil {
   308  		returnedError := containerErr
   309  		if netSetupErr != nil {
   310  			returnedError = netSetupErr // mutually exclusive
   311  		}
   312  		return nil, generateGcFunc(ctx, c, options.GOptions.Namespace, id, options.Name, dataStore, containerErr, containerNameStore, netManager, internalLabels), returnedError
   313  	}
   314  
   315  	return c, nil, nil
   316  }
   317  
   318  func generateRootfsOpts(args []string, id string, ensured *imgutil.EnsuredImage, options types.ContainerCreateOptions) (opts []oci.SpecOpts, cOpts []containerd.NewContainerOpts, err error) {
   319  	if !options.Rootfs {
   320  		cOpts = append(cOpts,
   321  			containerd.WithImage(ensured.Image),
   322  			containerd.WithSnapshotter(ensured.Snapshotter),
   323  			containerd.WithNewSnapshot(id, ensured.Image),
   324  			containerd.WithImageStopSignal(ensured.Image, "SIGTERM"),
   325  		)
   326  
   327  		if len(ensured.ImageConfig.Env) == 0 {
   328  			opts = append(opts, oci.WithDefaultPathEnv)
   329  		}
   330  		for ind, env := range ensured.ImageConfig.Env {
   331  			if strings.HasPrefix(env, "PATH=") {
   332  				break
   333  			}
   334  			if ind == len(ensured.ImageConfig.Env)-1 {
   335  				opts = append(opts, oci.WithDefaultPathEnv)
   336  			}
   337  		}
   338  	} else {
   339  		absRootfs, err := filepath.Abs(args[0])
   340  		if err != nil {
   341  			return nil, nil, err
   342  		}
   343  		opts = append(opts, oci.WithRootFSPath(absRootfs), oci.WithDefaultPathEnv)
   344  	}
   345  
   346  	entrypointPath := ""
   347  	if ensured != nil {
   348  		if len(ensured.ImageConfig.Entrypoint) > 0 {
   349  			entrypointPath = ensured.ImageConfig.Entrypoint[0]
   350  		} else if len(ensured.ImageConfig.Cmd) > 0 {
   351  			entrypointPath = ensured.ImageConfig.Cmd[0]
   352  		}
   353  	}
   354  
   355  	if !options.Rootfs && !options.EntrypointChanged {
   356  		opts = append(opts, oci.WithImageConfigArgs(ensured.Image, args[1:]))
   357  	} else {
   358  		if !options.Rootfs {
   359  			opts = append(opts, oci.WithImageConfig(ensured.Image))
   360  		}
   361  		var processArgs []string
   362  		if len(options.Entrypoint) != 0 {
   363  			processArgs = append(processArgs, options.Entrypoint...)
   364  		}
   365  		if len(args) > 1 {
   366  			processArgs = append(processArgs, args[1:]...)
   367  		}
   368  		if len(processArgs) == 0 {
   369  			// error message is from Podman
   370  			return nil, nil, errors.New("no command or entrypoint provided, and no CMD or ENTRYPOINT from image")
   371  		}
   372  
   373  		entrypointPath = processArgs[0]
   374  
   375  		opts = append(opts, oci.WithProcessArgs(processArgs...))
   376  	}
   377  
   378  	isEntryPointSystemd := (entrypointPath == "/sbin/init" ||
   379  		entrypointPath == "/usr/sbin/init" ||
   380  		entrypointPath == "/usr/local/sbin/init")
   381  
   382  	stopSignal := options.StopSignal
   383  
   384  	if options.Systemd == "always" || (options.Systemd == "true" && isEntryPointSystemd) {
   385  		if options.Privileged {
   386  			securityOptsMap := strutil.ConvertKVStringsToMap(strutil.DedupeStrSlice(options.SecurityOpt))
   387  			privilegedWithoutHostDevices, err := maputil.MapBoolValueAsOpt(securityOptsMap, "privileged-without-host-devices")
   388  			if err != nil {
   389  				return nil, nil, err
   390  			}
   391  
   392  			// See: https://github.com/containers/podman/issues/15878
   393  			if !privilegedWithoutHostDevices {
   394  				return nil, nil, errors.New("if --privileged is used with systemd `--security-opt privileged-without-host-devices` must also be used")
   395  			}
   396  		}
   397  
   398  		opts = append(opts,
   399  			oci.WithoutMounts("/sys/fs/cgroup"),
   400  			oci.WithMounts([]specs.Mount{
   401  				{Type: "cgroup", Source: "cgroup", Destination: "/sys/fs/cgroup", Options: []string{"rw"}},
   402  				{Type: "tmpfs", Source: "tmpfs", Destination: "/run"},
   403  				{Type: "tmpfs", Source: "tmpfs", Destination: "/run/lock"},
   404  				{Type: "tmpfs", Source: "tmpfs", Destination: "/tmp"},
   405  				{Type: "tmpfs", Source: "tmpfs", Destination: "/var/lib/journal"},
   406  			}),
   407  		)
   408  		stopSignal = "SIGRTMIN+3"
   409  	}
   410  
   411  	cOpts = append(cOpts, withStop(stopSignal, options.StopTimeout, ensured))
   412  
   413  	if options.InitBinary != nil {
   414  		options.InitProcessFlag = true
   415  	}
   416  	if options.InitProcessFlag {
   417  		binaryPath, err := exec.LookPath(*options.InitBinary)
   418  		if err != nil {
   419  			if errors.Is(err, exec.ErrNotFound) {
   420  				return nil, nil, fmt.Errorf(`init binary %q not found`, *options.InitBinary)
   421  			}
   422  			return nil, nil, err
   423  		}
   424  		inContainerPath := filepath.Join("/sbin", filepath.Base(*options.InitBinary))
   425  		opts = append(opts, func(_ context.Context, _ oci.Client, _ *containers.Container, spec *oci.Spec) error {
   426  			spec.Process.Args = append([]string{inContainerPath, "--"}, spec.Process.Args...)
   427  			spec.Mounts = append([]specs.Mount{{
   428  				Destination: inContainerPath,
   429  				Type:        "bind",
   430  				Source:      binaryPath,
   431  				Options:     []string{"bind", "ro"},
   432  			}}, spec.Mounts...)
   433  			return nil
   434  		})
   435  	}
   436  	if options.ReadOnly {
   437  		opts = append(opts, oci.WithRootFSReadonly())
   438  	}
   439  	return opts, cOpts, nil
   440  }
   441  
   442  // GenerateLogURI generates a log URI for the current container store
   443  func GenerateLogURI(dataStore string) (*url.URL, error) {
   444  	selfExe, err := os.Executable()
   445  	if err != nil {
   446  		return nil, err
   447  	}
   448  	args := map[string]string{
   449  		logging.MagicArgv1: dataStore,
   450  	}
   451  
   452  	return cio.LogURIGenerator("binary", selfExe, args)
   453  }
   454  
   455  func withNerdctlOCIHook(cmd string, args []string) (oci.SpecOpts, error) {
   456  	if rootlessutil.IsRootless() {
   457  		detachedNetNS, err := rootlessutil.DetachedNetNS()
   458  		if err != nil {
   459  			return nil, fmt.Errorf("failed to check whether RootlessKit is running with --detach-netns: %w", err)
   460  		}
   461  		if detachedNetNS != "" {
   462  			// Rewrite {cmd, args} if RootlessKit is running with --detach-netns, so that the hook can gain
   463  			// CAP_NET_ADMIN in the namespaces.
   464  			//   - Old:
   465  			//     - cmd:  "/usr/local/bin/nerdctl"
   466  			//     - args: {"--data-root=/foo", "internal", "oci-hook"}
   467  			//   - New:
   468  			//     - cmd:  "/usr/bin/nsenter"
   469  			//     - args: {"-n/run/user/1000/containerd-rootless/netns", "-F", "--", "/usr/local/bin/nerdctl", "--data-root=/foo", "internal", "oci-hook"}
   470  			oldCmd, oldArgs := cmd, args
   471  			cmd, err = exec.LookPath("nsenter")
   472  			if err != nil {
   473  				return nil, err
   474  			}
   475  			args = append([]string{"-n" + detachedNetNS, "-F", "--", oldCmd}, oldArgs...)
   476  		}
   477  	}
   478  
   479  	args = append([]string{cmd}, append(args, "internal", "oci-hook")...)
   480  	return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
   481  		if s.Hooks == nil {
   482  			s.Hooks = &specs.Hooks{}
   483  		}
   484  		crArgs := append(args, "createRuntime")
   485  		s.Hooks.CreateRuntime = append(s.Hooks.CreateRuntime, specs.Hook{
   486  			Path: cmd,
   487  			Args: crArgs,
   488  			Env:  os.Environ(),
   489  		})
   490  		scArgs := append(args, "startContainer")
   491  		s.Hooks.CreateRuntime = append(s.Hooks.StartContainer, specs.Hook{
   492  			Path: cmd,
   493  			Args: scArgs,
   494  			Env:  os.Environ(),
   495  		})
   496  		argsCopy := append([]string(nil), args...)
   497  		psArgs := append(argsCopy, "postStop")
   498  		s.Hooks.Poststop = append(s.Hooks.Poststop, specs.Hook{
   499  			Path: cmd,
   500  			Args: psArgs,
   501  			Env:  os.Environ(),
   502  		})
   503  		return nil
   504  	}, nil
   505  }
   506  
   507  func withContainerLabels(label, labelFile []string) ([]containerd.NewContainerOpts, error) {
   508  	labelMap, err := readKVStringsMapfFromLabel(label, labelFile)
   509  	if err != nil {
   510  		return nil, err
   511  	}
   512  	for k := range labelMap {
   513  		if strings.HasPrefix(k, annotations.Bypass4netns) {
   514  			log.L.Warnf("Label %q is deprecated, use an annotation instead", k)
   515  		} else if strings.HasPrefix(k, labels.Prefix) {
   516  			return nil, fmt.Errorf("internal label %q must not be specified manually", k)
   517  		}
   518  	}
   519  	o := containerd.WithAdditionalContainerLabels(labelMap)
   520  	return []containerd.NewContainerOpts{o}, nil
   521  }
   522  
   523  func readKVStringsMapfFromLabel(label, labelFile []string) (map[string]string, error) {
   524  	labelsMap := strutil.DedupeStrSlice(label)
   525  	labelsFilePath := strutil.DedupeStrSlice(labelFile)
   526  	kvStrings, err := dockercliopts.ReadKVStrings(labelsFilePath, labelsMap)
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  	return strutil.ConvertKVStringsToMap(kvStrings), nil
   531  }
   532  
   533  // parseKVStringsMapFromLogOpt parse log options KV entries and convert to Map
   534  func parseKVStringsMapFromLogOpt(logOpt []string, logDriver string) (map[string]string, error) {
   535  	logOptArray := strutil.DedupeStrSlice(logOpt)
   536  	logOptMap := strutil.ConvertKVStringsToMap(logOptArray)
   537  	if logDriver == "json-file" {
   538  		if _, ok := logOptMap[logging.MaxSize]; !ok {
   539  			delete(logOptMap, logging.MaxFile)
   540  		}
   541  	}
   542  	if err := logging.ValidateLogOpts(logDriver, logOptMap); err != nil {
   543  		return nil, err
   544  	}
   545  	return logOptMap, nil
   546  }
   547  
   548  func withStop(stopSignal string, stopTimeout int, ensuredImage *imgutil.EnsuredImage) containerd.NewContainerOpts {
   549  	return func(ctx context.Context, _ *containerd.Client, c *containers.Container) error {
   550  		if c.Labels == nil {
   551  			c.Labels = make(map[string]string)
   552  		}
   553  		var err error
   554  		if ensuredImage != nil {
   555  			stopSignal, err = containerd.GetOCIStopSignal(ctx, ensuredImage.Image, stopSignal)
   556  			if err != nil {
   557  				return err
   558  			}
   559  		}
   560  		c.Labels[containerd.StopSignalLabel] = stopSignal
   561  		if stopTimeout != 0 {
   562  			c.Labels[labels.StopTimeout] = strconv.Itoa(stopTimeout)
   563  		}
   564  		return nil
   565  	}
   566  }
   567  
   568  type internalLabels struct {
   569  	// labels from cmd options
   570  	namespace  string
   571  	platform   string
   572  	extraHosts []string
   573  	pidFile    string
   574  	// labels from cmd options or automatically set
   575  	name     string
   576  	hostname string
   577  	// automatically generated
   578  	stateDir string
   579  	// network
   580  	networks   []string
   581  	ipAddress  string
   582  	ip6Address string
   583  	ports      []gocni.PortMapping
   584  	macAddress string
   585  	// volume
   586  	mountPoints []*mountutil.Processed
   587  	anonVolumes []string
   588  	// pid namespace
   589  	pidContainer string
   590  	// ipc namespace & dev/shm
   591  	ipc string
   592  	// log
   593  	logURI string
   594  }
   595  
   596  // WithInternalLabels sets the internal labels for a container.
   597  func withInternalLabels(internalLabels internalLabels) (containerd.NewContainerOpts, error) {
   598  	m := make(map[string]string)
   599  	m[labels.Namespace] = internalLabels.namespace
   600  	if internalLabels.name != "" {
   601  		m[labels.Name] = internalLabels.name
   602  	}
   603  	m[labels.Hostname] = internalLabels.hostname
   604  	extraHostsJSON, err := json.Marshal(internalLabels.extraHosts)
   605  	if err != nil {
   606  		return nil, err
   607  	}
   608  	m[labels.ExtraHosts] = string(extraHostsJSON)
   609  	m[labels.StateDir] = internalLabels.stateDir
   610  	networksJSON, err := json.Marshal(internalLabels.networks)
   611  	if err != nil {
   612  		return nil, err
   613  	}
   614  	m[labels.Networks] = string(networksJSON)
   615  	if len(internalLabels.ports) > 0 {
   616  		portsJSON, err := json.Marshal(internalLabels.ports)
   617  		if err != nil {
   618  			return nil, err
   619  		}
   620  		m[labels.Ports] = string(portsJSON)
   621  	}
   622  	if internalLabels.logURI != "" {
   623  		m[labels.LogURI] = internalLabels.logURI
   624  	}
   625  	if len(internalLabels.anonVolumes) > 0 {
   626  		anonVolumeJSON, err := json.Marshal(internalLabels.anonVolumes)
   627  		if err != nil {
   628  			return nil, err
   629  		}
   630  		m[labels.AnonymousVolumes] = string(anonVolumeJSON)
   631  	}
   632  
   633  	if internalLabels.pidFile != "" {
   634  		m[labels.PIDFile] = internalLabels.pidFile
   635  	}
   636  
   637  	if internalLabels.ipAddress != "" {
   638  		m[labels.IPAddress] = internalLabels.ipAddress
   639  	}
   640  
   641  	if internalLabels.ip6Address != "" {
   642  		m[labels.IP6Address] = internalLabels.ip6Address
   643  	}
   644  
   645  	m[labels.Platform], err = platformutil.NormalizeString(internalLabels.platform)
   646  	if err != nil {
   647  		return nil, err
   648  	}
   649  
   650  	if len(internalLabels.mountPoints) > 0 {
   651  		mounts := dockercompatMounts(internalLabels.mountPoints)
   652  		mountPointsJSON, err := json.Marshal(mounts)
   653  		if err != nil {
   654  			return nil, err
   655  		}
   656  		m[labels.Mounts] = string(mountPointsJSON)
   657  	}
   658  
   659  	if internalLabels.macAddress != "" {
   660  		m[labels.MACAddress] = internalLabels.macAddress
   661  	}
   662  
   663  	if internalLabels.pidContainer != "" {
   664  		m[labels.PIDContainer] = internalLabels.pidContainer
   665  	}
   666  
   667  	if internalLabels.ipc != "" {
   668  		m[labels.IPC] = internalLabels.ipc
   669  	}
   670  
   671  	return containerd.WithAdditionalContainerLabels(m), nil
   672  }
   673  
   674  func dockercompatMounts(mountPoints []*mountutil.Processed) []dockercompat.MountPoint {
   675  	result := make([]dockercompat.MountPoint, len(mountPoints))
   676  	for i := range mountPoints {
   677  		mp := mountPoints[i]
   678  		result[i] = dockercompat.MountPoint{
   679  			Type:        mp.Type,
   680  			Name:        mp.Name,
   681  			Source:      mp.Mount.Source,
   682  			Destination: mp.Mount.Destination,
   683  			Driver:      "",
   684  			Mode:        mp.Mode,
   685  		}
   686  		result[i].RW, result[i].Propagation = dockercompat.ParseMountProperties(strings.Split(mp.Mode, ","))
   687  
   688  		// it's an anonymous volume
   689  		if mp.AnonymousVolume != "" {
   690  			result[i].Name = mp.AnonymousVolume
   691  		}
   692  
   693  		// volume only support local driver
   694  		if mp.Type == "volume" {
   695  			result[i].Driver = "local"
   696  		}
   697  	}
   698  	return result
   699  }
   700  
   701  func processeds(mountPoints []dockercompat.MountPoint) []*mountutil.Processed {
   702  	result := make([]*mountutil.Processed, len(mountPoints))
   703  	for i := range mountPoints {
   704  		mp := mountPoints[i]
   705  		result[i] = &mountutil.Processed{
   706  			Type: mp.Type,
   707  			Name: mp.Name,
   708  			Mount: specs.Mount{
   709  				Source:      mp.Source,
   710  				Destination: mp.Destination,
   711  			},
   712  			Mode: mp.Mode,
   713  		}
   714  	}
   715  	return result
   716  }
   717  
   718  func propagateInternalContainerdLabelsToOCIAnnotations() oci.SpecOpts {
   719  	return func(ctx context.Context, oc oci.Client, c *containers.Container, s *oci.Spec) error {
   720  		allowed := make(map[string]string)
   721  		for k, v := range c.Labels {
   722  			if strings.Contains(k, labels.Prefix) {
   723  				allowed[k] = v
   724  			}
   725  		}
   726  		return oci.WithAnnotations(allowed)(ctx, oc, c, s)
   727  	}
   728  }
   729  
   730  func writeCIDFile(path, id string) error {
   731  	_, err := os.Stat(path)
   732  	if err == nil {
   733  		return fmt.Errorf("container ID file found, make sure the other container isn't running or delete %s", path)
   734  	} else if errors.Is(err, os.ErrNotExist) {
   735  		f, err := os.Create(path)
   736  		if err != nil {
   737  			return fmt.Errorf("failed to create the container ID file: %s for %s, err: %s", path, id, err)
   738  		}
   739  		defer f.Close()
   740  
   741  		if _, err := f.WriteString(id); err != nil {
   742  			return err
   743  		}
   744  		return nil
   745  	}
   746  	return err
   747  }
   748  
   749  // generateLogConfig creates a LogConfig for the current container store
   750  func generateLogConfig(dataStore string, id string, logDriver string, logOpt []string, ns string) (logConfig logging.LogConfig, err error) {
   751  	var u *url.URL
   752  	if u, err = url.Parse(logDriver); err == nil && u.Scheme != "" {
   753  		logConfig.LogURI = logDriver
   754  	} else {
   755  		logConfig.Driver = logDriver
   756  		logConfig.Opts, err = parseKVStringsMapFromLogOpt(logOpt, logDriver)
   757  		if err != nil {
   758  			return
   759  		}
   760  		var (
   761  			logDriverInst logging.Driver
   762  			logConfigB    []byte
   763  			lu            *url.URL
   764  		)
   765  		logDriverInst, err = logging.GetDriver(logDriver, logConfig.Opts)
   766  		if err != nil {
   767  			return
   768  		}
   769  		if err = logDriverInst.Init(dataStore, ns, id); err != nil {
   770  			return
   771  		}
   772  
   773  		logConfigB, err = json.Marshal(logConfig)
   774  		if err != nil {
   775  			return
   776  		}
   777  
   778  		logConfigFilePath := logging.LogConfigFilePath(dataStore, ns, id)
   779  		if err = os.WriteFile(logConfigFilePath, logConfigB, 0600); err != nil {
   780  			return
   781  		}
   782  
   783  		lu, err = GenerateLogURI(dataStore)
   784  		if err != nil {
   785  			return
   786  		}
   787  		if lu != nil {
   788  			log.L.Debugf("generated log driver: %s", lu.String())
   789  			logConfig.LogURI = lu.String()
   790  		}
   791  	}
   792  	return logConfig, nil
   793  }
   794  
   795  func generateGcFunc(ctx context.Context, container containerd.Container, ns, id, name, dataStore string, containerErr error, containerNameStore namestore.NameStore, netManager containerutil.NetworkOptionsManager, internalLabels internalLabels) func() {
   796  	return func() {
   797  		if containerErr == nil {
   798  			netGcErr := netManager.CleanupNetworking(ctx, container)
   799  			if netGcErr != nil {
   800  				log.G(ctx).WithError(netGcErr).Warnf("failed to revert container %q networking settings", id)
   801  			}
   802  		}
   803  
   804  		ipc, ipcErr := ipcutil.DecodeIPCLabel(internalLabels.ipc)
   805  		if ipcErr != nil {
   806  			log.G(ctx).WithError(ipcErr).Warnf("failed to decode ipc label for container %q", id)
   807  		}
   808  		if ipcErr := ipcutil.CleanUp(ipc); ipcErr != nil {
   809  			log.G(ctx).WithError(ipcErr).Warnf("failed to clean up ipc for container %q", id)
   810  		}
   811  		if rmErr := os.RemoveAll(internalLabels.stateDir); rmErr != nil {
   812  			log.G(ctx).WithError(rmErr).Warnf("failed to remove container %q state dir %q", id, internalLabels.stateDir)
   813  		}
   814  
   815  		if name != "" {
   816  			var errE error
   817  			if containerNameStore, errE = namestore.New(dataStore, ns); errE != nil {
   818  				log.G(ctx).WithError(errE).Warnf("failed to instantiate container name store during cleanup for container %q", id)
   819  			}
   820  			if errE = containerNameStore.Release(name, id); errE != nil {
   821  				log.G(ctx).WithError(errE).Warnf("failed to release container name store for container %q (%s)", name, id)
   822  			}
   823  		}
   824  	}
   825  }