| 
									
										
										
										
											2025-01-11 21:36:59 +00:00
										 |  |  | # typed: strict | 
					
						
							| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  | require "fcntl" | 
					
						
							| 
									
										
										
										
											2024-09-27 02:50:37 +01:00
										 |  |  | require "utils/socket" | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | module Utils | 
					
						
							| 
									
										
										
										
											2025-01-11 21:36:59 +00:00
										 |  |  |   sig { params(child_error: T::Hash[String, T.untyped]).returns(Exception) } | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  |   def self.rewrite_child_error(child_error) | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |     inner_class = Object.const_get(child_error["json_class"]) | 
					
						
							|  |  |  |     error = if child_error["cmd"] && inner_class == ErrorDuringExecution | 
					
						
							|  |  |  |       ErrorDuringExecution.new(child_error["cmd"], | 
					
						
							|  |  |  |                                status: child_error["status"], | 
					
						
							|  |  |  |                                output: child_error["output"]) | 
					
						
							|  |  |  |     elsif child_error["cmd"] && inner_class == BuildError | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  |       # We fill `BuildError#formula` and `BuildError#options` in later, | 
					
						
							|  |  |  |       # when we rescue this in `FormulaInstaller#build`. | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |       BuildError.new(nil, child_error["cmd"], child_error["args"], child_error["env"]) | 
					
						
							|  |  |  |     elsif inner_class == Interrupt | 
					
						
							| 
									
										
										
										
											2018-09-06 23:48:07 -04:00
										 |  |  |       Interrupt.new | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  |     else | 
					
						
							|  |  |  |       # Everything other error in the child just becomes a RuntimeError. | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |       RuntimeError.new <<~EOS | 
					
						
							|  |  |  |         An exception occurred within a child process: | 
					
						
							|  |  |  |           #{inner_class}: #{child_error["m"]} | 
					
						
							|  |  |  |       EOS | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |     error.set_backtrace child_error["b"] | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     error | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |   # When using this function, remember to call `exec` as soon as reasonably possible. | 
					
						
							|  |  |  |   # This function does not protect against the pitfalls of what you can do pre-exec in a fork. | 
					
						
							|  |  |  |   # See `man fork` for more information. | 
					
						
							| 
									
										
										
										
											2025-01-11 21:36:59 +00:00
										 |  |  |   sig { | 
					
						
							|  |  |  |     params(directory: T.nilable(String), yield_parent: T::Boolean, | 
					
						
							|  |  |  |            _blk: T.proc.params(arg0: T.nilable(String)).void).void | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   def self.safe_fork(directory: nil, yield_parent: false, &_blk) | 
					
						
							| 
									
										
										
										
											2024-07-14 08:49:39 -04:00
										 |  |  |     require "json/add/exception" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |     block = proc do |tmpdir| | 
					
						
							| 
									
										
										
										
											2024-09-27 02:50:37 +01:00
										 |  |  |       UNIXServerExt.open("#{tmpdir}/socket") do |server| | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |         read, write = IO.pipe | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |         pid = fork do | 
					
						
							| 
									
										
										
										
											2021-02-02 11:53:51 +00:00
										 |  |  |           # bootsnap doesn't like these forked processes | 
					
						
							|  |  |  |           ENV["HOMEBREW_NO_BOOTSNAP"] = "1" | 
					
						
							| 
									
										
										
										
											2024-04-13 00:23:42 -04:00
										 |  |  |           error_pipe = server.path | 
					
						
							|  |  |  |           ENV["HOMEBREW_ERROR_PIPE"] = error_pipe | 
					
						
							| 
									
										
										
										
											2019-10-13 10:03:26 +01:00
										 |  |  |           server.close | 
					
						
							|  |  |  |           read.close | 
					
						
							|  |  |  |           write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) | 
					
						
							| 
									
										
										
										
											2024-03-21 03:25:49 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |           Process::UID.change_privilege(Process.euid) if Process.euid != Process.uid | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-13 00:23:42 -04:00
										 |  |  |           yield(error_pipe) | 
					
						
							| 
									
										
										
										
											2025-01-12 16:56:48 +00:00
										 |  |  |         # This could be any type of exception, so rescue them all. | 
					
						
							| 
									
										
										
										
											2019-10-13 10:03:26 +01:00
										 |  |  |         rescue Exception => e # rubocop:disable Lint/RescueException | 
					
						
							|  |  |  |           error_hash = JSON.parse e.to_json | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           # Special case: We need to recreate ErrorDuringExecutions | 
					
						
							|  |  |  |           # for proper error messages and because other code expects | 
					
						
							|  |  |  |           # to rescue them further down. | 
					
						
							|  |  |  |           if e.is_a?(ErrorDuringExecution) | 
					
						
							|  |  |  |             error_hash["cmd"] = e.cmd | 
					
						
							| 
									
										
										
										
											2021-02-23 21:30:36 +00:00
										 |  |  |             error_hash["status"] = if e.status.is_a?(Process::Status) | 
					
						
							|  |  |  |               { | 
					
						
							|  |  |  |                 exitstatus: e.status.exitstatus, | 
					
						
							|  |  |  |                 termsig:    e.status.termsig, | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               e.status | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2019-10-13 10:03:26 +01:00
										 |  |  |             error_hash["output"] = e.output | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-13 10:03:26 +01:00
										 |  |  |           write.puts error_hash.to_json | 
					
						
							|  |  |  |           write.close | 
					
						
							| 
									
										
										
										
											2018-09-06 23:48:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-13 10:03:26 +01:00
										 |  |  |           exit! | 
					
						
							| 
									
										
										
										
											2021-02-02 11:53:51 +00:00
										 |  |  |         else | 
					
						
							| 
									
										
										
										
											2019-10-14 09:03:02 +01:00
										 |  |  |           exit!(true) | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 14:32:49 -04:00
										 |  |  |         begin | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |           yield(nil) if yield_parent | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |           begin | 
					
						
							|  |  |  |             socket = server.accept_nonblock | 
					
						
							|  |  |  |           rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR | 
					
						
							| 
									
										
										
										
											2024-07-14 14:32:49 -04:00
										 |  |  |             retry unless Process.waitpid(pid, Process::WNOHANG) | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |           else | 
					
						
							|  |  |  |             socket.send_io(write) | 
					
						
							| 
									
										
										
										
											2015-05-12 21:52:30 -04:00
										 |  |  |             socket.close | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |           end | 
					
						
							|  |  |  |           write.close | 
					
						
							| 
									
										
										
										
											2018-09-06 23:48:07 -04:00
										 |  |  |           data = read.read | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |           read.close | 
					
						
							| 
									
										
										
										
											2024-07-14 14:32:49 -04:00
										 |  |  |           Process.waitpid(pid) unless socket.nil? | 
					
						
							|  |  |  |         rescue Interrupt | 
					
						
							|  |  |  |           Process.waitpid(pid) | 
					
						
							|  |  |  |         end | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 14:32:49 -04:00
										 |  |  |         # 130 is the exit status for a process interrupted via Ctrl-C. | 
					
						
							|  |  |  |         raise Interrupt if $CHILD_STATUS.exitstatus == 130
 | 
					
						
							|  |  |  |         raise Interrupt if $CHILD_STATUS.termsig == Signal.list["INT"] | 
					
						
							| 
									
										
										
										
											2018-09-06 23:48:07 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-14 14:32:49 -04:00
										 |  |  |         if data.present? | 
					
						
							|  |  |  |           error_hash = JSON.parse(T.must(data.lines.first)) | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |           raise rewrite_child_error(error_hash) | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2024-07-14 14:32:49 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  |         raise ChildProcessError, $CHILD_STATUS unless $CHILD_STATUS.success? | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2024-08-28 03:45:26 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if directory | 
					
						
							|  |  |  |       block.call(directory) | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       Dir.mktmpdir("homebrew-fork", HOMEBREW_TEMP, &block) | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  |   end | 
					
						
							|  |  |  | end |