class Arachni::Processes::Manager

Helper for managing processes.

@author Tasos “Zapotek” Laskos <tasos.laskos@arachni-scanner.com>

Constants

RUNNER

Attributes

pids[R]

@return [Array<Integer>] PIDs of all running processes.

Public Class Methods

method_missing( sym, *args, &block ) click to toggle source
Calls superclass method
# File lib/arachni/processes/manager.rb, line 224
def self.method_missing( sym, *args, &block )
    if instance.respond_to?( sym )
        instance.send( sym, *args, &block )
    else
        super( sym, *args, &block )
    end
end
new() click to toggle source
# File lib/arachni/processes/manager.rb, line 26
def initialize
    @pids           = []
    @discard_output = true
end
respond_to?( m ) click to toggle source
Calls superclass method
# File lib/arachni/processes/manager.rb, line 232
def self.respond_to?( m )
    super( m ) || instance.respond_to?( m )
end

Public Instance Methods

<<( pid ) click to toggle source

@param [Integer] pid

Adds a PID to the {#pids} and detaches the process.

@return [Integer] ‘pid`

# File lib/arachni/processes/manager.rb, line 35
def <<( pid )
    @pids << pid
    Process.detach pid
    pid
end
alive?( pid ) click to toggle source

@param [Integer] pid @return [Boolean]

`true` if the process is alive, `false` otherwise.
# File lib/arachni/processes/manager.rb, line 67
def alive?( pid )
    # Windows is not big on POSIX so try it its own way if possible.
    if Arachni.windows?
        begin
            alive = false
            wmi = WIN32OLE.connect( 'winmgmts://' )
            processes = wmi.ExecQuery( "select ProcessId from win32_process where ProcessID='#{pid}'" )
            processes.each do |proc|
                proc.ole_free
                alive = true
            end
            processes.ole_free
            wmi.ole_free

            return alive
        rescue WIN32OLERuntimeError
        end
    end

    !!(Process.kill( 0, pid ) rescue false)
end
discard_output() click to toggle source
# File lib/arachni/processes/manager.rb, line 117
def discard_output
    @discard_output = true
end
discard_output?() click to toggle source
# File lib/arachni/processes/manager.rb, line 121
def discard_output?
    @discard_output
end
kill( pid ) click to toggle source

@param [Integer] pid

PID of the process to kill.
# File lib/arachni/processes/manager.rb, line 43
def kill( pid )
    Timeout.timeout 10 do
        while sleep 0.1 do
            begin
                Process.kill( Arachni.windows? ? 'KILL' : 'TERM', pid )

            # Either kill was successful or we don't have enough perms or
            # we hit a reused PID for someone else's process, either way,
            # consider the process gone.
            rescue Errno::ESRCH, Errno::EPERM,
                # Don't kill ourselves.
                SignalException

                @pids.delete pid
                return
            end
        end
    end
rescue Timeout::Error
end
kill_many( pids ) click to toggle source

@param [Array<Integer>] pids

PIDs of the process to {Arachni::Processes::Manager#kill}.
# File lib/arachni/processes/manager.rb, line 91
def kill_many( pids )
    pids.each { |pid| kill pid }
end
kill_reactor() click to toggle source

Stops the Reactor.

# File lib/arachni/processes/manager.rb, line 102
def kill_reactor
    Reactor.stop
rescue
    nil
end
killall() click to toggle source

Kills all {#pids processes}.

# File lib/arachni/processes/manager.rb, line 96
def killall
    kill_many @pids.dup
    @pids.clear
end
preserve_output() click to toggle source

Overrides the default setting of discarding process outputs.

# File lib/arachni/processes/manager.rb, line 109
def preserve_output
    @discard_output = false
end
preserve_output?() click to toggle source
# File lib/arachni/processes/manager.rb, line 113
def preserve_output?
    !discard_output?
end
spawn( executable, options = {} ) click to toggle source

@param [String] executable

Name of the executable Ruby script found in {OptionGroups::Paths#executables}
without the '.rb' extension.

@param [Hash] options

Options to pass to the script -- can be retrieved from `$options`.

@return [Integer]

PID of the process.
# File lib/arachni/processes/manager.rb, line 133
def spawn( executable, options = {} )
    fork = options.delete(:fork)
    fork = false if fork.nil?

    stdin      = options.delete(:stdin)
    stdout     = options.delete(:stdout)
    stderr     = options.delete(:stderr)
    new_pgroup = options.delete(:new_pgroup)

    spawn_options = {}

    if new_pgroup
        if Arachni.windows?
            spawn_options[:new_pgroup] = new_pgroup
        else
            spawn_options[:pgroup] = new_pgroup
        end
    end

    spawn_options[:in]     = stdin  if stdin
    spawn_options[:out]    = stdout if stdout
    spawn_options[:err]    = stderr if stderr

    options[:ppid]  = Process.pid

    arachni_options = Options.to_h.merge( options.delete(:options) || {} )
    # Paths are not included in RPC nor Hash representations as they're
    # considered local, in this case though they're necessary to provide
    # the same environment the processes.
    arachni_options[:paths] = Options.paths.to_h
    encoded_arachni_options = Base64.strict_encode64( Marshal.dump( arachni_options ) )

    executable      = "#{Options.paths.executables}/#{executable}.rb"
    encoded_options = Base64.strict_encode64( Marshal.dump( options ) )
    argv            = [executable, encoded_options]

    # Process.fork is faster, less stressful to the CPU and lets the parent
    # and child share the same RAM due to copy-on-write support on Ruby 2.0.0.
    # It is, however, not available when running on Windows nor JRuby so
    # have a fallback ready.
    if fork && Process.respond_to?( :fork )
        pid = Process.fork do
            $stdin = spawn_options[:in] if spawn_options[:in]

            if spawn_options[:out]
                $stdout = spawn_options[:out]
            elsif discard_output?
                $stdout.reopen( Arachni.null_device, 'w' )
            end

            if spawn_options[:err]
                $stderr = spawn_options[:err]
            elsif discard_output?
                $stderr.reopen( Arachni.null_device, 'w' )
            end

            # Careful, Framework.reset will remove objects from Data
            # structures which off-load to disk, those files however belong
            # to our parent and should not be touched, thus, we remove
            # any references to them.
            Data.framework.page_queue.disk.clear
            Data.framework.url_queue.disk.clear
            Data.framework.rpc.distributed_page_queue.disk.clear

            # Provide a clean slate.
            Framework.reset
            Reactor.stop

            ENV['arachni_options'] = encoded_arachni_options

            ARGV.replace( argv )
            load RUNNER
        end
    else
        # It's very, **VERY** important that we use this argument format as
        # it bypasses the OS shell and we can thus count on a 1-to-1 process
        # creation and that the PID we get will be for the actual process.
        pid = Process.spawn(
            {
                'arachni_options' => encoded_arachni_options
            },
            RbConfig.ruby,
            RUNNER,
            *(argv + [spawn_options])
        )
    end

    self << pid
    pid
end