func (container *Container) stopSignal() int { var stopSignal syscall.Signal if container.Config.StopSignal != "" { stopSignal, _ = signal.ParseSignal(container.Config.StopSignal) } if int(stopSignal) == 0 { stopSignal, _ = signal.ParseSignal(signal.DefaultStopSignal) } return int(stopSignal) }
func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err } var sig syscall.Signal name := vars["name"] // If we have a signal, look at it. Otherwise, do nothing if sigStr := r.Form.Get("signal"); sigStr != "" { var err error if sig, err = signal.ParseSignal(sigStr); err != nil { return err } } if err := s.backend.ContainerKill(name, uint64(sig)); err != nil { var isStopped bool if e, ok := err.(errContainerIsRunning); ok { isStopped = !e.ContainerIsRunning() } // Return error that's not caused because the container is stopped. // Return error if the container is not running and the api is >= 1.20 // to keep backwards compatibility. version := httputils.VersionFromContext(ctx) if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped { return fmt.Errorf("Cannot kill container %s: %v", name, err) } } w.WriteHeader(http.StatusNoContent) return nil }
func (s *Server) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } if err := parseForm(r); err != nil { return err } var sig syscall.Signal name := vars["name"] // If we have a signal, look at it. Otherwise, do nothing if sigStr := r.Form.Get("signal"); sigStr != "" { var err error if sig, err = signal.ParseSignal(sigStr); err != nil { return err } } if err := s.daemon.ContainerKill(name, uint64(sig)); err != nil { theErr, isDerr := err.(errcode.ErrorCoder) isStopped := isDerr && theErr.ErrorCode() == derr.ErrorCodeNotRunning // Return error that's not caused because the container is stopped. // Return error if the container is not running and the api is >= 1.20 // to keep backwards compatibility. version := ctx.Version() if version.GreaterThanOrEqualTo("1.20") || !isStopped { return fmt.Errorf("Cannot kill container %s: %v", name, err) } } w.WriteHeader(http.StatusNoContent) return nil }
func TestContainerStopSignal(t *testing.T) { c := &Container{ CommonContainer: CommonContainer{ Config: &runconfig.Config{}, }, } def, err := signal.ParseSignal(signal.DefaultStopSignal) if err != nil { t.Fatal(err) } s := c.stopSignal() if s != int(def) { t.Fatalf("Expected %v, got %v", def, s) } c = &Container{ CommonContainer: CommonContainer{ Config: &runconfig.Config{StopSignal: "SIGKILL"}, }, } s = c.stopSignal() if s != 9 { t.Fatalf("Expected 9, got %v", s) } }
// verifyContainerSettings performs validation of the hostconfig and config // structures. func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool, validateHostname bool) ([]string, error) { // First perform verification of settings common across all platforms. if config != nil { if config.WorkingDir != "" { config.WorkingDir = filepath.FromSlash(config.WorkingDir) // Ensure in platform semantics if !system.IsAbs(config.WorkingDir) { return nil, fmt.Errorf("the working directory '%s' is invalid, it needs to be an absolute path", config.WorkingDir) } } if len(config.StopSignal) > 0 { _, err := signal.ParseSignal(config.StopSignal) if err != nil { return nil, err } } // Validate if the given hostname is RFC 1123 (https://tools.ietf.org/html/rfc1123) compliant. if validateHostname && len(config.Hostname) > 0 { // RFC1123 specifies that 63 bytes is the maximium length // Windows has the limitation of 63 bytes in length // Linux hostname is limited to HOST_NAME_MAX=64, not including the terminating null byte. // We limit the length to 63 bytes here to match RFC1035 and RFC1123. matched, _ := regexp.MatchString("^(([[:alnum:]]|[[:alnum:]][[:alnum:]\\-]*[[:alnum:]])\\.)*([[:alnum:]]|[[:alnum:]][[:alnum:]\\-]*[[:alnum:]])$", config.Hostname) if len(config.Hostname) > 63 || !matched { return nil, fmt.Errorf("invalid hostname format: %s", config.Hostname) } } } if hostConfig == nil { return nil, nil } if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() { return nil, fmt.Errorf("can't create 'AutoRemove' container with restart policy") } for port := range hostConfig.PortBindings { _, portStr := nat.SplitProtoPort(string(port)) if _, err := nat.ParsePort(portStr); err != nil { return nil, fmt.Errorf("invalid port specification: %q", portStr) } for _, pb := range hostConfig.PortBindings[port] { _, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort)) if err != nil { return nil, fmt.Errorf("invalid port specification: %q", pb.HostPort) } } } // Now do platform-specific verification return verifyPlatformContainerSettings(daemon, hostConfig, config, update) }
// killWithSignal sends the container the given signal. This wrapper for the // host specific kill command prepares the container before attempting // to send the signal. An error is returned if the container is paused // or not running, or if there is a problem returned from the // underlying kill command. func (daemon *Daemon) killWithSignal(container *container.Container, sig int) error { logrus.Debugf("Sending kill signal %d to container %s", sig, container.ID) container.Lock() defer container.Unlock() // We could unpause the container for them rather than returning this error if container.Paused { return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID) } if !container.Running { return errNotRunning{container.ID} } if container.Config.StopSignal != "" { containerStopSignal, err := signal.ParseSignal(container.Config.StopSignal) if err != nil { return err } if containerStopSignal == syscall.Signal(sig) { container.ExitOnNext() } } else { container.ExitOnNext() } if !daemon.IsShuttingDown() { container.HasBeenManuallyStopped = true } // if the container is currently restarting we do not need to send the signal // to the process. Telling the monitor that it should exit on its next event // loop is enough if container.Restarting { return nil } if err := daemon.kill(container, sig); err != nil { err = fmt.Errorf("Cannot kill container %s: %s", container.ID, err) // if container or process not exists, ignore the error if strings.Contains(err.Error(), "container not found") || strings.Contains(err.Error(), "no such process") { logrus.Warnf("container kill failed because of 'container not found' or 'no such process': %s", err.Error()) } else { return err } } attributes := map[string]string{ "signal": fmt.Sprintf("%d", sig), } daemon.LogContainerEventWithAttributes(container, "kill", attributes) return nil }
// STOPSIGNAL signal // // Set the signal that will be used to kill the container. func stopSignal(b *Builder, args []string, attributes map[string]bool, original string) error { if len(args) != 1 { return errExactlyOneArgument("STOPSIGNAL") } sig := args[0] _, err := signal.ParseSignal(sig) if err != nil { return err } b.runConfig.StopSignal = sig return b.commit("", b.runConfig.Cmd, fmt.Sprintf("STOPSIGNAL %v", args)) }
// STOPSIGNAL signal // // Set the signal that will be used to kill the container. func stopSignal(ctx context.Context, b *builder, args []string, attributes map[string]bool, original string) error { if len(args) != 1 { return fmt.Errorf("STOPSIGNAL requires exactly one argument") } sig := args[0] _, err := signal.ParseSignal(sig) if err != nil { return err } b.Config.StopSignal = sig return b.commit(ctx, "", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args)) }
// STOPSIGNAL signal // // Set the signal that will be used to kill the container. func stopSignal(b *builder, args []string, attributes map[string]bool, original string) error { if runtime.GOOS == "windows" { return fmt.Errorf("STOPSIGNAL is not supported on Windows") } if len(args) != 1 { return fmt.Errorf("STOPSIGNAL requires exactly one argument") } sig := args[0] _, err := signal.ParseSignal(sig) if err != nil { return err } b.Config.StopSignal = sig return b.commit("", b.Config.Cmd, fmt.Sprintf("STOPSIGNAL %v", args)) }
// verifyContainerSettings performs validation of the hostconfig and config // structures. func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { // First perform verification of settings common across all platforms. if config != nil { if config.WorkingDir != "" { config.WorkingDir = filepath.FromSlash(config.WorkingDir) // Ensure in platform semantics if !system.IsAbs(config.WorkingDir) { return nil, fmt.Errorf("The working directory '%s' is invalid. It needs to be an absolute path", config.WorkingDir) } } if len(config.StopSignal) > 0 { _, err := signal.ParseSignal(config.StopSignal) if err != nil { return nil, err } } } if hostConfig == nil { return nil, nil } logCfg := daemon.getLogConfig(hostConfig.LogConfig) if err := logger.ValidateLogOpts(logCfg.Type, logCfg.Config); err != nil { return nil, err } for port := range hostConfig.PortBindings { _, portStr := nat.SplitProtoPort(string(port)) if _, err := nat.ParsePort(portStr); err != nil { return nil, fmt.Errorf("Invalid port specification: %q", portStr) } for _, pb := range hostConfig.PortBindings[port] { _, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort)) if err != nil { return nil, fmt.Errorf("Invalid port specification: %q", pb.HostPort) } } } // Now do platform-specific verification return verifyPlatformContainerSettings(daemon, hostConfig, config, update) }
// verifyContainerSettings performs validation of the hostconfig and config // structures. func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostConfig, config *containertypes.Config, update bool) ([]string, error) { // First perform verification of settings common across all platforms. if config != nil { if config.WorkingDir != "" { config.WorkingDir = filepath.FromSlash(config.WorkingDir) // Ensure in platform semantics if !system.IsAbs(config.WorkingDir) { return nil, fmt.Errorf("the working directory '%s' is invalid, it needs to be an absolute path", config.WorkingDir) } } if len(config.StopSignal) > 0 { _, err := signal.ParseSignal(config.StopSignal) if err != nil { return nil, err } } // Validate if Env contains empty variable or not (e.g., ``, `=foo`) for _, env := range config.Env { if _, err := opts.ValidateEnv(env); err != nil { return nil, err } } } if hostConfig == nil { return nil, nil } if hostConfig.AutoRemove && !hostConfig.RestartPolicy.IsNone() { return nil, fmt.Errorf("can't create 'AutoRemove' container with restart policy") } for port := range hostConfig.PortBindings { _, portStr := nat.SplitProtoPort(string(port)) if _, err := nat.ParsePort(portStr); err != nil { return nil, fmt.Errorf("invalid port specification: %q", portStr) } for _, pb := range hostConfig.PortBindings[port] { _, err := nat.NewPort(nat.SplitProtoPort(pb.HostPort)) if err != nil { return nil, fmt.Errorf("invalid port specification: %q", pb.HostPort) } } } p := hostConfig.RestartPolicy switch p.Name { case "always", "unless-stopped", "no": if p.MaximumRetryCount != 0 { return nil, fmt.Errorf("maximum retry count cannot be used with restart policy '%s'", p.Name) } case "on-failure": if p.MaximumRetryCount < 0 { return nil, fmt.Errorf("maximum retry count cannot be negative") } case "": // do nothing default: return nil, fmt.Errorf("invalid restart policy '%s'", p.Name) } // Now do platform-specific verification return verifyPlatformContainerSettings(daemon, hostConfig, config, update) }