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