bwrap 1.1.1 → 1.2.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.
@@ -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
- # @see {Bwrap::Execution.popen2e}
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
- # @see {Bwrap::Execution.popen2e}
8
- # TODO: Add a test for this (does Ruby have anything I could use here?).
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 {Open3.popen2e} are accepted
48
+ # @note Also options accepted by upstream’s `Open3.popen2e` are accepted
11
49
  # 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
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 @dry_run || Bwrap::Execution::Execute.dry_run
75
+ return if dry_run?
21
76
 
22
77
  open_pipes
23
- popen_run(@actual_command, [ @in_read, @out_write ], [ @in_write, @out_read ], &block)
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::Execute.handle_logging temp_cmd, log_callback: @log_callback, log: @log, dry_run: @dry_run
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
@@ -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
@@ -2,19 +2,19 @@
2
2
 
3
3
  require "bwrap/execution"
4
4
  require "bwrap/output"
5
- require_relative "args"
5
+ require_relative "resolvers"
6
6
 
7
7
  # Class to clean up namespace for implementation specific reasons.
8
8
  #
9
9
  # @api private
10
- class Bwrap::Args::Library
10
+ class Bwrap::Resolvers::Library
11
11
  include Bwrap::Execution
12
12
  include Bwrap::Output
13
13
 
14
14
  # NOTE: This caching can be made more efficient, but need to look at it later, what to do about it.
15
15
  @@needed_libraries_cache ||= []
16
16
 
17
- # Empties {@@needed_libraries_cache}.
17
+ # Empties `@@needed_libraries_cache`.
18
18
  def self.clear_needed_libraries_cache
19
19
  @@needed_libraries_cache.clear
20
20
  end
@@ -22,6 +22,7 @@ class Bwrap::Args::Library
22
22
  # Otherwise similar to {#needed_libraries}, but checks used libc to handle musl executables.
23
23
  #
24
24
  # @param executable [String] Path to the executable to find dependencies for
25
+ # @return [Array] Libraries the executable needs, if any
25
26
  def libraries_needed_by executable
26
27
  # %i == interpreter, the library used to load the executable by kernel.
27
28
  # %F == Path to given file.
@@ -30,6 +31,12 @@ class Bwrap::Args::Library
30
31
  scanelf_command << executable
31
32
 
32
33
  data = execvalue scanelf_command
34
+
35
+ # If data is empty, target probably is a script of some sort.
36
+ if data.empty?
37
+ return []
38
+ end
39
+
33
40
  data = data.strip
34
41
  interpreter, _executable_path = data.split "::SEPARATOR::"
35
42
  interpreter = Pathname.new interpreter
@@ -43,16 +50,19 @@ class Bwrap::Args::Library
43
50
  end
44
51
 
45
52
  # @param binary_paths [String, Array] one or more paths to be resolved
46
- #
47
- # @todo Maybe caching should be done here too?
48
53
  def musl_needed_libraries binary_paths
49
54
  trace "Finding musl libraries #{binary_paths} requires"
50
55
  @needed_libraries = []
51
56
 
52
- if binary_paths.is_a? String
53
- binary_paths = [ binary_paths ]
57
+ binary_paths = convert_binary_paths binary_paths
58
+
59
+ # Check if the exe is already resolved.
60
+ binary_paths.delete_if do |binary_path|
61
+ @@needed_libraries_cache.include? binary_path
54
62
  end
55
63
 
64
+ return [] if binary_paths.empty?
65
+
56
66
  binary_paths.each do |binary_path|
