module Bundler::FileUtils

def self.collect_method(opt)


p Bundler::FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]

Returns an Array of methods names which have the option +opt+.
def self.collect_method(opt)
  OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
end

def self.commands


p Bundler::FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]

arguments.
Returns an Array of names of high-level methods that accept any keyword
def self.commands
  OPT_TABLE.keys
end

def self.have_option?(mid, opt)


p Bundler::FileUtils.have_option?(:rm, :preserve) #=> false
p Bundler::FileUtils.have_option?(:rm, :force) #=> true
p Bundler::FileUtils.have_option?(:cp, :noop) #=> true

Returns true if the method +mid+ have an option +opt+.
def self.have_option?(mid, opt)
  li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
  li.include?(opt)
end

def self.options


p Bundler::FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]

Returns an Array of option names.
def self.options
  OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
end

def self.options_of(mid)


p Bundler::FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"]

Returns an Array of option names of the method +mid+.
def self.options_of(mid)
  OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
end

def self.private_module_function(name) #:nodoc:

:nodoc:
def self.private_module_function(name)   #:nodoc:
  module_function name
  private_class_method name
end

def apply_mask(mode, user_mask, op, mode_mask) #:nodoc:

:nodoc:
def apply_mask(mode, user_mask, op, mode_mask)   #:nodoc:
  case op
  when '='
    (mode & ~user_mask) | (user_mask & mode_mask)
  when '+'
    mode | (user_mask & mode_mask)
  when '-'
    mode & ~(user_mask & mode_mask)
  end
end

def cd(dir, verbose: nil, &block) # :yield: dir

:yield: dir

end # return to original directory
# ... # do something
Bundler::FileUtils.cd('/') do # change directory

Bundler::FileUtils.cd('/', verbose: true) # change directory and report it

Bundler::FileUtils.cd('/') # change directory

working directory after the block execution has finished.
If this method is called with block, resumes to the previous

Changes the current directory to the directory +dir+.
def cd(dir, verbose: nil, &block) # :yield: dir
  fu_output_message "cd #{dir}" if verbose
  result = Dir.chdir(dir, &block)
  fu_output_message 'cd -' if verbose and block
  result
end

def chmod(mode, list, noop: nil, verbose: nil)

def chmod(mode, list, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
  return if noop
  list.each do |path|
    Entry_.new(path).chmod(fu_mode(mode, path))
  end
end

def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)


Bundler::FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
Bundler::FileUtils.chmod_R 0700, "/tmp/app.#{$$}"

to the bit pattern represented by +mode+.
Changes permission bits on the named files (in +list+)
def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
  list = fu_list(list)
  fu_output_message sprintf('chmod -R%s %s %s',
                            (force ? 'f' : ''),
                            mode_to_s(mode), list.join(' ')) if verbose
  return if noop
  list.each do |root|
    Entry_.new(root).traverse do |ent|
      begin
        ent.chmod(fu_mode(mode, ent.path))
      rescue
        raise unless force
      end
    end
  end
end

def chown(user, group, list, noop: nil, verbose: nil)


Bundler::FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true
Bundler::FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'

the attribute.
If +user+ or +group+ is nil, this method does not change
may be an ID (Integer/String) or a name (String).
to the user +user+ and the group +group+. +user+ and +group+
Changes owner and group on the named files (in +list+)
def chown(user, group, list, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message sprintf('chown %s %s',
                            (group ? "#{user}:#{group}" : user || ':'),
                            list.join(' ')) if verbose
  return if noop
  uid = fu_get_uid(user)
  gid = fu_get_gid(group)
  list.each do |path|
    Entry_.new(path).chown uid, gid
  end
end

def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)


Bundler::FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true
Bundler::FileUtils.chown_R 'www', 'www', '/var/www/htdocs'

