rubysl-fileutils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b32a64d95c1993c930254216320469b966869446
4
+ data.tar.gz: 387397e75debc65e26b03d8ade363b78a25e3875
5
+ SHA512:
6
+ metadata.gz: 6cc84df8c5cb4d77a9b3605e63b833a20217424e5b20a65ebb217178754b530473d0d8b332c8ee9c65696c9ee8966a5a3943a8f132cca5e674afaa1549e22daa
7
+ data.tar.gz: 2e13c00eebfcd395eccc9df567c940072d611e420ab5f2cd1bff29e1a41e7d1d4fdd435831f992a8b85c72256059ac141099bd854050aba96ba075761f8f3e56
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ env:
3
+ - RUBYLIB=lib
4
+ script: bundle exec mspec
5
+ rvm:
6
+ - 1.8.7
7
+ - rbx-nightly-18mode
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rubysl-fileutils.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2013, Brian Shirai
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ 3. Neither the name of the library nor the names of its contributors may be
13
+ used to endorse or promote products derived from this software without
14
+ specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
20
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
25
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,29 @@
1
+ # Rubysl::Fileutils
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'rubysl-fileutils'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install rubysl-fileutils
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ require "rubysl/fileutils"
@@ -0,0 +1,2 @@
1
+ require "rubysl/fileutils/version"
2
+ require "rubysl/fileutils/fileutils"
@@ -0,0 +1,1598 @@
1
+ #
2
+ # = fileutils.rb
3
+ #
4
+ # Copyright (c) 2000-2006 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
+
85
+ module 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
+ 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
123
+ end
124
+ module_function :cd
125
+
126
+ alias chdir cd
127
+ module_function :chdir
128
+
129
+ OPT_TABLE['cd'] =
130
+ OPT_TABLE['chdir'] = [:verbose]
131
+
132
+ #
133
+ # Options: (none)
134
+ #
135
+ # Returns true if +newer+ is newer than all +old_list+.
136
+ # Non-existent files are older than any file.
137
+ #
138
+ # FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
139
+ # system 'make hello.o'
140
+ #
141
+ def uptodate?(new, old_list, options = nil)
142
+ raise ArgumentError, 'uptodate? does not accept any option' if options
143
+
144
+ return false unless File.exist?(new)
145
+ new_time = File.mtime(new)
146
+ old_list.each do |old|
147
+ if File.exist?(old)
148
+ return false unless new_time > File.mtime(old)
149
+ end
150
+ end
151
+ true
152
+ end
153
+ module_function :uptodate?
154
+
155
+ #
156
+ # Options: mode noop verbose
157
+ #
158
+ # Creates one or more directories.
159
+ #
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
164
+ #
165
+ def mkdir(list, options = {})
166
+ fu_check_options options, OPT_TABLE['mkdir']
167
+ list = fu_list(list)
168
+ fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
169
+ return if options[:noop]
170
+
171
+ list.each do |dir|
172
+ fu_mkdir dir, options[:mode]
173
+ end
174
+ end
175
+ module_function :mkdir
176
+
177
+ OPT_TABLE['mkdir'] = [:mode, :noop, :verbose]
178
+
179
+ #
180
+ # Options: mode noop verbose
181
+ #
182
+ # Creates a directory and all its parent directories.
183
+ # For example,
184
+ #
185
+ # FileUtils.mkdir_p '/usr/local/lib/ruby'
186
+ #
187
+ # causes to make following directories, if it does not exist.
188
+ # * /usr
189
+ # * /usr/local
190
+ # * /usr/local/lib
191
+ # * /usr/local/lib/ruby
192
+ #
193
+ # You can pass several directories at a time in a list.
194
+ #
195
+ def mkdir_p(list, options = {})
196
+ fu_check_options options, OPT_TABLE['mkdir_p']
197
+ list = fu_list(list)
198
+ fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
199
+ return *list if options[:noop]
200
+
201
+ list.map {|path| path.sub(%r</\z>, '') }.each do |path|
202
+ # optimize for the most common case
203
+ begin
204
+ fu_mkdir path, options[:mode]
205
+ next
206
+ rescue SystemCallError
207
+ next if File.directory?(path)
208
+ end
209
+
210
+ stack = []
211
+ until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
212
+ stack.push path
213
+ path = File.dirname(path)
214
+ end
215
+ stack.reverse_each do |path|
216
+ begin
217
+ fu_mkdir path, options[:mode]
218
+ rescue SystemCallError => err
219
+ raise unless File.directory?(path)
220
+ end
221
+ end
222
+ end
223
+
224
+ return *list
225
+ end
226
+ module_function :mkdir_p
227
+
228
+ alias mkpath mkdir_p
229
+ alias makedirs mkdir_p
230
+ module_function :mkpath
231
+ module_function :makedirs
232
+
233
+ OPT_TABLE['mkdir_p'] =
234
+ OPT_TABLE['mkpath'] =
235
+ OPT_TABLE['makedirs'] = [:mode, :noop, :verbose]
236
+
237
+ def fu_mkdir(path, mode) #:nodoc:
238
+ path = path.sub(%r</\z>, '')
239
+ if mode
240
+ Dir.mkdir path, mode
241
+ File.chmod mode, path
242
+ else
243
+ Dir.mkdir path
244
+ end
245
+ end
246
+ private_module_function :fu_mkdir
247
+
248
+ #
249
+ # Options: noop, verbose
250
+ #
251
+ # Removes one or more directories.
252
+ #
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
257
+ #
258
+ def rmdir(list, options = {})
259
+ fu_check_options options, OPT_TABLE['rmdir']
260
+ list = fu_list(list)
261
+ fu_output_message "rmdir #{list.join ' '}" if options[:verbose]
262
+ return if options[:noop]
263
+ list.each do |dir|
264
+ Dir.rmdir dir.sub(%r</\z>, '')
265
+ end
266
+ end
267
+ module_function :rmdir
268
+
269
+ OPT_TABLE['rmdir'] = [:noop, :verbose]
270
+
271
+ #
272
+ # Options: force noop verbose
273
+ #
274
+ # <b><tt>ln(old, new, options = {})</tt></b>
275
+ #
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+.
280
+ #
281
+ # FileUtils.ln 'gcc', 'cc', :verbose => true
282
+ # FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
283
+ #
284
+ # <b><tt>ln(list, destdir, options = {})</tt></b>
285
+ #
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.
288
+ #
289
+ # include FileUtils
290
+ # cd '/sbin'
291
+ # FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
292
+ #
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]
299
+ File.link s, d
300
+ end
301
+ end
302
+ module_function :ln
303
+
304
+ alias link ln
305
+ module_function :link
306
+
307
+ OPT_TABLE['ln'] =
308
+ OPT_TABLE['link'] = [:force, :noop, :verbose]
309
+
310
+ #
311
+ # Options: force noop verbose
312
+ #
313
+ # <b><tt>ln_s(old, new, options = {})</tt></b>
314
+ #
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+.
319
+ #
320
+ # FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
321
+ # FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true
322
+ #
323
+ # <b><tt>ln_s(list, destdir, options = {})</tt></b>
324
+ #
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.
327
+ #
328
+ # If +destdir+ is not a directory, raises Errno::ENOTDIR.
329
+ #
330
+ # FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
331
+ #
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]
338
+ File.symlink s, d
339
+ end
340
+ end
341
+ module_function :ln_s
342
+
343
+ alias symlink ln_s
344
+ module_function :symlink
345
+
346
+ OPT_TABLE['ln_s'] =
347
+ OPT_TABLE['symlink'] = [:force, :noop, :verbose]
348
+
349
+ #
350
+ # Options: noop verbose
351
+ #
352
+ # Same as
353
+ # #ln_s(src, dest, :force)
354
+ #
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
360
+ end
361
+ module_function :ln_sf
362
+
363
+ OPT_TABLE['ln_sf'] = [:noop, :verbose]
364
+
365
+ #
366
+ # Options: preserve noop verbose
367
+ #
368
+ # Copies a file content +src+ to +dest+. If +dest+ is a directory,
369
+ # copies +src+ to +dest/src+.
370
+ #
371
+ # If +src+ is a list of files, then +dest+ must be a directory.
372
+ #
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
377
+ #
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]
384
+ end
385
+ end
386
+ module_function :cp
387
+
388
+ alias copy cp
389
+ module_function :copy
390
+
391
+ OPT_TABLE['cp'] =
392
+ OPT_TABLE['copy'] = [:preserve, :noop, :verbose]
393
+
394
+ #
395
+ # Options: preserve noop verbose dereference_root remove_destination
396
+ #
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+.
400
+ #
401
+ # +src+ can be a list of files.
402
+ #
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'
406
+ #
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
410
+ #
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.
416
+ #
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 = options.dup
422
+ options[:dereference_root] = true unless options.key?(:dereference_root)
423
+ fu_each_src_dest(src, dest) do |s, d|
424
+ copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
425
+ end
426
+ end
427
+ module_function :cp_r
428
+
429
+ OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose,
430
+ :dereference_root, :remove_destination]
431
+
432
+ #
433
+ # Copies a file system entry +src+ to +dest+.
434
+ # If +src+ is a directory, this method copies its contents recursively.
435
+ # This method preserves file types, c.f. symlink, directory...
436
+ # (FIFO, device files and etc. are not supported yet)
437
+ #
438
+ # Both of +src+ and +dest+ must be a path name.
439
+ # +src+ must exist, +dest+ must not exist.
440
+ #
441
+ # If +preserve+ is true, this method preserves owner, group, permissions
442
+ # and modified time.
443
+ #
444
+ # If +dereference_root+ is true, this method dereference tree root.
445
+ #
446
+ # If +remove_destination+ is true, this method removes each destination file before copy.
447
+ #
448
+ def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
449
+ Entry_.new(src, nil, dereference_root).traverse do |ent|
450
+ destent = Entry_.new(dest, ent.rel, false)
451
+ File.unlink destent.path if remove_destination && File.file?(destent.path)
452
+ ent.copy destent.path
453
+ ent.copy_metadata destent.path if preserve
454
+ end
455
+ end
456
+ module_function :copy_entry
457
+
458
+ #
459
+ # Copies file contents of +src+ to +dest+.
460
+ # Both of +src+ and +dest+ must be a path name.
461
+ #
462
+ def copy_file(src, dest, preserve = false, dereference = true)
463
+ ent = Entry_.new(src, nil, dereference)
464
+ ent.copy_file dest
465
+ ent.copy_metadata dest if preserve
466
+ end
467
+ module_function :copy_file
468
+
469
+ #
470
+ # Copies stream +src+ to +dest+.
471
+ # +src+ must respond to #read(n) and
472
+ # +dest+ must respond to #write(str).
473
+ #
474
+ def copy_stream(src, dest)
475
+ fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
476
+ end
477
+ module_function :copy_stream
478
+
479
+ #
480
+ # Options: force noop verbose
481
+ #
482
+ # Moves file(s) +src+ to +dest+. If +file+ and +dest+ exist on the different
483
+ # disk partition, the file is copied instead.
484
+ #
485
+ # FileUtils.mv 'badname.rb', 'goodname.rb'
486
+ # FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true # no error
487
+ #
488
+ # FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
489
+ # FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
490
+ #
491
+ def mv(src, dest, options = {})
492
+ fu_check_options options, OPT_TABLE['mv']
493
+ fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
494
+ return if options[:noop]
495
+ fu_each_src_dest(src, dest) do |s, d|
496
+ destent = Entry_.new(d, nil, true)
497
+ begin
498
+ if destent.exist?
499
+ if destent.directory?
500
+ raise Errno::EEXIST, dest
501
+ else
502
+ destent.remove_file if rename_cannot_overwrite_file?
503
+ end
504
+ end
505
+ begin
506
+ File.rename s, d
507
+ rescue Errno::EXDEV
508
+ copy_entry s, d, true
509
+ if options[:secure]
510
+ remove_entry_secure s, options[:force]
511
+ else
512
+ remove_entry s, options[:force]
513
+ end
514
+ end
515
+ rescue SystemCallError
516
+ raise unless options[:force]
517
+ end
518
+ end
519
+ end
520
+ module_function :mv
521
+
522
+ alias move mv
523
+ module_function :move
524
+
525
+ OPT_TABLE['mv'] =
526
+ OPT_TABLE['move'] = [:force, :noop, :verbose, :secure]
527
+
528
+ def rename_cannot_overwrite_file? #:nodoc:
529
+ /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
530
+ end
531
+ private_module_function :rename_cannot_overwrite_file?
532
+
533
+ #
534
+ # Options: force noop verbose
535
+ #
536
+ # Remove file(s) specified in +list+. This method cannot remove directories.
537
+ # All StandardErrors are ignored when the :force option is set.
538
+ #
539
+ # FileUtils.rm %w( junk.txt dust.txt )
540
+ # FileUtils.rm Dir.glob('*.so')
541
+ # FileUtils.rm 'NotExistFile', :force => true # never raises exception
542
+ #
543
+ def rm(list, options = {})
544
+ fu_check_options options, OPT_TABLE['rm']
545
+ list = fu_list(list)
546
+ fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
547
+ return if options[:noop]
548
+
549
+ list.each do |path|
550
+ remove_file path, options[:force]
551
+ end
552
+ end
553
+ module_function :rm
554
+
555
+ alias remove rm
556
+ module_function :remove
557
+
558
+ OPT_TABLE['rm'] =
559
+ OPT_TABLE['remove'] = [:force, :noop, :verbose]
560
+
561
+ #
562
+ # Options: noop verbose
563
+ #
564
+ # Equivalent to
565
+ #
566
+ # #rm(list, :force => true)
567
+ #
568
+ def rm_f(list, options = {})
569
+ fu_check_options options, OPT_TABLE['rm_f']
570
+ options = options.dup
571
+ options[:force] = true
572
+ rm list, options
573
+ end
574
+ module_function :rm_f
575
+
576
+ alias safe_unlink rm_f
577
+ module_function :safe_unlink
578
+
579
+ OPT_TABLE['rm_f'] =
580
+ OPT_TABLE['safe_unlink'] = [:noop, :verbose]
581
+
582
+ #
583
+ # Options: force noop verbose secure
584
+ #
585
+ # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
586
+ # removes its all contents recursively. This method ignores
587
+ # StandardError when :force option is set.
588
+ #
589
+ # FileUtils.rm_r Dir.glob('/tmp/*')
590
+ # FileUtils.rm_r '/', :force => true # :-)
591
+ #
592
+ # WARNING: This method causes local vulnerability
593
+ # if one of parent directories or removing directory tree are world
594
+ # writable (including /tmp, whose permission is 1777), and the current
595
+ # process has strong privilege such as Unix super user (root), and the
596
+ # system has symbolic link. For secure removing, read the documentation
597
+ # of #remove_entry_secure carefully, and set :secure option to true.
598
+ # Default is :secure=>false.
599
+ #
600
+ # NOTE: This method calls #remove_entry_secure if :secure option is set.
601
+ # See also #remove_entry_secure.
602
+ #
603
+ def rm_r(list, options = {})
604
+ fu_check_options options, OPT_TABLE['rm_r']
605
+ # options[:secure] = true unless options.key?(:secure)
606
+ list = fu_list(list)
607
+ fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
608
+ return if options[:noop]
609
+ list.each do |path|
610
+ if options[:secure]
611
+ remove_entry_secure path, options[:force]
612
+ else
613
+ remove_entry path, options[:force]
614
+ end
615
+ end
616
+ end
617
+ module_function :rm_r
618
+
619
+ OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure]
620
+
621
+ #
622
+ # Options: noop verbose secure
623
+ #
624
+ # Equivalent to
625
+ #
626
+ # #rm_r(list, :force => true)
627
+ #
628
+ # WARNING: This method causes local vulnerability.
629
+ # Read the documentation of #rm_r first.
630
+ #
631
+ def rm_rf(list, options = {})
632
+ fu_check_options options, OPT_TABLE['rm_rf']
633
+ options = options.dup
634
+ options[:force] = true
635
+ rm_r list, options
636
+ end
637
+ module_function :rm_rf
638
+
639
+ alias rmtree rm_rf
640
+ module_function :rmtree
641
+
642
+ OPT_TABLE['rm_rf'] =
643
+ OPT_TABLE['rmtree'] = [:noop, :verbose, :secure]
644
+
645
+ #
646
+ # This method removes a file system entry +path+. +path+ shall be a
647
+ # regular file, a directory, or something. If +path+ is a directory,
648
+ # remove it recursively. This method is required to avoid TOCTTOU
649
+ # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
650
+ # #rm_r causes security hole when:
651
+ #
652
+ # * Parent directory is world writable (including /tmp).
653
+ # * Removing directory tree includes world writable directory.
654
+ # * The system has symbolic link.
655
+ #
656
+ # To avoid this security hole, this method applies special preprocess.
657
+ # If +path+ is a directory, this method chown(2) and chmod(2) all
658
+ # removing directories. This requires the current process is the
659
+ # owner of the removing whole directory tree, or is the super user (root).
660
+ #
661
+ # WARNING: You must ensure that *ALL* parent directories cannot be
662
+ # moved by other untrusted users. For example, parent directories
663
+ # should not be owned by untrusted users, and should not be world
664
+ # writable except when the sticky bit set.
665
+ #
666
+ # WARNING: Only the owner of the removing directory tree, or Unix super
667
+ # user (root) should invoke this method. Otherwise this method does not
668
+ # work.
669
+ #
670
+ # For details of this security vulnerability, see Perl's case:
671
+ #
672
+ # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
673
+ # http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
674
+ #
675
+ # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
676
+ #
677
+ def remove_entry_secure(path, force = false)
678
+ unless fu_have_symlink?
679
+ remove_entry path, force
680
+ return
681
+ end
682
+ fullpath = File.expand_path(path)
683
+ st = File.lstat(fullpath)
684
+ unless st.directory?
685
+ File.unlink fullpath
686
+ return
687
+ end
688
+ # is a directory.
689
+ parent_st = File.stat(File.dirname(fullpath))
690
+ unless fu_world_writable?(parent_st)
691
+ remove_entry path, force
692
+ return
693
+ end
694
+ unless parent_st.sticky?
695
+ raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
696
+ end
697
+ # freeze tree root
698
+ euid = Process.euid
699
+ File.open(fullpath + '/.') {|f|
700
+ unless fu_stat_identical_entry?(st, f.stat)
701
+ # symlink (TOC-to-TOU attack?)
702
+ File.unlink fullpath
703
+ return
704
+ end
705
+ f.chown euid, -1
706
+ f.chmod 0700
707
+ unless fu_stat_identical_entry?(st, File.lstat(fullpath))
708
+ # TOC-to-TOU attack?
709
+ File.unlink fullpath
710
+ return
711
+ end
712
+ }
713
+ # ---- tree root is frozen ----
714
+ root = Entry_.new(path)
715
+ root.preorder_traverse do |ent|
716
+ if ent.directory?
717
+ ent.chown euid, -1
718
+ ent.chmod 0700
719
+ end
720
+ end
721
+ root.postorder_traverse do |ent|
722
+ begin
723
+ ent.remove
724
+ rescue
725
+ raise unless force
726
+ end
727
+ end
728
+ rescue
729
+ raise unless force
730
+ end
731
+ module_function :remove_entry_secure
732
+
733
+ def fu_world_writable?(st)
734
+ (st.mode & 0002) != 0
735
+ end
736
+ private_module_function :fu_world_writable?
737
+
738
+ def fu_have_symlink? #:nodoc
739
+ File.symlink nil, nil
740
+ rescue NotImplementedError
741
+ return false
742
+ rescue
743
+ return true
744
+ end
745
+ private_module_function :fu_have_symlink?
746
+
747
+ def fu_stat_identical_entry?(a, b) #:nodoc:
748
+ a.dev == b.dev and a.ino == b.ino
749
+ end
750
+ private_module_function :fu_stat_identical_entry?
751
+
752
+ #
753
+ # This method removes a file system entry +path+.
754
+ # +path+ might be a regular file, a directory, or something.
755
+ # If +path+ is a directory, remove it recursively.
756
+ #
757
+ # See also #remove_entry_secure.
758
+ #
759
+ def remove_entry(path, force = false)
760
+ Entry_.new(path).postorder_traverse do |ent|
761
+ begin
762
+ ent.remove
763
+ rescue
764
+ raise unless force
765
+ end
766
+ end
767
+ rescue
768
+ raise unless force
769
+ end
770
+ module_function :remove_entry
771
+
772
+ #
773
+ # Removes a file +path+.
774
+ # This method ignores StandardError if +force+ is true.
775
+ #
776
+ def remove_file(path, force = false)
777
+ Entry_.new(path).remove_file
778
+ rescue
779
+ raise unless force
780
+ end
781
+ module_function :remove_file
782
+
783
+ #
784
+ # Removes a directory +dir+ and its contents recursively.
785
+ # This method ignores StandardError if +force+ is true.
786
+ #
787
+ def remove_dir(path, force = false)
788
+ remove_entry path, force # FIXME?? check if it is a directory
789
+ end
790
+ module_function :remove_dir
791
+
792
+ #
793
+ # Returns true if the contents of a file A and a file B are identical.
794
+ #
795
+ # FileUtils.compare_file('somefile', 'somefile') #=> true
796
+ # FileUtils.compare_file('/bin/cp', '/bin/mv') #=> maybe false
797
+ #
798
+ def compare_file(a, b)
799
+ return false unless File.size(a) == File.size(b)
800
+ File.open(a, 'rb') {|fa|
801
+ File.open(b, 'rb') {|fb|
802
+ return compare_stream(fa, fb)
803
+ }
804
+ }
805
+ end
806
+ module_function :compare_file
807
+
808
+ alias identical? compare_file
809
+ alias cmp compare_file
810
+ module_function :identical?
811
+ module_function :cmp
812
+
813
+ #
814
+ # Returns true if the contents of a stream +a+ and +b+ are identical.
815
+ #
816
+ def compare_stream(a, b)
817
+ bsize = fu_stream_blksize(a, b)
818
+ sa = sb = nil
819
+ while sa == sb
820
+ sa = a.read(bsize)
821
+ sb = b.read(bsize)
822
+ unless sa and sb
823
+ if sa.nil? and sb.nil?
824
+ return true
825
+ end
826
+ end
827
+ end
828
+ false
829
+ end
830
+ module_function :compare_stream
831
+
832
+ #
833
+ # Options: mode preserve noop verbose
834
+ #
835
+ # If +src+ is not same as +dest+, copies it and changes the permission
836
+ # mode to +mode+. If +dest+ is a directory, destination is +dest+/+src+.
837
+ # This method removes destination before copy.
838
+ #
839
+ # FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
840
+ # FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
841
+ #
842
+ def install(src, dest, options = {})
843
+ fu_check_options options, OPT_TABLE['install']
844
+ fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
845
+ return if options[:noop]
846
+ fu_each_src_dest(src, dest) do |s, d|
847
+ unless File.exist?(d) and compare_file(s, d)
848
+ remove_file d, true
849
+ st = File.stat(s) if options[:preserve]
850
+ copy_file s, d
851
+ File.utime st.atime, st.mtime, d if options[:preserve]
852
+ File.chmod options[:mode], d if options[:mode]
853
+ end
854
+ end
855
+ end
856
+ module_function :install
857
+
858
+ OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose]
859
+
860
+ #
861
+ # Options: noop verbose
862
+ #
863
+ # Changes permission bits on the named files (in +list+) to the bit pattern
864
+ # represented by +mode+.
865
+ #
866
+ # FileUtils.chmod 0755, 'somecommand'
867
+ # FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
868
+ # FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
869
+ #
870
+ def chmod(mode, list, options = {})
871
+ fu_check_options options, OPT_TABLE['chmod']
872
+ list = fu_list(list)
873
+ fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
874
+ return if options[:noop]
875
+ list.each do |path|
876
+ Entry_.new(path).chmod mode
877
+ end
878
+ end
879
+ module_function :chmod
880
+
881
+ OPT_TABLE['chmod'] = [:noop, :verbose]
882
+
883
+ #
884
+ # Options: noop verbose force
885
+ #
886
+ # Changes permission bits on the named files (in +list+)
887
+ # to the bit pattern represented by +mode+.
888
+ #
889
+ # FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
890
+ #
891
+ def chmod_R(mode, list, options = {})
892
+ fu_check_options options, OPT_TABLE['chmod_R']
893
+ list = fu_list(list)
894
+ fu_output_message sprintf('chmod -R%s %o %s',
895
+ (options[:force] ? 'f' : ''),
896
+ mode, list.join(' ')) if options[:verbose]
897
+ return if options[:noop]
898
+ list.each do |root|
899
+ Entry_.new(root).traverse do |ent|
900
+ begin
901
+ ent.chmod mode
902
+ rescue
903
+ raise unless options[:force]
904
+ end
905
+ end
906
+ end
907
+ end
908
+ module_function :chmod_R
909
+
910
+ OPT_TABLE['chmod_R'] = [:noop, :verbose, :force]
911
+
912
+ #
913
+ # Options: noop verbose
914
+ #
915
+ # Changes owner and group on the named files (in +list+)
916
+ # to the user +user+ and the group +group+. +user+ and +group+
917
+ # may be an ID (Integer/String) or a name (String).
918
+ # If +user+ or +group+ is nil, this method does not change
919
+ # the attribute.
920
+ #
921
+ # FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
922
+ # FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
923
+ #
924
+ def chown(user, group, list, options = {})
925
+ fu_check_options options, OPT_TABLE['chown']
926
+ list = fu_list(list)
927
+ fu_output_message sprintf('chown %s%s',
928
+ [user,group].compact.join(':') + ' ',
929
+ list.join(' ')) if options[:verbose]
930
+ return if options[:noop]
931
+ uid = fu_get_uid(user)
932
+ gid = fu_get_gid(group)
933
+ list.each do |path|
934
+ Entry_.new(path).chown uid, gid
935
+ end
936
+ end
937
+ module_function :chown
938
+
939
+ OPT_TABLE['chown'] = [:noop, :verbose]
940
+
941
+ #
942
+ # Options: noop verbose force
943
+ #
944
+ # Changes owner and group on the named files (in +list+)
945
+ # to the user +user+ and the group +group+ recursively.
946
+ # +user+ and +group+ may be an ID (Integer/String) or
947
+ # a name (String). If +user+ or +group+ is nil, this
948
+ # method does not change the attribute.
949
+ #
950
+ # FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
951
+ # FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
952
+ #
953
+ def chown_R(user, group, list, options = {})
954
+ fu_check_options options, OPT_TABLE['chown_R']
955
+ list = fu_list(list)
956
+ fu_output_message sprintf('chown -R%s %s%s',
957
+ (options[:force] ? 'f' : ''),
958
+ [user,group].compact.join(':') + ' ',
959
+ list.join(' ')) if options[:verbose]
960
+ return if options[:noop]
961
+ uid = fu_get_uid(user)
962
+ gid = fu_get_gid(group)
963
+ return unless uid or gid
964
+ list.each do |root|
965
+ Entry_.new(root).traverse do |ent|
966
+ begin
967
+ ent.chown uid, gid
968
+ rescue
969
+ raise unless options[:force]
970
+ end
971
+ end
972
+ end
973
+ end
974
+ module_function :chown_R
975
+
976
+ OPT_TABLE['chown_R'] = [:noop, :verbose, :force]
977
+
978
+ begin
979
+ require 'etc'
980
+
981
+ def fu_get_uid(user) #:nodoc:
982
+ return nil unless user
983
+ user = user.to_s
984
+ if /\A\d+\z/ =~ user
985
+ then user.to_i
986
+ else Etc.getpwnam(user).uid
987
+ end
988
+ end
989
+ private_module_function :fu_get_uid
990
+
991
+ def fu_get_gid(group) #:nodoc:
992
+ return nil unless group
993
+ group = group.to_s
994
+ if /\A\d+\z/ =~ group
995
+ then group.to_i
996
+ else Etc.getgrnam(group).gid
997
+ end
998
+ end
999
+ private_module_function :fu_get_gid
1000
+
1001
+ rescue LoadError
1002
+ # need Win32 support???
1003
+
1004
+ def fu_get_uid(user) #:nodoc:
1005
+ user # FIXME
1006
+ end
1007
+ private_module_function :fu_get_uid
1008
+
1009
+ def fu_get_gid(group) #:nodoc:
1010
+ group # FIXME
1011
+ end
1012
+ private_module_function :fu_get_gid
1013
+ end
1014
+
1015
+ #
1016
+ # Options: noop verbose
1017
+ #
1018
+ # Updates modification time (mtime) and access time (atime) of file(s) in
1019
+ # +list+. Files are created if they don't exist.
1020
+ #
1021
+ # FileUtils.touch 'timestamp'
1022
+ # FileUtils.touch Dir.glob('*.c'); system 'make'
1023
+ #
1024
+ def touch(list, options = {})
1025
+ fu_check_options options, OPT_TABLE['touch']
1026
+ list = fu_list(list)
1027
+ created = nocreate = options[:nocreate]
1028
+ t = options[:mtime]
1029
+ if options[:verbose]
1030
+ fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}"
1031
+ end
1032
+ return if options[:noop]
1033
+ list.each do |path|
1034
+ created = nocreate
1035
+ begin
1036
+ File.utime(t, t, path)
1037
+ rescue Errno::ENOENT
1038
+ raise if created
1039
+ File.open(path, 'a') {
1040
+ ;
1041
+ }
1042
+ created = true
1043
+ retry if t
1044
+ end
1045
+ end
1046
+ end
1047
+ module_function :touch
1048
+
1049
+ OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate]
1050
+
1051
+ private
1052
+
1053
+ module StreamUtils_
1054
+ private
1055
+
1056
+ def fu_windows?
1057
+ /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
1058
+ end
1059
+
1060
+ def fu_copy_stream0(src, dest, blksize) #:nodoc:
1061
+ # FIXME: readpartial?
1062
+ while s = src.read(blksize)
1063
+ dest.write s
1064
+ end
1065
+ end
1066
+
1067
+ def fu_stream_blksize(*streams)
1068
+ streams.each do |s|
1069
+ next unless s.respond_to?(:stat)
1070
+ size = fu_blksize(s.stat)
1071
+ return size if size
1072
+ end
1073
+ fu_default_blksize()
1074
+ end
1075
+
1076
+ def fu_blksize(st)
1077
+ s = st.blksize
1078
+ return nil unless s
1079
+ return nil if s == 0
1080
+ s
1081
+ end
1082
+
1083
+ def fu_default_blksize
1084
+ 1024
1085
+ end
1086
+ end
1087
+
1088
+ include StreamUtils_
1089
+ extend StreamUtils_
1090
+
1091
+ class Entry_ #:nodoc: internal use only
1092
+ include StreamUtils_
1093
+
1094
+ def initialize(a, b = nil, deref = false)
1095
+ @prefix = @rel = @path = nil
1096
+ if b
1097
+ @prefix = a
1098
+ @rel = b
1099
+ else
1100
+ @path = a
1101
+ end
1102
+ @deref = deref
1103
+ @stat = nil
1104
+ @lstat = nil
1105
+ end
1106
+
1107
+ def inspect
1108
+ "\#<#{self.class} #{path()}>"
1109
+ end
1110
+
1111
+ def path
1112
+ if @path
1113
+ @path.to_str
1114
+ else
1115
+ join(@prefix, @rel)
1116
+ end
1117
+ end
1118
+
1119
+ def prefix
1120
+ @prefix || @path
1121
+ end
1122
+
1123
+ def rel
1124
+ @rel
1125
+ end
1126
+
1127
+ def dereference?
1128
+ @deref
1129
+ end
1130
+
1131
+ def exist?
1132
+ lstat! ? true : false
1133
+ end
1134
+
1135
+ def file?
1136
+ s = lstat!
1137
+ s and s.file?
1138
+ end
1139
+
1140
+ def directory?
1141
+ s = lstat!
1142
+ s and s.directory?
1143
+ end
1144
+
1145
+ def symlink?
1146
+ s = lstat!
1147
+ s and s.symlink?
1148
+ end
1149
+
1150
+ def chardev?
1151
+ s = lstat!
1152
+ s and s.chardev?
1153
+ end
1154
+
1155
+ def blockdev?
1156
+ s = lstat!
1157
+ s and s.blockdev?
1158
+ end
1159
+
1160
+ def socket?
1161
+ s = lstat!
1162
+ s and s.socket?
1163
+ end
1164
+
1165
+ def pipe?
1166
+ s = lstat!
1167
+ s and s.pipe?
1168
+ end
1169
+
1170
+ S_IF_DOOR = 0xD000
1171
+
1172
+ def door?
1173
+ s = lstat!
1174
+ s and (s.mode & 0xF000 == S_IF_DOOR)
1175
+ end
1176
+
1177
+ def entries
1178
+ Dir.entries(path())\
1179
+ .reject {|n| n == '.' or n == '..' }\
1180
+ .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
1181
+ end
1182
+
1183
+ def stat
1184
+ return @stat if @stat
1185
+ if lstat() and lstat().symlink?
1186
+ @stat = File.stat(path())
1187
+ else
1188
+ @stat = lstat()
1189
+ end
1190
+ @stat
1191
+ end
1192
+
1193
+ def stat!
1194
+ return @stat if @stat
1195
+ if lstat! and lstat!.symlink?
1196
+ @stat = File.stat(path())
1197
+ else
1198
+ @stat = lstat!
1199
+ end
1200
+ @stat
1201
+ rescue SystemCallError
1202
+ nil
1203
+ end
1204
+
1205
+ def lstat
1206
+ if dereference?
1207
+ @lstat ||= File.stat(path())
1208
+ else
1209
+ @lstat ||= File.lstat(path())
1210
+ end
1211
+ end
1212
+
1213
+ def lstat!
1214
+ lstat()
1215
+ rescue SystemCallError
1216
+ nil
1217
+ end
1218
+
1219
+ def chmod(mode)
1220
+ if symlink?
1221
+ File.lchmod mode, path() if have_lchmod?
1222
+ else
1223
+ File.chmod mode, path()
1224
+ end
1225
+ end
1226
+
1227
+ def chown(uid, gid)
1228
+ if symlink?
1229
+ File.lchown uid, gid, path() if have_lchown?
1230
+ else
1231
+ File.chown uid, gid, path()
1232
+ end
1233
+ end
1234
+
1235
+ def copy(dest)
1236
+ case
1237
+ when file?
1238
+ copy_file dest
1239
+ when directory?
1240
+ begin
1241
+ Dir.mkdir dest
1242
+ rescue
1243
+ raise unless File.directory?(dest)
1244
+ end
1245
+ when symlink?
1246
+ File.symlink File.readlink(path()), dest
1247
+ when chardev?
1248
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
1249
+ mknod dest, ?c, 0666, lstat().rdev
1250
+ when blockdev?
1251
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
1252
+ mknod dest, ?b, 0666, lstat().rdev
1253
+ when socket?
1254
+ raise "cannot handle socket" unless File.respond_to?(:mknod)
1255
+ mknod dest, nil, lstat().mode, 0
1256
+ when pipe?
1257
+ raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
1258
+ mkfifo dest, 0666
1259
+ when door?
1260
+ raise "cannot handle door: #{path()}"
1261
+ else
1262
+ raise "unknown file type: #{path()}"
1263
+ end
1264
+ end
1265
+
1266
+ def copy_file(dest)
1267
+ st = stat()
1268
+ File.open(path(), 'rb') {|r|
1269
+ File.open(dest, 'wb', st.mode) {|w|
1270
+ fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
1271
+ }
1272
+ }
1273
+ end
1274
+
1275
+ def copy_metadata(path)
1276
+ st = lstat()
1277
+ File.utime st.atime, st.mtime, path
1278
+ begin
1279
+ File.chown st.uid, st.gid, path
1280
+ rescue Errno::EPERM
1281
+ # clear setuid/setgid
1282
+ File.chmod st.mode & 01777, path
1283
+ else
1284
+ File.chmod st.mode, path
1285
+ end
1286
+ end
1287
+
1288
+ def remove
1289
+ if directory?
1290
+ remove_dir1
1291
+ else
1292
+ remove_file
1293
+ end
1294
+ end
1295
+
1296
+ def remove_dir1
1297
+ platform_support {
1298
+ Dir.rmdir path().sub(%r</\z>, '')
1299
+ }
1300
+ end
1301
+
1302
+ def remove_file
1303
+ platform_support {
1304
+ File.unlink path
1305
+ }
1306
+ end
1307
+
1308
+ def platform_support
1309
+ return yield unless fu_windows?
1310
+ first_time_p = true
1311
+ begin
1312
+ yield
1313
+ rescue Errno::ENOENT
1314
+ raise
1315
+ rescue => err
1316
+ if first_time_p
1317
+ first_time_p = false
1318
+ begin
1319
+ File.chmod 0700, path() # Windows does not have symlink
1320
+ retry
1321
+ rescue SystemCallError
1322
+ end
1323
+ end
1324
+ raise err
1325
+ end
1326
+ end
1327
+
1328
+ def preorder_traverse
1329
+ stack = [self]
1330
+ while ent = stack.pop
1331
+ yield ent
1332
+ stack.concat ent.entries.reverse if ent.directory?
1333
+ end
1334
+ end
1335
+
1336
+ alias traverse preorder_traverse
1337
+
1338
+ def postorder_traverse
1339
+ if directory?
1340
+ entries().each do |ent|
1341
+ ent.postorder_traverse do |e|
1342
+ yield e
1343
+ end
1344
+ end
1345
+ end
1346
+ yield self
1347
+ end
1348
+
1349
+ private
1350
+
1351
+ $fileutils_rb_have_lchmod = nil
1352
+
1353
+ def have_lchmod?
1354
+ # This is not MT-safe, but it does not matter.
1355
+ if $fileutils_rb_have_lchmod == nil
1356
+ $fileutils_rb_have_lchmod = check_have_lchmod?
1357
+ end
1358
+ $fileutils_rb_have_lchmod
1359
+ end
1360
+
1361
+ def check_have_lchmod?
1362
+ return false unless File.respond_to?(:lchmod)
1363
+ File.lchmod 0
1364
+ return true
1365
+ rescue NotImplementedError
1366
+ return false
1367
+ end
1368
+
1369
+ $fileutils_rb_have_lchown = nil
1370
+
1371
+ def have_lchown?
1372
+ # This is not MT-safe, but it does not matter.
1373
+ if $fileutils_rb_have_lchown == nil
1374
+ $fileutils_rb_have_lchown = check_have_lchown?
1375
+ end
1376
+ $fileutils_rb_have_lchown
1377
+ end
1378
+
1379
+ def check_have_lchown?
1380
+ return false unless File.respond_to?(:lchown)
1381
+ File.lchown nil, nil
1382
+ return true
1383
+ rescue NotImplementedError
1384
+ return false
1385
+ end
1386
+
1387
+ def join(dir, base)
1388
+ return dir.to_str if not base or base == '.'
1389
+ return base.to_str if not dir or dir == '.'
1390
+ File.join(dir, base)
1391
+ end
1392
+ end # class Entry_
1393
+
1394
+ def fu_list(arg) #:nodoc:
1395
+ [arg].flatten.map {|path| path.to_str }
1396
+ end
1397
+ private_module_function :fu_list
1398
+
1399
+ def fu_each_src_dest(src, dest) #:nodoc:
1400
+ fu_each_src_dest0(src, dest) do |s, d|
1401
+ raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
1402
+ yield s, d
1403
+ end
1404
+ end
1405
+ private_module_function :fu_each_src_dest
1406
+
1407
+ def fu_each_src_dest0(src, dest) #:nodoc:
1408
+ if src.is_a?(Array)
1409
+ src.each do |s|
1410
+ s = s.to_str
1411
+ yield s, File.join(dest, File.basename(s))
1412
+ end
1413
+ else
1414
+ src = src.to_str
1415
+ if File.directory?(dest)
1416
+ yield src, File.join(dest, File.basename(src))
1417
+ else
1418
+ yield src, dest.to_str
1419
+ end
1420
+ end
1421
+ end
1422
+ private_module_function :fu_each_src_dest0
1423
+
1424
+ def fu_same?(a, b) #:nodoc:
1425
+ if fu_have_st_ino?
1426
+ st1 = File.stat(a)
1427
+ st2 = File.stat(b)
1428
+ st1.dev == st2.dev and st1.ino == st2.ino
1429
+ else
1430
+ File.expand_path(a) == File.expand_path(b)
1431
+ end
1432
+ rescue Errno::ENOENT
1433
+ return false
1434
+ end
1435
+ private_module_function :fu_same?
1436
+
1437
+ def fu_have_st_ino? #:nodoc:
1438
+ not fu_windows?
1439
+ end
1440
+ private_module_function :fu_have_st_ino?
1441
+
1442
+ def fu_check_options(options, optdecl) #:nodoc:
1443
+ h = options.dup
1444
+ optdecl.each do |opt|
1445
+ h.delete opt
1446
+ end
1447
+ raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
1448
+ end
1449
+ private_module_function :fu_check_options
1450
+
1451
+ def fu_update_option(args, new) #:nodoc:
1452
+ if args.last.is_a?(Hash)
1453
+ args[-1] = args.last.dup.update(new)
1454
+ else
1455
+ args.push new
1456
+ end
1457
+ args
1458
+ end
1459
+ private_module_function :fu_update_option
1460
+
1461
+ @fileutils_output = $stderr
1462
+ @fileutils_label = ''
1463
+
1464
+ def fu_output_message(msg) #:nodoc:
1465
+ @fileutils_output ||= $stderr
1466
+ @fileutils_label ||= ''
1467
+ @fileutils_output.puts @fileutils_label + msg
1468
+ end
1469
+ private_module_function :fu_output_message
1470
+
1471
+ #
1472
+ # Returns an Array of method names which have any options.
1473
+ #
1474
+ # p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
1475
+ #
1476
+ def FileUtils.commands
1477
+ OPT_TABLE.keys
1478
+ end
1479
+
1480
+ #
1481
+ # Returns an Array of option names.
1482
+ #
1483
+ # p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
1484
+ #
1485
+ def FileUtils.options
1486
+ OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
1487
+ end
1488
+
1489
+ #
1490
+ # Returns true if the method +mid+ have an option +opt+.
1491
+ #
1492
+ # p FileUtils.have_option?(:cp, :noop) #=> true
1493
+ # p FileUtils.have_option?(:rm, :force) #=> true
1494
+ # p FileUtils.have_option?(:rm, :perserve) #=> false
1495
+ #
1496
+ def FileUtils.have_option?(mid, opt)
1497
+ li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
1498
+ li.include?(opt)
1499
+ end
1500
+
1501
+ #
1502
+ # Returns an Array of option names of the method +mid+.
1503
+ #
1504
+ # p FileUtils.options(:rm) #=> ["noop", "verbose", "force"]
1505
+ #
1506
+ def FileUtils.options_of(mid)
1507
+ OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
1508
+ end
1509
+
1510
+ #
1511
+ # Returns an Array of method names which have the option +opt+.
1512
+ #
1513
+ # p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
1514
+ #
1515
+ def FileUtils.collect_method(opt)
1516
+ OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
1517
+ end
1518
+
1519
+ METHODS = singleton_methods() - %w( private_module_function
1520
+ commands options have_option? options_of collect_method )
1521
+
1522
+ #
1523
+ # This module has all methods of FileUtils module, but it outputs messages
1524
+ # before acting. This equates to passing the <tt>:verbose</tt> flag to
1525
+ # methods in FileUtils.
1526
+ #
1527
+ module Verbose
1528
+ include FileUtils
1529
+ @fileutils_output = $stderr
1530
+ @fileutils_label = ''
1531
+ ::FileUtils.collect_method(:verbose).each do |name|
1532
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1533
+ def #{name}(*args)
1534
+ super(*fu_update_option(args, :verbose => true))
1535
+ end
1536
+ private :#{name}
1537
+ EOS
1538
+ end
1539
+ extend self
1540
+ class << self
1541
+ ::FileUtils::METHODS.each do |m|
1542
+ public m
1543
+ end
1544
+ end
1545
+ end
1546
+
1547
+ #
1548
+ # This module has all methods of FileUtils module, but never changes
1549
+ # files/directories. This equates to passing the <tt>:noop</tt> flag
1550
+ # to methods in FileUtils.
1551
+ #
1552
+ module NoWrite
1553
+ include FileUtils
1554
+ @fileutils_output = $stderr
1555
+ @fileutils_label = ''
1556
+ ::FileUtils.collect_method(:noop).each do |name|
1557
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1558
+ def #{name}(*args)
1559
+ super(*fu_update_option(args, :noop => true))
1560
+ end
1561
+ private :#{name}
1562
+ EOS
1563
+ end
1564
+ extend self
1565
+ class << self
1566
+ ::FileUtils::METHODS.each do |m|
1567
+ public m
1568
+ end
1569
+ end
1570
+ end
1571
+
1572
+ #
1573
+ # This module has all methods of FileUtils module, but never changes
1574
+ # files/directories, with printing message before acting.
1575
+ # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
1576
+ # to methods in FileUtils.
1577
+ #
1578
+ module DryRun
1579
+ include FileUtils
1580
+ @fileutils_output = $stderr
1581
+ @fileutils_label = ''
1582
+ ::FileUtils.collect_method(:noop).each do |name|
1583
+ module_eval(<<-EOS, __FILE__, __LINE__ + 1)
1584
+ def #{name}(*args)
1585
+ super(*fu_update_option(args, :noop => true, :verbose => true))
1586
+ end
1587
+ private :#{name}
1588
+ EOS
1589
+ end
1590
+ extend self
1591
+ class << self
1592
+ ::FileUtils::METHODS.each do |m|
1593
+ public m
1594
+ end
1595
+ end
1596
+ end
1597
+
1598
+ end