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