github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/container/container_unix.go (about)

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