diff --git a/src/crystal/system/panic.cr b/src/crystal/system/panic.cr new file mode 100644 index 000000000000..192b735e4d0f --- /dev/null +++ b/src/crystal/system/panic.cr @@ -0,0 +1,16 @@ +module Crystal::System + # Prints a system error message on the standard error then exits with an error + # status. + # + # You should always prefer raising an exception, built with + # `RuntimeError.from_os_error` for example, but there are a few cases where we + # can't allocate any memory (e.g. stop the world) and still need to fail when + # reaching a system error. + def self.panic(syscall_name : String, error : Errno | WinError | WasiError) : NoReturn + System.print_error("%s failed with ", syscall_name) + error.unsafe_message { |slice| System.print_error(slice) } + System.print_error(" (%s)\n", error.to_s) + + LibC._exit(1) + end +end diff --git a/src/crystal/tracing.cr b/src/crystal/tracing.cr index ad3ae184a54a..a680bfea717f 100644 --- a/src/crystal/tracing.cr +++ b/src/crystal/tracing.cr @@ -1,3 +1,5 @@ +require "./system/panic" + module Crystal # :nodoc: module Tracing @@ -143,23 +145,21 @@ module Crystal # not using LibC::INVALID_HANDLE_VALUE because it doesn't exist (yet) return handle.address unless handle == LibC::HANDLE.new(-1.to_u64!) - error = uninitialized UInt16[256] - len = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, WinError.value, 0, error, error.size, nil) - - # not using printf because filename and error are UTF-16 slices: - System.print_error "ERROR: failed to open " - System.print_error filename - System.print_error " for writing: " - System.print_error error.to_slice[0...len] - System.print_error "\n" + syscall_name = "CreateFileW" + error = WinError.value {% else %} fd = LibC.open(filename, LibC::O_CREAT | LibC::O_WRONLY | LibC::O_TRUNC | LibC::O_CLOEXEC, 0o644) return fd unless fd < 0 - System.print_error "ERROR: failed to open %s for writing: %s\n", filename, LibC.strerror(Errno.value) + syscall_name = "open" + error = Errno.value {% end %} - LibC._exit(1) + System.print_error "ERROR: failed to open " + System.print_error filename + System.print_error " for writing\n" + + System.panic(syscall_name, Errno.value) end private def self.parse_sections(slice) diff --git a/src/errno.cr b/src/errno.cr index 03ca6085eb8a..2a68371f4a19 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -40,10 +40,16 @@ enum Errno # Convert an Errno to an error message def message : String - String.new(LibC.strerror(value)) + unsafe_message { |slice| String.new(slice) } end - # Returns the value of libc's errno. + # :nodoc: + def unsafe_message(&) + pointer = LibC.strerror(value) + yield Bytes.new(pointer, LibC.strlen(pointer)) + end + + # returns the value of libc's errno. def self.value : self {% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %} Errno.new LibC.__errno.value diff --git a/src/wasi_error.cr b/src/wasi_error.cr index 9a04ed315463..a026de8c7ee2 100644 --- a/src/wasi_error.cr +++ b/src/wasi_error.cr @@ -83,6 +83,11 @@ enum WasiError : UInt16 end end + # :nodoc: + def unsafe_message(&) + yield message.to_slice + end + # Transforms this `WasiError` value to the equivalent `Errno` value. # # This is only defined for some values. If no transformation is defined for diff --git a/src/winerror.cr b/src/winerror.cr index 8da56d7a905e..ab978769d553 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -61,17 +61,21 @@ enum WinError : UInt32 # # On non-win32 platforms the result is always an empty string. def message : String - formatted_message + {% if flag?(:win32) %} + unsafe_message { |slice| String.from_utf16(slice).strip } + {% else %} + "" + {% end %} end # :nodoc: - def formatted_message : String + def unsafe_message(&) {% if flag?(:win32) %} buffer = uninitialized UInt16[256] size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, value, 0, buffer, buffer.size, nil) - String.from_utf16(buffer.to_slice[0, size]).strip + yield buffer.to_slice[0, size] {% else %} - "" + yield "".to_slice {% end %} end