Skip to content

Commit

Permalink
Add Crystal::System.panic (#14733)
Browse files Browse the repository at this point in the history
Prints a system error message on the standard error then exits with an error status. Raising an exception should always be preferred 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.
  • Loading branch information
ysbaddaden committed Jun 26, 2024
1 parent da33258 commit 6c34494
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 17 deletions.
16 changes: 16 additions & 0 deletions src/crystal/system/panic.cr
Original file line number Diff line number Diff line change
@@ -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
22 changes: 11 additions & 11 deletions src/crystal/tracing.cr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "./system/panic"

module Crystal
# :nodoc:
module Tracing
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 8 additions & 2 deletions src/errno.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/wasi_error.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions src/winerror.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 6c34494

Please sign in to comment.