## # 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 id‘s. Only a process with superuser # privileges may change the owner of a file. The # current owner of a file may change the file‘s # 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 owner‘s # home directory (the environment variable HOME must be set # correctly). "~user" expands to the named user‘s 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]). # : 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 doesn‘t 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