github.com/rish1988/moby@v25.0.2+incompatible/container/container_unix.go (about)

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