From 9082692c0156b8924c97b568de8c8b3fb010a58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 28 Nov 2023 18:26:25 +0100 Subject: [PATCH] Add `IO::Error#target` (#13865) --- spec/std/file_spec.cr | 4 ++-- src/crystal/system/unix/file.cr | 6 +++--- src/crystal/system/unix/file_descriptor.cr | 10 +++++----- src/crystal/system/win32/file.cr | 4 ++-- src/crystal/system/win32/file_descriptor.cr | 18 +++++++++--------- src/file/error.cr | 14 ++++++++------ src/io/error.cr | 20 ++++++++++++++++++++ src/io/evented.cr | 4 ++-- src/io/overlapped.cr | 6 +++--- 9 files changed, 54 insertions(+), 32 deletions(-) diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index 53d28bdcee4e..a88d0e65958f 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -941,7 +941,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError) { File.read(path) } + expect_raises(File::AccessDeniedError, path) { File.read(path) } end end {% end %} @@ -958,7 +958,7 @@ describe "File" do pending! "Spec cannot run as superuser" end {% end %} - expect_raises(File::AccessDeniedError) { File.write(path, "foo") } + expect_raises(File::AccessDeniedError, path) { File.write(path, "foo") } end end diff --git a/src/crystal/system/unix/file.cr b/src/crystal/system/unix/file.cr index 665a61b22493..ca04693d0b1a 100644 --- a/src/crystal/system/unix/file.cr +++ b/src/crystal/system/unix/file.cr @@ -239,7 +239,7 @@ module Crystal::System::File sleep 0.1 end else - flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked") + flock(op) || raise IO::Error.from_errno("Error applying file lock: file is already locked", target: self) end end @@ -251,7 +251,7 @@ module Crystal::System::File if errno.in?(Errno::EAGAIN, Errno::EWOULDBLOCK) false else - raise IO::Error.from_os_error("Error applying or removing file lock", errno) + raise IO::Error.from_os_error("Error applying or removing file lock", errno, target: self) end end end @@ -269,7 +269,7 @@ module Crystal::System::File end if ret != 0 - raise IO::Error.from_errno("Error syncing file") + raise IO::Error.from_errno("Error syncing file", target: self) end end end diff --git a/src/crystal/system/unix/file_descriptor.cr b/src/crystal/system/unix/file_descriptor.cr index d77708f314bb..d3995c205c3e 100644 --- a/src/crystal/system/unix/file_descriptor.cr +++ b/src/crystal/system/unix/file_descriptor.cr @@ -15,7 +15,7 @@ module Crystal::System::FileDescriptor evented_read(slice, "Error reading file") do LibC.read(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for reading" + raise IO::Error.new "File not open for reading", target: self end end end @@ -25,7 +25,7 @@ module Crystal::System::FileDescriptor evented_write(slice, "Error writing file") do |slice| LibC.write(fd, slice, slice.size).tap do |return_code| if return_code == -1 && Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing" + raise IO::Error.new "File not open for writing", target: self end end end @@ -90,13 +90,13 @@ module Crystal::System::FileDescriptor seek_value = LibC.lseek(fd, offset, whence) if seek_value == -1 - raise IO::Error.from_errno "Unable to seek" + raise IO::Error.from_errno "Unable to seek", target: self end end private def system_pos pos = LibC.lseek(fd, 0, IO::Seek::Current).to_i64 - raise IO::Error.from_errno "Unable to tell" if pos == -1 + raise IO::Error.from_errno("Unable to tell", target: self) if pos == -1 pos end @@ -147,7 +147,7 @@ module Crystal::System::FileDescriptor when Errno::EINTR, Errno::EINPROGRESS # ignore else - raise IO::Error.from_errno("Error closing file") + raise IO::Error.from_errno("Error closing file", target: self) end end end diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index 4fceee02c6d4..43b2dd8142ec 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -493,7 +493,7 @@ module Crystal::System::File if winerror == WinError::ERROR_LOCK_VIOLATION false else - raise IO::Error.from_os_error("LockFileEx", winerror) + raise IO::Error.from_os_error("LockFileEx", winerror, target: self) end end end @@ -509,7 +509,7 @@ module Crystal::System::File private def system_fsync(flush_metadata = true) : Nil if LibC._commit(fd) != 0 - raise IO::Error.from_errno("Error syncing file") + raise IO::Error.from_errno("Error syncing file", target: self) end end end diff --git a/src/crystal/system/win32/file_descriptor.cr b/src/crystal/system/win32/file_descriptor.cr index 84cfe12c6650..6e4f42d46b17 100644 --- a/src/crystal/system/win32/file_descriptor.cr +++ b/src/crystal/system/win32/file_descriptor.cr @@ -15,9 +15,9 @@ module Crystal::System::FileDescriptor bytes_read = LibC._read(fd, slice, slice.size) if bytes_read == -1 if Errno.value == Errno::EBADF - raise IO::Error.new "File not open for reading" + raise IO::Error.new "File not open for reading", target: self else - raise IO::Error.from_errno("Error reading file") + raise IO::Error.from_errno("Error reading file", target: self) end end bytes_read @@ -36,9 +36,9 @@ module Crystal::System::FileDescriptor bytes_written = LibC._write(fd, slice, slice.size) if bytes_written == -1 if Errno.value == Errno::EBADF - raise IO::Error.new "File not open for writing" + raise IO::Error.new "File not open for writing", target: self else - raise IO::Error.from_errno("Error writing file") + raise IO::Error.from_errno("Error writing file", target: self) end end else @@ -106,7 +106,7 @@ module Crystal::System::FileDescriptor if file_type == LibC::FILE_TYPE_UNKNOWN error = WinError.value - raise IO::Error.from_os_error("Unable to get info", error) unless error == WinError::ERROR_SUCCESS + raise IO::Error.from_os_error("Unable to get info", error, target: self) unless error == WinError::ERROR_SUCCESS end end @@ -129,13 +129,13 @@ module Crystal::System::FileDescriptor seek_value = LibC._lseeki64(fd, offset, whence) if seek_value == -1 - raise IO::Error.from_errno "Unable to seek" + raise IO::Error.from_errno "Unable to seek", target: self end end private def system_pos pos = LibC._lseeki64(fd, 0, IO::Seek::Current) - raise IO::Error.from_errno "Unable to tell" if pos == -1 + raise IO::Error.from_errno("Unable to tell", target: self) if pos == -1 pos end @@ -165,7 +165,7 @@ module Crystal::System::FileDescriptor when Errno::EINTR # ignore else - raise IO::Error.from_errno("Error closing file") + raise IO::Error.from_errno("Error closing file", target: self) end end end @@ -204,7 +204,7 @@ module Crystal::System::FileDescriptor if LibC.ReadFile(handle, buffer, buffer.size, out bytes_read, pointerof(overlapped)) == 0 error = WinError.value return 0_i64 if error == WinError::ERROR_HANDLE_EOF - raise IO::Error.from_os_error "Error reading file", error + raise IO::Error.from_os_error "Error reading file", error, target: self end bytes_read.to_i64 diff --git a/src/file/error.cr b/src/file/error.cr index 79a0ec5978dc..355496488bc4 100644 --- a/src/file/error.cr +++ b/src/file/error.cr @@ -2,9 +2,12 @@ class File < IO::FileDescriptor end class File::Error < IO::Error - getter file : String getter other : String? + def file : String + target.not_nil! + end + private def self.new_from_os_error(message, os_error, **opts) case os_error when Errno::ENOENT, WinError::ERROR_FILE_NOT_FOUND, WinError::ERROR_PATH_NOT_FOUND @@ -20,6 +23,10 @@ class File::Error < IO::Error end end + def initialize(message, *, file : String | Path, @other : String? = nil) + super message, target: file + end + protected def self.build_message(message, *, file : String) : String "#{message}: '#{file.inspect_unquoted}'" end @@ -38,11 +45,6 @@ class File::Error < IO::Error end end {% end %} - - def initialize(message, *, file : String | Path, @other : String? = nil) - @file = file.to_s - super message - end end class File::NotFoundError < File::Error diff --git a/src/io/error.cr b/src/io/error.cr index b611dc523818..4c6d30952f13 100644 --- a/src/io/error.cr +++ b/src/io/error.cr @@ -1,6 +1,26 @@ class IO class Error < Exception include SystemError + + getter target : String? + + protected def self.build_message(message, *, target : File) : String + build_message(message, target: target.path) + end + + protected def self.build_message(message, *, target : Nil) : String + message + end + + protected def self.build_message(message, *, target) : String + "#{message} (#{target})" + end + + def initialize(message : String? = nil, *, target = nil) + @target = target.try(&.to_s) + + super message + end end # Raised when an `IO` operation times out. diff --git a/src/io/evented.cr b/src/io/evented.cr index c490f3b5939f..191f136466a1 100644 --- a/src/io/evented.cr +++ b/src/io/evented.cr @@ -58,7 +58,7 @@ module IO::Evented if Errno.value == Errno::EAGAIN wait_readable else - raise IO::Error.from_errno(errno_msg) + raise IO::Error.from_errno(errno_msg, target: self) end end ensure @@ -79,7 +79,7 @@ module IO::Evented if Errno.value == Errno::EAGAIN wait_writable else - raise IO::Error.from_errno(errno_msg) + raise IO::Error.from_errno(errno_msg, target: self) end end end diff --git a/src/io/overlapped.cr b/src/io/overlapped.cr index d4b9f5f0cb6f..d7ed8aa516ec 100644 --- a/src/io/overlapped.cr +++ b/src/io/overlapped.cr @@ -211,9 +211,9 @@ module IO::Overlapped when .error_io_pending? # the operation is running asynchronously; do nothing when .error_access_denied? - raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}" + raise IO::Error.new "File not open for #{writing ? "writing" : "reading"}", target: self else - raise IO::Error.from_os_error(method, error) + raise IO::Error.from_os_error(method, error, target: self) end else operation.synchronous = true @@ -245,7 +245,7 @@ module IO::Overlapped when .wsa_io_pending? # the operation is running asynchronously; do nothing else - raise IO::Error.from_os_error(method, error) + raise IO::Error.from_os_error(method, error, target: self) end else operation.synchronous = true