mxc 0.1.0-aarch64-linux → 0.2.0-aarch64-linux
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
- data/NOTICE +2 -1
- data/README.md +14 -5
- data/lib/mxc/3.2/mxc.so +0 -0
- data/lib/mxc/3.3/mxc.so +0 -0
- data/lib/mxc/3.4/mxc.so +0 -0
- data/lib/mxc/4.0/mxc.so +0 -0
- data/lib/mxc/preflight.rb +23 -3
- data/lib/mxc/result.rb +18 -3
- data/lib/mxc/sandbox.rb +115 -7
- data/lib/mxc/version.rb +1 -1
- data/lib/mxc.rb +12 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1455398da297054d82b10de4c9f2596fee0a17c0a574385d963d9e196f646767
|
|
4
|
+
data.tar.gz: 1beb64ea6f6a11905bb8aab11417a5fdd5b11129c4a59715ddb096783b1c282c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4498ae8785ed173e38b153b2d016405d94c7770654da2f28b61bf839c906e9efe4558803cb9accd17a0220b1325963963bf4eacc380a171de0aac28b6dce1c0d
|
|
7
|
+
data.tar.gz: 81cc77bde0f315ae67bc7c8b99e1d688b73aa5289b5c14295804ebc7bf6fb8380b690b9c000abd6d71e7f1cf12690dc5e9ed56028c86c2d391773449a196b776
|
data/NOTICE
CHANGED
|
@@ -5,7 +5,8 @@ This gem is an unofficial Ruby binding to MXC. It is not affiliated with or
|
|
|
5
5
|
endorsed by Microsoft.
|
|
6
6
|
|
|
7
7
|
The native extension compiles and links the mxc-sdk crate
|
|
8
|
-
(https://github.com/microsoft/mxc, https://crates.io/crates/mxc-sdk),
|
|
8
|
+
(https://github.com/microsoft/mxc, https://crates.io/crates/mxc-sdk), from
|
|
9
|
+
a fork at https://github.com/crmne/microsoft-mxc that adds resource limits,
|
|
9
10
|
which is distributed under the MIT License:
|
|
10
11
|
|
|
11
12
|
MIT License
|
data/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
Your app runs code it did not write: scripts a model generated, skills users uploaded, build steps from someone else. MXC for Ruby runs them in a sandbox that decides what they can read, write, and reach, and how
|
|
17
|
+
Your app runs code it did not write: scripts a model generated, skills users uploaded, build steps from someone else. MXC for Ruby runs them in a sandbox that decides what they can read, write, and reach, how long they get, and how much CPU and memory they can use.
|
|
18
18
|
|
|
19
19
|
```ruby
|
|
20
20
|
result = MXC.run("python3 build.py", readable: "app/skills", writable: "tmp/out", timeout: 30)
|
|
@@ -22,7 +22,7 @@ result.stdout
|
|
|
22
22
|
result.success?
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
It is an unofficial Ruby binding to [Microsoft's MXC](https://github.com/microsoft/mxc), the MIT-licensed sandbox library. Not affiliated with or endorsed by Microsoft.
|
|
25
|
+
It is an unofficial Ruby binding to [Microsoft's MXC](https://github.com/microsoft/mxc), the MIT-licensed sandbox library. Not affiliated with or endorsed by Microsoft. Resource limits come from [a fork of MXC](https://github.com/crmne/microsoft-mxc) until they are [part of MXC itself](https://github.com/microsoft/mxc/issues/1498).
|
|
26
26
|
|
|
27
27
|
## Installation
|
|
28
28
|
|
|
@@ -68,6 +68,14 @@ result = sandbox.run("pip download --no-deps -d /tmp/wheels requests")
|
|
|
68
68
|
result.blocked # => hosts the allow-list refused
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
Limit what a command can use, and lower its priority so your app stays responsive:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
result = sandbox.with_limits(cpu_time: 2.minutes, memory: 2.gigabytes).with_priority(:low).run("python3 build.py")
|
|
75
|
+
result.cpu_time # => 3.42
|
|
76
|
+
result.limit_exceeded # => nil, or :cpu_time when it ran out
|
|
77
|
+
```
|
|
78
|
+
|
|
71
79
|
Stream output as it arrives:
|
|
72
80
|
|
|
73
81
|
```ruby
|
|
@@ -92,6 +100,7 @@ class SkillSandbox < MXC::Sandbox
|
|
|
92
100
|
writable "tmp/skills"
|
|
93
101
|
network false
|
|
94
102
|
timeout 30
|
|
103
|
+
limits cpu_time: 60, memory: 1.gigabyte
|
|
95
104
|
end
|
|
96
105
|
|
|
97
106
|
SkillSandbox.new.run("python3 app/skills/build.py")
|
|
@@ -108,10 +117,10 @@ MXC.backends # => [#<MXC::Backend name=:bubblewrap ...>]
|
|
|
108
117
|
|
|
109
118
|
## Platforms
|
|
110
119
|
|
|
111
|
-
| Platform | Backend | 0.
|
|
120
|
+
| Platform | Backend | 0.2.0 |
|
|
112
121
|
| --- | --- | --- |
|
|
113
|
-
| Linux | Bubblewrap | Supported and tested |
|
|
114
|
-
| macOS | Seatbelt | Supported and tested |
|
|
122
|
+
| Linux | Bubblewrap | Supported and tested, without process limits |
|
|
123
|
+
| macOS | Seatbelt | Supported and tested, without memory or process limits |
|
|
115
124
|
| Windows 11 24H2 and later | ProcessContainer | Supported and tested, without host allow-lists |
|
|
116
125
|
|
|
117
126
|
On Windows, a single String runs through `cmd.exe` instead of `sh`. See [Running Commands](https://crmne.github.io/mxc/running-commands/#windows) and [Policies](https://crmne.github.io/mxc/policies/#windows) for what else differs.
|
data/lib/mxc/3.2/mxc.so
CHANGED
|
Binary file
|
data/lib/mxc/3.3/mxc.so
CHANGED
|
Binary file
|
data/lib/mxc/3.4/mxc.so
CHANGED
|
Binary file
|
data/lib/mxc/4.0/mxc.so
CHANGED
|
Binary file
|
data/lib/mxc/preflight.rb
CHANGED
|
@@ -12,15 +12,35 @@ module MXC
|
|
|
12
12
|
'a sandbox to a proxy only by also accepting inbound connections to it'
|
|
13
13
|
WINDOWS_DENIED = 'Leave the paths out of readable and writable instead, or run on a Windows build where ' \
|
|
14
14
|
'MXC.supports?(:filesystem_denied_paths) is true'
|
|
15
|
+
MACOS_MEMORY = 'Leave memory out of the limits on macOS, which rejects memory rlimits. ' \
|
|
16
|
+
'Linux and Windows support it'
|
|
17
|
+
PROCESS_LIMIT = 'Leave processes out of the limits. Only Windows can count the processes of one sandbox; ' \
|
|
18
|
+
'in a container, limit them with docker run --pids-limit'
|
|
15
19
|
|
|
16
20
|
module_function
|
|
17
21
|
|
|
18
|
-
def check!(network:, denied: [])
|
|
19
|
-
error = problem(network:, denied:)
|
|
22
|
+
def check!(network:, denied: [], resources: nil)
|
|
23
|
+
error = problem(network:, denied:, resources:)
|
|
20
24
|
raise error if error
|
|
21
25
|
end
|
|
22
26
|
|
|
23
|
-
def problem(network:, denied: [], path: ENV.fetch('PATH', ''), tun: TUN)
|
|
27
|
+
def problem(network:, denied: [], resources: nil, path: ENV.fetch('PATH', ''), tun: TUN)
|
|
28
|
+
resource_problem(resources) || host_problem(network, denied, path, tun)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resource_problem(resources)
|
|
32
|
+
return if resources.nil? || windows?
|
|
33
|
+
|
|
34
|
+
if resources[:max_processes]
|
|
35
|
+
UnsupportedError.new("Process limits are not supported on #{linux? ? 'Linux' : 'macOS'}",
|
|
36
|
+
code: :backend_unavailable, remediation: PROCESS_LIMIT)
|
|
37
|
+
elsif resources[:memory_mb] && !linux?
|
|
38
|
+
UnsupportedError.new('Memory limits are not supported on macOS',
|
|
39
|
+
code: :backend_unavailable, remediation: MACOS_MEMORY)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def host_problem(network, denied, path, tun)
|
|
24
44
|
return windows_problem(network, denied) if windows?
|
|
25
45
|
return unless network && linux?
|
|
26
46
|
|
data/lib/mxc/result.rb
CHANGED
|
@@ -25,7 +25,18 @@ module MXC
|
|
|
25
25
|
# Returns the backend that ran the command, such as <tt>:bubblewrap</tt>.
|
|
26
26
|
attr_reader :backend
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
# Returns the CPU time the command used, user and system, in seconds as a Float. It
|
|
29
|
+
# includes every process the command started and waited for. Returns nil unless the
|
|
30
|
+
# sandbox has limits or a priority.
|
|
31
|
+
attr_reader :cpu_time
|
|
32
|
+
|
|
33
|
+
# Returns the limit the command ran into: <tt>:cpu_time</tt>, <tt>:memory</tt>,
|
|
34
|
+
# <tt>:processes</tt>, or nil. Linux and macOS report <tt>:cpu_time</tt> only, because a
|
|
35
|
+
# program that cannot allocate memory decides itself how to fail.
|
|
36
|
+
attr_reader :limit_exceeded
|
|
37
|
+
|
|
38
|
+
def initialize(stdout:, stderr:, exit_code:, timed_out:, warnings:, blocked:, backend:, # :nodoc:
|
|
39
|
+
cpu_time: nil, limit_exceeded: nil)
|
|
29
40
|
@stdout = text(stdout)
|
|
30
41
|
@stderr = text(stderr)
|
|
31
42
|
@exit_code = exit_code
|
|
@@ -33,6 +44,8 @@ module MXC
|
|
|
33
44
|
@warnings = warnings.freeze
|
|
34
45
|
@blocked = blocked.freeze
|
|
35
46
|
@backend = backend
|
|
47
|
+
@cpu_time = cpu_time
|
|
48
|
+
@limit_exceeded = limit_exceeded
|
|
36
49
|
end
|
|
37
50
|
|
|
38
51
|
# Returns true when the command exited with status 0 before its timeout.
|
|
@@ -47,11 +60,13 @@ module MXC
|
|
|
47
60
|
|
|
48
61
|
# Returns the result as a Hash.
|
|
49
62
|
def to_h
|
|
50
|
-
{ stdout:, stderr:, exit_code:, timed_out: timed_out?, warnings:, blocked:, backend
|
|
63
|
+
{ stdout:, stderr:, exit_code:, timed_out: timed_out?, warnings:, blocked:, backend:, cpu_time:,
|
|
64
|
+
limit_exceeded: }
|
|
51
65
|
end
|
|
52
66
|
|
|
53
67
|
def inspect # :nodoc:
|
|
54
|
-
"
|
|
68
|
+
usage = " cpu_time=#{cpu_time.round(3)} limit_exceeded=#{limit_exceeded.inspect}" if cpu_time
|
|
69
|
+
"#<MXC::Result exit_code=#{exit_code.inspect} timed_out=#{timed_out?}#{usage} backend=#{backend.inspect}>"
|
|
55
70
|
end
|
|
56
71
|
|
|
57
72
|
private
|
data/lib/mxc/sandbox.rb
CHANGED
|
@@ -22,19 +22,25 @@ module MXC
|
|
|
22
22
|
# writable "tmp/skills"
|
|
23
23
|
# network false
|
|
24
24
|
# timeout 30
|
|
25
|
+
# limits cpu_time: 60, memory: 2 * 1024**3
|
|
26
|
+
# priority :low
|
|
25
27
|
# end
|
|
26
28
|
#
|
|
27
29
|
# SkillSandbox.new.run("python3 app/skills/build.py")
|
|
28
30
|
class Sandbox
|
|
29
31
|
BACKENDS = %i[auto bubblewrap seatbelt process_container].freeze # :nodoc:
|
|
30
|
-
OPTIONS = %i[readable writable denied network env directory timeout backend].freeze # :nodoc:
|
|
32
|
+
OPTIONS = %i[readable writable denied network env directory timeout backend limits priority].freeze # :nodoc:
|
|
31
33
|
HOST_RESOLVER_SOCKETS = %w[/run/systemd/resolve].freeze # :nodoc:
|
|
32
34
|
WINDOWS_ENV = %w[SYSTEMROOT WINDIR SYSTEMDRIVE COMSPEC PATHEXT PATH LOCALAPPDATA TEMP TMP
|
|
33
35
|
PROCESSOR_ARCHITECTURE NUMBER_OF_PROCESSORS OS].freeze # :nodoc:
|
|
34
36
|
BATCH_FILE = /\.(bat|cmd)\z/i # :nodoc:
|
|
35
37
|
MAX_TIMEOUT_MS = (2**32) - 1 # :nodoc:
|
|
38
|
+
LIMITS = %i[cpu_time memory processes].freeze # :nodoc:
|
|
39
|
+
PRIORITIES = { normal: nil, low: 'low', lowest: 'idle' }.freeze # :nodoc:
|
|
40
|
+
MEGABYTE = 1024 * 1024 # :nodoc:
|
|
36
41
|
DEFAULTS = {
|
|
37
|
-
readable: [], writable: [], denied: [], env: {}, network: false, directory: nil, timeout: nil, backend: nil
|
|
42
|
+
readable: [], writable: [], denied: [], env: {}, network: false, directory: nil, timeout: nil, backend: nil,
|
|
43
|
+
limits: {}, priority: nil
|
|
38
44
|
}.freeze # :nodoc:
|
|
39
45
|
|
|
40
46
|
class << self
|
|
@@ -62,6 +68,12 @@ module MXC
|
|
|
62
68
|
# Sets every instance's backend.
|
|
63
69
|
def backend(name) = configure { |sandbox| sandbox.with_backend(name) }
|
|
64
70
|
|
|
71
|
+
# Sets every instance's resource limits. Takes the same arguments as #with_limits.
|
|
72
|
+
def limits(...) = configure { |sandbox| sandbox.with_limits(...) }
|
|
73
|
+
|
|
74
|
+
# Sets every instance's scheduling priority: <tt>:normal</tt>, <tt>:low</tt>, or <tt>:lowest</tt>.
|
|
75
|
+
def priority(level) = configure { |sandbox| sandbox.with_priority(level) }
|
|
76
|
+
|
|
65
77
|
def defaults # :nodoc:
|
|
66
78
|
@defaults ||= allocate.tap { |sandbox| sandbox.send(:inherit, self == Sandbox ? nil : superclass.defaults) }
|
|
67
79
|
end
|
|
@@ -95,9 +107,11 @@ module MXC
|
|
|
95
107
|
|
|
96
108
|
# Returns a new sandbox with the class's defaults, then applies +options+ through
|
|
97
109
|
# the matching setters: <tt>readable:</tt>, <tt>writable:</tt>, <tt>denied:</tt>,
|
|
98
|
-
# <tt>network:</tt>, <tt>env:</tt>, <tt>directory:</tt>, <tt>timeout:</tt
|
|
110
|
+
# <tt>network:</tt>, <tt>env:</tt>, <tt>directory:</tt>, <tt>timeout:</tt>, <tt>backend:</tt>,
|
|
111
|
+
# <tt>limits:</tt> and <tt>priority:</tt>.
|
|
99
112
|
#
|
|
100
113
|
# MXC::Sandbox.new(readable: "app/skills", network: { allow: ["pypi.org"] }, timeout: 30)
|
|
114
|
+
# MXC::Sandbox.new(limits: { cpu_time: 60, memory: 2 * 1024**3 }, priority: :low)
|
|
101
115
|
def initialize(**options)
|
|
102
116
|
unknown = options.keys - OPTIONS
|
|
103
117
|
raise ArgumentError, "unknown option#{'s' if unknown.size > 1}: #{unknown.join(', ')}" if unknown.any?
|
|
@@ -135,6 +149,13 @@ module MXC
|
|
|
135
149
|
# Returns the backend, falling back to MXC.config.backend.
|
|
136
150
|
def backend = @settings[:backend] || MXC.config.backend
|
|
137
151
|
|
|
152
|
+
# Returns the resource limits, such as <tt>{ cpu_time: 60, memory: 2147483648 }</tt>.
|
|
153
|
+
# A limit that is not set is absent.
|
|
154
|
+
def limits = @settings[:limits].dup.freeze
|
|
155
|
+
|
|
156
|
+
# Returns the scheduling priority: <tt>:normal</tt>, <tt>:low</tt>, or <tt>:lowest</tt>.
|
|
157
|
+
def priority = @settings[:priority] || :normal
|
|
158
|
+
|
|
138
159
|
# Adds paths the command can read. Accepts Strings and Pathnames; relative paths
|
|
139
160
|
# are expanded from the current directory when the command runs.
|
|
140
161
|
def with_readable(*paths)
|
|
@@ -204,6 +225,42 @@ module MXC
|
|
|
204
225
|
self
|
|
205
226
|
end
|
|
206
227
|
|
|
228
|
+
# Sets resource limits. Each process the command starts gets its own CPU time and memory.
|
|
229
|
+
#
|
|
230
|
+
# <tt>cpu_time:</tt> is the CPU time each process may use, in seconds. It accepts
|
|
231
|
+
# anything that responds to +to_f+, such as <tt>90</tt> or <tt>2.minutes</tt>, and is
|
|
232
|
+
# rounded up to whole seconds on Linux and macOS. A process that uses it up is stopped.
|
|
233
|
+
#
|
|
234
|
+
# <tt>memory:</tt> is the memory each process may allocate, in bytes. It accepts
|
|
235
|
+
# anything that responds to +to_i+, such as <tt>2 * 1024**3</tt> or
|
|
236
|
+
# <tt>2.gigabytes</tt>, and is rounded up to whole megabytes. Allocations beyond it
|
|
237
|
+
# fail. Not available on macOS.
|
|
238
|
+
#
|
|
239
|
+
# <tt>processes:</tt> is how many processes the command may run at the same time.
|
|
240
|
+
# Windows only.
|
|
241
|
+
#
|
|
242
|
+
# A limit given as +nil+ is removed, and <tt>with_limits(nil)</tt> removes them all.
|
|
243
|
+
# Raises ArgumentError for an unknown limit or a value that is not positive.
|
|
244
|
+
#
|
|
245
|
+
# sandbox.with_limits(cpu_time: 2.minutes, memory: 2.gigabytes)
|
|
246
|
+
def with_limits(reset = (keep = true), **limits)
|
|
247
|
+
unless keep
|
|
248
|
+
raise ArgumentError, "pass limits as keywords, or nil to remove them, got #{reset.inspect}" unless reset.nil?
|
|
249
|
+
|
|
250
|
+
@settings[:limits].clear
|
|
251
|
+
end
|
|
252
|
+
limits.each { |name, value| set_limit(name, value) }
|
|
253
|
+
self
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Sets the scheduling priority of every process the command starts: <tt>:normal</tt>
|
|
257
|
+
# (the default), <tt>:low</tt> (nice 10 on Linux and macOS, below normal on
|
|
258
|
+
# Windows), or <tt>:lowest</tt> (nice 19, or idle on Windows).
|
|
259
|
+
def with_priority(level)
|
|
260
|
+
@settings[:priority] = level && priority_name(level)
|
|
261
|
+
self
|
|
262
|
+
end
|
|
263
|
+
|
|
207
264
|
# Runs +command+ and returns its Result once it finishes. Like Kernel#system, one
|
|
208
265
|
# String runs through the platform shell: <tt>sh -c</tt>, or <tt>cmd.exe /d /s /c</tt>
|
|
209
266
|
# on Windows. Two or more Strings are a program and its arguments, and each argument
|
|
@@ -241,15 +298,16 @@ module MXC
|
|
|
241
298
|
# Returns the Availability of this sandbox on this host, with the reason and
|
|
242
299
|
# remediation when it cannot run.
|
|
243
300
|
def availability
|
|
244
|
-
problem = Preflight.problem(network:, denied:)
|
|
301
|
+
problem = Preflight.problem(network:, denied:, resources:)
|
|
245
302
|
return MXC.availability(backend:) unless problem
|
|
246
303
|
|
|
247
304
|
Availability.new(backend:, supported: false, reason: problem.message, remediation: problem.remediation)
|
|
248
305
|
end
|
|
249
306
|
|
|
250
307
|
def inspect # :nodoc:
|
|
308
|
+
resources = " limits=#{limits.inspect} priority=#{priority.inspect}" if self.resources
|
|
251
309
|
"#<#{self.class.name || 'MXC::Sandbox'} backend=#{backend.inspect} network=#{network.inspect} " \
|
|
252
|
-
"readable=#{readable.inspect} writable=#{writable.inspect} timeout=#{timeout.inspect}>"
|
|
310
|
+
"readable=#{readable.inspect} writable=#{writable.inspect} timeout=#{timeout.inspect}#{resources}>"
|
|
253
311
|
end
|
|
254
312
|
|
|
255
313
|
protected
|
|
@@ -266,10 +324,42 @@ module MXC
|
|
|
266
324
|
case name
|
|
267
325
|
when :readable, :writable, :denied then public_send(:"with_#{name}", *Array(value))
|
|
268
326
|
when :network then value.is_a?(Hash) ? with_network(**value) : with_network(value)
|
|
327
|
+
when :limits then value.nil? ? with_limits(nil) : with_limits(**value)
|
|
269
328
|
else public_send(:"with_#{name}", value)
|
|
270
329
|
end
|
|
271
330
|
end
|
|
272
331
|
|
|
332
|
+
def set_limit(name, value)
|
|
333
|
+
unless LIMITS.include?(name)
|
|
334
|
+
raise ArgumentError, "unknown limit #{name.inspect}, expected one of #{LIMITS.map(&:inspect).join(', ')}"
|
|
335
|
+
end
|
|
336
|
+
return @settings[:limits].delete(name) if value.nil?
|
|
337
|
+
|
|
338
|
+
@settings[:limits][name] = positive_limit(name, value)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def positive_limit(name, value)
|
|
342
|
+
amount = limit_amount(name, value)
|
|
343
|
+
raise ArgumentError, "#{name} must be a positive number, got #{value.inspect}" unless amount&.positive?
|
|
344
|
+
|
|
345
|
+
value
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def limit_amount(name, value)
|
|
349
|
+
return (value if value.is_a?(Integer)) if name == :processes
|
|
350
|
+
|
|
351
|
+
conversion = name == :cpu_time ? :to_f : :to_i
|
|
352
|
+
value.public_send(conversion) if value.respond_to?(conversion)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def priority_name(level)
|
|
356
|
+
level = level.to_sym
|
|
357
|
+
return level if PRIORITIES.key?(level)
|
|
358
|
+
|
|
359
|
+
raise ArgumentError,
|
|
360
|
+
"unknown priority #{level.inspect}, expected one of #{PRIORITIES.keys.map(&:inspect).join(', ')}"
|
|
361
|
+
end
|
|
362
|
+
|
|
273
363
|
def add_paths(name, paths)
|
|
274
364
|
list = @settings[name]
|
|
275
365
|
paths == [nil] ? list.clear : list.concat(paths.flatten.map { |path| File.path(path) })
|
|
@@ -322,7 +412,7 @@ module MXC
|
|
|
322
412
|
end
|
|
323
413
|
|
|
324
414
|
def request
|
|
325
|
-
Preflight.check!(network:, denied:)
|
|
415
|
+
Preflight.check!(network:, denied:, resources:)
|
|
326
416
|
{
|
|
327
417
|
readonly: expand(readable),
|
|
328
418
|
readwrite: expand(writable),
|
|
@@ -332,10 +422,28 @@ module MXC
|
|
|
332
422
|
timeout_ms: timeout && [(timeout.to_f * 1000).ceil, MAX_TIMEOUT_MS].min,
|
|
333
423
|
backend:,
|
|
334
424
|
network: network == true,
|
|
335
|
-
allow: network.is_a?(Hash) ? network[:allow] : nil
|
|
425
|
+
allow: network.is_a?(Hash) ? network[:allow] : nil,
|
|
426
|
+
resources:
|
|
336
427
|
}
|
|
337
428
|
end
|
|
338
429
|
|
|
430
|
+
protected
|
|
431
|
+
|
|
432
|
+
# The resource limits as MXC takes them, or nil when there are none.
|
|
433
|
+
def resources
|
|
434
|
+
limits = @settings[:limits]
|
|
435
|
+
return if limits.empty? && priority == :normal
|
|
436
|
+
|
|
437
|
+
{
|
|
438
|
+
cpu_time_ms: limits[:cpu_time] && (limits[:cpu_time].to_f * 1000).ceil,
|
|
439
|
+
memory_mb: limits[:memory] && -(-limits[:memory].to_i / MEGABYTE),
|
|
440
|
+
max_processes: limits[:processes],
|
|
441
|
+
priority: PRIORITIES.fetch(priority)
|
|
442
|
+
}.compact
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
private
|
|
446
|
+
|
|
339
447
|
def expand(paths)
|
|
340
448
|
paths.map { |path| native_path(File.expand_path(path)) }
|
|
341
449
|
end
|
data/lib/mxc/version.rb
CHANGED
data/lib/mxc.rb
CHANGED
|
@@ -19,6 +19,11 @@ require 'mxc/sandbox'
|
|
|
19
19
|
# result = MXC.run("python3 build.py", readable: "app/skills", writable: "tmp/out", timeout: 30)
|
|
20
20
|
# result.stdout
|
|
21
21
|
module MXC
|
|
22
|
+
RESOURCE_CAPABILITIES = {
|
|
23
|
+
cpu_time_limit: { cpu_time_ms: 1 }, memory_limit: { memory_mb: 1 },
|
|
24
|
+
process_limit: { max_processes: 1 }, priority: { priority: 'low' }
|
|
25
|
+
}.freeze # :nodoc:
|
|
26
|
+
|
|
22
27
|
@availability = {}
|
|
23
28
|
@availability_lock = Mutex.new
|
|
24
29
|
|
|
@@ -85,13 +90,19 @@ module MXC
|
|
|
85
90
|
# Returns true when this host supports +capability+ with the configured backend.
|
|
86
91
|
#
|
|
87
92
|
# +capability+ is <tt>:network</tt> for unrestricted network access,
|
|
88
|
-
# <tt>:network_policy</tt> for host allow-lists,
|
|
93
|
+
# <tt>:network_policy</tt> for host allow-lists, <tt>:cpu_time_limit</tt>,
|
|
94
|
+
# <tt>:memory_limit</tt>, <tt>:process_limit</tt> and <tt>:priority</tt> for the
|
|
95
|
+
# matching Sandbox#with_limits and Sandbox#with_priority settings, or any capability a
|
|
96
|
+
# Backend reports.
|
|
97
|
+
#
|
|
98
|
+
# MXC.supports?(:memory_limit) # => false on macOS
|
|
89
99
|
def supports?(capability)
|
|
90
100
|
return false unless supported?
|
|
91
101
|
|
|
92
102
|
case capability
|
|
93
103
|
when :network then Preflight.problem(network: true).nil?
|
|
94
104
|
when :network_policy then Preflight.problem(network: { allow: [] }).nil? && proxy_enforcement?
|
|
105
|
+
when *RESOURCE_CAPABILITIES.keys then Preflight.resource_problem(RESOURCE_CAPABILITIES[capability]).nil?
|
|
95
106
|
else configured_backends.any? { |backend| backend.supports?(capability) }
|
|
96
107
|
end
|
|
97
108
|
end
|