bwrap 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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