PageRenderTime 57ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/linux/right_popen.rb

https://github.com/gchpaco/right_popen
Ruby | 129 lines | 76 code | 17 blank | 36 comment | 3 complexity | 795fb57b28905f324ed5de19108292c6 MD5 | raw file
  1. #--
  2. # Copyright (c) 2009 RightScale Inc
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. #++
  23. # RightScale.popen3 allows running external processes aynchronously
  24. # while still capturing their standard and error outputs.
  25. # It relies on EventMachine for most of its internal mechanisms.
  26. require 'rubygems'
  27. require 'eventmachine'
  28. require 'tempfile'
  29. module RightScale
  30. # ensure uniqueness of handler to avoid confusion.
  31. raise "#{PipeHandler.name} is already defined" if defined?(PipeHandler)
  32. module PipeHandler
  33. def initialize(target, handler)
  34. @target = target
  35. @handler = handler
  36. end
  37. def receive_data(data)
  38. @target.method(@handler).call(data) if @handler
  39. end
  40. end
  41. # ensure uniqueness of handler to avoid confusion.
  42. raise "#{InputHandler.name} is already defined" if defined?(InputHandler)
  43. module InputHandler
  44. def initialize(string)
  45. @string = string
  46. end
  47. def post_init
  48. send_data(@string) if @string
  49. close_connection_after_writing
  50. end
  51. end
  52. # Forks process to run given command asynchronously, hooking all three
  53. # standard streams of the child process.
  54. #
  55. # === Parameters
  56. # options[:pid_handler](Symbol):: Token for pid handler method name.
  57. # options[:temp_dir]:: Path to temporary directory where executable files are
  58. # created, default to /tmp if not specified
  59. #
  60. # See RightScale.popen3
  61. def self.popen3_imp(options)
  62. GC.start # To garbage collect open file descriptors from passed executions
  63. EM.next_tick do
  64. inr, inw = IO::pipe
  65. outr, outw = IO::pipe
  66. errr, errw = IO::pipe
  67. [inr, inw, outr, outw, errr, errw].each {|fdes| fdes.sync = true}
  68. pid = fork do
  69. options[:environment].each do |k, v|
  70. ENV[k.to_s] = v
  71. end unless options[:environment].nil?
  72. inw.close
  73. outr.close
  74. errr.close
  75. $stdin.reopen inr
  76. $stdout.reopen outw
  77. $stderr.reopen errw
  78. if options[:command].instance_of?(String)
  79. exec "sh", "-c", options[:command]
  80. else
  81. exec *options[:command]
  82. end
  83. end
  84. inr.close
  85. outw.close
  86. errw.close
  87. stderr = EM.attach(errr, PipeHandler, options[:target],
  88. options[:stderr_handler])
  89. stdout = EM.attach(outr, PipeHandler, options[:target],
  90. options[:stdout_handler])
  91. stdin = EM.attach(inw, InputHandler, options[:input])
  92. options[:target].method(options[:pid_handler]).call(pid) if
  93. options.key? :pid_handler
  94. wait_timer = EM::PeriodicTimer.new(1) do
  95. value = Process.waitpid2(pid, Process::WNOHANG)
  96. unless value.nil?
  97. begin
  98. ignored, status = value
  99. options[:target].method(options[:exit_handler]).call(status) if
  100. options[:exit_handler]
  101. ensure
  102. stdin.close_connection
  103. stdout.close_connection
  104. stderr.close_connection
  105. wait_timer.cancel
  106. end
  107. end
  108. end
  109. end
  110. true
  111. end
  112. end