method does not change the attribute.
a name (String). If +user+ or +group+ is nil, this
+user+ and +group+ may be an ID (Integer/String) or
to the user +user+ and the group +group+ recursively.
Changes owner and group on the named files (in +list+)
def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
  list = fu_list(list)
  fu_output_message sprintf('chown -R%s %s %s',
                            (force ? 'f' : ''),
                            (group ? "#{user}:#{group}" : user || ':'),
                            list.join(' ')) if verbose
  return if noop
  uid = fu_get_uid(user)
  gid = fu_get_gid(group)
  list.each do |root|
    Entry_.new(root).traverse do |ent|
      begin
        ent.chown uid, gid
      rescue
        raise unless force
      end
    end
  end
end

def compare_file(a, b)


Bundler::FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
Bundler::FileUtils.compare_file('somefile', 'somefile') #=> true

Returns true if the contents of a file +a+ and a file +b+ are identical.
def compare_file(a, b)
  return false unless File.size(a) == File.size(b)
  File.open(a, 'rb') {|fa|
    File.open(b, 'rb') {|fb|
      return compare_stream(fa, fb)
    }
  }
end

def compare_stream(a, b)


Returns true if the contents of a stream +a+ and +b+ are identical.
def compare_stream(a, b)
  bsize = fu_stream_blksize(a, b)
  if RUBY_VERSION > "2.4"
    sa = String.new(capacity: bsize)
    sb = String.new(capacity: bsize)
  else
    sa = String.new
    sb = String.new
  end
  begin
    a.read(bsize, sa)
    b.read(bsize, sb)
    return true if sa.empty? && sb.empty?
  end while sa == sb
  false
end

def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)


If +remove_destination+ is true, this method removes each destination file before copy.

If +dereference_root+ is true, this method dereference tree root.

modified time. Permissions are copied regardless +preserve+.
If +preserve+ is true, this method preserves owner, group, and

+src+ must exist, +dest+ must not exist.
Both of +src+ and +dest+ must be a path name.

(FIFO, device files and etc. are not supported yet)
This method preserves file types, c.f. symlink, directory...
If +src+ is a directory, this method copies its contents recursively.
Copies a file system entry +src+ to +dest+.
def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
  if dereference_root
    src = File.realpath(src)
  end
  Entry_.new(src, nil, false).wrap_traverse(proc do |ent|
    destent = Entry_.new(dest, ent.rel, false)
    File.unlink destent.path if remove_destination && (File.file?(destent.path) || File.symlink?(destent.path))
    ent.copy destent.path
  end, proc do |ent|
    destent = Entry_.new(dest, ent.rel, false)
    ent.copy_metadata destent.path if preserve
  end)
end

def copy_file(src, dest, preserve = false, dereference = true)


Both of +src+ and +dest+ must be a path name.
Copies file contents of +src+ to +dest+.
def copy_file(src, dest, preserve = false, dereference = true)
  ent = Entry_.new(src, nil, dereference)
  ent.copy_file dest
  ent.copy_metadata dest if preserve
end

def copy_stream(src, dest)


+dest+ must respond to #write(str).
+src+ must respond to #read(n) and
Copies stream +src+ to +dest+.
def copy_stream(src, dest)
  IO.copy_stream(src, dest)
end

def cp(src, dest, preserve: nil, noop: nil, verbose: nil)


Bundler::FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true
Bundler::FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
Bundler::FileUtils.cp 'eval.c', 'eval.c.org'

If +src+ is a list of files, then +dest+ must be a directory.

copies +src+ to +dest/src+.
Copies a file content +src+ to +dest+. If +dest+ is a directory,
def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
  fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest(src, dest) do |s, d|
    copy_file s, d, preserve
  end
end

