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:
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:
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
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:
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:
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:
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:
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:
def fu_have_symlink? #:nodoc: File.symlink nil, nil rescue NotImplementedError return false rescue TypeError return true end
def fu_list(arg) #:nodoc:
def fu_list(arg) #:nodoc: [arg].flatten.map {|path| File.path(path) } end
def fu_mkdir(path, mode) #: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:
def fu_mode(mode, path) #:nodoc: mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode end
def fu_output_message(msg) #: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:
def fu_same?(a, b) #:nodoc: File.identical?(a, b) end
def fu_stat_identical_entry?(a, b) #: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:
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:
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:
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:
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