bwrap 1.1.1 → 1.3.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 +21 -0
- data/README.md +9 -0
- data/lib/bwrap/args/args.rb +33 -4
- data/lib/bwrap/args/bind/device.rb +48 -0
- data/lib/bwrap/args/bind/library.rb +63 -47
- data/lib/bwrap/args/bind.rb +39 -41
- data/lib/bwrap/args/construct.rb +27 -36
- data/lib/bwrap/args/environment.rb +5 -1
- data/lib/bwrap/args/features/ruby_binds.rb +3 -1
- data/lib/bwrap/args/features.rb +3 -4
- data/lib/bwrap/args/mount.rb +1 -7
- data/lib/bwrap/args/namespace.rb +25 -0
- data/lib/bwrap/args/network.rb +7 -2
- data/lib/bwrap/args/user.rb +36 -0
- data/lib/bwrap/bwrap.rb +58 -26
- data/lib/bwrap/config.rb +52 -9
- data/lib/bwrap/exceptions.rb +1 -0
- data/lib/bwrap/execution/exec.rb +78 -0
- data/lib/bwrap/execution/execute.rb +25 -43
- data/lib/bwrap/execution/execution.rb +77 -50
- data/lib/bwrap/execution/logging.rb +49 -0
- data/lib/bwrap/execution/popen2e.rb +84 -12
- data/lib/bwrap/execution.rb +1 -0
- data/lib/bwrap/resolvers/executable.rb +70 -0
- data/lib/bwrap/resolvers/library/base.rb +22 -0
- data/lib/bwrap/resolvers/library/library.rb +74 -0
- data/lib/bwrap/resolvers/library/llvm_readelf.rb +133 -0
- data/lib/bwrap/resolvers/library/musl.rb +54 -0
- data/lib/bwrap/resolvers/library.rb +12 -0
- data/lib/bwrap/resolvers/mime.rb +75 -0
- data/lib/bwrap/resolvers/resolvers.rb +7 -0
- data/lib/bwrap/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +29 -18
- metadata.gz.sig +0 -0
- data/lib/bwrap/args/bind/mime.rb +0 -65
- data/lib/bwrap/args/library.rb +0 -135
@@ -3,7 +3,9 @@
|
|
3
3
|
require "bwrap/version"
|
4
4
|
require "bwrap/output"
|
5
5
|
require_relative "exceptions"
|
6
|
+
require_relative "exec"
|
6
7
|
require_relative "execute"
|
8
|
+
require_relative "logging"
|
7
9
|
require_relative "path"
|
8
10
|
require_relative "popen2e"
|
9
11
|
|
@@ -36,68 +38,88 @@ module Bwrap::Execution
|
|
36
38
|
#
|
37
39
|
# fail == If true, an error is raised in case the command returns failure code.
|
38
40
|
#
|
39
|
-
# @
|
41
|
+
# @option kwargs [Boolean]
|
42
|
+
# clear_env (false)
|
43
|
+
# If true, executed command will run with no environment variables
|
44
|
+
# @option kwargs [Bwrap::Config|nil]
|
45
|
+
# config (nil)
|
46
|
+
# Configuration used to launch the command inside bubblewrap
|
47
|
+
# @option kwargs [Boolean]
|
48
|
+
# direct_output (false)
|
49
|
+
# Instead of buffering output, it will be directly outputted to `$stdout`
|
50
|
+
# @option kwargs [Hash<String, String>]
|
51
|
+
# env ({})
|
52
|
+
# Environment variables to add to the command. Also see `clear_env` option
|
53
|
+
# @option kwargs [:show|nil]
|
54
|
+
# error (nil)
|
55
|
+
# if :show and execution failed, prints output of the command with {#warn}
|
56
|
+
# @option kwargs [Boolean]
|
57
|
+
# fail (true)
|
58
|
+
# If true, {ExecutionFailed} is raised if process did not finish successfully
|
59
|
+
# @option kwargs [Boolean]
|
60
|
+
# log (true)
|
61
|
+
# If true, prints output if verbose flag has been set.
|
62
|
+
# And if a log file has been configured, also logs to that file
|
63
|
+
# @option kwargs [Integer]
|
64
|
+
# log_callback (1)
|
65
|
+
# Semi-internal variable used to tailor caller in debug messages
|
66
|
+
# @option kwargs [Array|Symbol|nil]
|
67
|
+
# rootcmd (nil)
|
68
|
+
# Flags used to construct bubblewrap environment
|
69
|
+
# @option kwargs [Boolean]
|
70
|
+
# wait (true)
|
71
|
+
# If true, this method blocks until the command has finished.
|
72
|
+
# Otherwise returns immediately. It is possible for user to
|
73
|
+
# call `Process.wait` to wait for result at suitable place
|
74
|
+
#
|
75
|
+
# TODO: log kwarg could take log level as argument. Like :trace.
|
40
76
|
#
|
41
77
|
# @see #execute
|
42
|
-
def self.do_execute command,
|
43
|
-
|
44
|
-
|
45
|
-
log: true,
|
46
|
-
direct_output: false,
|
47
|
-
env: {},
|
48
|
-
clear_env: false,
|
49
|
-
error: nil,
|
50
|
-
log_callback: 2,
|
51
|
-
rootcmd: nil
|
52
|
-
command = Execute.format_command command, rootcmd: rootcmd
|
53
|
-
Execute.handle_logging command, log_callback: log_callback, log: log, dry_run: @dry_run
|
54
|
-
return if @dry_run || Bwrap::Execution::Execute.dry_run
|
78
|
+
def self.do_execute command, **kwargs
|
79
|
+
executor = Exec.new command
|
80
|
+
executor.dry_run = @dry_run
|
55
81
|
|
56
|
-
|
82
|
+
executor.clear_env = kwargs.key?(:clear_env) ? kwargs.delete(:clear_env) : false
|
83
|
+
executor.config = kwargs.key?(:config) ? kwargs.delete(:config) : nil
|
84
|
+
executor.direct_output = kwargs.key?(:direct_output) ? kwargs.delete(:direct_output) : false
|
85
|
+
executor.env = kwargs.key?(:env) ? kwargs.delete(:env) : {}
|
86
|
+
executor.error = kwargs.key?(:error) ? kwargs.delete(:error) : nil
|
87
|
+
executor.fail = kwargs.key?(:fail) ? kwargs.delete(:fail) : true
|
88
|
+
executor.log = kwargs.key?(:log) ? kwargs.delete(:log) : true
|
89
|
+
executor.log_callback = kwargs.key?(:log_callback) ? kwargs.delete(:log_callback) : 1
|
90
|
+
executor.rootcmd = kwargs.key?(:rootcmd) ? kwargs.delete(:rootcmd) : nil
|
91
|
+
executor.wait = kwargs.key?(:wait) ? kwargs.delete(:wait) : true
|
57
92
|
|
58
|
-
|
59
|
-
|
60
|
-
if command.respond_to? :flatten!
|
61
|
-
command.flatten!
|
93
|
+
unless kwargs.empty?
|
94
|
+
raise ArgumentError, %{unknown keywords: #{kwargs.keys.join ","}}
|
62
95
|
end
|
63
96
|
|
64
|
-
|
65
|
-
# This causes spawning work correctly, as that’s how spawn() expects to have the arguments.
|
66
|
-
pid = spawn(env, *command, err: [ :child, :out ], out: Execute.w, unsetenv_others: clear_env)
|
67
|
-
output = Execute.finish_execution(log: log, wait: wait, direct_output: direct_output)
|
68
|
-
return pid unless wait
|
69
|
-
|
70
|
-
# This is instant return, but allows us to have $?/$CHILD_STATUS set.
|
71
|
-
Process.wait pid
|
72
|
-
@last_status = $CHILD_STATUS
|
73
|
-
|
74
|
-
output = Execute.process_output output: output
|
75
|
-
Execute.handle_execution_fail fail: fail, error: error, output: output, command: command
|
76
|
-
output
|
97
|
+
executor.execute
|
77
98
|
ensure
|
78
|
-
|
99
|
+
@last_status = executor.last_status if executor&.last_status
|
79
100
|
end
|
80
101
|
|
81
|
-
# Works similarly to
|
82
|
-
#
|
83
|
-
# TODO: If there will be any difference to input syntax, document those differences here.
|
84
|
-
# For now, rootcmd option has been implemented.
|
102
|
+
# Works similarly to Ruby’s official `Open3.popen2e`.
|
85
103
|
#
|
86
|
-
# A block is accepted, as does
|
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...
|
104
|
+
# A block is accepted, as does `Open3.popen2e`. For now, at least.
|
90
105
|
#
|
91
106
|
# TODO: Verify default log_callback is correct
|
92
107
|
#
|
93
108
|
# @warning Only array style commands are accepted. For example, `ls /`
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
109
|
+
# is not ok, but `ls` or `%w{ ls / }` is ok.
|
110
|
+
#
|
111
|
+
# @param config [Bwrap::Config] Configuration used to launch the executable inside bubblewrap
|
112
|
+
# @param rootcmd [Array|Symbol|nil] A strange way to use bwrap. Probably no sense to use this
|
113
|
+
# @param log [Boolean]
|
114
|
+
# If true, prints output if verbose flag has been set.
|
115
|
+
# And if a log file has been configured, also logs to that file
|
116
|
+
# @option log_callback [Integer] Semi-internal variable used to tailor caller in debug messages
|
117
|
+
def popen2e *cmd, rootcmd: nil, config: nil, log_callback: 2, log: true, &block
|
118
|
+
popen = Bwrap::Execution::Popen2e.new rootcmd: rootcmd, config: config
|
119
|
+
popen.dry_run = @dry_run # TODO: Add a test that tests that this works as it works with execute().
|
120
|
+
popen.popen2e(*cmd, log_callback: log_callback, log: log, &block)
|
99
121
|
ensure
|
100
|
-
@last_status =
|
122
|
+
@last_status = popen&.child_status
|
101
123
|
end
|
102
124
|
module_function :popen2e
|
103
125
|
|
@@ -115,13 +137,18 @@ module Bwrap::Execution
|
|
115
137
|
# execute commands.
|
116
138
|
#
|
117
139
|
# @see .do_execute .do_execute for documentation of argument syntax
|
140
|
+
#
|
141
|
+
# TODO Default log_callback should be 2 or something, and callers should add one for each subsequent method.
|
118
142
|
private def execute *args, **kwargs
|
119
143
|
# Mangle proper location to error message.
|
120
144
|
if kwargs.is_a? Hash
|
121
|
-
kwargs[:log_callback]
|
145
|
+
kwargs[:log_callback] ||= 1
|
122
146
|
else
|
123
|
-
kwargs = { log_callback:
|
147
|
+
kwargs = { log_callback: 1 }
|
124
148
|
end
|
149
|
+
# Add 1 to log callback so execution is shown to be from caller.
|
150
|
+
kwargs[:log_callback] += 1
|
151
|
+
|
125
152
|
Bwrap::Execution.do_execute(*args, **kwargs)
|
126
153
|
end
|
127
154
|
|
@@ -162,7 +189,7 @@ module Bwrap::Execution
|
|
162
189
|
Bundler.with_unbundled_env do
|
163
190
|
yield 2
|
164
191
|
end
|
165
|
-
elsif Bundler&.bundler_major_version == 1
|
192
|
+
elsif (Bundler&.bundler_major_version) == 1
|
166
193
|
Bundler.with_clean_env do
|
167
194
|
yield 1
|
168
195
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
# force_encoding modifies string, so can’t freeze strings.
|
4
|
+
|
5
|
+
require "bwrap/output"
|
6
|
+
require_relative "execute"
|
7
|
+
|
8
|
+
# Logging utilities for execution.
|
9
|
+
class Bwrap::Execution::Logging
|
10
|
+
# Formats given command for logging, depending on dry-run flag.
|
11
|
+
def self.handle_logging command, log_callback:, log:, dry_run:
|
12
|
+
log_callback += 1 # This is another method in the chain, so need to add one.
|
13
|
+
|
14
|
+
# The debug message contents will always be evaluated, so can just do it like this.
|
15
|
+
log_command = calculate_log_command command
|
16
|
+
|
17
|
+
if dry_run || Bwrap::Execution::Execute.dry_run
|
18
|
+
puts "Would execute “#{log_command.force_encoding("UTF-8")}” at #{caller_locations(log_callback, 1)[0]}"
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
return unless log
|
23
|
+
|
24
|
+
msg = "Executing “#{log_command.force_encoding("UTF-8")}” at #{caller_locations(log_callback, 1)[0]}"
|
25
|
+
Bwrap::Output.debug_output msg
|
26
|
+
end
|
27
|
+
|
28
|
+
private_class_method def self.calculate_log_command command
|
29
|
+
return command.dup unless command.respond_to?(:join)
|
30
|
+
|
31
|
+
temp = command.dup
|
32
|
+
|
33
|
+
# NOTE: These are not exactly safe, but this is only a debugging aid anyway.
|
34
|
+
temp.map! do |argument|
|
35
|
+
if argument.empty?
|
36
|
+
# If empty value, insert it to quotes so pasting to cli is safer.
|
37
|
+
argument = %{'#{argument}'}
|
38
|
+
elsif argument.include? " "
|
39
|
+
# Wrap multi word arguments to quotes, for convenience.
|
40
|
+
escaped = argument.gsub '"', '\"'
|
41
|
+
argument = %{"#{escaped}"}
|
42
|
+
end
|
43
|
+
argument
|
44
|
+
end
|
45
|
+
|
46
|
+
temp.join(" ")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -1,26 +1,83 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "bwrap"
|
4
|
+
require "bwrap/output"
|
5
|
+
require_relative "logging"
|
6
|
+
|
7
|
+
# @see Bwrap::Execution.popen2e
|
4
8
|
class Bwrap::Execution::Popen2e
|
9
|
+
include Bwrap::Output
|
10
|
+
|
5
11
|
attr_writer :dry_run
|
6
12
|
|
7
|
-
# @
|
8
|
-
#
|
13
|
+
# @param rootcmd [Array|Symbol] Flags used to construct bubblewrap environment
|
14
|
+
# @param config [Bwrap::Config] Configuration used to launch the command inside bubblewrap
|
15
|
+
def initialize rootcmd: nil, config: nil
|
16
|
+
self.rootcmd = rootcmd
|
17
|
+
self.config = config
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param value [Bwrap::Config] Configuration used to launch the command inside bubblewrap
|
21
|
+
def config= value
|
22
|
+
@config = value
|
23
|
+
|
24
|
+
return unless @config and @rootcmd
|
25
|
+
|
26
|
+
error "Only either rootcmd or config object can be given to popen2e. " \
|
27
|
+
"Please set rootcmd to nil if wanting to use config object."
|
28
|
+
end
|
29
|
+
|
30
|
+
# Can be used to implement kind of tag based bubblewrap command things.
|
31
|
+
# This system is not well documented, and originally done for purposes
|
32
|
+
# of the code where this gem was splitted from.
|
33
|
+
#
|
34
|
+
# Probably using {#config=} instead makes more sense.
|
35
|
+
#
|
36
|
+
# @param value [Array|Symbol] Flags used to construct bubblewrap environment
|
37
|
+
def rootcmd= value
|
38
|
+
@rootcmd = value
|
39
|
+
|
40
|
+
return unless @config and @rootcmd
|
41
|
+
|
42
|
+
error "Only either rootcmd or config object can be given to popen2e. " \
|
43
|
+
"Please set config to nil if wanting to use rootcmd flags."
|
44
|
+
end
|
45
|
+
|
46
|
+
# @see Bwrap::Execution.popen2e
|
9
47
|
#
|
10
|
-
# @note Also options accepted by
|
48
|
+
# @note Also options accepted by upstream’s `Open3.popen2e` are accepted
|
11
49
|
# here, in addition to those specified here.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
50
|
+
#
|
51
|
+
# @option kwargs [Boolean]
|
52
|
+
# log (true)
|
53
|
+
# If true, prints output if verbose flag has been set.
|
54
|
+
# And if a log file has been configured, also logs to that file
|
55
|
+
# @option kwargs [Integer]
|
56
|
+
# log_callback (3)
|
57
|
+
# Semi-internal variable used to tailor caller in debug messages
|
58
|
+
#
|
59
|
+
# @yieldreturn In case a block is given, its return value is also returned by popen2e
|
60
|
+
# @return [Array<(IO, IO, Thread)>] Write and read `IO` and a wait thread
|
61
|
+
def popen2e *cmd, **kwargs, &block
|
62
|
+
@log = kwargs.key?(:log) ? kwargs.delete(:log) : true
|
63
|
+
@log_callback = kwargs.key?(:log_callback) ? kwargs.delete(:log_callback) : 1
|
64
|
+
|
65
|
+
@log_callback += 1 # Passing to another method, so need to add one more.
|
66
|
+
|
16
67
|
self.opts_from_input = cmd
|
68
|
+
if kwargs
|
69
|
+
@opts ||= {}
|
70
|
+
@opts.merge! kwargs
|
71
|
+
end
|
17
72
|
|
18
73
|
resolve_actual_command cmd
|
19
74
|
|
20
|
-
return if
|
75
|
+
return if dry_run?
|
21
76
|
|
22
77
|
open_pipes
|
23
|
-
|
78
|
+
child_io = [ @in_read, @out_write ]
|
79
|
+
parent_io = [ @in_write, @out_read ]
|
80
|
+
popen_run(@actual_command, child_io, parent_io, &block)
|
24
81
|
ensure
|
25
82
|
if block
|
26
83
|
@in_read.close
|
@@ -30,6 +87,13 @@ class Bwrap::Execution::Popen2e
|
|
30
87
|
end
|
31
88
|
end
|
32
89
|
|
90
|
+
# Only contains `Process::Status` if no block has been passed to {#popen2e}.
|
91
|
+
#
|
92
|
+
# @return [Process::Status|nil] Exit status of the process
|
93
|
+
def child_status
|
94
|
+
@child_status
|
95
|
+
end
|
96
|
+
|
33
97
|
# Sets @opts from `cmd` given to {#popen2e}, if any extra options have been given.
|
34
98
|
private def opts_from_input= cmd
|
35
99
|
@opts = cmd.last.is_a?(Hash) && cmd.pop.dup || {}
|
@@ -54,8 +118,11 @@ class Bwrap::Execution::Popen2e
|
|
54
118
|
# Delete option hash.
|
55
119
|
option_hash = temp_cmd.pop if temp_cmd.last.is_a? Hash
|
56
120
|
|
57
|
-
temp_cmd = Bwrap::Execution::Execute.format_command temp_cmd, rootcmd: @rootcmd
|
58
|
-
Bwrap::Execution::
|
121
|
+
temp_cmd = Bwrap::Execution::Execute.format_command temp_cmd, rootcmd: @rootcmd, config: @config
|
122
|
+
Bwrap::Execution::Logging.handle_logging temp_cmd,
|
123
|
+
log_callback: @log_callback,
|
124
|
+
log: @log,
|
125
|
+
dry_run: @dry_run
|
59
126
|
|
60
127
|
temp_cmd.unshift env_hash if env_hash
|
61
128
|
temp_cmd.push option_hash if option_hash
|
@@ -79,9 +146,14 @@ class Bwrap::Execution::Popen2e
|
|
79
146
|
ensure
|
80
147
|
parent_io.each(&:close)
|
81
148
|
wait_thr.join
|
149
|
+
@child_status = wait_thr.value # Process::Status
|
82
150
|
end
|
83
151
|
end
|
84
152
|
|
85
153
|
result
|
86
154
|
end
|
155
|
+
|
156
|
+
private def dry_run?
|
157
|
+
@dry_run || Bwrap::Execution::Execute.dry_run
|
158
|
+
end
|
87
159
|
end
|
data/lib/bwrap/execution.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "resolvers"
|
4
|
+
|
5
|
+
# Resolves for example symlinks and such stuff.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class Bwrap::Resolvers::Executable
|
9
|
+
# May be either relative or absolute path. If relative, the path may be invalid.
|
10
|
+
#
|
11
|
+
# @return [Pathname|nil] The executable, without arguments
|
12
|
+
attr_reader :executable
|
13
|
+
|
14
|
+
# If the executable is a symlink, contains each resolved symlink
|
15
|
+
# and the real target. Otherwise only contains the executable.
|
16
|
+
#
|
17
|
+
# @return [Hash<Pathname, :path|:symlink>|nil] Resolved paths to the command
|
18
|
+
attr_reader :executable_paths
|
19
|
+
|
20
|
+
# @param command [Array|String|nil] The command given to {Bwrap#run}.
|
21
|
+
def initialize command = nil
|
22
|
+
self.command = command if command
|
23
|
+
end
|
24
|
+
|
25
|
+
# The command given to {Bwrap#run}.
|
26
|
+
#
|
27
|
+
# @see Bwrap::Args::Construct#command=
|
28
|
+
def command= value
|
29
|
+
self.executable = executable_from_command value
|
30
|
+
@raw_command = value
|
31
|
+
end
|
32
|
+
|
33
|
+
# Resolves given executable and sets relevant data.
|
34
|
+
def executable= value
|
35
|
+
@executable = Pathname.new value
|
36
|
+
|
37
|
+
@executable_paths = { @executable => :path }
|
38
|
+
|
39
|
+
return unless @executable.symlink?
|
40
|
+
|
41
|
+
temp = @executable
|
42
|
+
while temp.symlink?
|
43
|
+
temp = temp.readlink
|
44
|
+
@executable_paths[temp] = :symlink
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns true if executable name parsed from command is absolute
|
49
|
+
# path, and not only the executable name.
|
50
|
+
#
|
51
|
+
# @return [Bool] true if path full path, false if it is only executable name
|
52
|
+
def absolute_path?
|
53
|
+
return false unless @executable
|
54
|
+
|
55
|
+
@executable.absolute?
|
56
|
+
end
|
57
|
+
|
58
|
+
private def executable_from_command command
|
59
|
+
if command.is_a? String
|
60
|
+
return command
|
61
|
+
end
|
62
|
+
|
63
|
+
# Array-like.
|
64
|
+
if command.respond_to? :at
|
65
|
+
return command.at(0)
|
66
|
+
end
|
67
|
+
|
68
|
+
raise "Can’t recognize type of given command. Type: #{command.class}"
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/execution"
|
4
|
+
require "bwrap/output"
|
5
|
+
require_relative "../library"
|
6
|
+
|
7
|
+
# Base definitions for library resolver subclasses.
|
8
|
+
class Bwrap::Resolvers::Library::Base
|
9
|
+
include Bwrap::Execution
|
10
|
+
include Bwrap::Output
|
11
|
+
|
12
|
+
private def convert_binary_paths binary_paths
|
13
|
+
case binary_paths
|
14
|
+
when String
|
15
|
+
[ binary_paths ]
|
16
|
+
when Pathname
|
17
|
+
[ binary_paths.to_s ]
|
18
|
+
else
|
19
|
+
binary_paths
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/execution"
|
4
|
+
require "bwrap/output"
|
5
|
+
require_relative "llvm_readelf"
|
6
|
+
require_relative "musl"
|
7
|
+
require_relative "../resolvers"
|
8
|
+
|
9
|
+
# See ../library.rb for class documentation.
|
10
|
+
class Bwrap::Resolvers::Library
|
11
|
+
include Bwrap::Execution
|
12
|
+
include Bwrap::Output
|
13
|
+
|
14
|
+
# NOTE: This caching can be made more efficient, but need to look at it later, what to do about it.
|
15
|
+
@@needed_libraries_cache ||= []
|
16
|
+
|
17
|
+
# Empties `@@needed_libraries_cache`.
|
18
|
+
def self.clear_needed_libraries_cache
|
19
|
+
@@needed_libraries_cache.clear
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def needed_libraries_cache
|
24
|
+
@@needed_libraries_cache
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Otherwise similar to {#needed_libraries}, but checks used libc to handle musl executables.
|
29
|
+
#
|
30
|
+
# @param executable [String] Path to the executable to find dependencies for
|
31
|
+
# @return [Array] Libraries the executable needs, if any
|
32
|
+
def libraries_needed_by executable
|
33
|
+
trace "Finding libraries needed by #{executable}"
|
34
|
+
|
35
|
+
# %i == interpreter, the library used to load the executable by kernel.
|
36
|
+
# %F == Path to given file.
|
37
|
+
output_format = "%i::SEPARATOR::%F"
|
38
|
+
scanelf_command = %W{ scanelf --nobanner --quiet --format #{output_format} }
|
39
|
+
scanelf_command << executable
|
40
|
+
|
41
|
+
data = execvalue scanelf_command
|
42
|
+
|
43
|
+
# If data is empty, target probably is a script of some sort.
|
44
|
+
if data.empty?
|
45
|
+
return []
|
46
|
+
end
|
47
|
+
|
48
|
+
data = data.strip
|
49
|
+
interpreter, _executable_path = data.split "::SEPARATOR::"
|
50
|
+
interpreter = Pathname.new interpreter
|
51
|
+
|
52
|
+
if interpreter.basename.to_s[0..6] == "ld-musl"
|
53
|
+
trace "Resolved to musl interpreter: #{interpreter}"
|
54
|
+
musl_needed_libraries executable
|
55
|
+
else
|
56
|
+
trace "Defaulting to glibc interpreter: #{interpreter}"
|
57
|
+
# For glibc, scanelf can return full paths for us most of time.
|
58
|
+
needed_libraries executable
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param binary_paths [String, Array] one or more paths to be resolved
|
63
|
+
def musl_needed_libraries binary_paths
|
64
|
+
musl = Musl.new
|
65
|
+
@needed_libraries = musl.needed_libraries binary_paths
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param binary_paths [String, Array] one or more paths to be resolved
|
69
|
+
def needed_libraries binary_paths
|
70
|
+
llvm_readelf = LLVMReadelf.new
|
71
|
+
@needed_libraries = llvm_readelf.needed_libraries binary_paths
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# class Library ended
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
# Used via {Bwrap::Resolvers::Library}.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class Bwrap::Resolvers::Library::LLVMReadelf < Bwrap::Resolvers::Library::Base
|
9
|
+
# Resolve dependents using llvm-readelf.
|
10
|
+
#
|
11
|
+
# @param binary_paths [String, Array] one or more paths to be resolved
|
12
|
+
def needed_libraries binary_paths
|
13
|
+
raise ArgumentError, "binary_paths is nil, expected an Array" if binary_paths.nil?
|
14
|
+
|
15
|
+
trace "Finding libraries #{binary_paths} requires"
|
16
|
+
@needed_libraries = []
|
17
|
+
|
18
|
+
llvm_readelf_command = %w{ llvm-readelf --needed-libs }
|
19
|
+
|
20
|
+
binary_paths = convert_binary_paths binary_paths
|
21
|
+
|
22
|
+
# Check if the exe is already resolved.
|
23
|
+
binary_paths.delete_if do |binary_path|
|
24
|
+
Bwrap::Resolvers::Library.needed_libraries_cache.include? binary_path
|
25
|
+
end
|
26
|
+
|
27
|
+
return [] if binary_paths.empty?
|
28
|
+
|
29
|
+
data = execvalue(llvm_readelf_command + binary_paths)
|
30
|
+
parse_llvm_readelf_output binary_paths, data
|
31
|
+
|
32
|
+
@needed_libraries
|
33
|
+
end
|
34
|
+
|
35
|
+
# Parses output returned by llvm-readelf --needed-libraries
|
36
|
+
#
|
37
|
+
# Sets libraries to @needed_libraries variable.
|
38
|
+
private def parse_llvm_readelf_output binary_paths, data
|
39
|
+
iter = data.split("\n").each
|
40
|
+
source_binary = binary_paths.first
|
41
|
+
begin
|
42
|
+
while (line = iter.next)
|
43
|
+
source_binary = line[6..-1].strip if line[0..5] == "File: "
|
44
|
+
next unless line[0..16] == "NeededLibraries ["
|
45
|
+
|
46
|
+
while (library = iter.next)
|
47
|
+
library = library.strip
|
48
|
+
# End of library list for current NeededLibraries block.
|
49
|
+
break if library == "]"
|
50
|
+
|
51
|
+
library = convert_to_full_path source_binary, library
|
52
|
+
|
53
|
+
add_library_to_needed_libraries library
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue StopIteration
|
57
|
+
# End of the iteration.
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# NOTE: Maybe this could be in general file here, if needed elsewhere also?
|
62
|
+
# This also applies to some other methods here.
|
63
|
+
#
|
64
|
+
# Finds where given library would be loaded from.
|
65
|
+
#
|
66
|
+
# source_binary is used to to if it has RPATH set as third possibility.
|
67
|
+
# TODO: Implement above.
|
68
|
+
#
|
69
|
+
# @param source_binary [String] Executable or library which is loading the binary
|
70
|
+
private def convert_to_full_path source_binary, library
|
71
|
+
# TODO: Somewhere put a cleanup for this variable, so this is erased
|
72
|
+
# before application is run. That will save at least 200 kB of memory.
|
73
|
+
@@ld_so_cache ||= File.read("/etc/ld.so.cache", mode: "rb")
|
74
|
+
|
75
|
+
path_regex = %r{[\w\-/. ]{3,}}
|
76
|
+
|
77
|
+
check_next = false
|
78
|
+
@@ld_so_cache.scan(path_regex).each do |match|
|
79
|
+
if check_next
|
80
|
+
# TODO: Any sense checking for executable bit?
|
81
|
+
return match if File.exist? match
|
82
|
+
|
83
|
+
check_next = false
|
84
|
+
end
|
85
|
+
|
86
|
+
check_next = true if match == library
|
87
|
+
end
|
88
|
+
|
89
|
+
warn "Failed to resolve full path of library #{library}, needed by #{source_binary}."
|
90
|
+
library
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param library [Array] library to add.
|
94
|
+
private def add_library_to_needed_libraries library
|
95
|
+
raise ArgumentError, "library is nil, expected a String" if library.nil?
|
96
|
+
|
97
|
+
return if @needed_libraries.include? library
|
98
|
+
|
99
|
+
# Probably no sense to log these unless tracing,
|
100
|
+
# as dependencies should work most of time.
|
101
|
+
#
|
102
|
+
# It also outputs tons of output for more complex programs.
|
103
|
+
trace "Binding #{library}"
|
104
|
+
|
105
|
+
@needed_libraries << library
|
106
|
+
|
107
|
+
# Also check if requisite libraries needs some libraries.
|
108
|
+
inner = Bwrap::Resolvers::Library.new
|
109
|
+
@needed_libraries |= inner.needed_libraries library
|
110
|
+
|
111
|
+
Bwrap::Resolvers::Library.needed_libraries_cache << library
|
112
|
+
end
|
113
|
+
|
114
|
+
# @param libraries [Array] libraries to add.
|
115
|
+
private def add_libraries_to_needed_libraries libraries
|
116
|
+
# Add to needed libraries if not already added.
|
117
|
+
(@needed_libraries & libraries).each do |library|
|
118
|
+
# Probably no sense to log these unless tracing,
|
119
|
+
# as dependencies should work most of time.
|
120
|
+
#
|
121
|
+
# It also outputs tons of output for more complex programs.
|
122
|
+
trace "Binding #{library}"
|
123
|
+
end
|
124
|
+
|
125
|
+
@needed_libraries |= libraries
|
126
|
+
|
127
|
+
# Also check if requisite libraries needs some libraries.
|
128
|
+
inner = Bwrap::Resolvers::Library.new
|
129
|
+
@needed_libraries |= inner.needed_libraries libraries
|
130
|
+
|
131
|
+
Bwrap::Resolvers::Library.needed_libraries_cache |= libraries
|
132
|
+
end
|
133
|
+
end
|