Skip to content

Commit c689756

Browse files
Standardize system codes for file errors
1 parent 62f4a20 commit c689756

File tree

8 files changed

+60
-17
lines changed

8 files changed

+60
-17
lines changed

spec/std/file_spec.cr

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,41 @@ describe "File" do
562562
end
563563
end
564564

565+
long_path = "a" * 1000
566+
describe ".info" do
567+
it "raises for too long pathname" do
568+
expect_raises(File::NotFoundError, /Unable to get file info: '#{long_path}': (File ?name too long|The system cannot find the path specified)/) do
569+
File.info(long_path)
570+
end
571+
end
572+
573+
it "raises for invalid pathname" do
574+
expect_raises(File::NotFoundError, /Unable to get file info: '': (No such file or directory|The system cannot find the path specified)/) do
575+
File.info("")
576+
end
577+
end
578+
579+
it "raises for invalid pathname" do
580+
expect_raises(File::NotFoundError, /Unable to get file info: '<': (No such file or directory|The filename, directory name, or volume label syntax is incorrect)/) do
581+
File.info("<")
582+
end
583+
end
584+
end
585+
586+
describe ".info?" do
587+
it "returns nil for too long pathname" do
588+
File.info?(long_path).should be_nil
589+
end
590+
591+
it "returns nil for invalid pathname" do
592+
File.info?("").should be_nil
593+
end
594+
595+
it "returns nil for invalid pathname" do
596+
File.info?("<").should be_nil
597+
end
598+
end
599+
565600
describe "File::Info" do
566601
it "gets for this file" do
567602
info = File.info(datapath("test_file.txt"))

spec/std/process_spec.cr

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ describe Process do
6868
end
6969
end
7070

71+
it "raises for long path" do
72+
expect_raises(File::NotFoundError, "Error executing process: 'aaaaaaa") do
73+
Process.new("a" * 1000)
74+
end
75+
end
76+
7177
it "accepts nilable string for `chdir` (#13767)" do
7278
expect_raises(File::NotFoundError, "Error executing process: 'foobarbaz'") do
7379
Process.new("foobarbaz", chdir: nil.as(String?))

src/crystal/system/file.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ module Crystal::System::File
7171
when Tuple(FileDescriptor::Handle, Bool)
7272
fd, blocking = result
7373
return {fd, path, blocking}
74-
when Errno::EEXIST, WinError::ERROR_FILE_EXISTS
74+
when .in?(File::AlreadyExistsError::OS_ERRORS)
7575
# retry
7676
else
7777
raise ::File::Error.from_os_error("Error creating temporary file", result, file: path)

src/crystal/system/unix/file.cr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ module Crystal::System::File
3535
if ret == 0
3636
::File::Info.new(stat)
3737
else
38-
if Errno.value.in?(Errno::ENOENT, Errno::ENOTDIR)
38+
if ::File::NotFoundError::OS_ERRORS.includes?(Errno.value)
3939
nil
4040
else
4141
raise ::File::Error.from_errno("Unable to get file info", file: path)
@@ -129,7 +129,7 @@ module Crystal::System::File
129129
err = LibC.unlink(path.check_no_null_byte)
130130
if err != -1
131131
true
132-
elsif !raise_on_missing && Errno.value == Errno::ENOENT
132+
elsif !raise_on_missing && ::File::NotFoundError::OS_ERRORS.includes?(Errno.value)
133133
false
134134
else
135135
raise ::File::Error.from_errno("Error deleting file", file: path)
@@ -162,7 +162,7 @@ module Crystal::System::File
162162
3.times do |iter|
163163
bytesize = LibC.readlink(path, buf, buf.bytesize)
164164
if bytesize == -1
165-
if Errno.value.in?(Errno::EINVAL, Errno::ENOENT, Errno::ENOTDIR)
165+
if ::File::NotFoundError::OS_ERRORS.includes?(Errno.value) || Errno.value == Errno::EINVAL
166166
yield
167167
end
168168

src/crystal/system/unix/process.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ struct Crystal::System::Process
343343

