Skip to content
Closed
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
35 changes: 35 additions & 0 deletions src/crystal/system/dir_handle.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
struct Crystal::System::DirHandle
# Creates a new `DirHandle`
# def self.new(path : String) : DirHandle

# Reads the next entry from dir and returns it as a string. Returns `nil` at the end of the stream.
# def read

# Repositions this directory to the first entry.
# def rewind

# Closes the directory stream.
# def close

# Returns the current working directory.
# def self.current : String

# Changes the current working directory of the process to the given string.
# def self.cd(path : String)

# Returns `true` if the given path exists and is a directory
# def self.exists?(path : String) : Bool

# Creates a new directory at the given path, including any non-existing
# intermediate directories. The linux-style permission mode can be specified.
# def self.mkdir(path : String, mode)

# Removes the directory at the given path.
# def self.rmdir(path : String)
end

{% if flag?(:win32) %}
require "./windows/dir_handle"
{% else %}
require "./unix/dir_handle"
{% end %}
79 changes: 79 additions & 0 deletions src/crystal/system/unix/dir_handle.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require "c/dirent"
require "c/unistd"
require "c/sys/stat"

struct Crystal::System::DirHandle
@dirhandle : LibC::DIR*

@closed = false

def initialize(path : String)
@dirhandle = LibC.opendir(path.check_no_null_byte)
unless @dirhandle
raise Errno.new("Error opening directory '#{path}'")
end
@closed = false
end

def read
# readdir() returns NULL for failure and sets errno or returns NULL for EOF but leaves errno as is. wtf.
Errno.value = 0
ent = LibC.readdir(@dirhandle)
if ent
String.new(ent.value.d_name.to_unsafe)
elsif Errno.value != 0
raise Errno.new("readdir")
else
nil
end
end

def rewind
LibC.rewinddir(@dirhandle)
end

def close
return if @closed
if LibC.closedir(@dirhandle) != 0
raise Errno.new("closedir")
end
@closed = true
end

def self.current : String
if dir = LibC.getcwd(nil, 0)
String.new(dir).tap { LibC.free(dir.as(Void*)) }
else
raise Errno.new("getcwd")
end
end

def self.cd(path : String)
if LibC.chdir(path.check_no_null_byte) != 0
raise Errno.new("Error while changing directory to #{path.inspect}")
end
end

def self.exists?(path : String) : Bool
if LibC.stat(path.check_no_null_byte, out stat) != 0
if Errno.value == Errno::ENOENT || Errno.value == Errno::ENOTDIR
return false
else
raise Errno.new("stat")
end
end
File::Stat.new(stat).directory?
end

def self.mkdir(path : String, mode)
if LibC.mkdir(path.check_no_null_byte, mode) == -1
raise Errno.new("Unable to create directory '#{path}'")
end
end

def self.rmdir(path : String)
if LibC.rmdir(path.check_no_null_byte) == -1
raise Errno.new("Unable to remove directory '#{path}'")
end
end
end
83 changes: 83 additions & 0 deletions src/crystal/system/windows/dir_handle.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
require "c/winapi"
require "winerror"

struct Crystal::System::DirHandle
@dir_handle : LibC::Handle

def initialize(path : String)
if !DirHandle.exists?(path)
raise WinError.new("Error opening directory '#{path}'")
end
@dir_handle = LibC::INVALID_HANDLE_VALUE
end

def read
data = LibC::WIN32_FIND_DATA_A.new
if @dir_handle == LibC::INVALID_HANDLE_VALUE
@dir_handle = LibC._FindFirstFileA((path + "\\*").check_no_null_byte, pointerof(data))
if @dir_handle == LibC::INVALID_HANDLE_VALUE
raise WinError.new("FindFirstFileA")
end
elsif LibC._FindNextFileA(@dir_handle, pointerof(data)) == 0
error = LibC._GetLastError
if error == WinError::ERROR_NO_MORE_FILES
return nil
else
raise WinError.new("FindNextFileA", error)
end
end
String.new(data.cFileName.to_slice)
end

def rewind
close
end

def close
if @dir_handle != LibC::INVALID_HANDLE_VALUE
if LibC._FindClose(@dir_handle) == 0
raise WinError.new("FindClose")
end
@dir_handle = LibC::INVALID_HANDLE_VALUE
end
end

def self.current : String
len = LibC._GetCurrentDirectoryA(0, nil)
if len == 0
raise WinError.new("_GetCurrentDirectoryA")
end
String.new(len) do |buffer|
if LibC._GetCurrentDirectoryA(len, buffer) == 0
raise WinError.new("_GetCurrentDirectoryA")
end
{len - 1, len - 1} # remove \0 at the end
end
end