def cp_lr(src, dest, noop: nil, verbose: nil,


Bundler::FileUtils.cp_lr 'src/.', 'dest' # cp_lr('src', 'dest') makes dest/src, but this doesn't.
# use the following code.
# directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
# If you want to link all contents of a directory instead of the

Bundler::FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true
Bundler::FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail'
# Examples of linking several files to target directory.

Bundler::FileUtils.cp_lr 'lib/', site_ruby + '/mylib'
Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true

If +remove_destination+ is true, this method removes each destination file before copy.

If +dereference_root+ is true, this method dereference tree root.

+src+ can be a list of files.

+src+ to +dest/src+.
all its contents recursively. If +dest+ is a directory, links
Hard link +src+ to +dest+. If +src+ is a directory, this method links
def cp_lr(src, dest, noop: nil, verbose: nil,
          dereference_root: true, remove_destination: false)
  fu_output_message "cp -lr#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest(src, dest) do |s, d|
    link_entry s, d, dereference_root, remove_destination
  end
end

def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,


# but this doesn't.
Bundler::FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
# use following code.
# directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
# If you want to copy all contents of a directory instead of the

Bundler::FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true
Bundler::FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
# Examples of copying several files to target directory.

Bundler::FileUtils.cp_r 'lib/', site_ruby + '/mylib'
Bundler::FileUtils.rm_r site_ruby + '/mylib', force: true
# Installing Ruby library "mylib" under the site_ruby

If +remove_destination+ is true, this method removes each destination file before copy.

If +dereference_root+ is true, this method dereference tree root.

+src+ can be a list of files.

+src+ to +dest/src+.
all its contents recursively. If +dest+ is a directory, copies
Copies +src+ to +dest+. If +src+ is a directory, this method copies
def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
         dereference_root: true, remove_destination: nil)
  fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest(src, dest) do |s, d|
    copy_entry s, d, preserve, dereference_root, remove_destination
  end
end

def fu_each_src_dest(src, dest) #:nodoc:

:nodoc:
def fu_each_src_dest(src, dest)   #:nodoc:
  fu_each_src_dest0(src, dest) do |s, d|
    raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
    yield s, d
  end
end

def fu_each_src_dest0(src, dest) #:nodoc:

:nodoc:
def fu_each_src_dest0(src, dest)   #:nodoc:
  if tmp = Array.try_convert(src)
    tmp.each do |s|
      s = File.path(s)
      yield s, File.join(dest, File.basename(s))
    end
  else
    src = File.path(src)
    if File.directory?(dest)
      yield src, File.join(dest, File.basename(src))
    else
      yield src, File.path(dest)
    end
  end
end

def fu_get_gid(group) #:nodoc:

:nodoc:
def fu_get_gid(group)   #:nodoc:
  return nil unless group
  case group
  when Integer
    group
  when /\A\d+\z/
    group.to_i
  else
    require 'etc'
    Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil
  end
end

def fu_get_uid(user) #:nodoc:

:nodoc:
def fu_get_uid(user)   #:nodoc:
  return nil unless user
  case user
  when Integer
    user
  when /\A\d+\z/
    user.to_i
  else
    require 'etc'
    Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil
  end
end

def fu_have_symlink? #:nodoc:

:nodoc:
def fu_have_symlink?   #:nodoc:
  File.symlink nil, nil
rescue NotImplementedError
  return false
rescue TypeError
  return true
end

def fu_list(arg) #:nodoc:

:nodoc:
def fu_list(arg)   #:nodoc:
  [arg].flatten.map {|path| File.path(path) }
end

def fu_mkdir(path, mode) #:nodoc:

:nodoc:
def fu_mkdir(path, mode)   #:nodoc:
  path = remove_trailing_slash(path)
  if mode
    Dir.mkdir path, mode
    File.chmod mode, path
  else
    Dir.mkdir path
  end
end

def fu_mode(mode, path) #:nodoc:

:nodoc:
def fu_mode(mode, path)  #:nodoc:
  mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode
end

def fu_output_message(msg) #:nodoc:

:nodoc:
def fu_output_message(msg)   #:nodoc:
  output = @fileutils_output if defined?(@fileutils_output)
  output ||= $stderr
  if defined?(@fileutils_label)
    msg = @fileutils_label + msg
  end
  output.puts msg
end

def fu_same?(a, b) #:nodoc:

