gxg-web-client/gxg/core/file.rb
2021-11-16 08:58:51 -08:00

1289 lines
34 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##
# Platform specific behavior for the File class.
class File < IO
include Enumerable
class FileError < Exception; end
class NoFileError < FileError; end
class UnableToStat < FileError; end
class PermissionError < FileError; end
attr_reader :path
# The mode_t type is 2 bytes (ushort). Instead of getting whatever
# value happens to be in the least significant 16 bits, just set
# the value to 0 if it is greater than 0xffff. Also, negative values
# don't make any sense here.
def clamp_short(value)
mode = Rubinius::Type.coerce_to value, Integer, :to_int
mode < 0 || mode > 0xffff ? 0 : mode
end
module_function :clamp_short
def self.absolute_path(obj, dir = nil)
obj = path(obj)
if obj[0] == "~"
File.join Dir.getwd, dir.to_s, obj
else
expand_path(obj, dir)
end
end
##
# Returns the last access time for the named file as a Time object).
#
# File.atime("testfile") #=> Wed Apr 09 08:51:48 CDT 2003
def self.atime(path)
Stat.new(path).atime
end
##
# Returns the change time for the named file (the
# time at which directory information about the
# file was changed, not the file itself).
#
# File.ctime("testfile") #=> Wed Apr 09 08:53:13 CDT 2003
def self.ctime(path)
Stat.new(path).ctime
end
##
# Returns the birthtime for the named file
#
# Note this is not supported on all platforms.
# See stat(2) for more information.
def self.birthtime(path)
Stat.new(path).birthtime
end
##
# Returns the modification time for the named file as a Time object.
#
# File.mtime("testfile") #=> Tue Apr 08 12:58:04 CDT 2003
def self.mtime(path)
Stat.new(path).mtime
end
##
# Sets the access and modification times of each named
# file to the first two arguments. Returns the number
# of file names in the argument list.
# #=> Integer
def self.utime(a_in, m_in, *paths)
a_in ||= Time.now
a_in_usec = if a_in.is_a?(Time) || a_in.is_a?(Float) || a_in.is_a?(Rational)
Time.at(a_in).usec
else
0
end
m_in ||= Time.now
m_in_usec = if m_in.is_a?(Time) || m_in.is_a?(Float) || m_in.is_a?(Rational)
Time.at(m_in).usec
else
0
end
FFI::MemoryPointer.new(POSIX::TimeVal, 2) do |ptr|
atime = POSIX::TimeVal.new ptr
mtime = POSIX::TimeVal.new ptr[1]
atime[:tv_sec] = a_in.to_i
atime[:tv_usec] = a_in_usec
mtime[:tv_sec] = m_in.to_i
mtime[:tv_usec] = m_in_usec
paths.each do |path|
n = POSIX.utimes(Rubinius::Type.coerce_to_path(path), ptr)
Errno.handle unless n == 0
end
end
end
##
# Returns the last component of the filename given
# in file_name, which must be formed using forward
# slashes (``/) regardless of the separator used
# on the local file system. If suffix is given and
# present at the end of file_name, it is removed.
#
# File.basename("/home/gumby/work/ruby.rb") #=> "ruby.rb"
# File.basename("/home/gumby/work/ruby.rb", ".rb") #=> "ruby"
def self.basename(path, ext=undefined)
path = Rubinius::Type.coerce_to_path(path)
m = Rubinius::Mirror.reflect path
slash = "/"
ext_not_present = undefined.equal?(ext)
if pos = m.find_string_reverse(slash, path.bytesize)
# special case. If the string ends with a /, ignore it.
if pos == path.bytesize - 1
# Find the first non-/ from the right
data = path.data
found = false
pos.downto(0) do |i|
if data[i] != 47 # ?/
path = path.byteslice(0, i+1)
found = true
break
end
end
# edge case, it's all /'s, return "/"
return slash unless found
# Now that we've trimmed the /'s at the end, search again
m = Rubinius::Mirror.reflect path
pos = m.find_string_reverse(slash, path.bytesize)
if ext_not_present and !pos
# No /'s found and ext not present, return path.
return path
end
end
path = path.byteslice(pos + 1, path.bytesize - pos) if pos
end
return path if ext_not_present
m = Rubinius::Mirror.reflect path
# special case. if ext is ".*", remove any extension
ext = StringValue(ext)
if ext == ".*"
if pos = m.find_string_reverse(".", path.bytesize)
return path.byteslice(0, pos)
end
elsif pos = m.find_string_reverse(ext, path.bytesize)
# Check that ext is the last thing in the string
if pos == path.bytesize - ext.size
return path.byteslice(0, pos)
end
end
return path
end
##
# Returns true if the named file is a block device.
def self.blockdev?(path)
st = Stat.stat path
st ? st.blockdev? : false
end
##
# Returns true if the named file is a character device.
def self.chardev?(path)
st = Stat.stat path
st ? st.chardev? : false
end
##
# Changes permission bits on the named file(s) to
# the bit pattern represented by mode_int. Actual
# effects are operating system dependent (see the
# beginning of this section). On Unix systems, see
# chmod(2) for details. Returns the number of files processed.
#
# File.chmod(0644, "testfile", "out") #=> 2
def self.chmod(mode, *paths)
mode = clamp_short mode
paths.each do |path|
n = POSIX.chmod Rubinius::Type.coerce_to_path(path), mode
Errno.handle if n == -1
end
paths.size
end
##
# Equivalent to File::chmod, but does not follow symbolic
# links (so it will change the permissions associated with
# the link, not the file referenced by the link).
# Often not available.
def self.lchmod(mode, *paths)
raise NotImplementedError, "lchmod not implemented on this platform" unless Rubinius::HAVE_LCHMOD
mode = Rubinius::Type.coerce_to(mode, Integer, :to_int)
paths.each do |path|
n = POSIX.lchmod Rubinius::Type.coerce_to_path(path), mode
Errno.handle if n == -1
end
paths.size
end
##
# Changes the owner and group of the
# named file(s) to the given numeric owner
# and group ids. Only a process with superuser
# privileges may change the owner of a file. The
# current owner of a file may change the files
# group to any group to which the owner belongs.
# A nil or -1 owner or group id is ignored.
# Returns the number of files processed.
#
# File.chown(nil, 100, "testfile")
def self.chown(owner, group, *paths)
if owner
owner = Rubinius::Type.coerce_to(owner, Integer, :to_int)
else
owner = -1
end
if group
group = Rubinius::Type.coerce_to(group, Integer, :to_int)
else
group = -1
end
paths.each do |path|
n = POSIX.chown Rubinius::Type.coerce_to_path(path), owner, group
Errno.handle if n == -1
end
paths.size
end
def chmod(mode)
mode = Rubinius::Type.coerce_to(mode, Integer, :to_int)
n = POSIX.fchmod descriptor, clamp_short(mode)
Errno.handle if n == -1
n
end
def chown(owner, group)
if owner
owner = Rubinius::Type.coerce_to(owner, Integer, :to_int)
else
owner = -1
end
if group
group = Rubinius::Type.coerce_to(group, Integer, :to_int)
else
group = -1
end
n = POSIX.fchown descriptor, owner, group
Errno.handle if n == -1
n
end
##
# Equivalent to File::chown, but does not follow
# symbolic links (so it will change the owner
# associated with the link, not the file referenced
# by the link). Often not available. Returns number
# of files in the argument list.
def self.lchown(owner, group, *paths)
raise NotImplementedError, "lchown not implemented on this platform" unless Rubinius::HAVE_LCHOWN
if owner
owner = Rubinius::Type.coerce_to(owner, Integer, :to_int)
else
owner = -1
end
if group
group = Rubinius::Type.coerce_to(group, Integer, :to_int)
else
group = -1
end
paths.each do |path|
n = POSIX.lchown Rubinius::Type.coerce_to_path(path), owner, group
Errno.handle if n == -1
end
paths.size
end
##
# Returns true if the named file is a directory, false otherwise.
#
# File.directory?(".")
def self.directory?(io_or_path)
io = Rubinius::Type.try_convert io_or_path, IO, :to_io
if io.is_a? IO
Stat.fstat(io.fileno).directory?
else
st = Stat.stat io_or_path
st ? st.directory? : false
end
end
def self.last_nonslash(path, start=nil)
# Find the first non-/ from the right
data = path.data
idx = nil
start ||= (path.size - 1)
start.downto(0) do |i|
if data[i] != 47 # ?/
return i
end
end
return nil
end
##
# Returns all components of the filename given in
# file_name except the last one. The filename must be
# formed using forward slashes (``/) regardless of
# the separator used on the local file system.
#
# File.dirname("/home/gumby/work/ruby.rb") #=> "/home/gumby/work"
def self.dirname(path)
path = Rubinius::Type.coerce_to_path(path)
# edge case
return "." if path.empty?
slash = "/"
# pull off any /'s at the end to ignore
chunk_size = last_nonslash(path)
return "/" unless chunk_size
m = Rubinius::Mirror.reflect path
if pos = m.find_string_reverse(slash, chunk_size)
return "/" if pos == 0
path = path.byteslice(0, pos)
return "/" if path == "/"
return path unless path.suffix? slash
# prune any trailing /'s
idx = last_nonslash(path, pos)
# edge case, only /'s, return /
return "/" unless idx
return path.byteslice(0, idx - 1)
end
return "."
end
##
# Returns true if the named file is executable by the
# effective user id of this process.
def self.executable?(path)
st = Stat.stat path
st ? st.executable? : false
end
##
# Returns true if the named file is executable by
# the real user id of this process.
def self.executable_real?(path)
st = Stat.stat path
st ? st.executable_real? : false
end
##
# Return true if the named file exists.
def self.exist?(path)
st = Stat.stat(path)
st ? true : false
end
# Pull a constant for Dir local to File so that we don't have to depend
# on the global Dir constant working. This sounds silly, I know, but it's a
# little bit of defensive coding so Rubinius can run things like fakefs better.
PrivateDir = ::Dir
##
# Converts a pathname to an absolute pathname. Relative
# paths are referenced from the current working directory
# of the process unless dir_string is given, in which case
# it will be used as the starting point. The given pathname
# may start with a ``~, which expands to the process owners
# home directory (the environment variable HOME must be set
# correctly). "~user" expands to the named users home directory.
#
# File.expand_path("~oracle/bin") #=> "/home/oracle/bin"
# File.expand_path("../../bin", "/tmp/x") #=> "/bin"
def self.expand_path(path, dir=nil)
path = Rubinius::Type.coerce_to_path(path)
str = "".force_encoding path.encoding
first = path[0]
if first == ?~
case path[1]
when ?/
unless home = ENV["HOME"]
raise ArgumentError, "couldn't find HOME environment variable when expanding '~'"
end
path = ENV["HOME"] + path.byteslice(1, path.bytesize - 1)
when nil
unless home = ENV["HOME"]
raise ArgumentError, "couldn't find HOME environment variable when expanding '~'"
end
if home.empty?
raise ArgumentError, "HOME environment variable is empty expanding '~'"
end
return home.dup
else
m = Rubinius::Mirror.reflect path
unless length = m.find_string("/", 1)
length = path.bytesize
end
name = path.byteslice 1, length - 1
unless dir = Rubinius.get_user_home(name)
raise ArgumentError, "user #{name} does not exist"
end
path = dir + path.byteslice(length, path.bytesize - length)
end
elsif first != ?/
if dir
dir = expand_path dir
else
dir = PrivateDir.pwd
end
path = "#{dir}/#{path}"
end
items = []
start = 0
size = path.bytesize
m = Rubinius::Mirror.reflect path
while index = m.find_string("/", start) or (start < size and index = size)
length = index - start
if length > 0
item = path.byteslice start, length
if item == ".."
items.pop
elsif item != "."
items << item
end
end
start = index + 1
end
if items.empty?
str << "/"
else
items.each { |x| str.append "/#{x}" }
end
str
end
##
# Returns the extension (the portion of file name in
# path after the period).
#
# File.extname("test.rb") #=> ".rb"
# File.extname("a/b/d/test.rb") #=> ".rb"
# File.extname("test") #=> ""
# File.extname(".profile") #=> ""
def self.extname(path)
path = Rubinius::Type.coerce_to_path(path)
path_size = path.bytesize
m = Rubinius::Mirror.reflect path
dot_idx = m.find_string_reverse(".", path_size)
# No dots at all
return "" unless dot_idx
slash_idx = m.find_string_reverse("/", path_size)
# pretend there is / just to the left of the start of the string
slash_idx ||= -1
# no . in the last component of the path
return "" if dot_idx < slash_idx
# last component starts with a .
return "" if dot_idx == slash_idx + 1
# last component ends with a .
return "" if dot_idx == path_size - 1
return path.byteslice(dot_idx, path_size - dot_idx)
end
##
# Returns true if the named file exists and is a regular file.
def self.file?(path)
st = Stat.stat path
st ? st.file? : false
end
def self.braces(pattern, flags=0, patterns=[])
escape = (flags & FNM_NOESCAPE) == 0
rbrace = nil
lbrace = nil
# Do a quick search for a { to start the search better
i = pattern.index("{")
if i
nest = 0
while i < pattern.size
char = pattern[i]
if char == "{"
lbrace = i if nest == 0
nest += 1
end
if char == "}"
nest -= 1
end
if nest == 0
rbrace = i
break
end
if char == "\\" and escape
i += 1
end
i += 1
end
end
# There was a full {} expression detected, expand each part of it
# recursively.
if lbrace and rbrace
pos = lbrace
front = pattern[0...lbrace]
back = pattern[(rbrace + 1)..-1]
while pos < rbrace
nest = 0
pos += 1
last = pos
while pos < rbrace and not (pattern[pos] == "," and nest == 0)
nest += 1 if pattern[pos] == "{"
nest -= 1 if pattern[pos] == "}"
if pattern[pos] == "\\" and escape
pos += 1
break if pos == rbrace
end
pos += 1
end
brace_pattern = "#{front}#{pattern[last...pos]}#{back}"
patterns << brace_pattern
braces(brace_pattern, flags, patterns)
end
end
patterns
end
##
# Returns true if path matches against pattern The pattern
# is not a regular expression; instead it follows rules
# similar to shell filename globbing. It may contain the
# following metacharacters:
#
# *: Matches any file. Can be restricted by other values in the glob. * will match all files; c* will match all files beginning with c; *c will match all files ending with c; and c will match all files that have c in them (including at the beginning or end). Equivalent to / .* /x in regexp.
# **: Matches directories recursively or files expansively.
# ?: Matches any one character. Equivalent to /.{1}/ in regexp.
# [set]: Matches any one character in set. Behaves exactly like character sets in Regexp, including set negation ([^a-z]).
# <code></code>: Escapes the next metacharacter.
# flags is a bitwise OR of the FNM_xxx parameters. The same glob pattern and flags are used by Dir::glob.
#
# File.fnmatch('cat', 'cat') #=> true : match entire string
# File.fnmatch('cat', 'category') #=> false : only match partial string
# File.fnmatch('c{at,ub}s', 'cats') #=> false : { } isn't supported
# File.fnmatch('c{at,ub}s', 'cats', File::FNM_EXTGLOB) #=> true : { } is supported with FNM_EXTGLOB
#
# File.fnmatch('c?t', 'cat') #=> true : '?' match only 1 character
# File.fnmatch('c??t', 'cat') #=> false : ditto
# File.fnmatch('c*', 'cats') #=> true : '*' match 0 or more characters
# File.fnmatch('c*t', 'c/a/b/t') #=> true : ditto
# File.fnmatch('ca[a-z]', 'cat') #=> true : inclusive bracket expression
# File.fnmatch('ca[^t]', 'cat') #=> false : exclusive bracket expression ('^' or '!')
#
# File.fnmatch('cat', 'CAT') #=> false : case sensitive
# File.fnmatch('cat', 'CAT', File::FNM_CASEFOLD) #=> true : case insensitive
#
# File.fnmatch('?', '/', File::FNM_PATHNAME) #=> false : wildcard doesn't match '/' on FNM_PATHNAME
# File.fnmatch('*', '/', File::FNM_PATHNAME) #=> false : ditto
# File.fnmatch('[/]', '/', File::FNM_PATHNAME) #=> false : ditto
#
# File.fnmatch('\?', '?') #=> true : escaped wildcard becomes ordinary
# File.fnmatch('\a', 'a') #=> true : escaped ordinary remains ordinary
# File.fnmatch('\a', '\a', File::FNM_NOESCAPE) #=> true : FNM_NOESACPE makes '\' ordinary
# File.fnmatch('[\?]', '?') #=> true : can escape inside bracket expression
#
# File.fnmatch('*', '.profile') #=> false : wildcard doesn't match leading
# File.fnmatch('*', '.profile', File::FNM_DOTMATCH) #=> true period by default.
# File.fnmatch('.*', '.profile') #=> true
#
# rbfiles = '**' '/' '*.rb' # you don't have to do like this. just write in single string.
# File.fnmatch(rbfiles, 'main.rb') #=> false
# File.fnmatch(rbfiles, './main.rb') #=> false
# File.fnmatch(rbfiles, 'lib/song.rb') #=> true
# File.fnmatch('**.rb', 'main.rb') #=> true
# File.fnmatch('**.rb', './main.rb') #=> false
# File.fnmatch('**.rb', 'lib/song.rb') #=> true
# File.fnmatch('*', 'dave/.profile') #=> true
#
# pattern = '*' '/' '*'
# File.fnmatch(pattern, 'dave/.profile', File::FNM_PATHNAME) #=> false
# File.fnmatch(pattern, 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH) #=> true
#
# pattern = '**' '/' 'foo'
# File.fnmatch(pattern, 'a/b/c/foo', File::FNM_PATHNAME) #=> true
# File.fnmatch(pattern, '/a/b/c/foo', File::FNM_PATHNAME) #=> true
# File.fnmatch(pattern, 'c:/a/b/c/foo', File::FNM_PATHNAME) #=> true
# File.fnmatch(pattern, 'a/.b/c/foo', File::FNM_PATHNAME) #=> false
# File.fnmatch(pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH) #=> true
def self.fnmatch(pattern, path, flags=0)
pattern = StringValue(pattern)
path = Rubinius::Type.coerce_to_path(path)
flags = Rubinius::Type.coerce_to(flags, Fixnum, :to_int)
brace_match = false
if (flags & FNM_EXTGLOB) != 0
brace_match = braces(pattern, flags).any? { |p| super(p, path, flags) }
end
brace_match || super(pattern, path, flags)
end
##
# Identifies the type of the named file; the return string is
# one of "file", "directory", "characterSpecial",
# "blockSpecial", "fifo", "link", "socket", or "unknown".
#
# File.ftype("testfile") #=> "file"
# File.ftype("/dev/tty") #=> "characterSpecial"
# File.ftype("/tmp/.X11-unix/X0") #=> "socket"
def self.ftype(path)
lstat(path).ftype
end
##
# Returns true if the named file exists and the effective
# group id of the calling process is the owner of the file.
# Returns false on Windows.
def self.grpowned?(path)
begin
lstat(path).grpowned?
rescue
false
end
end
##
# Returns true if the named files are identical.
#
# open("a", "w") {}
# p File.identical?("a", "a") #=> true
# p File.identical?("a", "./a") #=> true
# File.link("a", "b")
# p File.identical?("a", "b") #=> true
# File.symlink("a", "c")
# p File.identical?("a", "c") #=> true
# open("d", "w") {}
# p File.identical?("a", "d") #=> false
def self.identical?(orig, copy)
orig = Rubinius::Type.coerce_to_path(orig)
st_o = File::Stat.stat(orig)
copy = Rubinius::Type.coerce_to_path(copy)
st_c = File::Stat.stat(copy)
return false if st_o.nil? || st_c.nil?
return false unless st_o.dev == st_c.dev
return false unless st_o.ino == st_c.ino
return false unless st_o.ftype == st_c.ftype
return false unless POSIX.access(orig, Constants::R_OK)
return false unless POSIX.access(copy, Constants::R_OK)
true
end
##
# Returns a new string formed by joining the strings using File::SEPARATOR.
#
# File.join("usr", "mail", "gumby") #=> "usr/mail/gumby"
def self.join(*args)
return '' if args.empty?
sep = SEPARATOR
# The first one is unrolled out of the loop to remove a condition
# from the loop. It seems needless, but you'd be surprised how much hinges
# on the performance of File.join
#
first = args.shift
case first
when String
first = first.dup
when Array
recursion = Thread.detect_recursion(first) do
first = join(*first)
end
raise ArgumentError, "recursive array" if recursion
else
# We need to use dup here, since it's possible that
# StringValue gives us a direct object we shouldn't mutate
first = Rubinius::Type.coerce_to_path(first).dup
end
ret = first
args.each do |el|
value = nil
case el
when String
value = el
when Array
recursion = Thread.detect_recursion(el) do
value = join(*el)
end
raise ArgumentError, "recursive array" if recursion
else
value = Rubinius::Type.coerce_to_path(el)
end
if value.prefix? sep
ret.gsub!(/#{SEPARATOR}+$/, '')
elsif not ret.suffix? sep
ret << sep
end
ret << value
end
ret
end
##
# Creates a new name for an existing file using a hard link.
# Will not overwrite new_name if it already exists (raising
# a subclass of SystemCallError). Not available on all platforms.
#
# File.link("testfile", ".testfile") #=> 0
# IO.readlines(".testfile")[0] #=> "This is line one\n"
def self.link(from, to)
n = POSIX.link Rubinius::Type.coerce_to_path(from), Rubinius::Type.coerce_to_path(to)
Errno.handle if n == -1
n
end
##
# Same as File::stat, but does not follow the last symbolic link.
# Instead, reports on the link itself.
#
# File.symlink("testfile", "link2test") #=> 0
# File.stat("testfile").size #=> 66
# File.lstat("link2test").size #=> 8
# File.stat("link2test").size #=> 66
def self.lstat(path)
Stat.lstat path
end
def self.path(obj)
return obj.to_path if obj.respond_to? :to_path
StringValue(obj)
end
##
# Returns true if the named file is a pipe.
def self.pipe?(path)
st = Stat.stat path
st ? st.pipe? : false
end
##
# Returns true if the named file is readable by the effective
# user id of this process.
def self.readable?(path)
st = Stat.stat path
st ? st.readable? : false
end
##
# Returns true if the named file is readable by the real user
# id of this process.
def self.readable_real?(path)
st = Stat.stat path
st ? st.readable_real? : false
end
##
# Returns the name of the file referenced by the given link.
# Not available on all platforms.
#
# File.symlink("testfile", "link2test") #=> 0
# File.readlink("link2test") #=> "testfile"
def self.readlink(path)
FFI::MemoryPointer.new(1024) do |ptr|
n = POSIX.readlink Rubinius::Type.coerce_to_path(path), ptr, 1024
Errno.handle if n == -1
return ptr.read_string(n)
end
end
def self.realpath(path, basedir = nil)
real = basic_realpath path, basedir
unless exist? real
raise Errno::ENOENT, real
end
real
end
def self.realdirpath(path, basedir = nil)
real = basic_realpath path, basedir
dir = dirname real
unless directory? dir
raise Errno::ENOENT, real
end
real
end
def self.basic_realpath(path, basedir = nil)
path = expand_path(path, basedir || Dir.pwd)
real = ''
symlinks = {}
while !path.empty?
pos = path.index(SEPARATOR, 1)
if pos
name = path[0...pos]
path = path[pos..-1]
else
name = path
path = ''
end
real = join(real, name)
if symlink?(real)
raise Errno::ELOOP if symlinks[real]
symlinks[real] = true
if path.empty?
path = expand_path(readlink(real), dirname(real))
else
path = expand_path(join(readlink(real), path), dirname(real))
end
real = ''
end
end
real
end
class << self
private :basic_realpath
end
##
# Renames the given file to the new name. Raises a SystemCallError
# if the file cannot be renamed.
#
# File.rename("afile", "afile.bak") #=> 0
def self.rename(from, to)
n = POSIX.rename Rubinius::Type.coerce_to_path(from), Rubinius::Type.coerce_to_path(to)
Errno.handle if n == -1
n
end
##
# Returns the size of file_name.
def self.size(io_or_path)
io = Rubinius::Type.try_convert io_or_path, IO, :to_io
if io.is_a? IO
Stat.fstat(io.fileno).size
else
stat(io_or_path).size
end
end
##
# Returns nil if file_name doesnt exist or has zero size,
# the size of the file otherwise.
def self.size?(io_or_path)
s = 0
io = Rubinius::Type.try_convert io_or_path, IO, :to_io
if io.is_a? IO
s = Stat.fstat(io.fileno).size
else
st = Stat.stat io_or_path
s = st.size if st
end
s > 0 ? s : nil
end
##
# Returns true if the named file is a socket.
def self.socket?(path)
st = Stat.stat path
st ? st.socket? : false
end
##
# Splits the given string into a directory and a file component and returns them in a two-element array. See also File::dirname and File::basename.
#
# File.split("/home/gumby/.profile") #=> ["/home/gumby", ".profile"]
def self.split(path)
p = Rubinius::Type.coerce_to_path(path)
[dirname(p), basename(p)]
end
##
# Returns a File::Stat object for the named file (see File::Stat).
#
# File.stat("testfile").mtime #=> Tue Apr 08 12:58:04 CDT 2003
def self.stat(path)
Stat.new path
end
##
# Creates a symbolic link called new_name for the
# existing file old_name. Raises a NotImplemented
# exception on platforms that do not support symbolic links.
#
# File.symlink("testfile", "link2test") #=> 0
def self.symlink(from, to)
n = POSIX.symlink Rubinius::Type.coerce_to_path(from), Rubinius::Type.coerce_to_path(to)
Errno.handle if n == -1
n
end
##
# Returns true if the named file is a symbolic link.
def self.symlink?(path)
Stat.lstat(path).symlink?
rescue Errno::ENOENT, Errno::ENOTDIR
false
end
##
# Copies a file from to to. If to is a directory, copies from to to/from.
def self.syscopy(from, to)
out = directory?(to) ? to + basename(from) : to
open(out, 'w') do |f|
f.write read(from).read
end
end
##
# Truncates the file file_name to be at most integer
# bytes long. Not available on all platforms.
#
# f = File.new("out", "w")
# f.write("1234567890") #=> 10
# f.close #=> nil
# File.truncate("out", 5) #=> 0
# File.size("out") #=> 5
def self.truncate(path, length)
path = Rubinius::Type.coerce_to_path(path)
unless exist?(path)
raise Errno::ENOENT, path
end
length = Rubinius::Type.coerce_to length, Integer, :to_int
FileDescriptor.truncate(path, length)
end
##
# Returns the current umask value for this process.
# If the optional argument is given, set the umask
# to that value and return the previous value. Umask
# values are subtracted from the default permissions,
# so a umask of 0222 would make a file read-only for
# everyone.
#
# File.umask(0006) #=> 18
# File.umask #=> 6
def self.umask(mask = nil)
if mask
POSIX.umask clamp_short(mask)
else
old_mask = POSIX.umask(0)
POSIX.umask old_mask
old_mask
end
end
##
# Deletes the named files, returning the number of names
# passed as arguments. Raises an exception on any error.
#
# See also Dir::rmdir.
def self.unlink(*paths)
paths.each do |path|
n = POSIX.unlink Rubinius::Type.coerce_to_path(path)
Errno.handle if n == -1
end
paths.size
end
def self.world_readable?(path)
path = Rubinius::Type.coerce_to_path path
return nil unless exist? path
mode = Stat.new(path).mode
if (mode & Stat::S_IROTH) == Stat::S_IROTH
tmp = mode & (Stat::S_IRUGO | Stat::S_IWUGO | Stat::S_IXUGO)
return Rubinius::Type.coerce_to tmp, Fixnum, :to_int
end
nil
end
def self.world_writable?(path)
path = Rubinius::Type.coerce_to_path path
return nil unless exist? path
mode = Stat.new(path).mode
if (mode & Stat::S_IWOTH) == Stat::S_IWOTH
tmp = mode & (Stat::S_IRUGO | Stat::S_IWUGO | Stat::S_IXUGO)
return Rubinius::Type.coerce_to tmp, Fixnum, :to_int
end
end
##
# Returns true if the named file is writable by the effective
# user id of this process.
def self.writable?(path)
st = Stat.stat path
st ? st.writable? : false
end
##
# Returns true if the named file is writable by the real user
# id of this process.
def self.writable_real?(path)
st = Stat.stat path
st ? st.writable_real? : false
end
##
# Returns true if the named file exists and has a zero size.
def self.zero?(path)
st = Stat.stat path
st ? st.zero? : false
end
##
# Returns true if the named file exists and the effective
# used id of the calling process is the owner of the file.
# File.owned?(file_name) => true or false
def self.owned?(file_name)
Stat.new(file_name).owned?
rescue Errno::ENOENT
return false
end
##
# Returns true if the named file has the setgid bit set.
def self.setgid?(file_name)
Stat.new(file_name).setgid?
rescue Errno::ENOENT
return false
end
##
# Returns true if the named file has the setuid bit set.
def self.setuid?(file_name)
Stat.new(file_name).setuid?
rescue Errno::ENOENT
return false
end
##
# Returns true if the named file has the sticky bit set.
def self.sticky?(file_name)
Stat.new(file_name).sticky?
rescue Errno::ENOENT
return false
end
def self.mkfifo(file_name, mode = 0666)
raise NotImplementedError, "mkfifo is not implemented on this platform" unless Rubinius::HAVE_MKFIFO
file_name = file_name.to_path if file_name.respond_to?(:to_path)
file_name = StringValue(file_name)
ret = POSIX.mkfifo(file_name, mode)
if ret == 0
ret
else
Errno.handle(file_name)
end
end
class << self
alias_method :delete, :unlink
alias_method :exists?, :exist?
alias_method :fnmatch?, :fnmatch
end
def initialize(path_or_fd, mode=undefined, perm=undefined, options=undefined)
if path_or_fd.kind_of? Integer
super(path_or_fd, mode, options)
@path = nil
else
path = Rubinius::Type.coerce_to_path path_or_fd
# TODO: fix normalize_options
case mode
when String, Fixnum
# do nothing
when nil, undefined
mode = "r"
when Hash
options = mode
mode = nil
else
options = Rubinius::Type.coerce_to mode, Hash, :to_hash
mode = nil
end
if undefined.equal?(options)
options = Rubinius::Type.try_convert(perm, Hash, :to_hash)
perm = undefined if options
end
nmode, binary, external, internal = IO.normalize_options(mode, options)
nmode ||= "r"
perm = 0666 if undefined.equal? perm
fd = IO.sysopen(path, nmode, perm)
Errno.handle path if fd < 0
@path = path
super(fd, mode, options)
end
end
private :initialize
##
# see File.atime
def atime
Stat.new(@path).atime
end
##
# see File.ctime
def ctime
Stat.new(@path).ctime
end
##
# see File.birthtime
def birthtime
Stat.new(@path).birthtime
end
##
# see File.mtime
def mtime
Stat.new(@path).mtime
end
def reopen(other, mode = 'r+')
rewind unless closed? rescue Errno::ESPIPE
unless other.kind_of? IO
other = Rubinius::Type.coerce_to_path(other)
end
super(other, mode)
end
def flock(const)
const = Rubinius::Type.coerce_to const, Integer, :to_int
result = POSIX.flock descriptor, const
return false if result == -1
result
end
def lstat
Stat.lstat @path
end
def stat
Stat.fstat descriptor
end
alias_method :to_path, :path
def truncate(length)
length = Rubinius::Type.coerce_to length, Integer, :to_int
ensure_open_and_writable
raise Errno::EINVAL, "Can't truncate a file to a negative length" if length < 0
flush
reset_buffering
@fd.ftruncate(length)
end
def inspect
return_string = "#<#{self.class}:0x#{object_id.to_s(16)} path=#{@path}"
return_string << " (closed)" if closed?
return_string << ">"
end
def size
raise IOError, "closed stream" if closed?
stat.size
end
##
# Return the equivalent S-Expression of the file given.
# Raises +SyntaxError+ if there is a syntax issue in the
# file, making it unparsable.
# File.to_sexp("/tmp/test.rb") #=> s(...)
def self.to_sexp(name)
File.read(name).to_sexp(name)
end
end