subspawn-posix 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e1e9a8cd307acbb03c05440d9300a7e48a3636d36734f2b4cb189aae0ab98bdd
4
+ data.tar.gz: e176b1617b2c674da1de970e1b9e7ecf7776ab9c418e746374ec3983e4d6a31b
5
+ SHA512:
6
+ metadata.gz: 47fc587e35e40da9bffc3b8a8877363441c98dd25cbe1c46509c92478bae2651661194ad080271226b23446eb3f1de089af9e132e212f29e05940d5adecc86d5
7
+ data.tar.gz: 540342bd3dc2c875a0f94a8d6d0a6f58698a8cdc456cf707e09f8c5c1251d71294342a2d802d220ad6feea64f03d3f482c4f3c00781a88a2b3a19614a34bde3a
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --format documentation
2
+ --color
3
+ -I ../ffi-binary-libfixposix/lib
4
+ -I ../ffi-bindings-libfixposix/lib
5
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in process-wrapper-mid.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # SubSpawn Native POSIX API
2
+
3
+ SubSpawn wrapper over libfixposix. Install `ffi-binary-libfixposix` if you need binaries.
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add subspawn-posix
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install subspawn-posix
14
+
15
+ ## Usage
16
+
17
+ ```rb
18
+ require 'subspawn/posix'
19
+ ```
20
+
21
+ See SubSpawn for more details.
22
+
23
+ An RBS file exists for this gem.
24
+
25
+ ## Development
26
+
27
+ See parent SubSpawn readme
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :build
@@ -0,0 +1,2 @@
1
+ require 'libfixposix/binary'
2
+ require 'subspawn/posix'
@@ -0,0 +1,39 @@
1
+ require 'ffi'
2
+
3
+ module SubSpawn::POSIX::Internal
4
+ module SignalFn
5
+ extend FFI::Library
6
+ ffi_lib FFI::Library::LIBC
7
+
8
+ attach_function :emptyset, :sigemptyset, [:pointer], :int
9
+ attach_function :fillset, :sigfillset, [:pointer], :int
10
+ attach_function :addset, :sigaddset, [:pointer, :int], :int
11
+ attach_function :delset, :sigdelset, [:pointer, :int], :int
12
+ attach_function :ismember, :sigismember, [:pointer, :int], :int
13
+
14
+
15
+ ffi_lib %w{pthread.so.0 pthread pthread.dylib}
16
+
17
+ attach_function :mask, :pthread_sigmask, %i{int pointer pointer}, :int
18
+ end
19
+
20
+ module OpenPTY
21
+ extend FFI::Library
22
+
23
+ ffi_lib FFI::Platform.mac? ? FFI::Library::LIBC : "util"
24
+ attach_function :openpty, [:buffer_out, :buffer_out, :buffer_out, :buffer_in, :buffer_in], :int
25
+
26
+ ffi_lib FFI::Library::LIBC
27
+ attach_function :close, [:int], :int
28
+
29
+ def self.call(termios: nil, winsize: nil)
30
+ FFI::MemoryPointer.new(:int, 2) do |fds|
31
+ FFI::MemoryPointer.new(:char, 4096) do |name| # max on linux = 4096
32
+ ret = self.openpty(fds[0], fds[1], name, termios, winsize)
33
+ raise SystemCallError.new("OpenPTY Error", LFP.errno) unless ret == 0
34
+ return [*fds.read_array_of_int(2), name.read_string]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module SubSpawn
6
+ class POSIX
7
+ module PtyHelper
8
+ class PtyIO < IO
9
+ def inspect
10
+ "#<IO:masterpty:#{@slave_path}>"
11
+ end
12
+
13
+ # Subspawn-specific feature
14
+ attr_reader :slave_path
15
+
16
+ private
17
+ def __subspawn_init(name)
18
+ # All other files are opened cloexec, this one isn't yet as it came from native code
19
+ self.close_on_exec = true
20
+ @slave_path = name.freeze
21
+ self.sync = true
22
+ end
23
+ end
24
+
25
+ def self.open_internal(chmod_for_open = false)
26
+ m, s, name = Internal::OpenPTY.call
27
+
28
+ # chmod the slave path, but only if were were called as ::open, not as ::spawn
29
+ # I don't understand why, but doing this just to mirror MRI
30
+ File.chmod(0o600, name) if chmod_for_open
31
+
32
+ master = PtyIO.for_fd(m, IO::RDWR | IO::SYNC)
33
+ master.send(:__subspawn_init, name)
34
+
35
+ # we could shim the slave to be a fake file, or we could just re-open the path
36
+ # which fixes #inspect, #tty?, #path, and #close_on_exec, all in one go
37
+ # https://bugs.ruby-lang.org/issues/19036
38
+ slave = File.open(name, IO::RDWR | IO::SYNC)
39
+ slave.sync = true # still must do this manually though, as IO::SYNC seems to be mostly ignored
40
+ Internal::OpenPTY.close(s)
41
+
42
+ [master, slave, name]
43
+ end
44
+
45
+ def self.open
46
+ *files, name = open_internal(true)
47
+ return files unless block_given?
48
+
49
+ begin
50
+ return yield files.dup # Array, not splatted
51
+ ensure
52
+ files.reject(&:closed?).each(&:close)
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,85 @@
1
+ class SubSpawn::POSIX::SigSet
2
+ def initialize(base=:empty)
3
+ base = base.to_sym
4
+ raise ArgumentError, "SigSet only accepts :full, :empty, or :current" unless %i{full empty current}.include? base
5
+ # TODO: warn about current?
6
+ @base = base
7
+ @ops = []
8
+ @ptr = nil
9
+ end
10
+ def self.empty
11
+ self.new(:empty)
12
+ end
13
+ def self.full
14
+ self.new(:full)
15
+ end
16
+ def self.current
17
+ self.new(:current)
18
+ end
19
+ def include(*signals)
20
+ clean_signals(signals).each do |sig|
21
+ @ops << [:add, sig]
22
+ end
23
+ self
24
+ end
25
+ def exclude(*signals)
26
+ clean_signals(signals).each do |sig|
27
+ @ops << [:rm, sig]
28
+ end
29
+ self
30
+ end
31
+ alias :- :exclude
32
+ alias :+ :include
33
+ alias :add :exclude
34
+ alias :del :include
35
+ alias :delete :include
36
+
37
+ def to_ptr(&block)
38
+ if @ptr.nil?
39
+ # sigset_t is largest on linux, at 128 bytes, so always allocate that much
40
+ if block_given?
41
+ ret = nil
42
+ FFI::MemoryPointer.new(:uint8, 128) {|ptr| alloc_internal(ptr); ret = block.call(ptr)}
43
+ ret
44
+ else
45
+ FFI::MemoryPointer.new(:uint8, 128).tap {|ptr| @ptr = ptr; alloc_internal(ptr)}
46
+ end
47
+ else
48
+ if block_given?
49
+ block.call(@ptr)
50
+ else
51
+ @ptr
52
+ end
53
+ end
54
+ end
55
+ private
56
+ def clean_signals(signals)
57
+ signals.flatten.map do |sig|
58
+ if Integer === sig
59
+ sig
60
+ else
61
+ Signal.list[sig.to_s.upcase].tap do |good|
62
+ raise ArgumentError, "#{sig} cannot be converted to a signal" if good.nil?
63
+ end
64
+ end
65
+ end
66
+ end
67
+ def alloc_internal ptr
68
+ sig = SubSpawn::POSIX::Internal::SignalFn
69
+ case @base
70
+ when :full then sig.fillset(ptr)
71
+ when :empty then sig.emptyset(ptr)
72
+ when :current
73
+ sig.emptyset(ptr)
74
+ sig.mask(0, nil, ptr) # get the current (old) set
75
+ else raise "Invalid State"
76
+ end
77
+ @ops.each do |op, num|
78
+ if op == :add
79
+ sig.addset(ptr, num)
80
+ else
81
+ sig.delset(ptr, num)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SubSpawn
4
+ class POSIX
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,375 @@
1
+ require 'libfixposix'
2
+ require 'subspawn/posix/version'
3
+ require 'subspawn/posix/ffi_helper'
4
+ require 'subspawn/posix/signals'
5
+ require 'subspawn/posix/pty'
6
+ module SubSpawn
7
+ class SpawnError < RuntimeError
8
+ end
9
+ class POSIX
10
+
11
+ OpenFD = Struct.new(:fd, :path, :mode, :flags)
12
+
13
+ def initialize(command, *args, arg0: command)
14
+ @path = command
15
+ #raise SpawnError, "Command not found: #{command}" unless @path
16
+ # TODO: we use envp, so can't check this now
17
+ @argv = [arg0, *args.map(&:to_str)]
18
+ @fd_map = {}
19
+ @fd_keeps = []
20
+ @fd_closes = []
21
+ @fd_opens = []
22
+ @signal_mask = @signal_default = nil
23
+ @cwd = nil
24
+ @sid = false
25
+ @pgroup = nil
26
+ @env = :default
27
+ @ctty = nil
28
+ @rlimits = {}
29
+ @umask = nil
30
+ end
31
+ attr_writer :cwd, :ctty
32
+
33
+ StdIn = 0
34
+ StdOut= 1
35
+ StdErr = 2
36
+ Std = {in: StdIn, out: StdOut, err: StdErr}.freeze
37
+
38
+ def validate!
39
+ @argv.map!(&:to_str) # By spec
40
+ raise SpawnError, "Invalid argv" unless @argv.length > 0
41
+ @fd_map = @fd_map.map do |number, source|
42
+ raise SpawnError, "Invalid FD map: Not a number: #{number.inspect}" unless number.is_a? Integer
43
+ [number, fd_check(source)]
44
+ end.to_h
45
+ @fd_keeps.each{|x| fd_check(x)}
46
+ @fd_closes.each{|x| fd_check(x)}
47
+ @fd_opens.each{|x|
48
+ fd_check(x.fd)
49
+ raise SpawnError, "Invalid FD open: Not a number: #{x.mode.inspect}" unless x.mode.is_a? Integer
50
+ raise SpawnError, "Invalid FD open: Not a flag: #{x.flags.inspect}" unless x.flags.is_a? Integer
51
+ raise SpawnError, "Invalid FD open: Not a file: #{x.file.inspect}" unless File.exist? x.path or Dir.exist?(File.dirname(x.path))
52
+ }
53
+
54
+ raise SpawnError, "Invalid cwd path" unless @cwd.nil? or Dir.exist?(@cwd = ensure_file_string(@cwd))
55
+
56
+ @ctty = @ctty.path if !@ctty.nil? and @ctty.is_a? File # PTY.open returns files
57
+ raise SpawnError, "Invalid controlling tty path" unless @ctty.nil? or File.exist?(@ctty = ensure_file_string(@ctty))
58
+
59
+ true
60
+ end
61
+
62
+ def spawn!
63
+ validate!
64
+ sfa = LFP::SpawnFileActions.new
65
+ sa = LFP::Spawnattr.new
66
+ raise "Spawn Init Error" if 0 != sfa.init
67
+ out_pid = nil
68
+ begin
69
+ raise "Spawn Init Error" if 0 != sa.init
70
+ begin
71
+ # set up file descriptors
72
+
73
+ @fd_keeps.each {|fd| sfa.addkeep(fd_number(fd)) }
74
+ @fd_opens.each {|opn|
75
+ sfa.addopen(fd_number(opn.fd), opn.path, opn.flags, opn.mode)
76
+ }
77
+ @fd_map.map{|k, v| [k, fd_number(v)] }.each do |dest, src|
78
+ sfa.adddup2(src, dest)
79
+ end
80
+ @fd_closes.each {|fd| sfa.addclose(fd_number(fd)) }
81
+
82
+ unless @rlimits.empty?
83
+ # allocate output (pid)
84
+ FFI::MemoryPointer.new(LFP::Rlimit, @rlimits.length) do |rlimits|
85
+ # build array
86
+ @rlimits.each_with_index {|(key, (cur, max)), i|
87
+ rlimit = LFP::Rlimit.new(rlimits[i])
88
+ #puts "building rlim at #{i} to #{[cur, max, key]}"
89
+ rlimit[:rlim_cur] = cur.to_i
90
+ rlimit[:rlim_max] = max.to_i
91
+ rlimit[:resource] = key.to_i
92
+ }
93
+ sa.setrlimit(rlimits, @rlimits.length)
94
+ end
95
+ end
96
+
97
+ # set up signals
98
+ sa.sigmask = @signal_mask.to_ptr if @signal_mask
99
+ sa.sigdefault = @signal_default.to_ptr if @signal_default
100
+
101
+ # set up ownership and groups
102
+ sa.pgroup = @pgroup.to_i if @pgroup
103
+ sa.umask = @umask.to_i if @umask
104
+
105
+ # Set up terminal control
106
+ sa.setsid if @sid
107
+ sa.ctty = @ctty if @ctty
108
+
109
+ # set up working dir
110
+ sa.cwd = @cwd if @cwd
111
+
112
+ # allocate output (pid)
113
+ FFI::MemoryPointer.new(:int, 1) do |pid|
114
+ argv_str = @argv.map{|a|
115
+ raise ArgumentError, "Nulls not allowed in command: #{a.inspect}" if a.include? "\0"
116
+ FFI::MemoryPointer.from_string a
117
+ } + [nil] # null end of argv
118
+ FFI::MemoryPointer.new(:pointer, argv_str.length) do |argv_holder|
119
+
120
+ # ARGV
121
+ argv_holder.write_array_of_pointer argv_str
122
+
123
+ # ARGP/ENV
124
+ make_envp do |envp_holder|
125
+
126
+ # Launch!
127
+ ret = LFP.spawnp(pid, @path, argv_holder, envp_holder, sfa, sa)
128
+ if ret != 0
129
+ raise SystemCallError.new("Spawn Error: #{ret}", LFP.errno)
130
+ end
131
+ out_pid = pid.read_int
132
+ end
133
+ end
134
+ end
135
+ ensure
136
+ sa.destroy
137
+ end
138
+ ensure
139
+ sfa.destroy
140
+ end
141
+ out_pid
142
+ end
143
+
144
+ # TODO: allow io on left?
145
+ def fd(number, io_or_fd)
146
+ num = number.is_a?(Symbol) ? Std[number] : number.to_i
147
+ raise ArgumentError, "Invalid file descriptor number: #{number}. Supported values = 0.. or #{std.keys.inspect}" if num.nil?
148
+ if fd_number(io_or_fd) == num
149
+ fd_keep(io_or_fd)
150
+ else
151
+ @fd_map[num] = io_or_fd
152
+ end
153
+ self
154
+ end
155
+
156
+ def fd_open(number, path, flags = 0, mode=0o666) # umask will remove bits
157
+ num = number.is_a?(Symbol) ? Std[number] : number.to_i
158
+ raise ArgumentError, "Invalid file descriptor number: #{number}. Supported values = 0.. or #{std.keys.inspect}" if num.nil?
159
+ @fd_opens << OpenFD.new(number, path, mode, flags)
160
+ self
161
+ end
162
+ def fd_keep(io_or_fd)
163
+ @fd_keep << io_or_fd
164
+ self
165
+ end
166
+ def fd_close(io_or_fd)
167
+ @fd_closes << io_or_fd
168
+ self
169
+ end
170
+ def name(string)
171
+ @argv[0] = string.to_s
172
+ self
173
+ end
174
+ alias :name= :name
175
+
176
+ def args(args)
177
+ @argv = [@argv[0], *args.map(&:to_str)]
178
+ self
179
+ end
180
+ alias :args= :args
181
+ def command(cmd)
182
+ @path = cmd
183
+ self
184
+ end
185
+ alias :command= :command
186
+
187
+ def env_reset!
188
+ @env = :default
189
+ self
190
+ end
191
+ def env(key, value)
192
+ @env = ENV.to_h.dup if @env == :default
193
+ @env[key.to_s] = value.to_s
194
+ self
195
+ end
196
+ def env=(hash)
197
+ @env = hash.to_h
198
+ self
199
+ end
200
+ # usage:
201
+ # signal_mask = SigSet.empty.add(:usr1).delete("USR2")
202
+ # signal_mask(:full, exclude: [9])
203
+ def signal_mask(sigmask = :default, add: [], delete: [], block: [], allow: [])
204
+ sigmask = :empty if sigmask == :default
205
+ @signal_mask = sigmask.is_a?(Symbol) ? SigSet.send(sigmask) : sigmask
206
+ @signal_mask.add(add, allow).delete(delete, block)
207
+ self
208
+ end
209
+ alias :signal_mask= :signal_mask
210
+ alias :sigmask= :signal_mask
211
+ alias :sigmask :signal_mask
212
+
213
+ # usage:
214
+ # signal_default = SigSet.empty.add(:usr1).delete("USR2")
215
+ # signal_default(:full, exclude: [9])
216
+ def signal_default(sigmask = :default, add: [], delete: [], default: [])
217
+ sigmask = :empty if sigmask == :default
218
+ @signal_default = sigmask.is_a?(Symbol) ? SigSet.send(sigmask) : sigmask
219
+ @signal_default.add(add, default).delete(delete)
220
+ self
221
+ end
222
+ alias :signal_default= :signal_default
223
+
224
+ def umask=(value)
225
+ @umask = value.nil? ? nil : value.to_i
226
+ self
227
+ end
228
+ alias :umask :umask=
229
+
230
+ def pwd(path)
231
+ @cwd = path
232
+ self
233
+ end
234
+ alias :cwd :pwd
235
+ alias :pwd= :cwd=
236
+ alias :chdir :pwd
237
+ alias :chdir= :cwd=
238
+
239
+ def sid!
240
+ @sid = true
241
+ self
242
+ end
243
+ def pgroup(pid)
244
+ raise ArgumentError, "Invalid pgroup: #{pid}" if pid < 0 or !pid.is_a?(Integer)
245
+ @pgroup = pid.to_i
246
+ self
247
+ end
248
+ alias :pgroup= :pgroup
249
+
250
+ def ctty(path)
251
+ @ctty = path
252
+ self
253
+ end
254
+ alias :tty= :ctty=
255
+ alias :tty :ctty
256
+
257
+
258
+ def rlimit(key, cur, max=nil)
259
+ key = if key.is_a? Integer
260
+ key.to_i
261
+ else # TODO: these const lookup should have better error handling
262
+ Process.const_get("RLIMIT_#{key.to_s.upcase}")
263
+ end
264
+ cur = ensure_rlimit(key, cur, 0)
265
+ max = ensure_rlimit(key, max, 1)
266
+ cur = max if cur > max
267
+ @rlimits[key] = [cur, max]
268
+ self
269
+ end
270
+ alias :setrlimit :rlimit
271
+
272
+ def validate
273
+ validate! rescue false
274
+ end
275
+
276
+
277
+
278
+ # generator for candidates for an executable name
279
+ # usage:
280
+ # SubSpawn::POSIX.each_which("ls", ENV) {|path| ...}
281
+ # SubSpawn::POSIX.each_which("ls", ENV).to_a
282
+ def self.expand_which(name, env=ENV)
283
+ return self.to_enum(:expand_which, name, env) unless block_given?
284
+ # only allow relative paths if they traverse, and if they traverse, only allow relative paths
285
+ if name.include? "/"
286
+ yield File.absolute_path(name)
287
+ else
288
+ env['PATH'].split(File::PATH_SEPARATOR).each do |path|
289
+ yield File.join(path, name)
290
+ end
291
+ end
292
+ end
293
+
294
+ def self.shell_command(string)
295
+ # MRI scans for "basic" commands and if so, just un-expands the shell
296
+ # we could do that too, and there are 2 tests about that in rubyspec
297
+ # but we shall ignore them for now
298
+ # TODO: implement that
299
+ ["sh", "-c", string.to_str]
300
+ end
301
+
302
+ COMPLETE_VERSION = {
303
+ subspawn_posix: SubSpawn::POSIX::VERSION,
304
+ libfixposix: LFP::COMPLETE_VERSION,
305
+ }
306
+
307
+ private
308
+ def none
309
+ @@none ||= Object.new
310
+ end
311
+ def ensure_rlimit(key, value, index)
312
+ if value.nil?
313
+ return Process.getrlimit(key)[index] # unspecified, load saved
314
+ end
315
+ return value.to_i if value.is_a? Integer
316
+ Process.const_get("RLIMIT_#{value.to_s.upcase}")
317
+ end
318
+
319
+ def make_envp
320
+ if @env == :default
321
+ yield LFP.get_environ
322
+ else
323
+ strings = @env.select{|k, v|
324
+ !k.nil? and !v.nil?
325
+ }.map{|k,v|
326
+ k = k.to_str
327
+ str = "#{k}=#{v.to_str}" # rubyspec says to convert to_str
328
+ raise ArgumentError, "Nulls not allowed in environment variable: #{str.inspect}" if str.include? "\0" # By Spec
329
+ raise ArgumentError, "Variable key cannot include '=': #{str.inspect}" if k.include? "=" # By Spec
330
+ FFI::MemoryPointer.from_string str
331
+ } + [nil] # null end of argp
332
+ FFI::MemoryPointer.new(:pointer, strings.length) do |argp_holder|
333
+ argp_holder.write_array_of_pointer strings
334
+ yield argp_holder
335
+ end
336
+ end
337
+ end
338
+ def ensure_file_string(path)
339
+ if defined? JRUBY_VERSION # accept File and Path java objects
340
+ path = path.to_file if path.respond_to? :to_file
341
+ if path.respond_to? :absolute_path
342
+ path.absoloute_path
343
+ else
344
+ path.to_s
345
+ end
346
+ else
347
+ path.to_s
348
+ end
349
+ end
350
+ def fd_check(source)
351
+ case source
352
+ when Integer then source
353
+ when IO then source
354
+ when :in, :out, :err
355
+ Std[source]
356
+ else
357
+ raise SpawnError, "Invalid FD map: Not a io or number: #{source.inspect}"
358
+ end
359
+ end
360
+ def fd_number(source)
361
+ case source
362
+ when Integer then source
363
+ when IO then source.fileno
364
+ when :in, :out, :err
365
+ Std[source]
366
+ else
367
+ raise SpawnError, "Invalid FD map: Not a io or number: #{source.inspect}"
368
+ end
369
+ end
370
+
371
+ end
372
+ end
373
+
374
+
375
+
@@ -0,0 +1,100 @@
1
+ module SubSpawn
2
+ class SpawnError < RuntimeError
3
+ end
4
+ class POSIX
5
+ VERSION: String
6
+
7
+ StdIn: Integer
8
+ StdErr: Integer
9
+ StdOut: Integer
10
+ Std: Hash[(:in | :out | :err), Integer]
11
+
12
+ def initialize: (String command, *String args, ?arg0: String) -> nil
13
+
14
+ # Environmental attributes
15
+ attr_writer cwd: String | File
16
+ def cwd: (String | File) -> self
17
+ alias pwd= cwd=
18
+ alias pwd cwd
19
+ alias chdir= cwd=
20
+ alias chdir cwd
21
+
22
+ def env_reset!: () -> self
23
+ attr_writer env: Hash
24
+ def env: (key: String, value: String) -> self
25
+
26
+ attr_writer umask: Integer
27
+ def umask: (Integer) -> self
28
+
29
+ # File descriptor mapping
30
+ type FD = IO | :in | :out | :err | Integer
31
+ def fd: (FD dest,FD source) -> self
32
+ def fd_open: (FD dest, String path, ?Integer flags, ?Integer create_file_mode) -> self
33
+ def fd_keep: (FD) -> self
34
+ def fd_close: (FD) -> self
35
+
36
+ # Head control
37
+ def sid!: () -> self
38
+ def pgroup: (Integer) -> self
39
+ attr_writer pgroup: Integer
40
+ attr_writer tty: String | File
41
+ def tty: (String | File) -> self
42
+ alias ctty= tty=
43
+ alias ctty tty
44
+
45
+ # Signals
46
+ type SignalName = String | Symbol | Integer
47
+ def signal_mask: (SubSpawn::POSIX::SigSet | :full | :empty | :current | :default, ?add: SignalName | Array[SignalName], ?delete: SignalName | Array[SignalName], ?block: SignalName | Array[SignalName], ?allow: SignalName | Array[SignalName]) -> self
48
+ attr_writer signal_mask: SubSpawn::POSIX::SigSet
49
+ alias sigmask signal_mask
50
+ alias sigmask= signal_mask=
51
+
52
+ def signal_default: (SubSpawn::POSIX::SigSet | :full | :empty | :current | :default, ?add: SignalName | Array[SignalName], ?delete: SignalName | Array[SignalName], ?default: SignalName | Array[SignalName]) -> self
53
+ alias signal_default= signal_mask=
54
+
55
+ # Misc
56
+ type ResourceName = String | Symbol | Integer
57
+ def name: (String) -> self
58
+ attr_writer name: String
59
+ def args: (Array[String]) -> self
60
+ attr_writer args: Array[String]
61
+ def command: (String) -> self
62
+ attr_writer command: String
63
+
64
+ def rlimit: (ResourceName resource_type, Integer soft_limit, ?Integer hard_max) -> self
65
+ alias setrlimit rlimit
66
+
67
+ # Action items
68
+ def validate: () -> bool
69
+ def validate!: () -> TrueClass
70
+ def spawn!: () -> Integer
71
+
72
+ # class interface methods
73
+ def self.expand_which: (String, ?Hash[String, String]) -> Enumerable[String]
74
+ | [T] (String, ?Hash[String, String]) { (String) -> T} -> T
75
+
76
+ def self.shell_command: (String) -> Array[String]
77
+
78
+ # Nested Types
79
+ class SigSet
80
+ # Builders
81
+ def self.empty: -> SigSet
82
+ def self.full: -> SigSet
83
+ def self.current: -> SigSet
84
+ def initialize: (?:current | :empty | :full base) -> nil
85
+
86
+ # Modifiers
87
+ def include: (*(SignalName | Array[SignalName]) signals) -> self
88
+ def exclude: (*(SignalName | Array[SignalName]) signals) -> self
89
+ alias - exclude
90
+ alias + include
91
+ alias add exclude
92
+ alias del include
93
+ alias delete include
94
+
95
+ # Output (library use only)
96
+ def to_ptr: [T] () {(FFI::MemoryPointer) -> T} -> T
97
+ | () -> FFI::MemoryPointer
98
+ end
99
+ end
100
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: subspawn-posix
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Patrick Plenefisch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi-bindings-libfixposix
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.0
27
+ description: A SubSpawn subproject to wrap libfixposix as a mid level API
28
+ email:
29
+ - simonpatp@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rspec"
35
+ - Gemfile
36
+ - README.md
37
+ - Rakefile
38
+ - lib/subspawn/binary.rb
39
+ - lib/subspawn/posix.rb
40
+ - lib/subspawn/posix/ffi_helper.rb
41
+ - lib/subspawn/posix/pty.rb
42
+ - lib/subspawn/posix/signals.rb
43
+ - lib/subspawn/posix/version.rb
44
+ - sig/subspawn/posix.rbs
45
+ homepage: https://github.com/byteit101/subspawn
46
+ licenses: []
47
+ metadata:
48
+ homepage_uri: https://github.com/byteit101/subspawn
49
+ source_code_uri: https://github.com/byteit101/subspawn
50
+ changelog_uri: https://github.com/byteit101/subspawn
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 2.6.0
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.0.3.1
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: SubSpawn Mid-level API for POSIX systems
70
+ test_files: []