aggkit 0.2.5

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.
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)