ion1-mischacks 0.0.3 → 0.0.4

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/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ === 0.0.4 / 2009-06-28
2
+
3
+ * Implement overwrite, a method to safely replace a file.
4
+ * Update README.
5
+ * Move exception printing from sh to catching_exit.
6
+
1
7
  === 0.0.3 / 2009-06-28
2
8
 
3
9
  * Rename checking_exit_status as fork_and_check to make it clear it forks.
data/README.txt CHANGED
@@ -4,7 +4,30 @@
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Safely pass untrusted parameters to sh scripts
7
+ Miscellaneous methods that may or may not be useful.
8
+
9
+ sh:: Safely pass untrusted parameters to sh scripts.
10
+
11
+ fork_and_check:: Run a block in a forked process and raise an exception if the
12
+ process returns a non-zero value.
13
+
14
+ do_and_exit, do_and_exit!:: Run a block. If the block does not run exit!, a
15
+ successful exec or equivalent, run exit(1) or exit!(1) ourselves. Useful to
16
+ make sure a forked block either runs a successful exec or dies.
17
+
18
+ Any exceptions from the block are printed to standard error.
19
+
20
+ overwrite:: Safely replace a file. Writes to a temporary file and then moves it
21
+ over the old file.
22
+
23
+ tempname_for:: Generates an unique temporary path based on a filename. The
24
+ generated filename resides in the same directory as the original one.
25
+
26
+ try_n_times:: Retries a block of code until it succeeds or a maximum number of
27
+ attempts (default 10) is exceeded.
28
+
29
+ Exception#to_formatted_string:: Returns a string that looks like how Ruby would
30
+ dump an uncaught exception.
8
31
 
9
32
  == FEATURES/PROBLEMS:
10
33
 
@@ -14,6 +37,8 @@ ever use an untrusted variable as a command.
14
37
 
15
38
  == SYNOPSIS:
16
39
 
40
+ # sh
41
+
17
42
  MiscHacks.sh %q{
18
43
  diff -u "$1" "$2" | tr a-z A-Z >"$output"
19
44
  }, '/dev/null', '/etc/motd', :output => 'foo'
@@ -21,6 +46,42 @@ ever use an untrusted variable as a command.
21
46
  unsafe_str = %q{" 'foo' $(bar) `baz` "}
22
47
  MiscHacks.sh 'printf "%s\n" "$1"', unsafe_str
23
48
 
49
+ # fork_and_check
50
+
51
+ # These examples raise MiscHacks::ChildError.
52
+ MiscHacks.fork_and_check do exit! 42 end
53
+ MiscHacks.fork_and_check do exec 'sh', '-c', 'exit 42' end
54
+ MiscHacks.fork_and_check do exec 'failure' end
55
+
56
+ # Does not raise an error.
57
+ MiscHacks.fork_and_check do exit! 0 end
58
+
59
+ # do_and_exit
60
+
61
+ # Prints foo if there are no failures. If anything fails, raises an
62
+ # exception.
63
+ MiscHacks.fork_and_check do
64
+ MiscHacks.do_and_exit! do
65
+ exec 'sh', '-c', 'echo foo'
66
+ end
67
+ end
68
+
69
+ # overwrite
70
+
71
+ MiscHacks.overwrite 'myconfig' do |io|
72
+ io << config.to_yaml
73
+ end
74
+
75
+ # tempname_for
76
+
77
+ MiscHacks.tempname_for '/foo/bar/baz' # => '/foo/bar/.baz.klyce3f517qkh9l'
78
+
79
+ # try_n_times
80
+
81
+ io = MiscHacks.try_n_times do
82
+ File.open path, File::RDWR|File::CREAT|File::EXCL
83
+ end
84
+
24
85
  == REQUIREMENTS:
25
86
 
26
87
  * POSIX sh
data/lib/mischacks.rb CHANGED
@@ -14,7 +14,7 @@
14
14
  # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
15
 
16
16
  module MiscHacks
17
- VERSION = '0.0.3'
17
+ VERSION = '0.0.4'
18
18
 
19
19
  class ChildError < RuntimeError
20
20
  attr_reader :status
@@ -25,6 +25,19 @@ module MiscHacks
25
25
  end
26
26
  end
27
27
 
