# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/components/logger' module Contrast module Api module Communication # Handles local service startup. As this should only ever be invoked by the Speedracer class, which includes # this, all methods here are private. module ServiceLifecycle include Contrast::Components::Logger::InstanceMethods private # Attempt to start up a local process with SpeedRacer. This will ensure the process isn't just a zombie and is # a real functioning process. # # @return [Boolean] Did the SpeedRacer process start? def attempt_local_service_startup zombie_check service_starter_thread.join(5) is_service_started = Contrast::Utils::OS.running? if is_service_started logger.info('The bundled service was successfully started.') else logger.error('The bundled service could not be started. The agent will not function properly.') end is_service_started end # Check if there's a zombie service that exists, and wait on it if so. Currently, this only happens when trying # to initialize SpeedRacer. If there is a zombie, we'll wait until the zombie completes. def zombie_check zombie_pid_list = Contrast::Utils::OS.zombie_pids zombie_pid_list.each do |pid| Process.wait(pid.to_i) rescue Errno::ECHILD => _e # Sometimes the zombie process dies between us finding it and killing it end end # Determine the options used to set up the SpeedRacer process. Specifically, this handles if we need to adjust # where the process needs to log. # # @return [Hash] options used to spawn the SpeedRacer process def determine_startup_options return { out: :out, err: :out } if ::Contrast::CONTRAST_SERVICE.logger_path == 'STDOUT' return { out: :err, err: :err } if ::Contrast::CONTRAST_SERVICE.logger_path == 'STDERR' { out: File::NULL, err: File::NULL } end # Create a new process for the SpeedRacer. This is a separate method so we can overwrite it globally in specs. def spawn_service options = determine_startup_options logger.debug('Spawning service') spawn 'contrast_service', options end # Create a thread to start the SpeedRacer, making calls to spawn until one starts successfully. As soon as the # SpeedRacer process starts, this thread terminates. def service_starter_thread Contrast::Agent::Thread.new do # Always check to see if it already started unless Contrast::Utils::OS.running? # Spawn the service process spawn_service # Block until service is running sleep(0.1) until Contrast::Utils::OS.running? end end end end end end end