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