From e8abc4d2fee56db510a9947923ae0d70e8641b04 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Mon, 22 May 2023 23:27:35 +0800 Subject: [PATCH] Add `File::BadExecutableError` --- spec/std/process_spec.cr | 11 ++++++++++- src/crystal/system/unix/process.cr | 2 +- src/crystal/system/win32/process.cr | 2 +- src/file/error.cr | 18 +++++++++++++++++- src/system_error.cr | 2 +- src/winerror.cr | 19 +++++++++++++++++++ 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index c881223eb92c..3fdfaa04d1e7 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -68,9 +68,18 @@ describe Process do end end - pending_win32 "raises if command is not executable" do + it "raises if command is not executable" do with_tempfile("crystal-spec-run") do |path| File.touch path + expect_raises({% if flag?(:win32) %} File::BadExecutableError {% else %} File::AccessDeniedError {% end %}, "Error executing process: '#{path.inspect_unquoted}'") do + Process.new(path) + end + end + end + + it "raises if command is not executable" do + with_tempfile("crystal-spec-run") do |path| + Dir.mkdir path expect_raises(File::AccessDeniedError, "Error executing process: '#{path.inspect_unquoted}'") do Process.new(path) end diff --git a/src/crystal/system/unix/process.cr b/src/crystal/system/unix/process.cr index 9558235ebcf1..f07a91806857 100644 --- a/src/crystal/system/unix/process.cr +++ b/src/crystal/system/unix/process.cr @@ -257,7 +257,7 @@ struct Crystal::System::Process private def self.raise_exception_from_errno(command, errno = Errno.value) case errno - when Errno::EACCES, Errno::ENOENT + when Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC raise ::File::Error.from_os_error("Error executing process", errno, file: command) else raise IO::Error.from_os_error("Error executing process: '#{command}'", errno) diff --git a/src/crystal/system/win32/process.cr b/src/crystal/system/win32/process.cr index e920a1d84d5c..36878bc935bf 100644 --- a/src/crystal/system/win32/process.cr +++ b/src/crystal/system/win32/process.cr @@ -212,7 +212,7 @@ struct Crystal::System::Process ) == 0 error = WinError.value case error.to_errno - when Errno::EACCES, Errno::ENOENT + when Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC raise ::File::Error.from_os_error("Error executing process", error, file: command_args) else raise IO::Error.from_os_error("Error executing process: '#{command_args}'", error) diff --git a/src/file/error.cr b/src/file/error.cr index 3a6a9b8f4ed5..79a0ec5978dc 100644 --- a/src/file/error.cr +++ b/src/file/error.cr @@ -11,8 +11,10 @@ class File::Error < IO::Error File::NotFoundError.new(message, **opts) when Errno::EEXIST, WinError::ERROR_ALREADY_EXISTS File::AlreadyExistsError.new(message, **opts) - when Errno::EACCES, WinError::ERROR_PRIVILEGE_NOT_HELD + when Errno::EACCES, WinError::ERROR_ACCESS_DENIED, WinError::ERROR_PRIVILEGE_NOT_HELD File::AccessDeniedError.new(message, **opts) + when Errno::ENOEXEC, WinError::ERROR_BAD_EXE_FORMAT + File::BadExecutableError.new(message, **opts) else super message, os_error, **opts end @@ -26,6 +28,17 @@ class File::Error < IO::Error "#{message}: '#{file.inspect_unquoted}' -> '#{other.inspect_unquoted}'" end + {% if flag?(:win32) %} + protected def self.os_error_message(os_error : WinError, *, file : String) : String? + case os_error + when WinError::ERROR_BAD_EXE_FORMAT + os_error.formatted_message(file) + else + super + end + end + {% end %} + def initialize(message, *, file : String | Path, @other : String? = nil) @file = file.to_s super message @@ -40,3 +53,6 @@ end class File::AccessDeniedError < File::Error end + +class File::BadExecutableError < File::Error +end diff --git a/src/system_error.cr b/src/system_error.cr index 7d434695b003..1f4cc4c7b3a4 100644 --- a/src/system_error.cr +++ b/src/system_error.cr @@ -36,7 +36,7 @@ # Returns the respective error message for *os_error*. # By default it returns the result of `Errno#message` or `WinError#message`. # This method can be overridden for customization of the error message based -# on *or_error* and *opts*. +# on *os_error* and *opts*. module SystemError macro included extend ::SystemError::ClassMethods diff --git a/src/winerror.cr b/src/winerror.cr index 16ad9da65ab6..466855813b74 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -61,6 +61,11 @@ enum WinError : UInt32 # # On non-win32 platforms the result is always an empty string. def message : String + formatted_message + end + + # :nodoc: + def formatted_message : String {% if flag?(:win32) %} buffer = uninitialized UInt16[256] size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, value, 0, buffer, buffer.size, nil) @@ -70,6 +75,20 @@ enum WinError : UInt32 {% end %} end + # :nodoc: + def formatted_message(*args : String) : String + {% if flag?(:win32) %} + buffer = uninitialized UInt16[512] + args = args.to_static_array.map do |arg| + Crystal::System.to_wstr(arg) + end + size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM | LibC::FORMAT_MESSAGE_ARGUMENT_ARRAY, nil, value, 0, buffer, buffer.size, args) + String.from_utf16(buffer.to_slice[0, size]).strip + {% else %} + "" + {% end %} + end + # Transforms this `WinError` value to the equivalent `Errno` value. # # This is only defined for some values. If no transformation is defined for