posix-spawn 0.3.10 → 0.3.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 88d9e42c2e091d60ef6bfaa4bee93671bfa7cdf1
4
- data.tar.gz: 0c34d4025ca8b876345fea00bb45a152b6909c10
2
+ SHA256:
3
+ metadata.gz: e80888255d57122ba2ad477c456582e0ebfdd05a61b175c60577ee8b38e579ad
4
+ data.tar.gz: 12d37e71310f0127b75b0f31da196c9c7554cac29a31d63754583f7737132190
5
5
  SHA512:
6
- metadata.gz: 6204dd64bc24a24a7ac155f8f7a0f018860b850b94efc68a68aa6b286a0de3a179deccb6f96ae7b1410045dface4de63aca14d235b1322bfac61399d825b65ff
7
- data.tar.gz: 988a0574b69a3accddfbb739c89b3da9949146cf7f316f41e2dc364c4ef5bb3cf7ae69858fe9c0318336e946cfc929a1c77178287e742db5a820044f4d680eee
6
+ metadata.gz: 3371e197b9b63ec2dce0b64d070bce8628ccb63112bcd14e8f7426891d1acf4d4ce4bc9558f44a832eaabd194b4b11a7feb2f12db2dd1d6db60968b8b9bc1a2a
7
+ data.tar.gz: 4d63126f9866a66ac6cc50d811be076939e1048f490aa40c55db1931398e016d225c3ae3da939a93646be551e6b207066f1dd4d2a1ee4f7dfe859a4404325242
@@ -0,0 +1,9 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 1.9
5
+ - 2.0
6
+ - 2.2
7
+ - 2.4
8
+ - 2.6
9
+ - ruby-head
data/COPYING CHANGED
@@ -20,9 +20,3 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20
20
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
21
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  SOFTWARE.
23
-
24
- A small portion of the environ dup'ing code in ext/posix-spawn.c
25
- was taken from glibc <http://www.gnu.org/s/libc/> and is maybe
26
- Copyright (c) 2011 by The Free Software Foundation or maybe
27
- by others mentioned in the glibc LICENSES file. glibc is
28
- distributed under the terms of the LGPL license.
data/Rakefile CHANGED
@@ -14,7 +14,13 @@ end
14
14
  # Ruby Extension
15
15
  # ==========================================================
16
16
 
17
- require 'rake/extensiontask'
17
+ begin
18
+ require 'rake/extensiontask'
19
+ rescue LoadError => boom
20
+ warn "ERROR: The rake-compiler gem dependency is missing."
21
+ warn "Please run `bundle install' and try again."
22
+ raise
23
+ end
18
24
  Rake::ExtensionTask.new('posix_spawn_ext', GEMSPEC) do |ext|
19
25
  ext.ext_dir = 'ext'
20
26
  end
@@ -45,7 +45,7 @@ static VALUE rb_mPOSIXSpawn;
45
45
  * an actual fd number:
46
46
  * - The symbols :in, :out, or :err for fds 0, 1, or 2.
