ec2_amitools 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +54 -0
  3. data/bin/console +14 -0
  4. data/bin/ec2-ami-tools-version +6 -0
  5. data/bin/ec2-bundle-image +6 -0
  6. data/bin/ec2-bundle-vol +6 -0
  7. data/bin/ec2-delete-bundle +6 -0
  8. data/bin/ec2-download-bundle +6 -0
  9. data/bin/ec2-migrate-bundle +6 -0
  10. data/bin/ec2-migrate-manifest +6 -0
  11. data/bin/ec2-unbundle +6 -0
  12. data/bin/ec2-upload-bundle +6 -0
  13. data/bin/setup +8 -0
  14. data/etc/ec2/amitools/cert-ec2-cn-north-1.pem +28 -0
  15. data/etc/ec2/amitools/cert-ec2-gov.pem +17 -0
  16. data/etc/ec2/amitools/cert-ec2.pem +23 -0
  17. data/etc/ec2/amitools/mappings.csv +9 -0
  18. data/lib/ec2/amitools/bundle.rb +251 -0
  19. data/lib/ec2/amitools/bundle_base.rb +58 -0
  20. data/lib/ec2/amitools/bundleimage.rb +94 -0
  21. data/lib/ec2/amitools/bundleimageparameters.rb +42 -0
  22. data/lib/ec2/amitools/bundlemachineparameters.rb +60 -0
  23. data/lib/ec2/amitools/bundleparameters.rb +120 -0
  24. data/lib/ec2/amitools/bundlevol.rb +240 -0
  25. data/lib/ec2/amitools/bundlevolparameters.rb +164 -0
  26. data/lib/ec2/amitools/crypto.rb +379 -0
  27. data/lib/ec2/amitools/decryptmanifest.rb +20 -0
  28. data/lib/ec2/amitools/defaults.rb +12 -0
  29. data/lib/ec2/amitools/deletebundle.rb +212 -0
  30. data/lib/ec2/amitools/deletebundleparameters.rb +78 -0
  31. data/lib/ec2/amitools/downloadbundle.rb +161 -0
  32. data/lib/ec2/amitools/downloadbundleparameters.rb +84 -0
  33. data/lib/ec2/amitools/exception.rb +86 -0
  34. data/lib/ec2/amitools/fileutil.rb +219 -0
  35. data/lib/ec2/amitools/format.rb +127 -0
  36. data/lib/ec2/amitools/instance-data.rb +97 -0
  37. data/lib/ec2/amitools/manifest_wrapper.rb +132 -0
  38. data/lib/ec2/amitools/manifestv20070829.rb +361 -0
  39. data/lib/ec2/amitools/manifestv20071010.rb +403 -0
  40. data/lib/ec2/amitools/manifestv3.rb +331 -0
  41. data/lib/ec2/amitools/mapids.rb +148 -0
  42. data/lib/ec2/amitools/migratebundle.rb +222 -0
  43. data/lib/ec2/amitools/migratebundleparameters.rb +173 -0
  44. data/lib/ec2/amitools/migratemanifest.rb +225 -0
  45. data/lib/ec2/amitools/migratemanifestparameters.rb +118 -0
  46. data/lib/ec2/amitools/minimalec2.rb +116 -0
  47. data/lib/ec2/amitools/parameter_exceptions.rb +34 -0
  48. data/lib/ec2/amitools/parameters_base.rb +168 -0
  49. data/lib/ec2/amitools/region.rb +93 -0
  50. data/lib/ec2/amitools/s3toolparameters.rb +183 -0
  51. data/lib/ec2/amitools/showversion.rb +12 -0
  52. data/lib/ec2/amitools/syschecks.rb +27 -0
  53. data/lib/ec2/amitools/tool_base.rb +224 -0
  54. data/lib/ec2/amitools/unbundle.rb +107 -0
  55. data/lib/ec2/amitools/unbundleparameters.rb +65 -0
  56. data/lib/ec2/amitools/uploadbundle.rb +361 -0
  57. data/lib/ec2/amitools/uploadbundleparameters.rb +108 -0
  58. data/lib/ec2/amitools/util.rb +532 -0
  59. data/lib/ec2/amitools/version.rb +33 -0
  60. data/lib/ec2/amitools/xmlbuilder.rb +237 -0
  61. data/lib/ec2/amitools/xmlutil.rb +55 -0
  62. data/lib/ec2/common/constants.rb +16 -0
  63. data/lib/ec2/common/curl.rb +110 -0
  64. data/lib/ec2/common/headers.rb +95 -0
  65. data/lib/ec2/common/headersv4.rb +173 -0
  66. data/lib/ec2/common/http.rb +333 -0
  67. data/lib/ec2/common/s3support.rb +231 -0
  68. data/lib/ec2/common/signature.rb +68 -0
  69. data/lib/ec2/oem/LICENSE.txt +58 -0
  70. data/lib/ec2/oem/open4.rb +399 -0
  71. data/lib/ec2/platform/base/architecture.rb +26 -0
  72. data/lib/ec2/platform/base/constants.rb +54 -0
  73. data/lib/ec2/platform/base/pipeline.rb +181 -0
  74. data/lib/ec2/platform/base.rb +57 -0
  75. data/lib/ec2/platform/current.rb +55 -0
  76. data/lib/ec2/platform/linux/architecture.rb +35 -0
  77. data/lib/ec2/platform/linux/constants.rb +23 -0
  78. data/lib/ec2/platform/linux/fstab.rb +99 -0
  79. data/lib/ec2/platform/linux/identity.rb +16 -0
  80. data/lib/ec2/platform/linux/image.rb +811 -0
  81. data/lib/ec2/platform/linux/mtab.rb +74 -0
  82. data/lib/ec2/platform/linux/pipeline.rb +40 -0
  83. data/lib/ec2/platform/linux/rsync.rb +114 -0
  84. data/lib/ec2/platform/linux/tar.rb +124 -0
  85. data/lib/ec2/platform/linux/uname.rb +50 -0
  86. data/lib/ec2/platform/linux.rb +83 -0
  87. data/lib/ec2/platform/solaris/architecture.rb +28 -0
  88. data/lib/ec2/platform/solaris/constants.rb +30 -0
  89. data/lib/ec2/platform/solaris/fstab.rb +43 -0
  90. data/lib/ec2/platform/solaris/identity.rb +16 -0
  91. data/lib/ec2/platform/solaris/image.rb +327 -0
  92. data/lib/ec2/platform/solaris/mtab.rb +29 -0
  93. data/lib/ec2/platform/solaris/pipeline.rb +40 -0
  94. data/lib/ec2/platform/solaris/rsync.rb +24 -0
  95. data/lib/ec2/platform/solaris/tar.rb +36 -0
  96. data/lib/ec2/platform/solaris/uname.rb +21 -0
  97. data/lib/ec2/platform/solaris.rb +38 -0
  98. data/lib/ec2/platform.rb +69 -0
  99. data/lib/ec2/version.rb +8 -0
  100. data/lib/ec2_amitools +1 -0
  101. data/lib/ec2_amitools.rb +7 -0
  102. metadata +184 -0
