* io.c (rb_open_file): encoding in mode string was ignored if perm is
[ruby-svn.git] / lib / fileutils.rb
blob6152c3d306df41f878fae9c82aa141353f752167
1
2 # = fileutils.rb
3
4 # Copyright (c) 2000-2007 Minero Aoki
5
6 # This program is free software.
7 # You can distribute/modify this program under the same terms of ruby.
8
9 # == module FileUtils
10
11 # Namespace for several file utility methods for copying, moving, removing, etc.
12
13 # === Module Functions
14
15 #   cd(dir, options)
16 #   cd(dir, options) {|dir| .... }
17 #   pwd()
18 #   mkdir(dir, options)
19 #   mkdir(list, options)
20 #   mkdir_p(dir, options)
21 #   mkdir_p(list, options)
22 #   rmdir(dir, options)
23 #   rmdir(list, options)
24 #   ln(old, new, options)
25 #   ln(list, destdir, options)
26 #   ln_s(old, new, options)
27 #   ln_s(list, destdir, options)
28 #   ln_sf(src, dest, options)
29 #   cp(src, dest, options)
30 #   cp(list, dir, options)
31 #   cp_r(src, dest, options)
32 #   cp_r(list, dir, options)
33 #   mv(src, dest, options)
34 #   mv(list, dir, options)
35 #   rm(list, options)
36 #   rm_r(list, options)
37 #   rm_rf(list, options)
38 #   install(src, dest, mode = <src's>, options)
39 #   chmod(mode, list, options)
40 #   chmod_R(mode, list, options)
41 #   chown(user, group, list, options)
42 #   chown_R(user, group, list, options)
43 #   touch(list, options)
45 # The <tt>options</tt> parameter is a hash of options, taken from the list
46 # <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
47 # <tt>:noop</tt> means that no changes are made.  The other two are obvious.
48 # Each method documents the options that it honours.
50 # All methods that have the concept of a "source" file or directory can take
51 # either one file or a list of files in that argument.  See the method
52 # documentation for examples.
54 # There are some `low level' methods, which do not accept any option:
56 #   copy_entry(src, dest, preserve = false, dereference = false)
57 #   copy_file(src, dest, preserve = false, dereference = true)
58 #   copy_stream(srcstream, deststream)
59 #   remove_entry(path, force = false)
60 #   remove_entry_secure(path, force = false)
61 #   remove_file(path, force = false)
62 #   compare_file(path_a, path_b)
63 #   compare_stream(stream_a, stream_b)
64 #   uptodate?(file, cmp_list)
66 # == module FileUtils::Verbose
67
68 # This module has all methods of FileUtils module, but it outputs messages
69 # before acting.  This equates to passing the <tt>:verbose</tt> flag to methods
70 # in FileUtils.
71
72 # == module FileUtils::NoWrite
73
74 # This module has all methods of FileUtils module, but never changes
75 # files/directories.  This equates to passing the <tt>:noop</tt> flag to methods
76 # in FileUtils.
77
78 # == module FileUtils::DryRun
79
80 # This module has all methods of FileUtils module, but never changes
81 # files/directories.  This equates to passing the <tt>:noop</tt> and
82 # <tt>:verbose</tt> flags to methods in FileUtils.
83
85 module FileUtils
87   def self.private_module_function(name)   #:nodoc:
88     module_function name
89     private_class_method name
90   end
92   # This hash table holds command options.
93   OPT_TABLE = {}   #:nodoc: internal use only
95   #
96   # Options: (none)
97   #
98   # Returns the name of the current directory.
99   #
100   def pwd
101     Dir.pwd
102   end
103   module_function :pwd
105   alias getwd pwd
106   module_function :getwd
108   #
109   # Options: verbose
110   # 
111   # Changes the current directory to the directory +dir+.
112   # 
113   # If this method is called with block, resumes to the old
114   # working directory after the block execution finished.
115   # 
116   #   FileUtils.cd('/', :verbose => true)   # chdir and report it
117   # 
118   def cd(dir, options = {}, &block) # :yield: dir
119     fu_check_options options, OPT_TABLE['cd']
120     fu_output_message "cd #{dir}" if options[:verbose]
121     Dir.chdir(dir, &block)
122     fu_output_message 'cd -' if options[:verbose] and block
123   end
124   module_function :cd
126   alias chdir cd
127   module_function :chdir
129   OPT_TABLE['cd']    =
130   OPT_TABLE['chdir'] = [:verbose]
132   #
133   # Options: (none)
134   # 
135   # Returns true if +newer+ is newer than all +old_list+.
136   # Non-existent files are older than any file.
137   # 
138   #   FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
139   #       system 'make hello.o'
140   # 
141   def uptodate?(new, old_list, options = nil)
142     raise ArgumentError, 'uptodate? does not accept any option' if options
144     return false unless File.exist?(new)
145     new_time = File.mtime(new)
146     old_list.each do |old|
147       if File.exist?(old)
148         return false unless new_time > File.mtime(old)
149       end
150     end
151     true
152   end
153   module_function :uptodate?
155   #
156   # Options: mode noop verbose
157   # 
158   # Creates one or more directories.
159   # 
160   #   FileUtils.mkdir 'test'
161   #   FileUtils.mkdir %w( tmp data )
162   #   FileUtils.mkdir 'notexist', :noop => true  # Does not really create.
163   #   FileUtils.mkdir 'tmp', :mode => 0700
164   # 
165   def mkdir(list, options = {})
166     fu_check_options options, OPT_TABLE['mkdir']
167     list = fu_list(list)
168     fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
169     return if options[:noop]
171     list.each do |dir|
172       fu_mkdir dir, options[:mode]
173     end
174   end
175   module_function :mkdir
177   OPT_TABLE['mkdir'] = [:mode, :noop, :verbose]
179   #
180   # Options: mode noop verbose
181   # 
182   # Creates a directory and all its parent directories.
183   # For example,
184   # 
185   #   FileUtils.mkdir_p '/usr/local/lib/ruby'
186   # 
187   # causes to make following directories, if it does not exist.
188   #     * /usr
189   #     * /usr/local
190   #     * /usr/local/lib
191   #     * /usr/local/lib/ruby
192   #
193   # You can pass several directories at a time in a list.
194   # 
195   def mkdir_p(list, options = {})
196     fu_check_options options, OPT_TABLE['mkdir_p']
197     list = fu_list(list)
198     fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
199     return *list if options[:noop]
201     list.map {|path| path.sub(%r</\z>, '') }.each do |path|
202       # optimize for the most common case
203       begin
204         fu_mkdir path, options[:mode]
205         next
206       rescue SystemCallError
207         next if File.directory?(path)
208       end
210       stack = []
211       until path == stack.last   # dirname("/")=="/", dirname("C:/")=="C:/"
212         stack.push path
213         path = File.dirname(path)
214       end
215       stack.reverse_each do |dir|
216         begin
217           fu_mkdir dir, options[:mode]
218         rescue SystemCallError => err
219           raise unless File.directory?(dir)
220         end
221       end
222     end
224     return *list
225   end
226   module_function :mkdir_p
228   alias mkpath    mkdir_p
229   alias makedirs  mkdir_p
230   module_function :mkpath
231   module_function :makedirs
233   OPT_TABLE['mkdir_p']  =
234   OPT_TABLE['mkpath']   =
235   OPT_TABLE['makedirs'] = [:mode, :noop, :verbose]
237   def fu_mkdir(path, mode)   #:nodoc:
238     path = path.sub(%r</\z>, '')
239     if mode
240       Dir.mkdir path, mode
241       File.chmod mode, path
242     else
243       Dir.mkdir path
244     end
245   end
246   private_module_function :fu_mkdir
248   #
249   # Options: noop, verbose
250   # 
251   # Removes one or more directories.
252   # 
253   #   FileUtils.rmdir 'somedir'
254   #   FileUtils.rmdir %w(somedir anydir otherdir)
255   #   # Does not really remove directory; outputs message.
256   #   FileUtils.rmdir 'somedir', :verbose => true, :noop => true
257   # 
258   def rmdir(list, options = {})
259     fu_check_options options, OPT_TABLE['rmdir']
260     list = fu_list(list)
261     fu_output_message "rmdir #{list.join ' '}" if options[:verbose]
262     return if options[:noop]
263     list.each do |dir|
264       Dir.rmdir dir.sub(%r</\z>, '')
265     end
266   end
267   module_function :rmdir
269   OPT_TABLE['rmdir'] = [:noop, :verbose]
271   #
272   # Options: force noop verbose
273   #
274   # <b><tt>ln(old, new, options = {})</tt></b>
275   #
276   # Creates a hard link +new+ which points to +old+.
277   # If +new+ already exists and it is a directory, creates a link +new/old+.
278   # If +new+ already exists and it is not a directory, raises Errno::EEXIST.
279   # But if :force option is set, overwrite +new+.
280   # 
281   #   FileUtils.ln 'gcc', 'cc', :verbose => true
282   #   FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
283   # 
284   # <b><tt>ln(list, destdir, options = {})</tt></b>
285   # 
286   # Creates several hard links in a directory, with each one pointing to the
287   # item in +list+.  If +destdir+ is not a directory, raises Errno::ENOTDIR.
288   # 
289   #   include FileUtils
290   #   cd '/sbin'
291   #   FileUtils.ln %w(cp mv mkdir), '/bin'   # Now /sbin/cp and /bin/cp are linked.
292   # 
293   def ln(src, dest, options = {})
294     fu_check_options options, OPT_TABLE['ln']
295     fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
296     return if options[:noop]
297     fu_each_src_dest0(src, dest) do |s,d|
298       remove_file d, true if options[:force]
299       File.link s, d
300     end
301   end
302   module_function :ln
304   alias link ln
305   module_function :link
307   OPT_TABLE['ln']   =
308   OPT_TABLE['link'] = [:force, :noop, :verbose]
310   #
311   # Options: force noop verbose
312   #
313   # <b><tt>ln_s(old, new, options = {})</tt></b>
314   # 
315   # Creates a symbolic link +new+ which points to +old+.  If +new+ already
316   # exists and it is a directory, creates a symbolic link +new/old+.  If +new+
317   # already exists and it is not a directory, raises Errno::EEXIST.  But if
318   # :force option is set, overwrite +new+.
319   # 
320   #   FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
321   #   FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true
322   # 
323   # <b><tt>ln_s(list, destdir, options = {})</tt></b>
324   # 
325   # Creates several symbolic links in a directory, with each one pointing to the
326   # item in +list+.  If +destdir+ is not a directory, raises Errno::ENOTDIR.
327   #
328   # If +destdir+ is not a directory, raises Errno::ENOTDIR.
329   # 
330   #   FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
331   # 
332   def ln_s(src, dest, options = {})
333     fu_check_options options, OPT_TABLE['ln_s']
334     fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
335     return if options[:noop]
336     fu_each_src_dest0(src, dest) do |s,d|
337       remove_file d, true if options[:force]
338       File.symlink s, d
339     end
340   end
341   module_function :ln_s
343   alias symlink ln_s
344   module_function :symlink
346   OPT_TABLE['ln_s']    =
347   OPT_TABLE['symlink'] = [:force, :noop, :verbose]
349   #
350   # Options: noop verbose
351   # 
352   # Same as
353   #   #ln_s(src, dest, :force)
354   # 
355   def ln_sf(src, dest, options = {})
356     fu_check_options options, OPT_TABLE['ln_sf']
357     options = options.dup
358     options[:force] = true
359     ln_s src, dest, options
360   end
361   module_function :ln_sf
363   OPT_TABLE['ln_sf'] = [:noop, :verbose]
365   #
366   # Options: preserve noop verbose
367   #
368   # Copies a file content +src+ to +dest+.  If +dest+ is a directory,
369   # copies +src+ to +dest/src+.
370   #
371   # If +src+ is a list of files, then +dest+ must be a directory.
372   #
373   #   FileUtils.cp 'eval.c', 'eval.c.org'
374   #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
375   #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
376   #   FileUtils.cp 'symlink', 'dest'   # copy content, "dest" is not a symlink
377   # 
378   def cp(src, dest, options = {})
379     fu_check_options options, OPT_TABLE['cp']
380     fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
381     return if options[:noop]
382     fu_each_src_dest(src, dest) do |s, d|
383       copy_file s, d, options[:preserve]
384     end
385   end
386   module_function :cp
388   alias copy cp
389   module_function :copy
391   OPT_TABLE['cp']   =
392   OPT_TABLE['copy'] = [:preserve, :noop, :verbose]
394   #
395   # Options: preserve noop verbose dereference_root remove_destination
396   # 
397   # Copies +src+ to +dest+. If +src+ is a directory, this method copies
398   # all its contents recursively. If +dest+ is a directory, copies
399   # +src+ to +dest/src+.
400   #
401   # +src+ can be a list of files.
402   # 
403   #   # Installing ruby library "mylib" under the site_ruby
404   #   FileUtils.rm_r site_ruby + '/mylib', :force
405   #   FileUtils.cp_r 'lib/', site_ruby + '/mylib'
406   # 
407   #   # Examples of copying several files to target directory.
408   #   FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
409   #   FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true
410   #
411   #   # If you want to copy all contents of a directory instead of the
412   #   # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
413   #   # use following code.
414   #   FileUtils.cp_r 'src/.', 'dest'     # cp_r('src', 'dest') makes src/dest,
415   #                                      # but this doesn't.
416   # 
417   def cp_r(src, dest, options = {})
418     fu_check_options options, OPT_TABLE['cp_r']
419     fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
420     return if options[:noop]
421     fu_each_src_dest(src, dest) do |s, d|
422       copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
423     end
424   end
425   module_function :cp_r
427   OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose,
428                        :dereference_root, :remove_destination]
430   #
431   # Copies a file system entry +src+ to +dest+.
432   # If +src+ is a directory, this method copies its contents recursively.
433   # This method preserves file types, c.f. symlink, directory...
434   # (FIFO, device files and etc. are not supported yet)
435   #
436   # Both of +src+ and +dest+ must be a path name.
437   # +src+ must exist, +dest+ must not exist.
438   #
439   # If +preserve+ is true, this method preserves owner, group, permissions
440   # and modified time.
441   #
442   # If +dereference_root+ is true, this method dereference tree root.
443   #
444   # If +remove_destination+ is true, this method removes each destination file before copy.
445   #
446   def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
447     Entry_.new(src, nil, dereference_root).traverse do |ent|
448       destent = Entry_.new(dest, ent.rel, false)
449       File.unlink destent.path if remove_destination && File.file?(destent.path)
450       ent.copy destent.path
451       ent.copy_metadata destent.path if preserve
452     end
453   end
454   module_function :copy_entry
456   #
457   # Copies file contents of +src+ to +dest+.
458   # Both of +src+ and +dest+ must be a path name.
459   #
460   def copy_file(src, dest, preserve = false, dereference = true)
461     ent = Entry_.new(src, nil, dereference)
462     ent.copy_file dest
463     ent.copy_metadata dest if preserve
464   end
465   module_function :copy_file
467   #
468   # Copies stream +src+ to +dest+.
469   # +src+ must respond to #read(n) and
470   # +dest+ must respond to #write(str).
471   #
472   def copy_stream(src, dest)
473     fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
474   end
475   module_function :copy_stream
477   #
478   # Options: force noop verbose
479   # 
480   # Moves file(s) +src+ to +dest+.  If +file+ and +dest+ exist on the different
481   # disk partition, the file is copied instead.
482   # 
483   #   FileUtils.mv 'badname.rb', 'goodname.rb'
484   #   FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true  # no error
485   # 
486   #   FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
487   #   FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
488   # 
489   def mv(src, dest, options = {})
490     fu_check_options options, OPT_TABLE['mv']
491     fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
492     return if options[:noop]
493     fu_each_src_dest(src, dest) do |s, d|
494       destent = Entry_.new(d, nil, true)
495       begin
496         if destent.exist?
497           if destent.directory?
498             raise Errno::EEXIST, dest
499           else
500             destent.remove_file if rename_cannot_overwrite_file?
501           end
502         end
503         begin
504           File.rename s, d
505         rescue Errno::EXDEV
506           copy_entry s, d, true
507           if options[:secure]
508             remove_entry_secure s, options[:force]
509           else
510             remove_entry s, options[:force]
511           end
512         end
513       rescue SystemCallError
514         raise unless options[:force]
515       end
516     end
517   end
518   module_function :mv
520   alias move mv
521   module_function :move
523   OPT_TABLE['mv']   =
524   OPT_TABLE['move'] = [:force, :noop, :verbose, :secure]
526   def rename_cannot_overwrite_file?   #:nodoc:
527     /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
528   end
529   private_module_function :rename_cannot_overwrite_file?
531   #
532   # Options: force noop verbose
533   # 
534   # Remove file(s) specified in +list+.  This method cannot remove directories.
535   # All StandardErrors are ignored when the :force option is set.
536   # 
537   #   FileUtils.rm %w( junk.txt dust.txt )
538   #   FileUtils.rm Dir.glob('*.so')
539   #   FileUtils.rm 'NotExistFile', :force => true   # never raises exception
540   # 
541   def rm(list, options = {})
542     fu_check_options options, OPT_TABLE['rm']
543     list = fu_list(list)
544     fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
545     return if options[:noop]
547     list.each do |path|
548       remove_file path, options[:force]
549     end
550   end
551   module_function :rm
553   alias remove rm
554   module_function :remove
556   OPT_TABLE['rm']     =
557   OPT_TABLE['remove'] = [:force, :noop, :verbose]
559   #
560   # Options: noop verbose
561   # 
562   # Equivalent to
563   #
564   #   #rm(list, :force => true)
565   #
566   def rm_f(list, options = {})
567     fu_check_options options, OPT_TABLE['rm_f']
568     options = options.dup
569     options[:force] = true
570     rm list, options
571   end
572   module_function :rm_f
574   alias safe_unlink rm_f
575   module_function :safe_unlink
577   OPT_TABLE['rm_f']        =
578   OPT_TABLE['safe_unlink'] = [:noop, :verbose]
580   #
581   # Options: force noop verbose secure
582   # 
583   # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
584   # removes its all contents recursively. This method ignores
585   # StandardError when :force option is set.
586   # 
587   #   FileUtils.rm_r Dir.glob('/tmp/*')
588   #   FileUtils.rm_r '/', :force => true          #  :-)
589   #
590   # WARNING: This method causes local vulnerability
591   # if one of parent directories or removing directory tree are world
592   # writable (including /tmp, whose permission is 1777), and the current
593   # process has strong privilege such as Unix super user (root), and the
594   # system has symbolic link.  For secure removing, read the documentation
595   # of #remove_entry_secure carefully, and set :secure option to true.
596   # Default is :secure=>false.
597   #
598   # NOTE: This method calls #remove_entry_secure if :secure option is set.
599   # See also #remove_entry_secure.
600   # 
601   def rm_r(list, options = {})
602     fu_check_options options, OPT_TABLE['rm_r']
603     # options[:secure] = true unless options.key?(:secure)
604     list = fu_list(list)
605     fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
606     return if options[:noop]
607     list.each do |path|
608       if options[:secure]
609         remove_entry_secure path, options[:force]
610       else
611         remove_entry path, options[:force]
612       end
613     end
614   end
615   module_function :rm_r
617   OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure]
619   #
620   # Options: noop verbose secure
621   # 
622   # Equivalent to
623   #
624   #   #rm_r(list, :force => true)
625   #
626   # WARNING: This method causes local vulnerability.
627   # Read the documentation of #rm_r first.
628   # 
629   def rm_rf(list, options = {})
630     fu_check_options options, OPT_TABLE['rm_rf']
631     options = options.dup
632     options[:force] = true
633     rm_r list, options
634   end
635   module_function :rm_rf
637   alias rmtree rm_rf
638   module_function :rmtree
640   OPT_TABLE['rm_rf']  =
641   OPT_TABLE['rmtree'] = [:noop, :verbose, :secure]
643   #
644   # This method removes a file system entry +path+.  +path+ shall be a
645   # regular file, a directory, or something.  If +path+ is a directory,
646   # remove it recursively.  This method is required to avoid TOCTTOU
647   # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
648   # #rm_r causes security hole when:
649   #
650   #   * Parent directory is world writable (including /tmp).
651   #   * Removing directory tree includes world writable directory.
652   #   * The system has symbolic link.
653   #
654   # To avoid this security hole, this method applies special preprocess.
655   # If +path+ is a directory, this method chown(2) and chmod(2) all
656   # removing directories.  This requires the current process is the
657   # owner of the removing whole directory tree, or is the super user (root).
658   #
659   # WARNING: You must ensure that *ALL* parent directories are not
660   # world writable.  Otherwise this method does not work.
661   # Only exception is temporary directory like /tmp and /var/tmp,
662   # whose permission is 1777.
663   #
664   # WARNING: Only the owner of the removing directory tree, or Unix super
665   # user (root) should invoke this method.  Otherwise this method does not
666   # work.
667   #
668   # For details of this security vulnerability, see Perl's case:
669   #
670   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
671   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
672   #
673   # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
674   #
675   def remove_entry_secure(path, force = false)
676     unless fu_have_symlink?
677       remove_entry path, force
678       return
679     end
680     fullpath = File.expand_path(path)
681     st = File.lstat(fullpath)
682     unless st.directory?
683       File.unlink fullpath
684       return
685     end
686     # is a directory.
687     parent_st = File.stat(File.dirname(fullpath))
688     unless parent_st.world_writable?
689       remove_entry path, force
690       return
691     end
692     unless parent_st.sticky?
693       raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
694     end
695     # freeze tree root
696     euid = Process.euid
697     File.open(fullpath + '/.') {|f|
698       unless fu_stat_identical_entry?(st, f.stat)
699         # symlink (TOC-to-TOU attack?)
700         File.unlink fullpath
701         return
702       end
703       f.chown euid, -1
704       f.chmod 0700
705     }
706     # ---- tree root is frozen ----
707     root = Entry_.new(path)
708     root.preorder_traverse do |ent|
709       if ent.directory?
710         ent.chown euid, -1
711         ent.chmod 0700
712       end
713     end
714     root.postorder_traverse do |ent|
715       begin
716         ent.remove
717       rescue
718         raise unless force
719       end
720     end
721   rescue
722     raise unless force
723   end
724   module_function :remove_entry_secure
726   def fu_have_symlink?   #:nodoc
727     File.symlink nil, nil
728   rescue NotImplementedError
729     return false
730   rescue
731     return true
732   end
733   private_module_function :fu_have_symlink?
735   def fu_stat_identical_entry?(a, b)   #:nodoc:
736     a.dev == b.dev and a.ino == b.ino
737   end
738   private_module_function :fu_stat_identical_entry?
740   #
741   # This method removes a file system entry +path+.
742   # +path+ might be a regular file, a directory, or something.
743   # If +path+ is a directory, remove it recursively.
744   #
745   # See also #remove_entry_secure.
746   #
747   def remove_entry(path, force = false)
748     Entry_.new(path).postorder_traverse do |ent|
749       begin
750         ent.remove
751       rescue
752         raise unless force
753       end
754     end
755   rescue
756     raise unless force
757   end
758   module_function :remove_entry
760   #
761   # Removes a file +path+.
762   # This method ignores StandardError if +force+ is true.
763   #
764   def remove_file(path, force = false)
765     Entry_.new(path).remove_file
766   rescue
767     raise unless force
768   end
769   module_function :remove_file
771   #
772   # Removes a directory +dir+ and its contents recursively.
773   # This method ignores StandardError if +force+ is true.
774   #
775   def remove_dir(path, force = false)
776     remove_entry path, force   # FIXME?? check if it is a directory
777   end
778   module_function :remove_dir
780   #
781   # Returns true if the contents of a file A and a file B are identical.
782   # 
783   #   FileUtils.compare_file('somefile', 'somefile')  #=> true
784   #   FileUtils.compare_file('/bin/cp', '/bin/mv')    #=> maybe false
785   #
786   def compare_file(a, b)
787     return false unless File.size(a) == File.size(b)
788     File.open(a, 'rb') {|fa|
789       File.open(b, 'rb') {|fb|
790         return compare_stream(fa, fb)
791       }
792     }
793   end
794   module_function :compare_file
796   alias identical? compare_file
797   alias cmp compare_file
798   module_function :identical?
799   module_function :cmp
801   #
802   # Returns true if the contents of a stream +a+ and +b+ are identical.
803   #
804   def compare_stream(a, b)
805     bsize = fu_stream_blksize(a, b)
806     sa = sb = nil
807     while sa == sb
808       sa = a.read(bsize)
809       sb = b.read(bsize)
810       unless sa and sb
811         if sa.nil? and sb.nil?
812           return true
813         end
814       end
815     end
816     false
817   end
818   module_function :compare_stream
820   #
821   # Options: mode preserve noop verbose
822   # 
823   # If +src+ is not same as +dest+, copies it and changes the permission
824   # mode to +mode+.  If +dest+ is a directory, destination is +dest+/+src+.
825   # This method removes destination before copy.
826   # 
827   #   FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
828   #   FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
829   # 
830   def install(src, dest, options = {})
831     fu_check_options options, OPT_TABLE['install']
832     fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
833     return if options[:noop]
834     fu_each_src_dest(src, dest) do |s, d|
835       unless File.exist?(d) and compare_file(s, d)
836         remove_file d, true
837         st = File.stat(s) if options[:preserve]
838         copy_file s, d
839         File.utime st.atime, st.mtime, d if options[:preserve]
840         File.chmod options[:mode], d if options[:mode]
841       end
842     end
843   end
844   module_function :install
846   OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose]
848   #
849   # Options: noop verbose
850   # 
851   # Changes permission bits on the named files (in +list+) to the bit pattern
852   # represented by +mode+.
853   # 
854   #   FileUtils.chmod 0755, 'somecommand'
855   #   FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
856   #   FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
857   # 
858   def chmod(mode, list, options = {})
859     fu_check_options options, OPT_TABLE['chmod']
860     list = fu_list(list)
861     fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
862     return if options[:noop]
863     list.each do |path|
864       Entry_.new(path).chmod mode
865     end
866   end
867   module_function :chmod
869   OPT_TABLE['chmod'] = [:noop, :verbose]
871   #
872   # Options: noop verbose force
873   # 
874   # Changes permission bits on the named files (in +list+)
875   # to the bit pattern represented by +mode+.
876   # 
877   #   FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
878   # 
879   def chmod_R(mode, list, options = {})
880     fu_check_options options, OPT_TABLE['chmod_R']
881     list = fu_list(list)
882     fu_output_message sprintf('chmod -R%s %o %s',
883                               (options[:force] ? 'f' : ''),
884                               mode, list.join(' ')) if options[:verbose]
885     return if options[:noop]
886     list.each do |root|
887       Entry_.new(root).traverse do |ent|
888         begin
889           ent.chmod mode
890         rescue
891           raise unless options[:force]
892         end
893       end
894     end
895   end
896   module_function :chmod_R
898   OPT_TABLE['chmod_R'] = [:noop, :verbose, :force]
900   #
901   # Options: noop verbose
902   # 
903   # Changes owner and group on the named files (in +list+)
904   # to the user +user+ and the group +group+.  +user+ and +group+
905   # may be an ID (Integer/String) or a name (String).
906   # If +user+ or +group+ is nil, this method does not change
907   # the attribute.
908   # 
909   #   FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
910   #   FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
911   # 
912   def chown(user, group, list, options = {})
913     fu_check_options options, OPT_TABLE['chown']
914     list = fu_list(list)
915     fu_output_message sprintf('chown %s%s',
916                               [user,group].compact.join(':') + ' ',
917                               list.join(' ')) if options[:verbose]
918     return if options[:noop]
919     uid = fu_get_uid(user)
920     gid = fu_get_gid(group)
921     list.each do |path|
922       Entry_.new(path).chown uid, gid
923     end
924   end
925   module_function :chown
927   OPT_TABLE['chown'] = [:noop, :verbose]
929   #
930   # Options: noop verbose force
931   # 
932   # Changes owner and group on the named files (in +list+)
933   # to the user +user+ and the group +group+ recursively.
934   # +user+ and +group+ may be an ID (Integer/String) or
935   # a name (String).  If +user+ or +group+ is nil, this
936   # method does not change the attribute.
937   # 
938   #   FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
939   #   FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
940   # 
941   def chown_R(user, group, list, options = {})
942     fu_check_options options, OPT_TABLE['chown_R']
943     list = fu_list(list)
944     fu_output_message sprintf('chown -R%s %s%s',
945                               (options[:force] ? 'f' : ''),
946                               [user,group].compact.join(':') + ' ',
947                               list.join(' ')) if options[:verbose]
948     return if options[:noop]
949     uid = fu_get_uid(user)
950     gid = fu_get_gid(group)
951     return unless uid or gid
952     list.each do |root|
953       Entry_.new(root).traverse do |ent|
954         begin
955           ent.chown uid, gid
956         rescue
957           raise unless options[:force]
958         end
959       end
960     end
961   end
962   module_function :chown_R
964   OPT_TABLE['chown_R'] = [:noop, :verbose, :force]
966   begin
967     require 'etc'
969     def fu_get_uid(user)   #:nodoc:
970       return nil unless user
971       user = user.to_s
972       if /\A\d+\z/ =~ user
973       then user.to_i
974       else Etc.getpwnam(user).uid
975       end
976     end
977     private_module_function :fu_get_uid
979     def fu_get_gid(group)   #:nodoc:
980       return nil unless group
981       if /\A\d+\z/ =~ group
982       then group.to_i
983       else Etc.getgrnam(group).gid
984       end
985     end
986     private_module_function :fu_get_gid
988   rescue LoadError
989     # need Win32 support???
991     def fu_get_uid(user)   #:nodoc:
992       user    # FIXME
993     end
994     private_module_function :fu_get_uid
996     def fu_get_gid(group)   #:nodoc:
997       group   # FIXME
998     end
999     private_module_function :fu_get_gid
1000   end
1002   #
1003   # Options: noop verbose
1004   # 
1005   # Updates modification time (mtime) and access time (atime) of file(s) in
1006   # +list+.  Files are created if they don't exist.
1007   # 
1008   #   FileUtils.touch 'timestamp'
1009   #   FileUtils.touch Dir.glob('*.c');  system 'make'
1010   # 
1011   def touch(list, options = {})
1012     fu_check_options options, OPT_TABLE['touch']
1013     list = fu_list(list)
1014     created = nocreate = options[:nocreate]
1015     t = options[:mtime]
1016     if options[:verbose]
1017       fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}"
1018     end
1019     return if options[:noop]
1020     list.each do |path|
1021       created = nocreate
1022       begin
1023         File.utime(t, t, path)
1024       rescue Errno::ENOENT
1025         raise if created
1026         File.open(path, 'a') {
1027           ;
1028         }
1029         created = true
1030         retry if t
1031       end
1032     end
1033   end
1034   module_function :touch
1036   OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate]
1038   private
1040   module StreamUtils_
1041     private
1043     def fu_windows?
1044       /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
1045     end
1047     def fu_copy_stream0(src, dest, blksize)   #:nodoc:
1048       # FIXME: readpartial?
1049       while s = src.read(blksize)
1050         dest.write s
1051       end
1052     end
1054     def fu_stream_blksize(*streams)
1055       streams.each do |s|
1056         next unless s.respond_to?(:stat)
1057         size = fu_blksize(s.stat)
1058         return size if size
1059       end
1060       fu_default_blksize()
1061     end
1063     def fu_blksize(st)
1064       s = st.blksize
1065       return nil unless s
1066       return nil if s == 0
1067       s
1068     end
1070     def fu_default_blksize
1071       1024
1072     end
1073   end
1075   include StreamUtils_
1076   extend StreamUtils_
1078   class Entry_   #:nodoc: internal use only
1079     include StreamUtils_
1081     def initialize(a, b = nil, deref = false)
1082       @prefix = @rel = @path = nil
1083       if b
1084         @prefix = a
1085         @rel = b
1086       else
1087         @path = a
1088       end
1089       @deref = deref
1090       @stat = nil
1091       @lstat = nil
1092     end
1094     def inspect
1095       "\#<#{self.class} #{path()}>"
1096     end
1098     def path
1099       if @path
1100         File.path(@path)
1101       else
1102         join(@prefix, @rel)
1103       end
1104     end
1106     def prefix
1107       @prefix || @path
1108     end
1110     def rel
1111       @rel
1112     end
1114     def dereference?
1115       @deref
1116     end
1118     def exist?
1119       lstat! ? true : false
1120     end
1122     def file?
1123       s = lstat!
1124       s and s.file?
1125     end
1127     def directory?
1128       s = lstat!
1129       s and s.directory?
1130     end
1132     def symlink?
1133       s = lstat!
1134       s and s.symlink?
1135     end
1137     def chardev?
1138       s = lstat!
1139       s and s.chardev?
1140     end
1142     def blockdev?
1143       s = lstat!
1144       s and s.blockdev?
1145     end
1147     def socket?
1148       s = lstat!
1149       s and s.socket?
1150     end
1152     def pipe?
1153       s = lstat!
1154       s and s.pipe?
1155     end
1157     S_IF_DOOR = 0xD000
1159     def door?
1160       s = lstat!
1161       s and (s.mode & 0xF000 == S_IF_DOOR)
1162     end
1164     def entries
1165       Dir.entries(path())\
1166           .reject {|n| n == '.' or n == '..' }\
1167           .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
1168     end
1170     def stat
1171       return @stat if @stat
1172       if lstat() and lstat().symlink?
1173         @stat = File.stat(path())
1174       else
1175         @stat = lstat()
1176       end
1177       @stat
1178     end
1180     def stat!
1181       return @stat if @stat
1182       if lstat! and lstat!.symlink?
1183         @stat = File.stat(path())
1184       else
1185         @stat = lstat!
1186       end
1187       @stat
1188     rescue SystemCallError
1189       nil
1190     end
1192     def lstat
1193       if dereference?
1194         @lstat ||= File.stat(path())
1195       else
1196         @lstat ||= File.lstat(path())
1197       end
1198     end
1200     def lstat!
1201       lstat()
1202     rescue SystemCallError
1203       nil
1204     end
1206     def chmod(mode)
1207       if symlink?
1208         File.lchmod mode, path() if have_lchmod?
1209       else
1210         File.chmod mode, path()
1211       end
1212     end
1214     def chown(uid, gid)
1215       if symlink?
1216         File.lchown uid, gid, path() if have_lchown?
1217       else
1218         File.chown uid, gid, path()
1219       end
1220     end
1222     def copy(dest)
1223       case
1224       when file?
1225         copy_file dest
1226       when directory?
1227         if !File.exist?(dest) and /^#{Regexp.quote(path)}/ =~ File.dirname(dest)
1228           raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
1229         end
1230         begin
1231           Dir.mkdir dest
1232         rescue
1233           raise unless File.directory?(dest)
1234         end
1235       when symlink?
1236         File.symlink File.readlink(path()), dest
1237       when chardev?
1238         raise "cannot handle device file" unless File.respond_to?(:mknod)
1239         mknod dest, ?c, 0666, lstat().rdev
1240       when blockdev?
1241         raise "cannot handle device file" unless File.respond_to?(:mknod)
1242         mknod dest, ?b, 0666, lstat().rdev
1243       when socket?
1244         raise "cannot handle socket" unless File.respond_to?(:mknod)
1245         mknod dest, nil, lstat().mode, 0
1246       when pipe?
1247         raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
1248         mkfifo dest, 0666
1249       when door?
1250         raise "cannot handle door: #{path()}"
1251       else
1252         raise "unknown file type: #{path()}"
1253       end
1254     end
1256     def copy_file(dest)
1257       st = stat()
1258       File.open(path(),  'rb') {|r|
1259         File.open(dest, 'wb', st.mode) {|w|
1260           fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
1261         }
1262       }
1263     end
1265     def copy_metadata(path)
1266       st = lstat()
1267       File.utime st.atime, st.mtime, path
1268       begin
1269         File.chown st.uid, st.gid, path
1270       rescue Errno::EPERM
1271         # clear setuid/setgid
1272         File.chmod st.mode & 01777, path
1273       else
1274         File.chmod st.mode, path
1275       end
1276     end
1278     def remove
1279       if directory?
1280         remove_dir1
1281       else
1282         remove_file
1283       end
1284     end
1286     def remove_dir1
1287       platform_support {
1288         Dir.rmdir path().sub(%r</\z>, '')
1289       }
1290     end
1292     def remove_file
1293       platform_support {
1294         File.unlink path
1295       }
1296     end
1298     def platform_support
1299       return yield unless fu_windows?
1300       first_time_p = true
1301       begin
1302         yield
1303       rescue Errno::ENOENT
1304         raise
1305       rescue => err
1306         if first_time_p
1307           first_time_p = false
1308           begin
1309             File.chmod 0700, path()   # Windows does not have symlink
1310             retry
1311           rescue SystemCallError
1312           end
1313         end
1314         raise err
1315       end
1316     end
1318     def preorder_traverse
1319       stack = [self]
1320       while ent = stack.pop
1321         yield ent
1322         stack.concat ent.entries.reverse if ent.directory?
1323       end
1324     end
1326     alias traverse preorder_traverse
1328     def postorder_traverse
1329       if directory?
1330         entries().each do |ent|
1331           ent.postorder_traverse do |e|
1332             yield e
1333           end
1334         end
1335       end
1336       yield self
1337     end
1339     private
1341     $fileutils_rb_have_lchmod = nil
1343     def have_lchmod?
1344       # This is not MT-safe, but it does not matter.
1345       if $fileutils_rb_have_lchmod == nil
1346         $fileutils_rb_have_lchmod = check_have_lchmod?
1347       end
1348       $fileutils_rb_have_lchmod
1349     end
1351     def check_have_lchmod?
1352       return false unless File.respond_to?(:lchmod)
1353       File.lchmod 0
1354       return true
1355     rescue NotImplementedError
1356       return false
1357     end
1359     $fileutils_rb_have_lchown = nil
1361     def have_lchown?
1362       # This is not MT-safe, but it does not matter.
1363       if $fileutils_rb_have_lchown == nil
1364         $fileutils_rb_have_lchown = check_have_lchown?
1365       end
1366       $fileutils_rb_have_lchown
1367     end
1369     def check_have_lchown?
1370       return false unless File.respond_to?(:lchown)
1371       File.lchown nil, nil
1372       return true
1373     rescue NotImplementedError
1374       return false
1375     end
1377     def join(dir, base)
1378       return File.path(dir) if not base or base == '.'
1379       return File.path(base) if not dir or dir == '.'
1380       File.join(dir, base)
1381     end
1382   end   # class Entry_
1384   def fu_list(arg)   #:nodoc:
1385     [arg].flatten.map {|path| File.path(path) }
1386   end
1387   private_module_function :fu_list
1389   def fu_each_src_dest(src, dest)   #:nodoc:
1390     fu_each_src_dest0(src, dest) do |s, d|
1391       raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
1392       yield s, d
1393     end
1394   end
1395   private_module_function :fu_each_src_dest
1397   def fu_each_src_dest0(src, dest)   #:nodoc:
1398     if tmp = Array.try_convert(src)
1399       tmp.each do |s|
1400         s = File.path(s)
1401         yield s, File.join(dest, File.basename(s))
1402       end
1403     else
1404       src = File.path(src)
1405       if File.directory?(dest)
1406         yield src, File.join(dest, File.basename(src))
1407       else
1408         yield src, File.path(dest)
1409       end
1410     end
1411   end
1412   private_module_function :fu_each_src_dest0
1414   def fu_same?(a, b)   #:nodoc:
1415     if fu_have_st_ino?
1416       st1 = File.stat(a)
1417       st2 = File.stat(b)
1418       st1.dev == st2.dev and st1.ino == st2.ino
1419     else
1420       File.expand_path(a) == File.expand_path(b)
1421     end
1422   rescue Errno::ENOENT
1423     return false
1424   end
1425   private_module_function :fu_same?
1427   def fu_have_st_ino?   #:nodoc:
1428     not fu_windows?
1429   end
1430   private_module_function :fu_have_st_ino?
1432   def fu_check_options(options, optdecl)   #:nodoc:
1433     h = options.dup
1434     optdecl.each do |opt|
1435       h.delete opt
1436     end
1437     raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
1438   end
1439   private_module_function :fu_check_options
1441   def fu_update_option(args, new)   #:nodoc:
1442     if tmp = Hash.try_convert(args.last)
1443       args[-1] = tmp.dup.update(new)
1444     else
1445       args.push new
1446     end
1447     args
1448   end
1449   private_module_function :fu_update_option
1451   @fileutils_output = $stderr
1452   @fileutils_label  = ''
1454   def fu_output_message(msg)   #:nodoc:
1455     @fileutils_output ||= $stderr
1456     @fileutils_label  ||= ''
1457     @fileutils_output.puts @fileutils_label + msg
1458   end
1459   private_module_function :fu_output_message
1461   #
1462   # Returns an Array of method names which have any options.
1463   #
1464   #   p FileUtils.commands  #=> ["chmod", "cp", "cp_r", "install", ...]
1465   #
1466   def FileUtils.commands
1467     OPT_TABLE.keys
1468   end
1470   #
1471   # Returns an Array of option names.
1472   #
1473   #   p FileUtils.options  #=> ["noop", "force", "verbose", "preserve", "mode"]
1474   #
1475   def FileUtils.options
1476     OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
1477   end
1479   #
1480   # Returns true if the method +mid+ have an option +opt+.
1481   #
1482   #   p FileUtils.have_option?(:cp, :noop)     #=> true
1483   #   p FileUtils.have_option?(:rm, :force)    #=> true
1484   #   p FileUtils.have_option?(:rm, :perserve) #=> false
1485   #
1486   def FileUtils.have_option?(mid, opt)
1487     li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
1488     li.include?(opt)
1489   end
1491   #
1492   # Returns an Array of option names of the method +mid+.
1493   #
1494   #   p FileUtils.options(:rm)  #=> ["noop", "verbose", "force"]
1495   #
1496   def FileUtils.options_of(mid)
1497     OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
1498   end
1500   #
1501   # Returns an Array of method names which have the option +opt+.
1502   #
1503   #   p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
1504   #
1505   def FileUtils.collect_method(opt)
1506     OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
1507   end
1509   METHODS = singleton_methods() - [:private_module_function,
1510       :commands, :options, :have_option?, :options_of, :collect_method]
1512   # 
1513   # This module has all methods of FileUtils module, but it outputs messages
1514   # before acting.  This equates to passing the <tt>:verbose</tt> flag to
1515   # methods in FileUtils.
1516   # 
1517   module Verbose
1518     include FileUtils
1519     @fileutils_output  = $stderr
1520     @fileutils_label   = ''
1521     ::FileUtils.collect_method(:verbose).each do |name|
1522       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1523         def #{name}(*args)
1524           super(*fu_update_option(args, :verbose => true))
1525         end
1526         private :#{name}
1527       EOS
1528     end
1529     extend self
1530     class << self
1531       ::FileUtils::METHODS.each do |m|
1532         public m
1533       end
1534     end
1535   end
1537   # 
1538   # This module has all methods of FileUtils module, but never changes
1539   # files/directories.  This equates to passing the <tt>:noop</tt> flag
1540   # to methods in FileUtils.
1541   # 
1542   module NoWrite
1543     include FileUtils
1544     @fileutils_output  = $stderr
1545     @fileutils_label   = ''
1546     ::FileUtils.collect_method(:noop).each do |name|
1547       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1548         def #{name}(*args)
1549           super(*fu_update_option(args, :noop => true))
1550         end
1551         private :#{name}
1552       EOS
1553     end
1554     extend self
1555     class << self
1556       ::FileUtils::METHODS.each do |m|
1557         public m
1558       end
1559     end
1560   end
1562   # 
1563   # This module has all methods of FileUtils module, but never changes
1564   # files/directories, with printing message before acting.
1565   # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
1566   # to methods in FileUtils.
1567   # 
1568   module DryRun
1569     include FileUtils
1570     @fileutils_output  = $stderr
1571     @fileutils_label   = ''
1572     ::FileUtils.collect_method(:noop).each do |name|
1573       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1574         def #{name}(*args)
1575           super(*fu_update_option(args, :noop => true, :verbose => true))
1576         end
1577         private :#{name}
1578       EOS
1579     end
1580     extend self
1581     class << self
1582       ::FileUtils::METHODS.each do |m|
1583         public m
1584       end
1585     end
1586   end