Class: SubSpawn::POSIX
- Inherits:
-
Object
- Object
- SubSpawn::POSIX
- Defined in:
- lib/subspawn/posix.rb,
lib/subspawn/posix/pty.rb,
lib/subspawn/posix/version.rb
Defined Under Namespace
Modules: Internal, PtyHelper Classes: OpenFD, SigSet
Constant Summary collapse
- StdIn =
0
- StdOut =
1
- StdErr =
2
- Std =
{in: StdIn, out: StdOut, err: StdErr}.freeze
- COMPLETE_VERSION =
{ subspawn_posix: SubSpawn::POSIX::VERSION, libfixposix: LFP::COMPLETE_VERSION, }
- VERSION =
"0.1.1"
Instance Attribute Summary collapse
- #ctty(path) ⇒ Object (also: #tty)
-
#cwd ⇒ Object
(also: #pwd=, #chdir=)
writeonly
Sets the attribute cwd.
Class Method Summary collapse
-
.expand_which(name, env = ENV) ⇒ Object
generator for candidates for an executable name usage: SubSpawn::POSIX.each_which(“ls”, ENV) {|path| …} SubSpawn::POSIX.each_which(“ls”, ENV).to_a.
- .shell_command(string) ⇒ Object
Instance Method Summary collapse
- #args(args) ⇒ Object (also: #args=)
- #command(cmd) ⇒ Object (also: #command=)
- #env(key, value) ⇒ Object
- #env=(hash) ⇒ Object
- #env_reset! ⇒ Object
-
#fd(number, io_or_fd) ⇒ Object
TODO: allow io on left?.
- #fd_close(io_or_fd) ⇒ Object
- #fd_keep(io_or_fd) ⇒ Object
-
#fd_open(number, path, flags = 0, mode = 0o666) ⇒ Object
umask will remove bits.
-
#initialize(command, *args, arg0: command) ⇒ POSIX
constructor
A new instance of POSIX.
- #name(string) ⇒ Object (also: #name=)
- #pgroup(pid) ⇒ Object (also: #pgroup=)
- #pwd(path) ⇒ Object (also: #cwd, #chdir)
- #rlimit(key, cur, max = nil) ⇒ Object (also: #setrlimit)
- #sid! ⇒ Object
-
#signal_default(sigmask = :default, add: [], delete: [], default: []) ⇒ Object
(also: #signal_default=)
usage: signal_default = SigSet.empty.add(:usr1).delete(“USR2”) signal_default(:full, exclude: [9]).
-
#signal_mask(sigmask = :default, add: [], delete: [], block: [], allow: []) ⇒ Object
(also: #signal_mask=, #sigmask=, #sigmask)
usage: signal_mask = SigSet.empty.add(:usr1).delete(“USR2”) signal_mask(:full, exclude: [9]).
- #spawn! ⇒ Object
- #umask=(value) ⇒ Object (also: #umask)
- #validate ⇒ Object
- #validate! ⇒ Object
Constructor Details
#initialize(command, *args, arg0: command) ⇒ POSIX
Returns a new instance of POSIX.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/subspawn/posix.rb', line 13 def initialize(command, *args, arg0: command) @path = command #raise SpawnError, "Command not found: #{command}" unless @path # TODO: we use envp, so can't check this now @argv = [arg0, *args.map(&:to_str)] @fd_map = {} @fd_keeps = [] @fd_closes = [] @fd_opens = [] @signal_mask = @signal_default = nil @cwd = nil @sid = false @pgroup = nil @env = :default @ctty = nil @rlimits = {} @umask = nil end |
Instance Attribute Details
#ctty(path) ⇒ Object Also known as: tty
250 251 252 253 |
# File 'lib/subspawn/posix.rb', line 250 def ctty(path) @ctty = path self end |
#cwd=(value) ⇒ Object (writeonly) Also known as: pwd=, chdir=
Sets the attribute cwd
31 32 33 |
# File 'lib/subspawn/posix.rb', line 31 def cwd=(value) @cwd = value end |
Class Method Details
.expand_which(name, env = ENV) ⇒ Object
generator for candidates for an executable name usage: SubSpawn::POSIX.each_which(“ls”, ENV) {|path| …} SubSpawn::POSIX.each_which(“ls”, ENV).to_a
282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/subspawn/posix.rb', line 282 def self.(name, env=ENV) return self.to_enum(:expand_which, name, env) unless block_given? # only allow relative paths if they traverse, and if they traverse, only allow relative paths if name.include? "/" yield File.absolute_path(name) else env['PATH'].split(File::PATH_SEPARATOR).each do |path| yield File.join(path, name) end end end |
.shell_command(string) ⇒ Object
294 295 296 297 298 299 300 |
# File 'lib/subspawn/posix.rb', line 294 def self.shell_command(string) # MRI scans for "basic" commands and if so, just un-expands the shell # we could do that too, and there are 2 tests about that in rubyspec # but we shall ignore them for now # TODO: implement that ["sh", "-c", string.to_str] end |
Instance Method Details
#args(args) ⇒ Object Also known as: args=
176 177 178 179 |
# File 'lib/subspawn/posix.rb', line 176 def args(args) @argv = [@argv[0], *args.map(&:to_str)] self end |
#command(cmd) ⇒ Object Also known as: command=
181 182 183 184 |
# File 'lib/subspawn/posix.rb', line 181 def command(cmd) @path = cmd self end |
#env(key, value) ⇒ Object
191 192 193 194 195 |
# File 'lib/subspawn/posix.rb', line 191 def env(key, value) @env = ENV.to_h.dup if @env == :default @env[key.to_s] = value.to_s self end |
#env=(hash) ⇒ Object
196 197 198 199 |
# File 'lib/subspawn/posix.rb', line 196 def env=(hash) @env = hash.to_h self end |
#env_reset! ⇒ Object
187 188 189 190 |
# File 'lib/subspawn/posix.rb', line 187 def env_reset! @env = :default self end |
#fd(number, io_or_fd) ⇒ Object
TODO: allow io on left?
145 146 147 148 149 150 151 152 153 154 |
# File 'lib/subspawn/posix.rb', line 145 def fd(number, io_or_fd) num = number.is_a?(Symbol) ? Std[number] : number.to_i raise ArgumentError, "Invalid file descriptor number: #{number}. Supported values = 0.. or #{std.keys.inspect}" if num.nil? if fd_number(io_or_fd) == num fd_keep(io_or_fd) else @fd_map[num] = io_or_fd end self end |
#fd_close(io_or_fd) ⇒ Object
166 167 168 169 |
# File 'lib/subspawn/posix.rb', line 166 def fd_close(io_or_fd) @fd_closes << io_or_fd self end |
#fd_keep(io_or_fd) ⇒ Object
162 163 164 165 |
# File 'lib/subspawn/posix.rb', line 162 def fd_keep(io_or_fd) @fd_keep << io_or_fd self end |
#fd_open(number, path, flags = 0, mode = 0o666) ⇒ Object
umask will remove bits
156 157 158 159 160 161 |
# File 'lib/subspawn/posix.rb', line 156 def fd_open(number, path, flags = 0, mode=0o666) # umask will remove bits num = number.is_a?(Symbol) ? Std[number] : number.to_i raise ArgumentError, "Invalid file descriptor number: #{number}. Supported values = 0.. or #{std.keys.inspect}" if num.nil? @fd_opens << OpenFD.new(number, path, mode, flags) self end |
#name(string) ⇒ Object Also known as: name=
170 171 172 173 |
# File 'lib/subspawn/posix.rb', line 170 def name(string) @argv[0] = string.to_s self end |
#pgroup(pid) ⇒ Object Also known as: pgroup=
243 244 245 246 247 |
# File 'lib/subspawn/posix.rb', line 243 def pgroup(pid) raise ArgumentError, "Invalid pgroup: #{pid}" if pid < 0 or !pid.is_a?(Integer) @pgroup = pid.to_i self end |
#pwd(path) ⇒ Object Also known as: cwd, chdir
230 231 232 233 |
# File 'lib/subspawn/posix.rb', line 230 def pwd(path) @cwd = path self end |
#rlimit(key, cur, max = nil) ⇒ Object Also known as: setrlimit
258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/subspawn/posix.rb', line 258 def rlimit(key, cur, max=nil) key = if key.is_a? Integer key.to_i else # TODO: these const lookup should have better error handling Process.const_get("RLIMIT_#{key.to_s.upcase}") end cur = ensure_rlimit(key, cur, 0) max = ensure_rlimit(key, max, 1) cur = max if cur > max @rlimits[key] = [cur, max] self end |
#sid! ⇒ Object
239 240 241 242 |
# File 'lib/subspawn/posix.rb', line 239 def sid! @sid = true self end |
#signal_default(sigmask = :default, add: [], delete: [], default: []) ⇒ Object Also known as: signal_default=
usage: signal_default = SigSet.empty.add(:usr1).delete(“USR2”) signal_default(:full, exclude: [9])
216 217 218 219 220 221 |
# File 'lib/subspawn/posix.rb', line 216 def signal_default(sigmask = :default, add: [], delete: [], default: []) sigmask = :empty if sigmask == :default @signal_default = sigmask.is_a?(Symbol) ? SigSet.send(sigmask) : sigmask @signal_default.add(add, default).delete(delete) self end |
#signal_mask(sigmask = :default, add: [], delete: [], block: [], allow: []) ⇒ Object Also known as: signal_mask=, sigmask=, sigmask
usage: signal_mask = SigSet.empty.add(:usr1).delete(“USR2”) signal_mask(:full, exclude: [9])
203 204 205 206 207 208 |
# File 'lib/subspawn/posix.rb', line 203 def signal_mask(sigmask = :default, add: [], delete: [], block: [], allow: []) sigmask = :empty if sigmask == :default @signal_mask = sigmask.is_a?(Symbol) ? SigSet.send(sigmask) : sigmask @signal_mask.add(add, allow).delete(delete, block) self end |
#spawn! ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/subspawn/posix.rb', line 62 def spawn! validate! sfa = LFP::SpawnFileActions.new sa = LFP::Spawnattr.new raise "Spawn Init Error" if 0 != sfa.init out_pid = nil begin raise "Spawn Init Error" if 0 != sa.init begin # set up file descriptors @fd_keeps.each {|fd| sfa.addkeep(fd_number(fd)) } @fd_opens.each {|opn| sfa.addopen(fd_number(opn.fd), opn.path, opn.flags, opn.mode) } @fd_map.map{|k, v| [k, fd_number(v)] }.each do |dest, src| sfa.adddup2(src, dest) end @fd_closes.each {|fd| sfa.addclose(fd_number(fd)) } unless @rlimits.empty? # allocate output (pid) FFI::MemoryPointer.new(LFP::Rlimit, @rlimits.length) do |rlimits| # build array @rlimits.each_with_index {|(key, (cur, max)), i| rlimit = LFP::Rlimit.new(rlimits[i]) #puts "building rlim at #{i} to #{[cur, max, key]}" rlimit[:rlim_cur] = cur.to_i rlimit[:rlim_max] = max.to_i rlimit[:resource] = key.to_i } sa.setrlimit(rlimits, @rlimits.length) end end # set up signals sa.sigmask = @signal_mask.to_ptr if @signal_mask sa.sigdefault = @signal_default.to_ptr if @signal_default # set up ownership and groups sa.pgroup = @pgroup.to_i if @pgroup sa.umask = @umask.to_i if @umask # Set up terminal control sa.setsid if @sid sa.ctty = @ctty if @ctty # set up working dir sa.cwd = @cwd if @cwd # allocate output (pid) FFI::MemoryPointer.new(:int, 1) do |pid| argv_str = @argv.map{|a| raise ArgumentError, "Nulls not allowed in command: #{a.inspect}" if a.include? "\0" FFI::MemoryPointer.from_string a } + [nil] # null end of argv FFI::MemoryPointer.new(:pointer, argv_str.length) do |argv_holder| # ARGV argv_holder.write_array_of_pointer argv_str # ARGP/ENV make_envp do |envp_holder| # Launch! ret = LFP.spawnp(pid, @path, argv_holder, envp_holder, sfa, sa) if ret != 0 raise SystemCallError.new("Spawn Error: #{ret}", LFP.errno) end out_pid = pid.read_int end end end ensure sa.destroy end ensure sfa.destroy end out_pid end |
#umask=(value) ⇒ Object Also known as: umask
224 225 226 227 |
# File 'lib/subspawn/posix.rb', line 224 def umask=(value) @umask = value.nil? ? nil : value.to_i self end |
#validate ⇒ Object
272 273 274 |
# File 'lib/subspawn/posix.rb', line 272 def validate validate! rescue false end |
#validate! ⇒ Object
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/subspawn/posix.rb', line 38 def validate! @argv.map!(&:to_str) # By spec raise SpawnError, "Invalid argv" unless @argv.length > 0 @fd_map = @fd_map.map do |number, source| raise SpawnError, "Invalid FD map: Not a number: #{number.inspect}" unless number.is_a? Integer [number, fd_check(source)] end.to_h @fd_keeps.each{|x| fd_check(x)} @fd_closes.each{|x| fd_check(x)} @fd_opens.each{|x| fd_check(x.fd) raise SpawnError, "Invalid FD open: Not a number: #{x.mode.inspect}" unless x.mode.is_a? Integer raise SpawnError, "Invalid FD open: Not a flag: #{x.flags.inspect}" unless x.flags.is_a? Integer raise SpawnError, "Invalid FD open: Not a file: #{x.file.inspect}" unless File.exist? x.path or Dir.exist?(File.dirname(x.path)) } raise SpawnError, "Invalid cwd path" unless @cwd.nil? or Dir.exist?(@cwd = ensure_file_string(@cwd)) @ctty = @ctty.path if !@ctty.nil? and @ctty.is_a? File # PTY.open returns files raise SpawnError, "Invalid controlling tty path" unless @ctty.nil? or File.exist?(@ctty = ensure_file_string(@ctty)) true end |