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