diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index de37f0d197fd..a6808e21d9a9 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -127,7 +127,7 @@ module Crystal::System::File end end - raise ::File::Error.from_errno("Cannot read link", Errno::ENAMETOOLONG, file: path) + raise ::File::Error.from_os_error("Cannot read link", Errno::ENAMETOOLONG, file: path) end def self.rename(old_filename, new_filename) diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index dc62f2db7bd9..d83497e00d04 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -42,7 +42,7 @@ struct Crystal::System::Process def self.pgid(pid) # Disallow users from depending on ppid(0) instead of `pgid` - raise RuntimeError.from_errno("getpgid", Errno::EINVAL) if pid == 0 + raise RuntimeError.from_os_error("getpgid", Errno::EINVAL) if pid == 0 ret = LibC.getpgid(pid) raise RuntimeError.from_errno("getpgid") if ret < 0 @@ -107,7 +107,7 @@ struct Crystal::System::Process # error: errno = Errno.value LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(oldmask), nil) - raise RuntimeError.from_errno("fork", errno) + raise RuntimeError.from_os_error("fork", errno) else # parent: LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(oldmask), nil) @@ -220,9 +220,9 @@ struct Crystal::System::Process private def self.raise_exception_from_errno(command, errno = Errno.value) case errno when Errno::EACCES, Errno::ENOENT - raise ::File::Error.from_errno("Error executing process", errno, file: command) + raise ::File::Error.from_os_error("Error executing process", errno, file: command) else - raise IO::Error.from_errno("Error executing process: '#{command}'", errno) + raise IO::Error.from_os_error("Error executing process: '#{command}'", errno) end end diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index df9fc026447d..1ff868311c37 100644 --- a/src/crystal/system/unix/pthread.cr +++ b/src/crystal/system/unix/pthread.cr @@ -30,7 +30,7 @@ class Thread }, self.as(Void*)) if ret != 0 - raise RuntimeError.from_errno("pthread_create", Errno.new(ret)) + raise RuntimeError.from_os_error("pthread_create", Errno.new(ret)) end end @@ -66,7 +66,7 @@ class Thread @@current_key = begin ret = LibC.pthread_key_create(out current_key, nil) - raise RuntimeError.from_errno("pthread_key_create", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_key_create", Errno.new(ret)) unless ret == 0 current_key end @@ -84,7 +84,7 @@ class Thread # Associates the Thread object to the running system thread. protected def self.current=(thread : Thread) : Thread ret = LibC.pthread_setspecific(@@current_key, thread.as(Void*)) - raise RuntimeError.from_errno("pthread_setspecific", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_setspecific", Errno.new(ret)) unless ret == 0 thread end {% else %} @@ -144,23 +144,23 @@ class Thread ret = LibC.pthread_attr_init(out attr) unless ret == 0 LibC.pthread_attr_destroy(pointerof(attr)) - raise RuntimeError.from_errno("pthread_attr_init", Errno.new(ret)) + raise RuntimeError.from_os_error("pthread_attr_init", Errno.new(ret)) end if LibC.pthread_attr_get_np(@th, pointerof(attr)) == 0 LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out _) end ret = LibC.pthread_attr_destroy(pointerof(attr)) - raise RuntimeError.from_errno("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 {% elsif flag?(:linux) %} if LibC.pthread_getattr_np(@th, out attr) == 0 LibC.pthread_attr_getstack(pointerof(attr), pointerof(address), out _) end ret = LibC.pthread_attr_destroy(pointerof(attr)) - raise RuntimeError.from_errno("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 {% elsif flag?(:openbsd) %} ret = LibC.pthread_stackseg_np(@th, out stack) - raise RuntimeError.from_errno("pthread_stackseg_np", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_stackseg_np", Errno.new(ret)) unless ret == 0 address = if LibC.pthread_main_np == 1 diff --git a/src/crystal/system/unix/pthread_condition_variable.cr b/src/crystal/system/unix/pthread_condition_variable.cr index ef16f4f9b979..c475eb15b6e5 100644 --- a/src/crystal/system/unix/pthread_condition_variable.cr +++ b/src/crystal/system/unix/pthread_condition_variable.cr @@ -13,24 +13,24 @@ class Thread {% end %} ret = LibC.pthread_cond_init(out @cond, pointerof(attributes)) - raise RuntimeError.from_errno("pthread_cond_init", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_cond_init", Errno.new(ret)) unless ret == 0 LibC.pthread_condattr_destroy(pointerof(attributes)) end def signal ret = LibC.pthread_cond_signal(self) - raise RuntimeError.from_errno("pthread_cond_signal", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_cond_signal", Errno.new(ret)) unless ret == 0 end def broadcast ret = LibC.pthread_cond_broadcast(self) - raise RuntimeError.from_errno("pthread_cond_broadcast", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_cond_broadcast", Errno.new(ret)) unless ret == 0 end def wait(mutex : Thread::Mutex) ret = LibC.pthread_cond_wait(self, mutex) - raise RuntimeError.from_errno("pthread_cond_wait", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_cond_wait", Errno.new(ret)) unless ret == 0 end def wait(mutex : Thread::Mutex, time : Time::Span) @@ -60,13 +60,13 @@ class Thread when Errno::ETIMEDOUT yield else - raise RuntimeError.from_errno("pthread_cond_timedwait", errno) + raise RuntimeError.from_os_error("pthread_cond_timedwait", errno) end end def finalize ret = LibC.pthread_cond_destroy(self) - raise RuntimeError.from_errno("pthread_cond_broadcast", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_cond_broadcast", Errno.new(ret)) unless ret == 0 end def to_unsafe diff --git a/src/crystal/system/unix/pthread_mutex.cr b/src/crystal/system/unix/pthread_mutex.cr index ca4ce0366aeb..78c293340c11 100644 --- a/src/crystal/system/unix/pthread_mutex.cr +++ b/src/crystal/system/unix/pthread_mutex.cr @@ -10,14 +10,14 @@ class Thread LibC.pthread_mutexattr_settype(pointerof(attributes), LibC::PTHREAD_MUTEX_ERRORCHECK) ret = LibC.pthread_mutex_init(out @mutex, pointerof(attributes)) - raise RuntimeError.from_errno("pthread_mutex_init", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_mutex_init", Errno.new(ret)) unless ret == 0 LibC.pthread_mutexattr_destroy(pointerof(attributes)) end def lock ret = LibC.pthread_mutex_lock(self) - raise RuntimeError.from_errno("pthread_mutex_lock", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_mutex_lock", Errno.new(ret)) unless ret == 0 end def try_lock @@ -27,13 +27,13 @@ class Thread when Errno::EBUSY false else - raise RuntimeError.from_errno("pthread_mutex_trylock", ret) + raise RuntimeError.from_os_error("pthread_mutex_trylock", ret) end end def unlock ret = LibC.pthread_mutex_unlock(self) - raise RuntimeError.from_errno("pthread_mutex_unlock", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_mutex_unlock", Errno.new(ret)) unless ret == 0 end def synchronize @@ -45,7 +45,7 @@ class Thread def finalize ret = LibC.pthread_mutex_destroy(self) - raise RuntimeError.from_errno("pthread_mutex_destroy", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_mutex_destroy", Errno.new(ret)) unless ret == 0 end def to_unsafe diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index ab4fe018b6e7..d06fb855fd3e 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -13,7 +13,7 @@ module Crystal::System::Dir def self.open(path : String) : DirHandle unless ::Dir.exists? path - raise ::File::Error.from_errno("Error opening directory", Errno::ENOENT, file: path) + raise ::File::Error.from_os_error("Error opening directory", Errno::ENOENT, file: path) end DirHandle.new(LibC::INVALID_HANDLE_VALUE, to_windows_path(path + "\\*")) @@ -31,7 +31,7 @@ module Crystal::System::Dir if error == WinError::ERROR_FILE_NOT_FOUND return nil else - raise ::File::Error.from_winerror("Error reading directory entries", error, file: path) + raise ::File::Error.from_os_error("Error reading directory entries", error, file: path) end end else @@ -43,7 +43,7 @@ module Crystal::System::Dir if error == WinError::ERROR_NO_MORE_FILES return nil else - raise ::File::Error.from_winerror("Error reading directory entries", error, file: path) + raise ::File::Error.from_os_error("Error reading directory entries", error, file: path) end end end diff --git a/src/crystal/system/win32/env.cr b/src/crystal/system/win32/env.cr index baad80a01234..a64096576571 100644 --- a/src/crystal/system/win32/env.cr +++ b/src/crystal/system/win32/env.cr @@ -43,7 +43,7 @@ module Crystal::System::Env when WinError::ERROR_ENVVAR_NOT_FOUND return else - raise RuntimeError.from_winerror("GetEnvironmentVariableW", last_error) + raise RuntimeError.from_os_error("GetEnvironmentVariableW", last_error) end end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index fdf28e474721..fb874c8fbb1a 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -51,7 +51,7 @@ module Crystal::System::File if NOT_FOUND_ERRORS.includes? error return nil else - raise ::File::Error.from_winerror(message, error, file: path) + raise ::File::Error.from_os_error(message, error, file: path) end end @@ -179,7 +179,7 @@ module Crystal::System::File end unless exists? real_path - raise ::File::Error.from_errno("Error resolving real path", Errno::ENOENT, file: path) + raise ::File::Error.from_os_error("Error resolving real path", Errno::ENOENT, file: path) end real_path diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 0fe008c93814..49dcec575188 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -64,7 +64,7 @@ module Crystal::System::FileDescriptor if file_type == LibC::FILE_TYPE_UNKNOWN error = WinError.value - raise IO::Error.from_winerror("Unable to get info", error) unless error == WinError::ERROR_SUCCESS + raise IO::Error.from_os_error("Unable to get info", error) unless error == WinError::ERROR_SUCCESS end if file_type == LibC::FILE_TYPE_DISK diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index 20a0c727837e..02cef69c0ff1 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -130,7 +130,7 @@ struct Crystal::System::Process error = WinError.value case error.to_errno when Errno::EACCES, Errno::ENOENT - raise ::File::Error.from_winerror("Error executing process", error, file: command_args) + raise ::File::Error.from_os_error("Error executing process", error, file: command_args) else raise IO::Error.from_winerror("Error executing process: '#{command_args}'", error) end diff --git a/src/errno.cr b/src/errno.cr index 7ee985ab794e..b3731ed69446 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -47,7 +47,7 @@ enum Errno Errno.new LibC.__error.value {% elsif flag?(:win32) %} ret = LibC._get_errno(out errno) - raise RuntimeError.from_errno("_get_errno", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("_get_errno", Errno.new(ret)) unless ret == 0 Errno.new errno {% end %} end @@ -60,7 +60,7 @@ enum Errno LibC.__error.value = errno.value {% elsif flag?(:win32) %} ret = LibC._set_errno(errno.value) - raise RuntimeError.from_errno("_set_errno", ret) unless ret == 0 + raise RuntimeError.from_os_error("_set_errno", ret) unless ret == 0 {% end %} errno end diff --git a/src/file/error.cr b/src/file/error.cr index cdf70913fc45..396cffd6962f 100644 --- a/src/file/error.cr +++ b/src/file/error.cr @@ -5,16 +5,16 @@ class File::Error < IO::Error getter file : String getter other : String? - private def self.new_from_errno(message, errno, **opts) - case errno - when Errno::ENOENT + private def self.new_from_os_error(message, os_error, **opts) + case os_error + when Errno::ENOENT, WinError::ERROR_FILE_NOT_FOUND, WinError::ERROR_PATH_NOT_FOUND File::NotFoundError.new(message, **opts) - when Errno::EEXIST + when Errno::EEXIST, WinError::ERROR_ALREADY_EXISTS File::AlreadyExistsError.new(message, **opts) - when Errno::EACCES + when Errno::EACCES, WinError::ERROR_PRIVILEGE_NOT_HELD File::AccessDeniedError.new(message, **opts) else - super message, errno, **opts + super message, os_error, **opts end end diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index b1707aa6b825..81a642e97d50 100644 --- a/src/gc/boehm.cr +++ b/src/gc/boehm.cr @@ -221,7 +221,7 @@ module GC # :nodoc: def self.pthread_join(thread : LibC::PthreadT) : Void* ret = LibGC.pthread_join(thread, out value) - raise RuntimeError.from_errno("pthread_join", Errno.new(ret)) unless ret == 0 + raise RuntimeError.from_os_error("pthread_join", Errno.new(ret)) unless ret == 0 value end diff --git a/src/socket/common.cr b/src/socket/common.cr index 1094f01c03c9..377e59dd4327 100644 --- a/src/socket/common.cr +++ b/src/socket/common.cr @@ -14,7 +14,7 @@ class Socket wsa_version = 0x202 err = LibC.WSAStartup(wsa_version, out wsadata) unless err.zero? - raise IO::Error.from_winerror("WSAStartup", WinError.new(err.to_u32)) + raise IO::Error.from_os_error("WSAStartup", WinError.new(err.to_u32)) end if wsadata.wVersion != wsa_version @@ -53,14 +53,14 @@ class Socket end class Error < IO::Error - private def self.new_from_errno(message, errno, **opts) - case errno + private def self.new_from_os_error(message, os_error, **opts) + case os_error when Errno::ECONNREFUSED Socket::ConnectError.new(message, **opts) when Errno::EADDRINUSE Socket::BindError.new(message, **opts) else - super message, errno, **opts + super message, os_error, **opts end end end diff --git a/src/system_error.cr b/src/system_error.cr index 852a1d3a46d3..e2d7b1f732c6 100644 --- a/src/system_error.cr +++ b/src/system_error.cr @@ -1,9 +1,13 @@ # This module can be included in any `Exception` subclass that is -# used to wrap some system error (`Errno` or `WinError`) +# used to wrap some system error (`Errno` or `WinError`). # -# When included it provides a `from_errno` method (and `from_winerror` on Windows) -# to create exception instances with a description of the original error. It also -# adds an `os_error` property that contains the original system error. +# It adds an `os_error` property that contains the original system error. +# It provides several constructor methods that set the `os_error` value: +# * `.from_os_error` receives an OS error value and creates an instance with that. +# * `.from_errno` constructs an instance with the current LibC errno value (`Errno.value`). +# * `.from_winerror` constructs an instance with the current LibC winerror value (`WinError.value`). +# +# An error message is automatically constructed based on the system error message. # # For example: # ``` @@ -13,6 +17,21 @@ # # MyError.from_errno("Something happened") # ``` +# +# ## Customization +# +# Including classes my override several protected methods to customize the +# instance creation based on OS errors: +# +# * `protected def build_message(message, **opts)` +# Prepares the message that goes before the system error description. +# By default it returns the original message unchanged. But that could be +# customized based on the keyword arguments passed to `from_errno` or `from_winerror`. +# * `protected def new_from_os_error(message : String, os_error, **opts)` +# Creates an instance of the exception that wraps a system error. +# This is a factory method and by default it creates an instance +# of the current class. It can be overridden to generate different +# classes based on the `os_error` value or keyword arguments. module SystemError macro included extend ::SystemError::ClassMethods @@ -26,27 +45,38 @@ module SystemError end module ClassMethods - # Builds an instance of the exception from a `Errno` + # Builds an instance of the exception from an *os_error* value. # - # By default it takes the current `errno` value. The `message` is appended - # with the system message corresponding to the `errno`. - # Additional keyword arguments can be passed and they will be forwarded - # to the exception initializer - def from_errno(message : String? = nil, errno : Errno = Errno.value, **opts) + # The system message corresponding to the OS error value amends the *message*. + # Additional keyword arguments are forwarded to the exception initializer `.new_from_os_error`. + def from_os_error(message : String?, os_error : Errno | WinError | Nil, **opts) message = self.build_message(message, **opts) message = if message - "#{message}: #{errno.message}" + "#{message}: #{os_error.message}" else - errno.message + os_error.message end - self.new_from_errno(message, errno, **opts).tap do |e| - e.os_error = errno + self.new_from_os_error(message, os_error, **opts).tap do |e| + e.os_error = os_error end end - # Prepare the message that goes before the system error description + # Builds an instance of the exception from the current system error value (`Errno.value`). + # + # The system message corresponding to the OS error value amends the *message*. + # Additional keyword arguments are forwarded to the exception initializer `.new_from_os_error`. + def from_errno(message : String, **opts) + from_os_error(message, Errno.value, **opts) + end + + @[Deprecated("Use `.from_os_error` instead")] + def from_errno(message : String? = nil, errno : Errno = nil, **opts) + from_os_error(message, errno, **opts) + end + + # Prepares the message that goes before the system error description. # # By default it returns the original message unchanged. But that could be # customized based on the keyword arguments passed to `from_errno` or `from_winerror`. @@ -54,43 +84,40 @@ module SystemError message end - # Create an instance of the exception that wraps a system error + # Creates an instance of the exception that wraps a system error. # # This is a factory method and by default it creates an instance # of the current class. It can be overridden to generate different - # classes based on the `errno` or keyword arguments. - protected def new_from_errno(message : String, errno : Errno, **opts) + # classes based on the `os_error` value or keyword arguments. + protected def new_from_os_error(message : String, os_error, **opts) self.new(message, **opts) end - {% if flag?(:win32) %} - # Builds an instance of the exception from a `WinError` - # - # By default it takes the current `WinError` value. The `message` is appended - # with the system message corresponding to the `WinError`. - # Additional keyword arguments can be passed and they will be forwarded - # to the exception initializer - def from_winerror(message : String? = nil, winerror : WinError = WinError.value, **opts) - message = self.build_message(message, **opts) - message = - if message - "#{message}: #{winerror.message}" - else - winerror.message - end + # Builds an instance of the exception from the current windows error value (`WinError.value`). + # The system message corresponding to the OS error value amends the *message*. + # Additional keyword arguments are forwarded to the exception initializer `.new_from_os_error`. + def from_winerror(message : String?, **opts) + from_os_error(message, WinError.value, **opts) + end - self.new_from_winerror(message, winerror, **opts).tap do |e| - e.os_error = winerror - end + # Builds an instance of the exception from a `WinError` + # + # By default it takes the current `WinError` value (see `WinError.value`). + # The system message corresponding to the OS error value amends the *message*. + # Additional keyword arguments are forwarded to the exception initializer. + def from_wsa_error(message : String? = nil, **opts) + from_os_error(message, WinError.wsa_value, **opts) + end + + {% if flag?(:win32) %} + @[Deprecated("Use `.from_os_error` instead")] + def from_winerror(message : String?, winerror : WinError, **opts) + from_os_error(message, winerror, **opts) end - # Create an instance of the exception that wraps a system error - # - # This is a factory method and by default it creates an instance - # of the current class. It can be overridden to generate different - # classes based on the `winerror` or keyword arguments. - protected def new_from_winerror(message : String, winerror : WinError, **opts) - new_from_errno(message, winerror.to_errno, **opts) + @[Deprecated("Use `.from_os_error` instead")] + def from_winerror(*, winerror : WinError = WinError.value, **opts) + from_os_error(message, winerror, **opts) end {% end %} end diff --git a/src/winerror.cr b/src/winerror.cr index 6a32e20ef342..6bcd1ced9530 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -4,7 +4,7 @@ # `WinError` represents Windows' [System Error Codes](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes#system-error-codes-1). enum WinError : UInt32 - # Returns the value of the [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) + # Returns the value of [`GetLastError`](https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror) # which is used to retrieve the error code of the previously called win32 function. # # Raises `NotImplementedError` on non-win32 platforms.