4 # Copyright (c) 2000-2007 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 |dir|
217 fu_mkdir dir, options[:mode]
218 rescue SystemCallError => err
219 raise unless File.directory?(dir)
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 fu_each_src_dest(src, dest) do |s, d|
422 copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
425 module_function :cp_r
427 OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose,
428 :dereference_root, :remove_destination]
431 # Copies a file system entry +src+ to +dest+.
432 # If +src+ is a directory, this method copies its contents recursively.
433 # This method preserves file types, c.f. symlink, directory...
434 # (FIFO, device files and etc. are not supported yet)
436 # Both of +src+ and +dest+ must be a path name.
437 # +src+ must exist, +dest+ must not exist.
439 # If +preserve+ is true, this method preserves owner, group, permissions
442 # If +dereference_root+ is true, this method dereference tree root.
444 # If +remove_destination+ is true, this method removes each destination file before copy.
446 def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
447 Entry_.new(src, nil, dereference_root).traverse do |ent|
448 destent = Entry_.new(dest, ent.rel, false)
449 File.unlink destent.path if remove_destination && File.file?(destent.path)
450 ent.copy destent.path
451 ent.copy_metadata destent.path if preserve
454 module_function :copy_entry
457 # Copies file contents of +src+ to +dest+.
458 # Both of +src+ and +dest+ must be a path name.
460 def copy_file(src, dest, preserve = false, dereference = true)
461 ent = Entry_.new(src, nil, dereference)
463 ent.copy_metadata dest if preserve
465 module_function :copy_file
468 # Copies stream +src+ to +dest+.
469 # +src+ must respond to #read(n) and
470 # +dest+ must respond to #write(str).
472 def copy_stream(src, dest)
473 fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
475 module_function :copy_stream
478 # Options: force noop verbose
480 # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
481 # disk partition, the file is copied instead.
483 # FileUtils.mv 'badname.rb', 'goodname.rb'
484 # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
486 # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
487 # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
489 def mv(src, dest, options = {})
490 fu_check_options options, OPT_TABLE['mv']
491 fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
492 return if options[:noop]
493 fu_each_src_dest(src, dest) do |s, d|
494 destent = Entry_.new(d, nil, true)
497 if destent.directory?
498 raise Errno::EEXIST, dest
500 destent.remove_file if rename_cannot_overwrite_file?
506 copy_entry s, d, true
508 remove_entry_secure s, options[:force]
510 remove_entry s, options[:force]
513 rescue SystemCallError
514 raise unless options[:force]
521 module_function :move
524 OPT_TABLE['move'] = [:force, :noop, :verbose, :secure]
526 def rename_cannot_overwrite_file? #:nodoc:
527 /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
529 private_module_function :rename_cannot_overwrite_file?
532 # Options: force noop verbose
534 # Remove file(s) specified in +list+. This method cannot remove directories.
535 # All StandardErrors are ignored when the :force option is set.
537 # FileUtils.rm %w( junk.txt dust.txt )
538 # FileUtils.rm Dir.glob('*.so')
539 # FileUtils.rm 'NotExistFile', :force => true # never raises exception
541 def rm(list, options = {})
542 fu_check_options options, OPT_TABLE['rm']
544 fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
545 return if options[:noop]
548 remove_file path, options[:force]
554 module_function :remove
557 OPT_TABLE['remove'] = [:force, :noop, :verbose]
560 # Options: noop verbose
564 # #rm(list, :force => true)
566 def rm_f(list, options = {})
567 fu_check_options options, OPT_TABLE['rm_f']
568 options = options.dup
569 options[:force] = true
572 module_function :rm_f
574 alias safe_unlink rm_f
575 module_function :safe_unlink
578 OPT_TABLE['safe_unlink'] = [:noop, :verbose]
581 # Options: force noop verbose secure
583 # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
584 # removes its all contents recursively. This method ignores
585 # StandardError when :force option is set.
587 # FileUtils.rm_r Dir.glob('/tmp/*')
588 # FileUtils.rm_r '/', :force => true # :-)
590 # WARNING: This method causes local vulnerability
591 # if one of parent directories or removing directory tree are world
592 # writable (including /tmp, whose permission is 1777), and the current
593 # process has strong privilege such as Unix super user (root), and the
594 # system has symbolic link. For secure removing, read the documentation
595 # of #remove_entry_secure carefully, and set :secure option to true.
596 # Default is :secure=>false.
598 # NOTE: This method calls #remove_entry_secure if :secure option is set.
599 # See also #remove_entry_secure.
601 def rm_r(list, options = {})
602 fu_check_options options, OPT_TABLE['rm_r']
603 # options[:secure] = true unless options.key?(:secure)
605 fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
606 return if options[:noop]
609 remove_entry_secure path, options[:force]
611 remove_entry path, options[:force]
615 module_function :rm_r
617 OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure]
620 # Options: noop verbose secure
624 # #rm_r(list, :force => true)
626 # WARNING: This method causes local vulnerability.
627 # Read the documentation of #rm_r first.
629 def rm_rf(list, options = {})
630 fu_check_options options, OPT_TABLE['rm_rf']
631 options = options.dup
632 options[:force] = true
635 module_function :rm_rf
638 module_function :rmtree
641 OPT_TABLE['rmtree'] = [:noop, :verbose, :secure]
644 # This method removes a file system entry +path+. +path+ shall be a
645 # regular file, a directory, or something. If +path+ is a directory,
646 # remove it recursively. This method is required to avoid TOCTTOU
647 # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
648 # #rm_r causes security hole when:
650 # * Parent directory is world writable (including /tmp).
651 # * Removing directory tree includes world writable directory.
652 # * The system has symbolic link.
654 # To avoid this security hole, this method applies special preprocess.
655 # If +path+ is a directory, this method chown(2) and chmod(2) all
656 # removing directories. This requires the current process is the
657 # owner of the removing whole directory tree, or is the super user (root).
659 # WARNING: You must ensure that *ALL* parent directories are not
660 # world writable. Otherwise this method does not work.
661 # Only exception is temporary directory like /tmp and /var/tmp,
662 # whose permission is 1777.
664 # WARNING: Only the owner of the removing directory tree, or Unix super
665 # user (root) should invoke this method. Otherwise this method does not
668 # For details of this security vulnerability, see Perl's case:
670 # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
671 # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
673 # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
675 def remove_entry_secure(path, force = false)
676 unless fu_have_symlink?
677 remove_entry path, force
680 fullpath = File.expand_path(path)
681 st = File.lstat(fullpath)
687 parent_st = File.stat(File.dirname(fullpath))
688 unless parent_st.world_writable?
689 remove_entry path, force
692 unless parent_st.sticky?
693 raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
697 File.open(fullpath + '/.') {|f|
698 unless fu_stat_identical_entry?(st, f.stat)
699 # symlink (TOC-to-TOU attack?)
706 # ---- tree root is frozen ----
707 root = Entry_.new(path)
708 root.preorder_traverse do |ent|
714 root.postorder_traverse do |ent|
724 module_function :remove_entry_secure
726 def fu_have_symlink? #:nodoc
727 File.symlink nil, nil
728 rescue NotImplementedError
733 private_module_function :fu_have_symlink?
735 def fu_stat_identical_entry?(a, b) #:nodoc:
736 a.dev == b.dev and a.ino == b.ino
738 private_module_function :fu_stat_identical_entry?
741 # This method removes a file system entry +path+.
742 # +path+ might be a regular file, a directory, or something.
743 # If +path+ is a directory, remove it recursively.
745 # See also #remove_entry_secure.
747 def remove_entry(path, force = false)
748 Entry_.new(path).postorder_traverse do |ent|
758 module_function :remove_entry
761 # Removes a file +path+.
762 # This method ignores StandardError if +force+ is true.
764 def remove_file(path, force = false)
765 Entry_.new(path).remove_file
769 module_function :remove_file
772 # Removes a directory +dir+ and its contents recursively.
773 # This method ignores StandardError if +force+ is true.
775 def remove_dir(path, force = false)
776 remove_entry path, force # FIXME?? check if it is a directory
778 module_function :remove_dir
781 # Returns true if the contents of a file A and a file B are identical.
783 # FileUtils.compare_file('somefile', 'somefile') #=> true
784 # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false
786 def compare_file(a, b)
787 return false unless File.size(a) == File.size(b)
788 File.open(a, 'rb') {|fa|
789 File.open(b, 'rb') {|fb|
790 return compare_stream(fa, fb)
794 module_function :compare_file
796 alias identical? compare_file
797 alias cmp compare_file
798 module_function :identical?
802 # Returns true if the contents of a stream +a+ and +b+ are identical.
804 def compare_stream(a, b)
805 bsize = fu_stream_blksize(a, b)
811 if sa.nil? and sb.nil?
818 module_function :compare_stream
821 # Options: mode preserve noop verbose
823 # If +src+ is not same as +dest+, copies it and changes the permission
824 # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
825 # This method removes destination before copy.
827 # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
828 # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
830 def install(src, dest, options = {})
831 fu_check_options options, OPT_TABLE['install']
832 fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
833 return if options[:noop]
834 fu_each_src_dest(src, dest) do |s, d|
835 unless File.exist?(d) and compare_file(s, d)
837 st = File.stat(s) if options[:preserve]
839 File.utime st.atime, st.mtime, d if options[:preserve]
840 File.chmod options[:mode], d if options[:mode]
844 module_function :install
846 OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose]
849 # Options: noop verbose
851 # Changes permission bits on the named files (in +list+) to the bit pattern
852 # represented by +mode+.
854 # FileUtils.chmod 0755, 'somecommand'
855 # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
856 # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
858 def chmod(mode, list, options = {})
859 fu_check_options options, OPT_TABLE['chmod']
861 fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
862 return if options[:noop]
864 Entry_.new(path).chmod mode
867 module_function :chmod
869 OPT_TABLE['chmod'] = [:noop, :verbose]
872 # Options: noop verbose force
874 # Changes permission bits on the named files (in +list+)
875 # to the bit pattern represented by +mode+.
877 # FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
879 def chmod_R(mode, list, options = {})
880 fu_check_options options, OPT_TABLE['chmod_R']
882 fu_output_message sprintf('chmod -R%s %o %s',
883 (options[:force] ? 'f' : ''),
884 mode, list.join(' ')) if options[:verbose]
885 return if options[:noop]
887 Entry_.new(root).traverse do |ent|
891 raise unless options[:force]
896 module_function :chmod_R
898 OPT_TABLE['chmod_R'] = [:noop, :verbose, :force]
901 # Options: noop verbose
903 # Changes owner and group on the named files (in +list+)
904 # to the user +user+ and the group +group+. +user+ and +group+
905 # may be an ID (Integer/String) or a name (String).
906 # If +user+ or +group+ is nil, this method does not change
909 # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
910 # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
912 def chown(user, group, list, options = {})
913 fu_check_options options, OPT_TABLE['chown']
915 fu_output_message sprintf('chown %s%s',
916 [user,group].compact.join(':') + ' ',
917 list.join(' ')) if options[:verbose]
918 return if options[:noop]
919 uid = fu_get_uid(user)
920 gid = fu_get_gid(group)
922 Entry_.new(path).chown uid, gid
925 module_function :chown
927 OPT_TABLE['chown'] = [:noop, :verbose]
930 # Options: noop verbose force
932 # Changes owner and group on the named files (in +list+)
933 # to the user +user+ and the group +group+ recursively.
934 # +user+ and +group+ may be an ID (Integer/String) or
935 # a name (String). If +user+ or +group+ is nil, this
936 # method does not change the attribute.
938 # FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
939 # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
941 def chown_R(user, group, list, options = {})
942 fu_check_options options, OPT_TABLE['chown_R']
944 fu_output_message sprintf('chown -R%s %s%s',
945 (options[:force] ? 'f' : ''),
946 [user,group].compact.join(':') + ' ',
947 list.join(' ')) if options[:verbose]
948 return if options[:noop]
949 uid = fu_get_uid(user)
950 gid = fu_get_gid(group)
951 return unless uid or gid
953 Entry_.new(root).traverse do |ent|
957 raise unless options[:force]
962 module_function :chown_R
964 OPT_TABLE['chown_R'] = [:noop, :verbose, :force]
969 def fu_get_uid(user) #:nodoc:
970 return nil unless user
974 else Etc.getpwnam(user).uid
977 private_module_function :fu_get_uid
979 def fu_get_gid(group) #:nodoc:
980 return nil unless group
981 if /\A\d+\z/ =~ group
983 else Etc.getgrnam(group).gid
986 private_module_function :fu_get_gid
989 # need Win32 support???
991 def fu_get_uid(user) #:nodoc:
994 private_module_function :fu_get_uid
996 def fu_get_gid(group) #:nodoc:
999 private_module_function :fu_get_gid
1003 # Options: noop verbose
1005 # Updates modification time (mtime) and access time (atime) of file(s) in
1006 # +list+. Files are created if they don't exist.
1008 # FileUtils.touch 'timestamp'
1009 # FileUtils.touch Dir.glob('*.c'); system 'make'
1011 def touch(list, options = {})
1012 fu_check_options options, OPT_TABLE['touch']
1013 list = fu_list(list)
1014 created = nocreate = options[:nocreate]
1016 if options[:verbose]
1017 fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}"
1019 return if options[:noop]
1023 File.utime(t, t, path)
1024 rescue Errno::ENOENT
1026 File.open(path, 'a') {
1034 module_function :touch
1036 OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate]
1044 /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
1047 def fu_copy_stream0(src, dest, blksize) #:nodoc:
1048 # FIXME: readpartial?
1049 while s = src.read(blksize)
1054 def fu_stream_blksize(*streams)
1056 next unless s.respond_to?(:stat)
1057 size = fu_blksize(s.stat)
1060 fu_default_blksize()
1066 return nil if s == 0
1070 def fu_default_blksize
1075 include StreamUtils_
1078 class Entry_ #:nodoc: internal use only
1079 include StreamUtils_
1081 def initialize(a, b = nil, deref = false)
1082 @prefix = @rel = @path = nil
1095 "\#<#{self.class} #{path()}>"
1119 lstat! ? true : false
1161 s and (s.mode & 0xF000 == S_IF_DOOR)
1165 Dir.entries(path())\
1166 .reject {|n| n == '.' or n == '..' }\
1167 .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
1171 return @stat if @stat
1172 if lstat() and lstat().symlink?
1173 @stat = File.stat(path())
1181 return @stat if @stat
1182 if lstat! and lstat!.symlink?
1183 @stat = File.stat(path())
1188 rescue SystemCallError
1194 @lstat ||= File.stat(path())
1196 @lstat ||= File.lstat(path())
1202 rescue SystemCallError
1208 File.lchmod mode, path() if have_lchmod?
1210 File.chmod mode, path()
1216 File.lchown uid, gid, path() if have_lchown?
1218 File.chown uid, gid, path()
1227 if !File.exist?(dest) and /^#{Regexp.quote(path)}/ =~ File.dirname(dest)
1228 raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
1233 raise unless File.directory?(dest)
1236 File.symlink File.readlink(path()), dest
1238 raise "cannot handle device file" unless File.respond_to?(:mknod)
1239 mknod dest, ?c, 0666, lstat().rdev
1241 raise "cannot handle device file" unless File.respond_to?(:mknod)
1242 mknod dest, ?b, 0666, lstat().rdev
1244 raise "cannot handle socket" unless File.respond_to?(:mknod)
1245 mknod dest, nil, lstat().mode, 0
1247 raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
1250 raise "cannot handle door: #{path()}"
1252 raise "unknown file type: #{path()}"
1258 File.open(path(), 'rb') {|r|
1259 File.open(dest, 'wb', st.mode) {|w|
1260 fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
1265 def copy_metadata(path)
1267 File.utime st.atime, st.mtime, path
1269 File.chown st.uid, st.gid, path
1271 # clear setuid/setgid
1272 File.chmod st.mode & 01777, path
1274 File.chmod st.mode, path
1288 Dir.rmdir path().sub(%r</\z>, '')
1298 def platform_support
1299 return yield unless fu_windows?
1303 rescue Errno::ENOENT
1307 first_time_p = false
1309 File.chmod 0700, path() # Windows does not have symlink
1311 rescue SystemCallError
1318 def preorder_traverse
1320 while ent = stack.pop
1322 stack.concat ent.entries.reverse if ent.directory?
1326 alias traverse preorder_traverse
1328 def postorder_traverse
1330 entries().each do |ent|
1331 ent.postorder_traverse do |e|
1341 $fileutils_rb_have_lchmod = nil
1344 # This is not MT-safe, but it does not matter.
1345 if $fileutils_rb_have_lchmod == nil
1346 $fileutils_rb_have_lchmod = check_have_lchmod?
1348 $fileutils_rb_have_lchmod
1351 def check_have_lchmod?
1352 return false unless File.respond_to?(:lchmod)
1355 rescue NotImplementedError
1359 $fileutils_rb_have_lchown = nil
1362 # This is not MT-safe, but it does not matter.
1363 if $fileutils_rb_have_lchown == nil
1364 $fileutils_rb_have_lchown = check_have_lchown?
1366 $fileutils_rb_have_lchown
1369 def check_have_lchown?
1370 return false unless File.respond_to?(:lchown)
1371 File.lchown nil, nil
1373 rescue NotImplementedError
1378 return File.path(dir) if not base or base == '.'
1379 return File.path(base) if not dir or dir == '.'
1380 File.join(dir, base)
1384 def fu_list(arg) #:nodoc:
1385 [arg].flatten.map {|path| File.path(path) }
1387 private_module_function :fu_list
1389 def fu_each_src_dest(src, dest) #:nodoc:
1390 fu_each_src_dest0(src, dest) do |s, d|
1391 raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
1395 private_module_function :fu_each_src_dest
1397 def fu_each_src_dest0(src, dest) #:nodoc:
1398 if tmp = Array.try_convert(src)
1401 yield s, File.join(dest, File.basename(s))
1404 src = File.path(src)
1405 if File.directory?(dest)
1406 yield src, File.join(dest, File.basename(src))
1408 yield src, File.path(dest)
1412 private_module_function :fu_each_src_dest0
1414 def fu_same?(a, b) #:nodoc:
1418 st1.dev == st2.dev and st1.ino == st2.ino
1420 File.expand_path(a) == File.expand_path(b)
1422 rescue Errno::ENOENT
1425 private_module_function :fu_same?
1427 def fu_have_st_ino? #:nodoc:
1430 private_module_function :fu_have_st_ino?
1432 def fu_check_options(options, optdecl) #:nodoc:
1434 optdecl.each do |opt|
1437 raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
1439 private_module_function :fu_check_options
1441 def fu_update_option(args, new) #:nodoc:
1442 if tmp = Hash.try_convert(args.last)
1443 args[-1] = tmp.dup.update(new)
1449 private_module_function :fu_update_option
1451 @fileutils_output = $stderr
1452 @fileutils_label = ''
1454 def fu_output_message(msg) #:nodoc:
1455 @fileutils_output ||= $stderr
1456 @fileutils_label ||= ''
1457 @fileutils_output.puts @fileutils_label + msg
1459 private_module_function :fu_output_message
1462 # Returns an Array of method names which have any options.
1464 # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
1466 def FileUtils.commands
1471 # Returns an Array of option names.
1473 # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
1475 def FileUtils.options
1476 OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
1480 # Returns true if the method +mid+ have an option +opt+.
1482 # p FileUtils.have_option?(:cp, :noop) #=> true
1483 # p FileUtils.have_option?(:rm, :force) #=> true
1484 # p FileUtils.have_option?(:rm, :perserve) #=> false
1486 def FileUtils.have_option?(mid, opt)
1487 li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
1492 # Returns an Array of option names of the method +mid+.
1494 # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"]
1496 def FileUtils.options_of(mid)
1497 OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
1501 # Returns an Array of method names which have the option +opt+.
1503 # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
1505 def FileUtils.collect_method(opt)
1506 OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
1509 METHODS = singleton_methods() - [:private_module_function,
1510 :commands, :options, :have_option?, :options_of, :collect_method]
1513 # This module has all methods of FileUtils module, but it outputs messages
1514 # before acting. This equates to passing the <tt>:verbose</tt> flag to
1515 # methods in FileUtils.
1519 @fileutils_output = $stderr
1520 @fileutils_label = ''
1521 ::FileUtils.collect_method(:verbose).each do |name|
1522 module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1524 super(*fu_update_option(args, :verbose => true))
1531 ::FileUtils::METHODS.each do |m|
1538 # This module has all methods of FileUtils module, but never changes
1539 # files/directories. This equates to passing the <tt>:noop</tt> flag
1540 # to methods in FileUtils.
1544 @fileutils_output = $stderr
1545 @fileutils_label = ''
1546 ::FileUtils.collect_method(:noop).each do |name|
1547 module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1549 super(*fu_update_option(args, :noop => true))
1556 ::FileUtils::METHODS.each do |m|
1563 # This module has all methods of FileUtils module, but never changes
1564 # files/directories, with printing message before acting.
1565 # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
1566 # to methods in FileUtils.
1570 @fileutils_output = $stderr
1571 @fileutils_label = ''
1572 ::FileUtils.collect_method(:noop).each do |name|
1573 module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1575 super(*fu_update_option(args, :noop => true, :verbose => true))
1582 ::FileUtils::METHODS.each do |m|