diff --git a/spec/std/file_spec.cr b/spec/std/file_spec.cr index e88adeed7ea2..49729f33d6c7 100644 --- a/spec/std/file_spec.cr +++ b/spec/std/file_spec.cr @@ -234,6 +234,10 @@ describe "File" do File.exists?(bad_path).should be_false end end + + it "gives true for null file (#15019)" do + File.exists?(File::NULL).should be_true + end end describe "file?" do diff --git a/src/crystal/system/win32/file.cr b/src/crystal/system/win32/file.cr index b6f9cf2b7ccd..e4e45ee3a664 100644 --- a/src/crystal/system/win32/file.cr +++ b/src/crystal/system/win32/file.cr @@ -200,10 +200,32 @@ module Crystal::System::File private def self.accessible?(path, *, check_writable, follow_symlinks) if follow_symlinks - path = realpath?(path) || return false + path = realpath?(path, check_exists: false) || return false + end + + handle = LibC.CreateFileW( + System.to_wstr(path), + LibC::FILE_READ_ATTRIBUTES, + LibC::DEFAULT_SHARE_MODE, + nil, + LibC::OPEN_EXISTING, + LibC::FILE_FLAG_BACKUP_SEMANTICS | LibC::FILE_FLAG_OPEN_REPARSE_POINT, + LibC::HANDLE.null, + ) + return false if handle == LibC::INVALID_HANDLE_VALUE + + begin + info = uninitialized LibC::FILE_ATTRIBUTE_TAG_INFO + if LibC.GetFileInformationByHandleEx(handle, LibC::FILE_INFO_BY_HANDLE_CLASS::FileAttributeTagInfo, pointerof(info), sizeof(typeof(info))) == 0 + # this can happen to special files like NUL and COM1, but we managed to + # open them anyway, so assume they are readable and writable + return true + end + attributes = info.fileAttributes + ensure + LibC.CloseHandle(handle) end - attributes = LibC.GetFileAttributesW(System.to_wstr(path)) return false if attributes == LibC::INVALID_FILE_ATTRIBUTES return true if attributes.bits_set?(LibC::FILE_ATTRIBUTE_DIRECTORY) return false if check_writable && attributes.bits_set?(LibC::FILE_ATTRIBUTE_READONLY) @@ -298,7 +320,7 @@ module Crystal::System::File private REALPATH_SYMLINK_LIMIT = 100 - private def self.realpath?(path : String) : String? + private def self.realpath?(path : String, *, check_exists : Bool = true) : String? REALPATH_SYMLINK_LIMIT.times do win_path = System.to_wstr(path) @@ -319,7 +341,7 @@ module Crystal::System::File next end - return exists?(realpath, follow_symlinks: false) ? realpath : nil + return !check_exists || exists?(realpath, follow_symlinks: false) ? realpath : nil end raise ::File::Error.from_os_error("Too many symbolic links", Errno::ELOOP, file: path) diff --git a/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr b/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr index bb36bbf279af..4aaf65fb239b 100644 --- a/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/minwinbase.cr @@ -46,7 +46,8 @@ lib LibC end enum FILE_INFO_BY_HANDLE_CLASS - FileBasicInfo = 0 + FileBasicInfo = 0 + FileAttributeTagInfo = 9 end LOCKFILE_FAIL_IMMEDIATELY = DWORD.new(0x00000001) diff --git a/src/lib_c/x86_64-windows-msvc/c/winbase.cr b/src/lib_c/x86_64-windows-msvc/c/winbase.cr index 7b7a8735ddf2..db6ed292e7df 100644 --- a/src/lib_c/x86_64-windows-msvc/c/winbase.cr +++ b/src/lib_c/x86_64-windows-msvc/c/winbase.cr @@ -72,6 +72,11 @@ lib LibC fileAttributes : DWORD end + struct FILE_ATTRIBUTE_TAG_INFO + fileAttributes : DWORD + reparseTag : DWORD + end + fun GetFileInformationByHandleEx(hFile : HANDLE, fileInformationClass : FILE_INFO_BY_HANDLE_CLASS, lpFileInformation : Void*, dwBufferSize : DWORD) : BOOL fun LookupAccountNameW(lpSystemName : LPWSTR, lpAccountName : LPWSTR, sid : SID*, cbSid : DWORD*, referencedDomainName : LPWSTR, cchReferencedDomainName : DWORD*, peUse : SID_NAME_USE*) : BOOL