Updated RubySpec submodule to 9f66d0b1.
[rbx.git] / kernel / core / file.rb
blob6bfa0ed555dc254ad20d86d0535bab50fbed062e
1 # depends on: io.rb class.rb module.rb enumerable.rb
3 module Platform::POSIX
4   class TimeVal < FFI::Struct
5     config 'rbx.platform.timeval', :tv_sec, :tv_usec
6   end
7 end
9 class File < IO
11   include Enumerable
13   #--
14   # Internal class for accessing timevals
15   #++
17   class FileError < Exception; end
18   class NoFileError < FileError; end
19   class UnableToStat < FileError; end
20   class PermissionError < FileError; end
22   module Constants
23     include IO::Constants
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
31     FNM_NOESCAPE = 0x01
32     FNM_PATHNAME = 0x02
33     FNM_DOTMATCH = 0x04
34     FNM_CASEFOLD = 0x08
35   end
36   include Constants
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)
47       @path = nil
48       return self
49     end
51     path = StringValue(path_or_fd)
53     fd = IO.sysopen(path, mode, perm)
54     Errno.handle path if fd < 0
56     @path = path
57     setup fd
58   end
60   attr_reader :path
62   def self.after_loaded
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
69   end
71   def self.atime(path)
72     Stat.new(path).atime
73   end
75   def self.basename(path,ext = "")
76     path = StringValue(path)
77     ext = StringValue(ext)
78     Platform::File.basename(path,ext)
79   end
81   def self.blockdev?(path)
82     st = Stat.stat? path
83     st ? st.blockdev? : false
84   end
86   def self.chardev?(path)
87     st = Stat.stat? path
88     st ? st.chardev? : false
89   end
91   def self.chmod(mode, *paths)
92     mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
93     paths.each do |path|
94       path = Type.coerce_to(path, String, :to_str) unless path.is_a? String
95       POSIX.chmod(path, mode)
96     end
97     paths.size
98   end
100   def self.lchmod(mode, *paths)
101     mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
102     paths.each do |path|
103       path = Type.coerce_to(path, String, :to_str) unless path.is_a? String
104       POSIX.lchmod(path, mode)
105     end
106     paths.size
107   end
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) }
113     paths.size
114   end
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) }
120     paths.size
121   end
123   def self.ctime(path)
124     Stat.new(path).ctime
125   end
127   def self.directory?(path)
128     st = Stat.stat? path
129     st ? st.directory? : false
130   end
132   def self.dirname(path)
133     path = StringValue(path)
134     Platform::File.dirname(path)
135   end
137   def self.executable?(path)
138     st = Stat.stat? path
139     st ? st.executable? : false
140   end
142   def self.executable_real?(path)
143     st = Stat.stat? path
144     st ? st.executable_real? : false
145   end
147   def self.exist?(path)
148     Stat.stat?(path) ? true : false
149   end
151   def self.expand_path(path, dir_string = nil)
152     Platform::File.expand_path(path, dir_string)
153   end
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
167   end
169   def self.file?(path)
170     st = Stat.stat? path
171     st ? st.file? : false
172   end
174   #--
175   # File.fnmatch and helpers. This is a port of JRuby's code
176   #++
178   def self.dirsep?(char)
179     DOSISH ? (char == ?\\ || char == ?/) : char == ?/
180   end
182   def self.next_path(str, start, strend)
183     start += 1 while start < strend and !dirsep? str[start]
184     start
185   end
187   def self.range(pattern, pstart, pend, test, flags)
188     ok = false
189     escape = (flags & FNM_NOESCAPE) == 0
190     case_sensitive = (flags & FNM_CASEFOLD) == 0
191     neg = pattern[pstart] == ?! || pattern[pstart] == ?^
193     pstart += 1 if neg
195     while pattern[pstart] != ?] do
196       pstart += 1 if escape && pattern[pstart] == ?\\
197       return -1 if pstart >= pend
198       cstart = cend = pattern[pstart]
199       pstart += 1
201       if pattern[pstart] == ?- && pattern[pstart+1] != ?]
202         pstart += 1
203         pstart += 1 if escape && pattern[pstart] == ?\\
204         return -1 if pstart >= pend
205         cend = pattern[pstart]
206         pstart += 1
207       end
209       if case_sensitive
210         ok = true if cstart <= test && test <= cend
211       else
212         ok = true if cstart.tolower <= test.tolower &&
213           test.tolower <= cend.tolower
214       end
215     end
217     ok == neg ? -1 : pstart + 1
218   end
220   def self.name_match(pattern, str, flags, patstart, patend, strstart, strend)
221     index = strstart
222     pstart = patstart
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]
230       pstart += 1
231       case char
232       when ??
233         if (index >= strend || (pathname && dirsep?(str[index])) ||
234             (period && str[index] == ?. && (index == 0 ||
235             (pathname && dirsep?(str[index-1])))))
236           return false
237         end
238         index += 1
239       when ?*
240         while pstart < patend
241           char = pattern[pstart]
242           pstart += 1
243           break unless char == ?*
244         end
245         if (index < strend && (period && str[index] == ?. &&
246             (index == 0 || (pathname && dirsep?(str[index-1])))))
247           return false
248         end
249         if pstart > patend || (pstart == patend && char == ?*)
250           if pathname && next_path(str, index, strend) < strend
251             return false
252           else
253             return true
254           end
255         elsif pathname && dirsep?(char)
256           index = next_path(str, index, strend)
257           if index < strend
258             index += 1
259           else
260             return false
261           end
262         else
263           test = (escape && char == ?\\ && pstart < patend ? pattern[pstart] : char).tolower
264           pstart -= 1
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))
268               return true
269             elsif (pathname && dirsep?(str[index]))
270               break
271             end
272             index += 1
273           end
274           return false
275         end
276       when ?[
277         if (index >= strend || (pathname && dirsep?(str[index]) ||
278             (period && str[index] == ?. && (index == 0 || (pathname && dirsep?(str[index-1]))))))
279           return false
280         end
281         pstart = range(pattern, pstart, patend, str[index], flags)
282         return false if pstart == -1
283         index += 1
284       else
285         if char == ?\\
286           if (escape && (!DOSISH || (pstart < patend && "*?[]\\".index(pattern[pstart]))))
287             char = pstart >= patend ? ?\\ : pattern[pstart]
288             pstart += 1
289           end
290         end
291         return false if index >= strend
293         if DOSISH && (pathname && isdirsep?(char) && dirsep?(str[index]))
294           # TODO: invert this boolean expression
295         else
296           if nocase
297             return false if char.tolower != str[index].tolower
298           else
299             return false if char != str[index]
300           end
301         end
302         index += 1
303       end
304     end
306     index >= strend ? true : false
307   end
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)
314   end
316   def self.ftype(path)
317     lstat(path).ftype
318   end
320   def self.grpowned?(path)
321     begin
322       lstat(path).grpowned?
323     rescue
324       false
325     end
326   end
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)
337     true
338   end
340   def self.join(*args)
341     args.map! { |o|
342       o = o.to_str unless Array === o || String === o
343       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/, ':/')
351   end
353   def self.link(from, to)
354     to = StringValue(to)
355     from = StringValue(from)
357     n = POSIX.link(from, to)
358     Errno.handle if n == -1
359     n
360   end
362   def self.lstat(path)
363     Stat.new path, false
364   end
366   def self.mtime(path)
367     Stat.new(path).mtime
368   end
370   def self.pipe?(path)
371     st = Stat.stat? path
372     st ? st.pipe? : false
373   end
375   def self.readable?(path)
376     st = Stat.stat? path
377     st ? st.readable? : false
378   end
380   def self.readable_real?(path)
381     st = Stat.stat? path
382     st ? st.readable_real? : false
383   end
385   def self.rename(from, to)
386     to = StringValue(to)
387     from = StringValue(from)
389     n = POSIX.rename(from, to)
390     Errno.handle if n == -1
391     n
392   end
394   def self.readlink(path)
395     StringValue(path)
397     buf = " " * 1024
399     n = POSIX.readlink(path, buf, buf.length)
400     Errno.handle if n == -1
402     buf[0, n]
403   end
405   def self.size(path)
406     stat(path).size
407   end
409   def self.size?(path)
410     st = Stat.stat? path
411     st && st.size > 0 ? st.size : nil
412   end
414   def self.socket?(path)
415     st = Stat.stat? path
416     st ? st.socket? : false
417   end
419   def self.split(path)
420     p = StringValue(path)
421     [dirname(p), basename(p)]
422   end
424   def self.stat(path)
425     Stat.new path
426   end
428   def self.symlink(from, to)
429     to = StringValue(to)
430     from = StringValue(from)
432     n = POSIX.symlink(from, to)
433     Errno.handle if n == -1
434     n
435   end
437   def self.symlink?(path)
438     st = Stat.stat? path, false
439     st ? st.symlink? : false
440   end
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)
447       exc.file = name
448       raise exc
449     end
451     out = [:newline, 0, name, [:nil]] unless out
452     out
453   end
455   def self.truncate(path, length)
456     unless self.exist?(path)
457       raise Errno::ENOENT, path
458     end
460     unless length.respond_to?(:to_int)
461       raise TypeError, "can't convert #{length.class} into Integer"
462     end
464     n = POSIX.truncate(path, length)
465     Errno.handle if n == -1
466     n
467   end
469   def self.umask(mask = nil)
470     if mask
471       POSIX.umask(mask)
472     else
473       old_mask = POSIX.umask(0)
474       POSIX.umask(old_mask)
475       old_mask
476     end
477   end
479   def self.unlink(*paths)
480     paths.each do |path|
481       path = StringValue(path)
483       n = POSIX.unlink(path)
484       Errno.handle if n == -1
485     end
487     paths.size
488   end
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
495     atime[:tv_usec] = 0
497     mtime[:tv_sec] = m_in.to_i
498     mtime[:tv_usec] = 0
500     paths.each do |path|
501       if POSIX.utimes(path, ptr) != 0
502         Errno.handle
503       end
504     end
506     ptr.free
507   end
509   def self.writable?(path)
510     st = Stat.stat? path
511     st ? st.writable? : false
512   end
514   def self.writable_real?(path)
515     st = Stat.stat? path
516     st ? st.writable_real? : false
517   end
519   def self.zero?(path)
520     st = Stat.stat? path
521     st ? st.zero? : false
522   end
524   class << self
525     alias_method :delete,   :unlink
526     alias_method :exists?,  :exist?
527     alias_method :fnmatch?, :fnmatch
528   end
530   def atime
531     Stat.new(@path).atime
532   end
534   def chmod(mode)
535     mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
536     POSIX.fchmod(@descriptor, mode)
537   end
539   def chown(owner_int, group_int)
540     POSIX.fchown(@descriptor, owner_int || -1, group_int || -1)
541   end
543   def ctime
544     Stat.new(@path).ctime
545   end
547   def flock(locking_constant)
548     result = POSIX.flock(@descriptor, locking_constant)
549     return false if result == -1
550     result
551   end
553   def lstat
554     Stat.new @path, false
555   end
557   def mtime
558     Stat.new(@path).mtime
559   end
561   def stat
562     Stat.new @path
563   end
565   def truncate(length)
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
573     n
574   end
576   def inspect
577     return_string = "#<#{self.class}:0x#{object_id.to_s(16)} path=#{@path}"
578     return_string << " (closed)" if closed?
579     return_string << ">"
580   end
581 end     # File
583 class File::Stat
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
588   end
590   include Comparable
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
618     @stat = Struct.new
619     if follow_links
620       result = POSIX.stat @path, @stat.pointer
621     else
622       result = POSIX.lstat @path, @stat.pointer
623     end
624     Errno.handle @path unless result == 0
625   end
627   def self.stat?(path, follow_links=true)
628     new path, follow_links
629   rescue Errno::ENOENT, Errno::ENOTDIR
630     nil
631   end
633   def atime
634     Time.at @stat[:st_atime]
635   end
637   def blksize
638     @stat[:st_blksize]
639   end
641   def blocks
642     @stat[:st_blocks]
643   end
645   def blockdev?
646     @stat[:st_mode] & S_IFMT == S_IFBLK
647   end
649   def chardev?
650     @stat[:st_mode] & S_IFMT == S_IFCHR
651   end
653   def ctime
654     Time.at @stat[:st_ctime]
655   end
657   def dev
658     @stat[:st_dev]
659   end
661   def dev_major
662     major = POSIX.major @stat[:st_dev]
663     major < 0 ? nil : major
664   end
666   def dev_minor
667     minor = POSIX.major @stat[:st_dev]
668     minor < 0 ? nil : minor
669   end
671   def directory?
672     @stat[:st_mode] & S_IFMT == S_IFDIR
673   end
675   def executable?
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
680   end
682   def executable_real?
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
687   end
689   def file?
690     @stat[:st_mode] & S_IFMT == S_IFREG
691   end
693   def ftype
694     if file?
695       "file"
696     elsif directory?
697       "directory"
698     elsif chardev?
699       "characterSpecial"
700     elsif blockdev?
701       "blockSpecial"
702     elsif pipe?
703       "fifo"
704     elsif socket?
705       "socket"
706     elsif symlink?
707       "link"
708     else
709       "unknown"
710     end
711   end
713   def gid
714     @stat[:st_gid]
715   end
717   def grpowned?
718     @stat[:st_gid] == POSIX.getegid
719   end
721   def ino
722     @stat[:st_ino]
723   end
725   def inspect
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}>"
731   end
733   def nlink
734     @stat[:st_nlink]
735   end
737   def mtime
738     Time.at @stat[:st_mtime]
739   end
741   def mode
742     @stat[:st_mode]
743   end
745   def owned?
746     @stat[:st_uid] == POSIX.geteuid
747   end
749   def path
750     @path
751   end
753   def pipe?
754     @stat[:st_mode] & S_IFMT == S_IFIFO
755   end
757   def rdev
758     @stat[:st_rdev]
759   end
761   def rdev_major
762     major = POSIX.major @stat[:st_rdev]
763     major < 0 ? nil : major
764   end
766   def rdev_minor
767     minor = POSIX.minor @stat[:st_rdev]
768     minor < 0 ? nil : minor
769   end
771   def readable?
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
776   end
778   def readable_real?
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
783   end
785   def size
786     @stat[:st_size]
787   end
789   def size?
790     size == 0 ? nil : size
791   end
793   def socket?
794     @stat[:st_mode] & S_IFMT == S_IFSOCK
795   end
797   def symlink?
798     @stat[:st_mode] & S_IFMT == S_IFLNK
799   end
801   def uid
802     @stat[:st_uid]
803   end
805   def writable?
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
810   end
812   def writable_real?
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
817   end
819   def zero?
820     @stat[:st_size] == 0
821   end
823   def <=> (other)
824     return nil unless other.is_a?(File::Stat)
825     self.mtime <=> other.mtime
826   end
828   def rgrpowned?
829     @stat[:st_gid] == POSIX.getgid
830   end
831   private :rgrpowned?
833   def rowned?
834     @stat[:st_uid] == POSIX.getuid
835   end
836   private :rowned?
838   def rsuperuser?
839     POSIX.getuid == 0
840   end
841   private :rsuperuser?
843   def superuser?
844     POSIX.geteuid == 0
845   end
846   private :superuser?
847 end     # File::Stat