memfs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/fileutils.rb ADDED
@@ -0,0 +1,1738 @@
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