Methods

Files

Class/Module Index [+]

Quicksearch

Chef::Mixin::Command::Unix

Public Instance Methods

popen4(cmd, args={}, &b) click to toggle source

This is taken directly from Ara T Howard’s Open4 library, and then modified to suit the needs of Chef. Any bugs here are most likely my own, and not Ara’s.

The original appears in external/open4.rb in its unmodified form.

Thanks Ara!

# File lib/chef/mixin/command/unix.rb, line 30
def popen4(cmd, args={}, &b)
  # Ruby 1.8 suffers from intermittent segfaults believed to be due to GC while IO.select
  # See CHEF-2916 / CHEF-1305
  GC.disable

  # Waitlast - this is magic.
  #
  # Do we wait for the child process to die before we yield
  # to the block, or after?  That is the magic of waitlast.
  #
  # By default, we are waiting before we yield the block.
  args[:waitlast] ||= false

  args[:user] ||= nil
  unless args[:user].kind_of?(Integer)
    args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
  end
  args[:group] ||= nil
  unless args[:group].kind_of?(Integer)
    args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
  end
  args[:environment] ||= {}

  # Default on C locale so parsing commands output can be done
  # independently of the node's default locale.
  # "LC_ALL" could be set to nil, in which case we also must ignore it.
  unless args[:environment].has_key?("LC_ALL")
    args[:environment]["LC_ALL"] = "C"
  end

  pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe

  verbose = $VERBOSE
  begin
    $VERBOSE = nil
    ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)

    cid = fork {
      pw.last.close
      STDIN.reopen pw.first
      pw.first.close

      pr.first.close
      STDOUT.reopen pr.last
      pr.last.close

      pe.first.close
      STDERR.reopen pe.last
      pe.last.close

      STDOUT.sync = STDERR.sync = true

      if args[:group]
        Process.egid = args[:group]
        Process.gid = args[:group]
      end

      if args[:user]
        Process.euid = args[:user]
        Process.uid = args[:user]
      end

      args[:environment].each do |key,value|
        ENV[key] = value
      end

      if args[:umask]
        umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
        File.umask(umask)
      end

      begin
        if cmd.kind_of?(Array)
          exec(*cmd)
        else
          exec(cmd)
        end
        raise 'forty-two'
      rescue Exception => e
        Marshal.dump(e, ps.last)
        ps.last.flush
      end
      ps.last.close unless (ps.last.closed?)
      exit!
    }
  ensure
    $VERBOSE = verbose
  end

  [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}

  begin
    e = Marshal.load ps.first
    raise(Exception === e ? e : "unknown failure!")
  rescue EOFError # If we get an EOF error, then the exec was successful
    42
  ensure
    ps.first.close
  end

  pw.last.sync = true

  pi = [pw.last, pr.first, pe.first]

  if b
    begin
      if args[:waitlast]
        b[cid, *pi]
        # send EOF so that if the child process is reading from STDIN
        # it will actually finish up and exit
        pi[0].close_write
        Process.waitpid2(cid).last
      else
        # This took some doing.
        # The trick here is to close STDIN
        # Then set our end of the childs pipes to be O_NONBLOCK
        # Then wait for the child to die, which means any IO it
        # wants to do must be done - it's dead.  If it isn't,
        # it's because something totally skanky is happening,
        # and we don't care.
        o = StringIO.new
        e = StringIO.new

        pi[0].close

        stdout = pi[1]
        stderr = pi[2]

        stdout.sync = true
        stderr.sync = true

        stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
        stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)

        stdout_finished = false
        stderr_finished = false

        results = nil

        while !stdout_finished || !stderr_finished
          begin
            channels_to_watch = []
            channels_to_watch << stdout if !stdout_finished
            channels_to_watch << stderr if !stderr_finished
            ready = IO.select(channels_to_watch, nil, nil, 1.0)
          rescue Errno::EAGAIN
          ensure
            results = Process.waitpid2(cid, Process::WNOHANG)
            if results
              stdout_finished = true
              stderr_finished = true
            end
          end

          if ready && ready.first.include?(stdout)
            line = results ? stdout.gets(nil) : stdout.gets
            if line
              o.write(line)
            else
              stdout_finished = true
            end
          end
          if ready && ready.first.include?(stderr)
            line = results ? stderr.gets(nil) : stderr.gets
            if line
              e.write(line)
            else
              stderr_finished = true
            end
          end
        end
        results = Process.waitpid2(cid) unless results
        o.rewind
        e.rewind
        b[cid, pi[0], o, e]
        results.last
      end
    ensure
      pi.each{|fd| fd.close unless fd.closed?}
    end
  else
    [cid, pw.last, pr.first, pe.first]
  end
ensure
  GC.enable
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.