:nodoc:
def fu_same?(a, b)   #:nodoc:
  File.identical?(a, b)
end

def fu_stat_identical_entry?(a, b) #:nodoc:

:nodoc:
def fu_stat_identical_entry?(a, b)   #:nodoc:
  a.dev == b.dev and a.ino == b.ino
end

def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,


Bundler::FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true
Bundler::FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true

This method removes destination before copy.
mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
If +src+ is not same as +dest+, copies it and changes the permission
def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
            noop: nil, verbose: nil)
  if verbose
    msg = +"install -c"
    msg << ' -p' if preserve
    msg << ' -m ' << mode_to_s(mode) if mode
    msg << " -o #{owner}" if owner
    msg << " -g #{group}" if group
    msg << ' ' << [src,dest].flatten.join(' ')
    fu_output_message msg
  end
  return if noop
  uid = fu_get_uid(owner)
  gid = fu_get_gid(group)
  fu_each_src_dest(src, dest) do |s, d|
    st = File.stat(s)
    unless File.exist?(d) and compare_file(s, d)
      remove_file d, true
      copy_file s, d
      File.utime st.atime, st.mtime, d if preserve
      File.chmod fu_mode(mode, st), d if mode
      File.chown uid, gid, d if uid or gid
    end
  end
end

def link_entry(src, dest, dereference_root = false, remove_destination = false)


If +remove_destination+ is true, this method removes each destination file before copy.

If +dereference_root+ is true, this method dereferences the tree root.

+src+ must exist, +dest+ must not exist.
Both of +src+ and +dest+ must be a path name.

If +src+ is a directory, this method links its contents recursively.
Hard links a file system entry +src+ to +dest+.
def link_entry(src, dest, dereference_root = false, remove_destination = false)
  Entry_.new(src, nil, dereference_root).traverse do |ent|
    destent = Entry_.new(dest, ent.rel, false)
    File.unlink destent.path if remove_destination && File.file?(destent.path)
    ent.link destent.path
  end
end

def ln(src, dest, force: nil, noop: nil, verbose: nil)


Bundler::FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
Bundler::FileUtils.cd '/sbin'

If +dir+ is not a directory, raises Errno::ENOTDIR.
pointing to each item in +targets+.
In the third form, creates several hard links in the directory +dir+,
In the second form, creates a link +dir/target+ pointing to +target+.

Bundler::FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
Bundler::FileUtils.ln 'gcc', 'cc', verbose: true

But if the +force+ option is set, overwrites +link+.
If +link+ already exists, raises Errno::EEXIST.
In the first form, creates a hard link +link+ which points to +target+.

Bundler::FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil)
Bundler::FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil)
Bundler::FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil)
:call-seq:
def ln(src, dest, force: nil, noop: nil, verbose: nil)
  fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest0(src, dest) do |s,d|
    remove_file d, true if force
    File.link s, d
  end
end

def ln_s(src, dest, force: nil, noop: nil, verbose: nil)


Bundler::FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'

If +dir+ is not a directory, raises Errno::ENOTDIR.
pointing to each item in +targets+.
In the third form, creates several symbolic links in the directory +dir+,
In the second form, creates a link +dir/target+ pointing to +target+.

Bundler::FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
Bundler::FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'

But if the force option is set, overwrites +link+.
If +link+ already exists, raises Errno::EEXIST.
In the first form, creates a symbolic link +link+ which points to +target+.

Bundler::FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
Bundler::FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil)
Bundler::FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
:call-seq:
def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
  fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest0(src, dest) do |s,d|
    remove_file d, true if force
    File.symlink s, d
  end
end

def ln_sf(src, dest, noop: nil, verbose: nil)


Bundler::FileUtils.ln_s(*args, force: true)

Same as

Bundler::FileUtils.ln_sf(*args)
:call-seq:
def ln_sf(src, dest, noop: nil, verbose: nil)
  ln_s src, dest, force: true, noop: noop, verbose: verbose