28
+ def self.sh cmd, *args
29
+ env = if args.last.is_a? Hash then args.pop else {} end
30
+
31
+ fork_and_check do
32
+ do_and_exit! do
33
+ env.each_pair do |k, v| ENV[k.to_s] = v.to_s end
34
+ exec *(%W{sh -e -c #{cmd} sh} + args.map {|a| a.to_s })
35
+ end
36
+ end
37
+
38
+ nil
39
+ end
40
+
28
41
  def self.fork_and_check
29
42
  fork do
30
43
  yield
@@ -43,6 +56,8 @@ module MiscHacks
43
56
  yield
44
57
  rescue SystemExit => e
45
58
  status = e.status
59
+ rescue Exception => e
60
+ warn e.to_formatted_string
46
61
  ensure
47
62
  final_proc.call status
48
63
  end
@@ -58,21 +73,66 @@ module MiscHacks
58
73
  catching_exit method(:exit!), status, &block
59
74
  end
60
75
 
61
- def self.sh cmd, *args
62
- env = if args.last.is_a? Hash then args.pop else {} end
76
+ def self.overwrite path, mode=nil
77
+ begin
78
+ stat = File.stat path
63
79
 
64
- fork_and_check do
65
- do_and_exit! do
66
- begin
67
- env.each_pair do |k, v| ENV[k.to_s] = v.to_s end
68
- exec *(%W{sh -e -c #{cmd} sh} + args.map {|a| a.to_s })
69
- rescue Exception => e
70
- warn e.to_formatted_string
71
- end
72
- end
80
+ raise ArgumentError, "Not a file: #{path}", caller unless stat.file?
81
+ raise ArgumentError, "Not writable: #{path}", caller unless stat.writable?
82
+
83
+ mode ||= stat.mode & 0777
84
+ rescue Errno::ENOENT
85
+ stat = nil
73
86
  end
74
87
 
75
- nil
88
+ temppath, io = try_n_times do
89
+ t = tempname_for path
90
+ io = File.open t, File::RDWR|File::CREAT|File::EXCL
91
+ [t, io]
92
+ end
93
+
94
+ begin
95
+ ret = yield io
96
+
97
+ File.chmod mode, temppath if mode
98
+
99
+ File.rename temppath, path
100
+
101
+ rescue
102
+ File.unlink temppath
103
+ raise
104
+
105
+ ensure
106
+ io.close
107
+ end
108
+
109
+ ret
110
+ end
111
+
112
+ def self.tempname_for path
113
+ dirname = File.dirname path
114
+ basename = File.basename path
115
+
116
+ '%s/.%s.%s%s%s' % [
117
+ dirname,
118
+ basename,
119
+ Time.now.to_i.to_s(36),
120
+ $$.to_s(36),
121
+ rand(1<<32).to_s(36)
122
+ ]
123
+ end
124
+
125
+ def self.try_n_times n=10
126
+ i = 0
127
+ begin
128
+ ret = yield
129
+ rescue
130
+ i += 1
131
+ retry if i < n
132
+ raise
133
+ end
134
+
135
+ ret
76
136
  end
77
137
  end
78
138
 
data/mischacks.gemspec CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{mischacks}
5
- s.version = "0.0.3"
5
+ s.version = "0.0.4"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Johan Kiviniemi"]
9
9
  s.date = %q{2009-06-28}
10
- s.description = %q{Safely pass untrusted parameters to sh scripts}
10
+ s.description = %q{Miscellaneous methods that may or may not be useful. sh:: Safely pass untrusted parameters to sh scripts. fork_and_check:: Run a block in a forked process and raise an exception if the process returns a non-zero value. do_and_exit, do_and_exit!:: Run a block. If the block does not run exit!, a successful exec or equivalent, run exit(1) or exit!(1) ourselves. Useful to make sure a forked block either runs a successful exec or dies. Any exceptions from the block are printed to standard error. overwrite:: Safely replace a file. Writes to a temporary file and then moves it over the old file. tempname_for:: Generates an unique temporary path based on a filename. The generated filename resides in the same directory as the original one. try_n_times:: Retries a block of code until it succeeds or a maximum number of attempts (default 10) is exceeded. Exception#to_formatted_string:: Returns a string that looks like how Ruby would dump an uncaught exception.}
11
11
  s.email = ["devel@johan.kiviniemi.name"]
12
12
  s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
13
13
  s.files = ["COPYING", "History.txt", "Manifest.txt", "README.txt", "Rakefile", "lib/mischacks.rb", "mischacks.gemspec", "spec/mischacks_spec.rb", "spec/spec_helper.rb"]
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.require_paths = ["lib"]
18
18
  s.rubyforge_project = %q{mischacks}
19
19
  s.rubygems_version = %q{1.3.1}
20
- s.summary = %q{Safely pass untrusted parameters to sh scripts}
20
+ s.summary = %q{Miscellaneous methods that may or may not be useful}
21
21
 
22
22
  if s.respond_to? :specification_version then
23
23
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
@@ -17,11 +17,64 @@ require File.expand_path(File.dirname(__FILE__)+'/spec_helper.rb')
17
17
  require 'mischacks'
18
18
 
19
19
  require 'digest/sha1'
20
+ require 'fileutils'
21
+ require 'set'
22
+ require 'tmpdir'
20
23
 
21
24
  mh = MiscHacks
22
25
  ce = MiscHacks::ChildError
23
26
 
24
27
  describe mh do
28
+ describe 'sh' do
29
+ unsafe_str = %q{" 'foo' $(bar) `baz` "}
30
+ checksum = Digest::SHA1.hexdigest unsafe_str
31
+
32
+ it "should raise #{ce} on error" do
33
+ good = ['true', '', 'printf ""']
34
+ bad = ['false', 'exit 2', 'return 2']
35
+
36
+ bad.each do |c|
37
+ lambda do mh.sh c end.should raise_error ce
38
+ end
39
+
40
+ good.each do |c|
41
+ lambda do mh.sh c end.should_not raise_error
42
+ end
43
+ end
44
+
45
+ it 'should call sh with -e' do
46
+ lambda do mh.sh 'false; true' end.should raise_error ce
47
+ end
48
+
49
+ it 'should pass normal parameters safely' do
50
+ test = lambda do |str, sha|
51
+ mh.sh %q{
52
+ temp="$(mktemp -t mischacks.XXXXXXXXXX)"
53
+ trap 'rm -f "$temp"' 0 1 2 13 15
54
+ printf "%s" "$1" >"$temp"
55
+ printf "%s *%s\n" "$2" "$temp" | sha1sum -c >/dev/null 2>&1
56
+ }, str, sha
57
+ end
58
+
59
+ lambda do test.call unsafe_str, checksum end.should_not raise_error
60
+ lambda do test.call 'foo', checksum end.should raise_error ce
61
+ end
62
+
63
+ it 'should pass environment variables safely' do
64
+ test = lambda do |str, sha|
65
+ mh.sh %q{
66
+ temp="$(mktemp -t mischacks.XXXXXXXXXX)"
67
+ trap 'rm -f "$temp"' 0 1 2 13 15
68
+ printf "%s" "$string" >"$temp"
69
+ printf "%s *%s\n" "$checksum" "$temp" | sha1sum -c >/dev/null 2>&1
70
+ }, :string => str, :checksum => sha
71
+ end
72
+
73
+ lambda do test.call unsafe_str, checksum end.should_not raise_error
74
+ lambda do test.call 'foo', checksum end.should raise_error ce
75
+ end
76
+ end
77
+
25
78
  describe 'fork_and_check' do
26
79
  it 'should raise an error when child fails' do
27
80
  lambda do mh.fork_and_check do exit 1 end end.should raise_error ce
@@ -127,53 +180,90 @@ describe mh do
127
180
  end
128
181
  end
129
182
 
130
- describe 'sh' do
131
- unsafe_str = %q{" 'foo' $(bar) `baz` "}
132
- checksum = Digest::SHA1.hexdigest unsafe_str
183
+ describe 'overwrite' do
184
+ before :all do
185
+ @dir = Dir.mktmpdir
186
+ @file = "#{@dir}/foo"
187
+ @content0 = "bar\n"
188
+ @content1 = "baz\n"
189
+ @mode0 = 0714
190
+ @mode1 = 0755
191
+ end
133
192
 
134
- it "should raise #{ce} on error" do
135
- good = ['true', '', 'printf ""']
136
- bad = ['false', 'exit 2', 'return 2']
193
+ after :all do
194
+ FileUtils.rm_rf @dir
195
+ end
137
196
 
138
- bad.each do |c|
139
- lambda do mh.sh c end.should raise_error ce
197
+ it 'should create a fresh file' do
198
+ mh.overwrite @file do |io|
199
+ io << @content0
200
+ File.exists?(@file).should == false
140
201
  end
202
+ File.read(@file).should == @content0
203
+ end
141
204
 
142
- good.each do |c|
143
- lambda do mh.sh c end.should_not raise_error
205
+ it 'should overwrite a file' do
206
+ mh.overwrite @file do |io|
207
+ io << @content1
208
+ File.read(@file).should == @content0
144
209
  end
210
+ File.read(@file).should == @content1
145
211
  end
146
212
 
147
- it 'should call sh with -e' do
148
- lambda do mh.sh 'false; true' end.should raise_error ce
213
+ it 'should retain the mode' do
214
+ File.chmod @mode0, @file
215
+ mh.overwrite @file do end
216
+ (File.stat(@file).mode & 07777).should == @mode0
149
217
  end
150
218
 
151
- it 'should pass normal parameters safely' do
152
- test = lambda do |str, sha|
153
- mh.sh %q{
154
- temp="$(mktemp -t mischacks.XXXXXXXXXX)"
155
- trap 'rm -f "$temp"' 0 1 2 13 15
156
- printf "%s" "$1" >"$temp"
157
- printf "%s *%s\n" "$2" "$temp" | sha1sum -c >/dev/null 2>&1
158
- }, str, sha
159
- end
160
-
161
- lambda do test.call unsafe_str, checksum end.should_not raise_error
162
- lambda do test.call 'foo', checksum end.should raise_error ce
219
+ it 'should set the mode' do
220
+ mh.overwrite @file, @mode1 do end
221
+ (File.stat(@file).mode & 07777).should == @mode1
163
222
  end
223
+ end
164
224
 
165
- it 'should pass environment variables safely' do
166
- test = lambda do |str, sha|
167
- mh.sh %q{
168
- temp="$(mktemp -t mischacks.XXXXXXXXXX)"
169
- trap 'rm -f "$temp"' 0 1 2 13 15
170
- printf "%s" "$string" >"$temp"
171
- printf "%s *%s\n" "$checksum" "$temp" | sha1sum -c >/dev/null 2>&1
172
- }, :string => str, :checksum => sha
225
+ describe 'tempname_for' do
226
+ it 'should generate an unique temporary path' do
227
+ path = '/foo/bar/baz'
228
+ tempnames = (0...10).map { mh.tempname_for(path) }.to_set
229
+
230
+ # It is very, very unlikely there are duplicates.
231
+ tempnames.length.should > 8
232
+
233
+ tempnames.each do |n|
234
+ n.should =~ %r{\A/foo/bar/.baz.[a-z0-9]+\z}
173
235
  end
236
+ end
237
+ end
174
238
 
175
- lambda do test.call unsafe_str, checksum end.should_not raise_error
176
- lambda do test.call 'foo', checksum end.should raise_error ce
239
+ describe 'try_n_times' do
240
+ it 'should try 10 times' do
241
+ e = Class.new RuntimeError
242
+
243
+ i = 0
244
+ mh.try_n_times do
245
+ i += 1
246
+ 42
247
+ end.should == 42
248
+ i.should == 1
249
+
250
+ i = 0
251
+ mh.try_n_times do
252
+ i += 1
253
+ raise e if i < 10
254
+ 42
255
+ end.should == 42
256
+ i.should == 10
257
+
258
+ i = 0
259
+ lambda do
260
+ mh.try_n_times do
261
+ i += 1
262
+ raise e if i < 11
263
+ 42
264
+ end
265
+ end.should raise_error e
266
+ i.should == 10
177
267
  end
178
268
  end
179
269
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ion1-mischacks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johan Kiviniemi
@@ -22,7 +22,7 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 2.3.1
24
24
  version:
25
- description: Safely pass untrusted parameters to sh scripts
25
+ description: "Miscellaneous methods that may or may not be useful. sh:: Safely pass untrusted parameters to sh scripts. fork_and_check:: Run a block in a forked process and raise an exception if the process returns a non-zero value. do_and_exit, do_and_exit!:: Run a block. If the block does not run exit!, a successful exec or equivalent, run exit(1) or exit!(1) ourselves. Useful to make sure a forked block either runs a successful exec or dies. Any exceptions from the block are printed to standard error. overwrite:: Safely replace a file. Writes to a temporary file and then moves it over the old file. tempname_for:: Generates an unique temporary path based on a filename. The generated filename resides in the same directory as the original one. try_n_times:: Retries a block of code until it succeeds or a maximum number of attempts (default 10) is exceeded. Exception#to_formatted_string:: Returns a string that looks like how Ruby would dump an uncaught exception."
26
26
  email:
27
27
  - devel@johan.kiviniemi.name
28
28
  executables: []
@@ -69,6 +69,6 @@ rubyforge_project: mischacks
69
69
  rubygems_version: 1.2.0
70
70
  signing_key:
71
71
  specification_version: 2
72
- summary: Safely pass untrusted parameters to sh scripts
72
+ summary: Miscellaneous methods that may or may not be useful
73
73
  test_files: []
74
74