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 }