Re-enable spec/library for full CI runs.
[rbx.git] / kernel / core / file.rb
blob2140e6ebbf8b019da3a54c765d077190343815d2
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   ##
72   # Returns the last access time for the named file as a Time object).
73   #
74   #  File.atime("testfile")   #=> Wed Apr 09 08:51:48 CDT 2003
75   def self.atime(path)
76     Stat.new(path).atime
77   end
79   ##
80   # Returns the last component of the filename given
81   # in file_name, which must be formed using forward
82   # slashes (``/’’) regardless of the separator used
83   # on the local file system. If suffix is given and
84   # present at the end of file_name, it is removed.
85   #
86   #  File.basename("/home/gumby/work/ruby.rb")          #=> "ruby.rb"
87   #  File.basename("/home/gumby/work/ruby.rb", ".rb")   #=> "ruby"
88   def self.basename(path,ext = "")
89     path = StringValue(path)
90     ext = StringValue(ext)
91     Platform::File.basename(path,ext)
92   end
94   ##
95   # Returns true if the named file is a block device.
96   def self.blockdev?(path)
97     st = Stat.stat? path
98     st ? st.blockdev? : false
99   end
101   ##
102   # Returns true if the named file is a character device.
103   def self.chardev?(path)
104     st = Stat.stat? path
105     st ? st.chardev? : false
106   end
108   ##
109   # Changes permission bits on the named file(s) to
110   # the bit pattern represented by mode_int. Actual
111   # effects are operating system dependent (see the
112   # beginning of this section). On Unix systems, see
113   # chmod(2) for details. Returns the number of files processed.
114   #
115   #  File.chmod(0644, "testfile", "out")   #=> 2
116   def self.chmod(mode, *paths)
117     mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
118     paths.each do |path|
119       path = Type.coerce_to(path, String, :to_str) unless path.is_a? String
120       POSIX.chmod(path, mode)
121     end
122     paths.size
123   end
125   ##
126   # Equivalent to File::chmod, but does not follow symbolic
127   # links (so it will change the permissions associated with
128   # the link, not the file referenced by the link).
129   # Often not available.
130   def self.lchmod(mode, *paths)
131     mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
132     paths.each do |path|
133       path = Type.coerce_to(path, String, :to_str) unless path.is_a? String
134       POSIX.lchmod(path, mode)
135     end
136     paths.size
137   end
139   ##
140   # Changes the owner and group of the
141   # named file(s) to the given numeric owner
142   # and group id‘s. Only a process with superuser 
143   # privileges may change the owner of a file. The
144   # current owner of a file may change the file‘s
145   # group to any group to which the owner belongs.
146   # A nil or -1 owner or group id is ignored.
147   # Returns the number of files processed.
148   # 
149   #  File.chown(nil, 100, "testfile")
150   def self.chown(owner_int, group_int, *paths)
151     owner_int = -1 if owner_int == nil
152     group_int = -1 if group_int == nil
153     paths.each { |path| POSIX.chown(path, owner_int, group_int) }
154     paths.size
155   end
157   ##
158   # Equivalent to File::chown, but does not follow
159   # symbolic links (so it will change the owner
160   # associated with the link, not the file referenced
161   # by the link). Often not available. Returns number
162   # of files in the argument list.
163   def self.lchown(owner_int, group_int, *paths)
164     owner_int = -1 if owner_int == nil
165     group_int = -1 if group_int == nil
166     paths.each { |path| POSIX.lchown(path, owner_int, group_int) }
167     paths.size
168   end
170   ##
171   # Returns the change time for the named file (the
172   # time at which directory information about the
173   # file was changed, not the file itself).
174   #
175   #  File.ctime("testfile")   #=> Wed Apr 09 08:53:13 CDT 2003
176   def self.ctime(path)
177     Stat.new(path).ctime
178   end
180   ##
181   # Returns true if the named file is a directory, false otherwise.
182   #
183   # File.directory?(".")
184   def self.directory?(path)
185     st = Stat.stat? path
186     st ? st.directory? : false
187   end
189   ##
190   # Returns all components of the filename given in
191   # file_name except the last one. The filename must be
192   # formed using forward slashes (``/’’) regardless of
193   # the separator used on the local file system.
194   # 
195   #  File.dirname("/home/gumby/work/ruby.rb")   #=> "/home/gumby/work"
196   def self.dirname(path)
197     path = StringValue(path)
198     Platform::File.dirname(path)
199   end
201   ##
202   # Returns true if the named file is executable by the
203   # effective user id of this process.
204   def self.executable?(path)
205     st = Stat.stat? path
206     st ? st.executable? : false
207   end
209   ##
210   # Returns true if the named file is executable by
211   # the real user id of this process.
212   def self.executable_real?(path)
213     st = Stat.stat? path
214     st ? st.executable_real? : false
215   end
217   ##
218   # Return true if the named file exists.
219   def self.exist?(path)
220     Stat.stat?(path) ? true : false
221   rescue SystemCallError
222     false
223   end
225   ##
226   # Converts a pathname to an absolute pathname. Relative
227   # paths are referenced from the current working directory
228   # of the process unless dir_string is given, in which case
229   # it will be used as the starting point. The given pathname
230   # may start with a ``~’’, which expands to the process owner‘s
231   # home directory (the environment variable HOME must be set
232   # correctly). "~user" expands to the named user‘s home directory.
233   # 
234   #  File.expand_path("~oracle/bin")           #=> "/home/oracle/bin"
235   #  File.expand_path("../../bin", "/tmp/x")   #=> "/bin"
236   def self.expand_path(path, dir_string = nil)
237     Platform::File.expand_path(path, dir_string)
238   end
240   ##
241   # Returns the extension (the portion of file name in
242   # path after the period).
243   #
244   #  File.extname("test.rb")         #=> ".rb"
245   #  File.extname("a/b/d/test.rb")   #=> ".rb"
246   #  File.extname("test")            #=> ""
247   #  File.extname(".profile")        #=> ""
248   def self.extname(path)
249     filename = File.basename(StringValue(path))
250     idx = filename.rindex '.'
251     have_dot = idx != nil
252     first_char = idx == 0
253     last_char = idx == filename.length - 1
254     only_dots = filename.match(/[^\.]/).nil?
256     return '' unless have_dot
257     return '' if first_char || last_char
258     return '' if only_dots
259     filename.slice idx..-1
260   end
262   ##
263   # Returns true if the named file exists and is a regular file.
264   def self.file?(path)
265     st = Stat.stat? path
266     st ? st.file? : false
267   end
269   #--
270   # File.fnmatch and helpers. This is a port of JRuby's code
271   #++
273   def self.dirsep?(char)
274     if DOSISH then
275       char == ?\\ || char == ?/
276     else
277       char == ?/
278     end
279   end
281   def self.next_path(str, start, strend)
282     start += 1 while start < strend and !dirsep? str[start]
283     start
284   end
286   def self.range(pattern, pstart, pend, test, flags)
287     ok = false
288     escape = (flags & FNM_NOESCAPE) == 0
289     case_sensitive = (flags & FNM_CASEFOLD) == 0
290     neg = pattern[pstart] == ?! || pattern[pstart] == ?^
292     pstart += 1 if neg
294     while pattern[pstart] != ?] do
295       pstart += 1 if escape && pattern[pstart] == ?\\
296       return -1 if pstart >= pend
297       cstart = cend = pattern[pstart]
298       pstart += 1
300       if pattern[pstart] == ?- && pattern[pstart+1] != ?]
301         pstart += 1
302         pstart += 1 if escape && pattern[pstart] == ?\\
303         return -1 if pstart >= pend
304         cend = pattern[pstart]
305         pstart += 1
306       end
308       if case_sensitive
309         ok = true if cstart <= test && test <= cend
310       else
311         ok = true if cstart.tolower <= test.tolower &&
312           test.tolower <= cend.tolower
313       end
314     end
316     ok == neg ? -1 : pstart + 1
317   end
319   def self.name_match(pattern, str, flags, patstart, patend, strstart, strend)
320     index = strstart
321     pstart = patstart
322     escape   = (flags & FNM_NOESCAPE) == 0
323     pathname = (flags & FNM_PATHNAME) != 0
324     period   = (flags & FNM_DOTMATCH) == 0
325     nocase   = (flags & FNM_CASEFOLD) != 0
327     while pstart < patend do
328       char = pattern[pstart]
329       pstart += 1
330       case char
331       when ??
332         if index >= strend || (pathname && dirsep?(str[index])) ||
333            (period && str[index] == ?. &&
334             (index == 0 || (pathname && dirsep?(str[index-1]))))
335           return false
336         end
338         index += 1
340       when ?*
341         while pstart < patend
342           char = pattern[pstart]
343           pstart += 1
344           break unless char == ?*
345         end
347         if index < strend &&
348            (period && str[index] == ?. &&
349             (index == 0 || (pathname && dirsep?(str[index-1]))))
350           return false
351         end
353         if pstart > patend || (pstart == patend && char == ?*)
354           return !(pathname && next_path(str, index, strend) < strend)
355         elsif pathname && dirsep?(char)
356           index = next_path(str, index, strend)
357           return false unless index < strend
358           index += 1
359         else
360           test = if escape && char == ?\\ && pstart < patend then
361                    pattern[pstart]
362                  else
363                    char
364                  end.tolower
366           pstart -= 1
368           while index < strend do
369             if (char == ?? || char == ?[ || str[index].tolower == test) &&
370                name_match(pattern, str, flags | FNM_DOTMATCH, pstart, patend,
371                           index, strend)
372               return true
373             elsif pathname && dirsep?(str[index])
374               break
375             end
377             index += 1
378           end
380           return false
381         end
383       when ?[
384         if index >= strend ||
385            (pathname && dirsep?(str[index]) ||
386             (period && str[index] == ?. &&
387              (index == 0 ||
388               (pathname && dirsep?(str[index-1])))))
389           return false
390         end
392         pstart = range pattern, pstart, patend, str[index], flags
394         return false if pstart == -1
396         index += 1
397       else
398         if char == ?\\
399           if escape &&
400              (!DOSISH || (pstart < patend && "*?[]\\".index(pattern[pstart])))
401             char = pstart >= patend ? ?\\ : pattern[pstart]
402             pstart += 1
403           end
404         end
406         return false if index >= strend
408         if DOSISH && (pathname && isdirsep?(char) && dirsep?(str[index]))
409           # TODO: invert this boolean expression
410         else
411           if nocase
412             return false if char.tolower != str[index].tolower
413           else
414             return false if char != str[index]
415           end
416         end
418         index += 1
419       end
420     end
422     index >= strend ? true : false
423   end
425   ##
426   # Returns true if path matches against pattern The pattern
427   # is not a regular expression; instead it follows rules
428   # similar to shell filename globbing. It may contain the
429   # following metacharacters:
430   # 
431   # *:  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.
432   # **: Matches directories recursively or files expansively.
433   # ?:  Matches any one character. Equivalent to /.{1}/ in regexp.
434   # [set]:      Matches any one character in set. Behaves exactly like character sets in Regexp, including set negation ([^a-z]).
435   # <code></code>:      Escapes the next metacharacter.
436   # flags is a bitwise OR of the FNM_xxx parameters. The same glob pattern and flags are used by Dir::glob.
437   # 
438   #  File.fnmatch('cat',       'cat')        #=> true  : match entire string
439   #  File.fnmatch('cat',       'category')   #=> false : only match partial string
440   #  File.fnmatch('c{at,ub}s', 'cats')       #=> false : { } isn't supported
441   #
442   #  File.fnmatch('c?t',     'cat')          #=> true  : '?' match only 1 character
443   #  File.fnmatch('c??t',    'cat')          #=> false : ditto
444   #  File.fnmatch('c*',      'cats')         #=> true  : '*' match 0 or more characters
445   #  File.fnmatch('c*t',     'c/a/b/t')      #=> true  : ditto
446   #  File.fnmatch('ca[a-z]', 'cat')          #=> true  : inclusive bracket expression
447   #  File.fnmatch('ca[^t]',  'cat')          #=> false : exclusive bracket expression ('^' or '!')
448   #
449   #  File.fnmatch('cat', 'CAT')                     #=> false : case sensitive
450   #  File.fnmatch('cat', 'CAT', File::FNM_CASEFOLD) #=> true  : case insensitive
451   #
452   #  File.fnmatch('?',   '/', File::FNM_PATHNAME)  #=> false : wildcard doesn't match '/' on FNM_PATHNAME
453   #  File.fnmatch('*',   '/', File::FNM_PATHNAME)  #=> false : ditto
454   #  File.fnmatch('[/]', '/', File::FNM_PATHNAME)  #=> false : ditto
455   #
456   #  File.fnmatch('\?',   '?')                       #=> true  : escaped wildcard becomes ordinary
457   #  File.fnmatch('\a',   'a')                       #=> true  : escaped ordinary remains ordinary
458   #  File.fnmatch('\a',   '\a', File::FNM_NOESCAPE)  #=> true  : FNM_NOESACPE makes '\' ordinary
459   #  File.fnmatch('[\?]', '?')                       #=> true  : can escape inside bracket expression
460   #
461   #  File.fnmatch('*',   '.profile')                      #=> false : wildcard doesn't match leading
462   #  File.fnmatch('*',   '.profile', File::FNM_DOTMATCH)  #=> true    period by default.
463   #  File.fnmatch('.*',  '.profile')                      #=> true
464   #
465   #  rbfiles = '**' '/' '*.rb' # you don't have to do like this. just write in single string.
466   #  File.fnmatch(rbfiles, 'main.rb')                    #=> false
467   #  File.fnmatch(rbfiles, './main.rb')                  #=> false
468   #  File.fnmatch(rbfiles, 'lib/song.rb')                #=> true
469   #  File.fnmatch('**.rb', 'main.rb')                    #=> true
470   #  File.fnmatch('**.rb', './main.rb')                  #=> false
471   #  File.fnmatch('**.rb', 'lib/song.rb')                #=> true
472   #  File.fnmatch('*',           'dave/.profile')                      #=> true
473   #
474   #  pattern = '*' '/' '*'
475   #  File.fnmatch(pattern, 'dave/.profile', File::FNM_PATHNAME)  #=> false
476   #  File.fnmatch(pattern, 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH) #=> true
477   #
478   #  pattern = '**' '/' 'foo'
479   #  File.fnmatch(pattern, 'a/b/c/foo', File::FNM_PATHNAME)     #=> true
480   #  File.fnmatch(pattern, '/a/b/c/foo', File::FNM_PATHNAME)    #=> true
481   #  File.fnmatch(pattern, 'c:/a/b/c/foo', File::FNM_PATHNAME)  #=> true
482   #  File.fnmatch(pattern, 'a/.b/c/foo', File::FNM_PATHNAME)    #=> false
483   #  File.fnmatch(pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH) #=> true
484   def self.fnmatch(pattern, path, flags=0)
485     pattern = StringValue(pattern).dup
486     path    = StringValue(path).dup
487     flags   = Type.coerce_to(flags, Fixnum, :to_int) unless Fixnum === flags
489     name_match(pattern, path, flags, 0, pattern.size, 0, path.size)
490   end
492   ##
493   # Identifies the type of the named file; the return string is 
494   # one of "file", "directory", "characterSpecial",
495   # "blockSpecial", "fifo", "link", "socket", or "unknown".
496   #
497   #  File.ftype("testfile")            #=> "file"
498   #  File.ftype("/dev/tty")            #=> "characterSpecial"
499   #  File.ftype("/tmp/.X11-unix/X0")   #=> "socket"
500   def self.ftype(path)
501     lstat(path).ftype
502   end
504   ##
505   # Returns true if the named file exists and the effective 
506   # group id of the calling process is the owner of the file.
507   # Returns false on Windows.
508   def self.grpowned?(path)
509     begin
510       lstat(path).grpowned?
511     rescue
512       false
513     end
514   end
516   ##
517   # Returns true if the named files are identical.
518   #
519   #   open("a", "w") {}
520   #   p File.identical?("a", "a")      #=> true
521   #   p File.identical?("a", "./a")    #=> true
522   #   File.link("a", "b")
523   #   p File.identical?("a", "b")      #=> true
524   #   File.symlink("a", "c")
525   #   p File.identical?("a", "c")      #=> true
526   #   open("d", "w") {}
527   #   p File.identical?("a", "d")      #=> false
528   def self.identical?(orig, copy)
529     st_o = stat(StringValue(orig))
530     st_c = stat(StringValue(copy))
532     return false unless st_o.ino == st_c.ino
533     return false unless st_o.ftype == st_c.ftype
534     return false unless POSIX.access(orig, Constants::R_OK)
535     return false unless POSIX.access(copy, Constants::R_OK)
537     true
538   end
540   ##
541   # Returns a new string formed by joining the strings using File::SEPARATOR.
542   # 
543   #  File.join("usr", "mail", "gumby")   #=> "usr/mail/gumby"
544   def self.join(*args)
545     args.map! { |o|
546       o = o.to_str unless Array === o || String === o
547       o
548     } rescue raise TypeError
550     # let join/split deal with all the recursive array complexities
551     # one small hack is to replace URI header with \0 and swap back later
552     result = args.join(SEPARATOR).gsub(/\:\//, "\0").split(/#{SEPARATOR}+/o)
553     result << '' if args.empty? || args.last.empty? || args.last[-1] == SEPARATOR[0]
554     result.join(SEPARATOR).gsub(/\0/, ':/')
555   end
557   ##
558   # Creates a new name for an existing file using a hard link.
559   # Will not overwrite new_name if it already exists (raising
560   # a subclass of SystemCallError). Not available on all platforms.
561   #
562   #  File.link("testfile", ".testfile")   #=> 0
563   #  IO.readlines(".testfile")[0]         #=> "This is line one\n"
564   def self.link(from, to)
565     to = StringValue(to)
566     from = StringValue(from)
568     n = POSIX.link(from, to)
569     Errno.handle if n == -1
570     n
571   end
573   ##
574   # Same as File::stat, but does not follow the last symbolic link.
575   # Instead, reports on the link itself.
576   #
577   #  File.symlink("testfile", "link2test")   #=> 0
578   #  File.stat("testfile").size              #=> 66
579   #  File.lstat("link2test").size            #=> 8
580   #  File.stat("link2test").size             #=> 66
581   def self.lstat(path)
582     Stat.new path, false
583   end
585   ##
586   # Returns the modification time for the named file as a Time object.
587   #
588   #  File.mtime("testfile")   #=> Tue Apr 08 12:58:04 CDT 2003
589   def self.mtime(path)
590     Stat.new(path).mtime
591   end
593   ##
594   # Returns true if the named file is a pipe.
595   def self.pipe?(path)
596     st = Stat.stat? path
597     st ? st.pipe? : false
598   end
600   ## 
601   # Returns true if the named file is readable by the effective
602   # user id of this process.
603   def self.readable?(path)
604     st = Stat.stat? path
605     st ? st.readable? : false
606   end
608   ##
609   # Returns true if the named file is readable by the real user
610   # id of this process.
611   def self.readable_real?(path)
612     st = Stat.stat? path
613     st ? st.readable_real? : false
614   end
616   ##
617   # Returns the name of the file referenced by the given link.
618   # Not available on all platforms.
619   #
620   #  File.symlink("testfile", "link2test")   #=> 0
621   #  File.readlink("link2test")              #=> "testfile"
622   def self.readlink(path)
623     StringValue(path)
625     buf = " " * 1024
627     n = POSIX.readlink(path, buf, buf.length)
628     Errno.handle if n == -1
630     buf[0, n]
631   end
633   ##
634   # Renames the given file to the new name. Raises a SystemCallError
635   # if the file cannot be renamed.
636   #
637   #  File.rename("afile", "afile.bak")   #=> 0
638   def self.rename(from, to)
639     to = StringValue(to)
640     from = StringValue(from)
642     n = POSIX.rename(from, to)
643     Errno.handle if n == -1
644     n
645   end
646   
647   ##
648   # Returns the size of file_name.
649   def self.size(path)
650     stat(path).size
651   end
653   ##
654   # Returns nil if file_name doesn‘t exist or has zero size,
655   # the size of the file otherwise.
656   def self.size?(path)
657     st = Stat.stat? path
658     st && st.size > 0 ? st.size : nil
659   end
661   ##
662   # Returns true if the named file is a socket.
663   def self.socket?(path)
664     st = Stat.stat? path
665     st ? st.socket? : false
666   end
668   ##
669   # 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.
670   # 
671   #  File.split("/home/gumby/.profile")   #=> ["/home/gumby", ".profile"]
672   def self.split(path)
673     p = StringValue(path)
674     [dirname(p), basename(p)]
675   end
677   ##
678   # Returns a File::Stat object for the named file (see File::Stat).
679   # 
680   #  File.stat("testfile").mtime   #=> Tue Apr 08 12:58:04 CDT 2003
681   def self.stat(path)
682     Stat.new path
683   end
685   ##
686   # Creates a symbolic link called new_name for the
687   # existing file old_name. Raises a NotImplemented
688   # exception on platforms that do not support symbolic links.
689   # 
690   #  File.symlink("testfile", "link2test")   #=> 0
691   def self.symlink(from, to)
692     to = StringValue(to)
693     from = StringValue(from)
695     n = POSIX.symlink(from, to)
696     Errno.handle if n == -1
697     n
698   end
700   ##
701   # Returns true if the named file is a symbolic link.
702   def self.symlink?(path)
703     st = Stat.stat? path, false
704     st ? st.symlink? : false
705   end
706   
707   ##
708   # Copies a file from to to. If to is a directory, copies from to to/from.
709   def self.syscopy(from, to)
710     out = File.directory?(to) ? to + File.basename(from) : to
711     
712     open(out, 'w') do |f|
713       f.write read(from).read
714     end
715   end
717   ##
718   # Return the equivalent S-Expression of the file given.
719   # Raises +SyntaxError+ if there is a syntax issue in the
720   # file, making it unparsable.
721   #  File.to_sexp("/tmp/test.rb") #=> [...]
722   def self.to_sexp(name, newlines=true)
723     out = to_sexp_full(name, newlines)
724     if out.kind_of? Tuple
725       exc = SyntaxError.new out.at(0)
726       exc.import_position out.at(1), out.at(2), out.at(3)
727       exc.file = name
728       raise exc
729     end
731     out = [:newline, 0, name, [:nil]] unless out
732     out
733   end
735   ##
736   # Truncates the file file_name to be at most integer
737   # bytes long. Not available on all platforms.
738   #
739   #  f = File.new("out", "w")
740   #  f.write("1234567890")     #=> 10
741   #  f.close                   #=> nil
742   #  File.truncate("out", 5)   #=> 0
743   #  File.size("out")          #=> 5
744   def self.truncate(path, length)
745     unless self.exist?(path)
746       raise Errno::ENOENT, path
747     end
749     unless length.respond_to?(:to_int)
750       raise TypeError, "can't convert #{length.class} into Integer"
751     end
753     n = POSIX.truncate(path, length)
754     Errno.handle if n == -1
755     n
756   end
758   ##
759   # Returns the current umask value for this process.
760   # If the optional argument is given, set the umask
761   # to that value and return the previous value. Umask
762   # values are subtracted from the default permissions,
763   # so a umask of 0222 would make a file read-only for
764   # everyone.
765   # 
766   #  File.umask(0006)   #=> 18
767   #  File.umask         #=> 6
768   def self.umask(mask = nil)
769     if mask
770       POSIX.umask(mask)
771     else
772       old_mask = POSIX.umask(0)
773       POSIX.umask(old_mask)
774       old_mask
775     end
776   end
778   ##
779   # Deletes the named files, returning the number of names
780   # passed as arguments. Raises an exception on any error.
781   # 
782   # See also Dir::rmdir.
783   def self.unlink(*paths)
784     paths.each do |path|
785       path = StringValue(path)
787       n = POSIX.unlink(path)
788       Errno.handle if n == -1
789     end
791     paths.size
792   end
794   ##
795   # Sets the access and modification times of each named
796   # file to the first two arguments. Returns the number
797   # of file names in the argument list.
798   #  #=> Integer
799   def self.utime(a_in, m_in, *paths)
800     ptr = MemoryPointer.new(POSIX::TimeVal, 2)
801     atime = POSIX::TimeVal.new ptr
802     mtime = POSIX::TimeVal.new ptr[1]
803     atime[:tv_sec] = a_in.to_i
804     atime[:tv_usec] = 0
806     mtime[:tv_sec] = m_in.to_i
807     mtime[:tv_usec] = 0
809     paths.each do |path|
810       if POSIX.utimes(path, ptr) != 0
811         Errno.handle
812       end
813     end
815     ptr.free
816   end
818   ##
819   # Returns true if the named file is writable by the effective 
820   # user id of this process.
821   def self.writable?(path)
822     st = Stat.stat? path
823     st ? st.writable? : false
824   end
826   ##
827   # Returns true if the named file is writable by the real user
828   # id of this process.
829   def self.writable_real?(path)
830     st = Stat.stat? path
831     st ? st.writable_real? : false
832   end
834   ##
835   # Returns true if the named file exists and has a zero size.
836   def self.zero?(path)
837     st = Stat.stat? path
838     st ? st.zero? : false
839   end
840   
841   ##
842   # Returns true if the named file exists and the effective
843   # used id of the calling process is the owner of the file.
844   #  File.owned?(file_name)   => true or false
845   def self.owned?(file_name)
846     Stat.new(file_name).owned?
847   end
848   
849   ##
850   # Returns true if the named file has the setgid bit set.
851   def self.setgid?(file_name)
852     Stat.new(file_name).setgid?
853   end
854   
855   ##
856   # Returns true if the named file has the setuid bit set.
857   def self.setuid?(file_name)
858     Stat.new(file_name).setuid?
859   end
860   
861   ##
862   # Returns true if the named file has the sticky bit set.
863   def self.sticky?(file_name)
864     Stat.new(file_name).sticky?
865   end
866   
867   ##
868   # Returns true if the named file exists and the effective
869   # used id of the calling process is the owner of the file.
870   def self.owned?(file_name)
871     Stat.new(file_name).owned?
872   end
874   class << self
875     alias_method :delete,   :unlink
876     alias_method :exists?,  :exist?
877     alias_method :fnmatch?, :fnmatch
878   end
880   def atime
881     Stat.new(@path).atime
882   end
884   def chmod(mode)
885     mode = Type.coerce_to(mode, Integer, :to_int) unless mode.is_a? Integer
886     POSIX.fchmod(@descriptor, mode)
887   end
889   def chown(owner_int, group_int)
890     POSIX.fchown(@descriptor, owner_int || -1, group_int || -1)
891   end
893   def ctime
894     Stat.new(@path).ctime
895   end
897   def flock(locking_constant)
898     result = POSIX.flock(@descriptor, locking_constant)
899     return false if result == -1
900     result
901   end
903   def lstat
904     Stat.new @path, false
905   end
907   def mtime
908     Stat.new(@path).mtime
909   end
911   def stat
912     Stat.new @path
913   end
915   def truncate(length)
916     length = Type.coerce_to(length, Integer, :to_int)
918     raise Errno::EINVAL, "Can't truncate a file to a negative length" if length < 0
919     raise IOError, "File is closed" if closed?
921     n = POSIX.ftruncate(@descriptor, length)
922     Errno.handle if n == -1
923     n
924   end
926   def inspect
927     return_string = "#<#{self.class}:0x#{object_id.to_s(16)} path=#{@path}"
928     return_string << " (closed)" if closed?
929     return_string << ">"
930   end
931 end     # File
933 class File::Stat
934   class Struct < FFI::Struct
935     config "rbx.platform.stat", :st_dev, :st_ino, :st_mode, :st_nlink,
936            :st_uid, :st_gid, :st_rdev, :st_size, :st_blksize, :st_blocks,
937            :st_atime, :st_mtime, :st_ctime
938   end
940   include Comparable
942   S_IRUSR  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IRUSR']
943   S_IWUSR  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IWUSR']
944   S_IXUSR  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IXUSR']
945   S_IRGRP  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IRGRP']
946   S_IWGRP  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IWGRP']
947   S_IXGRP  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IXGRP']
948   S_IROTH  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IROTH']
949   S_IWOTH  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IWOTH']
950   S_IXOTH  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IXOTH']
952   S_IFMT   = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFMT']
953   S_IFIFO  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFIFO']
954   S_IFCHR  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFCHR']
955   S_IFDIR  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFDIR']
956   S_IFBLK  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFBLK']
957   S_IFREG  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFREG']
958   S_IFLNK  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFLNK']
959   S_IFSOCK = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFSOCK']
960   S_IFWHT  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_IFWHT']
961   S_ISUID  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_ISUID']
962   S_ISGID  = Rubinius::RUBY_CONFIG['rbx.platform.file.S_ISGID']
964   POSIX    = Platform::POSIX
966   ##
967   # File::Stat#from_fd is used to support IO#stat which does not necessarily
968   # have a path.
970   def self.from_fd(descriptor)
971     stat = allocate
972     stat_struct = Struct.new
974     stat.instance_variable_set :@stat, stat_struct
975     stat.instance_variable_set :@path, nil
977     result = POSIX.fstat descriptor, stat_struct.pointer
979     Errno.handle "file descriptor #{descriptor}" unless result == 0
981     stat
982   end
984   def initialize(path, follow_links=true)
985     @stat = Struct.new
986     @path = StringValue path
988     result = if follow_links
989                POSIX.stat @path, @stat.pointer
990              else
991                POSIX.lstat @path, @stat.pointer
992              end
994     Errno.handle @path unless result == 0
995   end
997   def self.stat?(path, follow_links=true)
998     new path, follow_links
999   rescue Errno::ENOENT, Errno::ENOTDIR
1000     nil
1001   end
1003   def atime
1004     Time.at @stat[:st_atime]
1005   end
1007   def blksize
1008     @stat[:st_blksize]
1009   end
1011   def blocks
1012     @stat[:st_blocks]
1013   end
1015   def blockdev?
1016     @stat[:st_mode] & S_IFMT == S_IFBLK
1017   end
1019   def chardev?
1020     @stat[:st_mode] & S_IFMT == S_IFCHR
1021   end
1023   def ctime
1024     Time.at @stat[:st_ctime]
1025   end
1027   def dev
1028     @stat[:st_dev]
1029   end
1031   def dev_major
1032     major = POSIX.major @stat[:st_dev]
1033     major < 0 ? nil : major
1034   end
1036   def dev_minor
1037     minor = POSIX.major @stat[:st_dev]
1038     minor < 0 ? nil : minor
1039   end
1041   def directory?
1042     @stat[:st_mode] & S_IFMT == S_IFDIR
1043   end
1045   def executable?
1046     return true if superuser?
1047     return @stat[:st_mode] & S_IXUSR != 0 if owned?
1048     return @stat[:st_mode] & S_IXGRP != 0 if grpowned?
1049     return @stat[:st_mode] & S_IXOTH != 0
1050   end
1052   def executable_real?
1053     return true if rsuperuser?
1054     return @stat[:st_mode] & S_IXUSR != 0 if rowned?
1055     return @stat[:st_mode] & S_IXGRP != 0 if rgrpowned?
1056     return @stat[:st_mode] & S_IXOTH != 0
1057   end
1059   def file?
1060     @stat[:st_mode] & S_IFMT == S_IFREG
1061   end
1063   def ftype
1064     if file?
1065       "file"
1066     elsif directory?
1067       "directory"
1068     elsif chardev?
1069       "characterSpecial"
1070     elsif blockdev?
1071       "blockSpecial"
1072     elsif pipe?
1073       "fifo"
1074     elsif socket?
1075       "socket"
1076     elsif symlink?
1077       "link"
1078     else
1079       "unknown"
1080     end
1081   end
1083   def gid
1084     @stat[:st_gid]
1085   end
1087   def grpowned?
1088     @stat[:st_gid] == POSIX.getegid
1089   end
1091   def ino
1092     @stat[:st_ino]
1093   end
1095   def inspect
1096     "#<File::Stat dev=0x#{self.dev.to_s(16)}, ino=#{self.ino}, " \
1097     "mode=#{sprintf("%07d", self.mode.to_s(8).to_i)}, nlink=#{self.nlink}, " \
1098     "uid=#{self.uid}, gid=#{self.gid}, rdev=0x#{self.rdev.to_s(16)}, " \
1099     "size=#{self.size}, blksize=#{self.blksize}, blocks=#{self.blocks}, " \
1100     "atime=#{self.atime}, mtime=#{self.mtime}, ctime=#{self.ctime}>"
1101   end
1103   def nlink
1104     @stat[:st_nlink]
1105   end
1107   def mtime
1108     Time.at @stat[:st_mtime]
1109   end
1111   def mode
1112     @stat[:st_mode]
1113   end
1115   def owned?
1116     @stat[:st_uid] == POSIX.geteuid
1117   end
1119   def path
1120     @path
1121   end
1123   def pipe?
1124     @stat[:st_mode] & S_IFMT == S_IFIFO
1125   end
1127   def rdev
1128     @stat[:st_rdev]
1129   end
1131   def rdev_major
1132     major = POSIX.major @stat[:st_rdev]
1133     major < 0 ? nil : major
1134   end
1136   def rdev_minor
1137     minor = POSIX.minor @stat[:st_rdev]
1138     minor < 0 ? nil : minor
1139   end
1141   def readable?
1142     return true if superuser?
1143     return @stat[:st_mode] & S_IRUSR != 0 if owned?
1144     return @stat[:st_mode] & S_IRGRP != 0 if grpowned?
1145     return @stat[:st_mode] & S_IROTH != 0
1146   end
1148   def readable_real?
1149     return true if rsuperuser?
1150     return @stat[:st_mode] & S_IRUSR != 0 if rowned?
1151     return @stat[:st_mode] & S_IRGRP != 0 if rgrpowned?
1152     return @stat[:st_mode] & S_IROTH != 0
1153   end
1154   
1155   def setgid?
1156     !!@stat[:st_gid]
1157   end
1158   
1159   def setuid?
1160     !!@stat[:st_uid]
1161   end
1163   def size
1164     @stat[:st_size]
1165   end
1167   def size?
1168     size == 0 ? nil : size
1169   end
1171   def socket?
1172     @stat[:st_mode] & S_IFMT == S_IFSOCK
1173   end
1175   def symlink?
1176     @stat[:st_mode] & S_IFMT == S_IFLNK
1177   end
1179   def uid
1180     @stat[:st_uid]
1181   end
1183   def writable?
1184     return true if superuser?
1185     return @stat[:st_mode] & S_IWUSR != 0 if owned?
1186     return @stat[:st_mode] & S_IWGRP != 0 if grpowned?
1187     return @stat[:st_mode] & S_IWOTH != 0
1188   end
1190   def writable_real?
1191     return true if rsuperuser?
1192     return @stat[:st_mode] & S_IWUSR != 0 if rowned?
1193     return @stat[:st_mode] & S_IWGRP != 0 if rgrpowned?
1194     return @stat[:st_mode] & S_IWOTH != 0
1195   end
1197   def zero?
1198     @stat[:st_size] == 0
1199   end
1201   def <=> (other)
1202     return nil unless other.is_a?(File::Stat)
1203     self.mtime <=> other.mtime
1204   end
1206   def rgrpowned?
1207     @stat[:st_gid] == POSIX.getgid
1208   end
1209   private :rgrpowned?
1211   def rowned?
1212     @stat[:st_uid] == POSIX.getuid
1213   end
1214   private :rowned?
1216   def rsuperuser?
1217     POSIX.getuid == 0
1218   end
1219   private :rsuperuser?
1221   def superuser?
1222     POSIX.geteuid == 0
1223   end
1224   private :superuser?
1225 end     # File::Stat