subspawn-posix 0.1.0

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 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: []