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 }