github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/container/container.go (about) 1 package container 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "sync" 10 "syscall" 11 "time" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/docker/daemon/exec" 15 "github.com/docker/docker/daemon/execdriver" 16 "github.com/docker/docker/daemon/logger" 17 "github.com/docker/docker/daemon/logger/jsonfilelog" 18 "github.com/docker/docker/daemon/network" 19 derr "github.com/docker/docker/errors" 20 "github.com/docker/docker/image" 21 "github.com/docker/docker/layer" 22 "github.com/docker/docker/pkg/nat" 23 "github.com/docker/docker/pkg/promise" 24 "github.com/docker/docker/pkg/signal" 25 "github.com/docker/docker/pkg/symlink" 26 "github.com/docker/docker/runconfig" 27 "github.com/docker/docker/volume" 28 "github.com/opencontainers/runc/libcontainer/label" 29 ) 30 31 const configFileName = "config.v2.json" 32 33 // CommonContainer holds the fields for a container which are 34 // applicable across all platforms supported by the daemon. 35 type CommonContainer struct { 36 *runconfig.StreamConfig 37 // embed for Container to support states directly. 38 *State `json:"State"` // Needed for remote api version <= 1.11 39 Root string `json:"-"` // Path to the "home" of the container, including metadata. 40 BaseFS string `json:"-"` // Path to the graphdriver mountpoint 41 RWLayer layer.RWLayer `json:"-"` 42 ID string 43 Created time.Time 44 Path string 45 Args []string 46 Config *runconfig.Config 47 ImageID image.ID `json:"Image"` 48 NetworkSettings *network.Settings 49 LogPath string 50 Name string 51 Driver string 52 // MountLabel contains the options for the 'mount' command 53 MountLabel string 54 ProcessLabel string 55 RestartCount int 56 HasBeenStartedBefore bool 57 HasBeenManuallyStopped bool // used for unless-stopped restart policy 58 MountPoints map[string]*volume.MountPoint 59 HostConfig *runconfig.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable 60 Command *execdriver.Command `json:"-"` 61 monitor *containerMonitor 62 ExecCommands *exec.Store `json:"-"` 63 // logDriver for closing 64 LogDriver logger.Logger `json:"-"` 65 LogCopier *logger.Copier `json:"-"` 66 } 67 68 // NewBaseContainer creates a new container with its 69 // basic configuration. 70 func NewBaseContainer(id, root string) *Container { 71 return &Container{ 72 CommonContainer: CommonContainer{ 73 ID: id, 74 State: NewState(), 75 ExecCommands: exec.NewStore(), 76 Root: root, 77 MountPoints: make(map[string]*volume.MountPoint), 78 StreamConfig: runconfig.NewStreamConfig(), 79 }, 80 } 81 } 82 83 // FromDisk loads the container configuration stored in the host. 84 func (container *Container) FromDisk() error { 85 pth, err := container.ConfigPath() 86 if err != nil { 87 return err 88 } 89 90 jsonSource, err := os.Open(pth) 91 if err != nil { 92 return err 93 } 94 defer jsonSource.Close() 95 96 dec := json.NewDecoder(jsonSource) 97 98 // Load container settings 99 if err := dec.Decode(container); err != nil { 100 return err 101 } 102 103 if err := label.ReserveLabel(container.ProcessLabel); err != nil { 104 return err 105 } 106 return container.readHostConfig() 107 } 108 109 // ToDisk saves the container configuration on disk. 110 func (container *Container) ToDisk() error { 111 pth, err := container.ConfigPath() 112 if err != nil { 113 return err 114 } 115 116 jsonSource, err := os.Create(pth) 117 if err != nil { 118 return err 119 } 120 defer jsonSource.Close() 121 122 enc := json.NewEncoder(jsonSource) 123 124 // Save container settings 125 if err := enc.Encode(container); err != nil { 126 return err 127 } 128 129 return container.WriteHostConfig() 130 } 131 132 // ToDiskLocking saves the container configuration on disk in a thread safe way. 133 func (container *Container) ToDiskLocking() error { 134 container.Lock() 135 err := container.ToDisk() 136 container.Unlock() 137 return err 138 } 139 140 // readHostConfig reads the host configuration from disk for the container. 141 func (container *Container) readHostConfig() error { 142 container.HostConfig = &runconfig.HostConfig{} 143 // If the hostconfig file does not exist, do not read it. 144 // (We still have to initialize container.HostConfig, 145 // but that's OK, since we just did that above.) 146 pth, err := container.HostConfigPath() 147 if err != nil { 148 return err 149 } 150 151 f, err := os.Open(pth) 152 if err != nil { 153 if os.IsNotExist(err) { 154 return nil 155 } 156 return err 157 } 158 defer f.Close() 159 160 if err := json.NewDecoder(f).Decode(&container.HostConfig); err != nil { 161 return err 162 } 163 164 container.InitDNSHostConfig() 165 166 return nil 167 } 168 169 // WriteHostConfig saves the host configuration on disk for the container. 170 func (container *Container) WriteHostConfig() error { 171 pth, err := container.HostConfigPath() 172 if err != nil { 173 return err 174 } 175 176 f, err := os.Create(pth) 177 if err != nil { 178 return err 179 } 180 defer f.Close() 181 182 return json.NewEncoder(f).Encode(&container.HostConfig) 183 } 184 185 // GetResourcePath evaluates `path` in the scope of the container's BaseFS, with proper path 186 // sanitisation. Symlinks are all scoped to the BaseFS of the container, as 187 // though the container's BaseFS was `/`. 188 // 189 // The BaseFS of a container is the host-facing path which is bind-mounted as 190 // `/` inside the container. This method is essentially used to access a 191 // particular path inside the container as though you were a process in that 192 // container. 193 // 194 // NOTE: The returned path is *only* safely scoped inside the container's BaseFS 195 // if no component of the returned path changes (such as a component 196 // symlinking to a different path) between using this method and using the 197 // path. See symlink.FollowSymlinkInScope for more details. 198 func (container *Container) GetResourcePath(path string) (string, error) { 199 // IMPORTANT - These are paths on the OS where the daemon is running, hence 200 // any filepath operations must be done in an OS agnostic way. 201 cleanPath := filepath.Join(string(os.PathSeparator), path) 202 r, e := symlink.FollowSymlinkInScope(filepath.Join(container.BaseFS, cleanPath), container.BaseFS) 203 return r, e 204 } 205 206 // GetRootResourcePath evaluates `path` in the scope of the container's root, with proper path 207 // sanitisation. Symlinks are all scoped to the root of the container, as 208 // though the container's root was `/`. 209 // 210 // The root of a container is the host-facing configuration metadata directory. 211 // Only use this method to safely access the container's `container.json` or 212 // other metadata files. If in doubt, use container.GetResourcePath. 213 // 214 // NOTE: The returned path is *only* safely scoped inside the container's root 215 // if no component of the returned path changes (such as a component 216 // symlinking to a different path) between using this method and using the 217 // path. See symlink.FollowSymlinkInScope for more details. 218 func (container *Container) GetRootResourcePath(path string) (string, error) { 219 // IMPORTANT - These are paths on the OS where the daemon is running, hence 220 // any filepath operations must be done in an OS agnostic way. 221 cleanPath := filepath.Join(string(os.PathSeparator), path) 222 return symlink.FollowSymlinkInScope(filepath.Join(container.Root, cleanPath), container.Root) 223 } 224 225 // ExitOnNext signals to the monitor that it should not restart the container 226 // after we send the kill signal. 227 func (container *Container) ExitOnNext() { 228 container.monitor.ExitOnNext() 229 } 230 231 // Resize changes the TTY of the process running inside the container 232 // to the given height and width. The container must be running. 233 func (container *Container) Resize(h, w int) error { 234 if err := container.Command.ProcessConfig.Terminal.Resize(h, w); err != nil { 235 return err 236 } 237 return nil 238 } 239 240 // HostConfigPath returns the path to the container's JSON hostconfig 241 func (container *Container) HostConfigPath() (string, error) { 242 return container.GetRootResourcePath("hostconfig.json") 243 } 244 245 // ConfigPath returns the path to the container's JSON config 246 func (container *Container) ConfigPath() (string, error) { 247 return container.GetRootResourcePath(configFileName) 248 } 249 250 func validateID(id string) error { 251 if id == "" { 252 return derr.ErrorCodeEmptyID 253 } 254 return nil 255 } 256 257 // Returns true if the container exposes a certain port 258 func (container *Container) exposes(p nat.Port) bool { 259 _, exists := container.Config.ExposedPorts[p] 260 return exists 261 } 262 263 // GetLogConfig returns the log configuration for the container. 264 func (container *Container) GetLogConfig(defaultConfig runconfig.LogConfig) runconfig.LogConfig { 265 cfg := container.HostConfig.LogConfig 266 if cfg.Type != "" || len(cfg.Config) > 0 { // container has log driver configured 267 if cfg.Type == "" { 268 cfg.Type = jsonfilelog.Name 269 } 270 return cfg 271 } 272 // Use daemon's default log config for containers 273 return defaultConfig 274 } 275 276 // StartLogger starts a new logger driver for the container. 277 func (container *Container) StartLogger(cfg runconfig.LogConfig) (logger.Logger, error) { 278 c, err := logger.GetLogDriver(cfg.Type) 279 if err != nil { 280 return nil, derr.ErrorCodeLoggingFactory.WithArgs(err) 281 } 282 ctx := logger.Context{ 283 Config: cfg.Config, 284 ContainerID: container.ID, 285 ContainerName: container.Name, 286 ContainerEntrypoint: container.Path, 287 ContainerArgs: container.Args, 288 ContainerImageID: container.ImageID.String(), 289 ContainerImageName: container.Config.Image, 290 ContainerCreated: container.Created, 291 ContainerEnv: container.Config.Env, 292 ContainerLabels: container.Config.Labels, 293 } 294 295 // Set logging file for "json-logger" 296 if cfg.Type == jsonfilelog.Name { 297 ctx.LogPath, err = container.GetRootResourcePath(fmt.Sprintf("%s-json.log", container.ID)) 298 if err != nil { 299 return nil, err 300 } 301 } 302 return c(ctx) 303 } 304 305 // GetProcessLabel returns the process label for the container. 306 func (container *Container) GetProcessLabel() string { 307 // even if we have a process label return "" if we are running 308 // in privileged mode 309 if container.HostConfig.Privileged { 310 return "" 311 } 312 return container.ProcessLabel 313 } 314 315 // GetMountLabel returns the mounting label for the container. 316 // This label is empty if the container is privileged. 317 func (container *Container) GetMountLabel() string { 318 if container.HostConfig.Privileged { 319 return "" 320 } 321 return container.MountLabel 322 } 323 324 // GetExecIDs returns the list of exec commands running on the container. 325 func (container *Container) GetExecIDs() []string { 326 return container.ExecCommands.List() 327 } 328 329 // Attach connects to the container's TTY, delegating to standard 330 // streams or websockets depending on the configuration. 331 func (container *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { 332 return AttachStreams(container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, stdin, stdout, stderr) 333 } 334 335 // AttachStreams connects streams to a TTY. 336 // Used by exec too. Should this move somewhere else? 337 func AttachStreams(streamConfig *runconfig.StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { 338 var ( 339 cStdout, cStderr io.ReadCloser 340 cStdin io.WriteCloser 341 wg sync.WaitGroup 342 errors = make(chan error, 3) 343 ) 344 345 if stdin != nil && openStdin { 346 cStdin = streamConfig.StdinPipe() 347 wg.Add(1) 348 } 349 350 if stdout != nil { 351 cStdout = streamConfig.StdoutPipe() 352 wg.Add(1) 353 } 354 355 if stderr != nil { 356 cStderr = streamConfig.StderrPipe() 357 wg.Add(1) 358 } 359 360 // Connect stdin of container to the http conn. 361 go func() { 362 if stdin == nil || !openStdin { 363 return 364 } 365 logrus.Debugf("attach: stdin: begin") 366 defer func() { 367 if stdinOnce && !tty { 368 cStdin.Close() 369 } else { 370 // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr 371 if cStdout != nil { 372 cStdout.Close() 373 } 374 if cStderr != nil { 375 cStderr.Close() 376 } 377 } 378 wg.Done() 379 logrus.Debugf("attach: stdin: end") 380 }() 381 382 var err error 383 if tty { 384 _, err = copyEscapable(cStdin, stdin) 385 } else { 386 _, err = io.Copy(cStdin, stdin) 387 388 } 389 if err == io.ErrClosedPipe { 390 err = nil 391 } 392 if err != nil { 393 logrus.Errorf("attach: stdin: %s", err) 394 errors <- err 395 return 396 } 397 }() 398 399 attachStream := func(name string, stream io.Writer, streamPipe io.ReadCloser) { 400 if stream == nil { 401 return 402 } 403 defer func() { 404 // Make sure stdin gets closed 405 if stdin != nil { 406 stdin.Close() 407 } 408 streamPipe.Close() 409 wg.Done() 410 logrus.Debugf("attach: %s: end", name) 411 }() 412 413 logrus.Debugf("attach: %s: begin", name) 414 _, err := io.Copy(stream, streamPipe) 415 if err == io.ErrClosedPipe { 416 err = nil 417 } 418 if err != nil { 419 logrus.Errorf("attach: %s: %v", name, err) 420 errors <- err 421 } 422 } 423 424 go attachStream("stdout", stdout, cStdout) 425 go attachStream("stderr", stderr, cStderr) 426 427 return promise.Go(func() error { 428 wg.Wait() 429 close(errors) 430 for err := range errors { 431 if err != nil { 432 return err 433 } 434 } 435 return nil 436 }) 437 } 438 439 // Code c/c from io.Copy() modified to handle escape sequence 440 func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) { 441 buf := make([]byte, 32*1024) 442 for { 443 nr, er := src.Read(buf) 444 if nr > 0 { 445 // ---- Docker addition 446 // char 16 is C-p 447 if nr == 1 && buf[0] == 16 { 448 nr, er = src.Read(buf) 449 // char 17 is C-q 450 if nr == 1 && buf[0] == 17 { 451 if err := src.Close(); err != nil { 452 return 0, err 453 } 454 return 0, nil 455 } 456 } 457 // ---- End of docker 458 nw, ew := dst.Write(buf[0:nr]) 459 if nw > 0 { 460 written += int64(nw) 461 } 462 if ew != nil { 463 err = ew 464 break 465 } 466 if nr != nw { 467 err = io.ErrShortWrite 468 break 469 } 470 } 471 if er == io.EOF { 472 break 473 } 474 if er != nil { 475 err = er 476 break 477 } 478 } 479 return written, err 480 } 481 482 // ShouldRestart decides whether the daemon should restart the container or not. 483 // This is based on the container's restart policy. 484 func (container *Container) ShouldRestart() bool { 485 return container.HostConfig.RestartPolicy.Name == "always" || 486 (container.HostConfig.RestartPolicy.Name == "unless-stopped" && !container.HasBeenManuallyStopped) || 487 (container.HostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0) 488 } 489 490 // AddBindMountPoint adds a new bind mount point configuration to the container. 491 func (container *Container) AddBindMountPoint(name, source, destination string, rw bool) { 492 container.MountPoints[destination] = &volume.MountPoint{ 493 Name: name, 494 Source: source, 495 Destination: destination, 496 RW: rw, 497 } 498 } 499 500 // AddLocalMountPoint adds a new local mount point configuration to the container. 501 func (container *Container) AddLocalMountPoint(name, destination string, rw bool) { 502 container.MountPoints[destination] = &volume.MountPoint{ 503 Name: name, 504 Driver: volume.DefaultDriverName, 505 Destination: destination, 506 RW: rw, 507 } 508 } 509 510 // AddMountPointWithVolume adds a new mount point configured with a volume to the container. 511 func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) { 512 container.MountPoints[destination] = &volume.MountPoint{ 513 Name: vol.Name(), 514 Driver: vol.DriverName(), 515 Destination: destination, 516 RW: rw, 517 Volume: vol, 518 } 519 } 520 521 // IsDestinationMounted checks whether a path is mounted on the container or not. 522 func (container *Container) IsDestinationMounted(destination string) bool { 523 return container.MountPoints[destination] != nil 524 } 525 526 // StopSignal returns the signal used to stop the container. 527 func (container *Container) StopSignal() int { 528 var stopSignal syscall.Signal 529 if container.Config.StopSignal != "" { 530 stopSignal, _ = signal.ParseSignal(container.Config.StopSignal) 531 } 532 533 if int(stopSignal) == 0 { 534 stopSignal, _ = signal.ParseSignal(signal.DefaultStopSignal) 535 } 536 return int(stopSignal) 537 } 538 539 // InitDNSHostConfig ensures that the dns fields are never nil. 540 // New containers don't ever have those fields nil, 541 // but pre created containers can still have those nil values. 542 // The non-recommended host configuration in the start api can 543 // make these fields nil again, this corrects that issue until 544 // we remove that behavior for good. 545 // See https://github.com/docker/docker/pull/17779 546 // for a more detailed explanation on why we don't want that. 547 func (container *Container) InitDNSHostConfig() { 548 container.Lock() 549 defer container.Unlock() 550 if container.HostConfig.DNS == nil { 551 container.HostConfig.DNS = make([]string, 0) 552 } 553 554 if container.HostConfig.DNSSearch == nil { 555 container.HostConfig.DNSSearch = make([]string, 0) 556 } 557 558 if container.HostConfig.DNSOptions == nil { 559 container.HostConfig.DNSOptions = make([]string, 0) 560 } 561 }