diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 1f9db063f3d6..7a7238aa1a23 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -1,5 +1,4 @@ require "./spec_helper" -require "../support/errno" private def unset_tempdir {% if flag?(:windows) %} @@ -63,14 +62,14 @@ describe "Dir" do end it "tests empty? on nonexistent directory" do - expect_raises_errno(Errno::ENOENT, "Error determining size of '#{datapath("foo", "bar")}'") do + expect_raises(File::NotFoundError, "Error opening directory: '#{datapath("foo", "bar").inspect_unquoted}'") do Dir.empty?(datapath("foo", "bar")) end end # TODO: do we even want this? pending_win32 "tests empty? on a directory path to a file" do - expect_raises_errno(Errno::ENOTDIR, "Error determining size of '#{datapath("dir", "f1.txt", "/")}'") do + expect_raises(File::Error, "Error opening directory: '#{datapath("dir", "f1.txt", "/").inspect_unquoted}'") do Dir.empty?(datapath("dir", "f1.txt", "/")) end end @@ -86,7 +85,7 @@ describe "Dir" do end it "tests mkdir with an existing path" do - expect_raises_errno(Errno::EEXIST, "Unable to create directory '#{datapath}'") do + expect_raises(File::AlreadyExistsError, "Unable to create directory: '#{datapath.inspect_unquoted}'") do Dir.mkdir(datapath, 0o700) end end @@ -104,7 +103,7 @@ describe "Dir" do context "path exists" do it "fails when path is a file" do - expect_raises_errno(Errno::EEXIST, "Unable to create directory '#{datapath("test_file.txt")}': File exists") do + expect_raises(File::AlreadyExistsError, "Unable to create directory: '#{datapath("test_file.txt").inspect_unquoted}': File exists") do Dir.mkdir_p(datapath("test_file.txt")) end end @@ -119,14 +118,14 @@ describe "Dir" do it "tests rmdir with an nonexistent path" do with_tempfile("nonexistant") do |path| - expect_raises_errno(Errno::ENOENT, "Unable to remove directory '#{path}'") do + expect_raises(File::NotFoundError, "Unable to remove directory: '#{path.inspect_unquoted}'") do Dir.rmdir(path) end end end it "tests rmdir with a path that cannot be removed" do - expect_raises_errno(Errno::ENOTEMPTY, "Unable to remove directory '#{datapath}'") do + expect_raises(File::Error, "Unable to remove directory: '#{datapath.inspect_unquoted}'") do Dir.rmdir(datapath) end end @@ -372,7 +371,7 @@ describe "Dir" do end it "raises" do - expect_raises_errno(Errno::ENOENT, {{ flag?(:win32) ? /SetCurrentDirectory: .* No such file or directory/ : "Error while changing directory to '/nope'" }}) do + expect_raises(File::NotFoundError, "Error while changing directory: '/nope'") do Dir.cd("/nope") end end diff --git a/spec/std/file/tempfile_spec.cr b/spec/std/file/tempfile_spec.cr index 7b9a884ec703..4d2d700f3560 100644 --- a/spec/std/file/tempfile_spec.cr +++ b/spec/std/file/tempfile_spec.cr @@ -1,5 +1,4 @@ require "../spec_helper" -require "../../support/errno" describe File do describe ".tempname" do @@ -86,7 +85,7 @@ describe File do end it "fails in unwritable folder" do - expect_raises_errno(Errno::ENOENT, "mkstemp: '#{datapath("non-existing-folder")}/") do + expect_raises(File::NotFoundError, "Error creating temporary file: '#{datapath("non-existing-folder")}/") do File.tempfile dir: datapath("non-existing-folder") end end diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 82654a3119ed..4e80c742e472 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -1,5 +1,4 @@ require "./spec_helper" -require "../support/errno" private def base Dir.current @@ -106,14 +105,14 @@ describe "File" do it "raises an error when the file does not exist" do filename = datapath("non_existing_file.txt") - expect_raises_errno(Errno::ENOENT, "Error determining size of '#{filename}'") do + expect_raises(File::NotFoundError, "Unable to get file info: '#{filename}'") do File.empty?(filename) end end # TODO: do we even want this? pending_win32 "raises an error when a component of the path is a file" do - expect_raises_errno(Errno::ENOTDIR, "Error determining size of '#{datapath("test_file.txt", "")}'") do + expect_raises(File::Error, "Unable to get file info: '#{datapath("test_file.txt", "")}'") do File.empty?(datapath("test_file.txt", "")) end end @@ -387,7 +386,7 @@ describe "File" do end it "raises when destination doesn't exist" do - expect_raises_errno(Errno::ENOENT, "Error changing permissions of '#{datapath("unknown_chmod_path.txt")}'") do + expect_raises(File::NotFoundError, "Error changing permissions: '#{datapath("unknown_chmod_path.txt")}'") do File.chmod(datapath("unknown_chmod_path.txt"), 0o664) end end @@ -430,7 +429,7 @@ describe "File" do end it "gets for non-existent file and raises" do - expect_raises_errno(Errno::ENOENT, "Unable to get info for 'non-existent'") do + expect_raises(File::NotFoundError, "Unable to get file info: 'non-existent'") do File.info("non-existent") end end @@ -472,14 +471,14 @@ describe "File" do it "raises an error when the file does not exist" do filename = datapath("non_existing_file.txt") - expect_raises_errno(Errno::ENOENT, "Error determining size of '#{filename}'") do + expect_raises(File::NotFoundError, "Unable to get file info: '#{filename}'") do File.size(filename) end end # TODO: do we even want this? pending_win32 "raises an error when a component of the path is a file" do - expect_raises_errno(Errno::ENOTDIR, "Error determining size of '#{datapath("test_file.txt", "")}'") do + expect_raises(File::Error, "Unable to get file info: '#{datapath("test_file.txt", "")}'") do File.size(datapath("test_file.txt", "")) end end @@ -495,9 +494,9 @@ describe "File" do end end - it "raises errno when file doesn't exist" do + it "raises when file doesn't exist" do with_tempfile("nonexistant_file.txt") do |path| - expect_raises_errno(Errno::ENOENT, "Error deleting file '#{path}'") do + expect_raises(File::NotFoundError, "Error deleting file: '#{path}'") do File.delete(path) end end @@ -518,7 +517,7 @@ describe "File" do it "raises if old file doesn't exist" do with_tempfile("rename-fail-source.txt", "rename-fail-target.txt") do |source_path, target_path| - expect_raises_errno(Errno::ENOENT, "Error renaming file '#{source_path}' to '#{target_path}'") do + expect_raises(File::NotFoundError, "Error renaming file: '#{source_path}' -> '#{target_path}'") do File.rename(source_path, target_path) end end @@ -635,8 +634,8 @@ describe "File" do {% end %} end - it "raises Errno if file doesn't exist" do - expect_raises_errno(Errno::ENOENT, "Error resolving real path of '/usr/share/foo/bar'") do + it "raises if file doesn't exist" do + expect_raises(File::NotFoundError, "Error resolving real path: '/usr/share/foo/bar'") do File.real_path("/usr/share/foo/bar") end end @@ -841,6 +840,14 @@ describe "File" do end end + it "raises when reading a file with no permission" do + with_tempfile("file.txt") do |path| + File.touch(path) + File.chmod(path, 0) + expect_raises(File::AccessDeniedError) { File.read(path) } + end + end + describe "truncate" do it "truncates" do with_tempfile("truncate.txt") do |path| @@ -872,7 +879,7 @@ describe "File" do with_tempfile("truncate-opened.txt") do |path| File.write(path, "0123456789") File.open(path, "r") do |f| - expect_raises_errno(Errno::EINVAL, "Error truncating file '#{path}'") do + expect_raises(File::Error, "Error truncating file: '#{path}'") do f.truncate(4) end end @@ -899,7 +906,7 @@ describe "File" do File.open(datapath("test_file.txt")) do |file2| file1.flock_exclusive do # BUG: check for EWOULDBLOCK when exception filters are implemented - expect_raises_errno(Errno::EAGAIN, "flock") do + expect_raises(IO::Error, "Error applying or removing file lock") do file2.flock_exclusive(blocking: false) { } end end @@ -1152,7 +1159,7 @@ describe "File" do atime = Time.utc(2000, 1, 2) mtime = Time.utc(2000, 3, 4) - expect_raises_errno(Errno::ENOENT, "Error setting time on file '#{datapath("nonexistent_file.txt")}'") do + expect_raises(File::NotFoundError, "Error setting time on file: '#{datapath("nonexistent_file.txt")}'") do File.utime(atime, mtime, datapath("nonexistent_file.txt")) end end @@ -1188,7 +1195,7 @@ describe "File" do it "raises if path contains non-existent directory" do with_tempfile(File.join("nonexistant-dir", "touch.txt")) do |path| - expect_raises_errno(Errno::ENOENT, "Error opening file '#{path}'") do + expect_raises(File::NotFoundError, "Error opening file with mode 'a': '#{path}'") do File.touch(path) end end @@ -1196,7 +1203,7 @@ describe "File" do # TODO: there is no file which is reliably unwritable on windows pending_win32 "raises if file cannot be accessed" do - expect_raises_errno(Errno::EPERM, "Error setting time on file '/bin/ls'") do + expect_raises(File::Error, "Error setting time on file: '/bin/ls'") do File.touch("/bin/ls") end end diff --git a/spec/std/file_utils_spec.cr b/spec/std/file_utils_spec.cr index d98f10aaade3..e94444f71691 100644 --- a/spec/std/file_utils_spec.cr +++ b/spec/std/file_utils_spec.cr @@ -1,6 +1,5 @@ require "./spec_helper" require "file_utils" -require "../support/errno" private class OneByOneIO < IO @bytes : Bytes @@ -34,7 +33,7 @@ describe "FileUtils" do end it "raises" do - expect_raises_errno(Errno::ENOENT, "Error while changing directory to '/nope'") do + expect_raises(File::NotFoundError, "Error while changing directory: '/nope'") do FileUtils.cd("/nope") end end @@ -230,6 +229,12 @@ describe "FileUtils" do end it "doesn't return error on non existing file" do + with_tempfile("rm_rf-nonexistent") do |path| + FileUtils.rm_rf(path).should be_nil + end + end + + it "doesn't return error on non existing files" do with_tempfile("rm_rf-nonexistent") do |path1| path2 = File.join(path1, "a") FileUtils.mkdir(path1) @@ -253,7 +258,7 @@ describe "FileUtils" do it "raises an error if non correct arguments" do with_tempfile("mv-nonexitent") do |path| - expect_raises_errno(Errno::ENOENT, "Error renaming file '#{File.join(path, "a")}' to '#{File.join(path, "b")}'") do + expect_raises(File::NotFoundError, "Error renaming file: '#{File.join(path, "a")}' -> '#{File.join(path, "b")}'") do FileUtils.mv(File.join(path, "a"), File.join(path, "b")) end end @@ -323,18 +328,18 @@ describe "FileUtils" do end it "tests mkdir with an existing path" do - expect_raises_errno(Errno::EEXIST, "Unable to create directory '#{datapath}'") do + expect_raises(File::AlreadyExistsError, "Unable to create directory: '#{datapath}'") do Dir.mkdir(datapath, 0o700) end end it "tests mkdir with multiples existing paths" do - expect_raises_errno(Errno::EEXIST, "Unable to create directory '#{datapath}'") do + expect_raises(File::AlreadyExistsError, "Unable to create directory: '#{datapath}'") do FileUtils.mkdir([datapath, datapath], 0o700) end with_tempfile("mkdir-nonexisting") do |path| - expect_raises_errno(Errno::EEXIST, "Unable to create directory '#{datapath}'") do + expect_raises(File::AlreadyExistsError, "Unable to create directory: '#{datapath}'") do FileUtils.mkdir([path, datapath], 0o700) end end @@ -356,7 +361,7 @@ describe "FileUtils" do it "tests mkdir_p with multiple existing path" do FileUtils.mkdir_p([datapath, datapath]).should be_nil with_tempfile("mkdir_p-existing") do |path| - expect_raises_errno(Errno::EEXIST, "Unable to create directory '#{datapath("test_file.txt")}'") do + expect_raises(File::AlreadyExistsError, "Unable to create directory: '#{datapath("test_file.txt")}'") do FileUtils.mkdir_p([datapath("test_file.txt"), path]) end end @@ -364,7 +369,7 @@ describe "FileUtils" do it "tests rmdir with an non existing path" do with_tempfile("rmdir-nonexisting") do |path| - expect_raises_errno(Errno::ENOENT, "Unable to remove directory '#{path}'") do + expect_raises(File::NotFoundError, "Unable to remove directory: '#{path}'") do FileUtils.rmdir(path) end end @@ -372,20 +377,20 @@ describe "FileUtils" do it "tests rmdir with multiple non existing path" do with_tempfile("rmdir-nonexisting") do |path| - expect_raises_errno(Errno::ENOENT, "Unable to remove directory '#{path}1'") do + expect_raises(File::NotFoundError, "Unable to remove directory: '#{path}1'") do FileUtils.rmdir(["#{path}1", "#{path}2"]) end end end it "tests rmdir with a path that cannot be removed" do - expect_raises_errno(Errno::ENOTEMPTY, "Unable to remove directory '#{datapath}'") do + expect_raises(File::Error, "Unable to remove directory: '#{datapath}'") do FileUtils.rmdir(datapath) end end it "tests rmdir with multiple path that cannot be removed" do - expect_raises_errno(Errno::ENOTEMPTY, "Unable to remove directory '#{datapath}'") do + expect_raises(File::Error, "Unable to remove directory: '#{datapath}'") do FileUtils.rmdir([datapath, datapath]) end end @@ -400,7 +405,7 @@ describe "FileUtils" do it "tests rm with non existing path" do with_tempfile("rm-nonexistinent") do |path| - expect_raises_errno(Errno::ENOENT, "Error deleting file '#{path}'") do + expect_raises(File::NotFoundError, "Error deleting file: '#{path}'") do FileUtils.rm(path) end end @@ -421,7 +426,7 @@ describe "FileUtils" do File.write(path1, "") File.write(path2, "") - expect_raises_errno(Errno::ENOENT, "Error deleting file '#{path2}'") do + expect_raises(File::NotFoundError, "Error deleting file: '#{path2}'") do FileUtils.rm([path1, path2, path2]) end end @@ -482,7 +487,7 @@ describe "FileUtils" do path1 = "/tmp/crystal_ln_test_#{Process.pid}" path2 = "/tmp/crystal_ln_test_#{Process.pid + 1}" - ex = expect_raises_errno(Errno::ENOENT, "Error creating link from '#{path1}' to '#{path2}'") do + ex = expect_raises(File::NotFoundError, "Error creating link: '#{path1}' -> '#{path2}'") do FileUtils.ln(path1, path2) end end @@ -494,7 +499,7 @@ describe "FileUtils" do begin FileUtils.touch([path1, path2]) - expect_raises_errno(Errno::EEXIST, "Error creating link from '#{path1}' to '#{path2}'") do + expect_raises(File::AlreadyExistsError, "Error creating link: '#{path1}' -> '#{path2}'") do FileUtils.ln(path1, path2) end ensure @@ -563,7 +568,7 @@ describe "FileUtils" do File.exists?(path2).should be_false File.symlink?(path2).should be_true - expect_raises_errno(Errno::ENOENT, "Error resolving real path of '#{path2}'") do + expect_raises(File::NotFoundError, "Error resolving real path: '#{path2}'") do File.real_path(path2) end ensure @@ -578,7 +583,7 @@ describe "FileUtils" do begin FileUtils.touch([path1, path2]) - expect_raises_errno(Errno::EEXIST, "Error creating symlink from '#{path1}' to '#{path2}'") do + expect_raises(File::AlreadyExistsError, "Error creating symlink: '#{path1}' -> '#{path2}'") do FileUtils.ln_s(path1, path2) end ensure diff --git a/spec/std/http/client/client_spec.cr b/spec/std/http/client/client_spec.cr index 3ae04da27295..6b9453f47b32 100644 --- a/spec/std/http/client/client_spec.cr +++ b/spec/std/http/client/client_spec.cr @@ -216,7 +216,7 @@ module HTTP # the server if the socket is closed. test_server("localhost", 0, 0.5, write_response: false) do |server| client = Client.new("localhost", server.local_address.port) - expect_raises(IO::Timeout, "Read timed out") do + expect_raises(IO::TimeoutError, "Read timed out") do client.read_timeout = 0.001 client.get("/?sleep=1") end @@ -230,7 +230,7 @@ module HTTP # the server if the socket is closed. test_server("localhost", 0, 0, write_response: false) do |server| client = Client.new("localhost", server.local_address.port) - expect_raises(IO::Timeout, "Write timed out") do + expect_raises(IO::TimeoutError, "Write timed out") do client.write_timeout = 0.001 client.post("/", body: "a" * 5_000_000) end diff --git a/spec/std/http/server/request_processor_spec.cr b/spec/std/http/server/request_processor_spec.cr index a56fdbc41dc2..e0d193366554 100644 --- a/spec/std/http/server/request_processor_spec.cr +++ b/spec/std/http/server/request_processor_spec.cr @@ -1,13 +1,12 @@ require "spec" require "http/server/request_processor" -private class RaiseErrno < IO - def initialize(@value : Int32) +private class RaiseIOError < IO + def initialize end def read(slice : Bytes) - Errno.value = @value - raise Errno.new "..." + raise IO::Error.new("...") end def write(slice : Bytes) : Nil @@ -244,11 +243,9 @@ describe HTTP::Server::RequestProcessor do end end - it "handles Errno" do + it "handles IO::Error" do processor = HTTP::Server::RequestProcessor.new { } - # Using EPERM here instead of a more reasonable ECONNRESET because the - # latter is not available on all platforms (win32). - input = RaiseErrno.new(Errno::EPERM) + input = RaiseIOError.new output = IO::Memory.new processor.process(input, output) output.rewind.gets_to_end.empty?.should be_true diff --git a/spec/std/io/io_spec.cr b/spec/std/io/io_spec.cr index 20d7deb4b10b..6d7a4e651628 100644 --- a/spec/std/io/io_spec.cr +++ b/spec/std/io/io_spec.cr @@ -90,7 +90,7 @@ describe IO do read.read_timeout = 1 read.read(slice).should eq(6) - expect_raises(IO::Timeout) do + expect_raises(IO::TimeoutError) do read.read_timeout = 0.0000001 read.read(slice) end diff --git a/spec/std/process_spec.cr b/spec/std/process_spec.cr index 382bbf3bb886..4d1c86716c05 100644 --- a/spec/std/process_spec.cr +++ b/spec/std/process_spec.cr @@ -2,7 +2,6 @@ require "spec" require "process" require "./spec_helper" require "../spec_helper" -require "../support/errno" describe Process do it "runs true" do @@ -16,8 +15,7 @@ describe Process do end it "raises if command could not be executed" do - # FIXME: Oddly doubled error message - expect_raises_errno(Errno::ENOENT, %(execvp (foobarbaz "foo"): No such file or directory: No such file or directory)) do + expect_raises(RuntimeError, "Error executing process: No such file or directory") do Process.new("foobarbaz", ["foo"]) end end @@ -90,15 +88,13 @@ describe Process do begin Process.chroot("/usr") puts "FAIL" - rescue ex : Errno - puts (ex.errno == Errno::EPERM) ? "Success" : "FAIL: #{ex.message}" rescue ex - puts "FAIL: #{ex.message}" + puts ex.inspect end CODE status.success?.should be_true - output.should eq("Success\n") + output.should eq("#\n") end it "sets working directory" do diff --git a/spec/std/socket/socket_spec.cr b/spec/std/socket/socket_spec.cr index 0f832f64b22a..9b9ecb146c60 100644 --- a/spec/std/socket/socket_spec.cr +++ b/spec/std/socket/socket_spec.cr @@ -77,6 +77,18 @@ describe Socket do ensure socket.try &.close end + + it "binds to port using Socket::IPAddress" do + socket = TCPSocket.new family + socket.bind Socket::IPAddress.new(any_address, 0) + socket.listen + + address = socket.local_address.as(Socket::IPAddress) + address.address.should eq(any_address) + address.port.should be > 0 + ensure + socket.try &.close + end end end end diff --git a/spec/std/socket/tcp_server_spec.cr b/spec/std/socket/tcp_server_spec.cr index 34f413844345..fe38253890af 100644 --- a/spec/std/socket/tcp_server_spec.cr +++ b/spec/std/socket/tcp_server_spec.cr @@ -1,5 +1,4 @@ require "./spec_helper" -require "../../support/errno" describe TCPServer do describe ".new" do @@ -20,7 +19,7 @@ describe TCPServer do server.close server.closed?.should be_true - expect_raises_errno(Errno::EBADF, "getsockname: ") do + expect_raises(Socket::Error, "getsockname: ") do server.local_address end end @@ -46,7 +45,7 @@ describe TCPServer do describe "reuse_port" do it "raises when port is in use" do TCPServer.open(address, 0) do |server| - expect_raises_errno(Errno::EADDRINUSE, "bind: ") do + expect_raises(Socket::BindError, "Could not bind to '#{address}:#{server.local_address.port}': ") do TCPServer.open(address, server.local_address.port) { } end end @@ -54,7 +53,7 @@ describe TCPServer do it "raises when not binding with reuse_port" do TCPServer.open(address, 0, reuse_port: true) do |server| - expect_raises_errno(Errno::EADDRINUSE) do + expect_raises(Socket::BindError) do TCPServer.open(address, server.local_address.port) { } end end @@ -62,7 +61,7 @@ describe TCPServer do it "raises when port is not ready to be reused" do TCPServer.open(address, 0) do |server| - expect_raises_errno(Errno::EADDRINUSE, "bind: ") do + expect_raises(Socket::BindError) do TCPServer.open(address, server.local_address.port, reuse_port: true) { } end end diff --git a/spec/std/socket/tcp_socket_spec.cr b/spec/std/socket/tcp_socket_spec.cr index 8987bae959d2..8b77c8afb90c 100644 --- a/spec/std/socket/tcp_socket_spec.cr +++ b/spec/std/socket/tcp_socket_spec.cr @@ -1,5 +1,4 @@ require "./spec_helper" -require "../../support/errno" describe TCPSocket do describe "#connect" do @@ -28,7 +27,7 @@ describe TCPSocket do it "raises when connection is refused" do port = unused_local_port - expect_raises_errno(Errno::ECONNREFUSED, "Error connecting to '#{address}:#{port}'") do + expect_raises(Socket::ConnectError, "Error connecting to '#{address}:#{port}'") do TCPSocket.new(address, port) end end @@ -41,7 +40,7 @@ describe TCPSocket do end it "raises when port is zero" do - expect_raises_errno({% if flag?(:linux) %}Errno::ECONNREFUSED{% else %}Errno::EADDRNOTAVAIL{% end %}) do + expect_raises(Socket::ConnectError) do TCPSocket.new(address, 0) end end @@ -75,7 +74,7 @@ describe TCPSocket do port = unused_local_port TCPServer.open("0.0.0.0", port) do |server| - expect_raises_errno(Errno::ECONNREFUSED, "Error connecting to '::1:#{port}'") do + expect_raises(Socket::ConnectError, "Error connecting to '::1:#{port}'") do TCPSocket.new("::1", port) end end @@ -129,7 +128,7 @@ describe TCPSocket do server.local_address.port end - expect_raises_errno(Errno::ECONNREFUSED, "Error connecting to 'localhost:#{port}'") do + expect_raises(Socket::ConnectError, "Error connecting to 'localhost:#{port}'") do TCPSocket.new("localhost", port) end end diff --git a/spec/std/socket/udp_socket_spec.cr b/spec/std/socket/udp_socket_spec.cr index a1eed722e18b..087acca25aa5 100644 --- a/spec/std/socket/udp_socket_spec.cr +++ b/spec/std/socket/udp_socket_spec.cr @@ -1,5 +1,4 @@ require "./spec_helper" -require "../../support/errno" require "socket" describe UDPSocket do @@ -101,7 +100,7 @@ describe UDPSocket do udp.read_timeout = 1.second begin udp.receive[0].should eq("testing") - rescue IO::Timeout + rescue IO::TimeoutError # Since this test doesn't run over the loopback interface, this test # fails when there is a firewall in use. Don't fail in that case. end diff --git a/spec/std/socket/unix_server_spec.cr b/spec/std/socket/unix_server_spec.cr index 7ae1e5390296..3046f38cd444 100644 --- a/spec/std/socket/unix_server_spec.cr +++ b/spec/std/socket/unix_server_spec.cr @@ -1,6 +1,5 @@ -require "spec" +require "../spec_helper" require "socket" -require "../../support/errno" require "../../support/fibers" require "../../support/tempfile" @@ -40,7 +39,7 @@ describe UNIXServer do server = UNIXServer.new(path) begin - expect_raises_errno(Errno::EADDRINUSE, "bind: ") do + expect_raises(Socket::BindError) do UNIXServer.new(path) end ensure @@ -54,7 +53,7 @@ describe UNIXServer do File.write(path, "") File.exists?(path).should be_true - expect_raises_errno(Errno::EADDRINUSE, "bind: ") do + expect_raises(Socket::BindError) do UNIXServer.new(path) end diff --git a/spec/std/socket/unix_socket_spec.cr b/spec/std/socket/unix_socket_spec.cr index c8a7fe0ecc42..98278c177500 100644 --- a/spec/std/socket/unix_socket_spec.cr +++ b/spec/std/socket/unix_socket_spec.cr @@ -77,11 +77,11 @@ describe UNIXSocket do right.read_timeout = 0.0001 buf = ("a" * 4096).to_slice - expect_raises(IO::Timeout, "Write timed out") do + expect_raises(IO::TimeoutError, "Write timed out") do loop { left.write buf } end - expect_raises(IO::Timeout, "Read timed out") do + expect_raises(IO::TimeoutError, "Read timed out") do loop { right.read buf } end end diff --git a/spec/std/thread/mutex_spec.cr b/spec/std/thread/mutex_spec.cr index 125622f68b37..037c735b38e9 100644 --- a/spec/std/thread/mutex_spec.cr +++ b/spec/std/thread/mutex_spec.cr @@ -7,7 +7,6 @@ {% end %} require "spec" -require "../../support/errno" describe Thread::Mutex do it "synchronizes" do @@ -28,7 +27,7 @@ describe Thread::Mutex do mutex = Thread::Mutex.new mutex.try_lock.should be_true mutex.try_lock.should be_false - expect_raises_errno(Errno::EDEADLK, "pthread_mutex_lock: ") { mutex.lock } + expect_raises(RuntimeError, "pthread_mutex_lock: ") { mutex.lock } mutex.unlock end @@ -36,7 +35,7 @@ describe Thread::Mutex do mutex = Thread::Mutex.new mutex.lock - expect_raises_errno(Errno::EPERM, "pthread_mutex_unlock: ") do + expect_raises(RuntimeError, "pthread_mutex_unlock: ") do Thread.new { mutex.unlock }.join end diff --git a/spec/support/errno.cr b/spec/support/errno.cr deleted file mode 100644 index c9221b833c70..000000000000 --- a/spec/support/errno.cr +++ /dev/null @@ -1,15 +0,0 @@ -# This spec helper allows to easily test the error code of `Errno` errors. -# -# The error messages returned by `strerror` vary between different libc -# implementations. Error codes are a more reliable way to assert the expected -# kind of error. -# -# *message* should usually only validate parts of the error message added by -# Crystal. -def expect_raises_errno(errno, message = nil, file = __FILE__, line = __LINE__, end_line = __END_LINE__) - error = expect_raises(Errno, message, file: file, line: line) do - yield - end - error.errno.should eq(errno), file: file, line: line - error -end diff --git a/src/compiler/crystal/codegen/cache_dir.cr b/src/compiler/crystal/codegen/cache_dir.cr index 7eb65d4d66fa..87e7497a5154 100644 --- a/src/compiler/crystal/codegen/cache_dir.cr +++ b/src/compiler/crystal/codegen/cache_dir.cr @@ -81,7 +81,7 @@ module Crystal begin Dir.mkdir_p(candidate) return @dir = candidate - rescue Errno + rescue File::Error # Try next one end end diff --git a/src/compiler/crystal/config.cr b/src/compiler/crystal/config.cr index 55a557304632..2cfa54023836 100644 --- a/src/compiler/crystal/config.cr +++ b/src/compiler/crystal/config.cr @@ -60,7 +60,7 @@ module Crystal def self.linux_runtime_libc ldd_version = String.build do |io| Process.run("ldd", {"--version"}, output: io, error: io) - rescue Errno + rescue # In case of an error (eg. `ldd` not available), we assume it's gnu. return "gnu" end diff --git a/src/crystal/iconv.cr b/src/crystal/iconv.cr index 5c68cd09804f..278dfde4cfca 100644 --- a/src/crystal/iconv.cr +++ b/src/crystal/iconv.cr @@ -29,7 +29,7 @@ struct Crystal::Iconv raise ArgumentError.new("Invalid encoding: #{original_from} -> #{original_to}") end else - raise Errno.new("iconv_open") + raise RuntimeError.from_errno("iconv_open") end end end @@ -72,7 +72,7 @@ struct Crystal::Iconv def close if LibC.iconv_close(@iconv) == -1 - raise Errno.new("iconv_close") + raise RuntimeError.from_errno("iconv_close") end end end diff --git a/src/crystal/main.cr b/src/crystal/main.cr index c1a5c7c8a2b8..a5c7ed00b369 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -54,7 +54,6 @@ module Crystal def self.ignore_stdio_errors yield rescue IO::Error - rescue Errno end # Main method run by all Crystal programs at startup. diff --git a/src/crystal/system/dir.cr b/src/crystal/system/dir.cr index 024d17aaf41e..c5f57a04b82e 100644 --- a/src/crystal/system/dir.cr +++ b/src/crystal/system/dir.cr @@ -22,8 +22,8 @@ module Crystal::System::Dir # Returns the next directory entry name in the iterator represented by *handle*, or # `nil` if iteration is complete. - def self.next(dir) : String? - next_entry(dir).try &.name + def self.next(dir, path) : String? + next_entry(dir, path).try &.name end # Returns the next directory entry in the iterator represented by *handle*, or diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr index 815fdb968bf8..843595ad7dcd 100644 --- a/src/crystal/system/unix/dir.cr +++ b/src/crystal/system/unix/dir.cr @@ -3,20 +3,20 @@ require "c/dirent" module Crystal::System::Dir def self.open(path : String) : LibC::DIR* dir = LibC.opendir(path.check_no_null_byte) - raise Errno.new("Error opening directory '#{path.inspect_unquoted}'") unless dir + raise ::File::Error.from_errno("Error opening directory", file: path) unless dir dir end - def self.next_entry(dir) : Entry? + def self.next_entry(dir, path) : Entry? # LibC.readdir returns NULL and sets errno for failure or returns NULL for EOF but leaves errno as is. # This means we need to reset `Errno` before calling `readdir`. - Errno.value = 0 + Errno.value = Errno::NONE if entry = LibC.readdir(dir) name = String.new(entry.value.d_name.to_unsafe) dir = entry.value.d_type == LibC::DT_DIR Entry.new(name, dir) - elsif Errno.value != 0 - raise Errno.new("readdir") + elsif Errno.value != Errno::NONE + raise ::File::Error.from_errno("Error reading directory entries", file: path) else nil end @@ -26,15 +26,15 @@ module Crystal::System::Dir LibC.rewinddir(dir) end - def self.close(dir) : Nil + def self.close(dir, path) : Nil if LibC.closedir(dir) != 0 - raise Errno.new("closedir") + raise ::File::Error.from_errno("Error closing directory", file: path) end end def self.current : String unless dir = LibC.getcwd(nil, 0) - raise Errno.new("getcwd") + raise ::File::Error.from_errno("Error getting current directory", file: "./") end dir_str = String.new(dir) @@ -44,7 +44,7 @@ module Crystal::System::Dir def self.current=(path : String) if LibC.chdir(path.check_no_null_byte) != 0 - raise Errno.new("Error while changing directory to '#{path.inspect_unquoted}'") + raise ::File::Error.from_errno("Error while changing directory", file: path) end path @@ -57,13 +57,13 @@ module Crystal::System::Dir def self.create(path : String, mode : Int32) : Nil if LibC.mkdir(path.check_no_null_byte, mode) == -1 - raise Errno.new("Unable to create directory '#{path.inspect_unquoted}'") + raise ::File::Error.from_errno("Unable to create directory", file: path) end end def self.delete(path : String) : Nil if LibC.rmdir(path.check_no_null_byte) == -1 - raise Errno.new("Unable to remove directory '#{path.inspect_unquoted}'") + raise ::File::Error.from_errno("Unable to remove directory", file: path) end end end diff --git a/src/crystal/system/unix/env.cr b/src/crystal/system/unix/env.cr index 9b3dd517035f..4c5451e7b491 100644 --- a/src/crystal/system/unix/env.cr +++ b/src/crystal/system/unix/env.cr @@ -7,7 +7,7 @@ module Crystal::System::Env value.check_no_null_byte("value") if LibC.setenv(key, value, 1) != 0 - raise Errno.new("setenv") + raise RuntimeError.from_errno("setenv") end end @@ -16,7 +16,7 @@ module Crystal::System::Env key.check_no_null_byte("key") if LibC.unsetenv(key) != 0 - raise Errno.new("unsetenv") + raise RuntimeError.from_errno("unsetenv") end end diff --git a/src/crystal/system/unix/fiber.cr b/src/crystal/system/unix/fiber.cr index 844d274ebb9a..17cbe5c07fdb 100644 --- a/src/crystal/system/unix/fiber.cr +++ b/src/crystal/system/unix/fiber.cr @@ -8,7 +8,7 @@ module Crystal::System::Fiber {% end %} pointer = LibC.mmap(nil, stack_size, LibC::PROT_READ | LibC::PROT_WRITE, flags, -1, 0) - raise Errno.new("Cannot allocate new fiber stack") if pointer == LibC::MAP_FAILED + raise RuntimeError.from_errno("Cannot allocate new fiber stack") if pointer == LibC::MAP_FAILED {% if flag?(:linux) %} LibC.madvise(pointer, stack_size, LibC::MADV_NOHUGEPAGE) diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index f95899ea6f94..87b657b414d8 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -1,4 +1,5 @@ require "c/sys/file" +require "file/error" # :nodoc: module Crystal::System::File @@ -7,7 +8,7 @@ module Crystal::System::File fd = LibC.open(filename.check_no_null_byte, oflag, perm) if fd < 0 - raise Errno.new("Error opening file '#{filename.inspect_unquoted}' with mode '#{mode}'") + raise ::File::Error.from_errno("Error opening file with mode '#{mode}'", file: filename) end fd end @@ -22,7 +23,7 @@ module Crystal::System::File fd = LibC.mkstemp(path) end - raise Errno.new("mkstemp: '#{path.inspect_unquoted}'") if fd == -1 + raise ::File::Error.from_errno("Error creating temporary file", file: path) if fd == -1 {fd, path} end @@ -40,7 +41,7 @@ module Crystal::System::File if Errno.value.in?(Errno::ENOENT, Errno::ENOTDIR) return nil else - raise Errno.new("Unable to get info for '#{path.inspect_unquoted}'") + raise ::File::Error.from_errno("Unable to get file info", file: path) end end end @@ -71,37 +72,37 @@ module Crystal::System::File else LibC.chown(path, uid, gid) end - raise Errno.new("Error changing owner of '#{path.inspect_unquoted}'") if ret == -1 + raise ::File::Error.from_errno("Error changing owner", file: path) if ret == -1 end def self.chmod(path, mode) if LibC.chmod(path, mode) == -1 - raise Errno.new("Error changing permissions of '#{path.inspect_unquoted}'") + raise ::File::Error.from_errno("Error changing permissions", file: path) end end def self.delete(path) err = LibC.unlink(path.check_no_null_byte) if err == -1 - raise Errno.new("Error deleting file '#{path.inspect_unquoted}'") + raise ::File::Error.from_errno("Error deleting file", file: path) end end def self.real_path(path) real_path_ptr = LibC.realpath(path, nil) - raise Errno.new("Error resolving real path of '#{path.inspect_unquoted}'") unless real_path_ptr + raise ::File::Error.from_errno("Error resolving real path", file: path) unless real_path_ptr String.new(real_path_ptr).tap { LibC.free(real_path_ptr.as(Void*)) } end def self.link(old_path, new_path) ret = LibC.link(old_path.check_no_null_byte, new_path.check_no_null_byte) - raise Errno.new("Error creating link from '#{old_path.inspect_unquoted}' to '#{new_path.inspect_unquoted}'") if ret != 0 + raise ::File::Error.from_errno("Error creating link", file: old_path, other: new_path) if ret != 0 ret end def self.symlink(old_path, new_path) ret = LibC.symlink(old_path.check_no_null_byte, new_path.check_no_null_byte) - raise Errno.new("Error creating symlink from '#{old_path.inspect_unquoted}' to '#{new_path.inspect_unquoted}'") if ret != 0 + raise ::File::Error.from_errno("Error creating symlink", file: old_path, other: new_path) if ret != 0 ret end @@ -113,7 +114,7 @@ module Crystal::System::File 3.times do |iter| bytesize = LibC.readlink(path, buf, buf.bytesize) if bytesize == -1 - raise Errno.new("readlink") + raise ::File::Error.from_errno("Cannot read link", file: path) elsif bytesize == buf.bytesize break if iter >= 2 buf = Bytes.new(buf.bytesize * 4) @@ -122,14 +123,13 @@ module Crystal::System::File end end - Errno.value = Errno::ENAMETOOLONG - raise Errno.new("readlink") + raise ::File::Error.from_errno("Cannot read link", Errno::ENAMETOOLONG, file: path) end def self.rename(old_filename, new_filename) code = LibC.rename(old_filename.check_no_null_byte, new_filename.check_no_null_byte) if code != 0 - raise Errno.new("Error renaming file '#{old_filename.inspect_unquoted}' to '#{new_filename.inspect_unquoted}'") + raise ::File::Error.from_errno("Error renaming file", file: old_filename, other: new_filename) end end @@ -139,7 +139,7 @@ module Crystal::System::File timevals[1] = to_timeval(mtime) ret = LibC.utimes(filename, timevals) if ret != 0 - raise Errno.new("Error setting time on file '#{filename.inspect_unquoted}'") + raise ::File::Error.from_errno("Error setting time on file", file: filename) end end @@ -154,7 +154,7 @@ module Crystal::System::File flush code = LibC.ftruncate(fd, size) if code != 0 - raise Errno.new("Error truncating file '#{path.inspect_unquoted}'") + raise ::File::Error.from_errno("Error truncating file", file: path) end end @@ -174,21 +174,22 @@ module Crystal::System::File op |= LibC::FlockOp::NB unless blocking if LibC.flock(fd, op) != 0 - raise Errno.new("flock") + raise IO::Error.from_errno("Error applying or removing file lock") end nil end private def system_fsync(flush_metadata = true) : Nil - if flush_metadata - if LibC.fsync(fd) != 0 - raise Errno.new("fsync") - end - else - if LibC.fdatasync(fd) != 0 - raise Errno.new("fdatasync") + ret = + if flush_metadata + LibC.fsync(fd) + else + LibC.fdatasync(fd) end + + if ret != 0 + raise IO::Error.from_errno("Error syncing file") end end end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index 01224bc4fea8..08f21e25ad26 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -59,13 +59,13 @@ module Crystal::System::FileDescriptor def self.fcntl(fd, cmd, arg = 0) r = LibC.fcntl(fd, cmd, arg) - raise Errno.new("fcntl() failed") if r == -1 + raise IO::Error.from_errno("fcntl() failed") if r == -1 r end private def system_info if LibC.fstat(fd, out stat) != 0 - raise Errno.new("Unable to get info") + raise IO::Error.from_errno("Unable to get info") end FileInfo.new(stat) @@ -75,13 +75,13 @@ module Crystal::System::FileDescriptor seek_value = LibC.lseek(fd, offset, whence) if seek_value == -1 - raise Errno.new "Unable to seek" + raise IO::Error.from_errno "Unable to seek" end end private def system_pos pos = LibC.lseek(fd, 0, IO::Seek::Current) - raise Errno.new "Unable to tell" if pos == -1 + raise IO::Error.from_errno "Unable to tell" if pos == -1 pos end @@ -94,12 +94,12 @@ module Crystal::System::FileDescriptor # dup doesn't copy the CLOEXEC flag, so copy it manually using dup3 flags = other.close_on_exec? ? LibC::O_CLOEXEC : 0 if LibC.dup3(other.fd, fd, flags) == -1 - raise Errno.new("Could not reopen file descriptor") + raise IO::Error.from_errno("Could not reopen file descriptor") end {% else %} # dup doesn't copy the CLOEXEC flag, copy it manually to the new if LibC.dup2(other.fd, fd) == -1 - raise Errno.new("Could not reopen file descriptor") + raise IO::Error.from_errno("Could not reopen file descriptor") end if other.close_on_exec? @@ -132,7 +132,7 @@ module Crystal::System::FileDescriptor when Errno::EINTR, Errno::EINPROGRESS # ignore else - raise Errno.new("Error closing file") + raise IO::Error.from_errno("Error closing file") end end end @@ -140,7 +140,7 @@ module Crystal::System::FileDescriptor def self.pipe(read_blocking, write_blocking) pipe_fds = uninitialized StaticArray(LibC::Int, 2) if LibC.pipe(pipe_fds) != 0 - raise Errno.new("Could not create pipe") + raise IO::Error.from_errno("Could not create pipe") end r = IO::FileDescriptor.new(pipe_fds[0], read_blocking) @@ -156,7 +156,7 @@ module Crystal::System::FileDescriptor bytes_read = LibC.pread(fd, buffer, buffer.size, offset) if bytes_read == -1 - raise Errno.new "Error reading file" + raise IO::Error.from_errno "Error reading file" end bytes_read diff --git a/src/crystal/system/unix/getrandom.cr b/src/crystal/system/unix/getrandom.cr index 0a009a0f8c44..ec048b1a4271 100644 --- a/src/crystal/system/unix/getrandom.cr +++ b/src/crystal/system/unix/getrandom.cr @@ -62,7 +62,7 @@ module Crystal::System::Random end read_bytes = sys_getrandom(buf[0, chunk_size]) - raise Errno.new("getrandom") if read_bytes == -1 + raise RuntimeError.from_errno("getrandom") if read_bytes == -1 buf += read_bytes end diff --git a/src/crystal/system/unix/group.cr b/src/crystal/system/unix/group.cr index a24f10cd9f5c..07d0ea90f593 100644 --- a/src/crystal/system/unix/group.cr +++ b/src/crystal/system/unix/group.cr @@ -21,7 +21,7 @@ module Crystal::System::Group ret = LibC.getgrnam_r(groupname, grp_pointer, buf, buf.size, pointerof(grp_pointer)) end - raise Errno.new("getgrnam_r") if ret != 0 + raise RuntimeError.from_errno("getgrnam_r") if ret != 0 from_struct(grp) if grp_pointer end @@ -41,7 +41,7 @@ module Crystal::System::Group ret = LibC.getgrgid_r(groupid, grp_pointer, buf, buf.size, pointerof(grp_pointer)) end - raise Errno.new("getgrgid_r") if ret != 0 + raise RuntimeError.from_errno("getgrgid_r") if ret != 0 from_struct(grp) if grp_pointer end diff --git a/src/crystal/system/unix/hostname.cr b/src/crystal/system/unix/hostname.cr index e803ef514185..0d5af8bb5747 100644 --- a/src/crystal/system/unix/hostname.cr +++ b/src/crystal/system/unix/hostname.cr @@ -4,7 +4,7 @@ module Crystal::System def self.hostname String.new(255) do |buffer| unless LibC.gethostname(buffer, LibC::SizeT.new(255)) == 0 - raise Errno.new("Could not get hostname") + raise RuntimeError.from_errno("Could not get hostname") end len = LibC.strlen(buffer) {len, len} diff --git a/src/crystal/system/unix/pthread.cr b/src/crystal/system/unix/pthread.cr index 5e43f64c224f..85b99c59bc29 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 Errno.new("pthread_create", ret) + raise RuntimeError.from_errno("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 Errno.new("pthread_key_create", ret) unless ret == 0 + raise RuntimeError.from_errno("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 Errno.new("pthread_setspecific", ret) unless ret == 0 + raise RuntimeError.from_errno("pthread_setspecific", Errno.new(ret)) unless ret == 0 thread end {% else %} @@ -105,7 +105,7 @@ class Thread def self.yield ret = LibC.sched_yield - raise Errno.new("sched_yield") unless ret == 0 + raise RuntimeError.from_errno("sched_yield") unless ret == 0 end # Returns the Fiber representing the thread's main stack. @@ -144,23 +144,23 @@ class Thread ret = LibC.pthread_attr_init(out attr) unless ret == 0 LibC.pthread_attr_destroy(pointerof(attr)) - raise Errno.new("pthread_attr_init", ret) + raise RuntimeError.from_errno("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 Errno.new("pthread_attr_destroy", ret) unless ret == 0 + raise RuntimeError.from_errno("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 Errno.new("pthread_attr_destroy", ret) unless ret == 0 + raise RuntimeError.from_errno("pthread_attr_destroy", Errno.new(ret)) unless ret == 0 {% elsif flag?(:openbsd) %} ret = LibC.pthread_stackseg_np(@th, out stack) - raise Errno.new("pthread_stackseg_np", ret) unless ret == 0 + raise RuntimeError.from_errno("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 5c8da3365dd4..ef16f4f9b979 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 Errno.new("pthread_cond_init", ret) unless ret == 0 + raise RuntimeError.from_errno("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 Errno.new("pthread_cond_signal", ret) unless ret == 0 + raise RuntimeError.from_errno("pthread_cond_signal", Errno.new(ret)) unless ret == 0 end def broadcast ret = LibC.pthread_cond_broadcast(self) - raise Errno.new("pthread_cond_broadcast", ret) unless ret == 0 + raise RuntimeError.from_errno("pthread_cond_broadcast", Errno.new(ret)) unless ret == 0 end def wait(mutex : Thread::Mutex) ret = LibC.pthread_cond_wait(self, mutex) - raise Errno.new("pthread_cond_wait", ret) unless ret == 0 + raise RuntimeError.from_errno("pthread_cond_wait", Errno.new(ret)) unless ret == 0 end def wait(mutex : Thread::Mutex, time : Time::Span) @@ -54,19 +54,19 @@ class Thread LibC.pthread_cond_timedwait(self, mutex, pointerof(ts)) {% end %} - case ret - when 0 + case errno = Errno.new(ret) + when .none? # normal resume from #signal or #broadcast when Errno::ETIMEDOUT yield else - raise Errno.new("pthread_cond_timedwait", ret) + raise RuntimeError.from_errno("pthread_cond_timedwait", errno) end end def finalize ret = LibC.pthread_cond_destroy(self) - raise Errno.new("pthread_cond_broadcast", ret) unless ret == 0 + raise RuntimeError.from_errno("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 ab18f9501fbe..ca4ce0366aeb 100644 --- a/src/crystal/system/unix/pthread_mutex.cr +++ b/src/crystal/system/unix/pthread_mutex.cr @@ -10,30 +10,30 @@ class Thread LibC.pthread_mutexattr_settype(pointerof(attributes), LibC::PTHREAD_MUTEX_ERRORCHECK) ret = LibC.pthread_mutex_init(out @mutex, pointerof(attributes)) - raise Errno.new("pthread_mutex_init", ret) unless ret == 0 + raise RuntimeError.from_errno("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 Errno.new("pthread_mutex_lock", ret) unless ret == 0 + raise RuntimeError.from_errno("pthread_mutex_lock", Errno.new(ret)) unless ret == 0 end def try_lock - case ret = LibC.pthread_mutex_trylock(self) - when 0 + case ret = Errno.new(LibC.pthread_mutex_trylock(self)) + when .none? true when Errno::EBUSY false else - raise Errno.new("pthread_mutex_trylock", ret) + raise RuntimeError.from_errno("pthread_mutex_trylock", ret) end end def unlock ret = LibC.pthread_mutex_unlock(self) - raise Errno.new("pthread_mutex_unlock", ret) unless ret == 0 + raise RuntimeError.from_errno("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 Errno.new("pthread_mutex_destroy", ret) unless ret == 0 + raise RuntimeError.from_errno("pthread_mutex_destroy", Errno.new(ret)) unless ret == 0 end def to_unsafe diff --git a/src/crystal/system/unix/time.cr b/src/crystal/system/unix/time.cr index 5f1e4a5fa0da..5d15f5275070 100644 --- a/src/crystal/system/unix/time.cr +++ b/src/crystal/system/unix/time.cr @@ -16,11 +16,11 @@ module Crystal::System::Time def self.compute_utc_seconds_and_nanoseconds : {Int64, Int32} {% if LibC.methods.includes?("clock_gettime".id) %} ret = LibC.clock_gettime(LibC::CLOCK_REALTIME, out timespec) - raise Errno.new("clock_gettime") unless ret == 0 + raise RuntimeError.from_errno("clock_gettime") unless ret == 0 {timespec.tv_sec.to_i64 + UnixEpochInSeconds, timespec.tv_nsec.to_i} {% else %} ret = LibC.gettimeofday(out timeval, nil) - raise Errno.new("gettimeofday") unless ret == 0 + raise RuntimeError.from_errno("gettimeofday") unless ret == 0 {timeval.tv_sec.to_i64 + UnixEpochInSeconds, timeval.tv_usec.to_i * 1_000} {% end %} end @@ -34,7 +34,7 @@ module Crystal::System::Time {seconds.to_i64, nanoseconds.to_i32} {% else %} if LibC.clock_gettime(LibC::CLOCK_MONOTONIC, out tp) == 1 - raise Errno.new("clock_gettime(CLOCK_MONOTONIC)") + raise RuntimeError.from_errno("clock_gettime(CLOCK_MONOTONIC)") end {tp.tv_sec.to_i64, tp.tv_nsec.to_i32} {% end %} diff --git a/src/crystal/system/unix/user.cr b/src/crystal/system/unix/user.cr index 36f55c9268de..7e1230dffa82 100644 --- a/src/crystal/system/unix/user.cr +++ b/src/crystal/system/unix/user.cr @@ -22,7 +22,7 @@ module Crystal::System::User ret = LibC.getpwnam_r(username, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) end - raise Errno.new("getpwnam_r") if ret != 0 + raise RuntimeError.from_errno("getpwnam_r") if ret != 0 from_struct(pwd) if pwd_pointer end @@ -42,7 +42,7 @@ module Crystal::System::User ret = LibC.getpwuid_r(id, pwd_pointer, buf, buf.size, pointerof(pwd_pointer)) end - raise Errno.new("getpwuid_r") if ret != 0 + raise RuntimeError.from_errno("getpwuid_r") if ret != 0 from_struct(pwd) if pwd_pointer end diff --git a/src/crystal/system/win32/dir.cr b/src/crystal/system/win32/dir.cr index 04649c505285..ab4fe018b6e7 100644 --- a/src/crystal/system/win32/dir.cr +++ b/src/crystal/system/win32/dir.cr @@ -13,13 +13,13 @@ module Crystal::System::Dir def self.open(path : String) : DirHandle unless ::Dir.exists? path - raise Errno.new("Error opening directory #{path.inspect}", Errno::ENOENT) + raise ::File::Error.from_errno("Error opening directory", Errno::ENOENT, file: path) end DirHandle.new(LibC::INVALID_HANDLE_VALUE, to_windows_path(path + "\\*")) end - def self.next_entry(dir : DirHandle) : Entry? + def self.next_entry(dir : DirHandle, path : String) : Entry? if dir.handle == LibC::INVALID_HANDLE_VALUE # Directory is at start, use FindFirstFile handle = LibC.FindFirstFileW(dir.query, out data) @@ -27,11 +27,11 @@ module Crystal::System::Dir dir.handle = handle return data_to_entry(data) else - error = LibC.GetLastError + error = WinError.value if error == WinError::ERROR_FILE_NOT_FOUND return nil else - raise WinError.new("FindFirstFile", error) + raise ::File::Error.from_winerror("Error reading directory entries", error, file: path) end end else @@ -39,11 +39,11 @@ module Crystal::System::Dir if LibC.FindNextFileW(dir.handle, out data_) != 0 return data_to_entry(data_) else - error = LibC.GetLastError + error = WinError.value if error == WinError::ERROR_NO_MORE_FILES return nil else - raise WinError.new("FindNextFile", error) + raise ::File::Error.from_winerror("Error reading directory entries", error, file: path) end end end @@ -59,11 +59,11 @@ module Crystal::System::Dir close(dir) end - def self.close(dir : DirHandle) : Nil + def self.close(dir : DirHandle, path : String) : Nil return if dir.handle == LibC::INVALID_HANDLE_VALUE if LibC.FindClose(dir.handle) == 0 - raise WinError.new("FindClose") + raise ::File::Error.from_winerror("Error closing directory", file: path) end dir.handle = LibC::INVALID_HANDLE_VALUE @@ -77,14 +77,14 @@ module Crystal::System::Dir elsif small_buf && len > 0 next len else - raise WinError.new("Error while getting current directory") + raise ::File::Error.from_winerror("Error getting current directory", file: "./") end end end def self.current=(path : String) : String if LibC.SetCurrentDirectoryW(to_windows_path(path)) == 0 - raise WinError.new("SetCurrentDirectory") + raise ::File::Error.from_winerror("Error while changing directory", file: path) end path @@ -98,7 +98,7 @@ module Crystal::System::Dir elsif small_buf && len > 0 next len else - raise WinError.new("Error while getting current directory") + raise RuntimeError.from_winerror("Error getting temporary directory") end end @@ -107,13 +107,13 @@ module Crystal::System::Dir def self.create(path : String, mode : Int32) : Nil if LibC._wmkdir(to_windows_path(path)) == -1 - raise Errno.new("Unable to create directory '#{path}'") + raise ::File::Error.from_errno("Unable to create directory", file: path) end end def self.delete(path : String) : Nil if LibC._wrmdir(to_windows_path(path)) == -1 - raise Errno.new("Unable to remove directory '#{path}'") + raise ::File::Error.from_errno("Unable to remove directory", file: path) end end diff --git a/src/crystal/system/win32/env.cr b/src/crystal/system/win32/env.cr index 1bd71d020c7f..b908dfef1b4e 100644 --- a/src/crystal/system/win32/env.cr +++ b/src/crystal/system/win32/env.cr @@ -8,7 +8,7 @@ module Crystal::System::Env value.check_no_null_byte("value") if LibC.SetEnvironmentVariableW(key.to_utf16, value.to_utf16) == 0 - raise WinError.new("SetEnvironmentVariableW") + raise RuntimeError.from_winerror("SetEnvironmentVariableW") end end @@ -17,7 +17,7 @@ module Crystal::System::Env key.check_no_null_byte("key") if LibC.SetEnvironmentVariableW(key.to_utf16, nil) == 0 - raise WinError.new("SetEnvironmentVariableW") + raise RuntimeError.from_winerror("SetEnvironmentVariableW") end end @@ -37,13 +37,13 @@ module Crystal::System::Env elsif small_buf && length > 0 next length else - case last_error = LibC.GetLastError + case last_error = WinError.value when WinError::ERROR_SUCCESS return "" when WinError::ERROR_ENVVAR_NOT_FOUND return else - raise WinError.new("GetEnvironmentVariableW", last_error) + raise RuntimeError.from_winerror("GetEnvironmentVariableW", last_error) end end end @@ -60,7 +60,7 @@ module Crystal::System::Env # Iterates all environment variables. def self.each(&block : String, String ->) orig_pointer = pointer = LibC.GetEnvironmentStringsW - raise WinError.new("GetEnvironmentStringsW") if pointer.null? + raise RuntimeError.from_winerror("GetEnvironmentStringsW") if pointer.null? begin while !pointer.value.zero? diff --git a/src/crystal/system/win32/fiber.cr b/src/crystal/system/win32/fiber.cr index 7242ca5f532c..c987ed25b3b2 100644 --- a/src/crystal/system/win32/fiber.cr +++ b/src/crystal/system/win32/fiber.cr @@ -6,7 +6,7 @@ module Crystal::System::Fiber memory_pointer = LibC.VirtualAlloc(nil, stack_size, LibC::MEM_COMMIT | LibC::MEM_RESERVE, LibC::PAGE_READWRITE) if memory_pointer.null? - raise WinError.new("VirtualAlloc") + raise RuntimeError.from_winerror("VirtualAlloc") end memory_pointer @@ -14,7 +14,7 @@ module Crystal::System::Fiber def self.free_stack(stack : Void*, stack_size) : Nil if LibC.VirtualFree(stack, stack_size, LibC::MEM_RELEASE) == 0 - raise WinError.new("VirtualFree") + raise RuntimeError.from_winerror("VirtualFree") end end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 81ac7addb4c1..de8ea5c85835 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -19,7 +19,7 @@ module Crystal::System::File fd = LibC._wopen(to_windows_path(filename), oflag, perm) if fd == -1 - raise Errno.new("Error opening file #{filename.inspect} with mode #{mode.inspect}") + raise ::File::Error.from_errno("Error opening file with mode #{mode.inspect}", file: filename) end fd @@ -30,7 +30,7 @@ module Crystal::System::File fd = LibC._wopen(to_windows_path(path), LibC::O_RDWR | LibC::O_CREAT | LibC::O_EXCL | LibC::O_BINARY, ::File::DEFAULT_CREATE_PERMISSIONS) if fd == -1 - raise Errno.new("Error creating temporary file at #{path.inspect}") + raise ::File::Error.from_errno("Error creating temporary file", file: path) end {fd, path} @@ -44,12 +44,12 @@ module Crystal::System::File REPARSE_TAG_NAME_SURROGATE_MASK = 1 << 29 - private def self.check_not_found_error(func_name) - error = LibC.GetLastError + private def self.check_not_found_error(message, path) + error = WinError.value if NOT_FOUND_ERRORS.includes? error return nil else - raise WinError.new(func_name, error) + raise ::File::Error.from_winerror(message, error, file: path) end end @@ -64,15 +64,15 @@ module Crystal::System::File LibC::GET_FILEEX_INFO_LEVELS::GetFileExInfoStandard, pointerof(file_attributes) ) - return check_not_found_error("GetFileAttributesEx") if ret == 0 + return check_not_found_error("Unable to get file info", path) if ret == 0 if file_attributes.dwFileAttributes.bits_set? LibC::FILE_ATTRIBUTE_REPARSE_POINT # Could be a symlink, retrieve its reparse tag with FindFirstFile handle = LibC.FindFirstFileW(winpath, out find_data) - return check_not_found_error("FindFirstFile") if handle == LibC::INVALID_HANDLE_VALUE + return check_not_found_error("Unable to get file info", path) if handle == LibC::INVALID_HANDLE_VALUE if LibC.FindClose(handle) == 0 - raise WinError.new("FindClose") + raise RuntimeError.from_winerror("FindClose") end if find_data.dwReserved0.bits_set? REPARSE_TAG_NAME_SURROGATE_MASK @@ -91,11 +91,11 @@ module Crystal::System::File LibC::HANDLE.null ) - return check_not_found_error("CreateFile") if handle == LibC::INVALID_HANDLE_VALUE + return check_not_found_error("Unable to get file info", path) if handle == LibC::INVALID_HANDLE_VALUE begin if LibC.GetFileInformationByHandle(handle, out file_info) == 0 - raise WinError.new("GetFileInformationByHandle") + raise ::File::Error.from_winerror("Unable to get file info", file: path) end FileInfo.new(file_info, LibC::FILE_TYPE_DISK) @@ -135,7 +135,7 @@ module Crystal::System::File attributes = LibC.GetFileAttributesW(to_windows_path(path)) if attributes == LibC::INVALID_FILE_ATTRIBUTES - raise WinError.new("GetFileAttributes") + raise ::File::Error.from_winerror("Error changing permissions", file: path) end # Only the owner writable bit is used, since windows only supports @@ -147,13 +147,13 @@ module Crystal::System::File end if LibC.SetFileAttributesW(to_windows_path(path), attributes) == 0 - raise WinError.new("SetFileAttributes") + raise ::File::Error.from_winerror("Error changing permissions", file: path) end end def self.delete(path : String) : Nil if LibC._wunlink(to_windows_path(path)) != 0 - raise Errno.new("Error deleting file #{path.inspect}") + raise ::File::Error.from_errno("Error deleting file", file: path) end end @@ -168,12 +168,12 @@ module Crystal::System::File elsif small_buf && len > 0 next len else - raise WinError.new("Error resolving real path of #{path.inspect}") + raise ::File::Error.from_winerror("Error resolving real path", file: path) end end unless exists? real_path - raise Errno.new("Error resolving real path of #{path.inspect}", Errno::ENOTDIR) + raise ::File::Error.from_errno("Error resolving real path", Errno::ENOTDIR, file: path) end real_path @@ -181,14 +181,14 @@ module Crystal::System::File def self.link(old_path : String, new_path : String) : Nil if LibC.CreateHardLinkW(to_windows_path(new_path), to_windows_path(old_path), nil) == 0 - raise WinError.new("Error creating hard link from #{new_path.inspect} to #{old_path.inspect}") + raise ::File::Error.from_winerror("Error creating hard link", path: old_path, other: new_path) end end def self.symlink(old_path : String, new_path : String) : Nil # TODO: support directory symlinks (copy Go's stdlib logic here) if LibC.CreateSymbolicLinkW(to_windows_path(new_path), to_windows_path(old_path), 0) == 0 - raise WinError.new("Error creating symbolic link from #{new_path.inspect} to #{old_path.inspect}") + raise ::File::Error.from_winerror("Error creating symbolic link", path: old_path, other: new_path) end end @@ -198,7 +198,7 @@ module Crystal::System::File def self.rename(old_path : String, new_path : String) : Nil if LibC._wrename(to_windows_path(old_path), to_windows_path(new_path)) != 0 - raise Errno.new("Error renaming file from #{old_path.inspect} to #{new_path.inspect}") + raise ::File::Error.from_errno("Error renaming file", file: old_path, other: new_path) end end @@ -208,13 +208,13 @@ module Crystal::System::File times.modtime = modification_time.to_unix if LibC._wutime64(to_windows_path(path), pointerof(times)) != 0 - raise Errno.new("Error setting time on file #{path.inspect}") + raise ::File::Error.from_errno("Error setting time on file", file: path) end end private def system_truncate(size : Int) : Nil if LibC._chsize(fd, size) != 0 - raise Errno.new("Error truncating file #{path.inspect}") + raise ::File::Error.from_errno("Error truncating file", file: path) end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 1806a17b3f4d..174bf1e81575 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -9,7 +9,7 @@ module Crystal::System::FileDescriptor if Errno.value == Errno::EBADF raise IO::Error.new "File not open for reading" else - raise Errno.new("Error reading file") + raise IO::Error.from_errno("Error reading file") end end bytes_read @@ -22,7 +22,7 @@ module Crystal::System::FileDescriptor if Errno.value == Errno::EBADF raise IO::Error.new "File not open for writing" else - raise Errno.new("Error writing file") + raise IO::Error.from_errno("Error writing file") end end @@ -52,7 +52,7 @@ module Crystal::System::FileDescriptor private def windows_handle ret = LibC._get_osfhandle(fd) - raise Errno.new("_get_osfhandle") if ret == -1 + raise RuntimeError.from_errno("_get_osfhandle") if ret == -1 LibC::HANDLE.new(ret) end @@ -62,13 +62,13 @@ module Crystal::System::FileDescriptor file_type = LibC.GetFileType(handle) if file_type == LibC::FILE_TYPE_UNKNOWN - error = LibC.GetLastError - raise WinError.new("GetFileType", error) unless error == WinError::ERROR_SUCCESS + error = WinError.value + raise IO::Error.from_winerror("Unable to get info", error) unless error == WinError::ERROR_SUCCESS end if file_type == LibC::FILE_TYPE_DISK if LibC.GetFileInformationByHandle(handle, out file_info) == 0 - raise WinError.new("GetFileInformationByHandle") + raise IO::Error.from_winerror("Unable to get info") end FileInfo.new(file_info, file_type) @@ -81,13 +81,13 @@ module Crystal::System::FileDescriptor seek_value = LibC._lseek(fd, offset, whence) if seek_value == -1 - raise Errno.new "Unable to seek" + raise IO::Error.from_errno "Unable to seek" end end private def system_pos pos = LibC._lseek(fd, 0, IO::Seek::Current) - raise Errno.new "Unable to tell" if pos == -1 + raise IO::Error.from_errno "Unable to tell" if pos == -1 pos end @@ -100,12 +100,12 @@ module Crystal::System::FileDescriptor # dup doesn't copy the CLOEXEC flag, so copy it manually using dup3 flags = other.close_on_exec? ? LibC::O_CLOEXEC : 0 if LibC.dup3(other.fd, self.fd, flags) == -1 - raise Errno.new("Could not reopen file descriptor") + raise IO::Error.from_errno("Could not reopen file descriptor") end {% else %} # dup doesn't copy the CLOEXEC flag, copy it manually to the new if LibC._dup2(other.fd, self.fd) == -1 - raise Errno.new("Could not reopen file descriptor") + raise IO::Error.from_errno("Could not reopen file descriptor") end if other.close_on_exec? @@ -128,7 +128,7 @@ module Crystal::System::FileDescriptor when Errno::EINTR # ignore else - raise Errno.new("Error closing file") + raise IO::Error.from_errno("Error closing file") end end end @@ -136,7 +136,7 @@ module Crystal::System::FileDescriptor def self.pipe(read_blocking, write_blocking) pipe_fds = uninitialized StaticArray(LibC::Int, 2) if LibC._pipe(pipe_fds, 8192, LibC::O_BINARY) != 0 - raise Errno.new("Could not create pipe") + raise IO::Error.from_errno("Could not create pipe") end r = IO::FileDescriptor.new(pipe_fds[0], read_blocking) @@ -148,16 +148,16 @@ module Crystal::System::FileDescriptor def self.pread(fd, buffer, offset) handle = LibC._get_osfhandle(fd) - raise Errno.new("_get_osfhandle") if handle == -1 + raise IO::Error.from_errno("_get_osfhandle") if handle == -1 handle = LibC::HANDLE.new(handle) overlapped = LibC::OVERLAPPED.new overlapped.union.offset.offset = LibC::DWORD.new(offset) overlapped.union.offset.offsetHigh = LibC::DWORD.new(offset >> 32) if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0 - error = LibC.GetLastError + error = WinError.value return 0 if error == WinError::ERROR_HANDLE_EOF - raise WinError.new("ReadFile", error) + raise IO::Error.from_winerror "Error reading file", error end bytes_read diff --git a/src/crystal/system/win32/hostname.cr b/src/crystal/system/win32/hostname.cr index 2e92d076858b..cea9320dd255 100644 --- a/src/crystal/system/win32/hostname.cr +++ b/src/crystal/system/win32/hostname.cr @@ -9,7 +9,7 @@ module Crystal::System elsif small_buf && name_size > 0 next name_size else - raise WinError.new("GetComputerNameExW") + raise RuntimeError.from_winerror("Could not get hostname") end end end diff --git a/src/crystal/system/win32/random.cr b/src/crystal/system/win32/random.cr index 7067a6fab9aa..03c54fa8aa40 100644 --- a/src/crystal/system/win32/random.cr +++ b/src/crystal/system/win32/random.cr @@ -3,7 +3,7 @@ require "c/ntsecapi" module Crystal::System::Random def self.random_bytes(buf : Bytes) : Nil if LibC.RtlGenRandom(buf, buf.size) == 0 - raise WinError.new("RtlGenRandom") + raise RuntimeError.from_winerror("RtlGenRandom") end end diff --git a/src/crystal/system/win32/time.cr b/src/crystal/system/win32/time.cr index 41548e4c02ce..85089186964b 100644 --- a/src/crystal/system/win32/time.cr +++ b/src/crystal/system/win32/time.cr @@ -38,7 +38,7 @@ module Crystal::System::Time @@performance_frequency : Int64 = begin ret = LibC.QueryPerformanceFrequency(out frequency) if ret == 0 - raise WinError.new("QueryPerformanceFrequency") + raise RuntimeError.from_winerror("QueryPerformanceFrequency") end frequency @@ -46,7 +46,7 @@ module Crystal::System::Time def self.monotonic : {Int64, Int32} if LibC.QueryPerformanceCounter(out ticks) == 0 - raise WinError.new("QueryPerformanceCounter") + raise RuntimeError.from_winerror("QueryPerformanceCounter") end {ticks // @@performance_frequency, (ticks.remainder(@@performance_frequency) * NANOSECONDS_PER_SECOND / @@performance_frequency).to_i32} diff --git a/src/dir.cr b/src/dir.cr index a1bc4965766b..3d5dd74f7676 100644 --- a/src/dir.cr +++ b/src/dir.cr @@ -120,7 +120,7 @@ class Dir # array.sort # => [".", "..", "config.h"] # ``` def read - Crystal::System::Dir.next(@dir) + Crystal::System::Dir.next(@dir, path) end # Repositions this directory to the first entry. @@ -132,7 +132,7 @@ class Dir # Closes the directory stream. def close return if @closed - Crystal::System::Dir.close(@dir) + Crystal::System::Dir.close(@dir, path) @closed = true end @@ -210,7 +210,7 @@ class Dir end # Returns `true` if the directory at *path* is empty, otherwise returns `false`. - # Raises `Errno` if the directory at *path* does not exist. + # Raises `File::NotFoundError` if the directory at *path* does not exist. # # ``` # Dir.mkdir("bar") @@ -223,8 +223,6 @@ class Dir return false end true - rescue ex : Errno - raise Errno.new("Error determining size of '#{path}'", ex.errno) end # Creates a new directory at the given path. The linux-style permission mode diff --git a/src/dir/glob.cr b/src/dir/glob.cr index a3967022b8f5..28d2e4cfe1ae 100644 --- a/src/dir/glob.cr +++ b/src/dir/glob.cr @@ -208,7 +208,7 @@ class Dir begin dir = Dir.new(path || ".") dir_stack << dir - rescue Errno + rescue File::Error return end recurse = false @@ -217,7 +217,7 @@ class Dir if recurse begin dir = Dir.new(dir_path) - rescue Errno + rescue File::Error dir_path_stack.pop break if dir_path_stack.empty? dir_path = dir_path_stack.last @@ -299,8 +299,7 @@ class Dir yield entry end end - rescue exc : Errno - raise exc unless exc.errno == Errno::ENOENT + rescue exc : File::NotFoundError end private def self.read_entry(dir) @@ -309,7 +308,7 @@ class Dir # By doing this we get an Entry struct which already tells us # whether something is a directory or not, avoiding having to # call File.info? which is really expensive. - Crystal::System::Dir.next_entry(dir.@dir) + Crystal::System::Dir.next_entry(dir.@dir, dir.path) end end end diff --git a/src/ecr/process.cr b/src/ecr/process.cr index ccbbb401d76e..0e394bc5db1b 100644 --- a/src/ecr/process.cr +++ b/src/ecr/process.cr @@ -5,11 +5,7 @@ buffer_name = ARGV[1] begin puts ECR.process_file(filename, buffer_name) -rescue ex : Errno - if ex.errno.in?(Errno::ENOENT, Errno::EISDIR) - STDERR.puts ex.message - exit 1 - else - raise ex - end +rescue ex : File::Error + STDERR.puts ex.message + exit 1 end diff --git a/src/errno.cr b/src/errno.cr index 8b4b6af7b022..0c69d07ec0fe 100644 --- a/src/errno.cr +++ b/src/errno.cr @@ -16,232 +16,52 @@ end # Errno wraps and gives access to libc's errno. This is mostly useful when # dealing with C libraries. -# -# This class is the exception thrown when errno errors are encountered. -class Errno < Exception - # Argument list too long - E2BIG = LibC::E2BIG - # Operation not permitted - EPERM = LibC::EPERM - # No such file or directory - ENOENT = LibC::ENOENT - # No such process - ESRCH = LibC::ESRCH - # Interrupted system call - EINTR = LibC::EINTR - # Input/output error - EIO = LibC::EIO - # Device not configured - ENXIO = LibC::ENXIO - # Exec format error - ENOEXEC = LibC::ENOEXEC - # Bad file descriptor - EBADF = LibC::EBADF - # No child processes - ECHILD = LibC::ECHILD - # Resource deadlock avoided - EDEADLK = LibC::EDEADLK - # Cannot allocate memory - ENOMEM = LibC::ENOMEM - # Permission denied - EACCES = LibC::EACCES - # Bad address - EFAULT = LibC::EFAULT - # Block device required - ENOTBLK = LibC::ENOTBLK - # Device / Resource busy - EBUSY = LibC::EBUSY - # File exists - EEXIST = LibC::EEXIST - # Cross-device link - EXDEV = LibC::EXDEV - # Operation not supported by device - ENODEV = LibC::ENODEV - # Not a directory - ENOTDIR = LibC::ENOTDIR - # Is a directory - EISDIR = LibC::EISDIR - # Invalid argument - EINVAL = LibC::EINVAL - # Too many open files in system - ENFILE = LibC::ENFILE - # Too many open files - EMFILE = LibC::EMFILE - # Inappropriate ioctl for device - ENOTTY = LibC::ENOTTY - # Text file busy - ETXTBSY = LibC::ETXTBSY - # File too large - EFBIG = LibC::EFBIG - # No space left on device - ENOSPC = LibC::ENOSPC - # Illegal seek - ESPIPE = LibC::ESPIPE - # Read-only file system - EROFS = LibC::EROFS - # Too many links - EMLINK = LibC::EMLINK - # Broken pipe - EPIPE = LibC::EPIPE - # Numerical argument out of domain - EDOM = LibC::EDOM - # Result too large - ERANGE = LibC::ERANGE - # Resource temporarily unavailable - EAGAIN = LibC::EAGAIN - # Operation would block - EWOULDBLOCK = LibC::EWOULDBLOCK - # Operation now in progress - EINPROGRESS = LibC::EINPROGRESS - # Operation already in progress - EALREADY = LibC::EALREADY - # Socket operation on non-socket - ENOTSOCK = LibC::ENOTSOCK - # Destination address required - EDESTADDRREQ = LibC::EDESTADDRREQ - # Message too long - EMSGSIZE = LibC::EMSGSIZE - # Protocol wrong type for socket - EPROTOTYPE = LibC::EPROTOTYPE - # Protocol not available - ENOPROTOOPT = LibC::ENOPROTOOPT - # Protocol not supported - EPROTONOSUPPORT = LibC::EPROTONOSUPPORT - # Socket type not supported - ESOCKTNOSUPPORT = LibC::ESOCKTNOSUPPORT - # Protocol family not supported - EPFNOSUPPORT = LibC::EPFNOSUPPORT - # Address family not supported by protocol family - EAFNOSUPPORT = LibC::EAFNOSUPPORT - # Address already in use - EADDRINUSE = LibC::EADDRINUSE - # Can't assign requested address - EADDRNOTAVAIL = LibC::EADDRNOTAVAIL - # Network is down - ENETDOWN = LibC::ENETDOWN - # Network is unreachable - ENETUNREACH = LibC::ENETUNREACH - # Network dropped connection on reset - ENETRESET = LibC::ENETRESET - # Software caused connection abort - ECONNABORTED = LibC::ECONNABORTED - # Connection reset by peer - ECONNRESET = LibC::ECONNRESET - # No buffer space available - ENOBUFS = LibC::ENOBUFS - # Socket is already connected - EISCONN = LibC::EISCONN - # Socket is not connected - ENOTCONN = LibC::ENOTCONN - # Can't send after socket shutdown - ESHUTDOWN = LibC::ESHUTDOWN - # Too many references: can't splice - ETOOMANYREFS = LibC::ETOOMANYREFS - # Operation timed out - ETIMEDOUT = LibC::ETIMEDOUT - # Connection refused - ECONNREFUSED = LibC::ECONNREFUSED - # Too many levels of symbolic links - ELOOP = LibC::ELOOP - # File name too long - ENAMETOOLONG = LibC::ENAMETOOLONG - # Host is down - EHOSTDOWN = LibC::EHOSTDOWN - # No route to host - EHOSTUNREACH = LibC::EHOSTUNREACH - # Directory not empty - ENOTEMPTY = LibC::ENOTEMPTY - # Too many users - EUSERS = LibC::EUSERS - # Disc quota exceeded - EDQUOT = LibC::EDQUOT - # Stale NFS file handle - ESTALE = LibC::ESTALE - # Too many levels of remote in path - EREMOTE = LibC::EREMOTE - # No locks available - ENOLCK = LibC::ENOLCK - # Function not implemented - ENOSYS = LibC::ENOSYS - # Value too large to be stored in data type - EOVERFLOW = LibC::EOVERFLOW - # Operation canceled - ECANCELED = LibC::ECANCELED - # Identifier removed - EIDRM = LibC::EIDRM - # No message of desired type - ENOMSG = LibC::ENOMSG - # Illegal byte sequence - EILSEQ = LibC::EILSEQ - # Bad message - EBADMSG = LibC::EBADMSG - # Reserved - EMULTIHOP = LibC::EMULTIHOP - # No message available on STREAM - ENODATA = LibC::ENODATA - # Reserved - ENOLINK = LibC::ENOLINK - # No STREAM resources - ENOSR = LibC::ENOSR - # Not a STREAM - ENOSTR = LibC::ENOSTR - # Protocol error - EPROTO = LibC::EPROTO - # STREAM ioctl timeout - ETIME = LibC::ETIME - # Operation not supported on socket - EOPNOTSUPP = LibC::EOPNOTSUPP - # State not recoverable - ENOTRECOVERABLE = LibC::ENOTRECOVERABLE - # Previous owner died - EOWNERDEAD = LibC::EOWNERDEAD +enum Errno + NONE = 0 - # Returns the numeric value of errno. - getter errno : Int32 - - # Returns the message of errno. - getter errno_message : String + {% for value in %w(E2BIG EPERM ENOENT ESRCH EINTR EIO ENXIO ENOEXEC EBADF ECHILD EDEADLK ENOMEM + EACCES EFAULT ENOTBLK EBUSY EEXIST EXDEV ENODEV ENOTDIR EISDIR EINVAL ENFILE + EMFILE ENOTTY ETXTBSY EFBIG ENOSPC ESPIPE EROFS EMLINK EPIPE EDOM ERANGE EAGAIN + EWOULDBLOCK EINPROGRESS EALREADY ENOTSOCK EDESTADDRREQ EMSGSIZE EPROTOTYPE ENOPROTOOPT + EPROTONOSUPPORT ESOCKTNOSUPPORT EPFNOSUPPORT EAFNOSUPPORT EADDRINUSE EADDRNOTAVAIL + ENETDOWN ENETUNREACH ENETRESET ECONNABORTED ECONNRESET ENOBUFS EISCONN ENOTCONN + ESHUTDOWN ETOOMANYREFS ETIMEDOUT ECONNREFUSED ELOOP ENAMETOOLONG EHOSTDOWN + EHOSTUNREACH ENOTEMPTY EUSERS EDQUOT ESTALE EREMOTE ENOLCK ENOSYS EOVERFLOW + ECANCELED EIDRM ENOMSG EILSEQ EBADMSG EMULTIHOP ENODATA ENOLINK ENOSR ENOSTR + EPROTO ETIME EOPNOTSUPP ENOTRECOVERABLE EOWNERDEAD) %} + {% if LibC.has_constant?(value) %} + {{value.id}} = LibC::{{value.id}} + {% end %} + {% end %} - # Creates a new `Errno` with the given message. The message will - # have concatenated the errno message denoted by *errno*. - # - # Typical usage: - # - # ``` - # err = LibC.some_call - # if err == -1 - # raise Errno.new("some_call") - # end - # ``` - def initialize(message, errno = Errno.value) - @errno = errno - @errno_message = String.new(LibC.strerror(@errno)) - super "#{message}: #{@errno_message}" + # Convert an Errno to an error message + def message : String + String.new(LibC.strerror(value)) end # Returns the value of libc's errno. - def self.value : LibC::Int + def self.value : self {% if flag?(:linux) %} - LibC.__errno_location.value + Errno.new LibC.__errno_location.value {% elsif flag?(:darwin) || flag?(:freebsd) || flag?(:openbsd) %} - LibC.__error.value + Errno.new LibC.__error.value {% elsif flag?(:win32) %} ret = LibC._get_errno(out errno) - raise Errno.new("_get_errno", ret) unless ret == 0 - errno + raise RuntimeError.from_errno("_get_errno", Errno.new(ret)) unless ret == 0 + Errno.new errno {% end %} end # Sets the value of libc's errno. - def self.value=(value) + def self.value=(errno : Errno) {% if flag?(:linux) %} - LibC.__errno_location.value = value + LibC.__errno_location.value = errno.value {% elsif flag?(:darwin) || flag?(:freebsd) || flag?(:openbsd) %} - LibC.__error.value = value + LibC.__error.value = errno.value {% elsif flag?(:win32) %} - ret = LibC._set_errno(value) - raise Errno.new("_set_errno", ret) unless ret == 0 - value + ret = LibC._set_errno(errno.value) + raise RuntimeError.from_errno("_set_errno", ret) unless ret == 0 {% end %} + errno end end diff --git a/src/exception.cr b/src/exception.cr index d9c763dcbb69..4635ac1dc6a6 100644 --- a/src/exception.cr +++ b/src/exception.cr @@ -1,4 +1,5 @@ require "callstack" +require "system_error" CallStack.skip(__FILE__) @@ -163,3 +164,8 @@ class NilAssertionError < Exception super(message) end end + +# Raised when there is an internal runtime error +class RuntimeError < Exception + include SystemError +end diff --git a/src/file.cr b/src/file.cr index 70a62ff69d67..063c45d2d7a2 100644 --- a/src/file.cr +++ b/src/file.cr @@ -1,3 +1,7 @@ +class File < IO::FileDescriptor +end + +require "./file/error" require "crystal/system/file" # A `File` instance represents a file entry in the local file system and allows using it as an `IO`. @@ -129,7 +133,7 @@ class File < IO::FileDescriptor end # Returns a `File::Info` object for the file given by *path* or raises - # `Errno` in case of an error. + # `File::Error` in case of an error. # # If *follow_symlinks* is set (the default), symbolic links are followed. Otherwise, # symbolic links return information on the symlink itself. @@ -143,7 +147,7 @@ class File < IO::FileDescriptor # File.info("bar", follow_symlinks: false).type.symlink? # => true # ``` def self.info(path : Path | String, follow_symlinks = true) : Info - info?(path, follow_symlinks) || raise Errno.new("Unable to get info for '#{path.to_s.inspect_unquoted}''") + info?(path, follow_symlinks) || raise File::Error.from_errno("Unable to get file info", file: path.to_s) end # Returns `true` if *path* exists else returns `false` @@ -165,21 +169,19 @@ class File < IO::FileDescriptor end # Returns the size of the file at *filename* in bytes. - # Raises `Errno` if the file at *filename* does not exist. + # Raises `File::NotFoundError` if the file at *filename* does not exist. # # ``` - # File.size("foo") # raises Errno + # File.size("foo") # raises File::NotFoundError # File.write("foo", "foo") # File.size("foo") # => 3 # ``` def self.size(filename : Path | String) : UInt64 info(filename).size - rescue ex : Errno - raise Errno.new("Error determining size of '#{filename.to_s.inspect_unquoted}'", ex.errno) end # Returns `true` if the file at *path* is empty, otherwise returns `false`. - # Raises `Errno` if the file at *path* does not exist. + # Raises `File::NotFoundError` if the file at *path* does not exist. # # ``` # File.write("foo", "") @@ -320,7 +322,7 @@ class File < IO::FileDescriptor # ``` # File.write("foo", "") # File.delete("./foo") - # File.delete("./bar") # raises Errno (No such file or directory) + # File.delete("./bar") # raises File::NotFoundError (No such file or directory) # ``` def self.delete(path : Path | String) Crystal::System::File.delete(path.to_s) @@ -808,7 +810,7 @@ class File < IO::FileDescriptor end # Places a shared advisory lock. More than one process may hold a shared lock for a given file at a given time. - # `Errno::EWOULDBLOCK` is raised if *blocking* is set to `false` and an existing exclusive lock is set. + # `IO::Error` is raised if *blocking* is set to `false` and an existing exclusive lock is set. def flock_shared(blocking = true) system_flock_shared(blocking) end @@ -823,7 +825,7 @@ class File < IO::FileDescriptor end # Places an exclusive advisory lock. Only one process may hold an exclusive lock for a given file at a given time. - # `Errno::EWOULDBLOCK` is raised if *blocking* is set to `false` and any existing lock is set. + # `IO::Error` is raised if *blocking* is set to `false` and any existing lock is set. def flock_exclusive(blocking = true) system_flock_exclusive(blocking) end diff --git a/src/file/error.cr b/src/file/error.cr new file mode 100644 index 000000000000..64d3f2a0554d --- /dev/null +++ b/src/file/error.cr @@ -0,0 +1,41 @@ +class File < IO::FileDescriptor +end + +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 + File::NotFoundError.new(message, **opts) + when Errno::EEXIST + File::AlreadyExistsError.new(message, **opts) + when Errno::EACCES + File::AccessDeniedError.new(message, **opts) + else + super message, errno, **opts + end + end + + protected def self.build_message(message, *, file : String) + "#{message}: '#{file.inspect_unquoted}'" + end + + protected def self.build_message(message, *, file : String, other : String) + "#{message}: '#{file.inspect_unquoted}' -> '#{other.inspect_unquoted}'" + end + + def initialize(message, *, @file : String, @other : String? = nil) + super message + end +end + +class File::NotFoundError < File::Error +end + +class File::AlreadyExistsError < File::Error +end + +class File::AccessDeniedError < File::Error +end diff --git a/src/file_utils.cr b/src/file_utils.cr index 964884dac26c..5885ef0fd368 100644 --- a/src/file_utils.cr +++ b/src/file_utils.cr @@ -355,7 +355,7 @@ module FileUtils srcs.each do |src| begin mv(src, File.join(dest, File.basename(src))) - rescue Errno + rescue File::Error end end end @@ -448,7 +448,7 @@ module FileUtils def rm_rf(path : String) : Nil begin rm_r(path) - rescue Errno + rescue File::Error end end @@ -465,7 +465,7 @@ module FileUtils paths.each do |path| begin rm_r(path) - rescue Errno + rescue File::Error end end end diff --git a/src/gc/boehm.cr b/src/gc/boehm.cr index 717d565c1323..90119ecd2c9a 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 Errno.new("pthread_join", ret) unless ret == 0 + raise RuntimeError.from_errno("pthread_join", Errno.new(ret)) unless ret == 0 value end diff --git a/src/gc/none.cr b/src/gc/none.cr index 11743595b929..1c1690a0e368 100644 --- a/src/gc/none.cr +++ b/src/gc/none.cr @@ -72,7 +72,7 @@ module GC # :nodoc: def self.pthread_join(thread : LibC::PthreadT) : Void* ret = LibC.pthread_join(thread, out value) - raise Errno.new("pthread_join") unless ret == 0 + raise RuntimeError.from_errno("pthread_join") unless ret == 0 value end diff --git a/src/http/client.cr b/src/http/client.cr index cb4c1939520b..958f617e1413 100644 --- a/src/http/client.cr +++ b/src/http/client.cr @@ -262,7 +262,7 @@ class HTTP::Client end end - # Sets the number of seconds to wait when reading before raising an `IO::Timeout`. + # Sets the number of seconds to wait when reading before raising an `IO::TimeoutError`. # # ``` # require "http/client" @@ -271,7 +271,7 @@ class HTTP::Client # client.read_timeout = 1.5 # begin # response = client.get("/") - # rescue IO::Timeout + # rescue IO::TimeoutError # puts "Timeout!" # end # ``` @@ -279,7 +279,7 @@ class HTTP::Client @read_timeout = read_timeout.to_f end - # Sets the read timeout with a `Time::Span`, to wait when reading before raising an `IO::Timeout`. + # Sets the read timeout with a `Time::Span`, to wait when reading before raising an `IO::TimeoutError`. # # ``` # require "http/client" @@ -288,7 +288,7 @@ class HTTP::Client # client.read_timeout = 5.minutes # begin # response = client.get("/") - # rescue IO::Timeout + # rescue IO::TimeoutError # puts "Timeout!" # end # ``` @@ -297,18 +297,18 @@ class HTTP::Client end # Sets the write timeout - if any chunk of request is not written - # within the number of seconds provided, `IO::Timeout` exception is raised. + # within the number of seconds provided, `IO::TimeoutError` exception is raised. def write_timeout=(write_timeout : Number) @write_timeout = write_timeout.to_f end # Sets the write timeout - if any chunk of request is not written - # within the provided `Time::Span`, `IO::Timeout` exception is raised. + # within the provided `Time::Span`, `IO::TimeoutError` exception is raised. def write_timeout=(write_timeout : Time::Span) self.write_timeout = write_timeout.total_seconds end - # Sets the number of seconds to wait when connecting, before raising an `IO::Timeout`. + # Sets the number of seconds to wait when connecting, before raising an `IO::TimeoutError`. # # ``` # require "http/client" @@ -317,7 +317,7 @@ class HTTP::Client # client.connect_timeout = 1.5 # begin # response = client.get("/") - # rescue IO::Timeout + # rescue IO::TimeoutError # puts "Timeout!" # end # ``` @@ -325,7 +325,7 @@ class HTTP::Client @connect_timeout = connect_timeout.to_f end - # Sets the open timeout with a `Time::Span` to wait when connecting, before raising an `IO::Timeout`. + # Sets the open timeout with a `Time::Span` to wait when connecting, before raising an `IO::TimeoutError`. # # ``` # require "http/client" @@ -334,7 +334,7 @@ class HTTP::Client # client.connect_timeout = 5.minutes # begin # response = client.get("/") - # rescue IO::Timeout + # rescue IO::TimeoutError # puts "Timeout!" # end # ``` @@ -344,7 +344,7 @@ class HTTP::Client # **This method has no effect right now** # - # Sets the number of seconds to wait when resolving a name, before raising an `IO::Timeout`. + # Sets the number of seconds to wait when resolving a name, before raising an `IO::TimeoutError`. # # ``` # require "http/client" @@ -353,7 +353,7 @@ class HTTP::Client # client.dns_timeout = 1.5 # begin # response = client.get("/") - # rescue IO::Timeout + # rescue IO::TimeoutError # puts "Timeout!" # end # ``` @@ -363,7 +363,7 @@ class HTTP::Client # **This method has no effect right now** # - # Sets the number of seconds to wait when resolving a name with a `Time::Span`, before raising an `IO::Timeout`. + # Sets the number of seconds to wait when resolving a name with a `Time::Span`, before raising an `IO::TimeoutError`. # # ``` # require "http/client" @@ -372,7 +372,7 @@ class HTTP::Client # client.dns_timeout = 1.5.seconds # begin # response = client.get("/") - # rescue IO::Timeout + # rescue IO::TimeoutError # puts "Timeout!" # end # ``` diff --git a/src/http/server/request_processor.cr b/src/http/server/request_processor.cr index baab84ad9b48..9bde7e4836ab 100644 --- a/src/http/server/request_processor.cr +++ b/src/http/server/request_processor.cr @@ -80,12 +80,12 @@ class HTTP::Server::RequestProcessor break unless body.closed? end end - rescue ex : Errno + rescue IO::Error # IO-related error, nothing to do ensure begin input.close if must_close - rescue ex : Errno + rescue IO::Error # IO-related error, nothing to do end end diff --git a/src/io.cr b/src/io.cr index ba74d9ad2c8f..710282f8d6ca 100644 --- a/src/io.cr +++ b/src/io.cr @@ -77,15 +77,6 @@ abstract class IO @encoder : Encoder? @decoder : Decoder? - # Raised when an `IO` operation times out. - # - # ``` - # STDIN.read_timeout = 1 - # STDIN.gets # raises IO::Timeout (after 1 second) - # ``` - class Timeout < Exception - end - # Reads at most *slice.size* bytes from this `IO` into *slice*. # Returns the number of bytes read, which is 0 if and only if there is no # more data to read (so checking for 0 is the way to detect end of file). diff --git a/src/io/console.cr b/src/io/console.cr index 6ce463c3d38e..849a82af2c97 100644 --- a/src/io/console.cr +++ b/src/io/console.cr @@ -24,7 +24,7 @@ class IO::FileDescriptor < IO # Only call this when this IO is a TTY, such as a not redirected stdin. def noecho! if LibC.tcgetattr(fd, out mode) != 0 - raise Errno.new "can't set IO#noecho!" + raise IO::Error.from_errno "can't set IO#noecho!" end noecho_from_tc_mode! end @@ -53,7 +53,7 @@ class IO::FileDescriptor < IO # Only call this when this IO is a TTY, such as a not redirected stdin. def cooked! if LibC.tcgetattr(fd, out mode) != 0 - raise Errno.new "can't set IO#cooked!" + raise IO::Error.from_errno "can't set IO#cooked!" end cooked_from_tc_mode! end @@ -91,7 +91,7 @@ class IO::FileDescriptor < IO # Only call this when this IO is a TTY, such as a not redirected stdin. def raw! if LibC.tcgetattr(fd, out mode) != 0 - raise Errno.new "can't set IO#raw!" + raise IO::Error.from_errno "can't set IO#raw!" end raw_from_tc_mode! @@ -104,7 +104,7 @@ class IO::FileDescriptor < IO private def preserving_tc_mode(msg) if LibC.tcgetattr(fd, out mode) != 0 - raise Errno.new msg + raise IO::Error.from_errno msg end before = mode begin diff --git a/src/io/error.cr b/src/io/error.cr index b625207544aa..b611dc523818 100644 --- a/src/io/error.cr +++ b/src/io/error.cr @@ -1,5 +1,15 @@ class IO class Error < Exception + include SystemError + end + + # Raised when an `IO` operation times out. + # + # ``` + # STDIN.read_timeout = 1 + # STDIN.gets # raises IO::TimeoutError (after 1 second) + # ``` + class TimeoutError < Error end class EOFError < Error diff --git a/src/io/evented.cr b/src/io/evented.cr index 7d8a4f799f5d..15e802eb1c07 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -15,33 +15,33 @@ module IO::Evented @read_event = Crystal::ThreadLocalValue(Crystal::Event).new @write_event = Crystal::ThreadLocalValue(Crystal::Event).new - # Returns the time to wait when reading before raising an `IO::Timeout`. + # Returns the time to wait when reading before raising an `IO::TimeoutError`. def read_timeout : Time::Span? @read_timeout end - # Sets the time to wait when reading before raising an `IO::Timeout`. + # Sets the time to wait when reading before raising an `IO::TimeoutError`. def read_timeout=(timeout : Time::Span?) : ::Time::Span? @read_timeout = timeout end - # Sets the number of seconds to wait when reading before raising an `IO::Timeout`. + # Sets the number of seconds to wait when reading before raising an `IO::TimeoutError`. def read_timeout=(read_timeout : Number) : Number self.read_timeout = read_timeout.seconds read_timeout end - # Returns the time to wait when writing before raising an `IO::Timeout`. + # Returns the time to wait when writing before raising an `IO::TimeoutError`. def write_timeout : Time::Span? @write_timeout end - # Sets the time to wait when writing before raising an `IO::Timeout`. + # Sets the time to wait when writing before raising an `IO::TimeoutError`. def write_timeout=(timeout : Time::Span?) : ::Time::Span? @write_timeout = timeout end - # Sets the number of seconds to wait when writing before raising an `IO::Timeout`. + # Sets the number of seconds to wait when writing before raising an `IO::TimeoutError`. def write_timeout=(write_timeout : Number) : Number self.write_timeout = write_timeout.seconds write_timeout @@ -58,7 +58,7 @@ module IO::Evented if Errno.value == Errno::EAGAIN wait_readable else - raise Errno.new(errno_msg) + raise IO::Error.from_errno(errno_msg) end end ensure @@ -79,7 +79,7 @@ module IO::Evented if Errno.value == Errno::EAGAIN wait_writable else - raise Errno.new(errno_msg) + raise IO::Error.from_errno(errno_msg) end end end @@ -90,7 +90,7 @@ module IO::Evented def evented_send(slice : Bytes, errno_msg : String) : Int32 bytes_written = yield slice - raise Errno.new(errno_msg) if bytes_written == -1 + raise Socket::Error.from_errno(errno_msg) if bytes_written == -1 # `to_i32` is acceptable because `Slice#size` is an Int32 bytes_written.to_i32 ensure @@ -129,7 +129,7 @@ module IO::Evented if @read_timed_out @read_timed_out = false - yield Timeout.new("Read timed out") + yield TimeoutError.new("Read timed out") end check_open @@ -154,7 +154,7 @@ module IO::Evented if @write_timed_out @write_timed_out = false - yield Timeout.new("Write timed out") + yield TimeoutError.new("Write timed out") end check_open diff --git a/src/kernel.cr b/src/kernel.cr index 28c80364df8e..9afc1618c398 100644 --- a/src/kernel.cr +++ b/src/kernel.cr @@ -66,7 +66,7 @@ ARGV = Array.new(ARGC_UNSAFE - 1) { |i| String.new(ARGV_UNSAFE[1 + i]) } # $ echo "hello" | ./program # hello # $ ./program unknown -# Unhandled exception: Error opening file 'unknown' with mode 'r': No such file or directory (Errno) +# Unhandled exception: Error opening file with mode 'r': 'unknown': No such file or directory (File::NotFoundError) # ... # ``` # diff --git a/src/openssl.cr b/src/openssl.cr index 307f7ce4c435..f06899aa2051 100644 --- a/src/openssl.cr +++ b/src/openssl.cr @@ -112,7 +112,7 @@ module OpenSSL message = "Unexpected EOF" @underlying_eof = true when -1 - cause = Errno.new(func || "OpenSSL") + cause = RuntimeError.from_errno(func || "OpenSSL") message = "I/O error" else message = "Unknown error" diff --git a/src/openssl/ssl/socket.cr b/src/openssl/ssl/socket.cr index 0ff4a6e901b8..05e99f137686 100644 --- a/src/openssl/ssl/socket.cr +++ b/src/openssl/ssl/socket.cr @@ -178,7 +178,7 @@ abstract class OpenSSL::SSL::Socket < IO # ret == 0, retry, shutdown is not complete yet end - rescue IO::Error | Errno + rescue IO::Error ensure @bio.io.close if @sync_close end diff --git a/src/process.cr b/src/process.cr index 394a3e780ffe..cf94409eacbd 100644 --- a/src/process.cr +++ b/src/process.cr @@ -26,7 +26,7 @@ class Process # Returns the process group identifier of the process identified by *pid*. def self.pgid(pid : Int32) : LibC::PidT ret = LibC.getpgid(pid) - raise Errno.new("getpgid") if ret < 0 + raise RuntimeError.from_errno("getpgid") if ret < 0 ret end @@ -39,7 +39,7 @@ class Process def self.kill(signal : Signal, *pids : Int) pids.each do |pid| ret = LibC.kill(pid, signal.value) - raise Errno.new("kill") if ret < 0 + raise RuntimeError.from_errno("kill") if ret < 0 end nil end @@ -53,7 +53,7 @@ class Process true else return false if Errno.value == Errno::ESRCH - raise Errno.new("kill") + raise RuntimeError.from_errno("kill") end end @@ -121,7 +121,7 @@ class Process LibC.sigfillset(pointerof(newmask)) ret = LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(newmask), pointerof(oldmask)) - raise Errno.new("Failed to disable signals") unless ret == 0 + raise RuntimeError.from_errno("Failed to disable signals") unless ret == 0 case pid = LibC.fork when 0 @@ -142,7 +142,7 @@ class Process # error: errno = Errno.value LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(oldmask), nil) - raise Errno.new("fork", errno) + raise RuntimeError.from_errno("fork", errno) else # parent: LibC.pthread_sigmask(LibC::SIG_SETMASK, pointerof(oldmask), nil) @@ -265,14 +265,10 @@ class Process reader_pipe.close writer_pipe.close_on_exec = true Process.exec_internal(command, args, env, clear_env, fork_input, fork_output, fork_error, chdir) - rescue ex : Errno - writer_pipe.write_bytes(ex.errno) + rescue ex writer_pipe.write_bytes(ex.message.try(&.bytesize) || 0) writer_pipe << ex.message writer_pipe.close - rescue ex - ex.inspect_with_backtrace STDERR - STDERR.flush ensure LibC._exit 127 end @@ -281,13 +277,12 @@ class Process writer_pipe.close bytes = uninitialized UInt8[4] if reader_pipe.read(bytes.to_slice) == 4 - errno = IO::ByteFormat::SystemEndian.decode(Int32, bytes.to_slice) - message_size = reader_pipe.read_bytes(Int32) + message_size = IO::ByteFormat::SystemEndian.decode(Int32, bytes.to_slice) if message_size > 0 message = String.build(message_size) { |io| IO.copy(reader_pipe, io, message_size) } end reader_pipe.close - raise Errno.new(message, errno) + raise RuntimeError.new("Error executing process: #{message}") end reader_pipe.close @@ -479,17 +474,7 @@ class Process argv << Pointer(UInt8).null LibC.execvp(command, argv) - - error_message = String.build do |io| - io << "execvp (" - command.inspect_unquoted(io) - args.try &.each do |arg| - io << ' ' - arg.inspect(io) - end - io << ")" - end - raise Errno.new(error_message) + raise RuntimeError.from_errno end private def self.reopen_io(src_io : IO::FileDescriptor, dst_io : IO::FileDescriptor) @@ -528,11 +513,11 @@ class Process def self.chroot(path : String) : Nil path.check_no_null_byte if LibC.chroot(path) != 0 - raise Errno.new("Failed to chroot") + raise RuntimeError.from_errno("Failed to chroot") end if LibC.chdir("/") != 0 - errno = Errno.new("chdir after chroot failed") + errno = RuntimeError.from_errno("chdir after chroot failed") errno.callstack = CallStack.new errno.inspect_with_backtrace(STDERR) abort("Unresolvable state, exiting...") diff --git a/src/process/executable_path.cr b/src/process/executable_path.cr index 717120d50c4f..db4a7859fe55 100644 --- a/src/process/executable_path.cr +++ b/src/process/executable_path.cr @@ -23,7 +23,7 @@ class Process if executable = executable_path_impl begin File.real_path(executable) - rescue Errno + rescue File::Error end end end diff --git a/src/signal.cr b/src/signal.cr index 1aab3ea2569e..7b611addbc7c 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -195,7 +195,7 @@ module Crystal::Signal spawn(name: "Signal Loop") do loop do value = reader.read_bytes(Int32) - rescue Errno + rescue IO::Error next else process(::Signal.new(value)) @@ -299,7 +299,7 @@ module Crystal::SignalChildHandler return when -1 return if Errno.value == Errno::ECHILD - raise Errno.new("waitpid") + raise RuntimeError.from_errno("waitpid") end @@mutex.lock diff --git a/src/socket.cr b/src/socket.cr index b0e7a0528f83..634939432ba6 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -10,7 +10,23 @@ class Socket < IO include IO::Buffered include IO::Evented - class Error < Exception + class Error < IO::Error + private def self.new_from_errno(message, errno, **opts) + case errno + when Errno::ECONNREFUSED + Socket::ConnectError.new(message, **opts) + when Errno::EADDRINUSE + Socket::BindError.new(message, **opts) + else + super message, errno, **opts + end + end + end + + class ConnectError < Error + end + + class BindError < Error end enum Type @@ -71,7 +87,7 @@ class Socket < IO def initialize(@family, @type, @protocol = Protocol::IP, blocking = false) @closed = false fd = LibC.socket(family, type, protocol) - raise Errno.new("failed to create socket:") if fd == -1 + raise Socket::Error.from_errno("Failed to create socket") if fd == -1 init_close_on_exec(fd) @volatile_fd = Atomic.new(fd) @@ -127,8 +143,8 @@ class Socket < IO connect(addr, timeout) { |error| raise error } end - # Tries to connect to a remote address. Yields an `IO::Timeout` or an - # `Errno` error if the connection failed. + # Tries to connect to a remote address. Yields an `IO::TimeoutError` or an + # `Socket::ConnectError` error if the connection failed. def connect(addr, timeout = nil) timeout = timeout.seconds unless timeout.is_a? Time::Span | Nil loop do @@ -140,10 +156,10 @@ class Socket < IO return when Errno::EINPROGRESS, Errno::EALREADY wait_writable(timeout: timeout) do |error| - return yield IO::Timeout.new("connect timed out") + return yield IO::TimeoutError.new("connect timed out") end else - return yield Errno.new("connect") + return yield Socket::ConnectError.from_errno("connect") end end end @@ -158,7 +174,7 @@ class Socket < IO # ``` def bind(host : String, port : Int) Addrinfo.resolve(host, port, @family, @type, @protocol) do |addrinfo| - bind(addrinfo) { |errno| errno } + bind(addrinfo, "#{host}:#{port}") { |errno| errno } end end @@ -172,7 +188,7 @@ class Socket < IO # ``` def bind(port : Int) Addrinfo.resolve("::", port, @family, @type, @protocol) do |addrinfo| - bind(addrinfo) { |errno| errno } + bind(addrinfo, "::#{port}") { |errno| errno } end end @@ -184,15 +200,15 @@ class Socket < IO # sock = Socket.udp(Socket::Family::INET) # sock.bind Socket::IPAddress.new("192.168.1.25", 80) # ``` - def bind(addr) - bind(addr) { |errno| raise errno } + def bind(addr : Socket::Address) + bind(addr, addr.to_s) { |errno| raise errno } end # Tries to bind the socket to a local address. - # Yields an `Errno` if the binding failed. - def bind(addr) + # Yields an `Socket::BindError` if the binding failed. + private def bind(addr, addrstr) unless LibC.bind(fd, addr, addr.size) == 0 - yield Errno.new("bind") + yield BindError.from_errno("Could not bind to '#{addrstr}'") end end @@ -202,10 +218,10 @@ class Socket < IO end # Tries to listen for connections on the previously bound socket. - # Yields an `Errno` on failure. + # Yields an `Socket::Error` on failure. def listen(backlog : Int = SOMAXCONN) unless LibC.listen(fd, backlog) == 0 - yield Errno.new("listen") + yield Socket::Error.from_errno("Listen failed") end end @@ -223,7 +239,7 @@ class Socket < IO # socket.close # ``` def accept : Socket - accept? || raise IO::Error.new("Closed stream") + accept? || raise Socket::Error.new("Closed stream") end # Accepts an incoming connection. @@ -257,7 +273,7 @@ class Socket < IO elsif Errno.value == Errno::EAGAIN wait_readable rescue nil else - raise Errno.new("accept") + raise Socket::Error.from_errno("accept") end else return client_fd @@ -297,7 +313,7 @@ class Socket < IO def send(message, to addr : Address) : Int32 slice = message.to_slice bytes_sent = LibC.sendto(fd, slice.to_unsafe.as(Void*), slice.size, 0, addr, addr.size) - raise Errno.new("Error sending datagram to #{addr}") if bytes_sent == -1 + raise Socket::Error.from_errno("Error sending datagram to #{addr}") if bytes_sent == -1 # to_i32 is fine because string/slice sizes are an Int32 bytes_sent.to_i32 end @@ -361,7 +377,7 @@ class Socket < IO private def shutdown(how) if LibC.shutdown(fd, how) != 0 - raise Errno.new("shutdown #{how}") + raise Socket::Error.from_errno("shutdown #{how}") end end @@ -396,15 +412,15 @@ class Socket < IO end def reuse_port? - ret = getsockopt(LibC::SO_REUSEPORT, 0) do |errno| - # If SO_REUSEPORT is not supported, the return value should be `false` - if errno.errno == Errno::ENOPROTOOPT - return false - else - raise errno - end + getsockopt(LibC::SO_REUSEPORT, 0) do |value| + return value != 0 + end + + if Errno.value == Errno::ENOPROTOOPT + return false + else + raise Socket::Error.from_errno("getsockopt") end - ret != 0 end def reuse_port=(val : Bool) @@ -457,22 +473,23 @@ class Socket < IO end # Returns the modified *optval*. - def getsockopt(optname, optval, level = LibC::SOL_SOCKET) - getsockopt(optname, optval, level) { |errno| raise errno } + protected def getsockopt(optname, optval, level = LibC::SOL_SOCKET) + getsockopt(optname, optval, level) { |value| return value } + raise Socket::Error.from_errno("getsockopt") end protected def getsockopt(optname, optval, level = LibC::SOL_SOCKET) optsize = LibC::SocklenT.new(sizeof(typeof(optval))) ret = LibC.getsockopt(fd, level, optname, (pointerof(optval).as(Void*)), pointerof(optsize)) - yield Errno.new("getsockopt") if ret == -1 - optval + yield optval if ret == 0 + ret end # NOTE: *optval* is restricted to `Int32` until sizeof works on variables. def setsockopt(optname, optval, level = LibC::SOL_SOCKET) optsize = LibC::SocklenT.new(sizeof(typeof(optval))) ret = LibC.setsockopt(fd, level, optname, (pointerof(optval).as(Void*)), optsize) - raise Errno.new("setsockopt") if ret == -1 + raise Socket::Error.from_errno("setsockopt") if ret == -1 ret end @@ -520,7 +537,7 @@ class Socket < IO def self.fcntl(fd, cmd, arg = 0) r = LibC.fcntl fd, cmd, arg - raise Errno.new("fcntl() failed") if r == -1 + raise Socket::Error.from_errno("fcntl() failed") if r == -1 r end @@ -555,7 +572,7 @@ class Socket < IO end private def unbuffered_rewind - raise IO::Error.new("Can't rewind") + raise Socket::Error.new("Can't rewind") end private def unbuffered_close @@ -577,7 +594,7 @@ class Socket < IO when Errno::EINTR, Errno::EINPROGRESS # ignore else - err = Errno.new("Error closing socket") + err = Socket::Error.from_errno("Error closing socket") end end diff --git a/src/socket/address.cr b/src/socket/address.cr index 8eaeb8a28945..942f4dc61c50 100644 --- a/src/socket/address.cr +++ b/src/socket/address.cr @@ -188,7 +188,7 @@ class Socket private def address(addr : LibC::In6Addr) String.new(46) do |buffer| unless LibC.inet_ntop(family, pointerof(addr).as(Void*), buffer, 46) - raise Errno.new("Failed to convert IP address") + raise Socket::Error.from_errno("Failed to convert IP address") end {LibC.strlen(buffer), 0} end @@ -197,7 +197,7 @@ class Socket private def address(addr : LibC::InAddr) String.new(16) do |buffer| unless LibC.inet_ntop(family, pointerof(addr).as(Void*), buffer, 16) - raise Errno.new("Failed to convert IP address") + raise Socket::Error.from_errno("Failed to convert IP address") end {LibC.strlen(buffer), 0} end diff --git a/src/socket/addrinfo.cr b/src/socket/addrinfo.cr index 3d218f4c84e4..927a8f5c5ba1 100644 --- a/src/socket/addrinfo.cr +++ b/src/socket/addrinfo.cr @@ -69,8 +69,8 @@ class Socket end unless addrinfo = addrinfo.try(&.next?) - if error.is_a?(Errno) && error.errno == Errno::ECONNREFUSED - raise Errno.new("Error connecting to '#{domain}:#{service}'", error.errno) + if error.is_a?(Socket::ConnectError) + raise Socket::ConnectError.from_errno("Error connecting to '#{domain}:#{service}'") else raise error if error end diff --git a/src/socket/ip_socket.cr b/src/socket/ip_socket.cr index 12b63b556438..ea59b9fcc6c3 100644 --- a/src/socket/ip_socket.cr +++ b/src/socket/ip_socket.cr @@ -6,7 +6,7 @@ class IPSocket < Socket addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrIn6)) if LibC.getsockname(fd, sockaddr, pointerof(addrlen)) != 0 - raise Errno.new("getsockname") + raise Socket::Error.from_errno("getsockname") end IPAddress.from(sockaddr, addrlen) @@ -19,7 +19,7 @@ class IPSocket < Socket addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrIn6)) if LibC.getpeername(fd, sockaddr, pointerof(addrlen)) != 0 - raise Errno.new("getpeername") + raise Socket::Error.from_errno("getpeername") end IPAddress.from(sockaddr, addrlen) diff --git a/src/socket/tcp_server.cr b/src/socket/tcp_server.cr index 73dc1ce086ff..bfecdd146b5a 100644 --- a/src/socket/tcp_server.cr +++ b/src/socket/tcp_server.cr @@ -36,7 +36,7 @@ class TCPServer < TCPSocket self.reuse_address = true self.reuse_port = true if reuse_port - if errno = bind(addrinfo) { |errno| errno } + if errno = bind(addrinfo, "#{host}:#{port}") { |errno| errno } close next errno end diff --git a/src/socket/udp_socket.cr b/src/socket/udp_socket.cr index 712d02f2df7f..6887d5cc9706 100644 --- a/src/socket/udp_socket.cr +++ b/src/socket/udp_socket.cr @@ -39,17 +39,15 @@ require "./ip_socket" # server.close # ``` # -# The `send` methods may sporadically fail with `Errno::ECONNREFUSED` when sending datagrams +# The `send` methods may sporadically fail with `Socket::ConnectError` when sending datagrams # to a non-listening server. # Wrap with an exception handler to prevent raising. Example: # # ``` # begin # client.send(message, @destination) -# rescue ex : Errno -# if ex.errno == Errno::ECONNREFUSED -# p ex.inspect -# end +# rescue ex : Socket::ConnectError +# p ex.inspect # end # ``` class UDPSocket < IPSocket @@ -138,7 +136,7 @@ class UDPSocket < IPSocket # The multicast hops option controls the `hoplimit` field on uni-cast packets. # If `-1` is specified, the kernel will use a default value. # If a value of `0` to `255` is specified, the packet will have the specified - # value as `hoplimit`. Other values are considered invalid and `Errno` will be raised. + # value as `hoplimit`. Other values are considered invalid and `Socket::Error` will be raised. # Datagrams with a `hoplimit` of `1` are not forwarded beyond the local network. # Multicast datagrams with a `hoplimit` of `0` will not be transmitted on any # network, but may be delivered locally if the sending host belongs to the diff --git a/src/socket/unix_server.cr b/src/socket/unix_server.cr index 359631e8c5f2..d89f1b3eb763 100644 --- a/src/socket/unix_server.cr +++ b/src/socket/unix_server.cr @@ -34,7 +34,7 @@ class UNIXServer < UNIXSocket def initialize(@path : String, type : Type = Type::STREAM, backlog : Int = 128) super(Family::UNIX, type) - bind(UNIXAddress.new(path)) do |error| + bind(UNIXAddress.new(path), path) do |error| close(delete: false) raise error end diff --git a/src/socket/unix_socket.cr b/src/socket/unix_socket.cr index 5005d3a1eb95..aeeeb1533d9f 100644 --- a/src/socket/unix_socket.cr +++ b/src/socket/unix_socket.cr @@ -71,7 +71,7 @@ class UNIXSocket < Socket {% end %} if LibC.socketpair(Family::UNIX, socktype, 0, fds) != 0 - raise Errno.new("socketpair:") + raise Socket::Error.new("socketpair:") end {UNIXSocket.new(fd: fds[0], type: type), UNIXSocket.new(fd: fds[1], type: type)} diff --git a/src/system_error.cr b/src/system_error.cr new file mode 100644 index 000000000000..8bc4d0e109f0 --- /dev/null +++ b/src/system_error.cr @@ -0,0 +1,101 @@ +# This module can be included in any `Exception` subclass that is +# 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. +# +# For example: +# ``` +# class MyError < Exception +# include SystemError +# end +# +# MyError.from_errno("Something happened") +# ``` +module SystemError + macro included + extend ::SystemError::ClassMethods + end + + # The original system error wrapped by this exception + {% if flag?(:windows) %} + getter os_error : Errno | WinError | Nil + {% else %} + getter os_error : Errno? + {% end %} + + # :nodoc: + protected def os_error=(@os_error) + end + + module ClassMethods + # Builds an instance of the exception from a `Errno` + # + # 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) + message = self.build_message(message, **opts) + message = + if message + "#{message}: #{errno.message}" + else + errno.message + end + + self.new_from_errno(message, errno, **opts).tap do |e| + e.os_error = errno + end + end + + # Prepare 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 build_message(message, **opts) + message + 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 overrided to generate different + # classes based on the `errno` or keyword arguments. + protected def new_from_errno(message : String, errno : Errno, **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 + + self.new_from_winerror(message, winerror, **opts).tap do |e| + e.os_error = winerror + end + 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 overrided 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) + end + {% end %} + end +end diff --git a/src/winerror.cr b/src/winerror.cr index c52af4fc5d07..46a098cc6e70 100644 --- a/src/winerror.cr +++ b/src/winerror.cr @@ -1,23 +1,20 @@ require "c/winbase" -class WinError < Errno - # NOTE: `get_last_error` must be called BEFORE an instance of this class - # is malloced as it would change the "last error" to SUCCESS - def self.new(message) - new(message, LibC.GetLastError) +enum WinError : UInt32 + def self.value : self + WinError.new LibC.GetLastError end - def initialize(message, code) + def message : String buffer = uninitialized UInt16[256] - size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, code, 0, buffer, buffer.size, nil) - details = String.from_utf16(buffer.to_slice[0, size]).strip - super("#{message}: [WinError #{code}, #{details}]", winerror_to_errno(code)) + size = LibC.FormatMessageW(LibC::FORMAT_MESSAGE_FROM_SYSTEM, nil, value, 0, buffer, buffer.size, nil) + String.from_utf16(buffer.to_slice[0, size]).strip end # https://github.com/python/cpython/blob/master/PC/generrmap.c # https://github.com/python/cpython/blob/master/PC/errmap.h - def winerror_to_errno(winerror) - case winerror + def to_errno + case self when ERROR_FILE_NOT_FOUND then Errno::ENOENT when ERROR_PATH_NOT_FOUND then Errno::ENOENT when ERROR_TOO_MANY_OPEN_FILES then Errno::EMFILE