Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions spec/std/file_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 26 additions & 4 deletions src/crystal/system/win32/file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion src/lib_c/x86_64-windows-msvc/c/minwinbase.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions src/lib_c/x86_64-windows-msvc/c/winbase.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading