File: shell.rb

package info (click to toggle)
ruby-mini-magick 4.10.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, sid
  • size: 1,588 kB
  • sloc: ruby: 1,859; sh: 7; makefile: 4
file content (81 lines) | stat: -rw-r--r-- 2,384 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
require "timeout"
require "benchmark"

module MiniMagick
  ##
  # Sends commands to the shell (more precisely, it sends commands directly to
  # the operating system).
  #
  # @private
  #
  class Shell

    def run(command, options = {})
      stdout, stderr, status = execute(command, stdin: options[:stdin])

      if status != 0 && options.fetch(:whiny, MiniMagick.whiny)
        fail MiniMagick::Error, "`#{command.join(" ")}` failed with error:\n#{stderr}"
      end

      $stderr.print(stderr) unless options[:stderr] == false

      [stdout, stderr, status]
    end

    def execute(command, options = {})
      stdout, stderr, status =
        log(command.join(" ")) do
          send("execute_#{MiniMagick.shell_api.gsub("-", "_")}", command, options)
        end

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

    private

    def execute_open3(command, options = {})
      require "open3"

      # We would ideally use Open3.capture3, but it wouldn't allow us to
      # terminate the command after timing out.
      Open3.popen3(*command) do |in_w, out_r, err_r, thread|
        [in_w, out_r, err_r].each(&:binmode)
        stdout_reader = Thread.new { out_r.read }
        stderr_reader = Thread.new { err_r.read }
        begin
          in_w.write options[:stdin].to_s
        rescue Errno::EPIPE
        end
        in_w.close

        begin
          Timeout.timeout(MiniMagick.timeout) { thread.join }
        rescue Timeout::Error
          Process.kill("TERM", thread.pid) rescue nil
          Process.waitpid(thread.pid)      rescue nil
          raise Timeout::Error, "MiniMagick command timed out: #{command}"
        end

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

    def execute_posix_spawn(command, options = {})
      require "posix-spawn"
      child = POSIX::Spawn::Child.new(*command, input: options[:stdin].to_s, timeout: MiniMagick.timeout)
      [child.out, child.err, child.status]
    rescue POSIX::Spawn::TimeoutExceeded
      raise Timeout::Error, "MiniMagick command timed out: #{command}"
    end

    def log(command, &block)
      value = nil
      duration = Benchmark.realtime { value = block.call }
      MiniMagick.logger.debug "[%.2fs] %s" % [duration, command]
      value
    end

  end
end