Skip to content

Commit

Permalink
Separate platform-specific parts of File
Browse files Browse the repository at this point in the history
Similar to previous patch: Separate platform-specific parts of
IO::FileDescriptor.
  • Loading branch information
RX14 committed Nov 29, 2017
1 parent 27fda67 commit fd81b4d
Show file tree
Hide file tree
Showing 17 changed files with 414 additions and 224 deletions.
5 changes: 5 additions & 0 deletions src/crystal/system/file.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# :nodoc:
module Crystal::System::FileMixin
end

require "./unix/file"
236 changes: 236 additions & 0 deletions src/crystal/system/unix/file.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
require "c/sys/file"

# :nodoc:
module Crystal::System
module File
extend self

def open(filename, mode, perm)
oflag = open_flag(mode) | LibC::O_CLOEXEC

fd = LibC.open(filename.check_no_null_byte, oflag, perm)
if fd < 0
raise Errno.new("Error opening file '#{filename}' with mode '#{mode}'")
end
fd
end

private def open_flag(mode)
if mode.size == 0
raise "Invalid access mode #{mode}"
end

m = 0
o = 0
case mode[0]
when 'r'
m = LibC::O_RDONLY
when 'w'
m = LibC::O_WRONLY
o = LibC::O_CREAT | LibC::O_TRUNC
when 'a'
m = LibC::O_WRONLY
o = LibC::O_CREAT | LibC::O_APPEND
else
raise "Invalid access mode #{mode}"
end

case mode.size
when 1
# Nothing
when 2
case mode[1]
when '+'
m = LibC::O_RDWR
when 'b'
# Nothing
else
raise "Invalid access mode #{mode}"
end
else
raise "Invalid access mode #{mode}"
end

oflag = m | o
end

def mktemp(name, extension)
tmpdir = tempdir + ::File::SEPARATOR
path = "#{tmpdir}#{name}.XXXXXX#{extension}"

if extension
fd = LibC.mkstemps(path, extension.bytesize)
else
fd = LibC.mkstemp(path)
end

raise Errno.new("mkstemp") if fd == -1
{fd, path}
end

def tempdir
tmpdir = ENV["TMPDIR"]? || "/tmp"
tmpdir.rchop(::File::SEPARATOR)
end

def stat(path)
if LibC.stat(path.check_no_null_byte, out stat) != 0
raise Errno.new("Unable to get stat for '#{path}'")
end
::File::Stat.new(stat)
end

def lstat(path)
if LibC.lstat(path.check_no_null_byte, out stat) != 0
raise Errno.new("Unable to get lstat for '#{path}'")
end
::File::Stat.new(stat)
end

def empty?(path)
begin
stat(path).size == 0
rescue Errno
raise Errno.new("Error determining size of '#{path}'")
end
end

def exists?(path)
accessible?(path, LibC::F_OK)
end

def readable?(path) : Bool
accessible?(path, LibC::R_OK)
end

def writable?(path) : Bool
accessible?(path, LibC::W_OK)
end

def executable?(path) : Bool
accessible?(path, LibC::X_OK)
end

private def accessible?(path, flag)
LibC.access(path.check_no_null_byte, flag) == 0
end

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

def chown(path, uid : Int, gid : Int, follow_symlinks)
ret = if !follow_symlinks && symlink?(path)
LibC.lchown(path, uid, gid)
else
LibC.chown(path, uid, gid)
end
raise Errno.new("Error changing owner of '#{path}'") if ret == -1
end

def chmod(path, mode : Int)
if LibC.chmod(path, mode) == -1
raise Errno.new("Error changing permissions of '#{path}'")
end
end

def delete(path)
err = LibC.unlink(path.check_no_null_byte)
if err == -1
raise Errno.new("Error deleting file '#{path}'")
end
end

def real_path(path)
real_path_ptr = LibC.realpath(path, nil)
raise Errno.new("Error resolving real path of #{path}") unless real_path_ptr
String.new(real_path_ptr).tap { LibC.free(real_path_ptr.as(Void*)) }
end

def link(old_path, new_path)
ret = LibC.link(old_path.check_no_null_byte, new_path.check_no_null_byte)
raise Errno.new("Error creating link from #{old_path} to #{new_path}") if ret != 0
ret
end

def symlink(old_path, new_path)
ret = LibC.symlink(old_path.check_no_null_byte, new_path.check_no_null_byte)
raise Errno.new("Error creating symlink from #{old_path} to #{new_path}") if ret != 0
ret
end

def symlink?(path)
if LibC.lstat(path.check_no_null_byte, out stat) != 0
if Errno.value == Errno::ENOENT
return false
else
raise Errno.new("stat")
end
end
(stat.st_mode & LibC::S_IFMT) == LibC::S_IFLNK
end

def rename(old_filename, new_filename)
code = LibC.rename(old_filename.check_no_null_byte, new_filename.check_no_null_byte)
if code != 0
raise Errno.new("Error renaming file '#{old_filename}' to '#{new_filename}'")
end
end

def utime(atime : ::Time, mtime : ::Time, filename : String) : Nil
timevals = uninitialized LibC::Timeval[2]
timevals[0] = to_timeval(atime)
timevals[1] = to_timeval(mtime)
ret = LibC.utimes(filename, timevals)
if ret != 0
raise Errno.new("Error setting time to file '#{filename}'")
end
end

private def to_timeval(time : ::Time)
t = uninitialized LibC::Timeval
t.tv_sec = typeof(t.tv_sec).new(time.to_local.epoch)
t.tv_usec = typeof(t.tv_usec).new(0)
t
end
end

module FileMixin
private def platform_truncate(size) : Nil
flush
code = LibC.ftruncate(fd, size)
if code != 0
raise Errno.new("Error truncating file '#{path}'")
end
end

private def platform_flock_shared(blocking)
flock LibC::FlockOp::SH, blocking
end

private def platform_flock_exclusive(blocking)
flock LibC::FlockOp::EX, blocking
end

private def platform_flock_unlock
flock LibC::FlockOp::UN
end

private def flock(op : LibC::FlockOp, blocking : Bool = true)
op |= LibC::FlockOp::NB unless blocking

if LibC.flock(@fd, op) != 0
raise Errno.new("flock")
end

nil
end
end
end
2 changes: 1 addition & 1 deletion src/crystal/system/unix/file_descriptor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ module Crystal::System::FileDescriptorMixin
if LibC.fstat(@fd, out stat) != 0
raise Errno.new("Unable to get stat")
end
File::Stat.new(stat)
::File::Stat.new(stat)
end

private def platform_seek(offset, whence : IO::Seek)
Expand Down
4 changes: 2 additions & 2 deletions src/crystal/system/unix/getrandom.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ require "c/sys/syscall"
module Crystal::System::Random
@@initialized = false
@@getrandom_available = false
@@urandom : File?
@@urandom : ::File?

private def self.init
@@initialized = true

if sys_getrandom(Bytes.new(16)) >= 0
@@getrandom_available = true
else
urandom = File.open("/dev/urandom", "r")
urandom = ::File.open("/dev/urandom", "r")
return unless urandom.stat.chardev?

urandom.close_on_exec = true
Expand Down
4 changes: 2 additions & 2 deletions src/crystal/system/unix/urandom.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

module Crystal::System::Random
@@initialized = false
@@urandom : File?
@@urandom : ::File?

private def self.init
@@initialized = true

urandom = File.open("/dev/urandom", "r")
urandom = ::File.open("/dev/urandom", "r")
return unless urandom.stat.chardev?

urandom.close_on_exec = true
Expand Down
Loading

0 comments on commit fd81b4d

Please sign in to comment.