4 # Copyright (c) 2000-2006 Minero Aoki
6 # This program is free software.
7 # You can distribute/modify this program under the same terms of ruby.
11 # Namespace for several file utility methods for copying, moving, removing, etc.
13 # === Module Functions
16 # cd(dir, options) {|dir| .... }
19 # mkdir(list, options)
20 # mkdir_p(dir, options)
21 # mkdir_p(list, options)
23 # rmdir(list, options)
24 # ln(old, new, options)
25 # ln(list, destdir, options)
26 # ln_s(old, new, options)
27 # ln_s(list, destdir, options)
28 # ln_sf(src, dest, options)
29 # cp(src, dest, options)
30 # cp(list, dir, options)
31 # cp_r(src, dest, options)
32 # cp_r(list, dir, options)
33 # mv(src, dest, options)
34 # mv(list, dir, options)
37 # rm_rf(list, options)
38 # install(src, dest, mode = <src's>, options)
39 # chmod(mode, list, options)
40 # chmod_R(mode, list, options)
41 # chown(user, group, list, options)
42 # chown_R(user, group, list, options)
43 # touch(list, options)
45 # The <tt>options</tt> parameter is a hash of options, taken from the list
46 # <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
47 # <tt>:noop</tt> means that no changes are made. The other two are obvious.
48 # Each method documents the options that it honours.
50 # All methods that have the concept of a "source" file or directory can take
51 # either one file or a list of files in that argument. See the method
52 # documentation for examples.
54 # There are some `low level' methods, which do not accept any option:
56 # copy_entry(src, dest, preserve = false, dereference = false)
57 # copy_file(src, dest, preserve = false, dereference = true)
58 # copy_stream(srcstream, deststream)
59 # remove_entry(path, force = false)
60 # remove_entry_secure(path, force = false)
61 # remove_file(path, force = false)
62 # compare_file(path_a, path_b)
63 # compare_stream(stream_a, stream_b)
64 # uptodate?(file, cmp_list)
66 # == module FileUtils::Verbose
68 # This module has all methods of FileUtils module, but it outputs messages
69 # before acting. This equates to passing the <tt>:verbose</tt> flag to methods
72 # == module FileUtils::NoWrite
74 # This module has all methods of FileUtils module, but never changes
75 # files/directories. This equates to passing the <tt>:noop</tt> flag to methods
78 # == module FileUtils::DryRun
80 # This module has all methods of FileUtils module, but never changes
81 # files/directories. This equates to passing the <tt>:noop</tt> and
82 # <tt>:verbose</tt> flags to methods in FileUtils.
87 def self.private_module_function(name) #:nodoc:
89 private_class_method name
92 # This hash table holds command options.
93 OPT_TABLE = {} #:nodoc: internal use only
98 # Returns the name of the current directory.
106 module_function :getwd
111 # Changes the current directory to the directory +dir+.
113 # If this method is called with block, resumes to the old
114 # working directory after the block execution finished.
116 # FileUtils.cd('/', :verbose => true) # chdir and report it
118 def cd(dir, options = {}, &block) # :yield: dir
119 fu_check_options options, OPT_TABLE['cd']
120 fu_output_message "cd #{dir}" if options[:verbose]
121 Dir.chdir(dir, &block)
122 fu_output_message 'cd -' if options[:verbose] and block
127 module_function :chdir
130 OPT_TABLE['chdir'] = [:verbose]
135 # Returns true if +newer+ is newer than all +old_list+.
136 # Non-existent files are older than any file.
138 # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
139 # system 'make hello.o'
141 def uptodate?(new, old_list, options = nil)
142 raise ArgumentError, 'uptodate? does not accept any option' if options
144 return false unless File.exist?(new)
145 new_time = File.mtime(new)
146 old_list.each do |old|
148 return false unless new_time > File.mtime(old)
153 module_function :uptodate?
156 # Options: mode noop verbose
158 # Creates one or more directories.
160 # FileUtils.mkdir 'test'
161 # FileUtils.mkdir %w( tmp data )
162 # FileUtils.mkdir 'notexist', :noop => true # Does not really create.
163 # FileUtils.mkdir 'tmp', :mode => 0700
165 def mkdir(list, options = {})
166 fu_check_options options, OPT_TABLE['mkdir']
168 fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
169 return if options[:noop]
172 fu_mkdir dir, options[:mode]
175 module_function :mkdir
177 OPT_TABLE['mkdir'] = [:mode, :noop, :verbose]
180 # Options: mode noop verbose
182 # Creates a directory and all its parent directories.
185 # FileUtils.mkdir_p '/usr/local/lib/ruby'
187 # causes to make following directories, if it does not exist.
191 # * /usr/local/lib/ruby
193 # You can pass several directories at a time in a list.
195 def mkdir_p(list, options = {})
196 fu_check_options options, OPT_TABLE['mkdir_p']
198 fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
199 return *list if options[:noop]
201 list.map {|path| path.sub(%r</\z>, '') }.each do |path|
202 # optimize for the most common case
204 fu_mkdir path, options[:mode]
206 rescue SystemCallError
207 next if File.directory?(path)
211 until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
213 path = File.dirname(path)
215 stack.reverse_each do |path|
217 fu_mkdir path, options[:mode]
218 rescue SystemCallError => err
219 raise unless File.directory?(path)
226 module_function :mkdir_p
229 alias makedirs mkdir_p
230 module_function :mkpath
231 module_function :makedirs
233 OPT_TABLE['mkdir_p'] =
234 OPT_TABLE['mkpath'] =
235 OPT_TABLE['makedirs'] = [:mode, :noop, :verbose]
237 def fu_mkdir(path, mode) #:nodoc:
238 path = path.sub(%r</\z>, '')
241 File.chmod mode, path
246 private_module_function :fu_mkdir
249 # Options: noop, verbose
251 # Removes one or more directories.
253 # FileUtils.rmdir 'somedir'
254 # FileUtils.rmdir %w(somedir anydir otherdir)
255 # # Does not really remove directory; outputs message.
256 # FileUtils.rmdir 'somedir', :verbose => true, :noop => true
258 def rmdir(list, options = {})
259 fu_check_options options, OPT_TABLE['rmdir']
261 fu_output_message "rmdir #{list.join ' '}" if options[:verbose]
262 return if options[:noop]
264 Dir.rmdir dir.sub(%r</\z>, '')
267 module_function :rmdir
269 OPT_TABLE['rmdir'] = [:noop, :verbose]
272 # Options: force noop verbose
274 # <b><tt>ln(old, new, options = {})</tt></b>
276 # Creates a hard link +new+ which points to +old+.
277 # If +new+ already exists and it is a directory, creates a link +new/old+.
278 # If +new+ already exists and it is not a directory, raises Errno::EEXIST.
279 # But if :force option is set, overwrite +new+.
281 # FileUtils.ln 'gcc', 'cc', :verbose => true
282 # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
284 # <b><tt>ln(list, destdir, options = {})</tt></b>
286 # Creates several hard links in a directory, with each one pointing to the
287 # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR.
291 # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
293 def ln(src, dest, options = {})
294 fu_check_options options, OPT_TABLE['ln']
295 fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
296 return if options[:noop]
297 fu_each_src_dest0(src, dest) do |s,d|
298 remove_file d, true if options[:force]
305 module_function :link
308 OPT_TABLE['link'] = [:force, :noop, :verbose]
311 # Options: force noop verbose
313 # <b><tt>ln_s(old, new, options = {})</tt></b>
315 # Creates a symbolic link +new+ which points to +old+. If +new+ already
316 # exists and it is a directory, creates a symbolic link +new/old+. If +new+
317 # already exists and it is not a directory, raises Errno::EEXIST. But if
318 # :force option is set, overwrite +new+.
320 # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
321 # FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true
323 # <b><tt>ln_s(list, destdir, options = {})</tt></b>
325 # Creates several symbolic links in a directory, with each one pointing to the
326 # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR.
328 # If +destdir+ is not a directory, raises Errno::ENOTDIR.
330 # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
332 def ln_s(src, dest, options = {})
333 fu_check_options options, OPT_TABLE['ln_s']
334 fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
335 return if options[:noop]
336 fu_each_src_dest0(src, dest) do |s,d|
337 remove_file d, true if options[:force]
341 module_function :ln_s
344 module_function :symlink
347 OPT_TABLE['symlink'] = [:force, :noop, :verbose]
350 # Options: noop verbose
353 # #ln_s(src, dest, :force)
355 def ln_sf(src, dest, options = {})
356 fu_check_options options, OPT_TABLE['ln_sf']
357 options = options.dup
358 options[:force] = true
359 ln_s src, dest, options
361 module_function :ln_sf
363 OPT_TABLE['ln_sf'] = [:noop, :verbose]
366 # Options: preserve noop verbose
368 # Copies a file content +src+ to +dest+. If +dest+ is a directory,
369 # copies +src+ to +dest/src+.
371 # If +src+ is a list of files, then +dest+ must be a directory.
373 # FileUtils.cp 'eval.c', 'eval.c.org'
374 # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
375 # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
376 # FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
378 def cp(src, dest, options = {})
379 fu_check_options options, OPT_TABLE['cp']
380 fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
381 return if options[:noop]
382 fu_each_src_dest(src, dest) do |s, d|
383 copy_file s, d, options[:preserve]
389 module_function :copy
392 OPT_TABLE['copy'] = [:preserve, :noop, :verbose]
395 # Options: preserve noop verbose dereference_root remove_destination
397 # Copies +src+ to +dest+. If +src+ is a directory, this method copies
398 # all its contents recursively. If +dest+ is a directory, copies
399 # +src+ to +dest/src+.
401 # +src+ can be a list of files.
403 # # Installing ruby library "mylib" under the site_ruby
404 # FileUtils.rm_r site_ruby + '/mylib', :force
405 # FileUtils.cp_r 'lib/', site_ruby + '/mylib'
407 # # Examples of copying several files to target directory.
408 # FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
409 # FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true
411 # # If you want to copy all contents of a directory instead of the
412 # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
413 # # use following code.
414 # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest,
415 # # but this doesn't.
417 def cp_r(src, dest, options = {})
418 fu_check_options options, OPT_TABLE['cp_r']
419 fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
420 return if options[:noop]
421 options[:dereference_root] = true unless options.key?(:dereference_root)
422 fu_each_src_dest(src, dest) do |s, d|
423 copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
426 module_function :cp_r
428 OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose,
429 :dereference_root, :remove_destination]
432 # Copies a file system entry +src+ to +dest+.
433 # If +src+ is a directory, this method copies its contents recursively.
434 # This method preserves file types, c.f. symlink, directory...
435 # (FIFO, device files and etc. are not supported yet)
437 # Both of +src+ and +dest+ must be a path name.
438 # +src+ must exist, +dest+ must not exist.
440 # If +preserve+ is true, this method preserves owner, group, permissions
443 # If +dereference_root+ is true, this method dereference tree root.
445 # If +remove_destination+ is true, this method removes each destination file before copy.
447 def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
448 Entry_.new(src, nil, dereference_root).traverse do |ent|
449 destent = Entry_.new(dest, ent.rel, false)
450 File.unlink destent.path if remove_destination && File.file?(destent.path)
451 ent.copy destent.path
452 ent.copy_metadata destent.path if preserve
455 module_function :copy_entry
458 # Copies file contents of +src+ to +dest+.
459 # Both of +src+ and +dest+ must be a path name.
461 def copy_file(src, dest, preserve = false, dereference = true)
462 ent = Entry_.new(src, nil, dereference)
464 ent.copy_metadata dest if preserve
466 module_function :copy_file
469 # Copies stream +src+ to +dest+.
470 # +src+ must respond to #read(n) and
471 # +dest+ must respond to #write(str).
473 def copy_stream(src, dest)
474 fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
476 module_function :copy_stream
479 # Options: force noop verbose
481 # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
482 # disk partition, the file is copied instead.
484 # FileUtils.mv 'badname.rb', 'goodname.rb'
485 # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
487 # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
488 # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
490 def mv(src, dest, options = {})
491 fu_check_options options, OPT_TABLE['mv']
492 fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
493 return if options[:noop]
494 fu_each_src_dest(src, dest) do |s, d|
495 destent = Entry_.new(d, nil, true)
498 if destent.directory?
499 raise Errno::EEXIST, dest
501 destent.remove_file if rename_cannot_overwrite_file?
507 copy_entry s, d, true
509 remove_entry_secure s, options[:force]
511 remove_entry s, options[:force]
514 rescue SystemCallError
515 raise unless options[:force]
522 module_function :move
525 OPT_TABLE['move'] = [:force, :noop, :verbose, :secure]
527 def rename_cannot_overwrite_file? #:nodoc:
528 /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
530 private_module_function :rename_cannot_overwrite_file?
533 # Options: force noop verbose
535 # Remove file(s) specified in +list+. This method cannot remove directories.
536 # All StandardErrors are ignored when the :force option is set.
538 # FileUtils.rm %w( junk.txt dust.txt )
539 # FileUtils.rm Dir.glob('*.so')
540 # FileUtils.rm 'NotExistFile', :force => true # never raises exception
542 def rm(list, options = {})
543 fu_check_options options, OPT_TABLE['rm']
545 fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
546 return if options[:noop]
549 remove_file path, options[:force]
555 module_function :remove
558 OPT_TABLE['remove'] = [:force, :noop, :verbose]
561 # Options: noop verbose
565 # #rm(list, :force => true)
567 def rm_f(list, options = {})
568 fu_check_options options, OPT_TABLE['rm_f']
569 options = options.dup
570 options[:force] = true
573 module_function :rm_f
575 alias safe_unlink rm_f
576 module_function :safe_unlink
579 OPT_TABLE['safe_unlink'] = [:noop, :verbose]
582 # Options: force noop verbose secure
584 # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
585 # removes its all contents recursively. This method ignores
586 # StandardError when :force option is set.
588 # FileUtils.rm_r Dir.glob('/tmp/*')
589 # FileUtils.rm_r '/', :force => true # :-)
591 # WARNING: This method causes local vulnerability
592 # if one of parent directories or removing directory tree are world
593 # writable (including /tmp, whose permission is 1777), and the current
594 # process has strong privilege such as Unix super user (root), and the
595 # system has symbolic link. For secure removing, read the documentation
596 # of #remove_entry_secure carefully, and set :secure option to true.
597 # Default is :secure=>false.
599 # NOTE: This method calls #remove_entry_secure if :secure option is set.
600 # See also #remove_entry_secure.
602 def rm_r(list, options = {})
603 fu_check_options options, OPT_TABLE['rm_r']
604 # options[:secure] = true unless options.key?(:secure)
606 fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
607 return if options[:noop]
610 remove_entry_secure path, options[:force]
612 remove_entry path, options[:force]
616 module_function :rm_r
618 OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure]
621 # Options: noop verbose secure
625 # #rm_r(list, :force => true)
627 # WARNING: This method causes local vulnerability.
628 # Read the documentation of #rm_r first.
630 def rm_rf(list, options = {})
631 fu_check_options options, OPT_TABLE['rm_rf']
632 options = options.dup
633 options[:force] = true
636 module_function :rm_rf
639 module_function :rmtree
642 OPT_TABLE['rmtree'] = [:noop, :verbose, :secure]
645 # This method removes a file system entry +path+. +path+ shall be a
646 # regular file, a directory, or something. If +path+ is a directory,
647 # remove it recursively. This method is required to avoid TOCTTOU
648 # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
649 # #rm_r causes security hole when:
651 # * Parent directory is world writable (including /tmp).
652 # * Removing directory tree includes world writable directory.
653 # * The system has symbolic link.
655 # To avoid this security hole, this method applies special preprocess.
656 # If +path+ is a directory, this method chown(2) and chmod(2) all
657 # removing directories. This requires the current process is the
658 # owner of the removing whole directory tree, or is the super user (root).
660 # WARNING: You must ensure that *ALL* parent directories are not
661 # world writable. Otherwise this method does not work.
662 # Only exception is temporary directory like /tmp and /var/tmp,
663 # whose permission is 1777.
665 # WARNING: Only the owner of the removing directory tree, or Unix super
666 # user (root) should invoke this method. Otherwise this method does not
669 # For details of this security vulnerability, see Perl's case:
671 # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
672 # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
674 # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
676 def remove_entry_secure(path, force = false)
677 unless fu_have_symlink?
678 remove_entry path, force
681 fullpath = File.expand_path(path)
682 st = File.lstat(fullpath)
688 parent_st = File.stat(File.dirname(fullpath))
689 unless fu_world_writable?(parent_st)
690 remove_entry path, force
693 unless parent_st.sticky?
694 raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
698 File.open(fullpath + '/.') {|f|
699 unless fu_stat_identical_entry?(st, f.stat)
700 # symlink (TOC-to-TOU attack?)
707 # ---- tree root is frozen ----
708 root = Entry_.new(path)
709 root.preorder_traverse do |ent|
715 root.postorder_traverse do |ent|
725 module_function :remove_entry_secure
727 def fu_world_writable?(st)
728 (st.mode & 0002) != 0
730 private_module_function :fu_world_writable?
732 def fu_have_symlink? #:nodoc
733 File.symlink nil, nil
734 rescue NotImplementedError
739 private_module_function :fu_have_symlink?
741 def fu_stat_identical_entry?(a, b) #:nodoc:
742 a.dev == b.dev and a.ino == b.ino
744 private_module_function :fu_stat_identical_entry?
747 # This method removes a file system entry +path+.
748 # +path+ might be a regular file, a directory, or something.
749 # If +path+ is a directory, remove it recursively.
751 # See also #remove_entry_secure.
753 def remove_entry(path, force = false)
754 Entry_.new(path).postorder_traverse do |ent|
764 module_function :remove_entry
767 # Removes a file +path+.
768 # This method ignores StandardError if +force+ is true.
770 def remove_file(path, force = false)
771 Entry_.new(path).remove_file
775 module_function :remove_file
778 # Removes a directory +dir+ and its contents recursively.
779 # This method ignores StandardError if +force+ is true.
781 def remove_dir(path, force = false)
782 remove_entry path, force # FIXME?? check if it is a directory
784 module_function :remove_dir
787 # Returns true if the contents of a file A and a file B are identical.
789 # FileUtils.compare_file('somefile', 'somefile') #=> true
790 # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false
792 def compare_file(a, b)
793 return false unless File.size(a) == File.size(b)
794 File.open(a, 'rb') {|fa|
795 File.open(b, 'rb') {|fb|
796 return compare_stream(fa, fb)
800 module_function :compare_file
802 alias identical? compare_file
803 alias cmp compare_file
804 module_function :identical?
808 # Returns true if the contents of a stream +a+ and +b+ are identical.
810 def compare_stream(a, b)
811 bsize = fu_stream_blksize(a, b)
817 if sa.nil? and sb.nil?
824 module_function :compare_stream
827 # Options: mode preserve noop verbose
829 # If +src+ is not same as +dest+, copies it and changes the permission
830 # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
831 # This method removes destination before copy.
833 # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
834 # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
836 def install(src, dest, options = {})
837 fu_check_options options, OPT_TABLE['install']
838 fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
839 return if options[:noop]
840 fu_each_src_dest(src, dest) do |s, d|
841 unless File.exist?(d) and compare_file(s, d)
843 st = File.stat(s) if options[:preserve]
845 File.utime st.atime, st.mtime, d if options[:preserve]
846 File.chmod options[:mode], d if options[:mode]
850 module_function :install
852 OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose]
855 # Options: noop verbose
857 # Changes permission bits on the named files (in +list+) to the bit pattern
858 # represented by +mode+.
860 # FileUtils.chmod 0755, 'somecommand'
861 # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
862 # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
864 def chmod(mode, list, options = {})
865 fu_check_options options, OPT_TABLE['chmod']
867 fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
868 return if options[:noop]
870 Entry_.new(path).chmod mode
873 module_function :chmod
875 OPT_TABLE['chmod'] = [:noop, :verbose]
878 # Options: noop verbose force
880 # Changes permission bits on the named files (in +list+)
881 # to the bit pattern represented by +mode+.
883 # FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
885 def chmod_R(mode, list, options = {})
886 fu_check_options options, OPT_TABLE['chmod_R']
888 fu_output_message sprintf('chmod -R%s %o %s',
889 (options[:force] ? 'f' : ''),
890 mode, list.join(' ')) if options[:verbose]
891 return if options[:noop]
893 Entry_.new(root).traverse do |ent|
897 raise unless options[:force]
902 module_function :chmod_R
904 OPT_TABLE['chmod_R'] = [:noop, :verbose, :force]
907 # Options: noop verbose
909 # Changes owner and group on the named files (in +list+)
910 # to the user +user+ and the group +group+. +user+ and +group+
911 # may be an ID (Integer/String) or a name (String).
912 # If +user+ or +group+ is nil, this method does not change
915 # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
916 # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
918 def chown(user, group, list, options = {})
919 fu_check_options options, OPT_TABLE['chown']
921 fu_output_message sprintf('chown %s%s',
922 [user,group].compact.join(':') + ' ',
923 list.join(' ')) if options[:verbose]
924 return if options[:noop]
925 uid = fu_get_uid(user)
926 gid = fu_get_gid(group)
928 Entry_.new(path).chown uid, gid
931 module_function :chown
933 OPT_TABLE['chown'] = [:noop, :verbose]
936 # Options: noop verbose force
938 # Changes owner and group on the named files (in +list+)
939 # to the user +user+ and the group +group+ recursively.
940 # +user+ and +group+ may be an ID (Integer/String) or
941 # a name (String). If +user+ or +group+ is nil, this
942 # method does not change the attribute.
944 # FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
945 # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
947 def chown_R(user, group, list, options = {})
948 fu_check_options options, OPT_TABLE['chown_R']
950 fu_output_message sprintf('chown -R%s %s%s',
951 (options[:force] ? 'f' : ''),
952 [user,group].compact.join(':') + ' ',
953 list.join(' ')) if options[:verbose]
954 return if options[:noop]
955 uid = fu_get_uid(user)
956 gid = fu_get_gid(group)
957 return unless uid or gid
959 Entry_.new(root).traverse do |ent|
963 raise unless options[:force]
968 module_function :chown_R
970 OPT_TABLE['chown_R'] = [:noop, :verbose, :force]
975 def fu_get_uid(user) #:nodoc:
976 return nil unless user
980 else Etc.getpwnam(user).uid
983 private_module_function :fu_get_uid
985 def fu_get_gid(group) #:nodoc:
986 return nil unless group
987 if /\A\d+\z/ =~ group
989 else Etc.getgrnam(group).gid
992 private_module_function :fu_get_gid
995 # need Win32 support???
997 def fu_get_uid(user) #:nodoc:
1000 private_module_function :fu_get_uid
1002 def fu_get_gid(group) #:nodoc:
1005 private_module_function :fu_get_gid
1009 # Options: noop verbose
1011 # Updates modification time (mtime) and access time (atime) of file(s) in
1012 # +list+. Files are created if they don't exist.
1014 # FileUtils.touch 'timestamp'
1015 # FileUtils.touch Dir.glob('*.c'); system 'make'
1017 def touch(list, options = {})
1018 fu_check_options options, OPT_TABLE['touch']
1019 list = fu_list(list)
1020 created = nocreate = options[:nocreate]
1022 if options[:verbose]
1023 fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}"
1025 return if options[:noop]
1029 File.utime(t, t, path)
1030 rescue Errno::ENOENT
1032 File.open(path, 'a') {
1040 module_function :touch
1042 OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate]
1050 /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
1053 def fu_copy_stream0(src, dest, blksize) #:nodoc:
1054 # FIXME: readpartial?
1055 while s = src.read(blksize)
1060 def fu_stream_blksize(*streams)
1062 next unless s.respond_to?(:stat)
1063 size = fu_blksize(s.stat)
1066 fu_default_blksize()
1072 return nil if s == 0
1076 def fu_default_blksize
1081 include StreamUtils_
1084 class Entry_ #:nodoc: internal use only
1085 include StreamUtils_
1087 def initialize(a, b = nil, deref = false)
1088 @prefix = @rel = @path = nil
1101 "\#<#{self.class} #{path()}>"
1125 lstat! ? true : false
1167 s and (s.mode & 0xF000 == S_IF_DOOR)
1171 Dir.entries(path())\
1172 .reject {|n| n == '.' or n == '..' }\
1173 .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
1177 return @stat if @stat
1178 if lstat() and lstat().symlink?
1179 @stat = File.stat(path())
1187 return @stat if @stat
1188 if lstat! and lstat!.symlink?
1189 @stat = File.stat(path())
1194 rescue SystemCallError
1200 @lstat ||= File.stat(path())
1202 @lstat ||= File.lstat(path())
1208 rescue SystemCallError
1214 File.lchmod mode, path() if have_lchmod?
1216 File.chmod mode, path()
1222 File.lchown uid, gid, path() if have_lchown?
1224 File.chown uid, gid, path()
1236 raise unless File.directory?(dest)
1239 File.symlink File.readlink(path()), dest
1241 raise "cannot handle device file" unless File.respond_to?(:mknod)
1242 mknod dest, ?c, 0666, lstat().rdev
1244 raise "cannot handle device file" unless File.respond_to?(:mknod)
1245 mknod dest, ?b, 0666, lstat().rdev
1247 raise "cannot handle socket" unless File.respond_to?(:mknod)
1248 mknod dest, nil, lstat().mode, 0
1250 raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
1253 raise "cannot handle door: #{path()}"
1255 raise "unknown file type: #{path()}"
1261 File.open(path(), 'rb') {|r|
1262 File.open(dest, 'wb', st.mode) {|w|
1263 fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
1268 def copy_metadata(path)
1270 File.utime st.atime, st.mtime, path
1272 File.chown st.uid, st.gid, path
1274 # clear setuid/setgid
1275 File.chmod st.mode & 01777, path
1277 File.chmod st.mode, path
1291 Dir.rmdir path().sub(%r</\z>, '')
1301 def platform_support
1302 return yield unless fu_windows?
1306 rescue Errno::ENOENT
1310 first_time_p = false
1312 File.chmod 0700, path() # Windows does not have symlink
1314 rescue SystemCallError
1321 def preorder_traverse
1323 while ent = stack.pop
1325 stack.concat ent.entries.reverse if ent.directory?
1329 alias traverse preorder_traverse
1331 def postorder_traverse
1333 entries().each do |ent|
1334 ent.postorder_traverse do |e|
1344 $fileutils_rb_have_lchmod = nil
1347 # This is not MT-safe, but it does not matter.
1348 if $fileutils_rb_have_lchmod.nil?
1349 $fileutils_rb_have_lchmod = check_have_lchmod?
1351 $fileutils_rb_have_lchmod
1354 def check_have_lchmod?
1355 return false unless File.respond_to?(:lchmod)
1358 rescue NotImplementedError
1362 $fileutils_rb_have_lchown = nil
1365 # This is not MT-safe, but it does not matter.
1366 if $fileutils_rb_have_lchown.nil?
1367 $fileutils_rb_have_lchown = check_have_lchown?
1369 $fileutils_rb_have_lchown
1372 def check_have_lchown?
1373 return false unless File.respond_to?(:lchown)
1374 File.lchown nil, nil
1376 rescue NotImplementedError
1381 return dir.to_str if not base or base == '.'
1382 return base.to_str if not dir or dir == '.'
1383 File.join(dir, base)
1387 def fu_list(arg) #:nodoc:
1388 [arg].flatten.map {|path| path.to_str }
1390 private_module_function :fu_list
1392 def fu_each_src_dest(src, dest) #:nodoc:
1393 fu_each_src_dest0(src, dest) do |s, d|
1394 raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
1398 private_module_function :fu_each_src_dest
1400 def fu_each_src_dest0(src, dest) #:nodoc:
1404 yield s, File.join(dest, File.basename(s))
1408 if File.directory?(dest)
1409 yield src, File.join(dest, File.basename(src))
1411 yield src, dest.to_str
1415 private_module_function :fu_each_src_dest0
1417 def fu_same?(a, b) #:nodoc:
1421 st1.dev == st2.dev and st1.ino == st2.ino
1423 File.expand_path(a) == File.expand_path(b)
1425 rescue Errno::ENOENT
1428 private_module_function :fu_same?
1430 def fu_have_st_ino? #:nodoc:
1433 private_module_function :fu_have_st_ino?
1435 def fu_check_options(options, optdecl) #:nodoc:
1437 optdecl.each do |opt|
1440 raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
1442 private_module_function :fu_check_options
1444 def fu_update_option(args, new) #:nodoc:
1445 if args.last.is_a?(Hash)
1446 args[-1] = args.last.dup.update(new)
1452 private_module_function :fu_update_option
1454 @fileutils_output = $stderr
1455 @fileutils_label = ''
1457 def fu_output_message(msg) #:nodoc:
1458 @fileutils_output ||= $stderr
1459 @fileutils_label ||= ''
1460 @fileutils_output.puts @fileutils_label + msg
1462 private_module_function :fu_output_message
1465 # Returns an Array of method names which have any options.
1467 # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
1469 def FileUtils.commands
1474 # Returns an Array of option names.
1476 # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
1478 def FileUtils.options
1479 OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
1483 # Returns true if the method +mid+ have an option +opt+.
1485 # p FileUtils.have_option?(:cp, :noop) #=> true
1486 # p FileUtils.have_option?(:rm, :force) #=> true
1487 # p FileUtils.have_option?(:rm, :perserve) #=> false
1489 def FileUtils.have_option?(mid, opt)
1490 li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
1495 # Returns an Array of option names of the method +mid+.
1497 # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"]
1499 def FileUtils.options_of(mid)
1500 OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
1504 # Returns an Array of method names which have the option +opt+.
1506 # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
1508 def FileUtils.collect_method(opt)
1509 OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
1512 METHODS = singleton_methods() - %w( private_module_function
1513 commands options have_option? options_of collect_method )
1516 # This module has all methods of FileUtils module, but it outputs messages
1517 # before acting. This equates to passing the <tt>:verbose</tt> flag to
1518 # methods in FileUtils.
1522 @fileutils_output = $stderr
1523 @fileutils_label = ''
1524 ::FileUtils.collect_method(:verbose).each do |name|
1525 module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1527 super(*fu_update_option(args, :verbose => true))
1534 ::FileUtils::METHODS.each do |m|
1541 # This module has all methods of FileUtils module, but never changes
1542 # files/directories. This equates to passing the <tt>:noop</tt> flag
1543 # to methods in FileUtils.
1547 @fileutils_output = $stderr
1548 @fileutils_label = ''
1549 ::FileUtils.collect_method(:noop).each do |name|
1550 module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1552 super(*fu_update_option(args, :noop => true))
1559 ::FileUtils::METHODS.each do |m|
1566 # This module has all methods of FileUtils module, but never changes
1567 # files/directories, with printing message before acting.
1568 # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
1569 # to methods in FileUtils.
1573 @fileutils_output = $stderr
1574 @fileutils_label = ''
1575 ::FileUtils.collect_method(:noop).each do |name|
1576 module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1578 super(*fu_update_option(args, :noop => true, :verbose => true))
1585 ::FileUtils::METHODS.each do |m|