github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/container/container_unix.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package container // import "github.com/docker/docker/container"
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"syscall"
    10  
    11  	"github.com/containerd/continuity/fs"
    12  	"github.com/docker/docker/api/types"
    13  	containertypes "github.com/docker/docker/api/types/container"
    14  	mounttypes "github.com/docker/docker/api/types/mount"
    15  	swarmtypes "github.com/docker/docker/api/types/swarm"
    16  	"github.com/docker/docker/pkg/stringid"
    17  	"github.com/docker/docker/volume"
    18  	volumemounts "github.com/docker/docker/volume/mounts"
    19  	"github.com/moby/sys/mount"
    20  	"github.com/opencontainers/selinux/go-selinux/label"
    21  	"github.com/pkg/errors"
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  const (
    26  	// DefaultStopTimeout sets the default time, in seconds, to wait
    27  	// for the graceful container stop before forcefully terminating it.
    28  	DefaultStopTimeout = 10
    29  
    30  	containerConfigMountPath = "/"
    31  	containerSecretMountPath = "/run/secrets"
    32  )
    33  
    34  // TrySetNetworkMount attempts to set the network mounts given a provided destination and
    35  // the path to use for it; return true if the given destination was a network mount file
    36  func (container *Container) TrySetNetworkMount(destination string, path string) bool {
    37  	if destination == "/etc/resolv.conf" {
    38  		container.ResolvConfPath = path
    39  		return true
    40  	}
    41  	if destination == "/etc/hostname" {
    42  		container.HostnamePath = path
    43  		return true
    44  	}
    45  	if destination == "/etc/hosts" {
    46  		container.HostsPath = path
    47  		return true
    48  	}
    49  
    50  	return false
    51  }
    52  
    53  // BuildHostnameFile writes the container's hostname file.
    54  func (container *Container) BuildHostnameFile() error {
    55  	hostnamePath, err := container.GetRootResourcePath("hostname")
    56  	if err != nil {
    57  		return err
    58  	}
    59  	container.HostnamePath = hostnamePath
    60  	return os.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
    61  }
    62  
    63  // NetworkMounts returns the list of network mounts.
    64  func (container *Container) NetworkMounts() []Mount {
    65  	var mounts []Mount
    66  	shared := container.HostConfig.NetworkMode.IsContainer()
    67  	parser := volumemounts.NewParser(container.OS)
    68  	if container.ResolvConfPath != "" {
    69  		if _, err := os.Stat(container.ResolvConfPath); err != nil {
    70  			logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
    71  		} else {
    72  			writable := !container.HostConfig.ReadonlyRootfs
    73  			if m, exists := container.MountPoints["/etc/resolv.conf"]; exists {
    74  				writable = m.RW
    75  			} else {
    76  				label.Relabel(container.ResolvConfPath, container.MountLabel, shared)
    77  			}
    78  			mounts = append(mounts, Mount{
    79  				Source:      container.ResolvConfPath,
    80  				Destination: "/etc/resolv.conf",
    81  				Writable:    writable,
    82  				Propagation: string(parser.DefaultPropagationMode()),
    83  			})
    84  		}
    85  	}
    86  	if container.HostnamePath != "" {
    87  		if _, err := os.Stat(container.HostnamePath); err != nil {
    88  			logrus.Warnf("HostnamePath set to %q, but can't stat this filename (err = %v); skipping", container.HostnamePath, err)
    89  		} else {
    90  			writable := !container.HostConfig.ReadonlyRootfs
    91  			if m, exists := container.MountPoints["/etc/hostname"]; exists {
    92  				writable = m.RW
    93  			} else {
    94  				label.Relabel(container.HostnamePath, container.MountLabel, shared)
    95  			}
    96  			mounts = append(mounts, Mount{
    97  				Source:      container.HostnamePath,
    98  				Destination: "/etc/hostname",
    99  				Writable:    writable,
   100  				Propagation: string(parser.DefaultPropagationMode()),
   101  			})
   102  		}
   103  	}
   104  	if container.HostsPath != "" {
   105  		if _, err := os.Stat(container.HostsPath); err != nil {
   106  			logrus.Warnf("HostsPath set to %q, but can't stat this filename (err = %v); skipping", container.HostsPath, err)
   107  		} else {
   108  			writable := !container.HostConfig.ReadonlyRootfs
   109  			if m, exists := container.MountPoints["/etc/hosts"]; exists {
   110  				writable = m.RW
   111  			} else {
   112  				label.Relabel(container.HostsPath, container.MountLabel, shared)
   113  			}
   114  			mounts = append(mounts, Mount{
   115  				Source:      container.HostsPath,
   116  				Destination: "/etc/hosts",
   117  				Writable:    writable,
   118  				Propagation: string(parser.DefaultPropagationMode()),
   119  			})
   120  		}
   121  	}
   122  	return mounts
   123  }
   124  
   125  // CopyImagePathContent copies files in destination to the volume.
   126  func (container *Container) CopyImagePathContent(v volume.Volume, destination string) error {
   127  	rootfs, err := container.GetResourcePath(destination)
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	if _, err := os.Stat(rootfs); err != nil {
   133  		if os.IsNotExist(err) {
   134  			return nil
   135  		}
   136  		return err
   137  	}
   138  
   139  	id := stringid.GenerateRandomID()
   140  	path, err := v.Mount(id)
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	defer func() {
   146  		if err := v.Unmount(id); err != nil {
   147  			logrus.Warnf("error while unmounting volume %s: %v", v.Name(), err)
   148  		}
   149  	}()
   150  	if err := label.Relabel(path, container.MountLabel, true); err != nil && !errors.Is(err, syscall.ENOTSUP) {
   151  		return err
   152  	}
   153  	return copyExistingContents(rootfs, path)
   154  }
   155  
   156  // ShmResourcePath returns path to shm
   157  func (container *Container) ShmResourcePath() (string, error) {
   158  	return container.MountsResourcePath("shm")
   159  }
   160  
   161  // HasMountFor checks if path is a mountpoint
   162  func (container *Container) HasMountFor(path string) bool {
   163  	_, exists := container.MountPoints[path]
   164  	if exists {
   165  		return true
   166  	}
   167  
   168  	// Also search among the tmpfs mounts
   169  	for dest := range container.HostConfig.Tmpfs {
   170  		if dest == path {
   171  			return true
   172  		}
   173  	}
   174  
   175  	return false
   176  }
   177  
   178  // UnmountIpcMount unmounts shm if it was mounted
   179  func (container *Container) UnmountIpcMount() error {
   180  	if container.HasMountFor("/dev/shm") {
   181  		return nil
   182  	}
   183  
   184  	// container.ShmPath should not be used here as it may point
   185  	// to the host's or other container's /dev/shm
   186  	shmPath, err := container.ShmResourcePath()
   187  	if err != nil {
   188  		return err
   189  	}
   190  	if shmPath == "" {
   191  		return nil
   192  	}
   193  	if err = mount.Unmount(shmPath); err != nil && !errors.Is(err, os.ErrNotExist) {
   194  		return err
   195  	}
   196  	return nil
   197  }
   198  
   199  // IpcMounts returns the list of IPC mounts
   200  func (container *Container) IpcMounts() []Mount {
   201  	var mounts []Mount
   202  	parser := volumemounts.NewParser(container.OS)
   203  
   204  	if container.HasMountFor("/dev/shm") {
   205  		return mounts
   206  	}
   207  	if container.ShmPath == "" {
   208  		return mounts
   209  	}
   210  
   211  	label.SetFileLabel(container.ShmPath, container.MountLabel)
   212  	mounts = append(mounts, Mount{
   213  		Source:      container.ShmPath,
   214  		Destination: "/dev/shm",
   215  		Writable:    true,
   216  		Propagation: string(parser.DefaultPropagationMode()),
   217  	})
   218  
   219  	return mounts
   220  }
   221  
   222  // SecretMounts returns the mounts for the secret path.
   223  func (container *Container) SecretMounts() ([]Mount, error) {
   224  	var mounts []Mount
   225  	for _, r := range container.SecretReferences {
   226  		if r.File == nil {
   227  			continue
   228  		}
   229  		src, err := container.SecretFilePath(*r)
   230  		if err != nil {
   231  			return nil, err
   232  		}
   233  		mounts = append(mounts, Mount{
   234  			Source:      src,
   235  			Destination: getSecretTargetPath(r),
   236  			Writable:    false,
   237  		})
   238  	}
   239  	for _, r := range container.ConfigReferences {
   240  		fPath, err := container.ConfigFilePath(*r)
   241  		if err != nil {
   242  			return nil, err
   243  		}
   244  		mounts = append(mounts, Mount{
   245  			Source:      fPath,
   246  			Destination: getConfigTargetPath(r),
   247  			Writable:    false,
   248  		})
   249  	}
   250  
   251  	return mounts, nil
   252  }
   253  
   254  // UnmountSecrets unmounts the local tmpfs for secrets
   255  func (container *Container) UnmountSecrets() error {
   256  	p, err := container.SecretMountPath()
   257  	if err != nil {
   258  		return err
   259  	}
   260  	if _, err := os.Stat(p); err != nil {
   261  		if os.IsNotExist(err) {
   262  			return nil
   263  		}
   264  		return err
   265  	}
   266  
   267  	return mount.RecursiveUnmount(p)
   268  }
   269  
   270  type conflictingUpdateOptions string
   271  
   272  func (e conflictingUpdateOptions) Error() string {
   273  	return string(e)
   274  }
   275  
   276  func (e conflictingUpdateOptions) Conflict() {}
   277  
   278  // UpdateContainer updates configuration of a container. Callers must hold a Lock on the Container.
   279  func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
   280  	// update resources of container
   281  	resources := hostConfig.Resources
   282  	cResources := &container.HostConfig.Resources
   283  
   284  	// validate NanoCPUs, CPUPeriod, and CPUQuota
   285  	// Because NanoCPU effectively updates CPUPeriod/CPUQuota,
   286  	// once NanoCPU is already set, updating CPUPeriod/CPUQuota will be blocked, and vice versa.
   287  	// In the following we make sure the intended update (resources) does not conflict with the existing (cResource).
   288  	if resources.NanoCPUs > 0 && cResources.CPUPeriod > 0 {
   289  		return conflictingUpdateOptions("Conflicting options: Nano CPUs cannot be updated as CPU Period has already been set")
   290  	}
   291  	if resources.NanoCPUs > 0 && cResources.CPUQuota > 0 {
   292  		return conflictingUpdateOptions("Conflicting options: Nano CPUs cannot be updated as CPU Quota has already been set")
   293  	}
   294  	if resources.CPUPeriod > 0 && cResources.NanoCPUs > 0 {
   295  		return conflictingUpdateOptions("Conflicting options: CPU Period cannot be updated as NanoCPUs has already been set")
   296  	}
   297  	if resources.CPUQuota > 0 && cResources.NanoCPUs > 0 {
   298  		return conflictingUpdateOptions("Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set")
   299  	}
   300  
   301  	if resources.BlkioWeight != 0 {
   302  		cResources.BlkioWeight = resources.BlkioWeight
   303  	}
   304  	if resources.CPUShares != 0 {
   305  		cResources.CPUShares = resources.CPUShares
   306  	}
   307  	if resources.NanoCPUs != 0 {
   308  		cResources.NanoCPUs = resources.NanoCPUs
   309  	}
   310  	if resources.CPUPeriod != 0 {
   311  		cResources.CPUPeriod = resources.CPUPeriod
   312  	}
   313  	if resources.CPUQuota != 0 {
   314  		cResources.CPUQuota = resources.CPUQuota
   315  	}
   316  	if resources.CpusetCpus != "" {
   317  		cResources.CpusetCpus = resources.CpusetCpus
   318  	}
   319  	if resources.CpusetMems != "" {
   320  		cResources.CpusetMems = resources.CpusetMems
   321  	}
   322  	if resources.Memory != 0 {
   323  		// if memory limit smaller than already set memoryswap limit and doesn't
   324  		// update the memoryswap limit, then error out.
   325  		if resources.Memory > cResources.MemorySwap && resources.MemorySwap == 0 {
   326  			return conflictingUpdateOptions("Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time")
   327  		}
   328  		cResources.Memory = resources.Memory
   329  	}
   330  	if resources.MemorySwap != 0 {
   331  		cResources.MemorySwap = resources.MemorySwap
   332  	}
   333  	if resources.MemoryReservation != 0 {
   334  		cResources.MemoryReservation = resources.MemoryReservation
   335  	}
   336  	if resources.KernelMemory != 0 {
   337  		cResources.KernelMemory = resources.KernelMemory
   338  	}
   339  	if resources.CPURealtimePeriod != 0 {
   340  		cResources.CPURealtimePeriod = resources.CPURealtimePeriod
   341  	}
   342  	if resources.CPURealtimeRuntime != 0 {
   343  		cResources.CPURealtimeRuntime = resources.CPURealtimeRuntime
   344  	}
   345  	if resources.PidsLimit != nil {
   346  		cResources.PidsLimit = resources.PidsLimit
   347  	}
   348  
   349  	// update HostConfig of container
   350  	if hostConfig.RestartPolicy.Name != "" {
   351  		if container.HostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() {
   352  			return conflictingUpdateOptions("Restart policy cannot be updated because AutoRemove is enabled for the container")
   353  		}
   354  		container.HostConfig.RestartPolicy = hostConfig.RestartPolicy
   355  	}
   356  
   357  	return nil
   358  }
   359  
   360  // DetachAndUnmount uses a detached mount on all mount destinations, then
   361  // unmounts each volume normally.
   362  // This is used from daemon/archive for `docker cp`
   363  func (container *Container) DetachAndUnmount(volumeEventLog func(name, action string, attributes map[string]string)) error {
   364  	networkMounts := container.NetworkMounts()
   365  	mountPaths := make([]string, 0, len(container.MountPoints)+len(networkMounts))
   366  
   367  	for _, mntPoint := range container.MountPoints {
   368  		dest, err := container.GetResourcePath(mntPoint.Destination)
   369  		if err != nil {
   370  			logrus.Warnf("Failed to get volume destination path for container '%s' at '%s' while lazily unmounting: %v", container.ID, mntPoint.Destination, err)
   371  			continue
   372  		}
   373  		mountPaths = append(mountPaths, dest)
   374  	}
   375  
   376  	for _, m := range networkMounts {
   377  		dest, err := container.GetResourcePath(m.Destination)
   378  		if err != nil {
   379  			logrus.Warnf("Failed to get volume destination path for container '%s' at '%s' while lazily unmounting: %v", container.ID, m.Destination, err)
   380  			continue
   381  		}
   382  		mountPaths = append(mountPaths, dest)
   383  	}
   384  
   385  	for _, mountPath := range mountPaths {
   386  		if err := mount.Unmount(mountPath); err != nil {
   387  			logrus.WithError(err).WithField("container", container.ID).
   388  				Warn("Unable to unmount")
   389  		}
   390  	}
   391  	return container.UnmountVolumes(volumeEventLog)
   392  }
   393  
   394  // ignoreUnsupportedXAttrs ignores errors when extended attributes
   395  // are not supported
   396  func ignoreUnsupportedXAttrs() fs.CopyDirOpt {
   397  	xeh := func(dst, src, xattrKey string, err error) error {
   398  		if !errors.Is(err, syscall.ENOTSUP) {
   399  			return err
   400  		}
   401  		return nil
   402  	}
   403  	return fs.WithXAttrErrorHandler(xeh)
   404  }
   405  
   406  // copyExistingContents copies from the source to the destination and
   407  // ensures the ownership is appropriately set.
   408  func copyExistingContents(source, destination string) error {
   409  	dstList, err := os.ReadDir(destination)
   410  	if err != nil {
   411  		return err
   412  	}
   413  	if len(dstList) != 0 {
   414  		// destination is not empty, do not copy
   415  		return nil
   416  	}
   417  	return fs.CopyDir(destination, source, ignoreUnsupportedXAttrs())
   418  }
   419  
   420  // TmpfsMounts returns the list of tmpfs mounts
   421  func (container *Container) TmpfsMounts() ([]Mount, error) {
   422  	parser := volumemounts.NewParser(container.OS)
   423  	var mounts []Mount
   424  	for dest, data := range container.HostConfig.Tmpfs {
   425  		mounts = append(mounts, Mount{
   426  			Source:      "tmpfs",
   427  			Destination: dest,
   428  			Data:        data,
   429  		})
   430  	}
   431  	for dest, mnt := range container.MountPoints {
   432  		if mnt.Type == mounttypes.TypeTmpfs {
   433  			data, err := parser.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions, mnt.Spec.ReadOnly)
   434  			if err != nil {
   435  				return nil, err
   436  			}
   437  			mounts = append(mounts, Mount{
   438  				Source:      "tmpfs",
   439  				Destination: dest,
   440  				Data:        data,
   441  			})
   442  		}
   443  	}
   444  	return mounts, nil
   445  }
   446  
   447  // GetMountPoints gives a platform specific transformation to types.MountPoint. Callers must hold a Container lock.
   448  func (container *Container) GetMountPoints() []types.MountPoint {
   449  	mountPoints := make([]types.MountPoint, 0, len(container.MountPoints))
   450  	for _, m := range container.MountPoints {
   451  		mountPoints = append(mountPoints, types.MountPoint{
   452  			Type:        m.Type,
   453  			Name:        m.Name,
   454  			Source:      m.Path(),
   455  			Destination: m.Destination,
   456  			Driver:      m.Driver,
   457  			Mode:        m.Mode,
   458  			RW:          m.RW,
   459  			Propagation: m.Propagation,
   460  		})
   461  	}
   462  	return mountPoints
   463  }
   464  
   465  // ConfigFilePath returns the path to the on-disk location of a config.
   466  // On unix, configs are always considered secret
   467  func (container *Container) ConfigFilePath(configRef swarmtypes.ConfigReference) (string, error) {
   468  	mounts, err := container.SecretMountPath()
   469  	if err != nil {
   470  		return "", err
   471  	}
   472  	return filepath.Join(mounts, configRef.ConfigID), nil
   473  }