summaryrefslogtreecommitdiff
path: root/lib/fileutils.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/fileutils.rb')
-rw-r--r--lib/fileutils.rb1114
1 files changed, 588 insertions, 526 deletions
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index 043a24d2b5..f56d7f9cb9 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -1,50 +1,53 @@
-#
+# frozen_string_literal: true
+#
# = fileutils.rb
-#
-# Copyright (c) 2000-2005 Minero Aoki <aamine@loveruby.net>
-#
+#
+# Copyright (c) 2000-2007 Minero Aoki
+#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
-#
+#
# == module FileUtils
-#
+#
# Namespace for several file utility methods for copying, moving, removing, etc.
-#
+#
# === Module Functions
-#
-# cd(dir, options)
-# cd(dir, options) {|dir| .... }
-# pwd()
-# mkdir(dir, options)
-# mkdir(list, options)
-# mkdir_p(dir, options)
-# mkdir_p(list, options)
-# rmdir(dir, options)
-# rmdir(list, options)
-# ln(old, new, options)
-# ln(list, destdir, options)
-# ln_s(old, new, options)
-# ln_s(list, destdir, options)
-# ln_sf(src, dest, options)
-# cp(src, dest, options)
-# cp(list, dir, options)
-# cp_r(src, dest, options)
-# cp_r(list, dir, options)
-# mv(src, dest, options)
-# mv(list, dir, options)
-# rm(list, options)
-# rm_r(list, options)
-# rm_rf(list, options)
-# install(src, dest, mode = <src's>, options)
-# chmod(mode, list, options)
-# chmod_R(mode, list, options)
-# chown(user, group, list, options)
-# chown_R(user, group, list, options)
-# touch(list, options)
+#
+# require 'fileutils'
+#
+# FileUtils.cd(dir, options)
+# FileUtils.cd(dir, options) {|dir| block }
+# FileUtils.pwd()
+# FileUtils.mkdir(dir, options)
+# FileUtils.mkdir(list, options)
+# FileUtils.mkdir_p(dir, options)
+# FileUtils.mkdir_p(list, options)
+# FileUtils.rmdir(dir, options)
+# FileUtils.rmdir(list, options)
+# FileUtils.ln(target, link, options)
+# FileUtils.ln(targets, dir, options)
+# FileUtils.ln_s(target, link, options)
+# FileUtils.ln_s(targets, dir, options)
+# FileUtils.ln_sf(target, link, options)
+# FileUtils.cp(src, dest, options)
+# FileUtils.cp(list, dir, options)
+# FileUtils.cp_r(src, dest, options)
+# FileUtils.cp_r(list, dir, options)
+# FileUtils.mv(src, dest, options)
+# FileUtils.mv(list, dir, options)
+# FileUtils.rm(list, options)
+# FileUtils.rm_r(list, options)
+# FileUtils.rm_rf(list, options)
+# FileUtils.install(src, dest, options)
+# FileUtils.chmod(mode, list, options)
+# FileUtils.chmod_R(mode, list, options)
+# FileUtils.chown(user, group, list, options)
+# FileUtils.chown_R(user, group, list, options)
+# FileUtils.touch(list, options)
#
# The <tt>options</tt> parameter is a hash of options, taken from the list
# <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
-# <tt>:noop</tt> means that no changes are made. The other two are obvious.
+# <tt>:noop</tt> means that no changes are made. The other three are obvious.
# Each method documents the options that it honours.
#
# All methods that have the concept of a "source" file or directory can take
@@ -53,47 +56,44 @@
#
# There are some `low level' methods, which do not accept any option:
#
-# copy_entry(src, dest, preserve = false, dereference = false)
-# copy_file(src, dest, preserve = false, dereference = true)
-# copy_stream(srcstream, deststream)
-# remove_entry(path, force = false)
-# remove_entry_secure(path, force = false)
-# remove_file(path, force = false)
-# compare_file(path_a, path_b)
-# compare_stream(stream_a, stream_b)
-# uptodate?(file, cmp_list)
+# FileUtils.copy_entry(src, dest, preserve = false, dereference = false)
+# FileUtils.copy_file(src, dest, preserve = false, dereference = true)
+# FileUtils.copy_stream(srcstream, deststream)
+# FileUtils.remove_entry(path, force = false)
+# FileUtils.remove_entry_secure(path, force = false)
+# FileUtils.remove_file(path, force = false)
+# FileUtils.compare_file(path_a, path_b)
+# FileUtils.compare_stream(stream_a, stream_b)
+# FileUtils.uptodate?(file, cmp_list)
#
# == module FileUtils::Verbose
-#
+#
# This module has all methods of FileUtils module, but it outputs messages
# before acting. This equates to passing the <tt>:verbose</tt> flag to methods
# in FileUtils.
-#
+#
# == module FileUtils::NoWrite
-#
+#
# This module has all methods of FileUtils module, but never changes
# files/directories. This equates to passing the <tt>:noop</tt> flag to methods
# in FileUtils.
-#
+#
# == module FileUtils::DryRun
-#
+#
# This module has all methods of FileUtils module, but never changes
# files/directories. This equates to passing the <tt>:noop</tt> and
# <tt>:verbose</tt> flags to methods in FileUtils.
-#
+#
module FileUtils
+ VERSION = "1.0.2"
+
def self.private_module_function(name) #:nodoc:
module_function name
private_class_method name
end
- # This hash table holds command options.
- OPT_TABLE = {} #:nodoc: internal use only
-
- #
- # Options: (none)
#
# Returns the name of the current directory.
#
@@ -106,41 +106,35 @@ module FileUtils
module_function :getwd
#
- # Options: verbose
- #
# Changes the current directory to the directory +dir+.
- #
+ #
# If this method is called with block, resumes to the old
# working directory after the block execution finished.
- #
+ #
# FileUtils.cd('/', :verbose => true) # chdir and report it
- #
- def cd(dir, options = {}, &block) # :yield: dir
- fu_check_options options, :verbose
- fu_output_message "cd #{dir}" if options[:verbose]
+ #
+ # FileUtils.cd('/') do # chdir
+ # # ... # do something
+ # end # return to original directory
+ #
+ def cd(dir, verbose: nil, &block) # :yield: dir
+ fu_output_message "cd #{dir}" if verbose
Dir.chdir(dir, &block)
- fu_output_message 'cd -' if options[:verbose] and block
+ fu_output_message 'cd -' if verbose and block
end
module_function :cd
alias chdir cd
module_function :chdir
- OPT_TABLE['cd'] =
- OPT_TABLE['chdir'] = %w( verbose )
-
#
- # Options: (none)
- #
- # Returns true if +newer+ is newer than all +old_list+.
+ # Returns true if +new+ is newer than all +old_list+.
# Non-existent files are older than any file.
- #
+ #
# FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
# system 'make hello.o'
- #
- def uptodate?(new, old_list, options = nil)
- raise ArgumentError, 'uptodate? does not accept any option' if options
-
+ #
+ def uptodate?(new, old_list)
return false unless File.exist?(new)
new_time = File.mtime(new)
old_list.each do |old|
@@ -152,56 +146,54 @@ module FileUtils
end
module_function :uptodate?
+ def remove_trailing_slash(dir) #:nodoc:
+ dir == '/' ? dir : dir.chomp(?/)
+ end
+ private_module_function :remove_trailing_slash
+
#
- # Options: mode noop verbose
- #
# Creates one or more directories.
- #
+ #
# FileUtils.mkdir 'test'
# FileUtils.mkdir %w( tmp data )
# FileUtils.mkdir 'notexist', :noop => true # Does not really create.
# FileUtils.mkdir 'tmp', :mode => 0700
- #
- def mkdir(list, options = {})
- fu_check_options options, :mode, :noop, :verbose
+ #
+ def mkdir(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
- fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
- return if options[:noop]
+ fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
+ return if noop
list.each do |dir|
- fu_mkdir dir, options[:mode]
+ fu_mkdir dir, mode
end
end
module_function :mkdir
- OPT_TABLE['mkdir'] = %w( noop verbose mode )
-
#
- # Options: mode noop verbose
- #
# Creates a directory and all its parent directories.
# For example,
- #
+ #
# FileUtils.mkdir_p '/usr/local/lib/ruby'
- #
+ #
# causes to make following directories, if it does not exist.
- # * /usr
- # * /usr/local
- # * /usr/local/lib
- # * /usr/local/lib/ruby
+ #
+ # * /usr
+ # * /usr/local
+ # * /usr/local/lib
+ # * /usr/local/lib/ruby
#
# You can pass several directories at a time in a list.
- #
- def mkdir_p(list, options = {})
- fu_check_options options, :mode, :noop, :verbose
+ #
+ def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
- fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
- return *list if options[:noop]
+ fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
+ return *list if noop
- list.map {|path| path.sub(%r</\z>, '') }.each do |path|
+ list.map {|path| remove_trailing_slash(path)}.each do |path|
# optimize for the most common case
begin
- fu_mkdir path, options[:mode]
+ fu_mkdir path, mode
next
rescue SystemCallError
next if File.directory?(path)
@@ -212,11 +204,12 @@ module FileUtils
stack.push path
path = File.dirname(path)
end
- stack.reverse_each do |path|
+ stack.pop # root directory should exist
+ stack.reverse_each do |dir|
begin
- fu_mkdir path, options[:mode]
- rescue SystemCallError => err
- raise unless File.directory?(path)
+ fu_mkdir dir, mode
+ rescue SystemCallError
+ raise unless File.directory?(dir)
end
end
end
@@ -230,12 +223,8 @@ module FileUtils
module_function :mkpath
module_function :makedirs
- OPT_TABLE['mkdir_p'] =
- OPT_TABLE['mkpath'] =
- OPT_TABLE['makedirs'] = %w( noop verbose )
-
def fu_mkdir(path, mode) #:nodoc:
- path = path.sub(%r</\z>, '')
+ path = remove_trailing_slash(path)
if mode
Dir.mkdir path, mode
File.chmod mode, path
@@ -246,56 +235,58 @@ module FileUtils
private_module_function :fu_mkdir
#
- # Options: noop, verbose
- #
# Removes one or more directories.
- #
+ #
# FileUtils.rmdir 'somedir'
# FileUtils.rmdir %w(somedir anydir otherdir)
# # Does not really remove directory; outputs message.
# FileUtils.rmdir 'somedir', :verbose => true, :noop => true
- #
- def rmdir(list, options = {})
- fu_check_options options, :noop, :verbose
+ #
+ def rmdir(list, parents: nil, noop: nil, verbose: nil)
list = fu_list(list)
- fu_output_message "rmdir #{list.join ' '}" if options[:verbose]
- return if options[:noop]
+ fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
+ return if noop
list.each do |dir|
- Dir.rmdir dir.sub(%r</\z>, '')
+ 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
module_function :rmdir
- OPT_TABLE['rmdir'] = %w( noop verbose )
-
#
- # Options: force noop verbose
+ # :call-seq:
+ # FileUtils.ln(target, link, force: nil, noop: nil, verbose: nil)
+ # FileUtils.ln(target, dir, force: nil, noop: nil, verbose: nil)
+ # FileUtils.ln(targets, dir, force: nil, noop: nil, verbose: nil)
#
- # <b><tt>ln(old, new, options = {})</tt></b>
+ # In the first form, creates a hard link +link+ which points to +target+.
+ # If +link+ already exists, raises Errno::EEXIST.
+ # But if the :force option is set, overwrites +link+.
#
- # Creates a hard link +new+ which points to +old+.
- # If +new+ already exists and it is a directory, creates a link +new/old+.
- # If +new+ already exists and it is not a directory, raises Errno::EEXIST.
- # But if :force option is set, overwrite +new+.
- #
- # FileUtils.ln 'gcc', 'cc', :verbose => true
+ # FileUtils.ln 'gcc', 'cc', verbose: true
# FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
- #
- # <b><tt>ln(list, destdir, options = {})</tt></b>
- #
- # Creates several hard links in a directory, with each one pointing to the
- # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR.
- #
- # include FileUtils
- # cd '/sbin'
+ #
+ # In the second form, creates a link +dir/target+ pointing to +target+.
+ # In the third form, creates several hard links in the directory +dir+,
+ # pointing to each item in +targets+.
+ # If +dir+ is not a directory, raises Errno::ENOTDIR.
+ #
+ # FileUtils.cd '/sbin'
# FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
- #
- def ln(src, dest, options = {})
- fu_check_options options, :force, :noop, :verbose
- fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
- return if options[:noop]
+ #
+ 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 options[:force]
+ remove_file d, true if force
File.link s, d
end
end
@@ -304,37 +295,31 @@ module FileUtils
alias link ln
module_function :link
- OPT_TABLE['ln'] =
- OPT_TABLE['link'] = %w( noop verbose force )
-
#
- # Options: force noop verbose
+ # :call-seq:
+ # FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
+ # FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil)
+ # FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
+ #
+ # In the first form, creates a symbolic link +link+ which points to +target+.
+ # If +link+ already exists, raises Errno::EEXIST.
+ # But if the :force option is set, overwrites +link+.
#
- # <b><tt>ln_s(old, new, options = {})</tt></b>
- #
- # Creates a symbolic link +new+ which points to +old+. If +new+ already
- # exists and it is a directory, creates a symbolic link +new/old+. If +new+
- # already exists and it is not a directory, raises Errno::EEXIST. But if
- # :force option is set, overwrite +new+.
- #
# FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
- # FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true
- #
- # <b><tt>ln_s(list, destdir, options = {})</tt></b>
- #
- # Creates several symbolic links in a directory, with each one pointing to the
- # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR.
- #
- # If +destdir+ is not a directory, raises Errno::ENOTDIR.
- #
- # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
- #
- def ln_s(src, dest, options = {})
- fu_check_options options, :force, :noop, :verbose
- fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
- return if options[:noop]
+ # FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
+ #
+ # In the second form, creates a link +dir/target+ pointing to +target+.
+ # In the third form, creates several symbolic links in the directory +dir+,
+ # pointing to each item in +targets+.
+ # If +dir+ is not a directory, raises Errno::ENOTDIR.
+ #
+ # FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
+ #
+ 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 options[:force]
+ remove_file d, true if force
File.symlink s, d
end
end
@@ -343,27 +328,19 @@ module FileUtils
alias symlink ln_s
module_function :symlink
- OPT_TABLE['ln_s'] =
- OPT_TABLE['symlink'] = %w( noop verbose force )
-
#
- # Options: noop verbose
- #
+ # :call-seq:
+ # FileUtils.ln_sf(*args)
+ #
# Same as
- # #ln_s(src, dest, :force)
- #
- def ln_sf(src, dest, options = {})
- fu_check_options options, :noop, :verbose
- options = options.dup
- options[:force] = true
- ln_s src, dest, options
+ #
+ # FileUtils.ln_s(*args, force: true)
+ #
+ def ln_sf(src, dest, noop: nil, verbose: nil)
+ ln_s src, dest, force: true, noop: noop, verbose: verbose
end
module_function :ln_sf
- OPT_TABLE['ln_sf'] = %w( noop verbose )
-
- #
- # Options: preserve noop verbose
#
# Copies a file content +src+ to +dest+. If +dest+ is a directory,
# copies +src+ to +dest/src+.
@@ -374,13 +351,12 @@ module FileUtils
# FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
# FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
# FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
- #
- def cp(src, dest, options = {})
- fu_check_options options, :preserve, :noop, :verbose
- fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
- return if options[:noop]
+ #
+ 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, options[:preserve]
+ copy_file s, d, preserve
end
end
module_function :cp
@@ -388,45 +364,37 @@ module FileUtils
alias copy cp
module_function :copy
- OPT_TABLE['cp'] =
- OPT_TABLE['copy'] = %w( noop verbose preserve )
-
#
- # Options: preserve noop verbose dereference_root
- #
# Copies +src+ to +dest+. If +src+ is a directory, this method copies
# all its contents recursively. If +dest+ is a directory, copies
# +src+ to +dest/src+.
#
# +src+ can be a list of files.
- #
- # # Installing ruby library "mylib" under the site_ruby
+ #
+ # # Installing Ruby library "mylib" under the site_ruby
# FileUtils.rm_r site_ruby + '/mylib', :force
# FileUtils.cp_r 'lib/', site_ruby + '/mylib'
- #
+ #
# # Examples of copying several files to target directory.
# FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
- # FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true
+ # FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', :noop => true, :verbose => true
#
# # If you want to copy all contents of a directory instead of the
# # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
# # use following code.
- # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest,
+ # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
# # but this doesn't.
- #
- def cp_r(src, dest, options = {})
- fu_check_options options, :preserve, :noop, :verbose, :dereference_root
- fu_output_message "cp -r#{options[:preserve] ? 'p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
- return if options[:noop]
- options[:dereference_root] = true unless options.key?(:dereference_root)
+ #
+ 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, options[:preserve], options[:dereference_root]
+ copy_entry s, d, preserve, dereference_root, remove_destination
end
end
module_function :cp_r
- OPT_TABLE['cp_r'] = %w( noop verbose preserve dereference_root )
-
#
# Copies a file system entry +src+ to +dest+.
# If +src+ is a directory, this method copies its contents recursively.
@@ -436,17 +404,22 @@ module FileUtils
# Both of +src+ and +dest+ must be a path name.
# +src+ must exist, +dest+ must not exist.
#
- # If +preserve+ is true, this method preserves owner, group, permissions
- # and modified time.
+ # If +preserve+ is true, this method preserves owner, group, and
+ # modified time. Permissions are copied regardless +preserve+.
#
# If +dereference_root+ is true, this method dereference tree root.
#
- def copy_entry(src, dest, preserve = false, dereference_root = false)
- Entry_.new(src, nil, dereference_root).traverse do |ent|
+ # If +remove_destination+ is true, this method removes each destination file before copy.
+ #
+ def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
+ Entry_.new(src, nil, dereference_root).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)
end
module_function :copy_entry
@@ -467,32 +440,29 @@ module FileUtils
# +dest+ must respond to #write(str).
#
def copy_stream(src, dest)
- fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
+ IO.copy_stream(src, dest)
end
module_function :copy_stream
#
- # Options: force noop verbose
- #
# Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
- # disk partition, the file is copied instead.
- #
+ # disk partition, the file is copied then the original file is removed.
+ #
# FileUtils.mv 'badname.rb', 'goodname.rb'
# FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
- #
- # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
+ #
+ # FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
# FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
- #
- def mv(src, dest, options = {})
- fu_check_options options, :force, :noop, :verbose, :secure
- fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
- return if options[:noop]
+ #
+ 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, dest
+ raise Errno::EEXIST, d
else
destent.remove_file if rename_cannot_overwrite_file?
end
@@ -501,14 +471,14 @@ module FileUtils
File.rename s, d
rescue Errno::EXDEV
copy_entry s, d, true
- if options[:secure]
- remove_entry_secure s, options[:force]
+ if secure
+ remove_entry_secure s, force
else
- remove_entry s, options[:force]
+ remove_entry s, force
end
end
rescue SystemCallError
- raise unless options[:force]
+ raise unless force
end
end
end
@@ -517,32 +487,26 @@ module FileUtils
alias move mv
module_function :move
- OPT_TABLE['mv'] =
- OPT_TABLE['move'] = %w[force noop verbose secure]
-
def rename_cannot_overwrite_file? #:nodoc:
- /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
+ /emx/ =~ RUBY_PLATFORM
end
private_module_function :rename_cannot_overwrite_file?
#
- # Options: force noop verbose
- #
# Remove file(s) specified in +list+. This method cannot remove directories.
# All StandardErrors are ignored when the :force option is set.
- #
+ #
# FileUtils.rm %w( junk.txt dust.txt )
# FileUtils.rm Dir.glob('*.so')
# FileUtils.rm 'NotExistFile', :force => true # never raises exception
- #
- def rm(list, options = {})
- fu_check_options options, :force, :noop, :verbose
+ #
+ def rm(list, force: nil, noop: nil, verbose: nil)
list = fu_list(list)
- fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
- return if options[:noop]
+ fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
+ return if noop
list.each do |path|
- remove_file path, options[:force]
+ remove_file path, force
end
end
module_function :rm
@@ -550,39 +514,26 @@ module FileUtils
alias remove rm
module_function :remove
- OPT_TABLE['rm'] =
- OPT_TABLE['remove'] = %w( noop verbose force )
-
#
- # Options: noop verbose
- #
# Equivalent to
#
- # #rm(list, :force => true)
+ # FileUtils.rm(list, :force => true)
#
- def rm_f(list, options = {})
- fu_check_options options, :noop, :verbose
- options = options.dup
- options[:force] = true
- rm list, options
+ def rm_f(list, noop: nil, verbose: nil)
+ rm list, force: true, noop: noop, verbose: verbose
end
module_function :rm_f
alias safe_unlink rm_f
module_function :safe_unlink
- OPT_TABLE['rm_f'] =
- OPT_TABLE['safe_unlink'] = %w( noop verbose )
-
#
- # Options: force noop verbose secure
- #
# remove files +list+[0] +list+[1]... If +list+[n] is a directory,
# removes its all contents recursively. This method ignores
# StandardError when :force option is set.
- #
+ #
# FileUtils.rm_r Dir.glob('/tmp/*')
- # FileUtils.rm_r '/', :force => true # :-)
+ # FileUtils.rm_r 'some_dir', :force => true
#
# WARNING: This method causes local vulnerability
# if one of parent directories or removing directory tree are world
@@ -594,49 +545,37 @@ module FileUtils
#
# NOTE: This method calls #remove_entry_secure if :secure option is set.
# See also #remove_entry_secure.
- #
- def rm_r(list, options = {})
- fu_check_options options, :force, :noop, :verbose, :secure
- # options[:secure] = true unless options.key?(:secure)
+ #
+ def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
list = fu_list(list)
- fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
- return if options[:noop]
+ fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
+ return if noop
list.each do |path|
- if options[:secure]
- remove_entry_secure path, options[:force]
+ if secure
+ remove_entry_secure path, force
else
- remove_entry path, options[:force]
+ remove_entry path, force
end
end
end
module_function :rm_r
- OPT_TABLE['rm_r'] = %w( noop verbose force secure )
-
#
- # Options: noop verbose secure
- #
# Equivalent to
#
- # #rm_r(list, :force => true)
+ # FileUtils.rm_r(list, :force => true)
#
# WARNING: This method causes local vulnerability.
# Read the documentation of #rm_r first.
- #
- def rm_rf(list, options = {})
- fu_check_options options, :noop, :verbose, :secure
- options = options.dup
- options[:force] = true
- rm_r list, options
+ #
+ def rm_rf(list, noop: nil, verbose: nil, secure: nil)
+ rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
end
module_function :rm_rf
alias rmtree rm_rf
module_function :rmtree
- OPT_TABLE['rm_rf'] =
- OPT_TABLE['rmtree'] = %w( noop verbose secure )
-
#
# This method removes a file system entry +path+. +path+ shall be a
# regular file, a directory, or something. If +path+ is a directory,
@@ -644,19 +583,19 @@ module FileUtils
# (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
# #rm_r causes security hole when:
#
- # * Parent directory is world writable (including /tmp).
- # * Removing directory tree includes world writable directory.
- # * The system has symbolic link.
+ # * Parent directory is world writable (including /tmp).
+ # * Removing directory tree includes world writable directory.
+ # * The system has symbolic link.
#
# To avoid this security hole, this method applies special preprocess.
# If +path+ is a directory, this method chown(2) and chmod(2) all
# removing directories. This requires the current process is the
# owner of the removing whole directory tree, or is the super user (root).
#
- # WARNING: You must ensure that *ALL* parent directories are not
- # world writable. Otherwise this method does not work.
- # Only exception is temporary directory like /tmp and /var/tmp,
- # whose permission is 1777.
+ # WARNING: You must ensure that *ALL* parent directories cannot be
+ # moved by other untrusted users. For example, parent directories
+ # should not be owned by untrusted users, and should not be world
+ # writable except when the sticky bit set.
#
# WARNING: Only the owner of the removing directory tree, or Unix super
# user (root) should invoke this method. Otherwise this method does not
@@ -664,8 +603,8 @@ module FileUtils
#
# For details of this security vulnerability, see Perl's case:
#
- # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
- # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
+ # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
+ # * http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
#
# For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
#
@@ -682,7 +621,7 @@ module FileUtils
end
# is a directory.
parent_st = File.stat(File.dirname(fullpath))
- unless fu_world_writable?(parent_st)
+ unless parent_st.world_writable?
remove_entry path, force
return
end
@@ -699,6 +638,11 @@ module FileUtils
end
f.chown euid, -1
f.chmod 0700
+ 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)
@@ -720,16 +664,11 @@ module FileUtils
end
module_function :remove_entry_secure
- def fu_world_writable?(st)
- (st.mode & 0002) != 0
- end
- private_module_function :fu_world_writable?
-
- def fu_have_symlink? #:nodoc
+ def fu_have_symlink? #:nodoc:
File.symlink nil, nil
rescue NotImplementedError
return false
- rescue
+ rescue TypeError
return true
end
private_module_function :fu_have_symlink?
@@ -780,10 +719,10 @@ module FileUtils
module_function :remove_dir
#
- # Returns true if the contents of a file A and a file B are identical.
- #
- # FileUtils.compare_file('somefile', 'somefile') #=> true
- # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false
+ # Returns true if the contents of a file +a+ and a file +b+ are identical.
+ #
+ # FileUtils.compare_file('somefile', 'somefile') #=> true
+ # FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
#
def compare_file(a, b)
return false unless File.size(a) == File.size(b)
@@ -805,118 +744,226 @@ module FileUtils
#
def compare_stream(a, b)
bsize = fu_stream_blksize(a, b)
- sa = sb = nil
- while sa == sb
- sa = a.read(bsize)
- sb = b.read(bsize)
- unless sa and sb
- if sa.nil? and sb.nil?
- return true
- end
- end
- end
+ sa = String.new(capacity: bsize)
+ sb = String.new(capacity: bsize)
+ begin
+ a.read(bsize, sa)
+ b.read(bsize, sb)
+ return true if sa.empty? && sb.empty?
+ end while sa == sb
false
end
module_function :compare_stream
#
- # Options: mode noop verbose
- #
# If +src+ is not same as +dest+, copies it and changes the permission
# mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
- #
+ # This method removes destination before copy.
+ #
# FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
# FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
- #
- def install(src, dest, options = {})
- fu_check_options options, :mode, :preserve, :noop, :verbose
- fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
- return if options[:noop]
+ #
+ 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
- st = File.stat(s) if options[:preserve]
copy_file s, d
- File.utime st.atime, st.mtime, d if options[:preserve]
- File.chmod options[:mode], d if options[:mode]
+ 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
module_function :install
- OPT_TABLE['install'] = %w( noop verbose preserve mode )
+ 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
+ private_module_function :user_mask
+
+ 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
+ private_module_function :apply_mask
+
+ 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
+ private_module_function :symbolic_modes_to_i
+
+ def fu_mode(mode, path) #:nodoc:
+ mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode
+ end
+ private_module_function :fu_mode
+
+ def mode_to_s(mode) #:nodoc:
+ mode.is_a?(String) ? mode : "%o" % mode
+ end
+ private_module_function :mode_to_s
#
- # Options: noop verbose
- #
# Changes permission bits on the named files (in +list+) to the bit pattern
# represented by +mode+.
- #
+ #
+ # +mode+ is the symbolic and absolute mode can be used.
+ #
+ # Absolute mode is
# FileUtils.chmod 0755, 'somecommand'
# FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
# FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
- #
- def chmod(mode, list, options = {})
- fu_check_options options, :noop, :verbose
+ #
+ # Symbolic mode is
+ # FileUtils.chmod "u=wrx,go=rx", 'somecommand'
+ # FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb)
+ # FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', :verbose => true
+ #
+ # "a" :: is user, group, other mask.
+ # "u" :: is user's mask.
+ # "g" :: is group's mask.
+ # "o" :: is other's mask.
+ # "w" :: is write permission.
+ # "r" :: is read permission.
+ # "x" :: is execute permission.
+ # "X" ::
+ # is execute permission for directories only, must be used in conjunction with "+"
+ # "s" :: is uid, gid.
+ # "t" :: is sticky bit.
+ # "+" :: is added to a class given the specified mode.
+ # "-" :: Is removed from a given class given mode.
+ # "=" :: Is the exact nature of the class will be given a specified mode.
+
+ def chmod(mode, list, noop: nil, verbose: nil)
list = fu_list(list)
- fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
- return if options[:noop]
+ 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 mode
+ Entry_.new(path).chmod(fu_mode(mode, path))
end
end
module_function :chmod
- OPT_TABLE['chmod'] = %w( noop verbose )
-
#
- # Options: noop verbose force
- #
# Changes permission bits on the named files (in +list+)
# to the bit pattern represented by +mode+.
- #
+ #
# FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
- #
- def chmod_R(mode, list, options = {})
- fu_check_options options, :noop, :verbose, :force
+ # FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
+ #
+ def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
list = fu_list(list)
- fu_output_message sprintf('chmod -R%s %o %s',
- (options[:force] ? 'f' : ''),
- mode, list.join(' ')) if options[:verbose]
- return if options[:noop]
+ 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 mode
+ ent.chmod(fu_mode(mode, ent.path))
rescue
- raise unless options[:force]
+ raise unless force
end
end
end
end
module_function :chmod_R
- OPT_TABLE['chmod_R'] = %w( noop verbose )
-
#
- # Options: noop verbose
- #
# Changes owner and group on the named files (in +list+)
# to the user +user+ and the group +group+. +user+ and +group+
# may be an ID (Integer/String) or a name (String).
# If +user+ or +group+ is nil, this method does not change
# the attribute.
- #
+ #
# FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
# FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
- #
- def chown(user, group, list, options = {})
- fu_check_options options, :noop, :verbose
+ #
+ def chown(user, group, list, noop: nil, verbose: nil)
list = fu_list(list)
- fu_output_message sprintf('chown %s%s',
- [user,group].compact.join(':') + ' ',
- list.join(' ')) if options[:verbose]
- return if options[:noop]
+ 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|
@@ -925,124 +972,109 @@ module FileUtils
end
module_function :chown
- OPT_TABLE['chown'] = %w( noop verbose )
-
#
- # Options: noop verbose force
- #
# Changes owner and group on the named files (in +list+)
# to the user +user+ and the group +group+ recursively.
# +user+ and +group+ may be an ID (Integer/String) or
# a name (String). If +user+ or +group+ is nil, this
# method does not change the attribute.
- #
+ #
# FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
# FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
- #
- def chown_R(user, group, list, options = {})
- fu_check_options options, :noop, :verbose, :force
+ #
+ 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',
- (options[:force] ? 'f' : ''),
- [user,group].compact.join(':') + ' ',
- list.join(' ')) if options[:verbose]
- return if options[:noop]
+ 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)
- return unless uid or gid
list.each do |root|
Entry_.new(root).traverse do |ent|
begin
ent.chown uid, gid
rescue
- raise unless options[:force]
+ raise unless force
end
end
end
end
module_function :chown_R
- OPT_TABLE['chown_R'] = %w( noop verbose )
-
begin
require 'etc'
+ rescue LoadError # rescue LoadError for miniruby
+ end
- def fu_get_uid(user) #:nodoc:
- return nil unless user
- user = user.to_s
- if /\A\d+\z/ =~ user
- then user.to_i
- else Etc.getpwnam(user).uid
- end
- end
- private_module_function :fu_get_uid
-
- def fu_get_gid(group) #:nodoc:
- return nil unless group
- if /\A\d+\z/ =~ group
- then group.to_i
- else Etc.getgrnam(group).gid
- end
- end
- private_module_function :fu_get_gid
-
- rescue LoadError
- # need Win32 support???
-
- def fu_get_uid(user) #:nodoc:
- user # FIXME
+ def fu_get_uid(user) #:nodoc:
+ return nil unless user
+ case user
+ when Integer
+ user
+ when /\A\d+\z/
+ user.to_i
+ else
+ Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil
end
- private_module_function :fu_get_uid
-
- def fu_get_gid(group) #:nodoc:
- group # FIXME
+ end
+ private_module_function :fu_get_uid
+
+ def fu_get_gid(group) #:nodoc:
+ return nil unless group
+ case group
+ when Integer
+ group
+ when /\A\d+\z/
+ group.to_i
+ else
+ Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil
end
- private_module_function :fu_get_gid
end
+ private_module_function :fu_get_gid
#
- # Options: noop verbose
- #
# Updates modification time (mtime) and access time (atime) of file(s) in
# +list+. Files are created if they don't exist.
- #
+ #
# FileUtils.touch 'timestamp'
# FileUtils.touch Dir.glob('*.c'); system 'make'
- #
- def touch(list, options = {})
- fu_check_options options, :noop, :verbose
+ #
+ def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
list = fu_list(list)
- fu_output_message "touch #{list.join ' '}" if options[:verbose]
- return if options[:noop]
- t = Time.now
+ 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
module_function :touch
- OPT_TABLE['touch'] = %w( noop verbose )
-
private
module StreamUtils_
private
def fu_windows?
- /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
+ /mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM
end
- def fu_copy_stream0(src, dest, blksize) #:nodoc:
- # FIXME: readpartial?
- while s = src.read(blksize)
- dest.write s
- end
+ def fu_copy_stream0(src, dest, blksize = nil) #:nodoc:
+ IO.copy_stream(src, dest)
end
def fu_stream_blksize(*streams)
@@ -1091,7 +1123,7 @@ module FileUtils
def path
if @path
- @path.to_str
+ File.path(@path)
else
join(@prefix, @rel)
end
@@ -1110,7 +1142,12 @@ module FileUtils
end
def exist?
- lstat! ? true : false
+ begin
+ lstat
+ true
+ rescue Errno::ENOENT
+ false
+ end
end
def file?
@@ -1156,7 +1193,9 @@ module FileUtils
end
def entries
- Dir.entries(path())\
+ opts = {}
+ opts[:encoding] = ::Encoding::UTF_8 if fu_windows?
+ Dir.entries(path(), opts)\
.reject {|n| n == '.' or n == '..' }\
.map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
end
@@ -1214,10 +1253,14 @@ module FileUtils
end
def copy(dest)
+ lstat
case
when file?
copy_file dest
when directory?
+ if !File.exist?(dest) and descendant_directory?(dest, path)
+ raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
+ end
begin
Dir.mkdir dest
rescue
@@ -1245,24 +1288,39 @@ module FileUtils
end
def copy_file(dest)
- st = stat()
- File.open(path(), 'rb') {|r|
- File.open(dest, 'wb', st.mode) {|w|
- fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
- }
- }
+ File.open(path()) do |s|
+ File.open(dest, 'wb', s.stat.mode) do |f|
+ IO.copy_stream(s, f)
+ end
+ end
end
def copy_metadata(path)
st = lstat()
- File.utime st.atime, st.mtime, path
+ if !st.symlink?
+ File.utime st.atime, st.mtime, path
+ end
+ mode = st.mode
begin
- File.chown st.uid, st.gid, path
- rescue Errno::EPERM
+ if st.symlink?
+ begin
+ File.lchown st.uid, st.gid, path
+ rescue NotImplementedError
+ end
+ else
+ File.chown st.uid, st.gid, path
+ end
+ rescue Errno::EPERM, Errno::EACCES
# clear setuid/setgid
- File.chmod st.mode & 01777, path
+ mode &= 01777
+ end
+ if st.symlink?
+ begin
+ File.lchmod mode, path
+ rescue NotImplementedError
+ end
else
- File.chmod st.mode, path
+ File.chmod mode, path
end
end
@@ -1276,7 +1334,7 @@ module FileUtils
def remove_dir1
platform_support {
- Dir.rmdir path().sub(%r</\z>, '')
+ Dir.rmdir path().chomp(?/)
}
end
@@ -1324,9 +1382,20 @@ module FileUtils
end
end
end
+ ensure
yield self
end
+ def wrap_traverse(pre, post)
+ pre.call self
+ if directory?
+ entries.each do |ent|
+ ent.wrap_traverse pre, post
+ end
+ end
+ post.call self
+ end
+
private
$fileutils_rb_have_lchmod = nil
@@ -1366,14 +1435,25 @@ module FileUtils
end
def join(dir, base)
- return dir.to_str if not base or base == '.'
- return base.to_str if not dir or dir == '.'
+ return File.path(dir) if not base or base == '.'
+ return File.path(base) if not dir or dir == '.'
File.join(dir, base)
end
+
+ if File::ALT_SEPARATOR
+ DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)"
+ else
+ DIRECTORY_TERM = "(?=/|\\z)"
+ end
+ SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : ""
+
+ def descendant_directory?(descendant, ascendant)
+ /\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant)
+ end
end # class Entry_
def fu_list(arg) #:nodoc:
- [arg].flatten.map {|path| path.to_str }
+ [arg].flatten.map {|path| File.path(path) }
end
private_module_function :fu_list
@@ -1386,59 +1466,27 @@ module FileUtils
private_module_function :fu_each_src_dest
def fu_each_src_dest0(src, dest) #:nodoc:
- if src.is_a?(Array)
- src.each do |s|
- s = s.to_str
+ 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 = src.to_str
+ src = File.path(src)
if File.directory?(dest)
yield src, File.join(dest, File.basename(src))
else
- yield src, dest.to_str
+ yield src, File.path(dest)
end
end
end
private_module_function :fu_each_src_dest0
def fu_same?(a, b) #:nodoc:
- if fu_have_st_ino?
- st1 = File.stat(a)
- st2 = File.stat(b)
- st1.dev == st2.dev and st1.ino == st2.ino
- else
- File.expand_path(a) == File.expand_path(b)
- end
- rescue Errno::ENOENT
- return false
+ File.identical?(a, b)
end
private_module_function :fu_same?
- def fu_have_st_ino? #:nodoc:
- not fu_windows?
- end
- private_module_function :fu_have_st_ino?
-
- def fu_check_options(options, *optdecl) #:nodoc:
- h = options.dup
- optdecl.each do |name|
- h.delete name
- end
- raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
- end
- private_module_function :fu_check_options
-
- def fu_update_option(args, new) #:nodoc:
- if args.last.is_a?(Hash)
- args[-1] = args.last.dup.update(new)
- else
- args.push new
- end
- args
- end
- private_module_function :fu_update_option
-
@fileutils_output = $stderr
@fileutils_label = ''
@@ -1449,14 +1497,19 @@ module FileUtils
end
private_module_function :fu_output_message
- METHODS = singleton_methods() - ['private_module_function']
+ # This hash table holds command options.
+ OPT_TABLE = {} #:nodoc: internal use only
+ (private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|
+ (tbl[name.to_s] = instance_method(name).parameters).map! {|t, n| n if t == :key}.compact!
+ tbl
+ }
#
# Returns an Array of method names which have any options.
#
# p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
#
- def FileUtils.commands
+ def self.commands
OPT_TABLE.keys
end
@@ -1465,8 +1518,8 @@ module FileUtils
#
# p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
#
- def FileUtils.options
- OPT_TABLE.values.flatten.uniq
+ def self.options
+ OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
end
#
@@ -1474,20 +1527,20 @@ module FileUtils
#
# p FileUtils.have_option?(:cp, :noop) #=> true
# p FileUtils.have_option?(:rm, :force) #=> true
- # p FileUtils.have_option?(:rm, :perserve) #=> false
+ # p FileUtils.have_option?(:rm, :preserve) #=> false
#
- def FileUtils.have_option?(mid, opt)
+ def self.have_option?(mid, opt)
li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
- li.include?(opt.to_s)
+ li.include?(opt)
end
#
# Returns an Array of option names of the method +mid+.
#
- # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"]
+ # p FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"]
#
- def FileUtils.options_of(mid)
- OPT_TABLE[mid.to_s]
+ def self.options_of(mid)
+ OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
end
#
@@ -1495,83 +1548,92 @@ module FileUtils
#
# p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
#
- def FileUtils.collect_method(opt)
- OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt.to_s) }
+ def self.collect_method(opt)
+ OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
+ end
+
+ LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern)
+ module LowMethods
+ private
+ def _do_nothing(*)end
+ ::FileUtils::LOW_METHODS.map {|name| alias_method name, :_do_nothing}
end
- #
+ METHODS = singleton_methods() - [:private_module_function,
+ :commands, :options, :have_option?, :options_of, :collect_method]
+
+ #
# This module has all methods of FileUtils module, but it outputs messages
# before acting. This equates to passing the <tt>:verbose</tt> flag to
# methods in FileUtils.
- #
+ #
module Verbose
include FileUtils
@fileutils_output = $stderr
@fileutils_label = ''
- ::FileUtils.collect_method('verbose').each do |name|
+ names = ::FileUtils.collect_method(:verbose)
+ names.each do |name|
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{name}(*args)
- super(*fu_update_option(args, :verbose => true))
+ def #{name}(*args, **options)
+ super(*args, **options, verbose: true)
end
- private :#{name}
EOS
end
+ private(*names)
extend self
class << self
- ::FileUtils::METHODS.each do |m|
- public m
- end
+ public(*::FileUtils::METHODS)
end
end
- #
+ #
# This module has all methods of FileUtils module, but never changes
# files/directories. This equates to passing the <tt>:noop</tt> flag
# to methods in FileUtils.
- #
+ #
module NoWrite
include FileUtils
+ include LowMethods
@fileutils_output = $stderr
@fileutils_label = ''
- ::FileUtils.collect_method('noop').each do |name|
+ names = ::FileUtils.collect_method(:noop)
+ names.each do |name|
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{name}(*args)
- super(*fu_update_option(args, :noop => true))
+ def #{name}(*args, **options)
+ super(*args, **options, noop: true)
end
- private :#{name}
EOS
end
+ private(*names)
extend self
class << self
- ::FileUtils::METHODS.each do |m|
- public m
- end
+ public(*::FileUtils::METHODS)
end
end
- #
+ #
# This module has all methods of FileUtils module, but never changes
# files/directories, with printing message before acting.
# This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
# to methods in FileUtils.
- #
+ #
module DryRun
include FileUtils
+ include LowMethods
@fileutils_output = $stderr
@fileutils_label = ''
- ::FileUtils.collect_method('noop').each do |name|
+ names = ::FileUtils.collect_method(:noop)
+ names.each do |name|
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{name}(*args)
- super(*fu_update_option(args, :noop => true, :verbose => true))
+ def #{name}(*args, **options)
+ super(*args, **options, noop: true, verbose: true)
end
- private :#{name}
EOS
end
+ private(*names)
extend self
class << self
- ::FileUtils::METHODS.each do |m|
- public m
- end
+ public(*::FileUtils::METHODS)
end
end