1# 2# = fileutils.rb 3# 4# Copyright (c) 2000-2007 Minero Aoki 5# 6# This program is free software. 7# You can distribute/modify this program under the same terms of ruby. 8# 9# == module FileUtils 10# 11# Namespace for several file utility methods for copying, moving, removing, etc. 12# 13# === Module Functions 14# 15# cd(dir, options) 16# cd(dir, options) {|dir| .... } 17# pwd() 18# mkdir(dir, options) 19# mkdir(list, options) 20# mkdir_p(dir, options) 21# mkdir_p(list, options) 22# rmdir(dir, 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) 35# rm(list, options) 36# rm_r(list, 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) 44# 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. 49# 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. 53# 54# There are some `low level' methods, which do not accept any option: 55# 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) 65# 66# == module FileUtils::Verbose 67# 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 70# in FileUtils. 71# 72# == module FileUtils::NoWrite 73# 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 76# in FileUtils. 77# 78# == module FileUtils::DryRun 79# 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. 83# 84 85module FileUtils 86 87 def self.private_module_function(name) #:nodoc: 88 module_function name 89 private_class_method name 90 end 91 92 # This hash table holds command options. 93 OPT_TABLE = {} #:nodoc: internal use only 94 95 # 96 # Options: (none) 97 # 98 # Returns the name of the current directory. 99 # 100 def pwd 101 Dir.pwd 102 end 103 module_function :pwd 104 105 alias getwd pwd 106 module_function :getwd 107 108 # 109 # Options: verbose 110 # 111 # Changes the current directory to the directory +dir+. 112 # 113 # If this method is called with block, resumes to the old 114 # working directory after the block execution finished. 115 # 116 # FileUtils.cd('/', :verbose => true) # chdir and report it 117 # 118 # FileUtils.cd('/') do # chdir 119 # [...] # do something 120 # end # return to original directory 121 # 122 def cd(dir, options = {}, &block) # :yield: dir 123 fu_check_options options, OPT_TABLE['cd'] 124 fu_output_message "cd #{dir}" if options[:verbose] 125 Dir.chdir(dir, &block) 126 fu_output_message 'cd -' if options[:verbose] and block 127 end 128 module_function :cd 129 130 alias chdir cd 131 module_function :chdir 132 133 OPT_TABLE['cd'] = 134 OPT_TABLE['chdir'] = [:verbose] 135 136 # 137 # Options: (none) 138 # 139 # Returns true if +newer+ is newer than all +old_list+. 140 # Non-existent files are older than any file. 141 # 142 # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \ 143 # system 'make hello.o' 144 # 145 def uptodate?(new, old_list) 146 return false unless File.exist?(new) 147 new_time = File.mtime(new) 148 old_list.each do |old| 149 if File.exist?(old) 150 return false unless new_time > File.mtime(old) 151 end 152 end 153 true 154 end 155 module_function :uptodate? 156 157 # 158 # Options: mode noop verbose 159 # 160 # Creates one or more directories. 161 # 162 # FileUtils.mkdir 'test' 163 # FileUtils.mkdir %w( tmp data ) 164 # FileUtils.mkdir 'notexist', :noop => true # Does not really create. 165 # FileUtils.mkdir 'tmp', :mode => 0700 166 # 167 def mkdir(list, options = {}) 168 fu_check_options options, OPT_TABLE['mkdir'] 169 list = fu_list(list) 170 fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] 171 return if options[:noop] 172 173 list.each do |dir| 174 fu_mkdir dir, options[:mode] 175 end 176 end 177 module_function :mkdir 178 179 OPT_TABLE['mkdir'] = [:mode, :noop, :verbose] 180 181 # 182 # Options: mode noop verbose 183 # 184 # Creates a directory and all its parent directories. 185 # For example, 186 # 187 # FileUtils.mkdir_p '/usr/local/lib/ruby' 188 # 189 # causes to make following directories, if it does not exist. 190 # * /usr 191 # * /usr/local 192 # * /usr/local/lib 193 # * /usr/local/lib/ruby 194 # 195 # You can pass several directories at a time in a list. 196 # 197 def mkdir_p(list, options = {}) 198 fu_check_options options, OPT_TABLE['mkdir_p'] 199 list = fu_list(list) 200 fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose] 201 return *list if options[:noop] 202 203 list.map {|path| path.chomp(?/) }.each do |path| 204 # optimize for the most common case 205 begin 206 fu_mkdir path, options[:mode] 207 next 208 rescue SystemCallError 209 next if File.directory?(path) 210 end 211 212 stack = [] 213 until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/" 214 stack.push path 215 path = File.dirname(path) 216 end 217 stack.reverse_each do |dir| 218 begin 219 fu_mkdir dir, options[:mode] 220 rescue SystemCallError 221 raise unless File.directory?(dir) 222 end 223 end 224 end 225 226 return *list 227 end 228 module_function :mkdir_p 229 230 alias mkpath mkdir_p 231 alias makedirs mkdir_p 232 module_function :mkpath 233 module_function :makedirs 234 235 OPT_TABLE['mkdir_p'] = 236 OPT_TABLE['mkpath'] = 237 OPT_TABLE['makedirs'] = [:mode, :noop, :verbose] 238 239 def fu_mkdir(path, mode) #:nodoc: 240 path = path.chomp(?/) 241 if mode 242 Dir.mkdir path, mode 243 File.chmod mode, path 244 else 245 Dir.mkdir path 246 end 247 end 248 private_module_function :fu_mkdir 249 250 # 251 # Options: noop, verbose 252 # 253 # Removes one or more directories. 254 # 255 # FileUtils.rmdir 'somedir' 256 # FileUtils.rmdir %w(somedir anydir otherdir) 257 # # Does not really remove directory; outputs message. 258 # FileUtils.rmdir 'somedir', :verbose => true, :noop => true 259 # 260 def rmdir(list, options = {}) 261 fu_check_options options, OPT_TABLE['rmdir'] 262 list = fu_list(list) 263 parents = options[:parents] 264 fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if options[:verbose] 265 return if options[:noop] 266 list.each do |dir| 267 begin 268 Dir.rmdir(dir = dir.chomp(?/)) 269 if parents 270 until (parent = File.dirname(dir)) == '.' or parent == dir 271 Dir.rmdir(dir) 272 end 273 end 274 rescue Errno::ENOTEMPTY, Errno::ENOENT 275 end 276 end 277 end 278 module_function :rmdir 279 280 OPT_TABLE['rmdir'] = [:parents, :noop, :verbose] 281 282 # 283 # Options: force noop verbose 284 # 285 # <b><tt>ln(old, new, options = {})</tt></b> 286 # 287 # Creates a hard link +new+ which points to +old+. 288 # If +new+ already exists and it is a directory, creates a link +new/old+. 289 # If +new+ already exists and it is not a directory, raises Errno::EEXIST. 290 # But if :force option is set, overwrite +new+. 291 # 292 # FileUtils.ln 'gcc', 'cc', :verbose => true 293 # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs' 294 # 295 # <b><tt>ln(list, destdir, options = {})</tt></b> 296 # 297 # Creates several hard links in a directory, with each one pointing to the 298 # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. 299 # 300 # include FileUtils 301 # cd '/sbin' 302 # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked. 303 # 304 def ln(src, dest, options = {}) 305 fu_check_options options, OPT_TABLE['ln'] 306 fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 307 return if options[:noop] 308 fu_each_src_dest0(src, dest) do |s,d| 309 remove_file d, true if options[:force] 310 File.link s, d 311 end 312 end 313 module_function :ln 314 315 alias link ln 316 module_function :link 317 318 OPT_TABLE['ln'] = 319 OPT_TABLE['link'] = [:force, :noop, :verbose] 320 321 # 322 # Options: force noop verbose 323 # 324 # <b><tt>ln_s(old, new, options = {})</tt></b> 325 # 326 # Creates a symbolic link +new+ which points to +old+. If +new+ already 327 # exists and it is a directory, creates a symbolic link +new/old+. If +new+ 328 # already exists and it is not a directory, raises Errno::EEXIST. But if 329 # :force option is set, overwrite +new+. 330 # 331 # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' 332 # FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true 333 # 334 # <b><tt>ln_s(list, destdir, options = {})</tt></b> 335 # 336 # Creates several symbolic links in a directory, with each one pointing to the 337 # item in +list+. If +destdir+ is not a directory, raises Errno::ENOTDIR. 338 # 339 # If +destdir+ is not a directory, raises Errno::ENOTDIR. 340 # 341 # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin' 342 # 343 def ln_s(src, dest, options = {}) 344 fu_check_options options, OPT_TABLE['ln_s'] 345 fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 346 return if options[:noop] 347 fu_each_src_dest0(src, dest) do |s,d| 348 remove_file d, true if options[:force] 349 File.symlink s, d 350 end 351 end 352 module_function :ln_s 353 354 alias symlink ln_s 355 module_function :symlink 356 357 OPT_TABLE['ln_s'] = 358 OPT_TABLE['symlink'] = [:force, :noop, :verbose] 359 360 # 361 # Options: noop verbose 362 # 363 # Same as 364 # #ln_s(src, dest, :force => true) 365 # 366 def ln_sf(src, dest, options = {}) 367 fu_check_options options, OPT_TABLE['ln_sf'] 368 options = options.dup 369 options[:force] = true 370 ln_s src, dest, options 371 end 372 module_function :ln_sf 373 374 OPT_TABLE['ln_sf'] = [:noop, :verbose] 375 376 # 377 # Options: preserve noop verbose 378 # 379 # Copies a file content +src+ to +dest+. If +dest+ is a directory, 380 # copies +src+ to +dest/src+. 381 # 382 # If +src+ is a list of files, then +dest+ must be a directory. 383 # 384 # FileUtils.cp 'eval.c', 'eval.c.org' 385 # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' 386 # FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true 387 # FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink 388 # 389 def cp(src, dest, options = {}) 390 fu_check_options options, OPT_TABLE['cp'] 391 fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 392 return if options[:noop] 393 fu_each_src_dest(src, dest) do |s, d| 394 copy_file s, d, options[:preserve] 395 end 396 end 397 module_function :cp 398 399 alias copy cp 400 module_function :copy 401 402 OPT_TABLE['cp'] = 403 OPT_TABLE['copy'] = [:preserve, :noop, :verbose] 404 405 # 406 # Options: preserve noop verbose dereference_root remove_destination 407 # 408 # Copies +src+ to +dest+. If +src+ is a directory, this method copies 409 # all its contents recursively. If +dest+ is a directory, copies 410 # +src+ to +dest/src+. 411 # 412 # +src+ can be a list of files. 413 # 414 # # Installing ruby library "mylib" under the site_ruby 415 # FileUtils.rm_r site_ruby + '/mylib', :force 416 # FileUtils.cp_r 'lib/', site_ruby + '/mylib' 417 # 418 # # Examples of copying several files to target directory. 419 # FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail' 420 # FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true 421 # 422 # # If you want to copy all contents of a directory instead of the 423 # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y, 424 # # use following code. 425 # FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src, 426 # # but this doesn't. 427 # 428 def cp_r(src, dest, options = {}) 429 fu_check_options options, OPT_TABLE['cp_r'] 430 fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 431 return if options[:noop] 432 options = options.dup 433 options[:dereference_root] = true unless options.key?(:dereference_root) 434 fu_each_src_dest(src, dest) do |s, d| 435 copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination] 436 end 437 end 438 module_function :cp_r 439 440 OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose, 441 :dereference_root, :remove_destination] 442 443 # 444 # Copies a file system entry +src+ to +dest+. 445 # If +src+ is a directory, this method copies its contents recursively. 446 # This method preserves file types, c.f. symlink, directory... 447 # (FIFO, device files and etc. are not supported yet) 448 # 449 # Both of +src+ and +dest+ must be a path name. 450 # +src+ must exist, +dest+ must not exist. 451 # 452 # If +preserve+ is true, this method preserves owner, group, permissions 453 # and modified time. 454 # 455 # If +dereference_root+ is true, this method dereference tree root. 456 # 457 # If +remove_destination+ is true, this method removes each destination file before copy. 458 # 459 def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) 460 Entry_.new(src, nil, dereference_root).wrap_traverse(proc do |ent| 461 destent = Entry_.new(dest, ent.rel, false) 462 File.unlink destent.path if remove_destination && File.file?(destent.path) 463 ent.copy destent.path 464 end, proc do |ent| 465 destent = Entry_.new(dest, ent.rel, false) 466 ent.copy_metadata destent.path if preserve 467 end) 468 end 469 module_function :copy_entry 470 471 # 472 # Copies file contents of +src+ to +dest+. 473 # Both of +src+ and +dest+ must be a path name. 474 # 475 def copy_file(src, dest, preserve = false, dereference = true) 476 ent = Entry_.new(src, nil, dereference) 477 ent.copy_file dest 478 ent.copy_metadata dest if preserve 479 end 480 module_function :copy_file 481 482 # 483 # Copies stream +src+ to +dest+. 484 # +src+ must respond to #read(n) and 485 # +dest+ must respond to #write(str). 486 # 487 def copy_stream(src, dest) 488 IO.copy_stream(src, dest) 489 end 490 module_function :copy_stream 491 492 # 493 # Options: force noop verbose 494 # 495 # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different 496 # disk partition, the file is copied then the original file is removed. 497 # 498 # FileUtils.mv 'badname.rb', 'goodname.rb' 499 # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error 500 # 501 # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/' 502 # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true 503 # 504 def mv(src, dest, options = {}) 505 fu_check_options options, OPT_TABLE['mv'] 506 fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 507 return if options[:noop] 508 fu_each_src_dest(src, dest) do |s, d| 509 destent = Entry_.new(d, nil, true) 510 begin 511 if destent.exist? 512 if destent.directory? 513 raise Errno::EEXIST, dest 514 else 515 destent.remove_file if rename_cannot_overwrite_file? 516 end 517 end 518 begin 519 File.rename s, d 520 rescue Errno::EXDEV 521 copy_entry s, d, true 522 if options[:secure] 523 remove_entry_secure s, options[:force] 524 else 525 remove_entry s, options[:force] 526 end 527 end 528 rescue SystemCallError 529 raise unless options[:force] 530 end 531 end 532 end 533 module_function :mv 534 535 alias move mv 536 module_function :move 537 538 OPT_TABLE['mv'] = 539 OPT_TABLE['move'] = [:force, :noop, :verbose, :secure] 540 541 def rename_cannot_overwrite_file? #:nodoc: 542 /cygwin|mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM 543 end 544 private_module_function :rename_cannot_overwrite_file? 545 546 # 547 # Options: force noop verbose 548 # 549 # Remove file(s) specified in +list+. This method cannot remove directories. 550 # All StandardErrors are ignored when the :force option is set. 551 # 552 # FileUtils.rm %w( junk.txt dust.txt ) 553 # FileUtils.rm Dir.glob('*.so') 554 # FileUtils.rm 'NotExistFile', :force => true # never raises exception 555 # 556 def rm(list, options = {}) 557 fu_check_options options, OPT_TABLE['rm'] 558 list = fu_list(list) 559 fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose] 560 return if options[:noop] 561 562 list.each do |path| 563 remove_file path, options[:force] 564 end 565 end 566 module_function :rm 567 568 alias remove rm 569 module_function :remove 570 571 OPT_TABLE['rm'] = 572 OPT_TABLE['remove'] = [:force, :noop, :verbose] 573 574 # 575 # Options: noop verbose 576 # 577 # Equivalent to 578 # 579 # #rm(list, :force => true) 580 # 581 def rm_f(list, options = {}) 582 fu_check_options options, OPT_TABLE['rm_f'] 583 options = options.dup 584 options[:force] = true 585 rm list, options 586 end 587 module_function :rm_f 588 589 alias safe_unlink rm_f 590 module_function :safe_unlink 591 592 OPT_TABLE['rm_f'] = 593 OPT_TABLE['safe_unlink'] = [:noop, :verbose] 594 595 # 596 # Options: force noop verbose secure 597 # 598 # remove files +list+[0] +list+[1]... If +list+[n] is a directory, 599 # removes its all contents recursively. This method ignores 600 # StandardError when :force option is set. 601 # 602 # FileUtils.rm_r Dir.glob('/tmp/*') 603 # FileUtils.rm_r '/', :force => true # :-) 604 # 605 # WARNING: This method causes local vulnerability 606 # if one of parent directories or removing directory tree are world 607 # writable (including /tmp, whose permission is 1777), and the current 608 # process has strong privilege such as Unix super user (root), and the 609 # system has symbolic link. For secure removing, read the documentation 610 # of #remove_entry_secure carefully, and set :secure option to true. 611 # Default is :secure=>false. 612 # 613 # NOTE: This method calls #remove_entry_secure if :secure option is set. 614 # See also #remove_entry_secure. 615 # 616 def rm_r(list, options = {}) 617 fu_check_options options, OPT_TABLE['rm_r'] 618 # options[:secure] = true unless options.key?(:secure) 619 list = fu_list(list) 620 fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose] 621 return if options[:noop] 622 list.each do |path| 623 if options[:secure] 624 remove_entry_secure path, options[:force] 625 else 626 remove_entry path, options[:force] 627 end 628 end 629 end 630 module_function :rm_r 631 632 OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure] 633 634 # 635 # Options: noop verbose secure 636 # 637 # Equivalent to 638 # 639 # #rm_r(list, :force => true) 640 # 641 # WARNING: This method causes local vulnerability. 642 # Read the documentation of #rm_r first. 643 # 644 def rm_rf(list, options = {}) 645 fu_check_options options, OPT_TABLE['rm_rf'] 646 options = options.dup 647 options[:force] = true 648 rm_r list, options 649 end 650 module_function :rm_rf 651 652 alias rmtree rm_rf 653 module_function :rmtree 654 655 OPT_TABLE['rm_rf'] = 656 OPT_TABLE['rmtree'] = [:noop, :verbose, :secure] 657 658 # 659 # This method removes a file system entry +path+. +path+ shall be a 660 # regular file, a directory, or something. If +path+ is a directory, 661 # remove it recursively. This method is required to avoid TOCTTOU 662 # (time-of-check-to-time-of-use) local security vulnerability of #rm_r. 663 # #rm_r causes security hole when: 664 # 665 # * Parent directory is world writable (including /tmp). 666 # * Removing directory tree includes world writable directory. 667 # * The system has symbolic link. 668 # 669 # To avoid this security hole, this method applies special preprocess. 670 # If +path+ is a directory, this method chown(2) and chmod(2) all 671 # removing directories. This requires the current process is the 672 # owner of the removing whole directory tree, or is the super user (root). 673 # 674 # WARNING: You must ensure that *ALL* parent directories cannot be 675 # moved by other untrusted users. For example, parent directories 676 # should not be owned by untrusted users, and should not be world 677 # writable except when the sticky bit set. 678 # 679 # WARNING: Only the owner of the removing directory tree, or Unix super 680 # user (root) should invoke this method. Otherwise this method does not 681 # work. 682 # 683 # For details of this security vulnerability, see Perl's case: 684 # 685 # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448 686 # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452 687 # 688 # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100]. 689 # 690 def remove_entry_secure(path, force = false) 691 unless fu_have_symlink? 692 remove_entry path, force 693 return 694 end 695 fullpath = File.expand_path(path) 696 st = File.lstat(fullpath) 697 unless st.directory? 698 File.unlink fullpath 699 return 700 end 701 # is a directory. 702 parent_st = File.stat(File.dirname(fullpath)) 703 unless parent_st.world_writable? 704 remove_entry path, force 705 return 706 end 707 unless parent_st.sticky? 708 raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})" 709 end 710 # freeze tree root 711 euid = Process.euid 712 File.open(fullpath + '/.') {|f| 713 unless fu_stat_identical_entry?(st, f.stat) 714 # symlink (TOC-to-TOU attack?) 715 File.unlink fullpath 716 return 717 end 718 f.chown euid, -1 719 f.chmod 0700 720 unless fu_stat_identical_entry?(st, File.lstat(fullpath)) 721 # TOC-to-TOU attack? 722 File.unlink fullpath 723 return 724 end 725 } 726 # ---- tree root is frozen ---- 727 root = Entry_.new(path) 728 root.preorder_traverse do |ent| 729 if ent.directory? 730 ent.chown euid, -1 731 ent.chmod 0700 732 end 733 end 734 root.postorder_traverse do |ent| 735 begin 736 ent.remove 737 rescue 738 raise unless force 739 end 740 end 741 rescue 742 raise unless force 743 end 744 module_function :remove_entry_secure 745 746 def fu_have_symlink? #:nodoc: 747 File.symlink nil, nil 748 rescue NotImplementedError 749 return false 750 rescue TypeError 751 return true 752 end 753 private_module_function :fu_have_symlink? 754 755 def fu_stat_identical_entry?(a, b) #:nodoc: 756 a.dev == b.dev and a.ino == b.ino 757 end 758 private_module_function :fu_stat_identical_entry? 759 760 # 761 # This method removes a file system entry +path+. 762 # +path+ might be a regular file, a directory, or something. 763 # If +path+ is a directory, remove it recursively. 764 # 765 # See also #remove_entry_secure. 766 # 767 def remove_entry(path, force = false) 768 Entry_.new(path).postorder_traverse do |ent| 769 begin 770 ent.remove 771 rescue 772 raise unless force 773 end 774 end 775 rescue 776 raise unless force 777 end 778 module_function :remove_entry 779 780 # 781 # Removes a file +path+. 782 # This method ignores StandardError if +force+ is true. 783 # 784 def remove_file(path, force = false) 785 Entry_.new(path).remove_file 786 rescue 787 raise unless force 788 end 789 module_function :remove_file 790 791 # 792 # Removes a directory +dir+ and its contents recursively. 793 # This method ignores StandardError if +force+ is true. 794 # 795 def remove_dir(path, force = false) 796 remove_entry path, force # FIXME?? check if it is a directory 797 end 798 module_function :remove_dir 799 800 # 801 # Returns true if the contents of a file A and a file B are identical. 802 # 803 # FileUtils.compare_file('somefile', 'somefile') #=> true 804 # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false 805 # 806 def compare_file(a, b) 807 return false unless File.size(a) == File.size(b) 808 File.open(a, 'rb') {|fa| 809 File.open(b, 'rb') {|fb| 810 return compare_stream(fa, fb) 811 } 812 } 813 end 814 module_function :compare_file 815 816 alias identical? compare_file 817 alias cmp compare_file 818 module_function :identical? 819 module_function :cmp 820 821 # 822 # Returns true if the contents of a stream +a+ and +b+ are identical. 823 # 824 def compare_stream(a, b) 825 bsize = fu_stream_blksize(a, b) 826 sa = "" 827 sb = "" 828 begin 829 a.read(bsize, sa) 830 b.read(bsize, sb) 831 return true if sa.empty? && sb.empty? 832 end while sa == sb 833 false 834 end 835 module_function :compare_stream 836 837 # 838 # Options: mode preserve noop verbose 839 # 840 # If +src+ is not same as +dest+, copies it and changes the permission 841 # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+. 842 # This method removes destination before copy. 843 # 844 # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true 845 # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true 846 # 847 def install(src, dest, options = {}) 848 fu_check_options options, OPT_TABLE['install'] 849 fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose] 850 return if options[:noop] 851 fu_each_src_dest(src, dest) do |s, d, st| 852 unless File.exist?(d) and compare_file(s, d) 853 remove_file d, true 854 copy_file s, d 855 File.utime st.atime, st.mtime, d if options[:preserve] 856 File.chmod options[:mode], d if options[:mode] 857 end 858 end 859 end 860 module_function :install 861 862 OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose] 863 864 def user_mask(target) #:nodoc: 865 mask = 0 866 target.each_byte do |byte_chr| 867 case byte_chr.chr 868 when "u" 869 mask |= 04700 870 when "g" 871 mask |= 02070 872 when "o" 873 mask |= 01007 874 when "a" 875 mask |= 07777 876 end 877 end 878 mask 879 end 880 private_module_function :user_mask 881 882 def mode_mask(mode, path) #:nodoc: 883 mask = 0 884 mode.each_byte do |byte_chr| 885 case byte_chr.chr 886 when "r" 887 mask |= 0444 888 when "w" 889 mask |= 0222 890 when "x" 891 mask |= 0111 892 when "X" 893 mask |= 0111 if FileTest::directory? path 894 when "s" 895 mask |= 06000 896 when "t" 897 mask |= 01000 898 end 899 end 900 mask 901 end 902 private_module_function :mode_mask 903 904 def symbolic_modes_to_i(modes, path) #:nodoc: 905 current_mode = (File.stat(path).mode & 07777) 906 modes.split(/,/).inject(0) do |mode, mode_sym| 907 mode_sym = "a#{mode_sym}" if mode_sym =~ %r!^[=+-]! 908 target, mode = mode_sym.split %r![=+-]! 909 user_mask = user_mask(target) 910 mode_mask = mode_mask(mode ? mode : "", path) 911 912 case mode_sym 913 when /=/ 914 current_mode &= ~(user_mask) 915 current_mode |= user_mask & mode_mask 916 when /\+/ 917 current_mode |= user_mask & mode_mask 918 when /-/ 919 current_mode &= ~(user_mask & mode_mask) 920 end 921 end 922 end 923 private_module_function :symbolic_modes_to_i 924 925 def fu_mode(mode, path) #:nodoc: 926 mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode 927 end 928 private_module_function :fu_mode 929 930 def mode_to_s(mode) #:nodoc: 931 mode.is_a?(String) ? mode : "%o" % mode 932 end 933 private_module_function :mode_to_s 934 935 # 936 # Options: noop verbose 937 # 938 # Changes permission bits on the named files (in +list+) to the bit pattern 939 # represented by +mode+. 940 # 941 # +mode+ is the symbolic and absolute mode can be used. 942 # 943 # Absolute mode is 944 # FileUtils.chmod 0755, 'somecommand' 945 # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) 946 # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true 947 # 948 # Symbolic mode is 949 # FileUtils.chmod "u=wrx,go=rx", 'somecommand' 950 # FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) 951 # FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', :verbose => true 952 # 953 # "a" :: is user, group, other mask. 954 # "u" :: is user's mask. 955 # "g" :: is group's mask. 956 # "o" :: is other's mask. 957 # "w" :: is write permission. 958 # "r" :: is read permission. 959 # "x" :: is execute permission. 960 # "X" :: 961 # is execute permission for directories only, must be used in conjunction with "+" 962 # "s" :: is uid, gid. 963 # "t" :: is sticky bit. 964 # "+" :: is added to a class given the specified mode. 965 # "-" :: Is removed from a given class given mode. 966 # "=" :: Is the exact nature of the class will be given a specified mode. 967 968 def chmod(mode, list, options = {}) 969 fu_check_options options, OPT_TABLE['chmod'] 970 list = fu_list(list) 971 fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if options[:verbose] 972 return if options[:noop] 973 list.each do |path| 974 Entry_.new(path).chmod(fu_mode(mode, path)) 975 end 976 end 977 module_function :chmod 978 979 OPT_TABLE['chmod'] = [:noop, :verbose] 980 981 # 982 # Options: noop verbose force 983 # 984 # Changes permission bits on the named files (in +list+) 985 # to the bit pattern represented by +mode+. 986 # 987 # FileUtils.chmod_R 0700, "/tmp/app.#{$$}" 988 # FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}" 989 # 990 def chmod_R(mode, list, options = {}) 991 fu_check_options options, OPT_TABLE['chmod_R'] 992 list = fu_list(list) 993 fu_output_message sprintf('chmod -R%s %s %s', 994 (options[:force] ? 'f' : ''), 995 mode_to_s(mode), list.join(' ')) if options[:verbose] 996 return if options[:noop] 997 list.each do |root| 998 Entry_.new(root).traverse do |ent| 999 begin 1000 ent.chmod(fu_mode(mode, ent.path)) 1001 rescue 1002 raise unless options[:force] 1003 end 1004 end 1005 end 1006 end 1007 module_function :chmod_R 1008 1009 OPT_TABLE['chmod_R'] = [:noop, :verbose, :force] 1010 1011 # 1012 # Options: noop verbose 1013 # 1014 # Changes owner and group on the named files (in +list+) 1015 # to the user +user+ and the group +group+. +user+ and +group+ 1016 # may be an ID (Integer/String) or a name (String). 1017 # If +user+ or +group+ is nil, this method does not change 1018 # the attribute. 1019 # 1020 # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby' 1021 # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true 1022 # 1023 def chown(user, group, list, options = {}) 1024 fu_check_options options, OPT_TABLE['chown'] 1025 list = fu_list(list) 1026 fu_output_message sprintf('chown %s %s', 1027 (group ? "#{user}:#{group}" : user || ':'), 1028 list.join(' ')) if options[:verbose] 1029 return if options[:noop] 1030 uid = fu_get_uid(user) 1031 gid = fu_get_gid(group) 1032 list.each do |path| 1033 Entry_.new(path).chown uid, gid 1034 end 1035 end 1036 module_function :chown 1037 1038 OPT_TABLE['chown'] = [:noop, :verbose] 1039 1040 # 1041 # Options: noop verbose force 1042 # 1043 # Changes owner and group on the named files (in +list+) 1044 # to the user +user+ and the group +group+ recursively. 1045 # +user+ and +group+ may be an ID (Integer/String) or 1046 # a name (String). If +user+ or +group+ is nil, this 1047 # method does not change the attribute. 1048 # 1049 # FileUtils.chown_R 'www', 'www', '/var/www/htdocs' 1050 # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true 1051 # 1052 def chown_R(user, group, list, options = {}) 1053 fu_check_options options, OPT_TABLE['chown_R'] 1054 list = fu_list(list) 1055 fu_output_message sprintf('chown -R%s %s %s', 1056 (options[:force] ? 'f' : ''), 1057 (group ? "#{user}:#{group}" : user || ':'), 1058 list.join(' ')) if options[:verbose] 1059 return if options[:noop] 1060 uid = fu_get_uid(user) 1061 gid = fu_get_gid(group) 1062 return unless uid or gid 1063 list.each do |root| 1064 Entry_.new(root).traverse do |ent| 1065 begin 1066 ent.chown uid, gid 1067 rescue 1068 raise unless options[:force] 1069 end 1070 end 1071 end 1072 end 1073 module_function :chown_R 1074 1075 OPT_TABLE['chown_R'] = [:noop, :verbose, :force] 1076 1077 begin 1078 require 'etc' 1079 1080 def fu_get_uid(user) #:nodoc: 1081 return nil unless user 1082 case user 1083 when Integer 1084 user 1085 when /\A\d+\z/ 1086 user.to_i 1087 else 1088 Etc.getpwnam(user).uid 1089 end 1090 end 1091 private_module_function :fu_get_uid 1092 1093 def fu_get_gid(group) #:nodoc: 1094 return nil unless group 1095 case group 1096 when Integer 1097 group 1098 when /\A\d+\z/ 1099 group.to_i 1100 else 1101 Etc.getgrnam(group).gid 1102 end 1103 end 1104 private_module_function :fu_get_gid 1105 1106 rescue LoadError 1107 # need Win32 support??? 1108 1109 def fu_get_uid(user) #:nodoc: 1110 user # FIXME 1111 end 1112 private_module_function :fu_get_uid 1113 1114 def fu_get_gid(group) #:nodoc: 1115 group # FIXME 1116 end 1117 private_module_function :fu_get_gid 1118 end 1119 1120 # 1121 # Options: noop verbose 1122 # 1123 # Updates modification time (mtime) and access time (atime) of file(s) in 1124 # +list+. Files are created if they don't exist. 1125 # 1126 # FileUtils.touch 'timestamp' 1127 # FileUtils.touch Dir.glob('*.c'); system 'make' 1128 # 1129 def touch(list, options = {}) 1130 fu_check_options options, OPT_TABLE['touch'] 1131 list = fu_list(list) 1132 created = nocreate = options[:nocreate] 1133 t = options[:mtime] 1134 if options[:verbose] 1135 fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}" 1136 end 1137 return if options[:noop] 1138 list.each do |path| 1139 created = nocreate 1140 begin 1141 File.utime(t, t, path) 1142 rescue Errno::ENOENT 1143 raise if created 1144 File.open(path, 'a') { 1145 ; 1146 } 1147 created = true 1148 retry if t 1149 end 1150 end 1151 end 1152 module_function :touch 1153 1154 OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate] 1155 1156 private 1157 1158 module StreamUtils_ 1159 private 1160 1161 def fu_windows? 1162 /mswin|mingw|bccwin|emx/ =~ RUBY_PLATFORM 1163 end 1164 1165 def fu_copy_stream0(src, dest, blksize = nil) #:nodoc: 1166 IO.copy_stream(src, dest) 1167 end 1168 1169 def fu_stream_blksize(*streams) 1170 streams.each do |s| 1171 next unless s.respond_to?(:stat) 1172 size = fu_blksize(s.stat) 1173 return size if size 1174 end 1175 fu_default_blksize() 1176 end 1177 1178 def fu_blksize(st) 1179 s = st.blksize 1180 return nil unless s 1181 return nil if s == 0 1182 s 1183 end 1184 1185 def fu_default_blksize 1186 1024 1187 end 1188 end 1189 1190 include StreamUtils_ 1191 extend StreamUtils_ 1192 1193 class Entry_ #:nodoc: internal use only 1194 include StreamUtils_ 1195 1196 def initialize(a, b = nil, deref = false) 1197 @prefix = @rel = @path = nil 1198 if b 1199 @prefix = a 1200 @rel = b 1201 else 1202 @path = a 1203 end 1204 @deref = deref 1205 @stat = nil 1206 @lstat = nil 1207 end 1208 1209 def inspect 1210 "\#<#{self.class} #{path()}>" 1211 end 1212 1213 def path 1214 if @path 1215 File.path(@path) 1216 else 1217 join(@prefix, @rel) 1218 end 1219 end 1220 1221 def prefix 1222 @prefix || @path 1223 end 1224 1225 def rel 1226 @rel 1227 end 1228 1229 def dereference? 1230 @deref 1231 end 1232 1233 def exist? 1234 lstat! ? true : false 1235 end 1236 1237 def file? 1238 s = lstat! 1239 s and s.file? 1240 end 1241 1242 def directory? 1243 s = lstat! 1244 s and s.directory? 1245 end 1246 1247 def symlink? 1248 s = lstat! 1249 s and s.symlink? 1250 end 1251 1252 def chardev? 1253 s = lstat! 1254 s and s.chardev? 1255 end 1256 1257 def blockdev? 1258 s = lstat! 1259 s and s.blockdev? 1260 end 1261 1262 def socket? 1263 s = lstat! 1264 s and s.socket? 1265 end 1266 1267 def pipe? 1268 s = lstat! 1269 s and s.pipe? 1270 end 1271 1272 S_IF_DOOR = 0xD000 1273 1274 def door? 1275 s = lstat! 1276 s and (s.mode & 0xF000 == S_IF_DOOR) 1277 end 1278 1279 def entries 1280 opts = {} 1281 opts[:encoding] = ::Encoding::UTF_8 if fu_windows? 1282 Dir.entries(path(), opts)\ 1283 .reject {|n| n == '.' or n == '..' }\ 1284 .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) } 1285 end 1286 1287 def stat 1288 return @stat if @stat 1289 if lstat() and lstat().symlink? 1290 @stat = File.stat(path()) 1291 else 1292 @stat = lstat() 1293 end 1294 @stat 1295 end 1296 1297 def stat! 1298 return @stat if @stat 1299 if lstat! and lstat!.symlink? 1300 @stat = File.stat(path()) 1301 else 1302 @stat = lstat! 1303 end 1304 @stat 1305 rescue SystemCallError 1306 nil 1307 end 1308 1309 def lstat 1310 if dereference? 1311 @lstat ||= File.stat(path()) 1312 else 1313 @lstat ||= File.lstat(path()) 1314 end 1315 end 1316 1317 def lstat! 1318 lstat() 1319 rescue SystemCallError 1320 nil 1321 end 1322 1323 def chmod(mode) 1324 if symlink? 1325 File.lchmod mode, path() if have_lchmod? 1326 else 1327 File.chmod mode, path() 1328 end 1329 end 1330 1331 def chown(uid, gid) 1332 if symlink? 1333 File.lchown uid, gid, path() if have_lchown? 1334 else 1335 File.chown uid, gid, path() 1336 end 1337 end 1338 1339 def copy(dest) 1340 case 1341 when file? 1342 copy_file dest 1343 when directory? 1344 if !File.exist?(dest) and descendant_diretory?(dest, path) 1345 raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest] 1346 end 1347 begin 1348 Dir.mkdir dest 1349 rescue 1350 raise unless File.directory?(dest) 1351 end 1352 when symlink? 1353 File.symlink File.readlink(path()), dest 1354 when chardev? 1355 raise "cannot handle device file" unless File.respond_to?(:mknod) 1356 mknod dest, ?c, 0666, lstat().rdev 1357 when blockdev? 1358 raise "cannot handle device file" unless File.respond_to?(:mknod) 1359 mknod dest, ?b, 0666, lstat().rdev 1360 when socket? 1361 raise "cannot handle socket" unless File.respond_to?(:mknod) 1362 mknod dest, nil, lstat().mode, 0 1363 when pipe? 1364 raise "cannot handle FIFO" unless File.respond_to?(:mkfifo) 1365 mkfifo dest, 0666 1366 when door? 1367 raise "cannot handle door: #{path()}" 1368 else 1369 raise "unknown file type: #{path()}" 1370 end 1371 end 1372 1373 def copy_file(dest) 1374 File.open(path()) do |s| 1375 File.open(dest, 'wb', s.stat.mode) do |f| 1376 IO.copy_stream(s, f) 1377 end 1378 end 1379 end 1380 1381 def copy_metadata(path) 1382 st = lstat() 1383 if !st.symlink? 1384 File.utime st.atime, st.mtime, path 1385 end 1386 begin 1387 if st.symlink? 1388 begin 1389 File.lchown st.uid, st.gid, path 1390 rescue NotImplementedError 1391 end 1392 else 1393 File.chown st.uid, st.gid, path 1394 end 1395 rescue Errno::EPERM 1396 # clear setuid/setgid 1397 if st.symlink? 1398 begin 1399 File.lchmod st.mode & 01777, path 1400 rescue NotImplementedError 1401 end 1402 else 1403 File.chmod st.mode & 01777, path 1404 end 1405 else 1406 if st.symlink? 1407 begin 1408 File.lchmod st.mode, path 1409 rescue NotImplementedError 1410 end 1411 else 1412 File.chmod st.mode, path 1413 end 1414 end 1415 end 1416 1417 def remove 1418 if directory? 1419 remove_dir1 1420 else 1421 remove_file 1422 end 1423 end 1424 1425 def remove_dir1 1426 platform_support { 1427 Dir.rmdir path().chomp(?/) 1428 } 1429 end 1430 1431 def remove_file 1432 platform_support { 1433 File.unlink path 1434 } 1435 end 1436 1437 def platform_support 1438 return yield unless fu_windows? 1439 first_time_p = true 1440 begin 1441 yield 1442 rescue Errno::ENOENT 1443 raise 1444 rescue => err 1445 if first_time_p 1446 first_time_p = false 1447 begin 1448 File.chmod 0700, path() # Windows does not have symlink 1449 retry 1450 rescue SystemCallError 1451 end 1452 end 1453 raise err 1454 end 1455 end 1456 1457 def preorder_traverse 1458 stack = [self] 1459 while ent = stack.pop 1460 yield ent 1461 stack.concat ent.entries.reverse if ent.directory? 1462 end 1463 end 1464 1465 alias traverse preorder_traverse 1466 1467 def postorder_traverse 1468 if directory? 1469 entries().each do |ent| 1470 ent.postorder_traverse do |e| 1471 yield e 1472 end 1473 end 1474 end 1475 yield self 1476 end 1477 1478 def wrap_traverse(pre, post) 1479 pre.call self 1480 if directory? 1481 entries.each do |ent| 1482 ent.wrap_traverse pre, post 1483 end 1484 end 1485 post.call self 1486 end 1487 1488 private 1489 1490 $fileutils_rb_have_lchmod = nil 1491 1492 def have_lchmod? 1493 # This is not MT-safe, but it does not matter. 1494 if $fileutils_rb_have_lchmod == nil 1495 $fileutils_rb_have_lchmod = check_have_lchmod? 1496 end 1497 $fileutils_rb_have_lchmod 1498 end 1499 1500 def check_have_lchmod? 1501 return false unless File.respond_to?(:lchmod) 1502 File.lchmod 0 1503 return true 1504 rescue NotImplementedError 1505 return false 1506 end 1507 1508 $fileutils_rb_have_lchown = nil 1509 1510 def have_lchown? 1511 # This is not MT-safe, but it does not matter. 1512 if $fileutils_rb_have_lchown == nil 1513 $fileutils_rb_have_lchown = check_have_lchown? 1514 end 1515 $fileutils_rb_have_lchown 1516 end 1517 1518 def check_have_lchown? 1519 return false unless File.respond_to?(:lchown) 1520 File.lchown nil, nil 1521 return true 1522 rescue NotImplementedError 1523 return false 1524 end 1525 1526 def join(dir, base) 1527 return File.path(dir) if not base or base == '.' 1528 return File.path(base) if not dir or dir == '.' 1529 File.join(dir, base) 1530 end 1531 1532 if File::ALT_SEPARATOR 1533 DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)".freeze 1534 else 1535 DIRECTORY_TERM = "(?=/|\\z)".freeze 1536 end 1537 SYSCASE = File::FNM_SYSCASE.nonzero? ? "-i" : "" 1538 1539 def descendant_diretory?(descendant, ascendant) 1540 /\A(?#{SYSCASE}:#{Regexp.quote(ascendant)})#{DIRECTORY_TERM}/ =~ File.dirname(descendant) 1541 end 1542 end # class Entry_ 1543 1544 def fu_list(arg) #:nodoc: 1545 [arg].flatten.map {|path| File.path(path) } 1546 end 1547 private_module_function :fu_list 1548 1549 def fu_each_src_dest(src, dest) #:nodoc: 1550 fu_each_src_dest0(src, dest) do |s, d| 1551 raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d) 1552 yield s, d, File.stat(s) 1553 end 1554 end 1555 private_module_function :fu_each_src_dest 1556 1557 def fu_each_src_dest0(src, dest) #:nodoc: 1558 if tmp = Array.try_convert(src) 1559 tmp.each do |s| 1560 s = File.path(s) 1561 yield s, File.join(dest, File.basename(s)) 1562 end 1563 else 1564 src = File.path(src) 1565 if File.directory?(dest) 1566 yield src, File.join(dest, File.basename(src)) 1567 else 1568 yield src, File.path(dest) 1569 end 1570 end 1571 end 1572 private_module_function :fu_each_src_dest0 1573 1574 def fu_same?(a, b) #:nodoc: 1575 File.identical?(a, b) 1576 end 1577 private_module_function :fu_same? 1578 1579 def fu_check_options(options, optdecl) #:nodoc: 1580 h = options.dup 1581 optdecl.each do |opt| 1582 h.delete opt 1583 end 1584 raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty? 1585 end 1586 private_module_function :fu_check_options 1587 1588 def fu_update_option(args, new) #:nodoc: 1589 if tmp = Hash.try_convert(args.last) 1590 args[-1] = tmp.dup.update(new) 1591 else 1592 args.push new 1593 end 1594 args 1595 end 1596 private_module_function :fu_update_option 1597 1598 @fileutils_output = $stderr 1599 @fileutils_label = '' 1600 1601 def fu_output_message(msg) #:nodoc: 1602 @fileutils_output ||= $stderr 1603 @fileutils_label ||= '' 1604 @fileutils_output.puts @fileutils_label + msg 1605 end 1606 private_module_function :fu_output_message 1607 1608 # 1609 # Returns an Array of method names which have any options. 1610 # 1611 # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...] 1612 # 1613 def FileUtils.commands 1614 OPT_TABLE.keys 1615 end 1616 1617 # 1618 # Returns an Array of option names. 1619 # 1620 # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"] 1621 # 1622 def FileUtils.options 1623 OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s } 1624 end 1625 1626 # 1627 # Returns true if the method +mid+ have an option +opt+. 1628 # 1629 # p FileUtils.have_option?(:cp, :noop) #=> true 1630 # p FileUtils.have_option?(:rm, :force) #=> true 1631 # p FileUtils.have_option?(:rm, :perserve) #=> false 1632 # 1633 def FileUtils.have_option?(mid, opt) 1634 li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}" 1635 li.include?(opt) 1636 end 1637 1638 # 1639 # Returns an Array of option names of the method +mid+. 1640 # 1641 # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"] 1642 # 1643 def FileUtils.options_of(mid) 1644 OPT_TABLE[mid.to_s].map {|sym| sym.to_s } 1645 end 1646 1647 # 1648 # Returns an Array of method names which have the option +opt+. 1649 # 1650 # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"] 1651 # 1652 def FileUtils.collect_method(opt) 1653 OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) } 1654 end 1655 1656 LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern) 1657 module LowMethods 1658 module_eval("private\n" + ::FileUtils::LOW_METHODS.map {|name| "def #{name}(*)end"}.join("\n"), 1659 __FILE__, __LINE__) 1660 end 1661 1662 METHODS = singleton_methods() - [:private_module_function, 1663 :commands, :options, :have_option?, :options_of, :collect_method] 1664 1665 # 1666 # This module has all methods of FileUtils module, but it outputs messages 1667 # before acting. This equates to passing the <tt>:verbose</tt> flag to 1668 # methods in FileUtils. 1669 # 1670 module Verbose 1671 include FileUtils 1672 @fileutils_output = $stderr 1673 @fileutils_label = '' 1674 ::FileUtils.collect_method(:verbose).each do |name| 1675 module_eval(<<-EOS, __FILE__, __LINE__ + 1) 1676 def #{name}(*args) 1677 super(*fu_update_option(args, :verbose => true)) 1678 end 1679 private :#{name} 1680 EOS 1681 end 1682 extend self 1683 class << self 1684 ::FileUtils::METHODS.each do |m| 1685 public m 1686 end 1687 end 1688 end 1689 1690 # 1691 # This module has all methods of FileUtils module, but never changes 1692 # files/directories. This equates to passing the <tt>:noop</tt> flag 1693 # to methods in FileUtils. 1694 # 1695 module NoWrite 1696 include FileUtils 1697 include LowMethods 1698 @fileutils_output = $stderr 1699 @fileutils_label = '' 1700 ::FileUtils.collect_method(:noop).each do |name| 1701 module_eval(<<-EOS, __FILE__, __LINE__ + 1) 1702 def #{name}(*args) 1703 super(*fu_update_option(args, :noop => true)) 1704 end 1705 private :#{name} 1706 EOS 1707 end 1708 extend self 1709 class << self 1710 ::FileUtils::METHODS.each do |m| 1711 public m 1712 end 1713 end 1714 end 1715 1716 # 1717 # This module has all methods of FileUtils module, but never changes 1718 # files/directories, with printing message before acting. 1719 # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag 1720 # to methods in FileUtils. 1721 # 1722 module DryRun 1723 include FileUtils 1724 include LowMethods 1725 @fileutils_output = $stderr 1726 @fileutils_label = '' 1727 ::FileUtils.collect_method(:noop).each do |name| 1728 module_eval(<<-EOS, __FILE__, __LINE__ + 1) 1729 def #{name}(*args) 1730 super(*fu_update_option(args, :noop => true, :verbose => true)) 1731 end 1732 private :#{name} 1733 EOS 1734 end 1735 extend self 1736 class << self 1737 ::FileUtils::METHODS.each do |m| 1738 public m 1739 end 1740 end 1741 end 1742 1743end 1744