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