aggkit 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +20 -0
  3. data/.gitignore +109 -0
  4. data/.gitlab-ci.yml +66 -0
  5. data/.rspec +4 -0
  6. data/.rubocop.yml +98 -0
  7. data/.travis.yml +30 -0
  8. data/Gemfile +12 -0
  9. data/Gemfile.lock +55 -0
  10. data/README.md +96 -0
  11. data/aggkit.gemspec +38 -0
  12. data/bin/agg +167 -0
  13. data/bin/aggconsul +222 -0
  14. data/bin/agglock +71 -0
  15. data/bin/aggmerge +118 -0
  16. data/bin/aggwait +262 -0
  17. data/bin/consul.rb +222 -0
  18. data/bin/locker.rb +71 -0
  19. data/bin/merger.rb +118 -0
  20. data/bin/terminator.rb +71 -0
  21. data/bin/waiter.rb +262 -0
  22. data/docker/Dockerfile +112 -0
  23. data/docker/docker-compose.yml +12 -0
  24. data/docker/down.sh +4 -0
  25. data/docker/run_tests.sh +23 -0
  26. data/lib/aggkit/childprocess/abstract_io.rb +38 -0
  27. data/lib/aggkit/childprocess/abstract_process.rb +194 -0
  28. data/lib/aggkit/childprocess/errors.rb +28 -0
  29. data/lib/aggkit/childprocess/jruby/io.rb +17 -0
  30. data/lib/aggkit/childprocess/jruby/process.rb +161 -0
  31. data/lib/aggkit/childprocess/jruby/pump.rb +55 -0
  32. data/lib/aggkit/childprocess/jruby.rb +58 -0
  33. data/lib/aggkit/childprocess/tools/generator.rb +148 -0
  34. data/lib/aggkit/childprocess/unix/fork_exec_process.rb +72 -0
  35. data/lib/aggkit/childprocess/unix/io.rb +22 -0
  36. data/lib/aggkit/childprocess/unix/lib.rb +188 -0
  37. data/lib/aggkit/childprocess/unix/platform/i386-linux.rb +14 -0
  38. data/lib/aggkit/childprocess/unix/platform/i386-solaris.rb +13 -0
  39. data/lib/aggkit/childprocess/unix/platform/x86_64-linux.rb +14 -0
  40. data/lib/aggkit/childprocess/unix/platform/x86_64-macosx.rb +13 -0
  41. data/lib/aggkit/childprocess/unix/posix_spawn_process.rb +135 -0
  42. data/lib/aggkit/childprocess/unix/process.rb +91 -0
  43. data/lib/aggkit/childprocess/unix.rb +11 -0
  44. data/lib/aggkit/childprocess/version.rb +5 -0
  45. data/lib/aggkit/childprocess/windows/handle.rb +93 -0
  46. data/lib/aggkit/childprocess/windows/io.rb +25 -0
  47. data/lib/aggkit/childprocess/windows/lib.rb +418 -0
  48. data/lib/aggkit/childprocess/windows/process.rb +132 -0
  49. data/lib/aggkit/childprocess/windows/process_builder.rb +177 -0
  50. data/lib/aggkit/childprocess/windows/structs.rb +151 -0
  51. data/lib/aggkit/childprocess/windows.rb +35 -0
  52. data/lib/aggkit/childprocess.rb +213 -0
  53. data/lib/aggkit/env.rb +219 -0
  54. data/lib/aggkit/runner.rb +80 -0
  55. data/lib/aggkit/version.rb +5 -0
  56. data/lib/aggkit/watcher.rb +239 -0
  57. data/lib/aggkit.rb +15 -0
  58. metadata +196 -0
