Class: MiniMagick::Shell

Inherits:
Object
  • Object
show all
Defined in:
lib/mini_magick/shell.rb

Overview

Sends commands to the shell (more precisely, it sends commands directly to the operating system).

Instance Method Summary collapse

Instance Method Details

#execute(command, stdin: "", timeout: MiniMagick.timeout) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/mini_magick/shell.rb', line 28

def execute(command, stdin: "", timeout: MiniMagick.timeout)
  env = MiniMagick.restricted_env ? ENV.to_h.slice("HOME", "PATH", "LANG") : {} # Using #to_h for Ruby 2.5 compatibility.
  env.merge!(MiniMagick.cli_env)
  env["MAGICK_TIME_LIMIT"] = timeout.to_s if timeout

  stdout, stderr, status = log(command.join(" ")) do
    # We would ideally use Open3.capture3, but it doesn't allow us to
    # terminate the command after timing out. We can't rely solely on
    # ImageMagick's own $MAGICK_TIME_LIMIT for this, because it's only
    # checked periodically inside ImageMagick's processing loops, so it
    # can fire too late (or not at all) depending on the operation.
    Open3.popen3(env, *command, unsetenv_others: MiniMagick.restricted_env) do |stdin_io, stdout_io, stderr_io, wait_thread|
      stdin_io.binmode
      stdout_io.binmode
      stderr_io.binmode

      stdout_reader = Thread.new { stdout_io.read }
      stderr_reader = Thread.new { stderr_io.read }

      begin
        stdin_io.write(stdin)
      rescue Errno::EPIPE
      end
      stdin_io.close

      if timeout && !wait_thread.join(timeout)
        Process.kill("TERM", wait_thread.pid) rescue nil
        wait_thread.join
        fail MiniMagick::TimeoutError, "`#{command.join(" ")}` has timed out"
      end

      [stdout_reader.value, stderr_reader.value, wait_thread.value]
    end
  end

  [stdout, stderr, status&.exitstatus]
rescue Errno::ENOENT, IOError
  ["", "executable not found: \"#{command.first}\"", 127]
end

#run(command, errors: MiniMagick.errors, warnings: MiniMagick.warnings, **options) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/mini_magick/shell.rb', line 12

def run(command, errors: MiniMagick.errors, warnings: MiniMagick.warnings, **options)
  stdout, stderr, status = execute(command, **options)

  if status != 0
    if stderr.include?("time limit exceeded")
      fail MiniMagick::TimeoutError, "`#{command.join(" ")}` has timed out"
    elsif errors
      fail MiniMagick::Error, "`#{command.join(" ")}` failed with status: #{status.inspect} and error:\n#{stderr}"
    end
  end

  $stderr.print(stderr) if warnings

  [stdout, stderr, status]
end