| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  | require "fcntl" | 
					
						
							|  |  |  | require "socket" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Utils | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  |   def self.rewrite_child_error(child_error) | 
					
						
							| 
									
										
										
										
											2020-05-12 10:26:09 +01:00
										 |  |  |     error = if child_error.inner["cmd"] && | 
					
						
							|  |  |  |                child_error.inner_class == ErrorDuringExecution | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  |       ErrorDuringExecution.new(child_error.inner["cmd"], | 
					
						
							|  |  |  |                                status: child_error.inner["status"], | 
					
						
							|  |  |  |                                output: child_error.inner["output"]) | 
					
						
							| 
									
										
										
										
											2020-05-12 10:26:09 +01:00
										 |  |  |     elsif child_error.inner["cmd"] && | 
					
						
							|  |  |  |           child_error.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`. | 
					
						
							|  |  |  |       BuildError.new(nil, child_error.inner["cmd"], | 
					
						
							|  |  |  |                      child_error.inner["args"], child_error.inner["env"]) | 
					
						
							| 
									
										
										
										
											2018-09-06 23:48:07 -04:00
										 |  |  |     elsif child_error.inner_class == Interrupt | 
					
						
							|  |  |  |       Interrupt.new | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  |     else | 
					
						
							|  |  |  |       # Everything other error in the child just becomes a RuntimeError. | 
					
						
							|  |  |  |       RuntimeError.new(child_error.message) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     error.set_backtrace child_error.backtrace | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     error | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-08-03 13:09:07 +01:00
										 |  |  |   def self.safe_fork(&_block) | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |     Dir.mktmpdir("homebrew", HOMEBREW_TEMP) do |tmpdir| | 
					
						
							|  |  |  |       UNIXServer.open("#{tmpdir}/socket") do |server| | 
					
						
							|  |  |  |         read, write = IO.pipe | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |         pid = fork do | 
					
						
							| 
									
										
										
										
											2019-10-13 10:03:26 +01:00
										 |  |  |           ENV["HOMEBREW_ERROR_PIPE"] = server.path | 
					
						
							|  |  |  |           server.close | 
					
						
							|  |  |  |           read.close | 
					
						
							|  |  |  |           write.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) | 
					
						
							|  |  |  |           yield | 
					
						
							|  |  |  |         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 | 
					
						
							|  |  |  |             error_hash["status"] = e.status.exitstatus | 
					
						
							|  |  |  |             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! | 
					
						
							| 
									
										
										
										
											2019-10-16 19:10:52 +02:00
										 |  |  |         else # rubocop:disable Layout/ElseAlignment | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |         ignore_interrupts(:quietly) do # the child will receive the interrupt and marshal it back | 
					
						
							|  |  |  |           begin | 
					
						
							|  |  |  |             socket = server.accept_nonblock | 
					
						
							|  |  |  |           rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR | 
					
						
							|  |  |  |             retry unless Process.waitpid(pid, Process::WNOHANG) | 
					
						
							|  |  |  |           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 | 
					
						
							|  |  |  |           Process.wait(pid) unless socket.nil? | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-06 23:48:07 -04:00
										 |  |  |           # 130 is the exit status for a process interrupted via Ctrl-C. | 
					
						
							|  |  |  |           # We handle it here because of the possibility of an interrupted process terminating | 
					
						
							|  |  |  |           # without writing its Interrupt exception to the error pipe. | 
					
						
							|  |  |  |           raise Interrupt if $CHILD_STATUS.exitstatus == 130
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  |           if data && !data.empty? | 
					
						
							| 
									
										
										
										
											2018-09-06 23:48:07 -04:00
										 |  |  |             error_hash = JSON.parse(data.lines.first) | 
					
						
							| 
									
										
										
										
											2018-08-17 22:42:37 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |             e = ChildProcessError.new(error_hash) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             raise rewrite_child_error(e) | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-25 12:35:03 -07:00
										 |  |  |           raise "Forked child process failed: #{$CHILD_STATUS}" unless $CHILD_STATUS.success? | 
					
						
							| 
									
										
										
										
											2015-04-24 22:13:45 -04:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2015-04-13 18:02:46 +08:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |