Skip to content

Commit 6c34494

Browse files
authored
Add Crystal::System.panic (#14733)
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.
1 parent da33258 commit 6c34494

File tree

5 files changed

+48
-17
lines changed

5 files changed

+48
-17
lines changed

src/crystal/system/panic.cr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module Crystal::System
2+
# Prints a system error message on the standard error then exits with an error
3+
# status.
4+
#
5+
# You should always prefer raising an exception, built with
6+
# `RuntimeError.from_os_error` for example, but there are a few cases where we
7+
# can't allocate any memory (e.g. stop the world) and still need to fail when
8+
# reaching a system error.
9+
def self.panic(syscall_name : String, error : Errno | WinError | WasiError) : NoReturn
10+
System.print_error("%s failed with ", syscall_name)
11+
error.unsafe_message { |slice| System.print_error(slice) }
12+
System.print_error(" (%s)\n", error.to_s)
13+
14+
LibC._exit(1)
15+
end
16+
end

src/crystal/tracing.cr

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require "./system/panic"
2+
13
module Crystal
24
# :nodoc:
35
module Tracing
@@ -143,23 +145,21 @@ module Crystal
143145
# not using LibC::INVALID_HANDLE_VALUE because it doesn't exist (yet)
144146
return handle.address unless handle == LibC::HANDLE.new(-1.to_u64!)
145147

146-
error = uninitialized UInt16[256]
147-
len = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, WinError.value, 0, error, error.size, nil)
148-
149-
# not using printf because filename and error are UTF-16 slices:
150-
System.print_error "ERROR: failed to open "
151-
System.print_error filename
152-
System.print_error " for writing: "
153-
System.print_error error.to_slice[0...len]
154-
System.print_error "\n"
148+
syscall_name = "CreateFileW"
149+
error = WinError.value
155150
{% else %}
156151
fd = LibC.open(filename, LibC::O_CREAT | LibC::O_WRONLY | LibC::O_TRUNC | LibC::O_CLOEXEC, 0o644)
157152
return fd unless fd < 0
158153

159-
System.print_error "ERROR: failed to open %s for writing: %s\n", filename, LibC.strerror(Errno.value)
154+
syscall_name = "open"
155+
error = Errno.value
160156
{% end %}
161157

162-
LibC._exit(1)
158+
System.print_error "ERROR: failed to open "
159+
System.print_error filename
160+
System.print_error " for writing\n"
161+
162+
System.panic(syscall_name, Errno.value)
163163
end
164164

165165
private def self.parse_sections(slice)

src/errno.cr

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,16 @@ enum Errno
4040

4141
# Convert an Errno to an error message
4242
def message : String
43-
String.new(LibC.strerror(value))
43+
unsafe_message { |slice| String.new(slice) }
4444
end
4545

46-
# Returns the value of libc's errno.
46+
# :nodoc:
47+
def unsafe_message(&)
48+
pointer = LibC.strerror(value)
49+
yield Bytes.new(pointer, LibC.strlen(pointer))
50+
end
51+
52+
# returns the value of libc's errno.
4753
def self.value : self
4854
{% if flag?(:netbsd) || flag?(:openbsd) || flag?(:android) %}
4955
Errno.new LibC.__errno.value

src/wasi_error.cr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ enum WasiError : UInt16
8383
end
8484
end
8585

86+
# :nodoc:
87+
def unsafe_message(&)
88+
yield message.to_slice
89+
end
90+
8691
# Transforms this `WasiError` value to the equivalent `Errno` value.
8792
#
8893
# This is only defined for some values. If no transformation is defined for

src/winerror.cr

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,21 @@ enum WinError : UInt32
6161
#
6262
# On non-win32 platforms the result is always an empty string.
6363
def message : String
64-
formatted_message
64+
{% if flag?(:win32) %}
65+
unsafe_message { |slice| String.from_utf16(slice).strip }
66+
{% else %}
67+
""
68+
{% end %}
6569
end
6670

6771
# :nodoc:
68-
def formatted_message : String
72+
def unsafe_message(&)
6973
{% if flag?(:win32) %}
7074
buffer = uninitialized UInt16[256]
7175
size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, value, 0, buffer, buffer.size, nil)
72-
String.from_utf16(buffer.to_slice[0, size]).strip
76+
yield buffer.to_slice[0, size]
7377
{% else %}
74-
""
78+
yield "".to_slice
7579
{% end %}
7680
end
7781

0 commit comments

Comments
 (0)