47
47
  * - An IO object. (IO#fileno is returned)
48
- * - A Fixnum.
48
+ * - An Integer.
49
49
  *
50
50
  * Returns the fd number >= 0 if one could be established, or -1 if the object
51
51
  * does not map to an fd.
@@ -56,7 +56,11 @@ posixspawn_obj_to_fd(VALUE obj)
56
56
  int fd = -1;
57
57
  switch (TYPE(obj)) {
58
58
  case T_FIXNUM:
59
- /* Fixnum fd number */
59
+ case T_BIGNUM:
60
+ /* Integer fd number
61
+ * rb_fix2int takes care of raising if the provided object is a
62
+ * Bignum and is out of range of an int
63
+ */
60
64
  fd = FIX2INT(obj);
61
65
  break;
62
66
 
@@ -94,7 +98,7 @@ posixspawn_obj_to_fd(VALUE obj)
94
98
  /*
95
99
  * Hash iterator that sets up the posix_spawn_file_actions_t with addclose
96
100
  * operations. Only hash pairs whose value is :close are processed. Keys may
97
- * be the :in, :out, :err, an IO object, or a Fixnum fd number.
101
+ * be the :in, :out, :err, an IO object, or an Integer fd number.
98
102
  *
99
103
  * Returns ST_DELETE when an addclose operation was added; ST_CONTINUE when
100
104
  * no operation was performed.
@@ -269,27 +273,27 @@ each_env_check_i(VALUE key, VALUE val, VALUE arg)
269
273
  static int
270
274
  each_env_i(VALUE key, VALUE val, VALUE arg)
271
275
  {
272
- char *name = StringValuePtr(key);
273
- size_t len = strlen(name);
276
+ const char *name = StringValuePtr(key);
277
+ const size_t name_len = strlen(name);
274
278
 
275
- /*
276
- * Delete any existing values for this variable before inserting the new value.
277
- * This implementation was copied from glibc's unsetenv().
278
- */
279
- char **ep = (char **)arg;
280
- while (*ep != NULL)
281
- if (!strncmp (*ep, name, len) && (*ep)[len] == '=')
282
- {
283
- /* Found it. Remove this pointer by moving later ones back. */
284
- char **dp = ep;
285
-
286
- do
287
- dp[0] = dp[1];
288
- while (*dp++);
289
- /* Continue the loop in case NAME appears again. */
279
+ char **envp = (char **)arg;
280
+ size_t i, j;
281
+
282
+ for (i = 0; envp[i];) {
283
+ const char *ev = envp[i];
284
+
285
+ if (strlen(ev) > name_len && !memcmp(ev, name, name_len) && ev[name_len] == '=') {
286
+ /* This operates on a duplicated environment -- release the
287
+ * existing entry memory before shifting the subsequent entry
288
+ * pointers down. */
289
+ free(envp[i]);
290
+
291
+ for (j = i; envp[j]; ++j)
292
+ envp[j] = envp[j + 1];
293
+ continue;
290
294
  }
291
- else
292
- ++ep;
295
+ i++;
296
+ }
293
297
 
294
298
  /*
295
299
  * Insert the new value if we have one. We can assume there is space
@@ -301,15 +305,15 @@ each_env_i(VALUE key, VALUE val, VALUE arg)
301
305
  char *cval = StringValuePtr(val);
302
306
 
303
307
  size_t cval_len = strlen(cval);
304
- size_t ep_len = len + 1 + cval_len + 1; /* +2 for null terminator and '=' separator */
308
+ size_t ep_len = name_len + 1 + cval_len + 1; /* +2 for null terminator and '=' separator */
305
309
 
306
310
  /* find the last entry */
307
311
  while (*ep != NULL) ++ep;
308
312
  *ep = malloc(ep_len);
309
313
 
310
- strncpy(*ep, name, len);
311
- (*ep)[len] = '=';
312
- strncpy(*ep + len + 1, cval, cval_len);
314
+ strncpy(*ep, name, name_len);
315
+ (*ep)[name_len] = '=';
316
+ strncpy(*ep + name_len + 1, cval, cval_len);
313
317
  (*ep)[ep_len-1] = 0;
314
318
  }
315
319
 
@@ -373,6 +377,7 @@ rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options)
373
377
 
374
378
  if (RHASH_SIZE(env) > 0) {
375
379
  int size = 0;
380
+ char **new_env;
376
381
 
377
382
  char **curr = environ;
378
383
  if (curr) {
@@ -387,7 +392,7 @@ rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options)
387
392
  size = 0;
388
393
  }
389
394
 
390
- char **new_env = calloc(size+RHASH_SIZE(env)+1, sizeof(char*));
395
+ new_env = calloc(size+RHASH_SIZE(env)+1, sizeof(char*));
391
396
  for (i = 0; i < size; i++) {
392
397
  new_env[i] = strdup(environ[i]);
393
398
  }
@@ -85,7 +85,7 @@ module POSIX
85
85
  #
86
86
  # spawn(command, :chdir => "/var/tmp")
87
87
  #
88
- # The :in, :out, :err, a Fixnum, an IO object or an Array option specify
88
+ # The :in, :out, :err, an Integer, an IO object or an Array option specify
89
89
  # fd redirection. For example, stderr can be merged into stdout as follows:
90
90
  #
91
91
  # spawn(command, :err => :out)
@@ -460,11 +460,11 @@ module POSIX
460
460
 
461
461
  # Determine whether object is fd-like.
462
462
  #
463
- # Returns true if object is an instance of IO, Fixnum >= 0, or one of the
463
+ # Returns true if object is an instance of IO, Integer >= 0, or one of the
464
464
  # the symbolic names :in, :out, or :err.
465
465
  def fd?(object)
466
466
  case object
467
- when Fixnum
467
+ when Integer
468
468
  object >= 0
469
469
  when :in, :out, :err, STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr, IO
470
470
  true
@@ -486,7 +486,7 @@ module POSIX
486
486
  STDOUT
487
487
  when :err, 2
488
488
  STDERR
489
- when Fixnum
489
+ when Integer
490
490
  object >= 0 ? IO.for_fd(object) : nil
491
491
  when IO
492
492
  object
@@ -529,7 +529,7 @@ module POSIX
529
529
  #
530
530
  # Returns a [[cmdname, argv0], argv1, ...] array.
531
531
  def adjust_process_spawn_argv(args)
532
- if args.size == 1 && args[0] =~ /[ |>]/
532
+ if args.size == 1 && args[0].is_a?(String) && args[0] =~ /[ |>]/
533
533
  # single string with these characters means run it through the shell
534
534
  command_and_args = system_command_prefixes + [args[0]]
535
535
  [*command_and_args]
@@ -1,5 +1,3 @@
1
- require 'posix/spawn'
2
-
3
1
  module POSIX
4
2
  module Spawn
5
3
  # POSIX::Spawn::Child includes logic for executing child processes and
@@ -77,6 +75,9 @@ module POSIX
77
75
  # :max => total Maximum number of bytes of output to allow the
78
76
  # process to generate before aborting with a
79
77
  # MaximumOutputExceeded exception.
78
+ # :pgroup_kill => bool Boolean specifying whether to kill the process
79
+ # group (true) or individual process (false, default).
80
+ # Setting this option true implies :pgroup => true.
80
81
  #
81
82
  # Returns a new Child instance whose underlying process has already
82
83
  # executed to completion. The out, err, and status attributes are
@@ -87,6 +88,10 @@ module POSIX
87
88
  @input = @options.delete(:input)
88
89
  @timeout = @options.delete(:timeout)
89
90
  @max = @options.delete(:max)
91
+ if @options.delete(:pgroup_kill)
92
+ @pgroup_kill = true
93
+ @options[:pgroup] = true
94
+ end
90
95
  @options.delete(:chdir) if @options[:chdir].nil?
91
96
  exec! if !@options.delete(:noexec)
92
97
  end
@@ -128,6 +133,11 @@ module POSIX
128
133
  # Total command execution time (wall-clock time)
129
134
  attr_reader :runtime
130
135
 
136
+ # The pid of the spawned child process. This is unlikely to be a valid
137
+ # current pid since Child#exec! doesn't return until the process finishes
138
+ # and is reaped.
139
+ attr_reader :pid
140
+
131
141
  # Determine if the process did exit with a zero exit status.
132
142
  def success?
133
143
  @status && @status.success?
@@ -139,17 +149,22 @@ module POSIX
139
149
  def exec!
140
150
  # spawn the process and hook up the pipes
141
151
  pid, stdin, stdout, stderr = popen4(@env, *(@argv + [@options]))
152
+ @pid = pid
142
153
 
143
154
  # async read from all streams into buffers
144
155
  read_and_write(@input, stdin, stdout, stderr, @timeout, @max)
145
156
 
146
157
  # grab exit status
147
158
  @status = waitpid(pid)
148
- rescue Object => boom
159
+ rescue Object
149
160
  [stdin, stdout, stderr].each { |fd| fd.close rescue nil }
150
161
  if @status.nil?
151
- ::Process.kill('TERM', pid) rescue nil
152
- @status = waitpid(pid) rescue nil
162
+ if !@pgroup_kill
163
+ ::Process.kill('TERM', pid) rescue nil
164
+ else
165
+ ::Process.kill('-TERM', pid) rescue nil
166
+ end
167
+ @status = waitpid(pid) rescue nil
153
168
  end
154
169
  raise
155
170
  ensure
@@ -180,7 +195,6 @@ module POSIX
180
195
  def read_and_write(input, stdin, stdout, stderr, timeout=nil, max=nil)
181
196
  max = nil if max && max <= 0
182
197
  @out, @err = '', ''
183
- offset = 0
184
198
 
185
199
  # force all string and IO encodings to BINARY under 1.9 for now
186
200
  if @out.respond_to?(:force_encoding) and stdin.respond_to?(:set_encoding)
@@ -1,5 +1,5 @@
1
1
  module POSIX
2
2
  module Spawn
3
- VERSION = '0.3.10'
3
+ VERSION = '0.3.15'
4
4
  end
5
5
  end
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
 
12
12
  s.authors = ['Ryan Tomayko', 'Aman Gupta']
13
13
  s.email = ['r@tomayko.com', 'aman@tmm1.net']
14
- s.licenses = ['MIT', 'LGPL']
14
+ s.licenses = ['MIT']
15
15
 
16
16
  s.add_development_dependency 'rake-compiler', '0.7.6'
17
17
  s.add_development_dependency 'minitest', '>= 4'
@@ -1,10 +1,42 @@
1
1
  # coding: UTF-8
2
-
3
2
  require 'test_helper'
4
3
 
5
4
  class ChildTest < Minitest::Test
6
5
  include POSIX::Spawn
7
6
 
7
+ # Become a new process group.
8
+ def setup
9
+ Process.setpgrp
10
+ end
11
+
12
+ # Kill any orphaned processes in our process group before continuing but
13
+ # ignore the TERM signal we receive.
14
+ def teardown
15
+ trap("TERM") { trap("TERM", "DEFAULT") }
16
+ begin
17
+ Process.kill("-TERM", Process.pid)
18
+ Process.wait
19
+ rescue Errno::ECHILD
20
+ end
21
+ end
22
+
23
+ # verify the process is no longer running and has been reaped.
24
+ def assert_process_reaped(pid)
25
+ Process.kill(0, pid)
26
+ assert false, "Process #{pid} still running"
27
+ rescue Errno::ESRCH
28
+ end
29
+
30
+ # verifies that all processes in the given process group are no longer running
31
+ # and have been reaped. The current ruby test process is excluded.
32
+ # XXX It's weird to use the SUT here but the :pgroup option is useful. Could
33
+ # be a IO.popen under Ruby >= 1.9 since it also supports :pgroup.
34
+ def assert_process_group_reaped(pgid)
35
+ command = "ps axo pgid,pid,args | grep '^#{pgid} ' | grep -v '^#{pgid} #$$'"
36
+ procs = POSIX::Spawn::Child.new(command, :pgroup => true).out
37
+ assert procs.empty?, "Processes in group #{pgid} still running:\n#{procs}"
38
+ end
39
+
8
40
  def test_sanity
9
41
  assert_same POSIX::Spawn::Child, Child
10
42
  end
@@ -56,21 +88,45 @@ class ChildTest < Minitest::Test
56
88
  end
57
89
 
58
90
  def test_max
59
- assert_raises MaximumOutputExceeded do
60
- Child.new('yes', :max => 100_000)
61
- end
91
+ child = Child.build('yes', :max => 100_000)
92
+ assert_raises(MaximumOutputExceeded) { child.exec! }
93
+ assert_process_reaped child.pid
94
+ assert_process_group_reaped Process.pid
95
+ end
96
+
97
+ def test_max_pgroup_kill
98
+ child = Child.build('yes', :max => 100_000, :pgroup_kill => true)
99
+ assert_raises(MaximumOutputExceeded) { child.exec! }
100
+ assert_process_reaped child.pid
101
+ assert_process_group_reaped child.pid
62
102
  end
63
103
 
64
104
  def test_max_with_child_hierarchy
65
- assert_raises MaximumOutputExceeded do
66
- Child.new('/bin/sh', '-c', 'yes', :max => 100_000)
67
- end
105
+ child = Child.build('/bin/sh', '-c', 'true && yes', :max => 100_000)
106
+ assert_raises(MaximumOutputExceeded) { child.exec! }
107
+ assert_process_reaped child.pid
108
+ assert_process_group_reaped Process.pid
109
+ end
110
+
111
+ def test_max_with_child_hierarchy_pgroup_kill
112
+ child = Child.build('/bin/sh', '-c', 'true && yes', :max => 100_000, :pgroup_kill => true)
113
+ assert_raises(MaximumOutputExceeded) { child.exec! }
114
+ assert_process_reaped child.pid
115
+ assert_process_group_reaped child.pid
68
116
  end
69
117
 
70
118
  def test_max_with_stubborn_child
71
- assert_raises MaximumOutputExceeded do
72
- Child.new("trap '' TERM; yes", :max => 100_000)
73
- end
119
+ child = Child.build("trap '' TERM; yes", :max => 100_000)
120
+ assert_raises(MaximumOutputExceeded) { child.exec! }
121
+ assert_process_reaped child.pid
122
+ assert_process_group_reaped Process.pid
123
+ end
124
+
125
+ def test_max_with_stubborn_child_pgroup_kill
126
+ child = Child.build("trap '' TERM; yes", :max => 100_000, :pgroup_kill => true)
127
+ assert_raises(MaximumOutputExceeded) { child.exec! }
128
+ assert_process_reaped child.pid
129
+ assert_process_group_reaped child.pid
74
130
  end
75
131
 
76
132
  def test_max_with_partial_output
@@ -80,6 +136,8 @@ class ChildTest < Minitest::Test
80
136
  p.exec!
81
137
  end
82
138
  assert_output_exceeds_repeated_string("y\n", 100_000, p.out)
139
+ assert_process_reaped p.pid
140
+ assert_process_group_reaped Process.pid
83
141
  end
84
142
 
85
143
  def test_max_with_partial_output_long_lines
@@ -88,28 +146,47 @@ class ChildTest < Minitest::Test
88
146
  p.exec!
89
147
  end
90
148
  assert_output_exceeds_repeated_string("nice to meet you\n", 10_000, p.out)
149
+ assert_process_reaped p.pid
150
+ assert_process_group_reaped Process.pid
91
151
  end
92
152
 
93
153
  def test_timeout
94
154
  start = Time.now
95
- assert_raises TimeoutExceeded do
96
- Child.new('sleep', '1', :timeout => 0.05)
97
- end
155
+ child = Child.build('sleep', '1', :timeout => 0.05)
156
+ assert_raises(TimeoutExceeded) { child.exec! }
157
+ assert_process_reaped child.pid
158
+ assert_process_group_reaped Process.pid
159
+ assert (Time.now-start) <= 0.2
160
+ end
161
+
162
+ def test_timeout_pgroup_kill
163
+ start = Time.now
164
+ child = Child.build('sleep', '1', :timeout => 0.05, :pgroup_kill => true)
165
+ assert_raises(TimeoutExceeded) { child.exec! }
166
+ assert_process_reaped child.pid
167
+ assert_process_group_reaped child.pid
98
168
  assert (Time.now-start) <= 0.2
99
169
  end
100
170
 
101
171
  def test_timeout_with_child_hierarchy
102
- assert_raises TimeoutExceeded do
103
- Child.new('/bin/sh', '-c', 'sleep 1', :timeout => 0.05)
104
- end
172
+ child = Child.build('/bin/sh', '-c', 'true && sleep 1', :timeout => 0.05)
173
+ assert_raises(TimeoutExceeded) { child.exec! }
174
+ assert_process_reaped child.pid
175
+ end
176
+
177
+ def test_timeout_with_child_hierarchy_pgroup_kill
178
+ child = Child.build('/bin/sh', '-c', 'true && sleep 1', :timeout => 0.05, :pgroup_kill => true)
179
+ assert_raises(TimeoutExceeded) { child.exec! }
180
+ assert_process_reaped child.pid
181
+ assert_process_group_reaped child.pid
105
182
  end
106
183
 
107
184
  def test_timeout_with_partial_output
108
185
  start = Time.now
109
- p = Child.build('echo Hello; sleep 1', :timeout => 0.05)
110
- assert_raises TimeoutExceeded do
111
- p.exec!
112
- end
186
+ p = Child.build('echo Hello; sleep 1', :timeout => 0.05, :pgroup_kill => true)
187
+ assert_raises(TimeoutExceeded) { p.exec! }
188
+ assert_process_reaped p.pid
189
+ assert_process_group_reaped Process.pid
113
190
  assert (Time.now-start) <= 0.2
114
191
  assert_equal "Hello\n", p.out
115
192
  end
@@ -322,7 +322,7 @@ module SpawnImplementationTests
322
322
  end
323
323
  end
324
324
 
325
- assert_match /oops/, exception.message
325
+ assert_match(/oops/, exception.message)
326
326
  end
327
327
 
328
328
  ##
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posix-spawn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.10
4
+ version: 0.3.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Tomayko
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-02-17 00:00:00.000000000 Z
12
+ date: 2020-07-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake-compiler
@@ -52,6 +52,7 @@ extra_rdoc_files:
52
52
  - HACKING
53
53
  files:
54
54
  - ".gitignore"
55
+ - ".travis.yml"
55
56
  - COPYING
56
57
  - Gemfile
57
58
  - HACKING
@@ -75,7 +76,6 @@ files:
75
76
  homepage: https://github.com/rtomayko/posix-spawn
76
77
  licenses:
77
78
  - MIT
78
- - LGPL
79
79
  metadata: {}
80
80
  post_install_message:
81
81
  rdoc_options: []
@@ -92,8 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
94
  requirements: []
95
- rubyforge_project:
96
- rubygems_version: 2.2.2
95
+ rubygems_version: 3.0.3
97
96
  signing_key:
98
97
  specification_version: 4
99
98
  summary: posix_spawnp(2) for ruby