github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/errors.go (about)

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