fileutils 0.7.1

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