bwrap 1.0.0.pre.alpha1
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +13 -0
- data/README.md +33 -0
- data/lib/bwrap/args/args.rb +8 -0
- data/lib/bwrap/args/bind.rb +78 -0
- data/lib/bwrap/args/construct.rb +100 -0
- data/lib/bwrap/args/environment.rb +24 -0
- data/lib/bwrap/args/machine_id.rb +95 -0
- data/lib/bwrap/args/mount.rb +24 -0
- data/lib/bwrap/config.rb +95 -0
- data/lib/bwrap/execution/execute.rb +144 -0
- data/lib/bwrap/execution/execution.rb +8 -0
- data/lib/bwrap/execution/labels.rb +66 -0
- data/lib/bwrap/execution.rb +177 -0
- data/lib/bwrap/output/colors.rb +37 -0
- data/lib/bwrap/output/levels.rb +106 -0
- data/lib/bwrap/output/log.rb +47 -0
- data/lib/bwrap/output/output.rb +8 -0
- data/lib/bwrap/output.rb +153 -0
- data/lib/bwrap/version.rb +9 -0
- data/lib/bwrap.rb +74 -0
- data.tar.gz.sig +0 -0
- metadata +177 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e4193a0724aceb154c2e9be0809366ed2998837beeeb577dc101a58e20dca97d
|
4
|
+
data.tar.gz: a927a6a6b8cb415f3983440ca0437b852582227f5b163f34143ffb375dc96a40
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f2f49d20cf15a331d34439d71ef038645bc2a5520407cdb519ea8f5e9e32db526dd7524ad35939acd8098a328d6a3c1c2542d5fde44d0adcfd8ae26f2313d5fb
|
7
|
+
data.tar.gz: ee295629e684ef280652e38a94e7731b6e3227a2bac1150a79da268f3d2972eaac889ac1928103edf9d022c84430898ebc53d008a6f181d412d3f1ede441d0a9
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright © 2021 Samu Voutilainen
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
4
|
+
purpose with or without fee is hereby granted, provided that the above
|
5
|
+
copyright notice and this permission notice appear in all copies.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
|
8
|
+
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
9
|
+
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
|
10
|
+
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
11
|
+
DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
12
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
13
|
+
OF THIS SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Bwrap
|
2
|
+
|
3
|
+
Framework to create commands for bwrap.
|
4
|
+
|
5
|
+
For now this is tailored to my needs, so this may or may not be of any use.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem "bwrap"
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install bwrap
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
For now this is under ongoing development, though semantic versioning will apply.
|
26
|
+
|
27
|
+
There is few different modules, especially execution stuff probably should be moved to its own gem.
|
28
|
+
|
29
|
+
Please see [API documentation](https://www.rubydoc.info/gems/bwrap) for usage instructions.
|
30
|
+
|
31
|
+
## Contributing
|
32
|
+
|
33
|
+
Bug reports and pull requests are welcome at https://git.sr.ht/~smar/ruby-bwrap.
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "args"
|
4
|
+
|
5
|
+
# Bind arguments for bwrap.
|
6
|
+
module Bwrap::Args::Bind
|
7
|
+
# Arguments to bind /dev/dri from host to sandbox.
|
8
|
+
private def bind_dev_dri
|
9
|
+
%w{ --dev-bind /dev/dri /dev/dri }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Arguments to bind /sys/dev/char from host to sandbox.
|
13
|
+
private def bind_sys_dev_char
|
14
|
+
%w{ --ro-bind /sys/dev/char /sys/dev/char }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Arguments to bind /sys/devices/pci0000:00 from host to sandbox.
|
18
|
+
private def bind_pci_devices
|
19
|
+
%w{ --ro-bind /sys/devices/pci0000:00 /sys/devices/pci0000:00 }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Arguments to bind home directory from sandbox directory (`#{@config.sandbox_directory}/home`)
|
23
|
+
# as `/home/#{@config.user}`.
|
24
|
+
#
|
25
|
+
# @note Requires @config.user to be set.
|
26
|
+
private def bind_home_directory
|
27
|
+
unless @config.user
|
28
|
+
raise "Tried to bind user directory without user being set."
|
29
|
+
end
|
30
|
+
|
31
|
+
home_directory = "#{@config.sandbox_directory}/home"
|
32
|
+
|
33
|
+
unless Dir.exist? home_directory
|
34
|
+
raise "Home directory #{home_directory} does not exist. You need to create it yourself."
|
35
|
+
end
|
36
|
+
|
37
|
+
@environment["HOME"] = "/home/#{@config.user}"
|
38
|
+
|
39
|
+
%W{ --bind #{home_directory} /home/#{@config.user} }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Arguments to read-only bind whole system inside sandbox.
|
43
|
+
private def full_system_mounts
|
44
|
+
bindir_mounts = []
|
45
|
+
binaries_from = @config.binaries_from
|
46
|
+
binaries_from.each do |path|
|
47
|
+
bindir_mounts << "--ro-bind" << path << path
|
48
|
+
end
|
49
|
+
@environment["PATH"] = binaries_from.join(":")
|
50
|
+
|
51
|
+
libdir_mounts = %w{
|
52
|
+
--ro-bind /lib /lib
|
53
|
+
--ro-bind /lib64 /lib64
|
54
|
+
--ro-bind /usr/lib /usr/lib
|
55
|
+
--ro-bind /usr/lib64 /usr/lib64
|
56
|
+
}
|
57
|
+
|
58
|
+
system_mounts = bindir_mounts + libdir_mounts
|
59
|
+
if debug?
|
60
|
+
debug "Using following system mounts:\n" \
|
61
|
+
"#{system_mounts}\n" \
|
62
|
+
"(Odd is key, even is value)"
|
63
|
+
end
|
64
|
+
system_mounts
|
65
|
+
end
|
66
|
+
|
67
|
+
# These are something user can specify to do custom --ro-bind binds.
|
68
|
+
private def custom_read_only_binds
|
69
|
+
return [] unless @config.ro_binds
|
70
|
+
|
71
|
+
binds = []
|
72
|
+
@config.ro_binds.each do |source_path, destination_path|
|
73
|
+
binds << "--ro-bind" << source_path.to_s << destination_path.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
binds
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tempfile"
|
4
|
+
|
5
|
+
require "bwrap/output"
|
6
|
+
require_relative "bind"
|
7
|
+
require_relative "environment"
|
8
|
+
require_relative "machine_id"
|
9
|
+
require_relative "mount"
|
10
|
+
|
11
|
+
# Constructs arguments for bwrap execution.
|
12
|
+
class Bwrap::Args::Construct
|
13
|
+
include Bwrap::Output
|
14
|
+
include Bwrap::Args::Bind
|
15
|
+
include Bwrap::Args::Mount
|
16
|
+
|
17
|
+
attr_writer :config
|
18
|
+
|
19
|
+
# Constructs arguments for bwrap execution.
|
20
|
+
def construct_bwrap_args
|
21
|
+
@environment = Bwrap::Args::Environment.new
|
22
|
+
@environment.config = @config
|
23
|
+
@machine_id = Bwrap::Args::MachineId.new
|
24
|
+
@machine_id.config = @config
|
25
|
+
|
26
|
+
[
|
27
|
+
xauthority_args,
|
28
|
+
@machine_id.machine_id,
|
29
|
+
resolv_conf,
|
30
|
+
full_system_mounts,
|
31
|
+
custom_read_only_binds,
|
32
|
+
create_user_dir,
|
33
|
+
read_only_pulseaudio,
|
34
|
+
dev_mount,
|
35
|
+
bind_dev_dri,
|
36
|
+
bind_sys_dev_char,
|
37
|
+
bind_pci_devices,
|
38
|
+
proc_mount,
|
39
|
+
tmp_as_tmpfs,
|
40
|
+
bind_home_directory,
|
41
|
+
"--unshare-all",
|
42
|
+
share_net,
|
43
|
+
hostname,
|
44
|
+
@environment.environment_variables,
|
45
|
+
"--die-with-parent",
|
46
|
+
"--new-session"
|
47
|
+
]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Performs cleanup operations after execution.
|
51
|
+
def cleanup
|
52
|
+
@machine_id&.cleanup
|
53
|
+
end
|
54
|
+
|
55
|
+
# Arguments for generating .Xauthority file.
|
56
|
+
private def xauthority_args
|
57
|
+
xauth_args = %W{ --ro-bind #{Dir.home}/.Xauthority #{Dir.home}/.Xauthority }
|
58
|
+
debug "Binding following .Xauthority file: #{Dir.home}/.Xauthority"
|
59
|
+
xauth_args
|
60
|
+
end
|
61
|
+
|
62
|
+
# Arguments to read-only bind /etc/resolv.conf.
|
63
|
+
private def resolv_conf
|
64
|
+
#source_resolv_conf = "/etc/resolv.conf"
|
65
|
+
source_resolv_conf = "/run/netconfig/resolv.conf"
|
66
|
+
debug "Binding #{source_resolv_conf} as /etc/resolv.conf"
|
67
|
+
%w{ --ro-bind /run/netconfig/resolv.conf /etc/resolv.conf }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Arguments to create `/run/user/#{uid}`.
|
71
|
+
private def create_user_dir
|
72
|
+
trace "Creating directory /run/user/#{uid}"
|
73
|
+
%W{ --dir /run/user/#{uid} }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Arguments to bind necessary pulseaudio data for audio support.
|
77
|
+
private def read_only_pulseaudio
|
78
|
+
debug "Binding pulseaudio"
|
79
|
+
%W{ --ro-bind /run/user/#{uid}/pulse /run/user/#{uid}/pulse }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Arguments to allow network connection inside sandbox.
|
83
|
+
private def share_net
|
84
|
+
verb "Sharing network"
|
85
|
+
%w{ --share-net }
|
86
|
+
end
|
87
|
+
|
88
|
+
# Arguments to set hostname to whatever is configured.
|
89
|
+
private def hostname
|
90
|
+
return unless @config.hostname
|
91
|
+
|
92
|
+
debug "Setting hostname to #{@config.hostname}"
|
93
|
+
%W{ --hostname #{@config.hostname} }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns current user id.
|
97
|
+
private def uid
|
98
|
+
Process.uid
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/output"
|
4
|
+
require_relative "args"
|
5
|
+
|
6
|
+
# Environment variable calculation for bwrap.
|
7
|
+
class Bwrap::Args::Environment < Hash
|
8
|
+
include Bwrap::Output
|
9
|
+
|
10
|
+
# Instance of {Config}.
|
11
|
+
attr_writer :config
|
12
|
+
|
13
|
+
# Returns used environment variables.
|
14
|
+
def environment_variables
|
15
|
+
if debug?
|
16
|
+
debug "Passing following environment variables to bwrap:\n" \
|
17
|
+
"#{self}"
|
18
|
+
end
|
19
|
+
|
20
|
+
map do |key, value|
|
21
|
+
[ "--setenv", key, value ]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
require "bwrap/output"
|
6
|
+
require_relative "args"
|
7
|
+
|
8
|
+
# Calculate machine id data.
|
9
|
+
class Bwrap::Args::MachineId
|
10
|
+
include Bwrap::Output
|
11
|
+
|
12
|
+
# Instance of {Config}.
|
13
|
+
attr_writer :config
|
14
|
+
|
15
|
+
# machine_id == :random
|
16
|
+
# Generates random machine id for each launch and sets it as /etc/machine_id.
|
17
|
+
# machine_id == :dummy
|
18
|
+
# Uses 10000000000000000000000000000000 as dummy machine id and sets it as /etc/machine_id.
|
19
|
+
# machine_id == true
|
20
|
+
# A file from `#{sandbox_directory}/machine_id` is bound as /etc/machine_id.
|
21
|
+
# machine_id.is_a? String
|
22
|
+
# Given file as bound as /etc/machine_id.
|
23
|
+
def machine_id
|
24
|
+
# Returning [] means that execute() will ignore this fully.
|
25
|
+
# Nil would be converted to empty string, causing spawn() to pass it as argument, causing
|
26
|
+
# bwrap to misbehave.
|
27
|
+
return [] unless @config.machine_id
|
28
|
+
|
29
|
+
machine_id = @config.machine_id
|
30
|
+
|
31
|
+
if machine_id == :random
|
32
|
+
random_machine_id
|
33
|
+
elsif machine_id == :dummy
|
34
|
+
dummy_machine_id
|
35
|
+
elsif machine_id == true
|
36
|
+
machine_id_inside_sandbox_dir @config.sandbox_directory
|
37
|
+
elsif machine_id.is_a? String
|
38
|
+
string_machine_id machine_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Removes opened temporary machine id file.
|
43
|
+
#
|
44
|
+
# Can be called safely even if no file is opened.
|
45
|
+
def cleanup
|
46
|
+
@machine_id_file&.unlink
|
47
|
+
end
|
48
|
+
|
49
|
+
# Generated random machine id.
|
50
|
+
private def random_machine_id
|
51
|
+
debug "Using random machine id as /etc/machine-id"
|
52
|
+
|
53
|
+
@machine_id_file = Tempfile.new "bwrap-random_machine_id-", @config.tmpdir
|
54
|
+
@machine_id_file.write SecureRandom.uuid.gsub("-", "")
|
55
|
+
@machine_id_file.flush
|
56
|
+
|
57
|
+
%W{ --ro-bind-data #{machine_id_file.fileno} /etc/machine-id }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Uses `10000000000000000000000000000000` as machine id.
|
61
|
+
private def dummy_machine_id
|
62
|
+
debug "Using dummy machine id as /etc/machine-id"
|
63
|
+
|
64
|
+
@machine_id_file = Tempfile.new "bwrap-dummy_machine_id-", @config.tmpdir
|
65
|
+
@machine_id_file.write "10000000000000000000000000000000"
|
66
|
+
@machine_id_file.flush
|
67
|
+
|
68
|
+
%W{ --ro-bind-data #{@machine_id_file.fileno} /etc/machine-id }
|
69
|
+
end
|
70
|
+
|
71
|
+
# Uses given file as machine id.
|
72
|
+
private def string_machine_id machine_id_file
|
73
|
+
unless File.exist? machine_id_file
|
74
|
+
raise "Configured machine_id file #{machine_id_file} does not exist."
|
75
|
+
end
|
76
|
+
|
77
|
+
debug "Binding #{machine_id_file} as /etc/machine-id"
|
78
|
+
%W{ --ro-bind #{machine_id_file} /etc/machine-id }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Uses file inside sandbox directory as machine id.
|
82
|
+
private def machine_id_inside_sandbox_dir sandbox_directory
|
83
|
+
machine_id_file = "#{sandbox_directory}/machine-id"
|
84
|
+
|
85
|
+
unless File.exist? machine_id_file
|
86
|
+
raise "#{machine_id_file} does not exist.\n" \
|
87
|
+
"Hint: you can generate dummy machine-id file with:\n" \
|
88
|
+
" echo 10000000000000000000000000000000 > #{machine_id_file}"
|
89
|
+
end
|
90
|
+
|
91
|
+
debug "Binding #{machine_id_file} as /etc/machine-id"
|
92
|
+
%W{ --ro-bind #{machine_id_file} /etc/machine-id }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "args"
|
4
|
+
|
5
|
+
# Bind arguments for bwrap.
|
6
|
+
module Bwrap::Args::Mount
|
7
|
+
# Arguments for mounting devtmpfs to /dev.
|
8
|
+
private def dev_mount
|
9
|
+
debug "Mounting new devtmpfs to /dev"
|
10
|
+
%w{ --dev /dev }
|
11
|
+
end
|
12
|
+
|
13
|
+
# Arguments for mounting procfs to /proc.
|
14
|
+
private def proc_mount
|
15
|
+
debug "Mounting new procfs to /proc"
|
16
|
+
%w{ --proc /proc }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Arguments for mounting tmpfs to /tmp.
|
20
|
+
private def tmp_as_tmpfs
|
21
|
+
debug "Mounting tmpfs to /tmp"
|
22
|
+
%w{ --tmpfs /tmp }
|
23
|
+
end
|
24
|
+
end
|
data/lib/bwrap/config.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwrap/output"
|
4
|
+
require "bwrap/version"
|
5
|
+
|
6
|
+
# Represents configuration set by user for bwrap execution.
|
7
|
+
#
|
8
|
+
# Implementation NOTE: logger methods (debug, warn and so on) can’t be used here as logger
|
9
|
+
# isn’t initialized before this class.
|
10
|
+
class Bwrap::Config
|
11
|
+
attr_accessor :hostname
|
12
|
+
|
13
|
+
# What should be used as /etc/machine_id file.
|
14
|
+
#
|
15
|
+
# If not specified, no /etc/machine_id handling is done.
|
16
|
+
#
|
17
|
+
# machine_id == :random
|
18
|
+
# Generates random machine id for each launch and sets it as /etc/machine_id.
|
19
|
+
# machine_id == :dummy
|
20
|
+
# Uses 10000000000000000000000000000000 as dummy machine id and sets it as /etc/machine_id.
|
21
|
+
# machine_id == true
|
22
|
+
# A file from #{sandbox_directory}/machine_id is bound as /etc/machine_id.
|
23
|
+
# machine_id.is_a? String
|
24
|
+
# Given file as bound as /etc/machine_id.
|
25
|
+
attr_accessor :machine_id
|
26
|
+
|
27
|
+
attr_accessor :user
|
28
|
+
|
29
|
+
# Array of directories to be bind mounted and used to construct PATH environment variable.
|
30
|
+
attr_reader :binaries_from
|
31
|
+
|
32
|
+
attr_reader :sandbox_directory
|
33
|
+
|
34
|
+
# `Hash`[`Pathname`] => `Pathname` containing custom read-only binds.
|
35
|
+
attr_reader :ro_binds
|
36
|
+
|
37
|
+
# Path to temporary directory.
|
38
|
+
#
|
39
|
+
# Defaults to Dir.tmpdir.
|
40
|
+
attr_reader :tmpdir
|
41
|
+
|
42
|
+
def initialize
|
43
|
+
@binaries_from = []
|
44
|
+
@tmpdir = Dir.tmpdir
|
45
|
+
end
|
46
|
+
|
47
|
+
def binaries_from= array
|
48
|
+
@binaries_from = []
|
49
|
+
array.each do |path|
|
50
|
+
unless Dir.exist? path
|
51
|
+
raise "Path “#{path}” given to binaries_from does not exist."
|
52
|
+
end
|
53
|
+
|
54
|
+
@binaries_from << path
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def sandbox_directory= directory
|
59
|
+
unless Dir.exist? directory
|
60
|
+
raise "Given sandbox directory #{directory} does not exist. Please create it beforehand and setup to your needs."
|
61
|
+
end
|
62
|
+
|
63
|
+
@sandbox_directory = directory
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set given hash of paths to be bound with --ro-bind.
|
67
|
+
#
|
68
|
+
# Key is source path, value is destination path.
|
69
|
+
#
|
70
|
+
# Given source paths must exist.
|
71
|
+
def ro_binds= binds
|
72
|
+
@ro_binds = {}
|
73
|
+
binds.each do |source_path, destination_path|
|
74
|
+
source_path = Pathname.new source_path
|
75
|
+
unless source_path.exist?
|
76
|
+
raise "Given read only bind does not exist. Please check path “#{source_path}” is correct."
|
77
|
+
end
|
78
|
+
|
79
|
+
destination_path = Pathname.new destination_path
|
80
|
+
|
81
|
+
@ro_binds[source_path] = destination_path
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets given directory as temporary directory for certain operations.
|
86
|
+
#
|
87
|
+
# @note Requires `dir` to be path to existing directory.
|
88
|
+
# @raise [RuntimeError] If given directory does not exist
|
89
|
+
# @param dir Path to temporary directory
|
90
|
+
def tmpdir= dir
|
91
|
+
raise "Directory to be set as a temporary directory, “#{dir}”, does not exist." unless Dir.exist? dir
|
92
|
+
|
93
|
+
@tmpdir = dir
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
# force_encoding modifies string, so can’t freeze strings.
|
4
|
+
|
5
|
+
require "bwrap/output"
|
6
|
+
require_relative "execution"
|
7
|
+
|
8
|
+
# Methods that performs actual execution logic.
|
9
|
+
#
|
10
|
+
# @api internal
|
11
|
+
class Bwrap::Execution::Execute
|
12
|
+
include Bwrap::Output
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Can be used to access pipe where `Kernel#spawn` should write its data.
|
16
|
+
attr_reader :w
|
17
|
+
|
18
|
+
# Dry run flag from parent Execution module.
|
19
|
+
attr_accessor :dry_run
|
20
|
+
end
|
21
|
+
|
22
|
+
# Formats given command for logging, depending on dry-run flag.
|
23
|
+
def self.handle_logging command, log_callback:, log:, dry_run:
|
24
|
+
# The debug message contents will always be evaluated, so can just do it like this.
|
25
|
+
log_command = calculate_log_command command
|
26
|
+
|
27
|
+
if dry_run || Bwrap::Execution::Execute.dry_run
|
28
|
+
puts "Would execute “#{log_command.force_encoding("UTF-8")}” at #{caller_locations(log_callback, 1)[0]}"
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
return unless log
|
33
|
+
|
34
|
+
msg = "Executing “#{log_command.force_encoding("UTF-8")}” at #{caller_locations(log_callback, 1)[0]}"
|
35
|
+
Bwrap::Output.debug_output msg
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return formatted command.
|
39
|
+
def self.format_command command, rootcmd:
|
40
|
+
replace_nils command
|
41
|
+
return command if rootcmd.nil?
|
42
|
+
|
43
|
+
prepend_rootcmd command, rootcmd: rootcmd
|
44
|
+
end
|
45
|
+
|
46
|
+
# Opens pipes for command output handling.
|
47
|
+
def self.open_pipes direct_output
|
48
|
+
@r, @w = IO.pipe
|
49
|
+
return unless direct_output
|
50
|
+
|
51
|
+
@pipe_w = @w
|
52
|
+
@w = $stdout
|
53
|
+
end
|
54
|
+
|
55
|
+
# Converts output to be UTF-8.
|
56
|
+
def self.process_output output:
|
57
|
+
# read_nonblock() uses read(), which always reads as ASCII-8BIT:
|
58
|
+
# In the case of an integer length, the resulting string is always in ASCII-8BIT encoding.
|
59
|
+
output.force_encoding("UTF-8").strip
|
60
|
+
end
|
61
|
+
|
62
|
+
# Checks whether execution failed and acts accordingly.
|
63
|
+
def self.handle_execution_fail fail:, error:, output:
|
64
|
+
return unless fail and !$CHILD_STATUS.success?
|
65
|
+
|
66
|
+
if error == :show and !output.empty?
|
67
|
+
Bwrap::Output.warn_output "Command failed with output:\n“#{output}”"
|
68
|
+
end
|
69
|
+
raise Bwrap::Execution::ExecutionFailed, "Command execution failed.", caller
|
70
|
+
end
|
71
|
+
|
72
|
+
# @note It makes sense for caller to just return if wait has been set and not check output.
|
73
|
+
#
|
74
|
+
# @return output from command or nil if execution should stop here.
|
75
|
+
def self.finish_execution log:, wait:, direct_output:
|
76
|
+
@w = @pipe_w if direct_output
|
77
|
+
@w.close
|
78
|
+
unless wait
|
79
|
+
@r.close
|
80
|
+
return # With wait == false, execute() stops here.
|
81
|
+
end
|
82
|
+
output = buffer_exec_output @r, log
|
83
|
+
@r.close
|
84
|
+
|
85
|
+
output
|
86
|
+
end
|
87
|
+
|
88
|
+
# Removes data in class instance variables after execution has completed either successfully or with an error.
|
89
|
+
def self.clean_variables
|
90
|
+
@w = nil
|
91
|
+
@r = nil
|
92
|
+
@pipe_w = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
# Stub to instruct implementation in subclass.
|
96
|
+
def self.prepend_rootcmd command, rootcmd:
|
97
|
+
raise NotImplementedError, "If rootcmd execution is necessary, monkey patch Bwrap::Execution::Execute " \
|
98
|
+
"to add “self.prepend_rootcmd(command, rootcmd:)” method."
|
99
|
+
end
|
100
|
+
|
101
|
+
# Used by `#handle_logging`.
|
102
|
+
private_class_method def self.calculate_log_command command
|
103
|
+
return command.dup unless command.respond_to?(:join)
|
104
|
+
|
105
|
+
temp = command.dup
|
106
|
+
|
107
|
+
# Wrap multi word arguments to quotes, for convenience.
|
108
|
+
# NOTE: This is not exactly safe, but this is only a debugging aid anyway.
|
109
|
+
temp.map! do |argument|
|
110
|
+
if argument.include? " "
|
111
|
+
escaped = argument.gsub '"', '\"'
|
112
|
+
argument = %["#{escaped}"]
|
113
|
+
end
|
114
|
+
argument
|
115
|
+
end
|
116
|
+
|
117
|
+
temp.join(" ")
|
118
|
+
end
|
119
|
+
|
120
|
+
# If command is an `Array`, Replaces nil values with "".
|
121
|
+
private_class_method def self.replace_nils command
|
122
|
+
return unless command.respond_to? :map!
|
123
|
+
|
124
|
+
command.map! do |argument|
|
125
|
+
argument.nil? && "" || argument
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Used by Execution#execute, not useful for anything else.
|
130
|
+
private_class_method def self.buffer_exec_output read, log
|
131
|
+
buffer = String.new capacity: 1024
|
132
|
+
output = ""
|
133
|
+
until read.eof?
|
134
|
+
read.read_nonblock 1024, buffer
|
135
|
+
if log
|
136
|
+
print buffer if Bwrap::Output.verbose?
|
137
|
+
Bwrap::Output::Log.write_to_log buffer
|
138
|
+
end
|
139
|
+
output << buffer
|
140
|
+
end
|
141
|
+
|
142
|
+
output
|
143
|
+
end
|
144
|
+
end
|