Skip to content

Commit

Permalink
Implement File.readable? and .writable? in Win32 (#14420)
Browse files Browse the repository at this point in the history
Co-authored-by: Johannes Müller <straightshoota@gmail.com>
  • Loading branch information
HertzDevil and straight-shoota committed Apr 18, 2024
1 parent 4dd5814 commit 8d2d480
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 22 deletions.
59 changes: 43 additions & 16 deletions spec/std/file_spec.cr
Expand Up @@ -31,6 +31,17 @@ private def normalize_permissions(permissions, *, directory)
{% end %}
end

# TODO: Find a better way to execute specs involving file permissions when
# running as a privileged user. Compiling a program and running a separate
# process would be a lot of overhead.
private def pending_if_superuser!
{% if flag?(:unix) %}
if LibC.getuid == 0
pending! "Spec cannot run as superuser"
end
{% end %}
end

describe "File" do
it "gets path" do
path = datapath("test_file.txt")
Expand Down Expand Up @@ -217,6 +228,27 @@ describe "File" do
it "gives false when a component of the path is a file" do
File.readable?(datapath("dir", "test_file.txt", "")).should be_false
end

# win32 doesn't have a way to make files unreadable via chmod
{% unless flag?(:win32) %}
it "gives false when the file has no read permissions" do
with_tempfile("unreadable.txt") do |path|
File.write(path, "")
File.chmod(path, 0o222)
pending_if_superuser!
File.readable?(path).should be_false
end
end

it "gives false when the file has no permissions" do
with_tempfile("unaccessible.txt") do |path|
File.write(path, "")
File.chmod(path, 0o000)
pending_if_superuser!
File.readable?(path).should be_false
end
end
{% end %}
end

describe "writable?" do
Expand All @@ -231,6 +263,15 @@ describe "File" do
it "gives false when a component of the path is a file" do
File.writable?(datapath("dir", "test_file.txt", "")).should be_false
end

it "gives false when the file has no write permissions" do
with_tempfile("readonly.txt") do |path|
File.write(path, "")
File.chmod(path, 0o444)
pending_if_superuser!
File.writable?(path).should be_false
end
end
end

describe "file?" do
Expand Down Expand Up @@ -991,14 +1032,7 @@ describe "File" do
with_tempfile("file.txt") do |path|
File.touch(path)
File.chmod(path, File::Permissions::None)
{% if flag?(:unix) %}
# TODO: Find a better way to execute this spec when running as privileged
# user. Compiling a program and running a separate process would be a
# lot of overhead.
if LibC.getuid == 0
pending! "Spec cannot run as superuser"
end
{% end %}
pending_if_superuser!
expect_raises(File::AccessDeniedError, "Error opening file with mode 'r': '#{path.inspect_unquoted}'") { File.read(path) }
end
end
Expand All @@ -1008,14 +1042,7 @@ describe "File" do
with_tempfile("file.txt") do |path|
File.touch(path)
File.chmod(path, File::Permissions::None)
{% if flag?(:unix) %}
# TODO: Find a better way to execute this spec when running as privileged
# user. Compiling a program and running a separate process would be a
# lot of overhead.
if LibC.getuid == 0
pending! "Spec cannot run as superuser"
end
{% end %}
pending_if_superuser!
expect_raises(File::AccessDeniedError, "Error opening file with mode 'w': '#{path.inspect_unquoted}'") { File.write(path, "foo") }
end
end
Expand Down
15 changes: 10 additions & 5 deletions src/crystal/system/win32/file.cr
Expand Up @@ -177,23 +177,28 @@ module Crystal::System::File
if follow_symlinks
path = realpath?(path) || return false
end
accessible?(path, 0)
accessible?(path, check_writable: false)
end

def self.readable?(path) : Bool
accessible?(path, 4)
accessible?(path, check_writable: false)
end

def self.writable?(path) : Bool
accessible?(path, 2)
accessible?(path, check_writable: true)
end

def self.executable?(path) : Bool
LibC.GetBinaryTypeW(System.to_wstr(path), out result) != 0
end

private def self.accessible?(path, mode)
LibC._waccess_s(System.to_wstr(path), mode) == 0
private def self.accessible?(path, *, check_writable)
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)

true
end

def self.chown(path : String, uid : Int32, gid : Int32, follow_symlinks : Bool) : Nil
Expand Down
2 changes: 1 addition & 1 deletion src/lib_c/x86_64-windows-msvc/c/io.cr
Expand Up @@ -2,7 +2,6 @@ require "c/stdint"

lib LibC
fun _close(fd : Int) : Int
fun _waccess_s(path : WCHAR*, mode : Int) : ErrnoT
fun _wexecvp(cmdname : WCHAR*, argv : WCHAR**) : IntPtrT
fun _get_osfhandle(fd : Int) : IntPtrT
fun _dup2(fd1 : Int, fd2 : Int) : Int
Expand All @@ -14,6 +13,7 @@ lib LibC
fun _read(fd : Int, buffer : UInt8*, count : UInt) : Int
fun _lseeki64(fd : Int, offset : Int64, origin : Int) : Int64
fun _wopen(filename : WCHAR*, oflag : Int, ...) : Int
fun _waccess_s(path : WCHAR*, mode : Int) : ErrnoT
fun _wchmod(filename : WCHAR*, pmode : Int) : Int
fun _wunlink(filename : WCHAR*) : Int
fun _wmktemp_s(template : WCHAR*, sizeInChars : SizeT) : ErrnoT
Expand Down

0 comments on commit 8d2d480

Please sign in to comment.