ec2_amitools 1.0.2
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.
- checksums.yaml +7 -0
- data/README.md +54 -0
- data/bin/console +14 -0
- data/bin/ec2-ami-tools-version +6 -0
- data/bin/ec2-bundle-image +6 -0
- data/bin/ec2-bundle-vol +6 -0
- data/bin/ec2-delete-bundle +6 -0
- data/bin/ec2-download-bundle +6 -0
- data/bin/ec2-migrate-bundle +6 -0
- data/bin/ec2-migrate-manifest +6 -0
- data/bin/ec2-unbundle +6 -0
- data/bin/ec2-upload-bundle +6 -0
- data/bin/setup +8 -0
- data/etc/ec2/amitools/cert-ec2-cn-north-1.pem +28 -0
- data/etc/ec2/amitools/cert-ec2-gov.pem +17 -0
- data/etc/ec2/amitools/cert-ec2.pem +23 -0
- data/etc/ec2/amitools/mappings.csv +9 -0
- data/lib/ec2/amitools/bundle.rb +251 -0
- data/lib/ec2/amitools/bundle_base.rb +58 -0
- data/lib/ec2/amitools/bundleimage.rb +94 -0
- data/lib/ec2/amitools/bundleimageparameters.rb +42 -0
- data/lib/ec2/amitools/bundlemachineparameters.rb +60 -0
- data/lib/ec2/amitools/bundleparameters.rb +120 -0
- data/lib/ec2/amitools/bundlevol.rb +240 -0
- data/lib/ec2/amitools/bundlevolparameters.rb +164 -0
- data/lib/ec2/amitools/crypto.rb +379 -0
- data/lib/ec2/amitools/decryptmanifest.rb +20 -0
- data/lib/ec2/amitools/defaults.rb +12 -0
- data/lib/ec2/amitools/deletebundle.rb +212 -0
- data/lib/ec2/amitools/deletebundleparameters.rb +78 -0
- data/lib/ec2/amitools/downloadbundle.rb +161 -0
- data/lib/ec2/amitools/downloadbundleparameters.rb +84 -0
- data/lib/ec2/amitools/exception.rb +86 -0
- data/lib/ec2/amitools/fileutil.rb +219 -0
- data/lib/ec2/amitools/format.rb +127 -0
- data/lib/ec2/amitools/instance-data.rb +97 -0
- data/lib/ec2/amitools/manifest_wrapper.rb +132 -0
- data/lib/ec2/amitools/manifestv20070829.rb +361 -0
- data/lib/ec2/amitools/manifestv20071010.rb +403 -0
- data/lib/ec2/amitools/manifestv3.rb +331 -0
- data/lib/ec2/amitools/mapids.rb +148 -0
- data/lib/ec2/amitools/migratebundle.rb +222 -0
- data/lib/ec2/amitools/migratebundleparameters.rb +173 -0
- data/lib/ec2/amitools/migratemanifest.rb +225 -0
- data/lib/ec2/amitools/migratemanifestparameters.rb +118 -0
- data/lib/ec2/amitools/minimalec2.rb +116 -0
- data/lib/ec2/amitools/parameter_exceptions.rb +34 -0
- data/lib/ec2/amitools/parameters_base.rb +168 -0
- data/lib/ec2/amitools/region.rb +93 -0
- data/lib/ec2/amitools/s3toolparameters.rb +183 -0
- data/lib/ec2/amitools/showversion.rb +12 -0
- data/lib/ec2/amitools/syschecks.rb +27 -0
- data/lib/ec2/amitools/tool_base.rb +224 -0
- data/lib/ec2/amitools/unbundle.rb +107 -0
- data/lib/ec2/amitools/unbundleparameters.rb +65 -0
- data/lib/ec2/amitools/uploadbundle.rb +361 -0
- data/lib/ec2/amitools/uploadbundleparameters.rb +108 -0
- data/lib/ec2/amitools/util.rb +532 -0
- data/lib/ec2/amitools/version.rb +33 -0
- data/lib/ec2/amitools/xmlbuilder.rb +237 -0
- data/lib/ec2/amitools/xmlutil.rb +55 -0
- data/lib/ec2/common/constants.rb +16 -0
- data/lib/ec2/common/curl.rb +110 -0
- data/lib/ec2/common/headers.rb +95 -0
- data/lib/ec2/common/headersv4.rb +173 -0
- data/lib/ec2/common/http.rb +333 -0
- data/lib/ec2/common/s3support.rb +231 -0
- data/lib/ec2/common/signature.rb +68 -0
- data/lib/ec2/oem/LICENSE.txt +58 -0
- data/lib/ec2/oem/open4.rb +399 -0
- data/lib/ec2/platform/base/architecture.rb +26 -0
- data/lib/ec2/platform/base/constants.rb +54 -0
- data/lib/ec2/platform/base/pipeline.rb +181 -0
- data/lib/ec2/platform/base.rb +57 -0
- data/lib/ec2/platform/current.rb +55 -0
- data/lib/ec2/platform/linux/architecture.rb +35 -0
- data/lib/ec2/platform/linux/constants.rb +23 -0
- data/lib/ec2/platform/linux/fstab.rb +99 -0
- data/lib/ec2/platform/linux/identity.rb +16 -0
- data/lib/ec2/platform/linux/image.rb +811 -0
- data/lib/ec2/platform/linux/mtab.rb +74 -0
- data/lib/ec2/platform/linux/pipeline.rb +40 -0
- data/lib/ec2/platform/linux/rsync.rb +114 -0
- data/lib/ec2/platform/linux/tar.rb +124 -0
- data/lib/ec2/platform/linux/uname.rb +50 -0
- data/lib/ec2/platform/linux.rb +83 -0
- data/lib/ec2/platform/solaris/architecture.rb +28 -0
- data/lib/ec2/platform/solaris/constants.rb +30 -0
- data/lib/ec2/platform/solaris/fstab.rb +43 -0
- data/lib/ec2/platform/solaris/identity.rb +16 -0
- data/lib/ec2/platform/solaris/image.rb +327 -0
- data/lib/ec2/platform/solaris/mtab.rb +29 -0
- data/lib/ec2/platform/solaris/pipeline.rb +40 -0
- data/lib/ec2/platform/solaris/rsync.rb +24 -0
- data/lib/ec2/platform/solaris/tar.rb +36 -0
- data/lib/ec2/platform/solaris/uname.rb +21 -0
- data/lib/ec2/platform/solaris.rb +38 -0
- data/lib/ec2/platform.rb +69 -0
- data/lib/ec2/version.rb +8 -0
- data/lib/ec2_amitools +1 -0
- data/lib/ec2_amitools.rb +7 -0
- 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
|