| 
									
										
										
										
											2020-11-25 17:03:23 +01:00
										 |  |  | # typed: true | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-28 11:45:18 -08:00
										 |  |  | require "attrable" | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | require "open3" | 
					
						
							| 
									
										
										
										
											2018-09-13 15:24:18 +01:00
										 |  |  | require "plist" | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | require "shellwords" | 
					
						
							| 
									
										
										
										
											2024-07-14 08:49:39 -04:00
										 |  |  | require "uri" | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 17:33:55 -08:00
										 |  |  | require "context" | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | require "extend/io" | 
					
						
							| 
									
										
										
										
											2024-01-29 18:14:31 -08:00
										 |  |  | require "utils/timer" | 
					
						
							| 
									
										
										
										
											2021-03-24 10:55:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-10 17:53:31 +02:00
										 |  |  | # Class for running sub-processes and capturing their output and exit status. | 
					
						
							| 
									
										
										
										
											2020-08-19 07:33:07 +02:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2024-04-23 19:10:33 +02:00
										 |  |  | # @api internal | 
					
						
							| 
									
										
										
										
											2020-10-10 17:53:31 +02:00
										 |  |  | class SystemCommand | 
					
						
							| 
									
										
										
										
											2020-11-05 17:17:03 -05:00
										 |  |  |   # Helper functions for calling {SystemCommand.run}. | 
					
						
							| 
									
										
										
										
											2024-04-23 19:10:33 +02:00
										 |  |  |   # | 
					
						
							|  |  |  |   # @api internal | 
					
						
							| 
									
										
										
										
											2020-10-10 17:53:31 +02:00
										 |  |  |   module Mixin | 
					
						
							| 
									
										
										
										
											2024-04-23 19:10:33 +02:00
										 |  |  |     # Run a fallible system command. | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # @api internal | 
					
						
							| 
									
										
										
										
											2022-10-08 01:08:15 +01:00
										 |  |  |     def system_command(executable, **options) | 
					
						
							|  |  |  |       SystemCommand.run(executable, **options) | 
					
						
							| 
									
										
										
										
											2020-10-10 17:53:31 +02:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-07-22 23:13:32 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-23 19:10:33 +02:00
										 |  |  |     # Run an infallible system command. | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # @api internal | 
					
						
							| 
									
										
										
										
											2022-10-08 01:08:15 +01:00
										 |  |  |     def system_command!(command, **options) | 
					
						
							|  |  |  |       SystemCommand.run!(command, **options) | 
					
						
							| 
									
										
										
										
											2020-10-10 17:53:31 +02:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-10-01 12:29:21 +02:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-07-22 23:13:32 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-02 14:32:31 +02:00
										 |  |  |   include Context | 
					
						
							| 
									
										
										
										
											2023-12-28 11:45:18 -08:00
										 |  |  |   extend Attrable | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   def self.run(executable, **options) | 
					
						
							| 
									
										
										
										
											2022-10-08 01:08:15 +01:00
										 |  |  |     new(executable, **options).run! | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def self.run!(command, **options) | 
					
						
							| 
									
										
										
										
											2022-10-08 01:08:15 +01:00
										 |  |  |     run(command, **options, must_succeed: true) | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 12:03:48 +02:00
										 |  |  |   sig { returns(SystemCommand::Result) } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   def run! | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |     $stderr.puts redact_secrets(command.shelljoin.gsub('\=', "="), @secrets) if verbose? || debug? | 
					
						
							| 
									
										
										
										
											2018-07-24 18:25:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |     @output = [] | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     each_output_line do |type, line| | 
					
						
							|  |  |  |       case type | 
					
						
							|  |  |  |       when :stdout | 
					
						
							| 
									
										
										
										
											2023-10-10 02:08:27 +02:00
										 |  |  |         case @print_stdout | 
					
						
							|  |  |  |         when true | 
					
						
							|  |  |  |           $stdout << redact_secrets(line, @secrets) | 
					
						
							|  |  |  |         when :debug | 
					
						
							|  |  |  |           $stderr << redact_secrets(line, @secrets) if debug? | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |         @output << [:stdout, line] | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |       when :stderr | 
					
						
							| 
									
										
										
										
											2023-10-10 02:08:27 +02:00
										 |  |  |         case @print_stderr | 
					
						
							|  |  |  |         when true | 
					
						
							|  |  |  |           $stderr << redact_secrets(line, @secrets) | 
					
						
							|  |  |  |         when :debug | 
					
						
							|  |  |  |           $stderr << redact_secrets(line, @secrets) if debug? | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |         @output << [:stderr, line] | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-04 09:17:26 +02:00
										 |  |  |     result = Result.new(command, @output, @status, secrets: @secrets) | 
					
						
							|  |  |  |     result.assert_success! if must_succeed? | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     result | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-17 22:45:55 -08:00
										 |  |  |   sig { | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     params( | 
					
						
							|  |  |  |       executable:   T.any(String, Pathname), | 
					
						
							|  |  |  |       args:         T::Array[T.any(String, Integer, Float, URI::Generic)], | 
					
						
							| 
									
										
										
										
											2023-02-17 14:48:13 -08:00
										 |  |  |       sudo:         T::Boolean, | 
					
						
							| 
									
										
										
										
											2023-03-27 14:06:07 -07:00
										 |  |  |       sudo_as_root: T::Boolean, | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |       env:          T::Hash[String, String], | 
					
						
							|  |  |  |       input:        T.any(String, T::Array[String]), | 
					
						
							|  |  |  |       must_succeed: T::Boolean, | 
					
						
							| 
									
										
										
										
											2023-10-10 02:08:27 +02:00
										 |  |  |       print_stdout: T.any(T::Boolean, Symbol), | 
					
						
							|  |  |  |       print_stderr: T.any(T::Boolean, Symbol), | 
					
						
							| 
									
										
										
										
											2020-12-15 21:15:43 -05:00
										 |  |  |       debug:        T.nilable(T::Boolean), | 
					
						
							|  |  |  |       verbose:      T.nilable(T::Boolean), | 
					
						
							| 
									
										
										
										
											2020-11-27 17:45:18 +11:00
										 |  |  |       secrets:      T.any(String, T::Array[String]), | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |       chdir:        T.any(String, Pathname), | 
					
						
							| 
									
										
										
										
											2021-03-24 10:55:33 +01:00
										 |  |  |       timeout:      T.nilable(T.any(Integer, Float)), | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     ).void | 
					
						
							| 
									
										
										
										
											2021-01-17 22:45:55 -08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-03-24 10:55:33 +01:00
										 |  |  |   def initialize( | 
					
						
							|  |  |  |     executable, | 
					
						
							|  |  |  |     args: [], | 
					
						
							|  |  |  |     sudo: false, | 
					
						
							| 
									
										
										
										
											2023-03-27 14:06:07 -07:00
										 |  |  |     sudo_as_root: false, | 
					
						
							| 
									
										
										
										
											2021-03-24 10:55:33 +01:00
										 |  |  |     env: {}, | 
					
						
							|  |  |  |     input: [], | 
					
						
							|  |  |  |     must_succeed: false, | 
					
						
							|  |  |  |     print_stdout: false, | 
					
						
							|  |  |  |     print_stderr: true, | 
					
						
							|  |  |  |     debug: nil, | 
					
						
							|  |  |  |     verbose: false, | 
					
						
							|  |  |  |     secrets: [], | 
					
						
							|  |  |  |     chdir: T.unsafe(nil), | 
					
						
							|  |  |  |     timeout: nil | 
					
						
							|  |  |  |   ) | 
					
						
							| 
									
										
										
										
											2019-07-13 23:22:18 +08:00
										 |  |  |     require "extend/ENV" | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     @executable = executable | 
					
						
							|  |  |  |     @args = args | 
					
						
							| 
									
										
										
										
											2023-02-17 14:48:13 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 02:08:27 +02:00
										 |  |  |     raise ArgumentError, "`sudo_as_root` cannot be set if sudo is false" if !sudo && sudo_as_root | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if print_stdout.is_a?(Symbol) && print_stdout != :debug | 
					
						
							|  |  |  |       raise ArgumentError, "`print_stdout` is not a valid symbol" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |     if print_stderr.is_a?(Symbol) && print_stderr != :debug | 
					
						
							|  |  |  |       raise ArgumentError, "`print_stderr` is not a valid symbol" | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2023-02-17 14:48:13 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     @sudo = sudo | 
					
						
							| 
									
										
										
										
											2023-03-27 14:06:07 -07:00
										 |  |  |     @sudo_as_root = sudo_as_root | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     env.each_key do |name| | 
					
						
							|  |  |  |       next if /^[\w&&\D]\w*$/.match?(name) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 15:21:24 -05:00
										 |  |  |       raise ArgumentError, "Invalid variable name: #{name}" | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     end | 
					
						
							|  |  |  |     @env = env | 
					
						
							| 
									
										
										
										
											2020-07-13 22:48:53 +10:00
										 |  |  |     @input = Array(input) | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     @must_succeed = must_succeed | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     @print_stdout = print_stdout | 
					
						
							|  |  |  |     @print_stderr = print_stderr | 
					
						
							| 
									
										
										
										
											2020-12-14 12:36:32 -05:00
										 |  |  |     @debug = debug | 
					
						
							| 
									
										
										
										
											2018-07-24 18:25:59 +02:00
										 |  |  |     @verbose = verbose | 
					
						
							| 
									
										
										
										
											2019-07-13 23:22:18 +08:00
										 |  |  |     @secrets = (Array(secrets) + ENV.sensitive_environment.values).uniq | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     @chdir = chdir | 
					
						
							| 
									
										
										
										
											2021-03-24 10:55:33 +01:00
										 |  |  |     @timeout = timeout | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |   sig { returns(T::Array[String]) } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   def command | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:37 -08:00
										 |  |  |     [*command_prefix, executable.to_s, *expanded_args] | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |   attr_reader :executable, :args, :input, :chdir, :env | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 02:08:27 +02:00
										 |  |  |   attr_predicate :sudo?, :sudo_as_root?, :must_succeed? | 
					
						
							| 
									
										
										
										
											2020-08-02 14:32:31 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |   sig { returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2020-12-14 12:36:32 -05:00
										 |  |  |   def debug? | 
					
						
							|  |  |  |     return super if @debug.nil? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @debug | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2020-08-02 14:32:31 +02:00
										 |  |  |   def verbose? | 
					
						
							|  |  |  |     return super if @verbose.nil? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @verbose | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |   sig { returns(T::Array[String]) } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   def env_args | 
					
						
							| 
									
										
										
										
											2020-11-09 20:15:28 +11:00
										 |  |  |     set_variables = env.compact.map do |name, value| | 
					
						
							|  |  |  |       sanitized_name = Shellwords.escape(name) | 
					
						
							|  |  |  |       sanitized_value = Shellwords.escape(value) | 
					
						
							|  |  |  |       "#{sanitized_name}=#{sanitized_value}" | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |     return [] if set_variables.empty? | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:37 -08:00
										 |  |  |     set_variables | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-29 18:49:44 +01:00
										 |  |  |   sig { returns(T.nilable(String)) } | 
					
						
							|  |  |  |   def homebrew_sudo_user | 
					
						
							|  |  |  |     ENV.fetch("HOMEBREW_SUDO_USER", nil) | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |   sig { returns(T::Array[String]) } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   def sudo_prefix | 
					
						
							|  |  |  |     askpass_flags = ENV.key?("SUDO_ASKPASS") ? ["-A"] : [] | 
					
						
							| 
									
										
										
										
											2023-09-29 18:49:44 +01:00
										 |  |  |     user_flags = [] | 
					
						
							| 
									
										
										
										
											2023-12-20 09:36:46 -08:00
										 |  |  |     if Homebrew::EnvConfig.sudo_through_sudo_user? | 
					
						
							| 
									
										
										
										
											2023-09-29 18:49:44 +01:00
										 |  |  |       raise ArgumentError, "HOMEBREW_SUDO_THROUGH_SUDO_USER set but SUDO_USER unset!" if homebrew_sudo_user.blank? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       user_flags += ["--prompt", "Password for %p:", "-u", homebrew_sudo_user, | 
					
						
							|  |  |  |                      *askpass_flags, | 
					
						
							|  |  |  |                      "-E", *env_args, | 
					
						
							|  |  |  |                      "--", "/usr/bin/sudo"] | 
					
						
							| 
									
										
										
										
											2023-12-20 09:39:10 -08:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2023-12-21 01:16:09 -08:00
										 |  |  |     user_flags += ["-u", "root"] if sudo_as_root? | 
					
						
							| 
									
										
										
										
											2023-02-14 13:24:36 -08:00
										 |  |  |     ["/usr/bin/sudo", *user_flags, *askpass_flags, "-E", *env_args, "--"] | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:37 -08:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(T::Array[String]) } | 
					
						
							| 
									
										
										
										
											2023-02-14 13:02:59 +00:00
										 |  |  |   def env_prefix | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:37 -08:00
										 |  |  |     ["/usr/bin/env", *env_args] | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sig { returns(T::Array[String]) } | 
					
						
							|  |  |  |   def command_prefix | 
					
						
							| 
									
										
										
										
											2023-02-14 13:02:59 +00:00
										 |  |  |     sudo? ? sudo_prefix : env_prefix | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |   sig { returns(T::Array[String]) } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   def expanded_args | 
					
						
							|  |  |  |     @expanded_args ||= args.map do |arg| | 
					
						
							|  |  |  |       if arg.respond_to?(:to_path) | 
					
						
							|  |  |  |         File.absolute_path(arg) | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |       elsif arg.is_a?(Integer) || arg.is_a?(Float) || arg.is_a?(URI::Generic) | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |         arg.to_s | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         arg.to_str | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |   class ProcessTerminatedInterrupt < StandardError; end | 
					
						
							|  |  |  |   private_constant :ProcessTerminatedInterrupt | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |   sig { params(block: T.proc.params(type: Symbol, line: String).void).void } | 
					
						
							|  |  |  |   def each_output_line(&block) | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     executable, *args = command | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |     options = { | 
					
						
							|  |  |  |       # Create a new process group so that we can send `SIGINT` from | 
					
						
							|  |  |  |       # parent to child rather than the child receiving `SIGINT` directly. | 
					
						
							| 
									
										
										
										
											2020-12-19 19:30:33 +01:00
										 |  |  |       pgroup: sudo? ? nil : true, | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |     options[:chdir] = chdir if chdir | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 14:42:12 -04:00
										 |  |  |     raw_stdin, raw_stdout, raw_stderr, raw_wait_thr = Open3.popen3( | 
					
						
							|  |  |  |       env.merge({ "COLUMNS" => Tty.width.to_s }), | 
					
						
							|  |  |  |       [executable, executable], | 
					
						
							|  |  |  |       *args, | 
					
						
							|  |  |  |       **options, | 
					
						
							|  |  |  |     ) | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     write_input_to(raw_stdin) | 
					
						
							|  |  |  |     raw_stdin.close_write | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 03:39:42 +02:00
										 |  |  |     thread_context = Context.current | 
					
						
							| 
									
										
										
										
											2021-05-11 13:27:26 +01:00
										 |  |  |     thread_ready_queue = Queue.new | 
					
						
							|  |  |  |     thread_done_queue = Queue.new | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |     line_thread = Thread.new do | 
					
						
							| 
									
										
										
										
											2023-10-10 03:39:42 +02:00
										 |  |  |       # Ensure the new thread inherits the current context. | 
					
						
							|  |  |  |       Context.current = thread_context | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |       Thread.handle_interrupt(ProcessTerminatedInterrupt => :never) do | 
					
						
							| 
									
										
										
										
											2021-05-11 13:27:26 +01:00
										 |  |  |         thread_ready_queue << true | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |         each_line_from [raw_stdout, raw_stderr], &block | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2021-05-11 13:27:26 +01:00
										 |  |  |       thread_done_queue.pop | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |     rescue ProcessTerminatedInterrupt | 
					
						
							|  |  |  |       nil | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     end_time = Time.now + @timeout if @timeout | 
					
						
							| 
									
										
										
										
											2024-01-29 18:14:31 -08:00
										 |  |  |     raise Timeout::Error if raw_wait_thr.join(Utils::Timer.remaining(end_time)).nil? | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-24 00:09:11 +02:00
										 |  |  |     @status = raw_wait_thr.value | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-11 13:27:26 +01:00
										 |  |  |     thread_ready_queue.pop | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |     line_thread.raise ProcessTerminatedInterrupt.new | 
					
						
							| 
									
										
										
										
											2021-05-11 13:27:26 +01:00
										 |  |  |     thread_done_queue << true | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |     line_thread.join | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |   rescue Interrupt | 
					
						
							| 
									
										
										
										
											2023-04-17 23:30:25 +02:00
										 |  |  |     Process.kill("INT", raw_wait_thr.pid) if raw_wait_thr && !sudo? | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |     raise Interrupt | 
					
						
							| 
									
										
										
										
											2018-07-24 00:09:11 +02:00
										 |  |  |   rescue SystemCallError => e | 
					
						
							|  |  |  |     @status = $CHILD_STATUS | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |     @output << [:stderr, e.message] | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |   sig { params(raw_stdin: IO).void } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   def write_input_to(raw_stdin) | 
					
						
							| 
									
										
										
										
											2024-03-03 18:55:56 -08:00
										 |  |  |     input.each { raw_stdin.write(_1) } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |   sig { params(sources: T::Array[IO], _block: T.proc.params(type: Symbol, line: String).void).void } | 
					
						
							|  |  |  |   def each_line_from(sources, &_block) | 
					
						
							| 
									
										
										
										
											2021-04-03 06:03:59 +02:00
										 |  |  |     sources = { | 
					
						
							|  |  |  |       sources[0] => :stdout, | 
					
						
							|  |  |  |       sources[1] => :stderr, | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-03-24 10:55:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |     pending_interrupt = T.let(false, T::Boolean) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-29 03:43:52 +00:00
										 |  |  |     until pending_interrupt || sources.empty? | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |       readable_sources = T.let([], T::Array[IO]) | 
					
						
							|  |  |  |       begin | 
					
						
							|  |  |  |         Thread.handle_interrupt(ProcessTerminatedInterrupt => :on_blocking) do | 
					
						
							|  |  |  |           readable_sources = T.must(IO.select(sources.keys)).fetch(0) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       rescue ProcessTerminatedInterrupt | 
					
						
							|  |  |  |         readable_sources = sources.keys | 
					
						
							|  |  |  |         pending_interrupt = true | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2021-04-03 05:17:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-29 03:43:52 +00:00
										 |  |  |       readable_sources.each do |source| | 
					
						
							| 
									
										
										
										
											2021-05-03 22:22:34 +01:00
										 |  |  |         loop do | 
					
						
							|  |  |  |           line = source.readline_nonblock || "" | 
					
						
							|  |  |  |           yield(sources.fetch(source), line) | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2021-04-01 15:42:16 +01:00
										 |  |  |       rescue EOFError | 
					
						
							|  |  |  |         source.close_read | 
					
						
							| 
									
										
										
										
											2021-04-03 06:03:59 +02:00
										 |  |  |         sources.delete(source) | 
					
						
							| 
									
										
										
										
											2021-04-01 15:42:16 +01:00
										 |  |  |       rescue IO::WaitReadable | 
					
						
							| 
									
										
										
										
											2023-10-29 03:43:52 +00:00
										 |  |  |         # We've got all the data that was ready, but the other end of the stream isn't finished yet | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-03 06:03:59 +02:00
										 |  |  |     sources.each_key(&:close_read) | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-19 07:33:07 +02:00
										 |  |  |   # Result containing the output and exit status of a finished sub-process. | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |   class Result | 
					
						
							| 
									
										
										
										
											2020-08-02 14:32:31 +02:00
										 |  |  |     include Context | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |     attr_accessor :command, :status, :exit_status | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-17 22:45:55 -08:00
										 |  |  |     sig { | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |       params( | 
					
						
							|  |  |  |         command: T::Array[String], | 
					
						
							|  |  |  |         output:  T::Array[[Symbol, String]], | 
					
						
							|  |  |  |         status:  Process::Status, | 
					
						
							|  |  |  |         secrets: T::Array[String], | 
					
						
							|  |  |  |       ).void | 
					
						
							| 
									
										
										
										
											2021-01-17 22:45:55 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-10-04 09:17:26 +02:00
										 |  |  |     def initialize(command, output, status, secrets:) | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |       @command       = command | 
					
						
							|  |  |  |       @output        = output | 
					
						
							|  |  |  |       @status        = status | 
					
						
							|  |  |  |       @exit_status   = status.exitstatus | 
					
						
							| 
									
										
										
										
											2019-10-04 09:17:26 +02:00
										 |  |  |       @secrets       = secrets | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     sig { void } | 
					
						
							| 
									
										
										
										
											2019-10-04 09:17:26 +02:00
										 |  |  |     def assert_success! | 
					
						
							|  |  |  |       return if @status.success? | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       raise ErrorDuringExecution.new(command, status: @status, output: @output, secrets: @secrets) | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |     def stdout | 
					
						
							|  |  |  |       @stdout ||= @output.select { |type,| type == :stdout } | 
					
						
							|  |  |  |                          .map { |_, line| line } | 
					
						
							|  |  |  |                          .join | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2018-08-29 19:56:32 +02:00
										 |  |  |     def stderr | 
					
						
							|  |  |  |       @stderr ||= @output.select { |type,| type == :stderr } | 
					
						
							|  |  |  |                          .map { |_, line| line } | 
					
						
							|  |  |  |                          .join | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2018-09-19 03:09:07 +02:00
										 |  |  |     def merged_output | 
					
						
							|  |  |  |       @merged_output ||= @output.map { |_, line| line } | 
					
						
							|  |  |  |                                 .join | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     sig { returns(T::Boolean) } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     def success? | 
					
						
							| 
									
										
										
										
											2018-09-20 10:57:27 +01:00
										 |  |  |       return false if @exit_status.nil? | 
					
						
							| 
									
										
										
										
											2019-02-19 13:12:52 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |       @exit_status.zero? | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     sig { returns([String, String, Process::Status]) } | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |     def to_ary | 
					
						
							|  |  |  |       [stdout, stderr, status] | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     sig { returns(T.nilable(T.any(Array, Hash))) } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     def plist | 
					
						
							|  |  |  |       @plist ||= begin | 
					
						
							|  |  |  |         output = stdout | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |         output = output.sub(/\A(.*?)(\s*<\?\s*xml)/m) do | 
					
						
							|  |  |  |           warn_plist_garbage(T.must(Regexp.last_match(1))) | 
					
						
							|  |  |  |           Regexp.last_match(2) | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |         output = output.sub(%r{(<\s*/\s*plist\s*>\s*)(.*?)\Z}m) do | 
					
						
							|  |  |  |           warn_plist_garbage(T.must(Regexp.last_match(2))) | 
					
						
							|  |  |  |           Regexp.last_match(1) | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-22 22:52:06 +00:00
										 |  |  |         Plist.parse_xml(output, marshal: false) | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-23 02:05:50 +01:00
										 |  |  |     sig { params(garbage: String).void } | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |     def warn_plist_garbage(garbage) | 
					
						
							| 
									
										
										
										
											2020-08-02 14:32:31 +02:00
										 |  |  |       return unless verbose? | 
					
						
							| 
									
										
										
										
											2019-10-13 19:26:39 +01:00
										 |  |  |       return unless garbage.match?(/\S/) | 
					
						
							| 
									
										
										
										
											2018-09-17 02:45:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-19 23:56:51 +02:00
										 |  |  |       opoo "Received non-XML output from #{Formatter.identifier(command.first)}:" | 
					
						
							|  |  |  |       $stderr.puts garbage.strip | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |     private :warn_plist_garbage | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |