Module: Lrun

Defined in:
lib/lrun.rb

Overview

Run program using lrun. Require external lrun binary.

Example

Lrun.run('foo', Lrun.merge_options({:max_memory => 2 ** 20, :max_cpu_time => 5}, {:network => false}))

Defined Under Namespace

Classes: LrunError, Result, Runner

Constant Summary collapse

LRUN_BINARY =

Name of lrun executable

'lrun'
LRUN_PATH =

Full path of lrun executable, automatically detected using LRUN_BINARY and PATH environment variable

ENV['PATH'].split(':').map{|p| File.join(p, LRUN_BINARY)}.find{|p| File.executable? p}
LRUN_OPTIONS =

Available lrun options, and whether they can occur multiple times (1: no, 2: yes)

{
  :max_cpu_time => 1,
  :max_real_time => 1,
  :max_memory => 1,
  :max_output => 1,
  :max_nprocess => 1,
  :max_rtprio => 1,
  :max_nfile => 1,
  :max_stack => 1,
  :isolate_process => 1,
  :basic_devices => 1,
  :reset_env => 1,
  :network => 1,
  :chroot => 1,
  :chdir => 1,
  :nice => 1,
  :umask => 1,
  :uid => 1,
  :gid => 1,
  :interval => 1,
  :cgname => 1,
  :bindfs => 2,
  :cgroup_option => 2,
  :tmpfs => 2,
  :env => 2,
  :fd => 2,
  :group => 2,
  :cmd => 2,
}
TRUNCATE_OUTPUT_LENGTH =

Keep how many bytes of stdout and stderr, can be overrided using options[:truncate]

4096

Class Method Summary collapse

Class Method Details

.available!Object

Complain if lrun binary is not available

Raises:


272
273
274
# File 'lib/lrun.rb', line 272

def self.available!
  raise LrunError, "#{LRUN_BINARY} not found in PATH. Please install lrun first." unless available?
end

.available?Bool

Check if lrun binary exists

Returns:

  • (Bool)

    whether lrun binary is found


267
268
269
# File 'lib/lrun.rb', line 267

def self.available?
  !LRUN_PATH.nil?
end

.merge_options(*options) ⇒ Hash

Merge options so that it can be used in run.

Example

Lrun.merge_options({:uid => 1000}, {:gid => 100})
# => {:uid=>1000, :gid=>100}

Lrun.merge_options({:nice => 1, :uid => 1001}, {:nice => 2})
# => {:nice=>2, :uid=>1000}

Lrun.merge_options({:fd => [4, 6]}, {:fd => 5}, {:fd => 7})
# => {:fd=>[4, 6, 5, 7]}

Lrun.merge_options({:env => {'A'=>'1', 'B' => '2'}}, {:env => {'C' => '3'}})
# => {:env=>[["A", "1"], ["B", "2"], ["C", "3"]]}

Lrun.merge_options({:uid => 1000}, {:uid => nil})
# => {}

Lrun.merge_options({:fd => [4]}, {:fd => 5}, {:fd => nil})
# => {}

Lrun.merge_options({:network => true, :chdir => '/tmp', :bindfs => {'/a' => '/b'}},
                   {:network => nil, :bindfs => {'/c' => '/d'}})
# => {:chdir=>"/tmp", :bindfs=>[["/a", "/b"], ["/c", "/d"]]}

Parameters:

  • options (Array<Hash>)

    options to be merged

Returns:

Raises:

  • (TypeError)

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/lrun.rb', line 148

def self.merge_options(*options)
  # Remove nil
  options.compact!

  # Check type of options
  raise TypeError, 'options should be Hash' unless options.all? { |o| o.is_a? Hash }

  # Merge options
  options.inject({}) do |result, option|
    option.each do |k, v|
      # Remove an option using nil
      if v.nil?
        result.delete k
        next
      end

      # Append to or Replace an option
      case LRUN_OPTIONS[k]
      when 2
        # Append to previous options
        result[k] ||= []
        result[k] += [*v]
      else
        # Overwrite previous option
        result[k] = v
      end
    end
    result
  end
end

.run(commands, options = {}) ⇒ Lrun::Result

Run program using lrun binary.

Example

Lrun.run('echo hello')
# => #<struct Lrun::Result
#             memory=262144, cputime=0.002,
#             exceed=nil, exitcode=0, signal=nil,
#             stdout="hello\n", stderr="">

Lrun.run('java', :max_memory => 2 ** 19, :stdout => '/tmp/out.txt')
# => #<struct Lrun::Result
#             memory=524288, cputime=0.006,
#             exceed=:memory, exitcode=0, signal=nil,
#             stdout=nil, stderr="">

Lrun.run('sleep 30', :max_real_time => 1, :stderr => '/dev/null')
#  => #<struct Lrun::Result
#              memory=262144, cputime=0.002,
#              exceed=:time, exitcode=0, signal=nil,
#              stdout="", stderr=nil>

Lrun.run('cat', :max_output => 100, :stdin => '/dev/urandom', :truncate => 2)
# => #<struct Lrun::Result
#             memory=782336, cputime=0.05,
#             exceed=:output, exitcode=0, signal=nil,
#             stdout="U\xE1", stderr="">

Parameters:

  • commands (Array<String>, String)

    commands to be executed

  • options (Hash) (defaults to: {})

    options for lrun. Besides options in LRUN_OPTIONS, there are some additional options available:

    truncate

    maximum bytes read for stderr and stdout (default: TRUNCATE_OUTPUT_LENGTH).

    stdin

    stdin file path (default: no input).

    stdout

    stdout file path (default: a tempfile, will be deleted automatically). If this option is set, the returned result will have no stdout, you should read and delete stdout file manually.

    stderr

    stderr file path (default: a tempfile, will be deleted automatically). If this option is set, the returned result will have no stderr, you should read and delete stderr file manually.

    Note: lrun chroot and mounts does not affect above paths.

Returns:


231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/lrun.rb', line 231

def self.run(commands, options = {})
  # Make sure lrun binary is available
  available!

  # Temp files storing stdout and stderr of target process
  tmp_out = tmp_err = nil

  # Create temp stdout, stderr files if user does not redirect them
  options = options.dup
  options[:stdout] ||= (tmp_out = Tempfile.new("lrun.#{$$}.out")).path
  options[:stderr] ||= (tmp_err = Tempfile.new("lrun.#{$$}.err")).path

  IO.pipe do |rfd, wfd|
    # Keep pid of lrun process for checking its status
    pid = spawn_lrun commands, options, wfd

    # Read fd 3, where lrun write its report
    wfd.close
    report = rfd.read

    # Check if lrun exits normally
    stat = Process.wait2(pid)[-1]
    if stat.signaled? || stat.exitstatus != 0
      raise LrunError, "lrun exits abnormally: #{stat}. #{tmp_err.read unless tmp_err.nil?}"
    end

    # Build and return result
    build_result report, tmp_out, tmp_err, options[:truncate]
  end
ensure
  clean_tmpfile [tmp_out, tmp_err]
end