1 # depends on: io.rb class.rb module.rb enumerable.rb
4 class TimeVal < FFI::Struct
5 config 'rbx.platform.timeval', :tv_sec, :tv_usec
14 # Internal class for accessing timevals
17 class FileError < Exception; end
18 class NoFileError < FileError; end
19 class UnableToStat < FileError; end
20 class PermissionError < FileError; end
25 # TODO: "OK" constants aren't in File::Constants in MRI
26 F_OK = 0 # test for existence of file
27 X_OK = 1 # test for execute or search permission
28 W_OK = 2 # test for write permission
29 R_OK = 4 # test for read permission
38 SEPARATOR = Platform::File::SEPARATOR
39 Separator = Platform::File::SEPARATOR
40 ALT_SEPARATOR = Platform::File::ALT_SEPARATOR
41 PATH_SEPARATOR = Platform::File::PATH_SEPARATOR
42 POSIX = Platform::POSIX
44 def initialize(path_or_fd, mode = "r", perm = 0666)
45 if path_or_fd.kind_of?(Integer)
46 super(path_or_fd, mode)
51 path = StringValue(path_or_fd)
53 fd = IO.sysopen(path, mode, perm)
54 Errno.handle path if fd < 0
63 private_class_method :dirsep?, :next_path, :range, :name_match
65 # these will be necessary when we run on Windows
66 const_set :DOSISH, RUBY_PLATFORM.match("mswin")
67 const_set :CASEFOLD_FILESYSTEM, DOSISH
68 const_set :FNM_SYSCASE, CASEFOLD_FILESYSTEM ? FNM_CASEFOLD : false
75 def self.basename(path,ext = "")
76 path = StringValue(path)
77 ext = StringValue(ext)
78 Platform::File.basename(path,ext)
81 def self.blockdev?(path)
83 st ? st.blockdev? : false
86 def self.chardev?(path)
88 st ? st.chardev? : false
91 def self.chmod(mode, *paths)
92 mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
94 path = Type.coerce_to(path, String, :to_str) unless path.is_a? String
95 POSIX.chmod(path, mode)
100 def self.lchmod(mode, *paths)
101 mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
103 path = Type.coerce_to(path, String, :to_str) unless path.is_a? String
104 POSIX.lchmod(path, mode)
109 def self.chown(owner_int, group_int, *paths)
110 owner_int = -1 if owner_int == nil
111 group_int = -1 if group_int == nil
112 paths.each { |path| POSIX.chown(path, owner_int, group_int) }
116 def self.lchown(owner_int, group_int, *paths)
117 owner_int = -1 if owner_int == nil
118 group_int = -1 if group_int == nil
119 paths.each { |path| POSIX.lchown(path, owner_int, group_int) }
127 def self.directory?(path)
129 st ? st.directory? : false
132 def self.dirname(path)
133 path = StringValue(path)
134 Platform::File.dirname(path)
137 def self.executable?(path)
139 st ? st.executable? : false
142 def self.executable_real?(path)
144 st ? st.executable_real? : false
147 def self.exist?(path)
148 Stat.stat?(path) ? true : false
151 def self.expand_path(path, dir_string = nil)
152 Platform::File.expand_path(path, dir_string)
155 def self.extname(path)
156 filename = File.basename(StringValue(path))
157 idx = filename.rindex '.'
158 have_dot = idx != nil
159 first_char = idx == 0
160 last_char = idx == filename.length - 1
161 only_dots = filename.match(/[^\.]/).nil?
163 return '' unless have_dot
164 return '' if first_char || last_char
165 return '' if only_dots
166 filename.slice idx..-1
171 st ? st.file? : false
175 # File.fnmatch and helpers. This is a port of JRuby's code
178 def self.dirsep?(char)
179 DOSISH ? (char == ?\\ || char == ?/) : char == ?/
182 def self.next_path(str, start, strend)
183 start += 1 while start < strend and !dirsep? str[start]
187 def self.range(pattern, pstart, pend, test, flags)
189 escape = (flags & FNM_NOESCAPE) == 0
190 case_sensitive = (flags & FNM_CASEFOLD) == 0
191 neg = pattern[pstart] == ?! || pattern[pstart] == ?^
195 while pattern[pstart] != ?] do
196 pstart += 1 if escape && pattern[pstart] == ?\\
197 return -1 if pstart >= pend
198 cstart = cend = pattern[pstart]
201 if pattern[pstart] == ?- && pattern[pstart+1] != ?]
203 pstart += 1 if escape && pattern[pstart] == ?\\
204 return -1 if pstart >= pend
205 cend = pattern[pstart]
210 ok = true if cstart <= test && test <= cend
212 ok = true if cstart.tolower <= test.tolower &&
213 test.tolower <= cend.tolower
217 ok == neg ? -1 : pstart + 1
220 def self.name_match(pattern, str, flags, patstart, patend, strstart, strend)
223 escape = (flags & FNM_NOESCAPE) == 0
224 pathname = (flags & FNM_PATHNAME) != 0
225 period = (flags & FNM_DOTMATCH) == 0
226 nocase = (flags & FNM_CASEFOLD) != 0
228 while pstart < patend do
229 char = pattern[pstart]
233 if (index >= strend || (pathname && dirsep?(str[index])) ||
234 (period && str[index] == ?. && (index == 0 ||
235 (pathname && dirsep?(str[index-1])))))
240 while pstart < patend
241 char = pattern[pstart]
243 break unless char == ?*
245 if (index < strend && (period && str[index] == ?. &&
246 (index == 0 || (pathname && dirsep?(str[index-1])))))
249 if pstart > patend || (pstart == patend && char == ?*)
250 if pathname && next_path(str, index, strend) < strend
255 elsif pathname && dirsep?(char)
256 index = next_path(str, index, strend)
263 test = (escape && char == ?\\ && pstart < patend ? pattern[pstart] : char).tolower
265 while index < strend do
266 if ((char == ?? || char == ?[ || str[index].tolower == test) &&
267 name_match(pattern, str, flags | FNM_DOTMATCH, pstart, patend, index, strend))
269 elsif (pathname && dirsep?(str[index]))
277 if (index >= strend || (pathname && dirsep?(str[index]) ||
278 (period && str[index] == ?. && (index == 0 || (pathname && dirsep?(str[index-1]))))))
281 pstart = range(pattern, pstart, patend, str[index], flags)
282 return false if pstart == -1
286 if (escape && (!DOSISH || (pstart < patend && "*?[]\\".index(pattern[pstart]))))
287 char = pstart >= patend ? ?\\ : pattern[pstart]
291 return false if index >= strend
293 if DOSISH && (pathname && isdirsep?(char) && dirsep?(str[index]))
294 # TODO: invert this boolean expression
297 return false if char.tolower != str[index].tolower
299 return false if char != str[index]
306 index >= strend ? true : false
309 def self.fnmatch(pattern, path, flags=0)
310 pattern = StringValue(pattern).dup
311 path = StringValue(path).dup
312 flags = Type.coerce_to(flags, Fixnum, :to_int) unless flags.__kind_of__ Fixnum
313 name_match(pattern, path, flags, 0, pattern.size, 0, path.size)
320 def self.grpowned?(path)
322 lstat(path).grpowned?
328 def self.identical?(orig, copy)
329 st_o = stat(StringValue(orig))
330 st_c = stat(StringValue(copy))
332 return false unless st_o.ino == st_c.ino
333 return false unless st_o.ftype == st_c.ftype
334 return false unless POSIX.access(orig, Constants::R_OK)
335 return false unless POSIX.access(copy, Constants::R_OK)
342 o = o.to_str unless Array === o || String === o
344 } rescue raise TypeError
346 # let join/split deal with all the recursive array complexities
347 # one small hack is to replace URI header with \0 and swap back later
348 result = args.join(SEPARATOR).gsub(/\:\//, "\0").split(/#{SEPARATOR}+/o)
349 result << '' if args.last.empty? || args.last[-1] == SEPARATOR[0]
350 result.join(SEPARATOR).gsub(/\0/, ':/')
353 def self.link(from, to)
355 from = StringValue(from)
357 n = POSIX.link(from, to)
358 Errno.handle if n == -1
372 st ? st.pipe? : false
375 def self.readable?(path)
377 st ? st.readable? : false
380 def self.readable_real?(path)
382 st ? st.readable_real? : false
385 def self.rename(from, to)
387 from = StringValue(from)
389 n = POSIX.rename(from, to)
390 Errno.handle if n == -1
394 def self.readlink(path)
399 n = POSIX.readlink(path, buf, buf.length)
400 Errno.handle if n == -1
411 st && st.size > 0 ? st.size : nil
414 def self.socket?(path)
416 st ? st.socket? : false
420 p = StringValue(path)
421 [dirname(p), basename(p)]
428 def self.symlink(from, to)
430 from = StringValue(from)
432 n = POSIX.symlink(from, to)
433 Errno.handle if n == -1
437 def self.symlink?(path)
438 st = Stat.stat? path, false
439 st ? st.symlink? : false
442 def self.to_sexp(name, newlines=true)
443 out = to_sexp_full(name, newlines)
444 if out.kind_of? Tuple
445 exc = SyntaxError.new out.at(0)
446 exc.import_position out.at(1), out.at(2), out.at(3)
451 out = [:newline, 0, name, [:nil]] unless out
455 def self.truncate(path, length)
456 unless self.exist?(path)
457 raise Errno::ENOENT, path
460 unless length.respond_to?(:to_int)
461 raise TypeError, "can't convert #{length.class} into Integer"
464 n = POSIX.truncate(path, length)
465 Errno.handle if n == -1
469 def self.umask(mask = nil)
473 old_mask = POSIX.umask(0)
474 POSIX.umask(old_mask)
479 def self.unlink(*paths)
481 path = StringValue(path)
483 n = POSIX.unlink(path)
484 Errno.handle if n == -1
490 def self.utime(a_in, m_in, *paths)
491 ptr = MemoryPointer.new(POSIX::TimeVal, 2)
492 atime = POSIX::TimeVal.new ptr
493 mtime = POSIX::TimeVal.new ptr[1]
494 atime[:tv_sec] = a_in.to_i
497 mtime[:tv_sec] = m_in.to_i
501 if POSIX.utimes(path, ptr) != 0
509 def self.writable?(path)
511 st ? st.writable? : false
514 def self.writable_real?(path)
516 st ? st.writable_real? : false
521 st ? st.zero? : false
525 alias_method :delete, :unlink
526 alias_method :exists?, :exist?
527 alias_method :fnmatch?, :fnmatch
531 Stat.new(@path).atime
535 mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
536 POSIX.fchmod(@descriptor, mode)
539 def chown(owner_int, group_int)
540 POSIX.fchown(@descriptor, owner_int || -1, group_int || -1)
544 Stat.new(@path).ctime
547 def flock(locking_constant)
548 result = POSIX.flock(@descriptor, locking_constant)
549 return false if result == -1
554 Stat.new @path, false
558 Stat.new(@path).mtime
566 length = Type.coerce_to(length, Integer, :to_int)
568 raise Errno::EINVAL, "Can't truncate a file to a negative length" if length < 0
569 raise IOError, "File is closed" if closed?
571 n = POSIX.ftruncate(@descriptor, length)
572 Errno.handle if n == -1
577 return_string = "#<#{self.class}:0x#{object_id.to_s(16)} path=#{@path}"
578 return_string << " (closed)" if closed?
584 class Struct < FFI::Struct
585 config "rbx.platform.stat", :st_dev, :st_ino, :st_mode, :st_nlink,
586 :st_uid, :st_gid, :st_rdev, :st_size, :st_blksize, :st_blocks,
587 :st_atime, :st_mtime, :st_ctime
592 S_IRUSR = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IRUSR']
593 S_IWUSR = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IWUSR']
594 S_IXUSR = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IXUSR']
595 S_IRGRP = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IRGRP']
596 S_IWGRP = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IWGRP']
597 S_IXGRP = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IXGRP']
598 S_IROTH = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IROTH']
599 S_IWOTH = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IWOTH']
600 S_IXOTH = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IXOTH']
602 S_IFMT = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFMT']
603 S_IFIFO = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFIFO']
604 S_IFCHR = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFCHR']
605 S_IFDIR = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFDIR']
606 S_IFBLK = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFBLK']
607 S_IFREG = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFREG']
608 S_IFLNK = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFLNK']
609 S_IFSOCK = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFSOCK']
610 S_IFWHT = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFWHT']
611 S_ISUID = Rubinius::RUBY_CONFIG['rbx.platform.file.S_ISUID']
612 S_ISGID = Rubinius::RUBY_CONFIG['rbx.platform.file.S_ISGID']
614 POSIX = Platform::POSIX
616 def initialize(path, follow_links=true)
617 @path = StringValue path
620 result = POSIX.stat @path, @stat.pointer
622 result = POSIX.lstat @path, @stat.pointer
624 Errno.handle @path unless result == 0
627 def self.stat?(path, follow_links=true)
628 new path, follow_links
629 rescue Errno::ENOENT, Errno::ENOTDIR
634 Time.at @stat[:st_atime]
646 @stat[:st_mode] & S_IFMT == S_IFBLK
650 @stat[:st_mode] & S_IFMT == S_IFCHR
654 Time.at @stat[:st_ctime]
662 major = POSIX.major @stat[:st_dev]
663 major < 0 ? nil : major
667 minor = POSIX.major @stat[:st_dev]
668 minor < 0 ? nil : minor
672 @stat[:st_mode] & S_IFMT == S_IFDIR
676 return true if superuser?
677 return @stat[:st_mode] & S_IXUSR != 0 if owned?
678 return @stat[:st_mode] & S_IXGRP != 0 if grpowned?
679 return @stat[:st_mode] & S_IXOTH != 0
683 return true if rsuperuser?
684 return @stat[:st_mode] & S_IXUSR != 0 if rowned?
685 return @stat[:st_mode] & S_IXGRP != 0 if rgrpowned?
686 return @stat[:st_mode] & S_IXOTH != 0
690 @stat[:st_mode] & S_IFMT == S_IFREG
718 @stat[:st_gid] == POSIX.getegid
726 "#<File::Stat dev=0x#{self.dev.to_s(16)}, ino=#{self.ino}, " \
727 "mode=#{sprintf("%07d", self.mode.to_s(8).to_i)}, nlink=#{self.nlink}, " \
728 "uid=#{self.uid}, gid=#{self.gid}, rdev=0x#{self.rdev.to_s(16)}, " \
729 "size=#{self.size}, blksize=#{self.blksize}, blocks=#{self.blocks}, " \
730 "atime=#{self.atime}, mtime=#{self.mtime}, ctime=#{self.ctime}>"
738 Time.at @stat[:st_mtime]
746 @stat[:st_uid] == POSIX.geteuid
754 @stat[:st_mode] & S_IFMT == S_IFIFO
762 major = POSIX.major @stat[:st_rdev]
763 major < 0 ? nil : major
767 minor = POSIX.minor @stat[:st_rdev]
768 minor < 0 ? nil : minor
772 return true if superuser?
773 return @stat[:st_mode] & S_IRUSR != 0 if owned?
774 return @stat[:st_mode] & S_IRGRP != 0 if grpowned?
775 return @stat[:st_mode] & S_IROTH != 0
779 return true if rsuperuser?
780 return @stat[:st_mode] & S_IRUSR != 0 if rowned?
781 return @stat[:st_mode] & S_IRGRP != 0 if rgrpowned?
782 return @stat[:st_mode] & S_IROTH != 0
790 size == 0 ? nil : size
794 @stat[:st_mode] & S_IFMT == S_IFSOCK
798 @stat[:st_mode] & S_IFMT == S_IFLNK
806 return true if superuser?
807 return @stat[:st_mode] & S_IWUSR != 0 if owned?
808 return @stat[:st_mode] & S_IWGRP != 0 if grpowned?
809 return @stat[:st_mode] & S_IWOTH != 0
813 return true if rsuperuser?
814 return @stat[:st_mode] & S_IWUSR != 0 if rowned?
815 return @stat[:st_mode] & S_IWGRP != 0 if rgrpowned?
816 return @stat[:st_mode] & S_IWOTH != 0
824 return nil unless other.is_a?(File::Stat)
825 self.mtime <=> other.mtime
829 @stat[:st_gid] == POSIX.getgid
834 @stat[:st_uid] == POSIX.getuid