@@ -0,0 +1,231 @@
1
+ # Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
2
+ # Reserved. Licensed under the Amazon Software License (the
3
+ # "License"). You may not use this file except in compliance with the
4
+ # License. A copy of the License is located at
5
+ # http://aws.amazon.com/asl or in the "license" file accompanying this
6
+ # file. This file is distributed on an "AS IS" BASIS, WITHOUT
7
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
8
+ # the License for the specific language governing permissions and
9
+ # limitations under the License.
10
+
11
+ # ---------------------------------------------------------------------------
12
+ # Module that provides higher-level S3 functionality
13
+ # ---------------------------------------------------------------------------
14
+ require 'uri'
15
+ require 'cgi'
16
+ require 'ec2/common/constants'
17
+ require 'ec2/common/http'
18
+
19
+ module EC2
20
+ module Common
21
+ class S3Support
22
+
23
+ attr_accessor :s3_url,
24
+ :user,
25
+ :pass
26
+
27
+ DEFAULT_SIGV = EC2::Common::SIGV4
28
+ DEFAULT_REGION = 'us-east-1'
29
+
30
+ # Return true if the bucket name is S3 safe.
31
+ #
32
+ # Per the S3 dev guide @ http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?BucketRestrictions.html
33
+ # - Be between 3 and 255 characters long
34
+ # - Start with a number or letter
35
+ # - Contain lowercase letters, numbers, periods (.), underscores (_), and dashes (-)
36
+ # - Not be in an IP address style (e.g., "192.168.5.4")
37
+ #
38
+ # * Notes:
39
+ # - !!(....) silliness to force a boolean to be returned
40
+ def self.bucket_name_s3_safe?(bucket_name)
41
+ # Can most probably fold this all into 1 grand regexp but
42
+ # for now opt for semi-clarity.
43
+ !!((3..255).include?(bucket_name.length) and
44
+ (/^[a-z0-9][a-z0-9\._-]+$/ =~ bucket_name) and
45
+ (/^(\d{1,3}\.){3}\d{1,3}$/ !~ bucket_name))
46
+ end
47
+
48
+
49
+ # Return true if the bucket name is S3 (v2) safe.
50
+ #
51
+ # Per the S3 dev guide @ http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?BucketRestrictions.html
52
+ # - Bucket names should not contain underscores (_)
53
+ # - Bucket names should be between 3 and 63 characters long
54
+ # - Bucket names should not end with a dash
55
+ # - Bucket names cannot contain dashes next to periods (e.g., "my-.bucket.com" and "my.-bucket" are invalid)
56
+ # - Bucket names must only contain lower case letters
57
+ #
58
+ # and naturally also fulfills bucket_name_s3_safe?() requirements
59
+ #
60
+ # * Notes:
61
+ # - !!(....) silliness to force a boolean to be returned
62
+ def self.bucket_name_s3_v2_safe?(bucket_name)
63
+ # Can most probably fold this all into 1 grand regexp but
64
+ # for now opt for semi-clarity.
65
+ !!(self.bucket_name_s3_safe?(bucket_name) and
66
+ bucket_name.length <= 63 and
67
+ not bucket_name.include?('_') and
68
+ bucket_name[-1] != ?- and
69
+ /(-\.)|(\.-)/ !~ bucket_name)
70
+ end
71
+
72
+ def initialize(s3_url, user, pass, format=nil, debug=nil, sigv=DEFAULT_SIGV, region=DEFAULT_REGION)
73
+ @user = user
74
+ @pass = pass
75
+ @s3_url = fix_s3_url(s3_url)
76
+ @format = format
77
+ @debug = debug
78
+ @sigv = sigv
79
+ @region = region
80
+ end
81
+
82
+ def fix_s3_url(s3_url)
83
+ if s3_url !~ %r{://}
84
+ s3_url = "https://#{s3_url}"
85
+ end
86
+ if s3_url[-1..-1] != "/"
87
+ s3_url << "/"
88
+ end
89
+ s3_url
90
+ end
91
+
92
+ def get_bucket_url(bucket)
93
+ # The returned variable should not end with '/', makes constructing the path for sigv4 easier
94
+ if not @format
95
+ if bucket.include? "."
96
+ @format = :path
97
+ else
98
+ @format = :subdomain
99
+ end
100
+ end
101
+
102
+ case @format
103
+ when :subdomain
104
+ uri = URI.parse(@s3_url)
105
+ return ["#{uri.scheme}://#{bucket}.#{uri.host}", bucket]
106
+ when :path
107
+ return ["#{@s3_url}#{bucket}", bucket]
108
+ end
109
+ end
110
+
111
+ def get_acl(bucket, key, options={})
112
+ begin
113
+ url, bkt = get_bucket_url(bucket)
114
+ url << "/#{CGI::escape(key)}?acl"
115
+ http_fallback(url, @sigv) do |url, signature|
116
+ EC2::Common::HTTP::get(url, bkt, nil, options, @user, @pass, nil, nil, @debug, signature, @region)
117
+ end
118
+ end
119
+ end
120
+
121
+ def check_bucket_exists(bucket, options={})
122
+ url, bkt = get_bucket_url(bucket)
123
+ http_fallback(url, @sigv) do |url, signature|
124
+ EC2::Common::HTTP::head(url, bkt, options, @user, @pass, @debug, signature, @region)
125
+ end
126
+ end
127
+
128
+ def get_bucket_location(bucket, options={})
129
+ url, bkt = get_bucket_url(bucket)
130
+ url << "?location"
131
+ http_fallback(url, @sigv) do |url, signature|
132
+ EC2::Common::HTTP::get(url, bkt, nil, options, @user, @pass, nil, nil, @debug, signature, @region)
133
+ end
134
+ end
135
+
136
+ def create_bucket(bucket, location, options={})
137
+ url, bkt = get_bucket_url(bucket)
138
+ binary_data = ""
139
+ if (location != nil)
140
+ binary_data = "<CreateBucketConstraint><LocationConstraint>#{location}</LocationConstraint></CreateBucketConstraint>"
141
+ end
142
+ http_fallback(url, @sigv) do |url, signature|
143
+ EC2::Common::HTTP::putdir(url, bkt, binary_data, options, @user, @pass, @debug, signature, @region)
144
+ end
145
+ end
146
+
147
+ def list_bucket(bucket, prefix=nil, max_keys=nil, path=nil, options={})
148
+ url, bkt = get_bucket_url(bucket)
149
+ params = []
150
+ params << "prefix=#{CGI::escape(prefix)}" if prefix
151
+ params << "max-keys=#{CGI::escape(max_keys)}" if max_keys
152
+ url << "?" + params.join("&") unless params.empty?
153
+ http_fallback(url, @sigv) do |url, signature|
154
+ EC2::Common::HTTP::get(url, bkt, path, options, @user, @pass, nil, nil, @debug, signature, @region)
155
+ end
156
+ end
157
+
158
+ def get(bucket, key, path=nil, options={})
159
+ url, bkt = get_bucket_url(bucket)
160
+ url << "/#{CGI::escape(key)}"
161
+ http_fallback(url, @sigv) do |url, signature|
162
+ EC2::Common::HTTP::get(url, bkt, path, options, @user, @pass, nil, nil, @debug, signature, @region)
163
+ end
164
+ end
165
+
166
+ def put(bucket, key, file, options={})
167
+ url, bkt = get_bucket_url(bucket)
168
+ url << "/#{CGI::escape(key)}"
169
+ http_fallback(url, @sigv) do |url, signature|
170
+ EC2::Common::HTTP::put(url, bkt, file, options, @user, @pass, @debug, signature, @region)
171
+ end
172
+ end
173
+
174
+ def copy(bucket, key, source, options={})
175
+ url, bkt = get_bucket_url(bucket)
176
+ url << "/#{CGI::escape(key)}"
177
+ options['x-amz-copy-source'] = CGI::escape(source)
178
+ http_fallback(url, @sigv) do |url, signature|
179
+ EC2::Common::HTTP::put(url, bkt, nil, options, @user, @pass, @debug, signature, @region)
180
+ end
181
+ end
182
+
183
+ def delete(bucket, key="", options={})
184
+ url, bkt = get_bucket_url(bucket)
185
+ url << "/#{CGI::escape(key)}"
186
+ http_fallback(url, @sigv) do |url, signature|
187
+ EC2::Common::HTTP::delete(url, bkt, options, @user, @pass, @debug, signature, @region)
188
+ end
189
+ end
190
+
191
+ private
192
+ # This method will cycle through all possible signatures versions, starting
193
+ # with the given the one as first choice
194
+ def http_fallback url, sigv, &block
195
+ all_sigv = [EC2::Common::SIGV2, EC2::Common::SIGV4]
196
+ all_sigv.delete(sigv)
197
+ ordered_sigv = [sigv].concat all_sigv
198
+
199
+ resp = nil
200
+ access_denied = false
201
+ ordered_sigv.each do |o_sigv|
202
+ redirected = true
203
+ while redirected
204
+ redirected = false
205
+ begin
206
+ resp = block.call(url, o_sigv)
207
+ rescue EC2::Common::HTTP::Error::Redirect => e
208
+ redirected = true
209
+ url = e.endpoint
210
+ STDERR.puts "Redirecting to #{url}, resigning request"
211
+ rescue EC2::Common::HTTP::Error => e
212
+ if e.code == 403
213
+ access_denied = true
214
+ else
215
+ raise e
216
+ end
217
+ end
218
+ end
219
+ # If the response was 403, try other sigv
220
+ if access_denied == true
221
+ access_denied = false
222
+ STDERR.puts "Signature version #{o_sigv} authentication failed, trying different signature version"
223
+ else
224
+ break
225
+ end
226
+ end
227
+ resp
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,68 @@
1
+ # Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
2
+ # Reserved. Licensed under the Amazon Software License (the
3
+ # "License"). You may not use this file except in compliance with the
4
+ # License. A copy of the License is located at
5
+ # http://aws.amazon.com/asl or in the "license" file accompanying this
6
+ # file. This file is distributed on an "AS IS" BASIS, WITHOUT
7
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
8
+ # the License for the specific language governing permissions and
9
+ # limitations under the License.
10
+
11
+ require 'uri'
12
+
13
+ require 'ec2/common/headers'
14
+ require 'ec2/common/headersv4'
15
+
16
+ module EC2
17
+ module Common
18
+ module Signature
19
+ def self.curl_args_sigv2(url, bucket, http_method, options, user, pass)
20
+ headers = EC2::Common::Headers.new(http_method)
21
+ options.each do |name, value| headers.add(name, value) end
22
+ headers.sign(user, pass, url, bucket) if user and pass
23
+ headers.get
24
+ end
25
+
26
+ def self.curl_args_sigv4(url, region, bucket, http_method, optional_headers, user, pass, data=nil)
27
+ aws_secret_access_key = pass
28
+ aws_access_key_id = user
29
+ if (user.is_a?(Hash))
30
+ aws_access_key_id = user['aws_access_key_id']
31
+ aws_secret_access_key = user['aws_secret_access_key']
32
+ delegation_token = user['aws_delegation_token']
33
+ end
34
+ host, path, query = parseURL(url, bucket)
35
+ hexdigest = if data
36
+ HeadersV4::hexdigest data
37
+ else
38
+ HeadersV4::hexdigest ""
39
+ end
40
+ optional_headers ||= {}
41
+ unless delegation_token.nil?
42
+ optional_headers[EC2::Common::Headers::X_AMZ_SECURITY_TOKEN] = delegation_token
43
+ end
44
+ headers_obj = HeadersV4.new({:host => host,
45
+ :hexdigest_body => hexdigest,
46
+ :region => region,
47
+ :service => "s3",
48
+ :http_method => http_method,
49
+ :path => path,
50
+ :querystring => query,
51
+ :access_key_id => aws_access_key_id,
52
+ :secret_access_key => aws_secret_access_key},
53
+ optional_headers)
54
+ headers = headers_obj.add_authorization!
55
+ headers
56
+ end
57
+
58
+ def self.parseURL(url, bucket)
59
+ uri = URI.parse(url)
60
+ host = uri.host
61
+ path = uri.path
62
+ path = "/" if path == ""
63
+
64
+ [host, path, "#{uri.query}"]
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,58 @@
1
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
2
+ You can redistribute it and/or modify it under either the terms of the GPL
3
+ (see COPYING.txt file), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) rename any non-standard executables so the names do not conflict
21
+ with standard executables, which must also be provided.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or executable
26
+ form, provided that you do at least ONE of the following:
27
+
28
+ a) distribute the executables and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard executables non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under this terms.
43
+
44
+ They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
45
+ files under the ./missing directory. See each file for the copying
46
+ condition.
47
+
48
+ 5. The scripts and library files supplied as input to or produced as
49
+ output from the software do not automatically fall under the
50
+ copyright of the software, but belong to whomever generated them,
51
+ and may be sold commercially, and may be aggregated with this
52
+ software.
53
+
54
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
55
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
56
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
57
+ PURPOSE.
58
+
@@ -0,0 +1,399 @@
1
+ # ------------------------------------------------------------------------
2
+ # Name: Open4
3
+ # Version: 4-0.9.6 <http://codeforpeople.com/lib/ruby/open4/open4-0.9.6.tgz>
4
+ # Author: Ara T. Howard <ara[dot]t[dot]howard[at]noaa[dot]gov>
5
+ # License: Ruby License <http://www.ruby-lang.org/en/LICENSE.txt>
6
+ # ------------------------------------------------------------------------
7
+ # vim: ts=2:sw=2:sts=2:et:fdm=marker
8
+ require 'fcntl'
9
+ require 'timeout'
10
+ require 'thread'
11
+
12
+ module Open4
13
+ #--{{{
14
+ VERSION = '0.9.6'
15
+ def self.version() VERSION end
16
+
17
+ class Error < ::StandardError; end
18
+
19
+ def popen4(*cmd, &b)
20
+ #--{{{
21
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
22
+
23
+ verbose = $VERBOSE
24
+ begin
25
+ $VERBOSE = nil
26
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
27
+
28
+ cid = fork {
29
+ pw.last.close
30
+ STDIN.reopen pw.first
31
+ pw.first.close
32
+
33
+ pr.first.close
34
+ STDOUT.reopen pr.last
35
+ pr.last.close
36
+
37
+ pe.first.close
38
+ STDERR.reopen pe.last
39
+ pe.last.close
40
+
41
+ STDOUT.sync = STDERR.sync = true
42
+
43
+ begin
44
+ exec(*cmd)
45
+ raise 'forty-two'
46
+ rescue Exception => e
47
+ Marshal.dump(e, ps.last)
48
+ ps.last.flush
49
+ end
50
+ ps.last.close unless (ps.last.closed?)
51
+ exit!
52
+ }
53
+ ensure
54
+ $VERBOSE = verbose
55
+ end
56
+
57
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
58
+
59
+ begin
60
+ e = Marshal.load ps.first
61
+ raise(Exception === e ? e : "unknown failure!")
62
+ rescue EOFError # If we get an EOF error, then the exec was successful
63
+ 42
64
+ ensure
65
+ ps.first.close
66
+ end
67
+
68
+ pw.last.sync = true
69
+
70
+ pi = [pw.last, pr.first, pe.first]
71
+
72
+ if b
73
+ begin
74
+ b[cid, *pi]
75
+ Process.waitpid2(cid).last
76
+ ensure
77
+ pi.each{|fd| fd.close unless fd.closed?}
78
+ end
79
+ else
80
+ [cid, pw.last, pr.first, pe.first]
81
+ end
82
+ #--}}}
83
+ end
84
+ alias open4 popen4
85
+ module_function :popen4
86
+ module_function :open4
87
+
88
+ class SpawnError < Error
89
+ #--{{{
90
+ attr 'cmd'
91
+ attr 'status'
92
+ attr 'signals'
93
+ def exitstatus
94
+ @status.exitstatus
95
+ end
96
+ def initialize cmd, status
97
+ @cmd, @status = cmd, status
98
+ @signals = {}
99
+ if status.signaled?
100
+ @signals['termsig'] = status.termsig
101
+ @signals['stopsig'] = status.stopsig
102
+ end
103
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
104
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
105
+ end
106
+ #--}}}
107
+ end
108
+
109
+ class ThreadEnsemble
110
+ #--{{{
111
+ attr 'threads'
112
+
113
+ def initialize cid
114
+ @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
115
+ @killed = false
116
+ end
117
+
118
+ def add_thread *a, &b
119
+ @running ? raise : (@argv << [a, b])
120
+ end
121
+
122
+ #
123
+ # take down process more nicely
124
+ #
125
+ def killall
126
+ c = Thread.critical
127
+ return nil if @killed
128
+ Thread.critical = true
129
+ (@threads - [Thread.current]).each{|t| t.kill rescue nil}
130
+ @killed = true
131
+ ensure
132
+ Thread.critical = c
133
+ end
134
+
135
+ def run
136
+ @running = true
137
+
138
+ begin
139
+ @argv.each do |a, b|
140
+ @threads << Thread.new(*a) do |*a|
141
+ begin
142
+ b[*a]
143
+ ensure
144
+ killall rescue nil if $!
145
+ @done.push Thread.current
146
+ end
147
+ end
148
+ end
149
+ rescue
150
+ killall
151
+ raise
152
+ ensure
153
+ all_done
154
+ end
155
+
156
+ @threads.map{|t| t.value}
157
+ end
158
+
159
+ def all_done
160
+ @threads.size.times{ @done.pop }
161
+ end
162
+ #--}}}
163
+ end
164
+
165
+ def to timeout = nil
166
+ #--{{{
167
+ Timeout.timeout(timeout){ yield }
168
+ #--}}}
169
+ end
170
+ module_function :to
171
+
172
+ def new_thread *a, &b
173
+ #--{{{
174
+ cur = Thread.current
175
+ Thread.new(*a) do |*a|
176
+ begin
177
+ b[*a]
178
+ rescue Exception => e
179
+ cur.raise e
180
+ end
181
+ end
182
+ #--}}}
183
+ end
184
+ module_function :new_thread
185
+
186
+ def getopts opts = {}
187
+ #--{{{
188
+ lambda do |*args|
189
+ keys, default, ignored = args
190
+ catch('opt') do
191
+ [keys].flatten.each do |key|
192
+ [key, key.to_s, key.to_s.intern].each do |key|
193
+ throw 'opt', opts[key] if opts.has_key?(key)
194
+ end
195
+ end
196
+ default
197
+ end
198
+ end
199
+ #--}}}
200
+ end
201
+ module_function :getopts
202
+
203
+ def relay src, dst = nil, t = nil
204
+ #--{{{
205
+ unless src.nil?
206
+ if src.respond_to? :gets
207
+ while buf = to(t){ src.gets }
208
+ dst << buf if dst
209
+ end
210
+
211
+ elsif src.respond_to? :each
212
+ q = Queue.new
213
+ th = nil
214
+
215
+ timer_set = lambda do |t|
216
+ th = new_thread{ to(t){ q.pop } }
217
+ end
218
+
219
+ timer_cancel = lambda do |t|
220
+ th.kill if th rescue nil
221
+ end
222
+
223
+ timer_set[t]
224
+ begin
225
+ src.each do |buf|
226
+ timer_cancel[t]
227
+ dst << buf if dst
228
+ timer_set[t]
229
+ end
230
+ ensure
231
+ timer_cancel[t]
232
+ end
233
+
234
+ elsif src.respond_to? :read
235
+ buf = to(t){ src.read }
236
+ dst << buf if dst
237
+
238
+ else
239
+ buf = to(t){ src.to_s }
240
+ dst << buf if dst
241
+ end
242
+ end
243
+ #--}}}
244
+ end
245
+ module_function :relay
246
+
247
+ def spawn arg, *argv
248
+ #--{{{
249
+ argv.unshift(arg)
250
+ opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
251
+ argv.flatten!
252
+ cmd = argv.join(' ')
253
+
254
+
255
+ getopt = getopts opts
256
+
257
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
258
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
259
+ exitstatus = getopt[ %w( exitstatus exit_status status ) ]
260
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
261
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
262
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
263
+ pid = getopt[ 'pid' ]
264
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
265
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
266
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
267
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
268
+ status = getopt[ %w( status ) ]
269
+ cwd = getopt[ %w( cwd dir ) ]
270
+
271
+ exitstatus =
272
+ case exitstatus
273
+ when TrueClass, FalseClass
274
+ ignore_exit_failure = true if exitstatus
275
+ [0]
276
+ else
277
+ [*(exitstatus || 0)].map{|i| Integer i}
278
+ end
279
+
280
+ stdin ||= '' if stdin_timeout
281
+ stdout ||= '' if stdout_timeout
282
+ stderr ||= '' if stderr_timeout
283
+
284
+ started = false
285
+
286
+ status =
287
+ begin
288
+ chdir(cwd) do
289
+ Timeout::timeout(timeout) do
290
+ popen4(*argv) do |c, i, o, e|
291
+ started = true
292
+
293
+ %w( replace pid= << push update ).each do |msg|
294
+ break(pid.send(msg, c)) if pid.respond_to? msg
295
+ end
296
+
297
+ te = ThreadEnsemble.new c
298
+
299
+ te.add_thread(i, stdin) do |i, stdin|
300
+ relay stdin, i, stdin_timeout
301
+ i.close rescue nil
302
+ end
303
+
304
+ te.add_thread(o, stdout) do |o, stdout|
305
+ relay o, stdout, stdout_timeout
306
+ end
307
+
308
+ te.add_thread(e, stderr) do |o, stderr|
309
+ relay e, stderr, stderr_timeout
310
+ end
311
+
312
+ te.run
313
+ end
314
+ end
315
+ end
316
+ rescue
317
+ raise unless(not started and ignore_exec_failure)
318
+ end
319
+
320
+ raise SpawnError.new(cmd, status) unless
321
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
322
+
323
+ status
324
+ #--}}}
325
+ end
326
+ module_function :spawn
327
+
328
+ def chdir cwd, &block
329
+ return(block.call Dir.pwd) unless cwd
330
+ Dir.chdir cwd, &block
331
+ end
332
+ module_function :chdir
333
+
334
+ def background arg, *argv
335
+ #--{{{
336
+ require 'thread'
337
+ q = Queue.new
338
+ opts = { 'pid' => q, :pid => q }
339
+ case argv.last
340
+ when Hash
341
+ argv.last.update opts
342
+ else
343
+ argv.push opts
344
+ end
345
+ thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
346
+ sc = class << thread; self; end
347
+ sc.module_eval {
348
+ define_method(:pid){ @pid ||= q.pop }
349
+ define_method(:spawn_status){ @spawn_status ||= value }
350
+ define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus }
351
+ }
352
+ thread
353
+ #--}}}
354
+ end
355
+ alias bg background
356
+ module_function :background
357
+ module_function :bg
358
+
359
+ def maim pid, opts = {}
360
+ #--{{{
361
+ getopt = getopts opts
362
+ sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
363
+ suspend = getopt[ 'suspend', 4 ]
364
+ pid = Integer pid
365
+ existed = false
366
+ sigs.each do |sig|
367
+ begin
368
+ Process.kill sig, pid
369
+ existed = true
370
+ rescue Errno::ESRCH
371
+ return(existed ? nil : true)
372
+ end
373
+ return true unless alive? pid
374
+ sleep suspend
375
+ return true unless alive? pid
376
+ end
377
+ return(not alive?(pid))
378
+ #--}}}
379
+ end
380
+ module_function :maim
381
+
382
+ def alive pid
383
+ #--{{{
384
+ pid = Integer pid
385
+ begin
386
+ Process.kill 0, pid
387
+ true
388
+ rescue Errno::ESRCH
389
+ false
390
+ end
391
+ #--}}}
392
+ end
393
+ alias alive? alive
394
+ module_function :alive
395
+ module_function :'alive?'
396
+ #--}}}
397
+ end
398
+
399
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end