@@ -0,0 +1,161 @@
1
+ require "java"
2
+
3
+ module Aggkit
4
+ module ChildProcess
5
+ module JRuby
6
+ class Process < AbstractProcess
7
+ def initialize(args)
8
+ super(args)
9
+
10
+ @pumps = []
11
+ end
12
+
13
+ def io
14
+ @io ||= JRuby::IO.new
15
+ end
16
+
17
+ def exited?
18
+ return true if @exit_code
19
+
20
+ assert_started
21
+ @exit_code = @process.exitValue
22
+ stop_pumps
23
+
24
+ true
25
+ rescue java.lang.IllegalThreadStateException => ex
26
+ log(ex.class => ex.message)
27
+ false
28
+ ensure
29
+ log(:exit_code => @exit_code)
30
+ end
31
+
32
+ def stop(timeout = nil)
33
+ assert_started
34
+
35
+ @process.destroy
36
+ wait # no way to actually use the timeout here..
37
+ end
38
+
39
+ def wait
40
+ if exited?
41
+ exit_code
42
+ else
43
+ @process.waitFor
44
+
45
+ stop_pumps
46
+ @exit_code = @process.exitValue
47
+ end
48
+ end
49
+
50
+ #
51
+ # Only supported in JRuby on a Unix operating system, thanks to limitations
52
+ # in Java's classes
53
+ #
54
+ # @return [Integer] the pid of the process after it has started
55
+ # @raise [NotImplementedError] when trying to access pid on non-Unix platform
56
+ #
57
+ def pid
58
+ if @process.getClass.getName != "java.lang.UNIXProcess"
59
+ raise NotImplementedError, "pid is only supported by JRuby child processes on Unix"
60
+ end
61
+
62
+ # About the best way we can do this is with a nasty reflection-based impl
63
+ # Thanks to Martijn Courteaux
64
+ # http://stackoverflow.com/questions/2950338/how-can-i-kill-a-linux-process-in-java-with-sigkill-process-destroy-does-sigter/2951193#2951193
65
+ field = @process.getClass.getDeclaredField("pid")
66
+ field.accessible = true
67
+ field.get(@process)
68
+ end
69
+
70
+ private
71
+
72
+ def launch_process(&blk)
73
+ pb = java.lang.ProcessBuilder.new(@args)
74
+
75
+ pb.directory java.io.File.new(@cwd || Dir.pwd)
76
+ set_env pb.environment
77
+
78
+ begin
79
+ @process = pb.start
80
+ rescue java.io.IOException => ex
81
+ raise LaunchError, ex.message
82
+ end
83
+
84
+ setup_io
85
+ end
86
+
87
+ def setup_io
88
+ if @io
89
+ redirect(@process.getErrorStream, @io.stderr)
90
+ redirect(@process.getInputStream, @io.stdout)
91
+ else
92
+ @process.getErrorStream.close
93
+ @process.getInputStream.close
94
+ end
95
+
96
+ if duplex?
97
+ io._stdin = create_stdin
98
+ else
99
+ @process.getOutputStream.close
100
+ end
101
+ end
102
+
103
+ def redirect(input, output)
104
+ if output.nil?
105
+ input.close
106
+ return
107
+ end
108
+
109
+ @pumps << Pump.new(input, output.to_outputstream).run
110
+ end
111
+
112
+ def stop_pumps
113
+ @pumps.each { |pump| pump.stop }
114
+ end
115
+
116
+ def set_env(env)
117
+ merged = ENV.to_hash
118
+
119
+ @environment.each { |k, v| merged[k.to_s] = v }
120
+
121
+ merged.each do |k, v|
122
+ if v
123
+ env.put(k, v.to_s)
124
+ elsif env.has_key? k
125
+ env.remove(k)
126
+ end
127
+ end
128
+
129
+ removed_keys = env.key_set.to_a - merged.keys
130
+ removed_keys.each { |k| env.remove(k) }
131
+ end
132
+
133
+ def create_stdin
134
+ output_stream = @process.getOutputStream
135
+
136
+ stdin = output_stream.to_io
137
+ stdin.sync = true
138
+ stdin.instance_variable_set(:@childprocess_java_stream, output_stream)
139
+
140
+ class << stdin
141
+ # The stream provided is a BufferedeOutputStream, so we
142
+ # have to flush it to make the bytes flow to the process
143
+ def __childprocess_flush__
144
+ @childprocess_java_stream.flush
145
+ end
146
+
147
+ [:flush, :print, :printf, :putc, :puts, :write, :write_nonblock].each do |m|
148
+ define_method(m) do |*args|
149
+ super(*args)
150
+ self.__childprocess_flush__
151
+ end
152
+ end
153
+ end
154
+
155
+ stdin
156
+ end
157
+
158
+ end # Process
159
+ end # JRuby
160
+ end # ChildProcess
161
+ end
@@ -0,0 +1,55 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module JRuby
4
+ class Pump
5
+ BUFFER_SIZE = 2048
6
+
7
+ def initialize(input, output)
8
+ @input = input
9
+ @output = output
10
+ @stop = false
11
+ end
12
+
13
+ def stop
14
+ @stop = true
15
+ @thread && @thread.join
16
+ end
17
+
18
+ def run
19
+ @thread = Thread.new { pump }
20
+
21
+ self
22
+ end
23
+
24
+ private
25
+
26
+ def pump
27
+ buffer = Java.byte[BUFFER_SIZE].new
28
+
29
+ until @stop && (@input.available == 0)
30
+ read, avail = 0, 0
31
+
32
+ while read != -1
33
+ avail = [@input.available, 1].max
34
+ avail = BUFFER_SIZE if avail > BUFFER_SIZE
35
+ read = @input.read(buffer, 0, avail)
36
+
37
+ if read > 0
38
+ @output.write(buffer, 0, read)
39
+ @output.flush
40
+ end
41
+ end
42
+
43
+ sleep 0.1
44
+ end
45
+
46
+ @output.flush
47
+ rescue java.io.IOException => ex
48
+ ChildProcess.logger.debug ex.message
49
+ ChildProcess.logger.debug ex.backtrace
50
+ end
51
+
52
+ end # Pump
53
+ end # JRuby
54
+ end # ChildProcess
55
+ end
@@ -0,0 +1,58 @@
1
+ require 'java'
2
+ require 'jruby'
3
+
4
+ class Java::SunNioCh::FileChannelImpl
5
+ field_reader :fd
6
+ end
7
+
8
+ class Java::JavaIo::FileDescriptor
9
+ if ChildProcess.os == :windows
10
+ field_reader :handle
11
+ end
12
+
13
+ field_reader :fd
14
+ end
15
+
16
+ module Aggkit
17
+ module ChildProcess
18
+ module JRuby
19
+ def self.posix_fileno_for(obj)
20
+ channel = ::JRuby.reference(obj).channel
21
+ begin
22
+ channel.getFDVal
23
+ rescue NoMethodError
24
+ fileno = channel.fd
25
+ if fileno.kind_of?(Java::JavaIo::FileDescriptor)
26
+ fileno = fileno.fd
27
+ end
28
+
29
+ fileno == -1 ? obj.fileno : fileno
30
+ end
31
+ rescue
32
+ # fall back
33
+ obj.fileno
34
+ end
35
+
36
+ def self.windows_handle_for(obj)
37
+ channel = ::JRuby.reference(obj).channel
38
+ fileno = obj.fileno
39
+
40
+ begin
41
+ fileno = channel.getFDVal
42
+ rescue NoMethodError
43
+ fileno = channel.fd if channel.respond_to?(:fd)
44
+ end
45
+
46
+ if fileno.kind_of? Java::JavaIo::FileDescriptor
47
+ fileno.handle
48
+ else
49
+ Windows::Lib.handle_for fileno
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ require "aggkit/childprocess/jruby/pump"
57
+ require "aggkit/childprocess/jruby/io"
58
+ require "aggkit/childprocess/jruby/process"
@@ -0,0 +1,148 @@
1
+ require 'fileutils'
2
+
3
+ module Aggkit
4
+ module ChildProcess
5
+ module Tools
6
+ class Generator
7
+ EXE_NAME = "childprocess-sizeof-generator"
8
+ TMP_PROGRAM = "childprocess-sizeof-generator.c"
9
+ DEFAULT_INCLUDES = %w[stdio.h stddef.h]
10
+
11
+ def self.generate
12
+ new.generate
13
+ end
14
+
15
+ def initialize
16
+ @cc = ENV['CC'] || 'gcc'
17
+ @out = File.expand_path("../../unix/platform/#{ChildProcess.platform_name}.rb", __FILE__)
18
+ @sizeof = {}
19
+ @constants = {}
20
+ end
21
+
22
+ def generate
23
+ fetch_size 'posix_spawn_file_actions_t', :include => "spawn.h"
24
+ fetch_size 'posix_spawnattr_t', :include => "spawn.h"
25
+ fetch_size 'sigset_t', :include => "signal.h"
26
+
27
+ fetch_constant 'POSIX_SPAWN_RESETIDS', :include => 'spawn.h'
28
+ fetch_constant 'POSIX_SPAWN_SETPGROUP', :include => 'spawn.h'
29
+ fetch_constant 'POSIX_SPAWN_SETSIGDEF', :include => 'spawn.h'
30
+ fetch_constant 'POSIX_SPAWN_SETSIGMASK', :include => 'spawn.h'
31
+
32
+ if ChildProcess.linux?
33
+ fetch_constant 'POSIX_SPAWN_USEVFORK', :include => 'spawn.h', :define => {'_GNU_SOURCE' => nil}
34
+ end
35
+
36
+ write
37
+ end
38
+
39
+ def write
40
+ FileUtils.mkdir_p(File.dirname(@out))
41
+ File.open(@out, 'w') do |io|
42
+ io.puts result
43
+ end
44
+
45
+ puts "wrote #{@out}"
46
+ end
47
+
48
+ def fetch_size(type_name, opts = {})
49
+ print "sizeof(#{type_name}): "
50
+ src = <<-EOF
51
+ int main() {
52
+ printf("%d", (unsigned int)sizeof(#{type_name}));
53
+ return 0;
54
+ }
55
+ EOF
56
+
57
+ output = execute(src, opts)
58
+
59
+ if output.to_i < 1
60
+ raise "sizeof(#{type_name}) == #{output.to_i} (output=#{output})"
61
+ end
62
+
63
+ size = output.to_i
64
+ @sizeof[type_name] = size
65
+
66
+ puts size
67
+ end
68
+
69
+ def fetch_constant(name, opts)
70
+ print "#{name}: "
71
+ src = <<-EOF
72
+ int main() {
73
+ printf("%d", (unsigned int)#{name});
74
+ return 0;
75
+ }
76
+ EOF
77
+
78
+ output = execute(src, opts)
79
+ value = Integer(output)
80
+ @constants[name] = value
81
+
82
+ puts value
83
+ end
84
+
85
+
86
+ def execute(src, opts)
87
+ program = Array(opts[:define]).map do |key, value|
88
+ <<-SRC
89
+ #ifndef #{key}
90
+ #define #{key} #{value}
91
+ #endif
92
+ SRC
93
+ end.join("\n")
94
+ program << "\n"
95
+
96
+ includes = Array(opts[:include]) + DEFAULT_INCLUDES
97
+ program << includes.map { |include| "#include <#{include}>" }.join("\n")
98
+ program << "\n#{src}"
99
+
100
+ File.open(TMP_PROGRAM, 'w') do |file|
101
+ file << program
102
+ end
103
+
104
+ cmd = "#{@cc} #{TMP_PROGRAM} -o #{EXE_NAME}"
105
+ system cmd
106
+ unless $?.success?
107
+ raise "failed to compile program: #{cmd.inspect}\n#{program}"
108
+ end
109
+
110
+ output = `./#{EXE_NAME} 2>&1`
111
+
112
+ unless $?.success?
113
+ raise "failed to run program: #{cmd.inspect}\n#{output}"
114
+ end
115
+
116
+ output.chomp
117
+ ensure
118
+ File.delete TMP_PROGRAM if File.exist?(TMP_PROGRAM)
119
+ File.delete EXE_NAME if File.exist?(EXE_NAME)
120
+ end
121
+
122
+ def result
123
+ if @sizeof.empty? && @constants.empty?
124
+ raise "no data collected, nothing to do"
125
+ end
126
+
127
+ out = ['module ChildProcess::Unix::Platform']
128
+ out << ' SIZEOF = {'
129
+
130
+ max = @sizeof.keys.map { |e| e.length }.max
131
+ @sizeof.each_with_index do |(type, size), idx|
132
+ out << " :#{type.ljust max} => #{size}#{',' unless idx == @sizeof.size - 1}"
133
+ end
134
+ out << ' }'
135
+
136
+ max = @constants.keys.map { |e| e.length }.max
137
+ @constants.each do |name, val|
138
+ out << " #{name.ljust max} = #{val}"
139
+ end
140
+ out << 'end'
141
+
142
+ out.join "\n"
143
+ end
144
+
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,72 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Unix
4
+ class ForkExecProcess < Process
5
+ private
6
+
7
+ def launch_process
8
+ if @io
9
+ stdout = @io.stdout
10
+ stderr = @io.stderr
11
+ end
12
+
13
+ # pipe used to detect exec() failure
14
+ exec_r, exec_w = ::IO.pipe
15
+ ChildProcess.close_on_exec exec_w
16
+
17
+ if duplex?
18
+ reader, writer = ::IO.pipe
19
+ end
20
+
21
+ @pid = Kernel.fork {
22
+ # Children of the forked process will inherit its process group
23
+ # This is to make sure that all grandchildren dies when this Process instance is killed
24
+ ::Process.setpgid 0, 0 if leader?
25
+
26
+ if @cwd
27
+ Dir.chdir(@cwd)
28
+ end
29
+
30
+ exec_r.close
31
+ set_env
32
+
33
+ STDOUT.reopen(stdout || "/dev/null")
34
+ STDERR.reopen(stderr || "/dev/null")
35
+
36
+ if duplex?
37
+ STDIN.reopen(reader)
38
+ writer.close
39
+ end
40
+
41
+ executable, *args = @args
42
+
43
+ begin
44
+ Kernel.exec([executable, executable], *args)
45
+ rescue SystemCallError => ex
46
+ exec_w << ex.message
47
+ end
48
+ }
49
+
50
+ exec_w.close
51
+
52
+ if duplex?
53
+ io._stdin = writer
54
+ reader.close
55
+ end
56
+
57
+ # if we don't eventually get EOF, exec() failed
58
+ unless exec_r.eof?
59
+ raise LaunchError, exec_r.read || "executing command with #{@args.inspect} failed"
60
+ end
61
+
62
+ ::Process.detach(@pid) if detach?
63
+ end
64
+
65
+ def set_env
66
+ @environment.each { |k, v| ENV[k.to_s] = v.nil? ? nil : v.to_s }
67
+ end
68
+
69
+ end # Process
70
+ end # Unix
71
+ end # ChildProcess
72
+ end
@@ -0,0 +1,22 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Unix
4
+ class IO < AbstractIO
5
+ private
6
+
7
+ def check_type(io)
8
+ unless io.respond_to? :to_io
9
+ raise ArgumentError, "expected #{io.inspect} to respond to :to_io"
10
+ end
11
+
12
+ result = io.to_io
13
+ unless result && result.kind_of?(::IO)
14
+ raise TypeError, "expected IO, got #{result.inspect}:#{result.class}"
15
+ end
16
+ end
17
+
18
+ end # IO
19
+ end # Unix
20
+ end # ChildProcess
21
+ end
22
+
@@ -0,0 +1,188 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Unix
4
+ module Lib
5
+ extend FFI::Library
6
+ ffi_lib FFI::Library::LIBC
7
+
8
+ if ChildProcess.os == :macosx
9
+ attach_function :_NSGetEnviron, [], :pointer
10
+ def self.environ
11
+ _NSGetEnviron().read_pointer
12
+ end
13
+ elsif respond_to? :attach_variable
14
+ attach_variable :environ, :pointer
15
+ end
16
+
17
+ attach_function :strerror, [:int], :string
18
+ attach_function :chdir, [:string], :int
19
+ attach_function :fcntl, [:int, :int, :int], :int # fcntl actually takes varags, but we only need this version.
20
+
21
+ # int posix_spawnp(
22
+ # pid_t *restrict pid,
23
+ # const char *restrict file,
24
+ # const posix_spawn_file_actions_t *file_actions,
25
+ # const posix_spawnattr_t *restrict attrp,
26
+ # char *const argv[restrict],
27
+ # char *const envp[restrict]
28
+ # );
29
+
30
+ attach_function :posix_spawnp, [
31
+ :pointer,
32
+ :string,
33
+ :pointer,
34
+ :pointer,
35
+ :pointer,
36
+ :pointer
37
+ ], :int
38
+
39
+ # int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions);
40
+ attach_function :posix_spawn_file_actions_init, [:pointer], :int
41
+
42
+ # int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions);
43
+ attach_function :posix_spawn_file_actions_destroy, [:pointer], :int
44
+
45
+ # int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *file_actions, int filedes);
46
+ attach_function :posix_spawn_file_actions_addclose, [:pointer, :int], :int
47
+
48
+ # int posix_spawn_file_actions_addopen(
49
+ # posix_spawn_file_actions_t *restrict file_actions,
50
+ # int filedes,
51
+ # const char *restrict path,
52
+ # int oflag,
53
+ # mode_t mode
54
+ # );
55
+ attach_function :posix_spawn_file_actions_addopen, [:pointer, :int, :string, :int, :mode_t], :int
56
+
57
+ # int posix_spawn_file_actions_adddup2(
58
+ # posix_spawn_file_actions_t *file_actions,
59
+ # int filedes,
60
+ # int newfiledes
61
+ # );
62
+ attach_function :posix_spawn_file_actions_adddup2, [:pointer, :int, :int], :int
63
+
64
+ # int posix_spawnattr_init(posix_spawnattr_t *attr);
65
+ attach_function :posix_spawnattr_init, [:pointer], :int
66
+
67
+ # int posix_spawnattr_destroy(posix_spawnattr_t *attr);
68
+ attach_function :posix_spawnattr_destroy, [:pointer], :int
69
+
70
+ # int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags);
71
+ attach_function :posix_spawnattr_setflags, [:pointer, :short], :int
72
+
73
+ # int posix_spawnattr_getflags(const posix_spawnattr_t *restrict attr, short *restrict flags);
74
+ attach_function :posix_spawnattr_getflags, [:pointer, :pointer], :int
75
+
76
+ # int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, pid_t pgroup);
77
+ attach_function :posix_spawnattr_setpgroup, [:pointer, :pid_t], :int
78
+
79
+ # int posix_spawnattr_getpgroup(const posix_spawnattr_t *restrict attr, pid_t *restrict pgroup);
80
+ attach_function :posix_spawnattr_getpgroup, [:pointer, :pointer], :int
81
+
82
+ # int posix_spawnattr_setsigdefault(posix_spawnattr_t *restrict attr, const sigset_t *restrict sigdefault);
83
+ attach_function :posix_spawnattr_setsigdefault, [:pointer, :pointer], :int
84
+
85
+ # int posix_spawnattr_getsigdefault(const posix_spawnattr_t *restrict attr, sigset_t *restrict sigdefault);
86
+ attach_function :posix_spawnattr_getsigdefault, [:pointer, :pointer], :int
87
+
88
+ # int posix_spawnattr_setsigmask(posix_spawnattr_t *restrict attr, const sigset_t *restrict sigmask);
89
+ attach_function :posix_spawnattr_setsigmask, [:pointer, :pointer], :int
90
+
91
+ # int posix_spawnattr_getsigmask(const posix_spawnattr_t *restrict attr, sigset_t *restrict sigmask);
92
+ attach_function :posix_spawnattr_getsigmask, [:pointer, :pointer], :int
93
+
94
+ def self.check(errno)
95
+ if errno != 0
96
+ raise Error, Lib.strerror(FFI.errno)
97
+ end
98
+ end
99
+
100
+ class FileActions
101
+ def initialize
102
+ @ptr = FFI::MemoryPointer.new(1, Platform::SIZEOF.fetch(:posix_spawn_file_actions_t), false)
103
+ Lib.check Lib.posix_spawn_file_actions_init(@ptr)
104
+ end
105
+
106
+ def add_close(fileno)
107
+ Lib.check Lib.posix_spawn_file_actions_addclose(
108
+ @ptr,
109
+ fileno
110
+ )
111
+ end
112
+
113
+ def add_open(fileno, path, oflag, mode)
114
+ Lib.check Lib.posix_spawn_file_actions_addopen(
115
+ @ptr,
116
+ fileno,
117
+ path,
118
+ oflag,
119
+ mode
120
+ )
121
+ end
122
+
123
+ def add_dup(fileno, new_fileno)
124
+ Lib.check Lib.posix_spawn_file_actions_adddup2(
125
+ @ptr,
126
+ fileno,
127
+ new_fileno
128
+ )
129
+ end
130
+
131
+ def free
132
+ Lib.check Lib.posix_spawn_file_actions_destroy(@ptr)
133
+ @ptr = nil
134
+ end
135
+
136
+ def to_ptr
137
+ @ptr
138
+ end
139
+ end # FileActions
140
+
141
+ class Attrs
142
+ def initialize
143
+ @ptr = FFI::MemoryPointer.new(1, Platform::SIZEOF.fetch(:posix_spawnattr_t), false)
144
+ Lib.check Lib.posix_spawnattr_init(@ptr)
145
+ end
146
+
147
+ def free
148
+ Lib.check Lib.posix_spawnattr_destroy(@ptr)
149
+ @ptr = nil
150
+ end
151
+
152
+ def flags=(flags)
153
+ Lib.check Lib.posix_spawnattr_setflags(@ptr, flags)
154
+ end
155
+
156
+ def flags
157
+ ptr = FFI::MemoryPointer.new(:short)
158
+ Lib.check Lib.posix_spawnattr_getflags(@ptr, ptr)
159
+
160
+ ptr.read_short
161
+ end
162
+
163
+ def pgroup=(pid)
164
+ self.flags |= Platform::POSIX_SPAWN_SETPGROUP
165
+ Lib.check Lib.posix_spawnattr_setpgroup(@ptr, pid)
166
+ end
167
+
168
+ def to_ptr
169
+ @ptr
170
+ end
171
+ end # Attrs
172
+
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ # missing on rubinius
179
+ class FFI::MemoryPointer
180
+ unless method_defined?(:from_string)
181
+ def self.from_string(str)
182
+ ptr = new(1, str.bytesize + 1)
183
+ ptr.write_string("#{str}\0")
184
+
185
+ ptr
186
+ end
187
+ end
188
+ end if defined?(FFI)