memfs 0.0.2 → 0.1.0

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