end

def mkdir(list, mode: nil, noop: nil, verbose: nil)


Bundler::FileUtils.mkdir 'tmp', mode: 0700
Bundler::FileUtils.mkdir 'notexist', noop: true # Does not really create.
Bundler::FileUtils.mkdir %w(tmp data)
Bundler::FileUtils.mkdir 'test'

Creates one or more directories.
def mkdir(list, mode: nil, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
  return if noop
  list.each do |dir|
    fu_mkdir dir, mode
  end
end

def mkdir_p(list, mode: nil, noop: nil, verbose: nil)


You can pass several directories at a time in a list.

* /usr/local/lib/ruby
* /usr/local/lib
* /usr/local
* /usr

causes to make following directories, if they do not exist.

Bundler::FileUtils.mkdir_p '/usr/local/lib/ruby'

For example,
Creates a directory and all its parent directories.
def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
  return *list if noop
  list.map {|path| remove_trailing_slash(path)}.each do |path|
    # optimize for the most common case
    begin
      fu_mkdir path, mode
      next
    rescue SystemCallError
      next if File.directory?(path)
    end
    stack = []
    until path == stack.last   # dirname("/")=="/", dirname("C:/")=="C:/"
      stack.push path
      path = File.dirname(path)
    end
    stack.pop                 # root directory should exist
    stack.reverse_each do |dir|
      begin
        fu_mkdir dir, mode
      rescue SystemCallError
        raise unless File.directory?(dir)
      end
    end
  end
  return *list
end

def mode_to_s(mode) #:nodoc:

:nodoc:
def mode_to_s(mode)  #:nodoc:
  mode.is_a?(String) ? mode : "%o" % mode
end

def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)


Bundler::FileUtils.mv Dir.glob('test*.rb'), 'test', noop: true, verbose: true
Bundler::FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'

Bundler::FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error
Bundler::FileUtils.mv 'badname.rb', 'goodname.rb'

disk partition, the file is copied then the original file is removed.
Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
  fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
  return if noop
  fu_each_src_dest(src, dest) do |s, d|
    destent = Entry_.new(d, nil, true)
    begin
      if destent.exist?
        if destent.directory?
          raise Errno::EEXIST, d
        end
      end
      begin
        File.rename s, d
      rescue Errno::EXDEV,
             Errno::EPERM # move from unencrypted to encrypted dir (ext4)
        copy_entry s, d, true
        if secure
          remove_entry_secure s, force
        else
          remove_entry s, force
        end
      end
    rescue SystemCallError
      raise unless force
    end
  end
end

def pwd


Returns the name of the current directory.
def pwd
  Dir.pwd
end

def remove_dir(path, force = false)


This method ignores StandardError if +force+ is true.
Removes a directory +dir+ and its contents recursively.
def remove_dir(path, force = false)
  remove_entry path, force   # FIXME?? check if it is a directory
end

def remove_entry(path, force = false)


See also remove_entry_secure.

If +path+ is a directory, remove it recursively.
+path+ might be a regular file, a directory, or something.
This method removes a file system entry +path+.
def remove_entry(path, force = false)
  Entry_.new(path).postorder_traverse do |ent|
    begin
      ent.remove
    rescue
      raise unless force
    end
  end
rescue
  raise unless force
end

def remove_entry_secure(path, force = false)


For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].

* https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
* https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448

For details of this security vulnerability, see Perl's case:

work.
user (root) should invoke this method. Otherwise this method does not
WARNING: Only the owner of the removing directory tree, or Unix super

writable except when the sticky bit set.
should not be owned by untrusted users, and should not be world
moved by other untrusted users. For example, parent directories
WARNING: You must ensure that *ALL* parent directories cannot be

owner of the removing whole directory tree, or is the super user (root).
removing directories. This requires the current process is the
If +path+ is a directory, this method chown(2) and chmod(2) all
To avoid this security hole, this method applies special preprocess.