def self.cd(path : String)
if LibC._SetCurrentDirectoryA(path.check_no_null_byte) == 0
raise WinError.new("Error while changing directory to #{path.inspect}")
end
end

def self.exists?(path : String) : Bool
atr = LibC._GetFileAttributesA(path.check_no_null_byte)
if (atr == LibWindows::INVALID_FILE_ATTRIBUTES)
return false
end
return atr & LibC::FILE_ATTRIBUTE_DIRECTORY != 0
end

def self.mkdir(path : String, mode)
if LibC._CreateDirectoryA(path.check_no_null_byte, nil) == 0
raise WinError.new("Unable to create directory '#{path}'")
end
end

def self.rmdir(path : String)
if LibC._RemoveDirectoryA(path.check_no_null_byte) == 0
raise WinError.new("Unable to remove directory '#{path}'")
end
end
end
56 changes: 10 additions & 46 deletions src/dir.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
require "c/dirent"
require "c/unistd"
require "c/sys/stat"
require "crystal/system/dir_handle"

# Objects of class `Dir` are directory streams representing directories in the underlying file system.
# They provide a variety of ways to list directories and their contents.
Expand All @@ -17,11 +15,7 @@ class Dir

# Returns a new directory object for the named directory.
def initialize(@path)
@dir = LibC.opendir(@path.check_no_null_byte)
unless @dir
raise Errno.new("Error opening directory '#{@path}'")
end
@closed = false
@dir = Crystal::System::DirHandle.new(@path)
end

# Alias for `new(path)`
Expand Down Expand Up @@ -125,47 +119,28 @@ class Dir
# array.sort # => [".", "..", "config.h"]
# ```
def read
# readdir() returns NULL for failure and sets errno or returns NULL for EOF but leaves errno as is. wtf.
Errno.value = 0
ent = LibC.readdir(@dir)
if ent
String.new(ent.value.d_name.to_unsafe)
elsif Errno.value != 0
raise Errno.new("readdir")
else
nil
end
@dir.read
end

# Repositions this directory to the first entry.
def rewind
LibC.rewinddir(@dir)
@dir.rewind
self
end

# Closes the directory stream.
def close
return if @closed
if LibC.closedir(@dir) != 0
raise Errno.new("closedir")
end
@closed = true
@dir.close
end

# Returns the current working directory.
def self.current : String
if dir = LibC.getcwd(nil, 0)
String.new(dir).tap { LibC.free(dir.as(Void*)) }
else
raise Errno.new("getcwd")
end
Crystal::System::DirHandle.current
end

# Changes the current working directory of the process to the given string.
def self.cd(path)
if LibC.chdir(path.check_no_null_byte) != 0
raise Errno.new("Error while changing directory to #{path.inspect}")
end
Crystal::System::DirHandle.cd(path)
end

# Changes the current working directory of the process to the given string
Expand Down Expand Up @@ -215,14 +190,7 @@ class Dir

# Returns `true` if the given path exists and is a directory
def self.exists?(path) : Bool
if LibC.stat(path.check_no_null_byte, out stat) != 0
if Errno.value == Errno::ENOENT || Errno.value == Errno::ENOTDIR
return false
else
raise Errno.new("stat")
end
end
File::Stat.new(stat).directory?
Crystal::System::DirHandle.exists?(path)
end

# Returns `true` if the directory at *path* is empty, otherwise returns `false`.
Expand All @@ -246,9 +214,7 @@ class Dir
# Creates a new directory at the given path. The linux-style permission mode
# can be specified, with a default of 777 (0o777).
def self.mkdir(path, mode = 0o777)
if LibC.mkdir(path.check_no_null_byte, mode) == -1
raise Errno.new("Unable to create directory '#{path}'")
end
Crystal::System::DirHandle.mkdir(path, mode)
0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly related to this pr, but why does ˋmkdirˋ & ˋrmdirˋ returns ˋ0ˋ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, but in spec there is
Dir.mkdir(path, 0o700).should eq(0)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably they should be marked as : Nil for avoid leaking a useless return value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make another PR for it

end

Expand Down Expand Up @@ -276,9 +242,7 @@ class Dir

# Removes the directory at the given path.
def self.rmdir(path)
if LibC.rmdir(path.check_no_null_byte) == -1
raise Errno.new("Unable to remove directory '#{path}'")
end
Crystal::System::DirHandle.rmdir(path)
0
end

Expand Down