Tag unstable CGI specs.
[rbx.git] / lib / fileutils.rb
blobce718ab398b030630218f6c14a569e770800d379
1
2 # = fileutils.rb
3
4 # Copyright (c) 2000-2006 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 |path|
216         begin
217           fu_mkdir path, options[:mode]
218         rescue SystemCallError => err
219           raise unless File.directory?(path)
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     options[:dereference_root] = true unless options.key?(:dereference_root)
422     fu_each_src_dest(src, dest) do |s, d|
423       copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
424     end
425   end
426   module_function :cp_r
428   OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose,
429                        :dereference_root, :remove_destination]
431   #
432   # Copies a file system entry +src+ to +dest+.
433   # If +src+ is a directory, this method copies its contents recursively.
434   # This method preserves file types, c.f. symlink, directory...
435   # (FIFO, device files and etc. are not supported yet)
436   #
437   # Both of +src+ and +dest+ must be a path name.
438   # +src+ must exist, +dest+ must not exist.
439   #
440   # If +preserve+ is true, this method preserves owner, group, permissions
441   # and modified time.
442   #
443   # If +dereference_root+ is true, this method dereference tree root.
444   #
445   # If +remove_destination+ is true, this method removes each destination file before copy.
446   #
447   def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
448     Entry_.new(src, nil, dereference_root).traverse do |ent|
449       destent = Entry_.new(dest, ent.rel, false)
450       File.unlink destent.path if remove_destination && File.file?(destent.path)
451       ent.copy destent.path
452       ent.copy_metadata destent.path if preserve
453     end
454   end
455   module_function :copy_entry
457   #
458   # Copies file contents of +src+ to +dest+.
459   # Both of +src+ and +dest+ must be a path name.
460   #
461   def copy_file(src, dest, preserve = false, dereference = true)
462     ent = Entry_.new(src, nil, dereference)
463     ent.copy_file dest
464     ent.copy_metadata dest if preserve
465   end
466   module_function :copy_file
468   #
469   # Copies stream +src+ to +dest+.
470   # +src+ must respond to #read(n) and
471   # +dest+ must respond to #write(str).
472   #
473   def copy_stream(src, dest)
474     fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
475   end
476   module_function :copy_stream
478   #
479   # Options: force noop verbose
480   # 
481   # Moves file(s) +src+ to +dest+.  If +file+ and +dest+ exist on the different
482   # disk partition, the file is copied instead.
483   # 
484   #   FileUtils.mv 'badname.rb', 'goodname.rb'
485   #   FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true  # no error
486   # 
487   #   FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
488   #   FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
489   # 
490   def mv(src, dest, options = {})
491     fu_check_options options, OPT_TABLE['mv']
492     fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
493     return if options[:noop]
494     fu_each_src_dest(src, dest) do |s, d|
495       destent = Entry_.new(d, nil, true)
496       begin
497         if destent.exist?
498           if destent.directory?
499             raise Errno::EEXIST, dest
500           else
501             destent.remove_file if rename_cannot_overwrite_file?
502           end
503         end
504         begin
505           File.rename s, d
506         rescue Errno::EXDEV
507           copy_entry s, d, true
508           if options[:secure]
509             remove_entry_secure s, options[:force]
510           else
511             remove_entry s, options[:force]
512           end
513         end
514       rescue SystemCallError
515         raise unless options[:force]
516       end
517     end
518   end
519   module_function :mv
521   alias move mv
522   module_function :move
524   OPT_TABLE['mv']   =
525   OPT_TABLE['move'] = [:force, :noop, :verbose, :secure]
527   def rename_cannot_overwrite_file?   #:nodoc:
528     /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
529   end
530   private_module_function :rename_cannot_overwrite_file?
532   #
533   # Options: force noop verbose
534   # 
535   # Remove file(s) specified in +list+.  This method cannot remove directories.
536   # All StandardErrors are ignored when the :force option is set.
537   # 
538   #   FileUtils.rm %w( junk.txt dust.txt )
539   #   FileUtils.rm Dir.glob('*.so')
540   #   FileUtils.rm 'NotExistFile', :force => true   # never raises exception
541   # 
542   def rm(list, options = {})
543     fu_check_options options, OPT_TABLE['rm']
544     list = fu_list(list)
545     fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
546     return if options[:noop]
548     list.each do |path|
549       remove_file path, options[:force]
550     end
551   end
552   module_function :rm
554   alias remove rm
555   module_function :remove
557   OPT_TABLE['rm']     =
558   OPT_TABLE['remove'] = [:force, :noop, :verbose]
560   #
561   # Options: noop verbose
562   # 
563   # Equivalent to
564   #
565   #   #rm(list, :force => true)
566   #
567   def rm_f(list, options = {})
568     fu_check_options options, OPT_TABLE['rm_f']
569     options = options.dup
570     options[:force] = true
571     rm list, options
572   end
573   module_function :rm_f
575   alias safe_unlink rm_f
576   module_function :safe_unlink
578   OPT_TABLE['rm_f']        =
579   OPT_TABLE['safe_unlink'] = [:noop, :verbose]
581   #
582   # Options: force noop verbose secure
583   # 
584   # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
585   # removes its all contents recursively. This method ignores
586   # StandardError when :force option is set.
587   # 
588   #   FileUtils.rm_r Dir.glob('/tmp/*')
589   #   FileUtils.rm_r '/', :force => true          #  :-)
590   #
591   # WARNING: This method causes local vulnerability
592   # if one of parent directories or removing directory tree are world
593   # writable (including /tmp, whose permission is 1777), and the current
594   # process has strong privilege such as Unix super user (root), and the
595   # system has symbolic link.  For secure removing, read the documentation
596   # of #remove_entry_secure carefully, and set :secure option to true.
597   # Default is :secure=>false.
598   #
599   # NOTE: This method calls #remove_entry_secure if :secure option is set.
600   # See also #remove_entry_secure.
601   # 
602   def rm_r(list, options = {})
603     fu_check_options options, OPT_TABLE['rm_r']
604     # options[:secure] = true unless options.key?(:secure)
605     list = fu_list(list)
606     fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
607     return if options[:noop]
608     list.each do |path|
609       if options[:secure]
610         remove_entry_secure path, options[:force]
611       else
612         remove_entry path, options[:force]
613       end
614     end
615   end
616   module_function :rm_r
618   OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure]
620   #
621   # Options: noop verbose secure
622   # 
623   # Equivalent to
624   #
625   #   #rm_r(list, :force => true)
626   #
627   # WARNING: This method causes local vulnerability.
628   # Read the documentation of #rm_r first.
629   # 
630   def rm_rf(list, options = {})
631     fu_check_options options, OPT_TABLE['rm_rf']
632     options = options.dup
633     options[:force] = true
634     rm_r list, options
635   end
636   module_function :rm_rf
638   alias rmtree rm_rf
639   module_function :rmtree
641   OPT_TABLE['rm_rf']  =
642   OPT_TABLE['rmtree'] = [:noop, :verbose, :secure]
644   #
645   # This method removes a file system entry +path+.  +path+ shall be a
646   # regular file, a directory, or something.  If +path+ is a directory,
647   # remove it recursively.  This method is required to avoid TOCTTOU
648   # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
649   # #rm_r causes security hole when:
650   #
651   #   * Parent directory is world writable (including /tmp).
652   #   * Removing directory tree includes world writable directory.
653   #   * The system has symbolic link.
654   #
655   # To avoid this security hole, this method applies special preprocess.
656   # If +path+ is a directory, this method chown(2) and chmod(2) all
657   # removing directories.  This requires the current process is the
658   # owner of the removing whole directory tree, or is the super user (root).
659   #
660   # WARNING: You must ensure that *ALL* parent directories are not
661   # world writable.  Otherwise this method does not work.
662   # Only exception is temporary directory like /tmp and /var/tmp,
663   # whose permission is 1777.
664   #
665   # WARNING: Only the owner of the removing directory tree, or Unix super
666   # user (root) should invoke this method.  Otherwise this method does not
667   # work.
668   #
669   # For details of this security vulnerability, see Perl's case:
670   #
671   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
672   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
673   #
674   # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
675   #
676   def remove_entry_secure(path, force = false)
677     unless fu_have_symlink?
678       remove_entry path, force
679       return
680     end
681     fullpath = File.expand_path(path)
682     st = File.lstat(fullpath)
683     unless st.directory?
684       File.unlink fullpath
685       return
686     end
687     # is a directory.
688     parent_st = File.stat(File.dirname(fullpath))
689     unless fu_world_writable?(parent_st)
690       remove_entry path, force
691       return
692     end
693     unless parent_st.sticky?
694       raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
695     end
696     # freeze tree root
697     euid = Process.euid
698     File.open(fullpath + '/.') {|f|
699       unless fu_stat_identical_entry?(st, f.stat)
700         # symlink (TOC-to-TOU attack?)
701         File.unlink fullpath
702         return
703       end
704       f.chown euid, -1
705       f.chmod 0700
706     }
707     # ---- tree root is frozen ----
708     root = Entry_.new(path)
709     root.preorder_traverse do |ent|
710       if ent.directory?
711         ent.chown euid, -1
712         ent.chmod 0700
713       end
714     end
715     root.postorder_traverse do |ent|
716       begin
717         ent.remove
718       rescue
719         raise unless force
720       end
721     end
722   rescue
723     raise unless force
724   end
725   module_function :remove_entry_secure
727   def fu_world_writable?(st)
728     (st.mode & 0002) != 0
729   end
730   private_module_function :fu_world_writable?
732   def fu_have_symlink?   #:nodoc
733     File.symlink nil, nil
734   rescue NotImplementedError
735     return false
736   rescue
737     return true
738   end
739   private_module_function :fu_have_symlink?
741   def fu_stat_identical_entry?(a, b)   #:nodoc:
742     a.dev == b.dev and a.ino == b.ino
743   end
744   private_module_function :fu_stat_identical_entry?
746   #
747   # This method removes a file system entry +path+.
748   # +path+ might be a regular file, a directory, or something.
749   # If +path+ is a directory, remove it recursively.
750   #
751   # See also #remove_entry_secure.
752   #
753   def remove_entry(path, force = false)
754     Entry_.new(path).postorder_traverse do |ent|
755       begin
756         ent.remove
757       rescue
758         raise unless force
759       end
760     end
761   rescue
762     raise unless force
763   end
764   module_function :remove_entry
766   #
767   # Removes a file +path+.
768   # This method ignores StandardError if +force+ is true.
769   #
770   def remove_file(path, force = false)
771     Entry_.new(path).remove_file
772   rescue
773     raise unless force
774   end
775   module_function :remove_file
777   #
778   # Removes a directory +dir+ and its contents recursively.
779   # This method ignores StandardError if +force+ is true.
780   #
781   def remove_dir(path, force = false)
782     remove_entry path, force   # FIXME?? check if it is a directory
783   end
784   module_function :remove_dir
786   #
787   # Returns true if the contents of a file A and a file B are identical.
788   # 
789   #   FileUtils.compare_file('somefile', 'somefile')  #=> true
790   #   FileUtils.compare_file('/bin/cp', '/bin/mv')    #=> maybe false
791   #
792   def compare_file(a, b)
793     return false unless File.size(a) == File.size(b)
794     File.open(a, 'rb') {|fa|
795       File.open(b, 'rb') {|fb|
796         return compare_stream(fa, fb)
797       }
798     }
799   end
800   module_function :compare_file
802   alias identical? compare_file
803   alias cmp compare_file
804   module_function :identical?
805   module_function :cmp
807   #
808   # Returns true if the contents of a stream +a+ and +b+ are identical.
809   #
810   def compare_stream(a, b)
811     bsize = fu_stream_blksize(a, b)
812     sa = sb = nil
813     while sa == sb
814       sa = a.read(bsize)
815       sb = b.read(bsize)
816       unless sa and sb
817         if sa.nil? and sb.nil?
818           return true
819         end
820       end
821     end
822     false
823   end
824   module_function :compare_stream
826   #
827   # Options: mode preserve noop verbose
828   # 
829   # If +src+ is not same as +dest+, copies it and changes the permission
830   # mode to +mode+.  If +dest+ is a directory, destination is +dest+/+src+.
831   # This method removes destination before copy.
832   # 
833   #   FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
834   #   FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
835   # 
836   def install(src, dest, options = {})
837     fu_check_options options, OPT_TABLE['install']
838     fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
839     return if options[:noop]
840     fu_each_src_dest(src, dest) do |s, d|
841       unless File.exist?(d) and compare_file(s, d)
842         remove_file d, true
843         st = File.stat(s) if options[:preserve]
844         copy_file s, d
845         File.utime st.atime, st.mtime, d if options[:preserve]
846         File.chmod options[:mode], d if options[:mode]
847       end
848     end
849   end
850   module_function :install
852   OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose]
854   #
855   # Options: noop verbose
856   # 
857   # Changes permission bits on the named files (in +list+) to the bit pattern
858   # represented by +mode+.
859   # 
860   #   FileUtils.chmod 0755, 'somecommand'
861   #   FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
862   #   FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
863   # 
864   def chmod(mode, list, options = {})
865     fu_check_options options, OPT_TABLE['chmod']
866     list = fu_list(list)
867     fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
868     return if options[:noop]
869     list.each do |path|
870       Entry_.new(path).chmod mode
871     end
872   end
873   module_function :chmod
875   OPT_TABLE['chmod'] = [:noop, :verbose]
877   #
878   # Options: noop verbose force
879   # 
880   # Changes permission bits on the named files (in +list+)
881   # to the bit pattern represented by +mode+.
882   # 
883   #   FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
884   # 
885   def chmod_R(mode, list, options = {})
886     fu_check_options options, OPT_TABLE['chmod_R']
887     list = fu_list(list)
888     fu_output_message sprintf('chmod -R%s %o %s',
889                               (options[:force] ? 'f' : ''),
890                               mode, list.join(' ')) if options[:verbose]
891     return if options[:noop]
892     list.each do |root|
893       Entry_.new(root).traverse do |ent|
894         begin
895           ent.chmod mode
896         rescue
897           raise unless options[:force]
898         end
899       end
900     end
901   end
902   module_function :chmod_R
904   OPT_TABLE['chmod_R'] = [:noop, :verbose, :force]
906   #
907   # Options: noop verbose
908   # 
909   # Changes owner and group on the named files (in +list+)
910   # to the user +user+ and the group +group+.  +user+ and +group+
911   # may be an ID (Integer/String) or a name (String).
912   # If +user+ or +group+ is nil, this method does not change
913   # the attribute.
914   # 
915   #   FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
916   #   FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
917   # 
918   def chown(user, group, list, options = {})
919     fu_check_options options, OPT_TABLE['chown']
920     list = fu_list(list)
921     fu_output_message sprintf('chown %s%s',
922                               [user,group].compact.join(':') + ' ',
923                               list.join(' ')) if options[:verbose]
924     return if options[:noop]
925     uid = fu_get_uid(user)
926     gid = fu_get_gid(group)
927     list.each do |path|
928       Entry_.new(path).chown uid, gid
929     end
930   end
931   module_function :chown
933   OPT_TABLE['chown'] = [:noop, :verbose]
935   #
936   # Options: noop verbose force
937   # 
938   # Changes owner and group on the named files (in +list+)
939   # to the user +user+ and the group +group+ recursively.
940   # +user+ and +group+ may be an ID (Integer/String) or
941   # a name (String).  If +user+ or +group+ is nil, this
942   # method does not change the attribute.
943   # 
944   #   FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
945   #   FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
946   # 
947   def chown_R(user, group, list, options = {})
948     fu_check_options options, OPT_TABLE['chown_R']
949     list = fu_list(list)
950     fu_output_message sprintf('chown -R%s %s%s',
951                               (options[:force] ? 'f' : ''),
952                               [user,group].compact.join(':') + ' ',
953                               list.join(' ')) if options[:verbose]
954     return if options[:noop]
955     uid = fu_get_uid(user)
956     gid = fu_get_gid(group)
957     return unless uid or gid
958     list.each do |root|
959       Entry_.new(root).traverse do |ent|
960         begin
961           ent.chown uid, gid
962         rescue
963           raise unless options[:force]
964         end
965       end
966     end
967   end
968   module_function :chown_R
970   OPT_TABLE['chown_R'] = [:noop, :verbose, :force]
972   begin
973     require 'etc'
975     def fu_get_uid(user)   #:nodoc:
976       return nil unless user
977       user = user.to_s
978       if /\A\d+\z/ =~ user
979       then user.to_i
980       else Etc.getpwnam(user).uid
981       end
982     end
983     private_module_function :fu_get_uid
985     def fu_get_gid(group)   #:nodoc:
986       return nil unless group
987       if /\A\d+\z/ =~ group
988       then group.to_i
989       else Etc.getgrnam(group).gid
990       end
991     end
992     private_module_function :fu_get_gid
994   rescue LoadError
995     # need Win32 support???
997     def fu_get_uid(user)   #:nodoc:
998       user    # FIXME
999     end
1000     private_module_function :fu_get_uid
1002     def fu_get_gid(group)   #:nodoc:
1003       group   # FIXME
1004     end
1005     private_module_function :fu_get_gid
1006   end
1008   #
1009   # Options: noop verbose
1010   # 
1011   # Updates modification time (mtime) and access time (atime) of file(s) in
1012   # +list+.  Files are created if they don't exist.
1013   # 
1014   #   FileUtils.touch 'timestamp'
1015   #   FileUtils.touch Dir.glob('*.c');  system 'make'
1016   # 
1017   def touch(list, options = {})
1018     fu_check_options options, OPT_TABLE['touch']
1019     list = fu_list(list)
1020     created = nocreate = options[:nocreate]
1021     t = options[:mtime]
1022     if options[:verbose]
1023       fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}"
1024     end
1025     return if options[:noop]
1026     list.each do |path|
1027       created = nocreate
1028       begin
1029         File.utime(t, t, path)
1030       rescue Errno::ENOENT
1031         raise if created
1032         File.open(path, 'a') {
1033           ;
1034         }
1035         created = true
1036         retry if t
1037       end
1038     end
1039   end
1040   module_function :touch
1042   OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate]
1044   private
1046   module StreamUtils_
1047     private
1049     def fu_windows?
1050       /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
1051     end
1053     def fu_copy_stream0(src, dest, blksize)   #:nodoc:
1054       # FIXME: readpartial?
1055       while s = src.read(blksize)
1056         dest.write s
1057       end
1058     end
1060     def fu_stream_blksize(*streams)
1061       streams.each do |s|
1062         next unless s.respond_to?(:stat)
1063         size = fu_blksize(s.stat)
1064         return size if size
1065       end
1066       fu_default_blksize()
1067     end
1069     def fu_blksize(st)
1070       s = st.blksize
1071       return nil unless s
1072       return nil if s == 0
1073       s
1074     end
1076     def fu_default_blksize
1077       1024
1078     end
1079   end
1081   include StreamUtils_
1082   extend StreamUtils_
1084   class Entry_   #:nodoc: internal use only
1085     include StreamUtils_
1087     def initialize(a, b = nil, deref = false)
1088       @prefix = @rel = @path = nil
1089       if b
1090         @prefix = a
1091         @rel = b
1092       else
1093         @path = a
1094       end
1095       @deref = deref
1096       @stat = nil
1097       @lstat = nil
1098     end
1100     def inspect
1101       "\#<#{self.class} #{path()}>"
1102     end
1104     def path
1105       if @path
1106         @path.to_str
1107       else
1108         join(@prefix, @rel)
1109       end
1110     end
1112     def prefix
1113       @prefix || @path
1114     end
1116     def rel
1117       @rel
1118     end
1120     def dereference?
1121       @deref
1122     end
1124     def exist?
1125       lstat! ? true : false
1126     end
1128     def file?
1129       s = lstat!
1130       s and s.file?
1131     end
1133     def directory?
1134       s = lstat!
1135       s and s.directory?
1136     end
1138     def symlink?
1139       s = lstat!
1140       s and s.symlink?
1141     end
1143     def chardev?
1144       s = lstat!
1145       s and s.chardev?
1146     end
1148     def blockdev?
1149       s = lstat!
1150       s and s.blockdev?
1151     end
1153     def socket?
1154       s = lstat!
1155       s and s.socket?
1156     end
1158     def pipe?
1159       s = lstat!
1160       s and s.pipe?
1161     end
1163     S_IF_DOOR = 0xD000
1165     def door?
1166       s = lstat!
1167       s and (s.mode & 0xF000 == S_IF_DOOR)
1168     end
1170     def entries
1171       Dir.entries(path())\
1172           .reject {|n| n == '.' or n == '..' }\
1173           .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
1174     end
1176     def stat
1177       return @stat if @stat
1178       if lstat() and lstat().symlink?
1179         @stat = File.stat(path())
1180       else
1181         @stat = lstat()
1182       end
1183       @stat
1184     end
1186     def stat!
1187       return @stat if @stat
1188       if lstat! and lstat!.symlink?
1189         @stat = File.stat(path())
1190       else
1191         @stat = lstat!
1192       end
1193       @stat
1194     rescue SystemCallError
1195       nil
1196     end
1198     def lstat
1199       if dereference?
1200         @lstat ||= File.stat(path())
1201       else
1202         @lstat ||= File.lstat(path())
1203       end
1204     end
1206     def lstat!
1207       lstat()
1208     rescue SystemCallError
1209       nil
1210     end
1212     def chmod(mode)
1213       if symlink?
1214         File.lchmod mode, path() if have_lchmod?
1215       else
1216         File.chmod mode, path()
1217       end
1218     end
1220     def chown(uid, gid)
1221       if symlink?
1222         File.lchown uid, gid, path() if have_lchown?
1223       else
1224         File.chown uid, gid, path()
1225       end
1226     end
1228     def copy(dest)
1229       case
1230       when file?
1231         copy_file dest
1232       when directory?
1233         begin
1234           Dir.mkdir dest
1235         rescue
1236           raise unless File.directory?(dest)
1237         end
1238       when symlink?
1239         File.symlink File.readlink(path()), dest
1240       when chardev?
1241         raise "cannot handle device file" unless File.respond_to?(:mknod)
1242         mknod dest, ?c, 0666, lstat().rdev
1243       when blockdev?
1244         raise "cannot handle device file" unless File.respond_to?(:mknod)
1245         mknod dest, ?b, 0666, lstat().rdev
1246       when socket?
1247         raise "cannot handle socket" unless File.respond_to?(:mknod)
1248         mknod dest, nil, lstat().mode, 0
1249       when pipe?
1250         raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
1251         mkfifo dest, 0666
1252       when door?
1253         raise "cannot handle door: #{path()}"
1254       else
1255         raise "unknown file type: #{path()}"
1256       end
1257     end
1259     def copy_file(dest)
1260       st = stat()
1261       File.open(path(),  'rb') {|r|
1262         File.open(dest, 'wb', st.mode) {|w|
1263           fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
1264         }
1265       }
1266     end
1268     def copy_metadata(path)
1269       st = lstat()
1270       File.utime st.atime, st.mtime, path
1271       begin
1272         File.chown st.uid, st.gid, path
1273       rescue Errno::EPERM
1274         # clear setuid/setgid
1275         File.chmod st.mode & 01777, path
1276       else
1277         File.chmod st.mode, path
1278       end
1279     end
1281     def remove
1282       if directory?
1283         remove_dir1
1284       else
1285         remove_file
1286       end
1287     end
1289     def remove_dir1
1290       platform_support {
1291         Dir.rmdir path().sub(%r</\z>, '')
1292       }
1293     end
1295     def remove_file
1296       platform_support {
1297         File.unlink path
1298       }
1299     end
1301     def platform_support
1302       return yield unless fu_windows?
1303       first_time_p = true
1304       begin
1305         yield
1306       rescue Errno::ENOENT
1307         raise
1308       rescue => err
1309         if first_time_p
1310           first_time_p = false
1311           begin
1312             File.chmod 0700, path()   # Windows does not have symlink
1313             retry
1314           rescue SystemCallError
1315           end
1316         end
1317         raise err
1318       end
1319     end
1321     def preorder_traverse
1322       stack = [self]
1323       while ent = stack.pop
1324         yield ent
1325         stack.concat ent.entries.reverse if ent.directory?
1326       end
1327     end
1329     alias traverse preorder_traverse
1331     def postorder_traverse
1332       if directory?
1333         entries().each do |ent|
1334           ent.postorder_traverse do |e|
1335             yield e
1336           end
1337         end
1338       end
1339       yield self
1340     end
1342     private
1344     $fileutils_rb_have_lchmod = nil
1346     def have_lchmod?
1347       # This is not MT-safe, but it does not matter.
1348       if $fileutils_rb_have_lchmod.nil?
1349         $fileutils_rb_have_lchmod = check_have_lchmod?
1350       end
1351       $fileutils_rb_have_lchmod
1352     end
1354     def check_have_lchmod?
1355       return false unless File.respond_to?(:lchmod)
1356       File.lchmod 0
1357       return true
1358     rescue NotImplementedError
1359       return false
1360     end
1362     $fileutils_rb_have_lchown = nil
1364     def have_lchown?
1365       # This is not MT-safe, but it does not matter.
1366       if $fileutils_rb_have_lchown.nil?
1367         $fileutils_rb_have_lchown = check_have_lchown?
1368       end
1369       $fileutils_rb_have_lchown
1370     end
1372     def check_have_lchown?
1373       return false unless File.respond_to?(:lchown)
1374       File.lchown nil, nil
1375       return true
1376     rescue NotImplementedError
1377       return false
1378     end
1380     def join(dir, base)
1381       return dir.to_str if not base or base == '.'
1382       return base.to_str if not dir or dir == '.'
1383       File.join(dir, base)
1384     end
1385   end   # class Entry_
1387   def fu_list(arg)   #:nodoc:
1388     [arg].flatten.map {|path| path.to_str }
1389   end
1390   private_module_function :fu_list
1392   def fu_each_src_dest(src, dest)   #:nodoc:
1393     fu_each_src_dest0(src, dest) do |s, d|
1394       raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
1395       yield s, d
1396     end
1397   end
1398   private_module_function :fu_each_src_dest
1400   def fu_each_src_dest0(src, dest)   #:nodoc:
1401     if src.is_a?(Array)
1402       src.each do |s|
1403         s = s.to_str
1404         yield s, File.join(dest, File.basename(s))
1405       end
1406     else
1407       src = src.to_str
1408       if File.directory?(dest)
1409         yield src, File.join(dest, File.basename(src))
1410       else
1411         yield src, dest.to_str
1412       end
1413     end
1414   end
1415   private_module_function :fu_each_src_dest0
1417   def fu_same?(a, b)   #:nodoc:
1418     if fu_have_st_ino?
1419       st1 = File.stat(a)
1420       st2 = File.stat(b)
1421       st1.dev == st2.dev and st1.ino == st2.ino
1422     else
1423       File.expand_path(a) == File.expand_path(b)
1424     end
1425   rescue Errno::ENOENT
1426     return false
1427   end
1428   private_module_function :fu_same?
1430   def fu_have_st_ino?   #:nodoc:
1431     not fu_windows?
1432   end
1433   private_module_function :fu_have_st_ino?
1435   def fu_check_options(options, optdecl)   #:nodoc:
1436     h = options.dup
1437     optdecl.each do |opt|
1438       h.delete opt
1439     end
1440     raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
1441   end
1442   private_module_function :fu_check_options
1444   def fu_update_option(args, new)   #:nodoc:
1445     if args.last.is_a?(Hash)
1446       args[-1] = args.last.dup.update(new)
1447     else
1448       args.push new
1449     end
1450     args
1451   end
1452   private_module_function :fu_update_option
1454   @fileutils_output = $stderr
1455   @fileutils_label  = ''
1457   def fu_output_message(msg)   #:nodoc:
1458     @fileutils_output ||= $stderr
1459     @fileutils_label  ||= ''
1460     @fileutils_output.puts @fileutils_label + msg
1461   end
1462   private_module_function :fu_output_message
1464   #
1465   # Returns an Array of method names which have any options.
1466   #
1467   #   p FileUtils.commands  #=> ["chmod", "cp", "cp_r", "install", ...]
1468   #
1469   def FileUtils.commands
1470     OPT_TABLE.keys
1471   end
1473   #
1474   # Returns an Array of option names.
1475   #
1476   #   p FileUtils.options  #=> ["noop", "force", "verbose", "preserve", "mode"]
1477   #
1478   def FileUtils.options
1479     OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
1480   end
1482   #
1483   # Returns true if the method +mid+ have an option +opt+.
1484   #
1485   #   p FileUtils.have_option?(:cp, :noop)     #=> true
1486   #   p FileUtils.have_option?(:rm, :force)    #=> true
1487   #   p FileUtils.have_option?(:rm, :perserve) #=> false
1488   #
1489   def FileUtils.have_option?(mid, opt)
1490     li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
1491     li.include?(opt)
1492   end
1494   #
1495   # Returns an Array of option names of the method +mid+.
1496   #
1497   #   p FileUtils.options(:rm)  #=> ["noop", "verbose", "force"]
1498   #
1499   def FileUtils.options_of(mid)
1500     OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
1501   end
1503   #
1504   # Returns an Array of method names which have the option +opt+.
1505   #
1506   #   p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
1507   #
1508   def FileUtils.collect_method(opt)
1509     OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
1510   end
1512   METHODS = singleton_methods() - %w( private_module_function
1513       commands options have_option? options_of collect_method )
1515   # 
1516   # This module has all methods of FileUtils module, but it outputs messages
1517   # before acting.  This equates to passing the <tt>:verbose</tt> flag to
1518   # methods in FileUtils.
1519   # 
1520   module Verbose
1521     include FileUtils
1522     @fileutils_output  = $stderr
1523     @fileutils_label   = ''
1524     ::FileUtils.collect_method(:verbose).each do |name|
1525       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1526         def #{name}(*args)
1527           super(*fu_update_option(args, :verbose => true))
1528         end
1529         private :#{name}
1530       EOS
1531     end
1532     extend self
1533     class << self
1534       ::FileUtils::METHODS.each do |m|
1535         public m
1536       end
1537     end
1538   end
1540   # 
1541   # This module has all methods of FileUtils module, but never changes
1542   # files/directories.  This equates to passing the <tt>:noop</tt> flag
1543   # to methods in FileUtils.
1544   # 
1545   module NoWrite
1546     include FileUtils
1547     @fileutils_output  = $stderr
1548     @fileutils_label   = ''
1549     ::FileUtils.collect_method(:noop).each do |name|
1550       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1551         def #{name}(*args)
1552           super(*fu_update_option(args, :noop => true))
1553         end
1554         private :#{name}
1555       EOS
1556     end
1557     extend self
1558     class << self
1559       ::FileUtils::METHODS.each do |m|
1560         public m
1561       end
1562     end
1563   end
1565   # 
1566   # This module has all methods of FileUtils module, but never changes
1567   # files/directories, with printing message before acting.
1568   # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
1569   # to methods in FileUtils.
1570   # 
1571   module DryRun
1572     include FileUtils
1573     @fileutils_output  = $stderr
1574     @fileutils_label   = ''
1575     ::FileUtils.collect_method(:noop).each do |name|
1576       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1577         def #{name}(*args)
1578           super(*fu_update_option(args, :noop => true, :verbose => true))
1579         end
1580         private :#{name}
1581       EOS
1582     end
1583     extend self
1584     class << self
1585       ::FileUtils::METHODS.each do |m|
1586         public m
1587       end
1588     end
1589   end