wineserver: Report non-zero exit code for abnormal process termination
Overview:
Wine currently reports an exit code of zero for processes that terminate due to either of the following conditions:
-
The Linux process that represents the Windows process receives a signal such as
SIGKILL
(e.g. due to a user manually killing the process, or the Linux OOM killer targeting the process due to memory pressure) -
The process fails to start because the image is a 32-bit executable and Wine has only been built with 64-bit support
Both of these scenarios represent failures, and so a non-zero exit code is appropriate. This patch ensures that an exit code of 1 is reported for these abnormal process termination edge cases.
Underlying cause:
The following logic flow occurs in the Wine server for typical process termination:
-
When a
process
object is created by the Wine server, itsexit_code
field is set to an initial value ofSTILL_ACTIVE
, and theexit_code
field in each of its correspondingthread
objects is set to a default value of zero. -
When a client process terminates, it sends a
terminate_process
protocol request to the server, and the handler passes the specified exit code to theterminate_process()
function. This function sets theis_terminating
field of the process object to a value of 1, and if the exit code is non-zero then it also copies its value to each of the thread objects for the process. -
The client process closes the pipe that it had been using to communicate with the server.
-
The server detects the pipe close event and calls the
kill_process()
function, specifying a value of zero for theviolent_death
parameter. -
The logic in the
kill_process()
function identifies this as a normal termination on pipe close, and then subsequently calls thekill_thread()
function to kill each of the threads for the process. -
The
kill_thread()
function then callsremove_process_thread()
, which copies the exit code from the last thread object to theexit_code
field of the process object. -
The final value of the
exit_code
field is reported when responding toget_process_info
protocol requests, as sent byNtQueryInformationProcess()
.
The abnormal process termination scenarios listed earlier will skip the terminate_process
protocol request and immediately close the pipe. As a result, the is_terminating
field of the process object will retain its default value of zero, and the exit_code
field of each thread for the process will also remain at the default value of zero.
Since the logic in process_poll_event()
always treats a pipe close event as a non-violent termination, remove_process_thread()
will simply copy the exit code value of zero from the last thread into the process object, and this will then be reported when querying the process exit code.
Fix details:
The fix modifies process_poll_event()
to check the value of the is_terminating
field of the process object, and to treat a pipe close event as a violent termination if the value of the field is zero. This triggers the alternative code path in kill_process()
that calls terminate_process()
and specifies an exit code value of 1, which is copied to the thread objects and subsequently to the process object.
In my testing, only the abnormal process termination scenarios listed above result in the value of the is_terminating
field being zero when the pipe is closed. In all other scenarios, I have observed the value to be 1 when the pipe close event is detected. Non-abnormal scenarios tested include ordinary process completion with both zero and non-zero exit codes, crashes in Windows processes due to unhandled exceptions, and ending processes via the Wine implementation of Task Manager.