memfs 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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