bwrap 1.0.0.pre.alpha2 → 1.0.0.pre.beta1
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 +34 -0
- data/README.md +2 -0
- data/lib/bwrap/args/args.rb +5 -1
- data/lib/bwrap/args/bind/library.rb +125 -0
- data/lib/bwrap/args/bind/mime.rb +58 -0
- data/lib/bwrap/args/bind.rb +84 -28
- data/lib/bwrap/args/construct.rb +69 -35
- data/lib/bwrap/args/environment.rb +32 -1
- data/lib/bwrap/args/features.rb +103 -0
- data/lib/bwrap/args/library.rb +137 -0
- data/lib/bwrap/args/machine_id.rb +3 -2
- data/lib/bwrap/args/mount.rb +11 -3
- data/lib/bwrap/bwrap.rb +150 -0
- data/lib/bwrap/config/features.rb +81 -0
- data/lib/bwrap/config.rb +116 -16
- data/lib/bwrap/execution/exceptions.rb +24 -0
- data/lib/bwrap/execution/execute.rb +5 -2
- data/lib/bwrap/execution/execution.rb +147 -3
- data/lib/bwrap/execution/labels.rb +8 -1
- data/lib/bwrap/execution/path.rb +84 -0
- data/lib/bwrap/execution.rb +5 -173
- data/lib/bwrap/output/colors.rb +0 -2
- data/lib/bwrap/output/log.rb +4 -6
- data/lib/bwrap/output/output_impl.rb +181 -0
- data/lib/bwrap/output.rb +7 -149
- data/lib/bwrap/version.rb +1 -2
- data/lib/bwrap.rb +25 -71
- data.tar.gz.sig +0 -0
- metadata +17 -21
- metadata.gz.sig +0 -0
- data/lib/bwrap/output/output.rb +0 -8
data/lib/bwrap/config.rb
CHANGED
@@ -3,10 +3,20 @@
|
|
3
3
|
require "bwrap/output"
|
4
4
|
require "bwrap/version"
|
5
5
|
|
6
|
-
#
|
6
|
+
# Features classes, used through {Config#features}.
|
7
|
+
require_relative "config/features"
|
8
|
+
|
9
|
+
# Represents configuration used to tailor bwrap execution.
|
10
|
+
#
|
11
|
+
# @developer_note
|
12
|
+
# Logger methods (debug, warn and so on) can’t be used here as logger
|
13
|
+
# isn’t initialized before this class.
|
14
|
+
#
|
15
|
+
# For same reason, it’s not advised to execute anything here.
|
7
16
|
#
|
8
|
-
#
|
9
|
-
#
|
17
|
+
# Note that all attributes also have writers, even though they are not documented.
|
18
|
+
#
|
19
|
+
# @todo Add some documentation about syntax where necessary, like for #binaries_from.
|
10
20
|
class Bwrap::Config
|
11
21
|
attr_accessor :hostname
|
12
22
|
|
@@ -24,6 +34,9 @@ class Bwrap::Config
|
|
24
34
|
# Given file as bound as /etc/machine_id.
|
25
35
|
attr_accessor :machine_id
|
26
36
|
|
37
|
+
# Name of the user inside chroot.
|
38
|
+
#
|
39
|
+
# This is optional and defaults to no user.
|
27
40
|
attr_accessor :user
|
28
41
|
|
29
42
|
# Set to true to indicate we’re running a X.org application, meaning we need to do some extra holes,
|
@@ -32,22 +45,87 @@ class Bwrap::Config
|
|
32
45
|
# @return [Boolean] Whether Xorg specific binds are used.
|
33
46
|
attr_accessor :xorg_application
|
34
47
|
|
48
|
+
# Array of audio schemes usable inside chroot.
|
49
|
+
#
|
50
|
+
# Currently supports:
|
51
|
+
# - :pulseaudio
|
52
|
+
#
|
53
|
+
attr_accessor :audio
|
54
|
+
|
55
|
+
# @return [Boolean] true if network should be shared from host.
|
56
|
+
attr_accessor :share_net
|
57
|
+
|
58
|
+
# Causes libraries required by the executable given to {Bwrap#run} to be
|
59
|
+
# mounted inside sandbox.
|
60
|
+
#
|
61
|
+
# Often it is enough to use this flag instead of binding all system libraries
|
62
|
+
# using {#libdir_mounts=}
|
63
|
+
#
|
64
|
+
# @return [Boolean] true if Linux library loaders are mounted inside chroot
|
65
|
+
attr_accessor :full_system_mounts
|
66
|
+
|
67
|
+
# Set to true if basic system directories, like /usr/lib and /usr/lib64,
|
68
|
+
# should be bound inside chroot.
|
69
|
+
#
|
70
|
+
# /usr/bin can be mounted using {Config#binaries_from=}.
|
71
|
+
#
|
72
|
+
# Often it is enough to use {#full_system_mounts=} instead of binding all
|
73
|
+
# system libraries using this flag.
|
74
|
+
#
|
75
|
+
# @return [Boolean] true if libdirs are mounted to the chroot
|
76
|
+
attr_accessor :libdir_mounts
|
77
|
+
|
78
|
+
# Set to `true` if command given to {Bwrap::Bwrap#run} is expected to
|
79
|
+
# be inside sandbox, and not bound from host.
|
80
|
+
#
|
81
|
+
# @return [Boolean] `true` if executed command is inside sandbox
|
82
|
+
attr_accessor :command_inside_root
|
83
|
+
|
84
|
+
attr_accessor :extra_executables
|
85
|
+
|
35
86
|
# Array of directories to be bind mounted and used to construct PATH environment variable.
|
36
87
|
attr_reader :binaries_from
|
37
88
|
|
89
|
+
# TODO: Document this.
|
90
|
+
# TODO: I wonder if this should just be removed. I don’t know, this is a bit ...
|
91
|
+
# Well, I can see it can have some merit, but very hard to say.
|
38
92
|
attr_reader :sandbox_directory
|
39
93
|
|
40
|
-
#
|
94
|
+
# Use given directory as root. End result is similar to classic chroot.
|
95
|
+
attr_reader :root
|
96
|
+
|
97
|
+
# @overload ro_binds
|
98
|
+
# `Hash`[`Pathname`] => `Pathname` containing custom read-only binds.
|
99
|
+
# @overload ro_binds=
|
100
|
+
# Set given hash of paths to be bound with --ro-bind.
|
101
|
+
#
|
102
|
+
# Key is source path, value is destination path.
|
103
|
+
#
|
104
|
+
# Given source paths must exist.
|
41
105
|
attr_reader :ro_binds
|
42
106
|
|
43
|
-
#
|
107
|
+
# @overload tmpdir
|
108
|
+
# Path to temporary directory.
|
109
|
+
#
|
110
|
+
# Defaults to Dir.tmpdir.
|
111
|
+
# @overload tmpdir=(dir)
|
112
|
+
# Sets given directory as temporary directory for certain operations.
|
44
113
|
#
|
45
|
-
#
|
114
|
+
# @note Requires `dir` to be path to existing directory.
|
115
|
+
# @raise [RuntimeError] If given directory does not exist
|
116
|
+
# @param dir Path to temporary directory
|
46
117
|
attr_reader :tmpdir
|
47
118
|
|
119
|
+
# Paths to be added to sandbox instance’s PATH environment variable.
|
120
|
+
#
|
121
|
+
# @see #add_env_path
|
122
|
+
attr_reader :env_paths
|
123
|
+
|
48
124
|
def initialize
|
49
125
|
@binaries_from = []
|
50
126
|
@tmpdir = Dir.tmpdir
|
127
|
+
@audio = []
|
128
|
+
@env_paths = []
|
51
129
|
end
|
52
130
|
|
53
131
|
def binaries_from= array
|
@@ -61,6 +139,17 @@ class Bwrap::Config
|
|
61
139
|
end
|
62
140
|
end
|
63
141
|
|
142
|
+
# Enable or disable feature sets to control various aspects of sandboxing.
|
143
|
+
#
|
144
|
+
# @example To enable Ruby feature set
|
145
|
+
# @config.features.ruby = true
|
146
|
+
#
|
147
|
+
# @see {Features} List of available features
|
148
|
+
# @return [Features] Object used to toggle features
|
149
|
+
def features
|
150
|
+
@features ||= ::Bwrap::Config::Features.new
|
151
|
+
end
|
152
|
+
|
64
153
|
def sandbox_directory= directory
|
65
154
|
unless Dir.exist? directory
|
66
155
|
raise "Given sandbox directory #{directory} does not exist. Please create it beforehand and setup to your needs."
|
@@ -69,14 +158,22 @@ class Bwrap::Config
|
|
69
158
|
@sandbox_directory = directory
|
70
159
|
end
|
71
160
|
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
161
|
+
# Directory used as writable root, akin to classic chroot.
|
162
|
+
def root= directory
|
163
|
+
unless Dir.exist? directory
|
164
|
+
raise "Given root directory #{directory} does not exist. Please create it beforehand and set up to your needs."
|
165
|
+
end
|
166
|
+
|
167
|
+
@root = directory
|
168
|
+
end
|
169
|
+
|
77
170
|
def ro_binds= binds
|
78
171
|
@ro_binds = {}
|
79
172
|
binds.each do |source_path, destination_path|
|
173
|
+
if destination_path.nil?
|
174
|
+
raise "binds should be key-value storage of Strings, for example a Hash."
|
175
|
+
end
|
176
|
+
|
80
177
|
source_path = Pathname.new source_path
|
81
178
|
unless source_path.exist?
|
82
179
|
raise "Given read only bind does not exist. Please check path “#{source_path}” is correct."
|
@@ -88,14 +185,17 @@ class Bwrap::Config
|
|
88
185
|
end
|
89
186
|
end
|
90
187
|
|
91
|
-
#
|
92
|
-
#
|
93
|
-
# @note Requires `dir` to be path to existing directory.
|
94
|
-
# @raise [RuntimeError] If given directory does not exist
|
95
|
-
# @param dir Path to temporary directory
|
188
|
+
# See attr_accessor :tmpdir for documentation.
|
96
189
|
def tmpdir= dir
|
97
190
|
raise "Directory to be set as a temporary directory, “#{dir}”, does not exist." unless Dir.exist? dir
|
98
191
|
|
99
192
|
@tmpdir = dir
|
100
193
|
end
|
194
|
+
|
195
|
+
# Add a path to sandbox instance’s PATH environment variable.
|
196
|
+
#
|
197
|
+
# @param path [String] Path to be added added to PATH environment variable
|
198
|
+
def add_env_path path
|
199
|
+
@env_paths << path
|
200
|
+
end
|
101
201
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bwrap::Execution
|
4
|
+
# Unspecified execution related error.
|
5
|
+
class CommandError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Signifies that command execution has failed.
|
9
|
+
class ExecutionFailed < CommandError
|
10
|
+
end
|
11
|
+
|
12
|
+
# Thrown if given command was not found.
|
13
|
+
class CommandNotFound < CommandError
|
14
|
+
# Command that was looked at.
|
15
|
+
attr_reader :command
|
16
|
+
|
17
|
+
def initialize command:
|
18
|
+
@command = command
|
19
|
+
msg = "Failed to find #{command} from PATH."
|
20
|
+
|
21
|
+
super msg
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -3,11 +3,14 @@
|
|
3
3
|
# force_encoding modifies string, so can’t freeze strings.
|
4
4
|
|
5
5
|
require "bwrap/output"
|
6
|
+
require_relative "exceptions"
|
6
7
|
require_relative "execution"
|
7
8
|
|
8
9
|
# Methods that performs actual execution logic.
|
9
10
|
#
|
10
|
-
# @
|
11
|
+
# @note This is kind of pseudo-internal API. Hopefully there won’t be breaking changes.
|
12
|
+
# But please use {Bwrap::Execution} instead of relying functionality of this class,
|
13
|
+
# outside of {.prepend_rootcmd}.
|
11
14
|
class Bwrap::Execution::Execute
|
12
15
|
include Bwrap::Output
|
13
16
|
|
@@ -95,7 +98,7 @@ class Bwrap::Execution::Execute
|
|
95
98
|
# Stub to instruct implementation in subclass.
|
96
99
|
def self.prepend_rootcmd command, rootcmd:
|
97
100
|
raise NotImplementedError, "If rootcmd execution is necessary, monkey patch Bwrap::Execution::Execute " \
|
98
|
-
|
101
|
+
"to add “self.prepend_rootcmd(command, rootcmd:)” method."
|
99
102
|
end
|
100
103
|
|
101
104
|
# Used by `#handle_logging`.
|
@@ -1,8 +1,152 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bwrap/version"
|
4
|
+
require "bwrap/output"
|
5
|
+
require_relative "exceptions"
|
6
|
+
require_relative "execute"
|
7
|
+
require_relative "path"
|
4
8
|
|
5
|
-
#
|
9
|
+
# Provides methods to execute commands and handle its output.
|
10
|
+
#
|
11
|
+
# Output can be controlled by using log levels of Output module.
|
12
|
+
#
|
13
|
+
# @example Executing a command
|
14
|
+
# class MyClass
|
15
|
+
# include Bwrap::Execution
|
16
|
+
#
|
17
|
+
# def my_method
|
18
|
+
# execute %w{ ls /dev/null }
|
6
19
|
module Bwrap::Execution
|
7
|
-
|
8
|
-
|
20
|
+
include Bwrap::Output
|
21
|
+
include Bwrap::Execution::Path
|
22
|
+
|
23
|
+
# Actual implementation of execution command. Can be used when static method is needed.
|
24
|
+
#
|
25
|
+
# @note When an array is given as a command, empty strings are passed as empty arguments.
|
26
|
+
#
|
27
|
+
# This means that `[ "foo", "bar" ]` passes one argument to "foo" command, when
|
28
|
+
# `[ "foo", "", "bar" ]` passes two arguments.
|
29
|
+
#
|
30
|
+
# This may or may not be what is assumed, so it can’t be fixed here. It is up to the
|
31
|
+
# command to decide how to handle empty arguments.
|
32
|
+
#
|
33
|
+
# Returns pid of executed command if wait is false.
|
34
|
+
# Returns command output if wait is true.
|
35
|
+
#
|
36
|
+
# fail == If true, an error is raised in case the command returns failure code.
|
37
|
+
#
|
38
|
+
# @param error if :show, warn()s output of the command is shown if execution failed.
|
39
|
+
#
|
40
|
+
# @see #execute
|
41
|
+
def self.do_execute command,
|
42
|
+
fail: true,
|
43
|
+
wait: true,
|
44
|
+
log: true,
|
45
|
+
direct_output: false,
|
46
|
+
env: {},
|
47
|
+
clear_env: false,
|
48
|
+
error: nil,
|
49
|
+
log_callback: 2,
|
50
|
+
rootcmd: nil
|
51
|
+
command = Execute.format_command command, rootcmd: rootcmd
|
52
|
+
Execute.handle_logging command, log_callback: log_callback, log: log, dry_run: @dry_run
|
53
|
+
return if @dry_run || Bwrap::Execution::Execute.dry_run
|
54
|
+
|
55
|
+
Execute.open_pipes direct_output
|
56
|
+
|
57
|
+
# If command is an array, there can’t be arrays inside the array.
|
58
|
+
# For convenience, the array is flattened here, so callers can construct commands more easily.
|
59
|
+
if command.respond_to? :flatten!
|
60
|
+
command.flatten!
|
61
|
+
end
|
62
|
+
|
63
|
+
# 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 argu
|
65
|
+
pid = spawn(env, *command, err: [ :child, :out ], out: Execute.w, unsetenv_others: clear_env)
|
66
|
+
output = Execute.finish_execution(log: log, wait: wait, direct_output: direct_output)
|
67
|
+
return pid unless wait
|
68
|
+
|
69
|
+
# This is instant return, but allows us to have $?/$CHILD_STATUS set.
|
70
|
+
Process.wait pid
|
71
|
+
@last_status = $CHILD_STATUS
|
72
|
+
|
73
|
+
output = Execute.process_output output: output
|
74
|
+
Execute.handle_execution_fail fail: fail, error: error, output: output
|
75
|
+
output
|
76
|
+
ensure
|
77
|
+
Execute.clean_variables
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns Process::Status instance of last execution.
|
81
|
+
#
|
82
|
+
# @note This only is able to return the status if wait is true, as otherwise caller is assumed to
|
83
|
+
# handle execution flow.
|
84
|
+
def self.last_status
|
85
|
+
@last_status
|
86
|
+
end
|
87
|
+
|
88
|
+
# Execute a command.
|
89
|
+
#
|
90
|
+
# This method can be used by including Execution module in a class that should be able to
|
91
|
+
# execute commands.
|
92
|
+
#
|
93
|
+
# @see .do_execute .do_execute for documentation of argument syntax
|
94
|
+
private def execute *args
|
95
|
+
# Mangle proper location to error message.
|
96
|
+
if args.last.is_a? Hash
|
97
|
+
args.last[:log_callback] = 3
|
98
|
+
else
|
99
|
+
args << { log_callback: 3 }
|
100
|
+
end
|
101
|
+
Bwrap::Execution.do_execute(*args)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Same as ::execute, but uses log: false to avoid unnecessary output when we’re just getting a
|
105
|
+
# value for internal needs.
|
106
|
+
#
|
107
|
+
# Defaults to fail: false, since when one just wants to get the value, there is not that much
|
108
|
+
# need to unconditionally die if getting bad exit code.
|
109
|
+
private def execvalue *args, fail: false, rootcmd: nil, env: {}
|
110
|
+
# This logging handling is a bit of duplication from execute(), but to be extra safe, it is duplicated.
|
111
|
+
# The debug message contents will always be evaluated, so can just do it like this.
|
112
|
+
log_command = args[0].respond_to?(:join) && args[0].join(" ") || args[0]
|
113
|
+
log_command =
|
114
|
+
if log_command.frozen?
|
115
|
+
log_command.dup.force_encoding("UTF-8")
|
116
|
+
else
|
117
|
+
log_command.force_encoding("UTF-8")
|
118
|
+
end
|
119
|
+
if @dry_run
|
120
|
+
puts "Would execvalue “#{log_command}” at #{caller_locations(1, 1)[0]}"
|
121
|
+
return
|
122
|
+
end
|
123
|
+
trace "Execvaluing “#{log_command}” at #{caller_locations(1, 1)[0]}"
|
124
|
+
execute(*args, fail: fail, log: false, rootcmd: rootcmd, env: env)
|
125
|
+
end
|
126
|
+
|
127
|
+
private def exec_success?
|
128
|
+
$CHILD_STATUS.success?
|
129
|
+
end
|
130
|
+
|
131
|
+
private def exec_failure?
|
132
|
+
!exec_success?
|
133
|
+
end
|
134
|
+
|
135
|
+
# When running through bundler, don’t use whatever it defines as we’re running inside chroot.
|
136
|
+
private def clean_execute
|
137
|
+
if (Bundler&.bundler_major_version) >= 2
|
138
|
+
Bundler.with_unbundled_env do
|
139
|
+
yield 2
|
140
|
+
end
|
141
|
+
elsif Bundler&.bundler_major_version == 1
|
142
|
+
Bundler.with_clean_env do
|
143
|
+
yield 1
|
144
|
+
end
|
145
|
+
else
|
146
|
+
yield nil
|
147
|
+
end
|
148
|
+
rescue NameError
|
149
|
+
# If NameError is thrown, no Bundler is available.
|
150
|
+
yield nil
|
151
|
+
end
|
152
|
+
end
|
@@ -1,7 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bwrap/version"
|
4
|
-
|
4
|
+
|
5
|
+
# Just declaring the module here so full Execution module
|
6
|
+
# doesn’t need to be required just to have labels.
|
7
|
+
#
|
8
|
+
# Most users probably should just require bwrap/execution directly
|
9
|
+
# instead of this file, but bwrap/output.rb benefits from this.
|
10
|
+
module Bwrap::Execution
|
11
|
+
end
|
5
12
|
|
6
13
|
# Exit codes for exit status handling.
|
7
14
|
#
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/output"
|
4
|
+
require_relative "exceptions"
|
5
|
+
|
6
|
+
# Path checking methods.
|
7
|
+
module Bwrap::Execution::Path
|
8
|
+
# Utilities to handle environment path operations.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Environment
|
12
|
+
# Loop through each path in global PATH environment variable to
|
13
|
+
# perform an operation in each path, for example to resolve
|
14
|
+
# absolute path to a command.
|
15
|
+
#
|
16
|
+
# NOTE: This has nothing to do with {Bwrap::Config#env_paths}, as the
|
17
|
+
# env paths looped are what the system has defined, in ENV["PATH"].
|
18
|
+
#
|
19
|
+
# Should be cross-platform.
|
20
|
+
#
|
21
|
+
# @yield Command appended to each path in PATH environment variable
|
22
|
+
# @yieldparam path [String] Full path to executable
|
23
|
+
def self.each_env_path command, env_path_var: ENV["PATH"]
|
24
|
+
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
|
25
|
+
|
26
|
+
env_path_var.split(File::PATH_SEPARATOR).each do |env_path|
|
27
|
+
exts.each do |ext|
|
28
|
+
exe = File.join(env_path, "#{command}#{ext}")
|
29
|
+
yield exe
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Check if requested program can be found.
|
36
|
+
#
|
37
|
+
# Should work cross-platform and in restricted environments pretty well.
|
38
|
+
#
|
39
|
+
# @param command [String] executable to be resolved
|
40
|
+
# @param env_path_var [String] PATH environment variable as string.
|
41
|
+
# Defaults to `ENV["PATH"]`
|
42
|
+
private def command_available? command, env_path_var: ENV["PATH"]
|
43
|
+
# Special handling for absolute paths.
|
44
|
+
path = Pathname.new command
|
45
|
+
if path.absolute?
|
46
|
+
if path.executable? && !path.directory?
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
|
53
|
+
Bwrap::Execution::Path::Environment.each_env_path command, env_path_var: env_path_var do |exe|
|
54
|
+
return true if File.executable?(exe) && !File.directory?(exe)
|
55
|
+
end
|
56
|
+
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns path to given executable.
|
61
|
+
#
|
62
|
+
# @param (see #command_available?)
|
63
|
+
private def which command, fail: true, env_path_var: ENV["PATH"]
|
64
|
+
# Special handling for absolute paths.
|
65
|
+
path = Pathname.new command
|
66
|
+
if path.absolute?
|
67
|
+
if path.executable?
|
68
|
+
return command
|
69
|
+
end
|
70
|
+
|
71
|
+
raise Bwrap::Execution::CommandNotFound.new command: command if fail
|
72
|
+
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
|
76
|
+
Bwrap::Execution::Path::Environment.each_env_path command, env_path_var: env_path_var do |exe|
|
77
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
78
|
+
end
|
79
|
+
|
80
|
+
return nil unless fail
|
81
|
+
|
82
|
+
raise Bwrap::Execution::CommandNotFound.new command: command
|
83
|
+
end
|
84
|
+
end
|
data/lib/bwrap/execution.rb
CHANGED
@@ -1,177 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# @abstract Module to be included in a class that needs to execute commands.
|
8
|
-
#
|
9
|
-
# Methods to execute processes.
|
3
|
+
# Declare Execution module here so Bwrap::Execution module is
|
4
|
+
# already declared for Execution module classes, to avoid
|
5
|
+
# a circular dependency.
|
10
6
|
module Bwrap::Execution
|
11
|
-
include Bwrap::Output
|
12
|
-
|
13
|
-
# Unspecified execution related error.
|
14
|
-
class CommandError < StandardError
|
15
|
-
end
|
16
|
-
|
17
|
-
# Signifies that command execution has failed.
|
18
|
-
class ExecutionFailed < CommandError
|
19
|
-
end
|
20
|
-
|
21
|
-
# Actual implementation of execution command. Can be used when static method is needed.
|
22
|
-
#
|
23
|
-
# @note When an array is given as a command, empty strings are passed as empty arguments.
|
24
|
-
#
|
25
|
-
# This means that `[ "foo", "bar" ]` passes one argument to "foo" command, when
|
26
|
-
# `[ "foo", "", "bar" ]` passes two arguments.
|
27
|
-
#
|
28
|
-
# This may or may not be what is assumed, so it can’t be fixed here. It is up to the
|
29
|
-
# command to decide how to handle empty arguments.
|
30
|
-
#
|
31
|
-
# Returns pid of executed command if wait is false.
|
32
|
-
# Returns command output if wait is true.
|
33
|
-
#
|
34
|
-
# fail == If true, an error is raised in case the command returns failure code.
|
35
|
-
#
|
36
|
-
# @param error if :show, warn()s output of the command is shown if execution failed.
|
37
|
-
#
|
38
|
-
# @see #execute
|
39
|
-
def self.do_execute command,
|
40
|
-
fail: true,
|
41
|
-
wait: true,
|
42
|
-
log: true,
|
43
|
-
direct_output: false,
|
44
|
-
env: {},
|
45
|
-
clear_env: false,
|
46
|
-
error: nil,
|
47
|
-
log_callback: 2,
|
48
|
-
rootcmd: nil
|
49
|
-
command = Execute.format_command command, rootcmd: rootcmd
|
50
|
-
Execute.handle_logging command, log_callback: log_callback, log: log, dry_run: @dry_run
|
51
|
-
return if @dry_run || Bwrap::Execution::Execute.dry_run
|
52
|
-
|
53
|
-
Execute.open_pipes direct_output
|
54
|
-
|
55
|
-
# If command is an array, there can’t be arrays inside the array.
|
56
|
-
# For convenience, the array is flattened here, so callers can construct commands more easily.
|
57
|
-
if command.respond_to? :flatten!
|
58
|
-
command.flatten!
|
59
|
-
end
|
60
|
-
|
61
|
-
# If command is string, splat operator (the *) does not do anything. If array, it expand the arguments.
|
62
|
-
# This causes spawning work correctly, as that’s how spawn() expects to have the argu
|
63
|
-
pid = spawn(env, *command, err: [ :child, :out ], out: Execute.w, unsetenv_others: clear_env)
|
64
|
-
output = Execute.finish_execution(log: log, wait: wait, direct_output: direct_output)
|
65
|
-
return pid unless wait
|
66
|
-
|
67
|
-
# This is instant return, but allows us to have $?/$CHILD_STATUS set.
|
68
|
-
Process.wait pid
|
69
|
-
@last_status = $CHILD_STATUS
|
70
|
-
|
71
|
-
output = Execute.process_output output: output
|
72
|
-
Execute.handle_execution_fail fail: fail, error: error, output: output
|
73
|
-
output
|
74
|
-
ensure
|
75
|
-
Execute.clean_variables
|
76
|
-
end
|
77
|
-
|
78
|
-
# Returns Process::Status instance of last execution.
|
79
|
-
#
|
80
|
-
# @note This only is able to return the status if wait is true, as otherwise caller is assumed to
|
81
|
-
# handle execution flow.
|
82
|
-
def self.last_status
|
83
|
-
@last_status
|
84
|
-
end
|
85
|
-
|
86
|
-
# Check if requested program can be found.
|
87
|
-
#
|
88
|
-
# Should work cross-platform and in restricted environents pretty well.
|
89
|
-
private def command_available? command
|
90
|
-
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
|
91
|
-
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
92
|
-
exts.each do |ext|
|
93
|
-
exe = File.join(path, "#{command}#{ext}")
|
94
|
-
return true if File.executable?(exe) && !File.directory?(exe)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
false
|
98
|
-
end
|
99
|
-
|
100
|
-
# Returns path to given executable.
|
101
|
-
private def which command, fail: true
|
102
|
-
exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [ "" ]
|
103
|
-
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
104
|
-
exts.each do |ext|
|
105
|
-
exe = File.join(path, "#{command}#{ext}")
|
106
|
-
return exe if File.executable?(exe) && !File.directory?(exe)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
error "Failed to find #{command} from PATH." if fail
|
110
|
-
nil
|
111
|
-
end
|
112
|
-
|
113
|
-
# Execute a command.
|
114
|
-
#
|
115
|
-
# This method can be used by including Execution module in a class that should be able to
|
116
|
-
# execute commands.
|
117
|
-
#
|
118
|
-
# @see .do_execute .do_execute for documentation of argument syntax
|
119
|
-
private def execute *args
|
120
|
-
# Mangle proper location to error message.
|
121
|
-
if args.last.is_a? Hash
|
122
|
-
args.last[:log_callback] = 3
|
123
|
-
else
|
124
|
-
args << { log_callback: 3 }
|
125
|
-
end
|
126
|
-
Bwrap::Execution.do_execute(*args)
|
127
|
-
end
|
128
|
-
|
129
|
-
# Same as ::execute, but uses log: false to avoid unnecessary output when we’re just getting a
|
130
|
-
# value for internal needs.
|
131
|
-
#
|
132
|
-
# Defaults to fail: false, since when one just wants to get the value, there is not that much
|
133
|
-
# need to unconditionally die if getting bad exit code.
|
134
|
-
private def execvalue *args, fail: false, rootcmd: nil, env: {}
|
135
|
-
# This logging handling is a bit of duplication from execute(), but to be extra safe, it is duplicated.
|
136
|
-
# The debug message contents will always be evaluated, so can just do it like this.
|
137
|
-
log_command = args[0].respond_to?(:join) && args[0].join(" ") || args[0]
|
138
|
-
log_command =
|
139
|
-
if log_command.frozen?
|
140
|
-
log_command.dup.force_encoding("UTF-8")
|
141
|
-
else
|
142
|
-
log_command.force_encoding("UTF-8")
|
143
|
-
end
|
144
|
-
if @dry_run
|
145
|
-
puts "Would execvalue “#{log_command}” at #{caller_locations(1, 1)[0]}"
|
146
|
-
return
|
147
|
-
end
|
148
|
-
trace "Execvaluing “#{log_command}” at #{caller_locations(1, 1)[0]}"
|
149
|
-
execute(*args, fail: fail, log: false, rootcmd: rootcmd, env: env)
|
150
|
-
end
|
151
|
-
|
152
|
-
private def exec_success?
|
153
|
-
$CHILD_STATUS.success?
|
154
|
-
end
|
155
|
-
|
156
|
-
private def exec_failure?
|
157
|
-
!exec_success?
|
158
|
-
end
|
159
|
-
|
160
|
-
# When running through bundler, don’t use whatever it defines as we’re running inside chroot.
|
161
|
-
private def clean_execute
|
162
|
-
if (Bundler&.bundler_major_version) >= 2
|
163
|
-
Bundler.with_unbundled_env do
|
164
|
-
yield 2
|
165
|
-
end
|
166
|
-
elsif Bundler&.bundler_major_version == 1
|
167
|
-
Bundler.with_clean_env do
|
168
|
-
yield 1
|
169
|
-
end
|
170
|
-
else
|
171
|
-
yield nil
|
172
|
-
end
|
173
|
-
rescue NameError
|
174
|
-
# If NameError is thrown, no Bundler is available.
|
175
|
-
yield nil
|
176
|
-
end
|
177
7
|
end
|
8
|
+
|
9
|
+
require_relative "execution/execution"
|