57
67
  output = execvalue %W{ ldd #{binary_path} }
58
68
  lines = output.split "\n"
@@ -75,9 +85,7 @@ class Bwrap::Args::Library
75
85
  output_format = "%F::SEPARATOR::%n"
76
86
  scanelf_command = %W{ scanelf --nobanner --quiet --format #{output_format} --ldcache --needed }
77
87
 
78
- if binary_paths.is_a? String
79
- binary_paths = [ binary_paths ]
80
- end
88
+ binary_paths = convert_binary_paths binary_paths
81
89
 
82
90
  # Check if the exe is already resolved.
83
91
  binary_paths.delete_if do |binary_path|
@@ -97,19 +105,34 @@ class Bwrap::Args::Library
97
105
  @needed_libraries
98
106
  end
99
107
 
108
+ private def convert_binary_paths binary_paths
109
+ if binary_paths.is_a? String
110
+ [ binary_paths ]
111
+ elsif binary_paths.is_a? Pathname
112
+ [ binary_paths.to_s ]
113
+ else
114
+ binary_paths
115
+ end
116
+ end
117
+
100
118
  # Used by {#needed_libraries}.
101
119
  private def parse_scanelf_line line
102
120
  binary_path, libraries_line = line.split "::SEPARATOR::"
103
121
  libraries = libraries_line.split ","
104
122
 
123
+ # Add to needed libraries if not already added.
105
124
  (@needed_libraries & libraries).each do |library|
106
- debug "Binding #{library} as dependency of #{binary_path}"
125
+ # Probably no sense to log these unless tracing,
126
+ # as dependencies should work most of time.
127
+ #
128
+ # It also outputs tons of output for more complex programs.
129
+ trace "Binding #{library} as dependency of #{binary_path}"
107
130
  end
108
131
 
109
132
  @needed_libraries |= libraries
110
133
 
111
134
  # Also check if requisite libraries needs some libraries.
112
- inner = Bwrap::Args::Library.new
135
+ inner = Bwrap::Resolvers::Library.new
113
136
  @needed_libraries |= inner.needed_libraries libraries
114
137
 
115
138
  @@needed_libraries_cache |= libraries
@@ -128,8 +151,10 @@ class Bwrap::Args::Library
128
151
  end
129
152
 
130
153
  # Also check if requisite libraries needs some libraries.
131
- inner = Bwrap::Args::Library.new
154
+ inner = Bwrap::Resolvers::Library.new
132
155
  @needed_libraries |= inner.musl_needed_libraries library_path
156
+
157
+ @@needed_libraries_cache << library_path
133
158
  end
134
159
  end
135
160
  # class Library ended
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/execution"
4
+ require "bwrap/output"
5
+ require_relative "resolvers"
6
+
7
+ # Finds out mime type of given executable.
8
+ #
9
+ # @api private
10
+ class Bwrap::Resolvers::Mime
11
+ include Bwrap::Execution
12
+ include Bwrap::Execution::Path
13
+ include Bwrap::Output
14
+
15
+ # Name given to {#initialize}.
16
+ attr_reader :executable_name
17
+
18
+ # Either path given to {#initialize} or one parsed from shebang.
19
+ attr_reader :executable_path
20
+
21
+ # @return [String|nil] Resolved mime type of the executable
22
+ attr_reader :mime_type
23
+
24
+ def initialize executable_name, executable_path
25
+ @executable_name = executable_name
26
+ @executable_path = executable_path
27
+ end
28
+
29
+ # Checks if target executable is a script, in which case executable
30
+ # is parsed from a shebang line, if found.
31
+ def resolve_mime_type
32
+ @mime_type = execvalue %W{ file --brief --mime-type #{@executable_path} }
33
+ trace "Mime type of #{@executable_path} is #{@mime_type}"
34
+ end
35
+
36
+ # Checks if the executable is run using another executable,
37
+ # using a shebang.
38
+ #
39
+ # For example, if the executable is a bash script, returns `true`.
40
+ def interpreter?
41
+ return false unless @mime_type[0..6] == "text/x-"
42
+
43
+ @shebang_line = File.open @executable_path, &:readline
44
+ if @shebang_line[0..1] != "#!"
45
+ return false
46
+ end
47
+
48
+ true
49
+ end
50
+
51
+ # Parses shebang line to find out path to actual executable
52
+ # used to run the script.
53
+ #
54
+ # TODO: Handle this is better way.
55
+ def resolve_real_executable
56
+ # Following sets @shebang_line.
57
+ interpreter? if @shebang_line.nil?
58
+
59
+ shebang = @shebang_line
60
+ #trace "Figuring out correct executable from shebang #{shebang}"
61
+
62
+ command_line = shebang.delete_prefix("#!").strip
63
+ real_executable, args = command_line.split " ", 2
64
+
65
+ if [ "/usr/bin/env", "/bin/env" ].include? real_executable
66
+ # First argument is name of the executable, resolved from PATH.
67
+ executable_name = args.split(" ", 2).first
68
+ real_executable = which executable_name
69
+ end
70
+
71
+ debug "Parsed #{real_executable} from the script’s shebang. Using as executable."
72
+
73
+ real_executable
74
+ end
75
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bwrap/bwrap_module"
4
+
5
+ # Classes that allows resolution of executable dependencies, for example.
6
+ module Bwrap::Resolvers
7
+ end
data/lib/bwrap/version.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Bwrap
4
4
  # Current version of bwrap.
5
- VERSION = "1.1.1"
5
+ VERSION = "1.2.0"
6
6
  end
7
7
 
8
8
  require "deep-cover" if ENV["DEEP_COVER"]
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bwrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samu Voutilainen
@@ -34,7 +34,7 @@ cert_chain:
34
34
  X4ioQwEn1/9tHs19VO1CLF58451HgEo1BXd7eWLmV1V5cqw0YWok1ly4L/Su/Phf
35
35
  MRxVMHiVAqY=
36
36
  -----END CERTIFICATE-----
37
- date: 2022-06-07 00:00:00.000000000 Z
37
+ date: 2022-07-20 00:00:00.000000000 Z
38
38
  dependencies:
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: bundler
@@ -148,18 +148,18 @@ files:
148
148
  - lib/bwrap.rb
149
149
  - lib/bwrap/args/args.rb
150
150
  - lib/bwrap/args/bind.rb
151
+ - lib/bwrap/args/bind/device.rb
151
152
  - lib/bwrap/args/bind/library.rb
152
153
  - lib/bwrap/args/bind/library/ruby_binds.rb
153
- - lib/bwrap/args/bind/mime.rb
154
154
  - lib/bwrap/args/construct.rb
155
155
  - lib/bwrap/args/environment.rb
156
156
  - lib/bwrap/args/features.rb
157
157
  - lib/bwrap/args/features/binds_base.rb
158
158
  - lib/bwrap/args/features/ruby_binds.rb
159
- - lib/bwrap/args/library.rb
160
159
  - lib/bwrap/args/machine_id.rb
161
160
  - lib/bwrap/args/mount.rb
162
161
  - lib/bwrap/args/network.rb
162
+ - lib/bwrap/args/user.rb
163
163
  - lib/bwrap/bwrap.rb
164
164
  - lib/bwrap/bwrap_module.rb
165
165
  - lib/bwrap/config.rb
@@ -168,9 +168,11 @@ files:
168
168
  - lib/bwrap/config/features/ruby.rb
169
169
  - lib/bwrap/execution.rb
170
170
  - lib/bwrap/execution/exceptions.rb
171
+ - lib/bwrap/execution/exec.rb
171
172
  - lib/bwrap/execution/execute.rb
172
173
  - lib/bwrap/execution/execution.rb
173
174
  - lib/bwrap/execution/labels.rb
175
+ - lib/bwrap/execution/logging.rb
174
176
  - lib/bwrap/execution/path.rb
175
177
  - lib/bwrap/execution/popen2e.rb
176
178
  - lib/bwrap/output.rb
@@ -178,6 +180,10 @@ files:
178
180
  - lib/bwrap/output/levels.rb
179
181
  - lib/bwrap/output/log.rb
180
182
  - lib/bwrap/output/output_impl.rb
183
+ - lib/bwrap/resolvers/executable.rb
184
+ - lib/bwrap/resolvers/library.rb
185
+ - lib/bwrap/resolvers/mime.rb
186
+ - lib/bwrap/resolvers/resolvers.rb
181
187
  - lib/bwrap/version.rb
182
188
  homepage: https://git.sr.ht/~smar/ruby-bwrap
183
189
  licenses:
metadata.gz.sig CHANGED
Binary file
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bwrap/execution"
4
- require "bwrap/output"
5
-
6
- class Bwrap::Args::Bind
7
- # Inner class to clean up namespace for implementation specific reasons.
8
- #
9
- # @api private
10
- class Mime
11
- include Bwrap::Execution
12
- include Bwrap::Output
13
-
14
- # Name given to {#initialize}.
15
- attr_reader :executable_name
16
-
17
- # Either path given to {#initialize} or one parsed from shebang.
18
- attr_reader :executable_path
19
-
20
- def initialize executable_name, executable_path
21
- @executable_name = executable_name
22
- @executable_path = executable_path
23
- end
24
-
25
- # Checks if target executable is a script, in which case executable
26
- # is parsed from a shebang line, if found.
27
- #
28
- # @return false if caller should also return
29
- def resolve_mime_type
30
- mime_type = execvalue %W{ file --brief --mime-type #{@executable_path} }
31
- debug "Mime type of #{@executable_path} is #{mime_type}"
32
- return true unless mime_type[0..6] == "text/x-"
33
-
34
- shebang = File.open @executable_path, &:readline
35
- if shebang[0..1] != "#!"
36
- warn "Executable #{@executable_name} was recognized as #{mime_type} but does not have " \
37
- "proper shebang line. Skipping automatic library mounts."
38
- return false
39
- end
40
-
41
- resolve_real_executable shebang
42
-
43
- true
44
- end
45
-
46
- # Parses shebang line to find out path to actual executable
47
- # used to run the script.
48
- private def resolve_real_executable shebang
49
- #trace "Figuring out correct executable from shebang #{shebang}"
50
-
51
- command_line = shebang.delete_prefix("#!").strip
52
- real_executable, args = command_line.split " ", 2
53
-
54
- if [ "/usr/bin/env", "/bin/env" ].include? real_executable
55
- # First argument is name of the executable, resolved from PATH.
56
- executable_name = args.split(" ", 2).first
57
- real_executable = which executable_name
58
- end
59
-
60
- debug "Parsed #{real_executable} from the script’s shebang. Using as executable."
61
-
62
- @executable_path = real_executable
63
- end
64
- end
65
- end