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