errors.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package daemon // import "github.com/docker/docker/daemon"
  2. import (
  3. "fmt"
  4. "strings"
  5. "syscall"
  6. "github.com/docker/docker/errdefs"
  7. "github.com/pkg/errors"
  8. "google.golang.org/grpc/status"
  9. )
  10. func isNotRunning(err error) bool {
  11. var nre *containerNotRunningError
  12. return errors.As(err, &nre)
  13. }
  14. func errNotRunning(id string) error {
  15. return &containerNotRunningError{errors.Errorf("container %s is not running", id)}
  16. }
  17. type containerNotRunningError struct {
  18. error
  19. }
  20. func (e containerNotRunningError) Conflict() {}
  21. func containerNotFound(id string) error {
  22. return objNotFoundError{"container", id}
  23. }
  24. type objNotFoundError struct {
  25. object string
  26. id string
  27. }
  28. func (e objNotFoundError) Error() string {
  29. return "No such " + e.object + ": " + e.id
  30. }
  31. func (e objNotFoundError) NotFound() {}
  32. func errContainerIsRestarting(containerID string) error {
  33. cause := errors.Errorf("Container %s is restarting, wait until the container is running", containerID)
  34. return errdefs.Conflict(cause)
  35. }
  36. func errExecNotFound(id string) error {
  37. return objNotFoundError{"exec instance", id}
  38. }
  39. func errExecPaused(id string) error {
  40. cause := errors.Errorf("Container %s is paused, unpause the container before exec", id)
  41. return errdefs.Conflict(cause)
  42. }
  43. func errNotPaused(id string) error {
  44. cause := errors.Errorf("Container %s is already paused", id)
  45. return errdefs.Conflict(cause)
  46. }
  47. type nameConflictError struct {
  48. id string
  49. name string
  50. }
  51. func (e nameConflictError) Error() string {
  52. return fmt.Sprintf("Conflict. The container name %q is already in use by container %q. You have to remove (or rename) that container to be able to reuse that name.", e.name, e.id)
  53. }
  54. func (nameConflictError) Conflict() {}
  55. type invalidIdentifier string
  56. func (e invalidIdentifier) Error() string {
  57. return fmt.Sprintf("invalid name or ID supplied: %q", string(e))
  58. }
  59. func (invalidIdentifier) InvalidParameter() {}
  60. type incompatibleDeviceRequest struct {
  61. driver string
  62. caps [][]string
  63. }
  64. func (i incompatibleDeviceRequest) Error() string {
  65. return fmt.Sprintf("could not select device driver %q with capabilities: %v", i.driver, i.caps)
  66. }
  67. func (incompatibleDeviceRequest) InvalidParameter() {}
  68. type duplicateMountPointError string
  69. func (e duplicateMountPointError) Error() string {
  70. return "Duplicate mount point: " + string(e)
  71. }
  72. func (duplicateMountPointError) InvalidParameter() {}
  73. type containerFileNotFound struct {
  74. file string
  75. container string
  76. }
  77. func (e containerFileNotFound) Error() string {
  78. return "Could not find the file " + e.file + " in container " + e.container
  79. }
  80. func (containerFileNotFound) NotFound() {}
  81. type startInvalidConfigError string
  82. func (e startInvalidConfigError) Error() string {
  83. return string(e)
  84. }
  85. func (e startInvalidConfigError) InvalidParameter() {} // Is this right???
  86. // exitStatus is the exit-code as set by setExitCodeFromError
  87. type exitStatus = int
  88. const (
  89. exitEaccess exitStatus = 126 // container cmd can't be invoked (permission denied)
  90. exitCmdNotFound exitStatus = 127 // container cmd not found/does not exist or invalid bind-mount
  91. exitUnknown exitStatus = 128 // unknown error
  92. )
  93. // setExitCodeFromError converts the error returned by containerd
  94. // when starting a container, and applies the corresponding exitStatus to the
  95. // container. It returns an errdefs error (either errdefs.ErrInvalidParameter
  96. // or errdefs.ErrUnknown).
  97. func setExitCodeFromError(setExitCode func(exitStatus), err error) error {
  98. if err == nil {
  99. return nil
  100. }
  101. errDesc := status.Convert(err).Message()
  102. contains := func(s1, s2 string) bool {
  103. return strings.Contains(strings.ToLower(s1), s2)
  104. }
  105. // set to 126 for container cmd can't be invoked errors
  106. if contains(errDesc, syscall.EACCES.Error()) {
  107. setExitCode(exitEaccess)
  108. return startInvalidConfigError(errDesc)
  109. }
  110. // Go 1.20 changed the error for attempting to execute a directory from
  111. // syscall.EACCESS to syscall.EISDIR. Unfortunately docker/cli checks
  112. // whether the error message contains syscall.EACCESS.Error() to
  113. // determine whether to exit with code 126 or 125, so we have little
  114. // choice but to fudge the error string.
  115. if contains(errDesc, syscall.EISDIR.Error()) {
  116. errDesc += ": " + syscall.EACCES.Error()
  117. setExitCode(exitEaccess)
  118. return startInvalidConfigError(errDesc)
  119. }
  120. // attempted to mount a file onto a directory, or a directory onto a file, maybe from user specified bind mounts
  121. if contains(errDesc, syscall.ENOTDIR.Error()) {
  122. errDesc += ": Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type"
  123. setExitCode(exitCmdNotFound)
  124. return startInvalidConfigError(errDesc)
  125. }
  126. // if we receive an internal error from the initial start of a container then lets
  127. // return it instead of entering the restart loop
  128. // set to 127 for container cmd not found/does not exist.
  129. if isInvalidCommand(errDesc) {
  130. setExitCode(exitCmdNotFound)
  131. return startInvalidConfigError(errDesc)
  132. }
  133. // TODO: it would be nice to get some better errors from containerd so we can return better errors here
  134. setExitCode(exitUnknown)
  135. return errdefs.Unknown(errors.New(errDesc))
  136. }
  137. // isInvalidCommand tries to detect if the reason the container failed to start
  138. // was due to an invalid command for the container (command not found, or not
  139. // a valid executable).
  140. func isInvalidCommand(errMessage string) bool {
  141. errMessage = strings.ToLower(errMessage)
  142. errMessages := []string{
  143. "executable file not found",
  144. "no such file or directory",
  145. "system cannot find the file specified",
  146. "failed to run runc create/exec call",
  147. }
  148. for _, msg := range errMessages {
  149. if strings.Contains(errMessage, msg) {
  150. return true
  151. }
  152. }
  153. return false
  154. }