* The system has symbolic link.
* Removing directory tree includes world writable directory.
* Parent directory is world writable (including /tmp).

#rm_r causes security hole when:
(time-of-check-to-time-of-use) local security vulnerability of rm_r.
remove it recursively. This method is required to avoid TOCTTOU
regular file, a directory, or something. If +path+ is a directory,
This method removes a file system entry +path+. +path+ shall be a
def remove_entry_secure(path, force = false)
  unless fu_have_symlink?
    remove_entry path, force
    return
  end
  fullpath = File.expand_path(path)
  st = File.lstat(fullpath)
  unless st.directory?
    File.unlink fullpath
    return
  end
  # is a directory.
  parent_st = File.stat(File.dirname(fullpath))
  unless parent_st.world_writable?
    remove_entry path, force
    return
  end
  unless parent_st.sticky?
    raise ArgumentError, "parent directory is world writable, Bundler::FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
  end
  # freeze tree root
  euid = Process.euid
  dot_file = fullpath + "/."
  begin
    File.open(dot_file) {|f|
      unless fu_stat_identical_entry?(st, f.stat)
        # symlink (TOC-to-TOU attack?)
        File.unlink fullpath
        return
      end
      f.chown euid, -1
      f.chmod 0700
    }
  rescue Errno::EISDIR # JRuby in non-native mode can't open files as dirs
    File.lstat(dot_file).tap {|fstat|
      unless fu_stat_identical_entry?(st, fstat)
        # symlink (TOC-to-TOU attack?)
        File.unlink fullpath
        return
      end
      File.chown euid, -1, dot_file
      File.chmod 0700, dot_file
    }
  end
  unless fu_stat_identical_entry?(st, File.lstat(fullpath))
    # TOC-to-TOU attack?
    File.unlink fullpath
    return
  end
  # ---- tree root is frozen ----
  root = Entry_.new(path)
  root.preorder_traverse do |ent|
    if ent.directory?
      ent.chown euid, -1
      ent.chmod 0700
    end
  end
  root.postorder_traverse do |ent|
    begin
      ent.remove
    rescue
      raise unless force
    end
  end
rescue
  raise unless force
end

def remove_file(path, force = false)


This method ignores StandardError if +force+ is true.
Removes a file +path+.
def remove_file(path, force = false)
  Entry_.new(path).remove_file
rescue
  raise unless force
end

def remove_trailing_slash(dir) #:nodoc:

:nodoc:
def remove_trailing_slash(dir)   #:nodoc:
  dir == '/' ? dir : dir.chomp(?/)
end

def rm(list, force: nil, noop: nil, verbose: nil)


Bundler::FileUtils.rm 'NotExistFile', force: true # never raises exception
Bundler::FileUtils.rm Dir.glob('*.so')
Bundler::FileUtils.rm %w( junk.txt dust.txt )

All StandardErrors are ignored when the :force option is set.
Remove file(s) specified in +list+. This method cannot remove directories.
def rm(list, force: nil, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
  return if noop
  list.each do |path|
    remove_file path, force
  end
end

def rm_f(list, noop: nil, verbose: nil)


Bundler::FileUtils.rm(list, force: true)

Equivalent to
def rm_f(list, noop: nil, verbose: nil)
  rm list, force: true, noop: noop, verbose: verbose
end

def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)


See also remove_entry_secure.
NOTE: This method calls remove_entry_secure if :secure option is set.

Default is secure: false.
of remove_entry_secure carefully, and set :secure option to true.
system has symbolic link. For secure removing, read the documentation
process has strong privilege such as Unix super user (root), and the
writable (including /tmp, whose permission is 1777), and the current
if one of parent directories or removing directory tree are world
WARNING: This method causes local vulnerability

Bundler::FileUtils.rm_r 'some_dir', force: true
Bundler::FileUtils.rm_r Dir.glob('/tmp/*')