344344
private def self.raise_exception_from_errno(command, errno = Errno.value)
345345
case errno
346-
when Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC
346+
when .in?(::File::NotFoundError::OS_ERRORS), .in?(::File::AccessDeniedError::OS_ERRORS), Errno::ENOEXEC
347347
raise ::File::Error.from_os_error("Error executing process", errno, file: command)
348348
else
349349
raise IO::Error.from_os_error("Error executing process: '#{command}'", errno)

src/crystal/system/win32/file.cr

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,9 @@ module Crystal::System::File
9797
write_blocking(handle, slice, pos: @system_append ? UInt64::MAX : nil)
9898
end
9999

100-
NOT_FOUND_ERRORS = {
101-
WinError::ERROR_FILE_NOT_FOUND,
102-
WinError::ERROR_PATH_NOT_FOUND,
103-
WinError::ERROR_INVALID_NAME,
104-
WinError::ERROR_DIRECTORY,
105-
}
106-
107100
def self.check_not_found_error(message, path)
108101
error = WinError.value
109-
if NOT_FOUND_ERRORS.includes? error
102+
if ::File::NotFoundError::OS_ERRORS.includes?(error)
110103
nil
111104
else
112105
raise ::File::Error.from_os_error(message, error, file: path)
@@ -429,7 +422,7 @@ module Crystal::System::File
429422
info = symlink_info?(path)
430423
unless info
431424
{% begin %}
432-
if WinError.value.in?({{ NOT_FOUND_ERRORS.splat }}, WinError::ERROR_NOT_A_REPARSE_POINT)
425+
if ::File::NotFoundError::OS_ERRORS.includes?(WinError.value) || WinError.value == WinError::ERROR_NOT_A_REPARSE_POINT
433426
yield
434427
end
435428
{% end %}

src/crystal/system/win32/process.cr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,8 @@ struct Crystal::System::Process
296296
pointerof(startup_info), pointerof(process_info)
297297
) == 0
298298
error = WinError.value
299-
case error.to_errno
300-
when Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC
299+
case error
300+
when .in?(::File::NotFoundError::OS_ERRORS), .in?(::File::AccessDeniedError::OS_ERRORS), WinError::ERROR_BAD_EXE_FORMAT
301301
raise ::File::Error.from_os_error("Error executing process", error, file: command_args)
302302
else
303303
raise IO::Error.from_os_error("Error executing process: '#{command_args}'", error)
@@ -380,7 +380,7 @@ struct Crystal::System::Process
380380

381381
private def self.raise_exception_from_errno(command, errno = Errno.value)
382382
case errno
383-
when Errno::EACCES, Errno::ENOENT
383+
when .in?(::File::NotFoundError::OS_ERRORS), .in?(::File::AccessDeniedError::OS_ERRORS)
384384
raise ::File::Error.from_os_error("Error executing process", errno, file: command)
385385
else
386386
raise IO::Error.from_os_error("Error executing process: '#{command}'", errno)

src/file/error.cr

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,21 @@ end
4949

5050
class File::NotFoundError < File::Error
5151
# :nodoc:
52+
# See https://github.com/crystal-lang/crystal/issues/15905#issuecomment-2975820840
5253
OS_ERRORS = [
54+
Errno::ENAMETOOLONG,
5355
Errno::ENOENT,
56+
Errno::ENOTDIR,
57+
WinError::ERROR_BAD_NETPATH,
58+
WinError::ERROR_BAD_NET_NAME,
59+
WinError::ERROR_BAD_PATHNAME,
5460
WinError::ERROR_DIRECTORY,
5561
WinError::ERROR_FILE_NOT_FOUND,
62+
WinError::ERROR_FILENAME_EXCED_RANGE,
63+
WinError::ERROR_INVALID_DRIVE,
5664
WinError::ERROR_INVALID_NAME,
5765
WinError::ERROR_PATH_NOT_FOUND,
66+
WinError::WSAENAMETOOLONG,
5867
]
5968
end
6069

0 commit comments

Comments
 (0)