bwrap 1.0.0 → 1.1.1
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +16 -0
- data/lib/bwrap/args/args.rb +36 -1
- data/lib/bwrap/args/bind/library/ruby_binds.rb +45 -0
- data/lib/bwrap/args/bind/library.rb +25 -51
- data/lib/bwrap/args/bind/mime.rb +9 -2
- data/lib/bwrap/args/bind.rb +28 -16
- data/lib/bwrap/args/construct.rb +80 -42
- data/lib/bwrap/args/features/binds_base.rb +13 -0
- data/lib/bwrap/args/features/ruby_binds.rb +47 -0
- data/lib/bwrap/args/features.rb +11 -43
- data/lib/bwrap/args/library.rb +1 -3
- data/lib/bwrap/args/mount.rb +5 -5
- data/lib/bwrap/args/network.rb +43 -0
- data/lib/bwrap/bwrap.rb +9 -2
- data/lib/bwrap/config/features/base.rb +28 -0
- data/lib/bwrap/config/features/ruby.rb +86 -0
- data/lib/bwrap/config/features.rb +6 -78
- data/lib/bwrap/execution/exceptions.rb +12 -0
- data/lib/bwrap/execution/execute.rb +21 -3
- data/lib/bwrap/execution/execution.rb +26 -2
- data/lib/bwrap/execution/path.rb +3 -3
- data/lib/bwrap/execution/popen2e.rb +87 -0
- data/lib/bwrap/output/levels.rb +33 -3
- data/lib/bwrap/output/output_impl.rb +54 -0
- data/lib/bwrap/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +39 -4
- metadata.gz.sig +0 -0
data/lib/bwrap/args/features.rb
CHANGED
@@ -8,6 +8,12 @@ require_relative "library"
|
|
8
8
|
#
|
9
9
|
# @see Config::Features
|
10
10
|
class Bwrap::Args::Features < Hash
|
11
|
+
# Requires are here so there is no extra trickiness with namespaces.
|
12
|
+
#
|
13
|
+
# Feature implementations are not meant to be used outside of this class anyway.
|
14
|
+
require_relative "features/binds_base"
|
15
|
+
require_relative "features/ruby_binds"
|
16
|
+
|
11
17
|
include Bwrap::Output
|
12
18
|
|
13
19
|
# Implementation for Bash feature set.
|
@@ -45,44 +51,6 @@ class Bwrap::Args::Features < Hash
|
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
48
|
-
# Implementation for Ruby feature set.
|
49
|
-
#
|
50
|
-
# @api private
|
51
|
-
class RubyBinds
|
52
|
-
# Returns mounts needed by Ruby feature set.
|
53
|
-
attr_reader :mounts
|
54
|
-
|
55
|
-
# Bind system paths so scripts works inside sandbox.
|
56
|
-
def sitedir_mounts
|
57
|
-
mounts = []
|
58
|
-
mounts << "--ro-bind" << RbConfig::CONFIG["sitedir"] << RbConfig::CONFIG["sitedir"]
|
59
|
-
mounts << "--ro-bind" << RbConfig::CONFIG["rubyhdrdir"] << RbConfig::CONFIG["rubyhdrdir"]
|
60
|
-
mounts << "--ro-bind" << RbConfig::CONFIG["rubylibdir"] << RbConfig::CONFIG["rubylibdir"]
|
61
|
-
mounts << "--ro-bind" << RbConfig::CONFIG["vendordir"] << RbConfig::CONFIG["vendordir"]
|
62
|
-
|
63
|
-
mounts
|
64
|
-
end
|
65
|
-
|
66
|
-
# Create binds for required system libraries.
|
67
|
-
#
|
68
|
-
# These are in path like /usr/lib64/ruby/2.5.0/x86_64-linux-gnu/,
|
69
|
-
# and as they are mostly shared libraries, they may have some extra
|
70
|
-
# dependencies that also need to be bound inside the sandbox.
|
71
|
-
def stdlib_mounts stdlib
|
72
|
-
library_mounts = []
|
73
|
-
library = Bwrap::Args::Library.new
|
74
|
-
stdlib.each do |lib|
|
75
|
-
path = "#{RbConfig::CONFIG["rubyarchdir"]}/#{lib}.so"
|
76
|
-
|
77
|
-
library.needed_libraries(path).each do |requisite_library|
|
78
|
-
library_mounts << "--ro-bind" << requisite_library << requisite_library
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
library_mounts
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
54
|
# `Array` of parameters passed to bwrap.
|
87
55
|
attr_writer :args
|
88
56
|
|
@@ -104,7 +72,7 @@ class Bwrap::Args::Features < Hash
|
|
104
72
|
|
105
73
|
binds = BashBinds.new
|
106
74
|
|
107
|
-
@args.
|
75
|
+
@args.add :feature_binds, binds.bash_mounts
|
108
76
|
end
|
109
77
|
|
110
78
|
private def nscd_binds
|
@@ -112,7 +80,7 @@ class Bwrap::Args::Features < Hash
|
|
112
80
|
|
113
81
|
binds = NscdBinds.new
|
114
82
|
|
115
|
-
@args.
|
83
|
+
@args.add :feature_binds, binds.custom_binds
|
116
84
|
end
|
117
85
|
|
118
86
|
# @note This does not allow development headers needed for compilation for now.
|
@@ -120,9 +88,9 @@ class Bwrap::Args::Features < Hash
|
|
120
88
|
private def ruby_binds
|
121
89
|
return unless @config.features.ruby.enabled?
|
122
90
|
|
123
|
-
binds = RubyBinds.new
|
91
|
+
binds = RubyBinds.new @config
|
124
92
|
|
125
|
-
@args.
|
126
|
-
@args.
|
93
|
+
@args.add :feature_binds, binds.sitedir_mounts
|
94
|
+
@args.add :feature_binds, binds.stdlib_mounts(@config.features.ruby.stdlib)
|
127
95
|
end
|
128
96
|
end
|
data/lib/bwrap/args/library.rb
CHANGED
@@ -66,8 +66,6 @@ class Bwrap::Args::Library
|
|
66
66
|
@needed_libraries
|
67
67
|
end
|
68
68
|
|
69
|
-
# Used by {Bwrap::Args::Bind#libs_command_requires}.
|
70
|
-
#
|
71
69
|
# @param binary_paths [String, Array] one or more paths to be resolved
|
72
70
|
def needed_libraries binary_paths
|
73
71
|
trace "Finding libraries #{binary_paths} requires"
|
@@ -105,7 +103,7 @@ class Bwrap::Args::Library
|
|
105
103
|
libraries = libraries_line.split ","
|
106
104
|
|
107
105
|
(@needed_libraries & libraries).each do |library|
|
108
|
-
|
106
|
+
debug "Binding #{library} as dependency of #{binary_path}"
|
109
107
|
end
|
110
108
|
|
111
109
|
@needed_libraries |= libraries
|
data/lib/bwrap/args/mount.rb
CHANGED
@@ -9,25 +9,25 @@ module Bwrap::Args::Mount
|
|
9
9
|
return unless @config.root
|
10
10
|
|
11
11
|
debug "Binding #{@config.root} as /"
|
12
|
-
@args.
|
13
|
-
@args.
|
12
|
+
@args.add :root_mount, "--bind", @config.root, "/"
|
13
|
+
@args.add :root_mount, "--chdir", "/"
|
14
14
|
end
|
15
15
|
|
16
16
|
# Arguments for mounting devtmpfs to /dev.
|
17
17
|
private def dev_mount
|
18
18
|
debug "Mounting new devtmpfs to /dev"
|
19
|
-
@args.
|
19
|
+
@args.add :dev_mounts, "--dev", "/dev"
|
20
20
|
end
|
21
21
|
|
22
22
|
# Arguments for mounting procfs to /proc.
|
23
23
|
private def proc_mount
|
24
24
|
debug "Mounting new procfs to /proc"
|
25
|
-
@args.
|
25
|
+
@args.add :proc_mount, "--proc", "/proc"
|
26
26
|
end
|
27
27
|
|
28
28
|
# Arguments for mounting tmpfs to /tmp.
|
29
29
|
private def tmp_as_tmpfs
|
30
30
|
debug "Mounting tmpfs to /tmp"
|
31
|
-
@args.
|
31
|
+
@args.add :tmp_mount, "--tmpfs", "/tmp"
|
32
32
|
end
|
33
33
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/output"
|
4
|
+
require_relative "args"
|
5
|
+
|
6
|
+
# Network related binds.
|
7
|
+
class Bwrap::Args::Network
|
8
|
+
include Bwrap::Output
|
9
|
+
|
10
|
+
# Instance of {Config}.
|
11
|
+
attr_writer :config
|
12
|
+
|
13
|
+
# @param args [Bwrap::Args::Args] Arguments to be passed to bwrap.
|
14
|
+
def initialize args
|
15
|
+
@args = args
|
16
|
+
end
|
17
|
+
|
18
|
+
# Arguments to set hostname to whatever is configured.
|
19
|
+
def hostname
|
20
|
+
return unless @config.hostname
|
21
|
+
|
22
|
+
debug "Setting hostname to #{@config.hostname}"
|
23
|
+
@args.add :hostname, %W{ --hostname #{@config.hostname} }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Arguments to read-only bind /etc/resolv.conf.
|
27
|
+
def resolv_conf
|
28
|
+
# We can’t really bind symlinks, so let’s resolve real path to resolv.conf, in case it is symlinked.
|
29
|
+
source_resolv_conf = Pathname.new "/etc/resolv.conf"
|
30
|
+
source_resolv_conf = source_resolv_conf.realpath
|
31
|
+
|
32
|
+
debug "Binding #{source_resolv_conf} as /etc/resolv.conf"
|
33
|
+
@args.add :resolv_conf, %W{ --ro-bind #{source_resolv_conf} /etc/resolv.conf }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Arguments to allow network connection inside sandbox.
|
37
|
+
def share_net
|
38
|
+
return unless @config.share_net
|
39
|
+
|
40
|
+
verb "Sharing network"
|
41
|
+
@args.add :network, %w{ --share-net }
|
42
|
+
end
|
43
|
+
end
|
data/lib/bwrap/bwrap.rb
CHANGED
@@ -55,7 +55,9 @@ class Bwrap::Bwrap
|
|
55
55
|
construct = Bwrap::Args::Construct.new
|
56
56
|
construct.command = command
|
57
57
|
construct.config = @config
|
58
|
-
|
58
|
+
|
59
|
+
construct.calculate
|
60
|
+
bwrap_args = construct.bwrap_arguments
|
59
61
|
|
60
62
|
exec_command = [ "bwrap" ]
|
61
63
|
exec_command += bwrap_args
|
@@ -89,7 +91,9 @@ class Bwrap::Bwrap
|
|
89
91
|
construct = Bwrap::Args::Construct.new
|
90
92
|
construct.command = command
|
91
93
|
construct.config = config
|
92
|
-
|
94
|
+
|
95
|
+
construct.calculate
|
96
|
+
bwrap_args = construct.bwrap_arguments
|
93
97
|
|
94
98
|
exec_command = [ "bwrap" ]
|
95
99
|
exec_command += bwrap_args
|
@@ -134,6 +138,9 @@ class Bwrap::Bwrap
|
|
134
138
|
opt :trace,
|
135
139
|
"Show trace output (noisiest, probably not useful for most of time)",
|
136
140
|
short: :none
|
141
|
+
opt [ :quiet, :silent ],
|
142
|
+
"Hides notice messages. Warnings and errors are still shown.",
|
143
|
+
short: :none
|
137
144
|
opt :version,
|
138
145
|
"Print version and exit",
|
139
146
|
short: "V"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @abstract
|
4
|
+
#
|
5
|
+
# Base of all features.
|
6
|
+
class Bwrap::Config::Features::Base
|
7
|
+
# @param features [Bwrap::Config::Features] Instance of features object in {Config}
|
8
|
+
def initialize features
|
9
|
+
@features = features
|
10
|
+
end
|
11
|
+
|
12
|
+
# Checks if the feature has been enabled.
|
13
|
+
#
|
14
|
+
# @return [Boolean] whether feature is enabled
|
15
|
+
def enabled?
|
16
|
+
@enabled
|
17
|
+
end
|
18
|
+
|
19
|
+
# Enable the feature.
|
20
|
+
def enable
|
21
|
+
@enabled = true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Disable the feature.
|
25
|
+
def disable
|
26
|
+
@enabled = false
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/execution"
|
4
|
+
|
5
|
+
# Defines Ruby feature set.
|
6
|
+
#
|
7
|
+
# Implies {Nscd} feature.
|
8
|
+
class Bwrap::Config::Features::Ruby < Bwrap::Config::Features::Base
|
9
|
+
include Bwrap::Execution
|
10
|
+
|
11
|
+
def initialize features
|
12
|
+
super features
|
13
|
+
|
14
|
+
@gem_env_paths = true
|
15
|
+
@stdlib = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return true if bindirs from “gem environment” should be added to sandbox.
|
19
|
+
def gem_env_paths?
|
20
|
+
@gem_env_paths
|
21
|
+
end
|
22
|
+
|
23
|
+
# Enable Ruby feature set.
|
24
|
+
#
|
25
|
+
# Among others, binds `RbConfig::CONFIG["sitedir"]` so scripts works.
|
26
|
+
#
|
27
|
+
# @note This does not allow development headers needed for compilation for now.
|
28
|
+
# I’ll look at it after I have an use for it.
|
29
|
+
#
|
30
|
+
# @note Also enables {Nscd} feature.
|
31
|
+
def enable
|
32
|
+
super
|
33
|
+
|
34
|
+
@features.nscd.enable
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Pathname|nil] path to Ruby interpreter.
|
38
|
+
def interpreter
|
39
|
+
@features.mime&.executable_path
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Hash(String, String)] RbConfig::CONFIG from interpreter returned by {#interpreter}
|
43
|
+
def ruby_config
|
44
|
+
unless interpreter
|
45
|
+
raise "Interpreter is not set, so ruby_config() can’t be called yet."
|
46
|
+
end
|
47
|
+
|
48
|
+
return @ruby_config if @ruby_config
|
49
|
+
|
50
|
+
script = %q{RbConfig::CONFIG.each { |k, v| puts "#{k}=#{v}" }}
|
51
|
+
config_string = execvalue %W{ #{interpreter} -e #{script} }
|
52
|
+
|
53
|
+
ruby_config = {}
|
54
|
+
config_string.split("\n").each do |line|
|
55
|
+
key, value = line.split "=", 2
|
56
|
+
ruby_config[key] = value
|
57
|
+
end
|
58
|
+
|
59
|
+
@ruby_config = ruby_config
|
60
|
+
end
|
61
|
+
|
62
|
+
# Extra libraries to be loaded from script’s interpreter’s `RbConfig::CONFIG["rubyarchdir"]`.
|
63
|
+
#
|
64
|
+
# @note Existence of library paths are checked here, and not in {#stdlib=},
|
65
|
+
# to avoid error about missing interpreter, as it is set in {Bwrap::Bwrap#run},
|
66
|
+
# and config is meant to be created beforehand.
|
67
|
+
#
|
68
|
+
# @note This is only required to be called if extra dependencies are necessary.
|
69
|
+
# For example, psych.so requires libyaml.so.
|
70
|
+
#
|
71
|
+
# @return [Array] list of needed libraries.
|
72
|
+
def stdlib
|
73
|
+
# Just a little check to have error earlier.
|
74
|
+
@stdlib.each do |lib|
|
75
|
+
unless File.exist? "#{ruby_config["rubyarchdir"]}/#{lib}.so"
|
76
|
+
raise "Library “#{lib}” passed to Bwrap::Config::Ruby.stdlib= does not exist."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
@stdlib
|
81
|
+
end
|
82
|
+
|
83
|
+
def stdlib= libs
|
84
|
+
@stdlib = libs
|
85
|
+
end
|
86
|
+
end
|
@@ -3,32 +3,14 @@
|
|
3
3
|
class Bwrap::Config
|
4
4
|
# Methods to enable or disable feature sets to control various aspects of sandboxing.
|
5
5
|
class Features
|
6
|
-
#
|
6
|
+
# Requires are here so there is no extra trickiness with namespaces.
|
7
7
|
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
def initialize features
|
12
|
-
@features = features
|
13
|
-
end
|
8
|
+
# Feature implementations are not meant to be used outside of this class anyway.
|
9
|
+
require_relative "features/base"
|
10
|
+
require_relative "features/ruby"
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
# @return [Boolean] whether feature is enabled
|
18
|
-
def enabled?
|
19
|
-
@enabled
|
20
|
-
end
|
21
|
-
|
22
|
-
# Enable the feature.
|
23
|
-
def enable
|
24
|
-
@enabled = true
|
25
|
-
end
|
26
|
-
|
27
|
-
# Disable the feature.
|
28
|
-
def disable
|
29
|
-
@enabled = false
|
30
|
-
end
|
31
|
-
end
|
12
|
+
# Instance of {Bwrap::Args::Bind::Mime}.
|
13
|
+
attr_accessor :mime
|
32
14
|
|
33
15
|
# Defines Bash feature set.
|
34
16
|
class Bash < Base
|
@@ -44,60 +26,6 @@ class Bwrap::Config
|
|
44
26
|
# Nya.
|
45
27
|
end
|
46
28
|
|
47
|
-
# Defines Ruby feature set.
|
48
|
-
#
|
49
|
-
# Implies {Nscd} feature.
|
50
|
-
class Ruby < Base
|
51
|
-
# Extra libraries to be loaded from `RbConfig::CONFIG["rubyarchdir"]`.
|
52
|
-
#
|
53
|
-
# @note This is only required to be called if extra dependencies are necessary.
|
54
|
-
# For example, psych.so requires libyaml.so.
|
55
|
-
#
|
56
|
-
# @return [Array] list of needed libraries.
|
57
|
-
#
|
58
|
-
# @overload stdlib
|
59
|
-
# @overload stdlib=(libs)
|
60
|
-
attr_reader :stdlib
|
61
|
-
|
62
|
-
def initialize features
|
63
|
-
super features
|
64
|
-
|
65
|
-
@gem_env_paths = true
|
66
|
-
@stdlib = []
|
67
|
-
end
|
68
|
-
|
69
|
-
# @return true if bindirs from “gem environment” should be added to sandbox.
|
70
|
-
def gem_env_paths?
|
71
|
-
@gem_env_paths
|
72
|
-
end
|
73
|
-
|
74
|
-
# Enable Ruby feature set.
|
75
|
-
#
|
76
|
-
# Among others, binds `RbConfig::CONFIG["sitedir"]` so scripts works.
|
77
|
-
#
|
78
|
-
# @note This does not allow development headers needed for compilation for now.
|
79
|
-
# I’ll look at it after I have an use for it.
|
80
|
-
#
|
81
|
-
# @note Also enables {Nscd} feature.
|
82
|
-
def enable
|
83
|
-
super
|
84
|
-
|
85
|
-
@features.nscd.enable
|
86
|
-
end
|
87
|
-
|
88
|
-
# @see #stdlib
|
89
|
-
def stdlib= libs
|
90
|
-
# Just a little check to have error earlier.
|
91
|
-
libs.each do |lib|
|
92
|
-
unless File.exist? "#{RbConfig::CONFIG["rubyarchdir"]}/#{lib}.so"
|
93
|
-
raise "Library “#{lib}” passed to Bwrap::Config::Ruby.stdlib= does not exist."
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
@stdlib = libs
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
29
|
# @return [Bash] Instance of feature class for Bash
|
102
30
|
def bash
|
103
31
|
@bash ||= Bash.new self
|
@@ -7,6 +7,18 @@ module Bwrap::Execution
|
|
7
7
|
|
8
8
|
# Signifies that command execution has failed.
|
9
9
|
class ExecutionFailed < CommandError
|
10
|
+
# The command that was executed.
|
11
|
+
attr_reader :command
|
12
|
+
|
13
|
+
# Output of the command.
|
14
|
+
attr_reader :output
|
15
|
+
|
16
|
+
def initialize msg, command:, output:
|
17
|
+
@command = command
|
18
|
+
@output = output
|
19
|
+
|
20
|
+
super msg
|
21
|
+
end
|
10
22
|
end
|
11
23
|
|
12
24
|
# Thrown if given command was not found.
|
@@ -40,6 +40,13 @@ class Bwrap::Execution::Execute
|
|
40
40
|
|
41
41
|
# @return formatted command.
|
42
42
|
def self.format_command command, rootcmd:
|
43
|
+
# Flatten the command if required, so nils can be converted in more of cases.
|
44
|
+
# Flattenization is also done in executions, but they also take in account
|
45
|
+
# for example rootcmd, so they probably should be done in addition to this one.
|
46
|
+
if command.respond_to? :flatten!
|
47
|
+
command.flatten!
|
48
|
+
end
|
49
|
+
|
43
50
|
replace_nils command
|
44
51
|
return command if rootcmd.nil?
|
45
52
|
|
@@ -63,13 +70,17 @@ class Bwrap::Execution::Execute
|
|
63
70
|
end
|
64
71
|
|
65
72
|
# Checks whether execution failed and acts accordingly.
|
66
|
-
def self.handle_execution_fail fail:, error:, output:
|
67
|
-
return unless fail and
|
73
|
+
def self.handle_execution_fail fail:, error:, output:, command:
|
74
|
+
return unless fail and !execution_success?
|
68
75
|
|
69
76
|
if error == :show and !output.empty?
|
70
77
|
Bwrap::Output.warn_output "Command failed with output:\n“#{output}”"
|
71
78
|
end
|
72
|
-
|
79
|
+
|
80
|
+
exception = Bwrap::Execution::ExecutionFailed.new "Command execution failed",
|
81
|
+
command: command,
|
82
|
+
output: output
|
83
|
+
raise exception, caller
|
73
84
|
end
|
74
85
|
|
75
86
|
# @note It makes sense for caller to just return if wait has been set and not check output.
|
@@ -101,6 +112,13 @@ class Bwrap::Execution::Execute
|
|
101
112
|
"to add “self.prepend_rootcmd(command, rootcmd:)” method."
|
102
113
|
end
|
103
114
|
|
115
|
+
# A wrapper to get status of an execution.
|
116
|
+
#
|
117
|
+
# Mainly here so test implementation is easier.
|
118
|
+
private_class_method def self.execution_success?
|
119
|
+
$CHILD_STATUS.success?
|
120
|
+
end
|
121
|
+
|
104
122
|
# Used by `#handle_logging`.
|
105
123
|
private_class_method def self.calculate_log_command command
|
106
124
|
return command.dup unless command.respond_to?(:join)
|
@@ -5,6 +5,7 @@ require "bwrap/output"
|
|
5
5
|
require_relative "exceptions"
|
6
6
|
require_relative "execute"
|
7
7
|
require_relative "path"
|
8
|
+
require_relative "popen2e"
|
8
9
|
|
9
10
|
# Provides methods to execute commands and handle its output.
|
10
11
|
#
|
@@ -61,7 +62,7 @@ module Bwrap::Execution
|
|
61
62
|
end
|
62
63
|
|
63
64
|
# If command is string, splat operator (the *) does not do anything. If array, it expand the arguments.
|
64
|
-
# This causes spawning work correctly, as that’s how spawn() expects to have the
|
65
|
+
# This causes spawning work correctly, as that’s how spawn() expects to have the arguments.
|
65
66
|
pid = spawn(env, *command, err: [ :child, :out ], out: Execute.w, unsetenv_others: clear_env)
|
66
67
|
output = Execute.finish_execution(log: log, wait: wait, direct_output: direct_output)
|
67
68
|
return pid unless wait
|
@@ -71,12 +72,35 @@ module Bwrap::Execution
|
|
71
72
|
@last_status = $CHILD_STATUS
|
72
73
|
|
73
74
|
output = Execute.process_output output: output
|
74
|
-
Execute.handle_execution_fail fail: fail, error: error, output: output
|
75
|
+
Execute.handle_execution_fail fail: fail, error: error, output: output, command: command
|
75
76
|
output
|
76
77
|
ensure
|
77
78
|
Execute.clean_variables
|
78
79
|
end
|
79
80
|
|
81
|
+
# Works similarly to {Open3.popen2e}.
|
82
|
+
#
|
83
|
+
# TODO: If there will be any difference to input syntax, document those differences here.
|
84
|
+
# For now, rootcmd option has been implemented.
|
85
|
+
#
|
86
|
+
# A block is accepted, as does {Open3.popen2e}. For now, at least.
|
87
|
+
#
|
88
|
+
# TODO: Implement this so that this uses same execution things as other things here.
|
89
|
+
# This way bwrap actually can be integrated to this...
|
90
|
+
#
|
91
|
+
# TODO: Verify default log_callback is correct
|
92
|
+
#
|
93
|
+
# @warning Only array style commands are accepted. For example, `ls /`
|
94
|
+
# is not ok, but `ls` or `%w{ ls / }` is ok.
|
95
|
+
def popen2e *cmd, rootcmd: nil, log_callback: 1, log: true, &block
|
96
|
+
popen = Bwrap::Execution::Popen2e.new
|
97
|
+
popen.dry_run = @dry_run
|
98
|
+
popen.popen2e(*cmd, rootcmd: rootcmd, log_callback: log_callback, log: log, &block)
|
99
|
+
ensure
|
100
|
+
@last_status = $CHILD_STATUS
|
101
|
+
end
|
102
|
+
module_function :popen2e
|
103
|
+
|
80
104
|
# Returns Process::Status instance of last execution.
|
81
105
|
#
|
82
106
|
# @note This only is able to return the status if wait is true, as otherwise caller is assumed to
|
data/lib/bwrap/execution/path.rb
CHANGED
@@ -20,7 +20,7 @@ module Bwrap::Execution::Path
|
|
20
20
|
#
|
21
21
|
# @yield Command appended to each path in PATH environment variable
|
22
22
|
# @yieldparam path [String] Full path to executable
|
23
|
-
def self.each_env_path command, env_path_var: ENV
|
23
|
+
def self.each_env_path command, env_path_var: ENV.fetch("PATH", nil)
|
24
24
|
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
|
25
25
|
|
26
26
|
env_path_var.split(File::PATH_SEPARATOR).each do |env_path|
|
@@ -39,7 +39,7 @@ module Bwrap::Execution::Path
|
|
39
39
|
# @param command [String] executable to be resolved
|
40
40
|
# @param env_path_var [String] PATH environment variable as string.
|
41
41
|
# Defaults to `ENV["PATH"]`
|
42
|
-
private def command_available? command, env_path_var: ENV
|
42
|
+
private def command_available? command, env_path_var: ENV.fetch("PATH", nil)
|
43
43
|
# Special handling for absolute paths.
|
44
44
|
path = Pathname.new command
|
45
45
|
if path.absolute?
|
@@ -60,7 +60,7 @@ module Bwrap::Execution::Path
|
|
60
60
|
# Returns path to given executable.
|
61
61
|
#
|
62
62
|
# @param (see #command_available?)
|
63
|
-
private def which command, fail: true, env_path_var: ENV
|
63
|
+
private def which command, fail: true, env_path_var: ENV.fetch("PATH", nil)
|
64
64
|
# Special handling for absolute paths.
|
65
65
|
path = Pathname.new command
|
66
66
|
if path.absolute?
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @see {Bwrap::Execution.popen2e}
|
4
|
+
class Bwrap::Execution::Popen2e
|
5
|
+
attr_writer :dry_run
|
6
|
+
|
7
|
+
# @see {Bwrap::Execution.popen2e}
|
8
|
+
# TODO: Add a test for this (does Ruby have anything I could use here?).
|
9
|
+
#
|
10
|
+
# @note Also options accepted by {Open3.popen2e} are accepted
|
11
|
+
# here, in addition to those specified here.
|
12
|
+
def popen2e *cmd, rootcmd: nil, log_callback: 1, log: true, &block
|
13
|
+
@rootcmd = rootcmd
|
14
|
+
@log_callback = log_callback + 1 # Passing to another method, so need to add one more.
|
15
|
+
@log = log
|
16
|
+
self.opts_from_input = cmd
|
17
|
+
|
18
|
+
resolve_actual_command cmd
|
19
|
+
|
20
|
+
return if @dry_run || Bwrap::Execution::Execute.dry_run
|
21
|
+
|
22
|
+
open_pipes
|
23
|
+
popen_run(@actual_command, [ @in_read, @out_write ], [ @in_write, @out_read ], &block)
|
24
|
+
ensure
|
25
|
+
if block
|
26
|
+
@in_read.close
|
27
|
+
@in_write.close
|
28
|
+
@out_read.close
|
29
|
+
@out_write.close
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets @opts from `cmd` given to {#popen2e}, if any extra options have been given.
|
34
|
+
private def opts_from_input= cmd
|
35
|
+
@opts = cmd.last.is_a?(Hash) && cmd.pop.dup || {}
|
36
|
+
end
|
37
|
+
|
38
|
+
private def open_pipes
|
39
|
+
@in_read, @in_write = IO.pipe
|
40
|
+
@opts[:in] = @in_read
|
41
|
+
@in_write.sync = true
|
42
|
+
|
43
|
+
@out_read, @out_write = IO.pipe
|
44
|
+
@opts[[ :out, :err ]] = @out_write
|
45
|
+
end
|
46
|
+
|
47
|
+
# First element may be optional environment variables.
|
48
|
+
private def resolve_actual_command cmd
|
49
|
+
temp_cmd = cmd.dup
|
50
|
+
|
51
|
+
# Delete environment hash.
|
52
|
+
env_hash = temp_cmd.shift if temp_cmd.first.is_a? Hash
|
53
|
+
|
54
|
+
# Delete option hash.
|
55
|
+
option_hash = temp_cmd.pop if temp_cmd.last.is_a? Hash
|
56
|
+
|
57
|
+
temp_cmd = Bwrap::Execution::Execute.format_command temp_cmd, rootcmd: @rootcmd
|
58
|
+
Bwrap::Execution::Execute.handle_logging temp_cmd, log_callback: @log_callback, log: @log, dry_run: @dry_run
|
59
|
+
|
60
|
+
temp_cmd.unshift env_hash if env_hash
|
61
|
+
temp_cmd.push option_hash if option_hash
|
62
|
+
|
63
|
+
# If command is an array, there can’t be arrays inside the array (hashes are preserved).
|
64
|
+
# For convenience, the array is flattened here, so callers can construct commands more easily.
|
65
|
+
temp_cmd.flatten!
|
66
|
+
|
67
|
+
@actual_command = temp_cmd
|
68
|
+
end
|
69
|
+
|
70
|
+
private def popen_run cmd, child_io, parent_io
|
71
|
+
pid = spawn(*cmd, @opts)
|
72
|
+
wait_thr = Process.detach(pid)
|
73
|
+
child_io.each(&:close)
|
74
|
+
result = [ *parent_io, wait_thr ]
|
75
|
+
|
76
|
+
if defined? yield
|
77
|
+
begin
|
78
|
+
return yield(*result)
|
79
|
+
ensure
|
80
|
+
parent_io.each(&:close)
|
81
|
+
wait_thr.join
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
result
|
86
|
+
end
|
87
|
+
end
|