StandardError when :force option is set.
removes its all contents recursively. This method ignores
remove files +list+[0] +list+[1]... If +list+[n] is a directory,
def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
  list = fu_list(list)
  fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
  return if noop
  list.each do |path|
    if secure
      remove_entry_secure path, force
    else
      remove_entry path, force
    end
  end
end

def rm_rf(list, noop: nil, verbose: nil, secure: nil)


Read the documentation of rm_r first.
WARNING: This method causes local vulnerability.

Bundler::FileUtils.rm_r(list, force: true)

Equivalent to
def rm_rf(list, noop: nil, verbose: nil, secure: nil)
  rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
end

def rmdir(list, parents: nil, noop: nil, verbose: nil)


Bundler::FileUtils.rmdir 'somedir', verbose: true, noop: true
# Does not really remove directory; outputs message.
Bundler::FileUtils.rmdir %w(somedir anydir otherdir)
Bundler::FileUtils.rmdir 'somedir'

Removes one or more directories.
def rmdir(list, parents: nil, noop: nil, verbose: nil)
  list = fu_list(list)
  fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
  return if noop
  list.each do |dir|
    Dir.rmdir(dir = remove_trailing_slash(dir))
    if parents
      begin
        until (parent = File.dirname(dir)) == '.' or parent == dir
          dir = parent
          Dir.rmdir(dir)
        end
      rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
      end
    end
  end
end

def symbolic_modes_to_i(mode_sym, path) #:nodoc:

:nodoc:
def symbolic_modes_to_i(mode_sym, path)  #:nodoc:
  mode = if File::Stat === path
           path.mode
         else
           File.stat(path).mode
         end
  mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause|
    target, *actions = clause.split(/([=+-])/)
    raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty?
    target = 'a' if target.empty?
    user_mask = user_mask(target)
    actions.each_slice(2) do |op, perm|
      need_apply = op == '='
      mode_mask = (perm || '').each_char.inject(0) do |mask, chr|
        case chr
        when "r"
          mask | 0444
        when "w"
          mask | 0222
        when "x"
          mask | 0111
        when "X"
          if FileTest.directory? path
            mask | 0111
          else
            mask
          end
        when "s"
          mask | 06000
        when "t"
          mask | 01000
        when "u", "g", "o"
          if mask.nonzero?
            current_mode = apply_mask(current_mode, user_mask, op, mask)
          end
          need_apply = false
          copy_mask = user_mask(chr)
          (current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111)
        else
          raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}"
        end
      end
      if mode_mask.nonzero? || need_apply
        current_mode = apply_mask(current_mode, user_mask, op, mode_mask)
      end
    end
    current_mode
  end
end

def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)


Bundler::FileUtils.touch Dir.glob('*.c'); system 'make'
Bundler::FileUtils.touch 'timestamp'

+list+. Files are created if they don't exist.
Updates modification time (mtime) and access time (atime) of file(s) in
def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
  list = fu_list(list)
  t = mtime
  if verbose
    fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
  end
  return if noop
  list.each do |path|
    created = nocreate
    begin
      File.utime(t, t, path)
    rescue Errno::ENOENT
      raise if created
      File.open(path, 'a') {
        ;
      }
      created = true
      retry if t
    end
  end
end

def uptodate?(new, old_list)


system 'make hello.o'
Bundler::FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \

Non-existent files are older than any file.
Returns true if +new+ is newer than all +old_list+.
def uptodate?(new, old_list)
  return false unless File.exist?(new)
  new_time = File.mtime(new)
  old_list.each do |old|
    if File.exist?(old)
      return false unless new_time > File.mtime(old)
    end
  end
  true
end

def user_mask(target) #:nodoc:

:nodoc:
def user_mask(target)  #:nodoc:
  target.each_char.inject(0) do |mask, chr|
    case chr
    when "u"
      mask | 04700
    when "g"
      mask | 02070
    when "o"
      mask | 01007
    when "a"
      mask | 07777
    else
      raise ArgumentError, "invalid `who' symbol in file mode: #{chr}"
    end
  end
end