1# frozen_string_literal: true
2
3begin
4  require 'rbconfig'
5rescue LoadError
6  # for make mjit-headers
7end
8
9require "fileutils/version"
10
11#
12# = fileutils.rb
13#
14# Copyright (c) 2000-2007 Minero Aoki
15#
16# This program is free software.
17# You can distribute/modify this program under the same terms of ruby.
18#
19# == module FileUtils
20#
21# Namespace for several file utility methods for copying, moving, removing, etc.
22#
23# === Module Functions
24#
25#   require 'fileutils'
26#
27#   FileUtils.cd(dir, options)
28#   FileUtils.cd(dir, options) {|dir| block }
29#   FileUtils.pwd()
30#   FileUtils.mkdir(dir, options)
31#   FileUtils.mkdir(list, options)
32#   FileUtils.mkdir_p(dir, options)
33#   FileUtils.mkdir_p(list, options)
34#   FileUtils.rmdir(dir, options)
35#   FileUtils.rmdir(list, options)
36#   FileUtils.ln(target, link, options)
37#   FileUtils.ln(targets, dir, options)
38#   FileUtils.ln_s(target, link, options)
39#   FileUtils.ln_s(targets, dir, options)
40#   FileUtils.ln_sf(target, link, options)
41#   FileUtils.cp(src, dest, options)
42#   FileUtils.cp(list, dir, options)
43#   FileUtils.cp_r(src, dest, options)
44#   FileUtils.cp_r(list, dir, options)
45#   FileUtils.mv(src, dest, options)
46#   FileUtils.mv(list, dir, options)
47#   FileUtils.rm(list, options)
48#   FileUtils.rm_r(list, options)
49#   FileUtils.rm_rf(list, options)
50#   FileUtils.install(src, dest, options)
51#   FileUtils.chmod(mode, list, options)
52#   FileUtils.chmod_R(mode, list, options)
53#   FileUtils.chown(user, group, list, options)
54#   FileUtils.chown_R(user, group, list, options)
55#   FileUtils.touch(list, options)
56#
57# The <tt>options</tt> parameter is a hash of options, taken from the list
58# <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
59# <tt>:noop</tt> means that no changes are made.  The other three are obvious.
60# Each method documents the options that it honours.
61#
62# All methods that have the concept of a "source" file or directory can take
63# either one file or a list of files in that argument.  See the method
64# documentation for examples.
65#
66# There are some `low level' methods, which do not accept any option:
67#
68#   FileUtils.copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
69#   FileUtils.copy_file(src, dest, preserve = false, dereference = true)
70#   FileUtils.copy_stream(srcstream, deststream)
71#   FileUtils.remove_entry(path, force = false)
72#   FileUtils.remove_entry_secure(path, force = false)
73#   FileUtils.remove_file(path, force = false)
74#   FileUtils.compare_file(path_a, path_b)
75#   FileUtils.compare_stream(stream_a, stream_b)
76#   FileUtils.uptodate?(file, cmp_list)
77#
78# == module FileUtils::Verbose
79#
80# This module has all methods of FileUtils module, but it outputs messages
81# before acting.  This equates to passing the <tt>:verbose</tt> flag to methods
82# in FileUtils.
83#
84# == module FileUtils::NoWrite
85#
86# This module has all methods of FileUtils module, but never changes
87# files/directories.  This equates to passing the <tt>:noop</tt> flag to methods
88# in FileUtils.
89#
90# == module FileUtils::DryRun
91#
92# This module has all methods of FileUtils module, but never changes
93# files/directories.  This equates to passing the <tt>:noop</tt> and
94# <tt>:verbose</tt> flags to methods in FileUtils.
95#
96module FileUtils
97
98  def self.private_module_function(name)   #:nodoc:
99    module_function name
100    private_class_method name
101  end
102
103  #
104  # Returns the name of the current directory.
105  #
106  def pwd
107    Dir.pwd
108  end
109  module_function :pwd
110
111  alias getwd pwd
112  module_function :getwd
113
114  #
115  # Changes the current directory to the directory +dir+.
116  #
117  # If this method is called with block, resumes to the old
118  # working directory after the block execution finished.
119  #
120  #   FileUtils.cd('/', :verbose => true)   # chdir and report it
121  #
122  #   FileUtils.cd('/') do  # chdir
123  #     # ...               # do something
124  #   end                   # return to original directory
125  #
126  def cd(dir, verbose: nil, &block) # :yield: dir
127    fu_output_message "cd #{dir}" if verbose
128    result = Dir.chdir(dir, &block)
129    fu_output_message 'cd -' if verbose and block
130    result
131  end
132  module_function :cd
133
134  alias chdir cd
135  module_function :chdir
136
137  #
138  # Returns true if +new+ is newer than all +old_list+.
139  # Non-existent files are older than any file.
140  #
141  #   FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
142  #       system 'make hello.o'
143  #
144  def uptodate?(new, old_list)
145    return false unless File.exist?(new)
146    new_time = File.mtime(new)
147    old_list.each do |old|
148      if File.exist?(old)
149        return false unless new_time > File.mtime(old)
150      end
151    end
152    true
153  end
154  module_function :uptodate?
155
156  def remove_trailing_slash(dir)   #:nodoc:
157    dir == '/' ? dir : dir.chomp(?/)
158  end
159  private_module_function :remove_trailing_slash
160
161  #
162  # Creates one or more directories.
163  #
164  #   FileUtils.mkdir 'test'
165  #   FileUtils.mkdir %w( tmp data )
166  #   FileUtils.mkdir 'notexist', :noop => true  # Does not really create.
167  #   FileUtils.mkdir 'tmp', :mode => 0700
168  #
169  def mkdir(list, mode: nil, noop: nil, verbose: nil)
170    list = fu_list(list)
171    fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
172    return if noop
173
174    list.each do |dir|
175      fu_mkdir dir, mode
176    end
177  end
178  module_function :mkdir
179
180  #
181  # Creates a directory and all its parent directories.
182  # For example,
183  #
184  #   FileUtils.mkdir_p '/usr/local/lib/ruby'
185  #
186  # causes to make following directories, if it does not exist.
187  #
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, mode: nil, noop: nil, verbose: nil)
196    list = fu_list(list)
197    fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
198    return *list if noop
199
200    list.map {|path| remove_trailing_slash(path)}.each do |path|
201      # optimize for the most common case
202      begin
203        fu_mkdir path, mode
204        next
205      rescue SystemCallError
206        next if File.directory?(path)
207      end
208
209      stack = []
210      until path == stack.last   # dirname("/")=="/", dirname("C:/")=="C:/"
211        stack.push path
212        path = File.dirname(path)
213      end
214      stack.pop                 # root directory should exist
215      stack.reverse_each do |dir|
216        begin
217          fu_mkdir dir, mode
218        rescue SystemCallError
219          raise unless File.directory?(dir)
220        end
221      end
222    end
223
224    return *list
225  end
226  module_function :mkdir_p
227
228  alias mkpath    mkdir_p
229  alias makedirs  mkdir_p
230  module_function :mkpath
231  module_function :makedirs
232
233  def fu_mkdir(path, mode)   #:nodoc:
234    path = remove_trailing_slash(path)
235    if mode
236      Dir.mkdir path, mode
237      File.chmod mode, path
238    else
239      Dir.mkdir path
240    end
241  end
242  private_module_function :fu_mkdir
243
244  #
245  # Removes one or more directories.
246  #
247  #   FileUtils.rmdir 'somedir'
248  #   FileUtils.rmdir %w(somedir anydir otherdir)
249  #   # Does not really remove directory; outputs message.
250  #   FileUtils.rmdir 'somedir', :verbose => true, :noop => true
251  #
252  def rmdir(list, parents: nil, noop: nil, verbose: nil)
253    list = fu_list(list)
254    fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
255    return if noop
256    list.each do |dir|
257      Dir.rmdir(dir = remove_trailing_slash(dir))
258      if parents
259        begin
260          until (parent = File.dirname(dir)) == '.' or parent == dir
261            dir = parent
262            Dir.rmdir(dir)
263          end
264        rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
265        end
266      end
267    end
268  end
269  module_function :rmdir
270
271  #
272  # :call-seq:
273  #   FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil)
274  #   FileUtils.ln(target,  dir, force: nil, noop: nil, verbose: nil)
275  #   FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil)
276  #
277  # In the first form, creates a hard link +link+ which points to +target+.
278  # If +link+ already exists, raises Errno::EEXIST.
279  # But if the :force option is set, overwrites +link+.
280  #
281  #   FileUtils.ln 'gcc', 'cc', verbose: true
282  #   FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
283  #
284  # In the second form, creates a link +dir/target+ pointing to +target+.
285  # In the third form, creates several hard links in the directory +dir+,
286  # pointing to each item in +targets+.
287  # If +dir+ is not a directory, raises Errno::ENOTDIR.
288  #
289  #   FileUtils.cd '/sbin'
290  #   FileUtils.ln %w(cp mv mkdir), '/bin'   # Now /sbin/cp and /bin/cp are linked.
291  #
292  def ln(src, dest, force: nil, noop: nil, verbose: nil)
293    fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
294    return if noop
295    fu_each_src_dest0(src, dest) do |s,d|
296      remove_file d, true if force
297      File.link s, d
298    end
299  end
300  module_function :ln
301
302  alias link ln
303  module_function :link
304
305  #
306  # :call-seq:
307  #   FileUtils.cp_lr(src, dest, noop: nil, verbose: nil, dereference_root: true, remove_destination: false)
308  #
309  # Hard link +src+ to +dest+. If +src+ is a directory, this method links
310  # all its contents recursively. If +dest+ is a directory, links
311  # +src+ to +dest/src+.
312  #
313  # +src+ can be a list of files.
314  #
315  #   # Installing the library "mylib" under the site_ruby directory.
316  #   FileUtils.rm_r site_ruby + '/mylib', :force => true
317  #   FileUtils.cp_lr 'lib/', site_ruby + '/mylib'
318  #
319  #   # Examples of linking several files to target directory.
320  #   FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail'
321  #   FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true
322  #
323  #   # If you want to link all contents of a directory instead of the
324  #   # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
325  #   # use the following code.
326  #   FileUtils.cp_lr 'src/.', 'dest'  # cp_lr('src', 'dest') makes dest/src, but this doesn't.
327  #
328  def cp_lr(src, dest, noop: nil, verbose: nil,
329            dereference_root: true, remove_destination: false)
330    fu_output_message "cp -lr#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
331    return if noop
332    fu_each_src_dest(src, dest) do |s, d|
333      link_entry s, d, dereference_root, remove_destination
334    end
335  end
336  module_function :cp_lr
337
338  #
339  # :call-seq:
340  #   FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
341  #   FileUtils.ln_s(target,  dir, force: nil, noop: nil, verbose: nil)
342  #   FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
343  #
344  # In the first form, creates a symbolic link +link+ which points to +target+.
345  # If +link+ already exists, raises Errno::EEXIST.
346  # But if the :force option is set, overwrites +link+.
347  #
348  #   FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
349  #   FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
350  #
351  # In the second form, creates a link +dir/target+ pointing to +target+.
352  # In the third form, creates several symbolic links in the directory +dir+,
353  # pointing to each item in +targets+.
354  # If +dir+ is not a directory, raises Errno::ENOTDIR.
355  #
356  #   FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
357  #
358  def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
359    fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
360    return if noop
361    fu_each_src_dest0(src, dest) do |s,d|
362      remove_file d, true if force
363      File.symlink s, d
364    end
365  end
366  module_function :ln_s
367
368  alias symlink ln_s
369  module_function :symlink
370
371  #
372  # :call-seq:
373  #   FileUtils.ln_sf(*args)
374  #
375  # Same as
376  #
377  #   FileUtils.ln_s(*args, force: true)
378  #
379  def ln_sf(src, dest, noop: nil, verbose: nil)
380    ln_s src, dest, force: true, noop: noop, verbose: verbose
381  end
382  module_function :ln_sf
383
384  #
385  # Hard links a file system entry +src+ to +dest+.
386  # If +src+ is a directory, this method links its contents recursively.
387  #
388  # Both of +src+ and +dest+ must be a path name.
389  # +src+ must exist, +dest+ must not exist.
390  #
391  # If +dereference_root+ is true, this method dereferences the tree root.
392  #
393  # If +remove_destination+ is true, this method removes each destination file before copy.
394  #
395  def link_entry(src, dest, dereference_root = false, remove_destination = false)
396    Entry_.new(src, nil, dereference_root).traverse do |ent|
397      destent = Entry_.new(dest, ent.rel, false)
398      File.unlink destent.path if remove_destination && File.file?(destent.path)
399      ent.link destent.path
400    end
401  end
402  module_function :link_entry
403
404  #
405  # Copies a file content +src+ to +dest+.  If +dest+ is a directory,
406  # copies +src+ to +dest/src+.
407  #
408  # If +src+ is a list of files, then +dest+ must be a directory.
409  #
410  #   FileUtils.cp 'eval.c', 'eval.c.org'
411  #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
412  #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
413  #   FileUtils.cp 'symlink', 'dest'   # copy content, "dest" is not a symlink
414  #
415  def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
416    fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
417    return if noop
418    fu_each_src_dest(src, dest) do |s, d|
419      copy_file s, d, preserve
420    end
421  end
422  module_function :cp
423
424  alias copy cp
425  module_function :copy
426
427  #
428  # Copies +src+ to +dest+. If +src+ is a directory, this method copies
429  # all its contents recursively. If +dest+ is a directory, copies
430  # +src+ to +dest/src+.
431  #
432  # +src+ can be a list of files.
433  #
434  #   # Installing Ruby library "mylib" under the site_ruby
435  #   FileUtils.rm_r site_ruby + '/mylib', :force
436  #   FileUtils.cp_r 'lib/', site_ruby + '/mylib'
437  #
438  #   # Examples of copying several files to target directory.
439  #   FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
440  #   FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', :noop => true, :verbose => true
441  #
442  #   # If you want to copy all contents of a directory instead of the
443  #   # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
444  #   # use following code.
445  #   FileUtils.cp_r 'src/.', 'dest'     # cp_r('src', 'dest') makes dest/src,
446  #                                      # but this doesn't.
447  #
448  def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
449           dereference_root: true, remove_destination: nil)
450    fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
451    return if noop
452    fu_each_src_dest(src, dest) do |s, d|
453      copy_entry s, d, preserve, dereference_root, remove_destination
454    end
455  end
456  module_function :cp_r
457
458  #
459  # Copies a file system entry +src+ to +dest+.
460  # If +src+ is a directory, this method copies its contents recursively.
461  # This method preserves file types, c.f. symlink, directory...
462  # (FIFO, device files and etc. are not supported yet)
463  #
464  # Both of +src+ and +dest+ must be a path name.
465  # +src+ must exist, +dest+ must not exist.
466  #
467  # If +preserve+ is true, this method preserves owner, group, and
468  # modified time.  Permissions are copied regardless +preserve+.
469  #
470  # If +dereference_root+ is true, this method dereference tree root.
471  #
472  # If +remove_destination+ is true, this method removes each destination file before copy.
473  #
474  def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
475    Entry_.new(src, nil, dereference_root).wrap_traverse(proc do |ent|
476      destent = Entry_.new(dest, ent.rel, false)
477      File.unlink destent.path if remove_destination && (File.file?(destent.path) || File.symlink?(destent.path))
478      ent.copy destent.path
479    end, proc do |ent|
480      destent = Entry_.new(dest, ent.rel, false)
481      ent.copy_metadata destent.path if preserve
482    end)
483  end
484  module_function :copy_entry
485
486  #
487  # Copies file contents of +src+ to +dest+.
488  # Both of +src+ and +dest+ must be a path name.
489  #
490  def copy_file(src, dest, preserve = false, dereference = true)
491    ent = Entry_.new(src, nil, dereference)
492    ent.copy_file dest
493    ent.copy_metadata dest if preserve
494  end
495  module_function :copy_file
496
497  #
498  # Copies stream +src+ to +dest+.
499  # +src+ must respond to #read(n) and
500  # +dest+ must respond to #write(str).
501  #
502  def copy_stream(src, dest)
503    IO.copy_stream(src, dest)
504  end
505  module_function :copy_stream
506
507  #
508  # Moves file(s) +src+ to +dest+.  If +file+ and +dest+ exist on the different
509  # disk partition, the file is copied then the original file is removed.
510  #
511  #   FileUtils.mv 'badname.rb', 'goodname.rb'
512  #   FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true  # no error
513  #
514  #   FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
515  #   FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
516  #
517  def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
518    fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
519    return if noop
520    fu_each_src_dest(src, dest) do |s, d|
521      destent = Entry_.new(d, nil, true)
522      begin
523        if destent.exist?
524          if destent.directory?
525            raise Errno::EEXIST, d
526          end
527        end
528        begin
529          File.rename s, d
530        rescue Errno::EXDEV
531          copy_entry s, d, true
532          if secure
533            remove_entry_secure s, force
534          else
535            remove_entry s, force
536          end
537        end
538      rescue SystemCallError
539        raise unless force
540      end
541    end
542  end
543  module_function :mv
544
545  alias move mv
546  module_function :move
547
548  #
549  # Remove file(s) specified in +list+.  This method cannot remove directories.
550  # All StandardErrors are ignored when the :force option is set.
551  #
552  #   FileUtils.rm %w( junk.txt dust.txt )
553  #   FileUtils.rm Dir.glob('*.so')
554  #   FileUtils.rm 'NotExistFile', :force => true   # never raises exception
555  #
556  def rm(list, force: nil, noop: nil, verbose: nil)
557    list = fu_list(list)
558    fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
559    return if noop
560
561    list.each do |path|
562      remove_file path, force
563    end
564  end
565  module_function :rm
566
567  alias remove rm
568  module_function :remove
569
570  #
571  # Equivalent to
572  #
573  #   FileUtils.rm(list, :force => true)
574  #
575  def rm_f(list, noop: nil, verbose: nil)
576    rm list, force: true, noop: noop, verbose: verbose
577  end
578  module_function :rm_f
579
580  alias safe_unlink rm_f
581  module_function :safe_unlink
582
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 'some_dir', :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, force: nil, noop: nil, verbose: nil, secure: nil)
603    list = fu_list(list)
604    fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
605    return if noop
606    list.each do |path|
607      if secure
608        remove_entry_secure path, force
609      else
610        remove_entry path, force
611      end
612    end
613  end
614  module_function :rm_r
615
616  #
617  # Equivalent to
618  #
619  #   FileUtils.rm_r(list, :force => true)
620  #
621  # WARNING: This method causes local vulnerability.
622  # Read the documentation of #rm_r first.
623  #
624  def rm_rf(list, noop: nil, verbose: nil, secure: nil)
625    rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
626  end
627  module_function :rm_rf
628
629  alias rmtree rm_rf
630  module_function :rmtree
631
632  #
633  # This method removes a file system entry +path+.  +path+ shall be a
634  # regular file, a directory, or something.  If +path+ is a directory,
635  # remove it recursively.  This method is required to avoid TOCTTOU
636  # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
637  # #rm_r causes security hole when:
638  #
639  # * Parent directory is world writable (including /tmp).
640  # * Removing directory tree includes world writable directory.
641  # * The system has symbolic link.
642  #
643  # To avoid this security hole, this method applies special preprocess.
644  # If +path+ is a directory, this method chown(2) and chmod(2) all
645  # removing directories.  This requires the current process is the
646  # owner of the removing whole directory tree, or is the super user (root).
647  #
648  # WARNING: You must ensure that *ALL* parent directories cannot be
649  # moved by other untrusted users.  For example, parent directories
650  # should not be owned by untrusted users, and should not be world
651  # writable except when the sticky bit set.
652  #
653  # WARNING: Only the owner of the removing directory tree, or Unix super
654  # user (root) should invoke this method.  Otherwise this method does not
655  # work.
656  #
657  # For details of this security vulnerability, see Perl's case:
658  #
659  # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
660  # * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
661  #
662  # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
663  #
664  def remove_entry_secure(path, force = false)
665    unless fu_have_symlink?
666      remove_entry path, force
667      return
668    end
669    fullpath = File.expand_path(path)
670    st = File.lstat(fullpath)
671    unless st.directory?
672      File.unlink fullpath
673      return
674    end
675    # is a directory.
676    parent_st = File.stat(File.dirname(fullpath))
677    unless parent_st.world_writable?
678      remove_entry path, force
679      return
680    end
681    unless parent_st.sticky?
682      raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
683    end
684
685    # freeze tree root
686    euid = Process.euid
687    dot_file = fullpath + "/."
688    begin
689      File.open(dot_file) {|f|
690        unless fu_stat_identical_entry?(st, f.stat)
691          # symlink (TOC-to-TOU attack?)
692          File.unlink fullpath
693          return
694        end
695        f.chown euid, -1
696        f.chmod 0700
697      }
698    rescue Errno::EISDIR # JRuby in non-native mode can't open files as dirs
699      File.lstat(dot_file).tap {|fstat|
700        unless fu_stat_identical_entry?(st, fstat)
701          # symlink (TOC-to-TOU attack?)
702          File.unlink fullpath
703          return
704        end
705        File.chown euid, -1, dot_file
706        File.chmod 0700, dot_file
707      }
708    end
709
710    unless fu_stat_identical_entry?(st, File.lstat(fullpath))
711      # TOC-to-TOU attack?
712      File.unlink fullpath
713      return
714    end
715
716    # ---- tree root is frozen ----
717    root = Entry_.new(path)
718    root.preorder_traverse do |ent|
719      if ent.directory?
720        ent.chown euid, -1
721        ent.chmod 0700
722      end
723    end
724    root.postorder_traverse do |ent|
725      begin
726        ent.remove
727      rescue
728        raise unless force
729      end
730    end
731  rescue
732    raise unless force
733  end
734  module_function :remove_entry_secure
735
736  def fu_have_symlink?   #:nodoc:
737    File.symlink nil, nil
738  rescue NotImplementedError
739    return false
740  rescue TypeError
741    return true
742  end
743  private_module_function :fu_have_symlink?
744
745  def fu_stat_identical_entry?(a, b)   #:nodoc:
746    a.dev == b.dev and a.ino == b.ino
747  end
748  private_module_function :fu_stat_identical_entry?
749
750  #
751  # This method removes a file system entry +path+.
752  # +path+ might be a regular file, a directory, or something.
753  # If +path+ is a directory, remove it recursively.
754  #
755  # See also #remove_entry_secure.
756  #
757  def remove_entry(path, force = false)
758    Entry_.new(path).postorder_traverse do |ent|
759      begin
760        ent.remove
761      rescue
762        raise unless force
763      end
764    end
765  rescue
766    raise unless force
767  end
768  module_function :remove_entry
769
770  #
771  # Removes a file +path+.
772  # This method ignores StandardError if +force+ is true.
773  #
774  def remove_file(path, force = false)
775    Entry_.new(path).remove_file
776  rescue
777    raise unless force
778  end
779  module_function :remove_file
780
781  #
782  # Removes a directory +dir+ and its contents recursively.
783  # This method ignores StandardError if +force+ is true.
784  #
785  def remove_dir(path, force = false)
786    remove_entry path, force   # FIXME?? check if it is a directory
787  end
788  module_function :remove_dir
789
790  #
791  # Returns true if the contents of a file +a+ and a file +b+ are identical.
792  #
793  #   FileUtils.compare_file('somefile', 'somefile')       #=> true
794  #   FileUtils.compare_file('/dev/null', '/dev/urandom')  #=> false
795  #
796  def compare_file(a, b)
797    return false unless File.size(a) == File.size(b)
798    File.open(a, 'rb') {|fa|
799      File.open(b, 'rb') {|fb|
800        return compare_stream(fa, fb)
801      }
802    }
803  end
804  module_function :compare_file
805
806  alias identical? compare_file
807  alias cmp compare_file
808  module_function :identical?
809  module_function :cmp
810
811  #
812  # Returns true if the contents of a stream +a+ and +b+ are identical.
813  #
814  def compare_stream(a, b)
815    bsize = fu_stream_blksize(a, b)
816
817    if RUBY_VERSION > "2.4"
818      sa = String.new(capacity: bsize)
819      sb = String.new(capacity: bsize)
820    else
821      sa = String.new
822      sb = String.new
823    end
824
825    begin
826      a.read(bsize, sa)
827      b.read(bsize, sb)
828      return true if sa.empty? && sb.empty?
829    end while sa == sb
830    false
831  end
832  module_function :compare_stream
833
834  #
835  # If +src+ is not same as +dest+, copies it and changes the permission
836  # mode to +mode+.  If +dest+ is a directory, destination is +dest+/+src+.
837  # This method removes destination before copy.
838  #
839  #   FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
840  #   FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
841  #
842  def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
843              noop: nil, verbose: nil)
844    if verbose
845      msg = +"install -c"
846      msg << ' -p' if preserve
847      msg << ' -m ' << mode_to_s(mode) if mode
848      msg << " -o #{owner}" if owner
849      msg << " -g #{group}" if group
850      msg << ' ' << [src,dest].flatten.join(' ')
851      fu_output_message msg
852    end
853    return if noop
854    uid = fu_get_uid(owner)
855    gid = fu_get_gid(group)
856    fu_each_src_dest(src, dest) do |s, d|
857      st = File.stat(s)
858      unless File.exist?(d) and compare_file(s, d)
859        remove_file d, true
860        copy_file s, d
861        File.utime st.atime, st.mtime, d if preserve
862        File.chmod fu_mode(mode, st), d if mode
863        File.chown uid, gid, d if uid or gid
864      end
865    end
866  end
867  module_function :install
868
869  def user_mask(target)  #:nodoc:
870    target.each_char.inject(0) do |mask, chr|
871      case chr
872      when "u"
873        mask | 04700
874      when "g"
875        mask | 02070
876      when "o"
877        mask | 01007
878      when "a"
879        mask | 07777
880      else
881        raise ArgumentError, "invalid `who' symbol in file mode: #{chr}"
882      end
883    end
884  end
885  private_module_function :user_mask
886
887  def apply_mask(mode, user_mask, op, mode_mask)   #:nodoc:
888    case op
889    when '='
890      (mode & ~user_mask) | (user_mask & mode_mask)
891    when '+'
892      mode | (user_mask & mode_mask)
893    when '-'
894      mode & ~(user_mask & mode_mask)
895    end
896  end
897  private_module_function :apply_mask
898
899  def symbolic_modes_to_i(mode_sym, path)  #:nodoc:
900    mode = if File::Stat === path
901             path.mode
902           else
903             File.stat(path).mode
904           end
905    mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause|
906      target, *actions = clause.split(/([=+-])/)
907      raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty?
908      target = 'a' if target.empty?
909      user_mask = user_mask(target)
910      actions.each_slice(2) do |op, perm|
911        need_apply = op == '='
912        mode_mask = (perm || '').each_char.inject(0) do |mask, chr|
913          case chr
914          when "r"
915            mask | 0444
916          when "w"
917            mask | 0222
918          when "x"
919            mask | 0111
920          when "X"
921            if FileTest.directory? path
922              mask | 0111
923            else
924              mask
925            end
926          when "s"
927            mask | 06000
928          when "t"
929            mask | 01000
930          when "u", "g", "o"
931            if mask.nonzero?
932              current_mode = apply_mask(current_mode, user_mask, op, mask)
933            end
934            need_apply = false
935            copy_mask = user_mask(chr)
936            (current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111)
937          else
938            raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}"
939          end
940        end
941
942        if mode_mask.nonzero? || need_apply
943          current_mode = apply_mask(current_mode, user_mask, op, mode_mask)
944        end
945      end
946      current_mode
947    end
948  end
949  private_module_function :symbolic_modes_to_i
950
951  def fu_mode(mode, path)  #:nodoc:
952    mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode
953  end
954  private_module_function :fu_mode
955
956  def mode_to_s(mode)  #:nodoc:
957    mode.is_a?(String) ? mode : "%o" % mode
958  end
959  private_module_function :mode_to_s
960
961  #
962  # Changes permission bits on the named files (in +list+) to the bit pattern
963  # represented by +mode+.
964  #
965  # +mode+ is the symbolic and absolute mode can be used.
966  #
967  # Absolute mode is
968  #   FileUtils.chmod 0755, 'somecommand'
969  #   FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
970  #   FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
971  #
972  # Symbolic mode is
973  #   FileUtils.chmod "u=wrx,go=rx", 'somecommand'
974  #   FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb)
975  #   FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', :verbose => true
976  #
977  # "a" :: is user, group, other mask.
978  # "u" :: is user's mask.
979  # "g" :: is group's mask.
980  # "o" :: is other's mask.
981  # "w" :: is write permission.
982  # "r" :: is read permission.
983  # "x" :: is execute permission.
984  # "X" ::
985  #   is execute permission for directories only, must be used in conjunction with "+"
986  # "s" :: is uid, gid.
987  # "t" :: is sticky bit.
988  # "+" :: is added to a class given the specified mode.
989  # "-" :: Is removed from a given class given mode.
990  # "=" :: Is the exact nature of the class will be given a specified mode.
991
992  def chmod(mode, list, noop: nil, verbose: nil)
993    list = fu_list(list)
994    fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
995    return if noop
996    list.each do |path|
997      Entry_.new(path).chmod(fu_mode(mode, path))
998    end
999  end
1000  module_function :chmod
1001
1002  #
1003  # Changes permission bits on the named files (in +list+)
1004  # to the bit pattern represented by +mode+.
1005  #
1006  #   FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
1007  #   FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
1008  #
1009  def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
1010    list = fu_list(list)
1011    fu_output_message sprintf('chmod -R%s %s %s',
1012                              (force ? 'f' : ''),
1013                              mode_to_s(mode), list.join(' ')) if verbose
1014    return if noop
1015    list.each do |root|
1016      Entry_.new(root).traverse do |ent|
1017        begin
1018          ent.chmod(fu_mode(mode, ent.path))
1019        rescue
1020          raise unless force
1021        end
1022      end
1023    end
1024  end
1025  module_function :chmod_R
1026
1027  #
1028  # Changes owner and group on the named files (in +list+)
1029  # to the user +user+ and the group +group+.  +user+ and +group+
1030  # may be an ID (Integer/String) or a name (String).
1031  # If +user+ or +group+ is nil, this method does not change
1032  # the attribute.
1033  #
1034  #   FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
1035  #   FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
1036  #
1037  def chown(user, group, list, noop: nil, verbose: nil)
1038    list = fu_list(list)
1039    fu_output_message sprintf('chown %s %s',
1040                              (group ? "#{user}:#{group}" : user || ':'),
1041                              list.join(' ')) if verbose
1042    return if noop
1043    uid = fu_get_uid(user)
1044    gid = fu_get_gid(group)
1045    list.each do |path|
1046      Entry_.new(path).chown uid, gid
1047    end
1048  end
1049  module_function :chown
1050
1051  #
1052  # Changes owner and group on the named files (in +list+)
1053  # to the user +user+ and the group +group+ recursively.
1054  # +user+ and +group+ may be an ID (Integer/String) or
1055  # a name (String).  If +user+ or +group+ is nil, this
1056  # method does not change the attribute.
1057  #
1058  #   FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
1059  #   FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
1060  #
1061  def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
1062    list = fu_list(list)
1063    fu_output_message sprintf('chown -R%s %s %s',
1064                              (force ? 'f' : ''),
1065                              (group ? "#{user}:#{group}" : user || ':'),
1066                              list.join(' ')) if verbose
1067    return if noop
1068    uid = fu_get_uid(user)
1069    gid = fu_get_gid(group)
1070    list.each do |root|
1071      Entry_.new(root).traverse do |ent|
1072        begin
1073          ent.chown uid, gid
1074        rescue
1075          raise unless force
1076        end
1077      end
1078    end
1079  end
1080  module_function :chown_R
1081
1082  begin
1083    require 'etc'
1084  rescue LoadError # rescue LoadError for miniruby
1085  end
1086
1087  def fu_get_uid(user)   #:nodoc:
1088    return nil unless user
1089    case user
1090    when Integer
1091      user
1092    when /\A\d+\z/
1093      user.to_i
1094    else
1095      Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil
1096    end
1097  end
1098  private_module_function :fu_get_uid
1099
1100  def fu_get_gid(group)   #:nodoc:
1101    return nil unless group
1102    case group
1103    when Integer
1104      group
1105    when /\A\d+\z/
1106      group.to_i
1107    else
1108      Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil
1109    end
1110  end
1111  private_module_function :fu_get_gid
1112
1113  #
1114  # Updates modification time (mtime) and access time (atime) of file(s) in
1115  # +list+.  Files are created if they don't exist.
1116  #
1117  #   FileUtils.touch 'timestamp'
1118  #   FileUtils.touch Dir.glob('*.c');  system 'make'
1119  #
1120  def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
1121    list = fu_list(list)
1122    t = mtime
1123    if verbose
1124      fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
1125    end
1126    return if noop
1127    list.each do |path|
1128      created = nocreate
1129      begin
1130        File.utime(t, t, path)
1131      rescue Errno::ENOENT
1132        raise if created
1133        File.open(path, 'a') {
1134          ;
1135        }
1136        created = true
1137        retry if t
1138      end
1139    end
1140  end
1141  module_function :touch
1142
1143  private
1144
1145  module StreamUtils_
1146    private
1147
1148    case (defined?(::RbConfig) ? ::RbConfig::CONFIG['host_os'] : ::RUBY_PLATFORM)
1149    when /mswin|mingw/
1150      def fu_windows?; true end
1151    else
1152      def fu_windows?; false end
1153    end
1154
1155    def fu_copy_stream0(src, dest, blksize = nil)   #:nodoc:
1156      IO.copy_stream(src, dest)
1157    end
1158
1159    def fu_stream_blksize(*streams)
1160      streams.each do |s|
1161        next unless s.respond_to?(:stat)
1162        size = fu_blksize(s.stat)
1163        return size if size
1164      end
1165      fu_default_blksize()
1166    end
1167
1168    def fu_blksize(st)
1169      s = st.blksize
1170      return nil unless s
1171      return nil if s == 0
1172      s
1173    end
1174
1175    def fu_default_blksize
1176      1024
1177    end
1178  end
1179
1180  include StreamUtils_
1181  extend StreamUtils_
1182
1183  class Entry_   #:nodoc: internal use only
1184    include StreamUtils_
1185
1186    def initialize(a, b = nil, deref = false)
1187      @prefix = @rel = @path = nil
1188      if b
1189        @prefix = a
1190        @rel = b
1191      else
1192        @path = a
1193      end
1194      @deref = deref
1195      @stat = nil
1196      @lstat = nil
1197    end
1198
1199    def inspect
1200      "\#<#{self.class} #{path()}>"
1201    end
1202
1203    def path
1204      if @path
1205        File.path(@path)
1206      else
1207        join(@prefix, @rel)
1208      end
1209    end
1210
1211    def prefix
1212      @prefix || @path
1213    end
1214
1215    def rel
1216      @rel
1217    end
1218
1219    def dereference?
1220      @deref
1221    end
1222
1223    def exist?
1224      begin
1225        lstat
1226        true
1227      rescue Errno::ENOENT
1228        false
1229      end
1230    end
1231
1232    def file?
1233      s = lstat!
1234      s and s.file?
1235    end
1236
1237    def directory?
1238      s = lstat!
1239      s and s.directory?
1240    end
1241
1242    def symlink?
1243      s = lstat!
1244      s and s.symlink?
1245    end
1246
1247    def chardev?
1248      s = lstat!
1249      s and s.chardev?
1250    end
1251
1252    def blockdev?
1253      s = lstat!
1254      s and s.blockdev?
1255    end
1256
1257    def socket?
1258      s = lstat!
1259      s and s.socket?
1260    end
1261
1262    def pipe?
1263      s = lstat!
1264      s and s.pipe?
1265    end
1266
1267    S_IF_DOOR = 0xD000
1268
1269    def door?
1270      s = lstat!
1271      s and (s.mode & 0xF000 == S_IF_DOOR)
1272    end
1273
1274    def entries
1275      opts = {}
1276      opts[:encoding] = ::Encoding::UTF_8 if fu_windows?
1277      Dir.children(path, opts)\
1278          .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
1279    end
1280
1281    def stat
1282      return @stat if @stat
1283      if lstat() and lstat().symlink?
1284        @stat = File.stat(path())
1285      else
1286        @stat = lstat()
1287      end
1288      @stat
1289    end
1290
1291    def stat!
1292      return @stat if @stat
1293      if lstat! and lstat!.symlink?
1294        @stat = File.stat(path())
1295      else
1296        @stat = lstat!
1297      end
1298      @stat
1299    rescue SystemCallError
1300      nil
1301    end
1302
1303    def lstat
1304      if dereference?
1305        @lstat ||= File.stat(path())
1306      else
1307        @lstat ||= File.lstat(path())
1308      end
1309    end
1310
1311    def lstat!
1312      lstat()
1313    rescue SystemCallError
1314      nil
1315    end
1316
1317    def chmod(mode)
1318      if symlink?
1319        File.lchmod mode, path() if have_lchmod?
1320      else
1321        File.chmod mode, path()
1322      end
1323    rescue Errno::EOPNOTSUPP
1324    end
1325
1326    def chown(uid, gid)
1327      if symlink?
1328        File.lchown uid, gid, path() if have_lchown?
1329      else
1330        File.chown uid, gid, path()
1331      end
1332    end
1333
1334    def link(dest)
1335      case
1336      when directory?
1337        if !File.exist?(dest) and descendant_directory?(dest, path)
1338          raise ArgumentError, "cannot link directory %s to itself %s" % [path, dest]
1339        end
1340        begin
1341          Dir.mkdir dest
1342        rescue
1343          raise unless File.directory?(dest)
1344        end
1345      else
1346        File.link path(), dest
1347      end
1348    end
1349
1350    def copy(dest)
1351      lstat
1352      case
1353      when file?
1354        copy_file dest
1355      when directory?
1356        if !File.exist?(dest) and descendant_directory?(dest, path)
1357          raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
1358        end
1359        begin
1360          Dir.mkdir dest
1361        rescue
1362          raise unless File.directory?(dest)
1363        end
1364      when symlink?
1365        File.symlink File.readlink(path()), dest
1366      when chardev?
1367        raise "cannot handle device file" unless File.respond_to?(:mknod)
1368        mknod dest, ?c, 0666, lstat().rdev
1369      when blockdev?
1370        raise "cannot handle device file" unless File.respond_to?(:mknod)
1371        mknod dest, ?b, 0666, lstat().rdev
1372      when socket?
1373        raise "cannot handle socket" unless File.respond_to?(:mknod)
1374        mknod dest, nil, lstat().mode, 0
1375      when pipe?
1376        raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
1377        mkfifo dest, 0666
1378      when door?
1379        raise "cannot handle door: #{path()}"
1380      else
1381        raise "unknown file type: #{path()}"
1382      end
1383    end
1384
1385    def copy_file(dest)
1386      File.open(path()) do |s|
1387        File.open(dest, 'wb', s.stat.mode) do |f|
1388          IO.copy_stream(s, f)
1389        end
1390      end
1391    end
1392
1393    def copy_metadata(path)
1394      st = lstat()
1395      if !st.symlink?
1396        File.utime st.atime, st.mtime, path
1397      end
1398      mode = st.mode
1399      begin
1400        if st.symlink?
1401          begin
1402            File.lchown st.uid, st.gid, path
1403          rescue NotImplementedError
1404          end
1405        else
1406          File.chown st.uid, st.gid, path
1407        end
1408      rescue Errno::EPERM, Errno::EACCES
1409        # clear setuid/setgid
1410        mode &= 01777
1411      end
1412      if st.symlink?
1413        begin
1414          File.lchmod mode, path
1415        rescue NotImplementedError, Errno::EOPNOTSUPP
1416        end
1417      else
1418        File.chmod mode, path
1419      end
1420    end
1421
1422    def remove
1423      if directory?
1424        remove_dir1
1425      else
1426        remove_file
1427      end
1428    end
1429
1430    def remove_dir1
1431      platform_support {
1432        Dir.rmdir path().chomp(?/)
1433      }
1434    end
1435
1436    def remove_file
1437      platform_support {
1438        File.unlink path
1439      }
1440    end
1441
1442    def platform_support
1443      return yield unless fu_windows?
1444      first_time_p = true
1445      begin
1446        yield
1447      rescue Errno::ENOENT
1448        raise
1449      rescue => err
1450        if first_time_p
1451          first_time_p = false
1452          begin
1453            File.chmod 0700, path()   # Windows does not have symlink
1454            retry
1455          rescue SystemCallError
1456          end
1457        end
1458        raise err
1459      end
1460    end
1461
1462    def preorder_traverse
1463      stack = [self]
1464      while ent = stack.pop
1465        yield ent
1466        stack.concat ent.entries.reverse if ent.directory?
1467      end
1468    end
1469
1470    alias traverse preorder_traverse
1471
1472    def postorder_traverse
1473      if directory?
1474        entries().each do |ent|
1475          ent.postorder_traverse do |e|
1476            yield e
1477          end
1478        end
1479      end
1480    ensure
1481      yield self
1482    end
1483
1484    def wrap_traverse(pre, post)
1485      pre.call self
1486      if directory?
1487        entries.each do |ent|
1488          ent.wrap_traverse pre, post
1489        end
1490      end
1491      post.call self
1492    end
1493
1494    private
1495
1496    $fileutils_rb_have_lchmod = nil
1497
1498    def have_lchmod?
1499      # This is not MT-safe, but it does not matter.
1500      if $fileutils_rb_have_lchmod == nil
1501        $fileutils_rb_have_lchmod = check_have_lchmod?
1502      end
1503      $fileutils_rb_have_lchmod
1504    end
1505
1506    def check_have_lchmod?
1507      return false unless File.respond_to?(:lchmod)
1508      File.lchmod 0
1509      return true
1510    rescue NotImplementedError
1511      return false
1512    end
1513
1514    $fileutils_rb_have_lchown = nil
1515
1516    def have_lchown?
1517      # This is not MT-safe, but it does not matter.
1518      if $fileutils_rb_have_lchown == nil
1519        $fileutils_rb_have_lchown = check_have_lchown?
1520      end
1521      $fileutils_rb_have_lchown
1522    end
1523
1524    def check_have_lchown?
1525      return false unless File.respond_to?(:lchown)
1526      File.lchown nil, nil
1527      return true
1528    rescue NotImplementedError
1529      return false
1530    end
1531
1532    def join(dir, base)
1533      return File.path(dir) if not base or base == '.'
1534      return File.path(base) if not dir or dir == '.'
1535      File.join(dir, base)
1536    end
1537
1538    if File::ALT_SEPARATOR
1539      DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)"
1540    else
1541      DIRECTORY_TERM = "(?=/|\\z)"
1542    end
1543    SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : ""
1544
1545    def descendant_directory?(descendant, ascendant)
1546      /\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant)
1547    end
1548  end   # class Entry_
1549
1550  def fu_list(arg)   #:nodoc:
1551    [arg].flatten.map {|path| File.path(path) }
1552  end
1553  private_module_function :fu_list
1554
1555  def fu_each_src_dest(src, dest)   #:nodoc:
1556    fu_each_src_dest0(src, dest) do |s, d|
1557      raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
1558      yield s, d
1559    end
1560  end
1561  private_module_function :fu_each_src_dest
1562
1563  def fu_each_src_dest0(src, dest)   #:nodoc:
1564    if tmp = Array.try_convert(src)
1565      tmp.each do |s|
1566        s = File.path(s)
1567        yield s, File.join(dest, File.basename(s))
1568      end
1569    else
1570      src = File.path(src)
1571      if File.directory?(dest)
1572        yield src, File.join(dest, File.basename(src))
1573      else
1574        yield src, File.path(dest)
1575      end
1576    end
1577  end
1578  private_module_function :fu_each_src_dest0
1579
1580  def fu_same?(a, b)   #:nodoc:
1581    File.identical?(a, b)
1582  end
1583  private_module_function :fu_same?
1584
1585  @fileutils_output = $stderr
1586  @fileutils_label  = ''
1587
1588  def fu_output_message(msg)   #:nodoc:
1589    @fileutils_output ||= $stderr
1590    @fileutils_label  ||= ''
1591    @fileutils_output.puts @fileutils_label + msg
1592  end
1593  private_module_function :fu_output_message
1594
1595  # This hash table holds command options.
1596  OPT_TABLE = {}    #:nodoc: internal use only
1597  (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|
1598    (tbl[name.to_s] = instance_method(name).parameters).map! {|t, n| n if t == :key}.compact!
1599    tbl
1600  }
1601
1602  #
1603  # Returns an Array of method names which have any options.
1604  #
1605  #   p FileUtils.commands  #=> ["chmod", "cp", "cp_r", "install", ...]
1606  #
1607  def self.commands
1608    OPT_TABLE.keys
1609  end
1610
1611  #
1612  # Returns an Array of option names.
1613  #
1614  #   p FileUtils.options  #=> ["noop", "force", "verbose", "preserve", "mode"]
1615  #
1616  def self.options
1617    OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
1618  end
1619
1620  #
1621  # Returns true if the method +mid+ have an option +opt+.
1622  #
1623  #   p FileUtils.have_option?(:cp, :noop)     #=> true
1624  #   p FileUtils.have_option?(:rm, :force)    #=> true
1625  #   p FileUtils.have_option?(:rm, :preserve) #=> false
1626  #
1627  def self.have_option?(mid, opt)
1628    li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
1629    li.include?(opt)
1630  end
1631
1632  #
1633  # Returns an Array of option names of the method +mid+.
1634  #
1635  #   p FileUtils.options_of(:rm)  #=> ["noop", "verbose", "force"]
1636  #
1637  def self.options_of(mid)
1638    OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
1639  end
1640
1641  #
1642  # Returns an Array of method names which have the option +opt+.
1643  #
1644  #   p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
1645  #
1646  def self.collect_method(opt)
1647    OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
1648  end
1649
1650  LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern)
1651  module LowMethods
1652    private
1653    def _do_nothing(*)end
1654    ::FileUtils::LOW_METHODS.map {|name| alias_method name, :_do_nothing}
1655  end
1656
1657  METHODS = singleton_methods() - [:private_module_function,
1658      :commands, :options, :have_option?, :options_of, :collect_method]
1659
1660  #
1661  # This module has all methods of FileUtils module, but it outputs messages
1662  # before acting.  This equates to passing the <tt>:verbose</tt> flag to
1663  # methods in FileUtils.
1664  #
1665  module Verbose
1666    include FileUtils
1667    @fileutils_output  = $stderr
1668    @fileutils_label   = ''
1669    names = ::FileUtils.collect_method(:verbose)
1670    names.each do |name|
1671      module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1672        def #{name}(*args, **options)
1673          super(*args, **options, verbose: true)
1674        end
1675      EOS
1676    end
1677    private(*names)
1678    extend self
1679    class << self
1680      public(*::FileUtils::METHODS)
1681    end
1682  end
1683
1684  #
1685  # This module has all methods of FileUtils module, but never changes
1686  # files/directories.  This equates to passing the <tt>:noop</tt> flag
1687  # to methods in FileUtils.
1688  #
1689  module NoWrite
1690    include FileUtils
1691    include LowMethods
1692    @fileutils_output  = $stderr
1693    @fileutils_label   = ''
1694    names = ::FileUtils.collect_method(:noop)
1695    names.each do |name|
1696      module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1697        def #{name}(*args, **options)
1698          super(*args, **options, noop: true)
1699        end
1700      EOS
1701    end
1702    private(*names)
1703    extend self
1704    class << self
1705      public(*::FileUtils::METHODS)
1706    end
1707  end
1708
1709  #
1710  # This module has all methods of FileUtils module, but never changes
1711  # files/directories, with printing message before acting.
1712  # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
1713  # to methods in FileUtils.
1714  #
1715  module DryRun
1716    include FileUtils
1717    include LowMethods
1718    @fileutils_output  = $stderr
1719    @fileutils_label   = ''
1720    names = ::FileUtils.collect_method(:noop)
1721    names.each do |name|
1722      module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1723        def #{name}(*args, **options)
1724          super(*args, **options, noop: true, verbose: true)
1725        end
1726      EOS
1727    end
1728    private(*names)
1729    extend self
1730    class << self
1731      public(*::FileUtils::METHODS)
1732    end
1733  end
1734
1735end
1736