lib/linux_admin/common.rb in linux_admin-0.0.1 vs lib/linux_admin/common.rb in linux_admin-0.1.0

- old
+ new

@@ -1,21 +1,94 @@ -module LinuxAdmin +require 'shellwords' + +class LinuxAdmin module Common - def self.run(cmd, options = {}) + def write(file, content) + raise ArgumentError, "file and content can not be empty" if file.blank? || content.blank? + File.open(file, "w") do |f| + f.write(content) + end + end + + def run(cmd, options = {}) + params = options[:params] || options[:parameters] + begin - r, w = IO.pipe - pid, status = Process.wait2(Kernel.spawn(cmd, :err => [:child, :out], :out => w)) - w.close - if options[:return_output] && status.exitstatus == 0 - r.read - elsif options[:return_exitstatus] || status.exitstatus == 0 - status.exitstatus + out = launch(build_cmd(cmd, params)) + + if options[:return_output] && exitstatus == 0 + out + elsif options[:return_exitstatus] || exitstatus == 0 + exitstatus else - raise "Error: Exit Code #{status.exitstatus}" + raise "Error: Exit Code #{exitstatus}" end rescue return nil if options[:return_exitstatus] raise + ensure + self.exitstatus = nil end + end + + private + + def sanitize(params) + return {} if params.blank? + params.each_with_object({}) do |(k, v), h| + h[k] = + case v + when Array; v.collect {|s| s.shellescape} + when NilClass; v + else v.shellescape + end + end + end + + def assemble_params(sanitized_params) + sanitized_params.collect do |pair| + pair_joiner = pair.first.try(:end_with?, "=") ? "" : " " + pair.flatten.compact.join(pair_joiner) + end.join(" ") + end + + def build_cmd(cmd, params = nil) + return cmd if params.blank? + "#{cmd} #{assemble_params(sanitize(params))}" + end + + # IO pipes have a maximum size of 64k before blocking, + # so we need to read and write synchronously. + # http://stackoverflow.com/questions/13829830/ruby-process-spawn-stdout-pipe-buffer-size-limit/13846146#13846146 + THREAD_SYNC_KEY = "LinuxAdmin-exitstatus" + + def launch(cmd) + pipe_r, pipe_w = IO.pipe + pid = Kernel.spawn(cmd, :err => [:child, :out], :out => pipe_w) + wait_for_process(pid, pipe_w) + wait_for_output(pipe_r) + end + + def wait_for_process(pid, pipe_w) + self.exitstatus = :not_done + Thread.new(Thread.current) do |parent_thread| + _, status = Process.wait2(pid) + pipe_w.close + parent_thread[THREAD_SYNC_KEY] = status.exitstatus + end + end + + def wait_for_output(pipe_r) + out = pipe_r.read + sleep(0.1) while exitstatus == :not_done + return out + end + + def exitstatus + Thread.current[THREAD_SYNC_KEY] + end + + def exitstatus=(value) + Thread.current[THREAD_SYNC_KEY] = value end end end \ No newline at end of file