rubysl-fileutils 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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