package daemon // import "github.com/docker/docker/daemon" import ( "fmt" "strings" "syscall" "github.com/docker/docker/errdefs" "github.com/pkg/errors" "google.golang.org/grpc/status" ) func isNotRunning(err error) bool { var nre *containerNotRunningError return errors.As(err, &nre) } func errNotRunning(id string) error { return &containerNotRunningError{errors.Errorf("container %s is not running", id)} } type containerNotRunningError struct { error } func (e containerNotRunningError) Conflict() {} func containerNotFound(id string) error { return objNotFoundError{"container", id} } type objNotFoundError struct { object string id string } func (e objNotFoundError) Error() string { return "No such " + e.object + ": " + e.id } func (e objNotFoundError) NotFound() {} func errContainerIsRestarting(containerID string) error { cause := errors.Errorf("Container %s is restarting, wait until the container is running", containerID) return errdefs.Conflict(cause) } func errExecNotFound(id string) error { return objNotFoundError{"exec instance", id} } func errExecPaused(id string) error { cause := errors.Errorf("Container %s is paused, unpause the container before exec", id) return errdefs.Conflict(cause) } func errNotPaused(id string) error { cause := errors.Errorf("Container %s is already paused", id) return errdefs.Conflict(cause) } type nameConflictError struct { id string name string } func (e nameConflictError) Error() string { 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) } func (nameConflictError) Conflict() {} type invalidIdentifier string func (e invalidIdentifier) Error() string { return fmt.Sprintf("invalid name or ID supplied: %q", string(e)) } func (invalidIdentifier) InvalidParameter() {} type incompatibleDeviceRequest struct { driver string caps [][]string } func (i incompatibleDeviceRequest) Error() string { return fmt.Sprintf("could not select device driver %q with capabilities: %v", i.driver, i.caps) } func (incompatibleDeviceRequest) InvalidParameter() {} type duplicateMountPointError string func (e duplicateMountPointError) Error() string { return "Duplicate mount point: " + string(e) } func (duplicateMountPointError) InvalidParameter() {} type containerFileNotFound struct { file string container string } func (e containerFileNotFound) Error() string { return "Could not find the file " + e.file + " in container " + e.container } func (containerFileNotFound) NotFound() {} type startInvalidConfigError string func (e startInvalidConfigError) Error() string { return string(e) } func (e startInvalidConfigError) InvalidParameter() {} // Is this right??? // exitStatus is the exit-code as set by setExitCodeFromError type exitStatus = int const ( exitEaccess exitStatus = 126 // container cmd can't be invoked (permission denied) exitCmdNotFound exitStatus = 127 // container cmd not found/does not exist or invalid bind-mount exitUnknown exitStatus = 128 // unknown error ) // setExitCodeFromError converts the error returned by containerd // when starting a container, and applies the corresponding exitStatus to the // container. It returns an errdefs error (either errdefs.ErrInvalidParameter // or errdefs.ErrUnknown). func setExitCodeFromError(setExitCode func(exitStatus), err error) error { if err == nil { return nil } errDesc := status.Convert(err).Message() contains := func(s1, s2 string) bool { return strings.Contains(strings.ToLower(s1), s2) } // set to 126 for container cmd can't be invoked errors if contains(errDesc, syscall.EACCES.Error()) { setExitCode(exitEaccess) return startInvalidConfigError(errDesc) } // Go 1.20 changed the error for attempting to execute a directory from // syscall.EACCESS to syscall.EISDIR. Unfortunately docker/cli checks // whether the error message contains syscall.EACCESS.Error() to // determine whether to exit with code 126 or 125, so we have little // choice but to fudge the error string. if contains(errDesc, syscall.EISDIR.Error()) { errDesc += ": " + syscall.EACCES.Error() setExitCode(exitEaccess) return startInvalidConfigError(errDesc) } // attempted to mount a file onto a directory, or a directory onto a file, maybe from user specified bind mounts if contains(errDesc, syscall.ENOTDIR.Error()) { 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" setExitCode(exitCmdNotFound) return startInvalidConfigError(errDesc) } // if we receive an internal error from the initial start of a container then lets // return it instead of entering the restart loop // set to 127 for container cmd not found/does not exist. if isInvalidCommand(errDesc) { setExitCode(exitCmdNotFound) return startInvalidConfigError(errDesc) } // TODO: it would be nice to get some better errors from containerd so we can return better errors here setExitCode(exitUnknown) return errdefs.Unknown(errors.New(errDesc)) } // isInvalidCommand tries to detect if the reason the container failed to start // was due to an invalid command for the container (command not found, or not // a valid executable). func isInvalidCommand(errMessage string) bool { errMessage = strings.ToLower(errMessage) errMessages := []string{ "executable file not found", "no such file or directory", "system cannot find the file specified", "failed to run runc create/exec call", } for _, msg := range errMessages { if strings.Contains(errMessage, msg) { return true } } return false }