1 /**
2  * \file
3  * System.Diagnostics.Process support
4  *
5  * Author:
6  *	Dick Porter (dick@ximian.com)
7  *
8  * Copyright 2002 Ximian, Inc.
9  * Copyright 2002-2006 Novell, Inc.
10  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
11  */
12 
13 #include <config.h>
14 
15 #include <glib.h>
16 #include <string.h>
17 
18 #include <winsock2.h>
19 #include <windows.h>
20 
21 #include <mono/metadata/object-internals.h>
22 #include <mono/metadata/w32process.h>
23 #include <mono/metadata/w32process-win32-internals.h>
24 #include <mono/metadata/assembly.h>
25 #include <mono/metadata/appdomain.h>
26 #include <mono/metadata/image.h>
27 #include <mono/metadata/cil-coff.h>
28 #include <mono/metadata/exception.h>
29 #include <mono/metadata/threadpool-io.h>
30 #include <mono/utils/strenc.h>
31 #include <mono/utils/mono-proclib.h>
32 /* FIXME: fix this code to not depend so much on the internals */
33 #include <mono/metadata/class-internals.h>
34 #include <mono/metadata/w32handle.h>
35 #include <mono/utils/w32api.h>
36 
37 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
38 #include <shellapi.h>
39 #endif
40 
41 void
mono_w32process_init(void)42 mono_w32process_init (void)
43 {
44 }
45 
46 void
mono_w32process_cleanup(void)47 mono_w32process_cleanup (void)
48 {
49 }
50 
51 void
mono_w32process_signal_finished(void)52 mono_w32process_signal_finished (void)
53 {
54 }
55 
56 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
57 HANDLE
ves_icall_System_Diagnostics_Process_GetProcess_internal(guint32 pid)58 ves_icall_System_Diagnostics_Process_GetProcess_internal (guint32 pid)
59 {
60 	HANDLE handle;
61 
62 	/* GetCurrentProcess returns a pseudo-handle, so use
63 	 * OpenProcess instead
64 	 */
65 	handle = OpenProcess (PROCESS_ALL_ACCESS, TRUE, pid);
66 	if (handle == NULL)
67 		/* FIXME: Throw an exception */
68 		return NULL;
69 	return handle;
70 }
71 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT | HAVE_UWP_WINAPI_SUPPORT) */
72 
73 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
74 MonoBoolean
ves_icall_System_Diagnostics_Process_ShellExecuteEx_internal(MonoW32ProcessStartInfo * proc_start_info,MonoW32ProcessInfo * process_info)75 ves_icall_System_Diagnostics_Process_ShellExecuteEx_internal (MonoW32ProcessStartInfo *proc_start_info, MonoW32ProcessInfo *process_info)
76 {
77 	SHELLEXECUTEINFO shellex = {0};
78 	gboolean ret;
79 
80 	shellex.cbSize = sizeof(SHELLEXECUTEINFO);
81 	shellex.fMask = (gulong)(SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_UNICODE);
82 	shellex.nShow = (gulong)proc_start_info->window_style;
83 	shellex.nShow = (gulong)((shellex.nShow == 0) ? 1 : (shellex.nShow == 1 ? 0 : shellex.nShow));
84 
85 	if (proc_start_info->filename != NULL) {
86 		shellex.lpFile = mono_string_chars (proc_start_info->filename);
87 	}
88 
89 	if (proc_start_info->arguments != NULL) {
90 		shellex.lpParameters = mono_string_chars (proc_start_info->arguments);
91 	}
92 
93 	if (proc_start_info->verb != NULL &&
94 	    mono_string_length (proc_start_info->verb) != 0) {
95 		shellex.lpVerb = mono_string_chars (proc_start_info->verb);
96 	}
97 
98 	if (proc_start_info->working_directory != NULL &&
99 	    mono_string_length (proc_start_info->working_directory) != 0) {
100 		shellex.lpDirectory = mono_string_chars (proc_start_info->working_directory);
101 	}
102 
103 	if (proc_start_info->error_dialog) {
104 		shellex.hwnd = proc_start_info->error_dialog_parent_handle;
105 	} else {
106 		shellex.fMask = (gulong)(shellex.fMask | SEE_MASK_FLAG_NO_UI);
107 	}
108 
109 	ret = ShellExecuteEx (&shellex);
110 	if (ret == FALSE) {
111 		process_info->pid = -GetLastError ();
112 	} else {
113 		process_info->process_handle = shellex.hProcess;
114 #if !defined(MONO_CROSS_COMPILE)
115 		process_info->pid = GetProcessId (shellex.hProcess);
116 #else
117 		process_info->pid = 0;
118 #endif
119 	}
120 
121 	return ret;
122 }
123 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
124 
125 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
126 static inline void
mono_process_init_startup_info(HANDLE stdin_handle,HANDLE stdout_handle,HANDLE stderr_handle,STARTUPINFO * startinfo)127 mono_process_init_startup_info (HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle, STARTUPINFO *startinfo)
128 {
129 	startinfo->cb = sizeof(STARTUPINFO);
130 	startinfo->dwFlags = STARTF_USESTDHANDLES;
131 	startinfo->hStdInput = stdin_handle;
132 	startinfo->hStdOutput = stdout_handle;
133 	startinfo->hStdError = stderr_handle;
134 	return;
135 }
136 
137 static gboolean
mono_process_create_process(MonoW32ProcessInfo * mono_process_info,MonoString * cmd,guint32 creation_flags,gunichar2 * env_vars,gunichar2 * dir,STARTUPINFO * start_info,PROCESS_INFORMATION * process_info)138 mono_process_create_process (MonoW32ProcessInfo *mono_process_info, MonoString *cmd, guint32 creation_flags,
139 	gunichar2 *env_vars, gunichar2 *dir, STARTUPINFO *start_info, PROCESS_INFORMATION *process_info)
140 {
141 	gboolean result = FALSE;
142 
143 	if (mono_process_info->username) {
144 		guint32 logon_flags = mono_process_info->load_user_profile ? LOGON_WITH_PROFILE : 0;
145 
146 		result = CreateProcessWithLogonW (mono_string_chars (mono_process_info->username),
147 						  mono_process_info->domain ? mono_string_chars (mono_process_info->domain) : NULL,
148 						  (const gunichar2 *)mono_process_info->password,
149 						  logon_flags,
150 						  NULL,
151 						  cmd ? mono_string_chars (cmd) : NULL,
152 						  creation_flags,
153 						  env_vars, dir, start_info, process_info);
154 
155 	} else {
156 
157 		result = CreateProcessW (NULL,
158 					cmd ? mono_string_chars (cmd): NULL,
159 					NULL,
160 					NULL,
161 					TRUE,
162 					creation_flags,
163 					env_vars,
164 					dir,
165 					start_info,
166 					process_info);
167 
168 	}
169 
170 	return result;
171 }
172 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
173 
174 static gchar*
process_unquote_application_name(gchar * appname)175 process_unquote_application_name (gchar *appname)
176 {
177 	size_t len = strlen (appname);
178 	if (len) {
179 		if (appname[len-1] == '\"')
180 			appname[len-1] = '\0';
181 		if (appname[0] == '\"')
182 			appname++;
183 	}
184 
185 	return appname;
186 }
187 
188 static gchar*
process_quote_path(const gchar * path)189 process_quote_path (const gchar *path)
190 {
191 	gchar *res = g_shell_quote (path);
192 	gchar *q = res;
193 	while (*q) {
194 		if (*q == '\'')
195 			*q = '\"';
196 		q++;
197 	}
198 	return res;
199 }
200 
201 /* Only used when UseShellExecute is false */
202 static gboolean
process_complete_path(const gunichar2 * appname,gchar ** completed)203 process_complete_path (const gunichar2 *appname, gchar **completed)
204 {
205 	gchar *utf8app, *utf8appmemory;
206 	gchar *found;
207 
208 	utf8appmemory = g_utf16_to_utf8 (appname, -1, NULL, NULL, NULL);
209 	utf8app = process_unquote_application_name (utf8appmemory);
210 
211 	if (g_path_is_absolute (utf8app)) {
212 		*completed = process_quote_path (utf8app);
213 		g_free (utf8appmemory);
214 		return TRUE;
215 	}
216 
217 	if (g_file_test (utf8app, G_FILE_TEST_IS_EXECUTABLE) && !g_file_test (utf8app, G_FILE_TEST_IS_DIR)) {
218 		*completed = process_quote_path (utf8app);
219 		g_free (utf8appmemory);
220 		return TRUE;
221 	}
222 
223 	found = g_find_program_in_path (utf8app);
224 	if (found == NULL) {
225 		*completed = NULL;
226 		g_free (utf8appmemory);
227 		return FALSE;
228 	}
229 
230 	*completed = process_quote_path (found);
231 	g_free (found);
232 	g_free (utf8appmemory);
233 	return TRUE;
234 }
235 
236 static gboolean
process_get_shell_arguments(MonoW32ProcessStartInfo * proc_start_info,MonoString ** cmd)237 process_get_shell_arguments (MonoW32ProcessStartInfo *proc_start_info, MonoString **cmd)
238 {
239 	gchar		*spath = NULL;
240 	gchar		*new_cmd, *cmd_utf8;
241 	MonoError	mono_error;
242 
243 	*cmd = proc_start_info->arguments;
244 
245 	if (process_complete_path (mono_string_chars (proc_start_info->filename), &spath)) {
246 		/* Seems like our CreateProcess does not work as the windows one.
247 		 * This hack is needed to deal with paths containing spaces */
248 		if (*cmd) {
249 			cmd_utf8 = mono_string_to_utf8_checked (*cmd, &mono_error);
250 			if (!mono_error_set_pending_exception (&mono_error)) {
251 				new_cmd = g_strdup_printf ("%s %s", spath, cmd_utf8);
252 				*cmd = mono_string_new_wrapper (new_cmd);
253 				g_free (cmd_utf8);
254 				g_free (new_cmd);
255 			} else {
256 				*cmd = NULL;
257 			}
258 		}
259 		else {
260 			*cmd = mono_string_new_wrapper (spath);
261 		}
262 
263 		g_free (spath);
264 	}
265 
266 	return (*cmd != NULL) ? TRUE : FALSE;
267 }
268 
269 MonoBoolean
ves_icall_System_Diagnostics_Process_CreateProcess_internal(MonoW32ProcessStartInfo * proc_start_info,HANDLE stdin_handle,HANDLE stdout_handle,HANDLE stderr_handle,MonoW32ProcessInfo * process_info)270 ves_icall_System_Diagnostics_Process_CreateProcess_internal (MonoW32ProcessStartInfo *proc_start_info, HANDLE stdin_handle,
271 							     HANDLE stdout_handle, HANDLE stderr_handle, MonoW32ProcessInfo *process_info)
272 {
273 	gboolean ret;
274 	gunichar2 *dir;
275 	STARTUPINFO startinfo={0};
276 	PROCESS_INFORMATION procinfo;
277 	gunichar2 *env_vars = NULL;
278 	MonoString *cmd = NULL;
279 	guint32 creation_flags;
280 
281 	mono_process_init_startup_info (stdin_handle, stdout_handle, stderr_handle, &startinfo);
282 
283 	creation_flags = CREATE_UNICODE_ENVIRONMENT;
284 	if (proc_start_info->create_no_window)
285 		creation_flags |= CREATE_NO_WINDOW;
286 
287 	if (process_get_shell_arguments (proc_start_info, &cmd) == FALSE) {
288 		process_info->pid = -ERROR_FILE_NOT_FOUND;
289 		return FALSE;
290 	}
291 
292 	if (process_info->env_variables) {
293 		gint i, len;
294 		MonoString *var;
295 		gunichar2 *str, *ptr;
296 
297 		len = 0;
298 
299 		for (i = 0; i < mono_array_length (process_info->env_variables); i++) {
300 			var = mono_array_get (process_info->env_variables, MonoString*, i);
301 
302 			len += mono_string_length (var) * sizeof (gunichar2);
303 
304 			/* null-separated */
305 			len += sizeof (gunichar2);
306 		}
307 		/* null-terminated */
308 		len += sizeof (gunichar2);
309 
310 		env_vars = ptr = g_new0 (gunichar2, len);
311 
312 		for (i = 0; i < mono_array_length (process_info->env_variables); i++) {
313 			var = mono_array_get (process_info->env_variables, MonoString*, i);
314 
315 			memcpy (ptr, mono_string_chars (var), mono_string_length (var) * sizeof (gunichar2));
316 			ptr += mono_string_length (var);
317 			ptr += 1; // Skip over the null-separator
318 		}
319 	}
320 
321 	/* The default dir name is "".  Turn that into NULL to mean
322 	 * "current directory"
323 	 */
324 	if (proc_start_info->working_directory == NULL || mono_string_length (proc_start_info->working_directory) == 0)
325 		dir = NULL;
326 	else
327 		dir = mono_string_chars (proc_start_info->working_directory);
328 
329 	ret = mono_process_create_process (process_info, cmd, creation_flags, env_vars, dir, &startinfo, &procinfo);
330 
331 	g_free (env_vars);
332 
333 	if (ret) {
334 		process_info->process_handle = procinfo.hProcess;
335 		/*process_info->thread_handle=procinfo.hThread;*/
336 		if (procinfo.hThread != NULL && procinfo.hThread != INVALID_HANDLE_VALUE)
337 			CloseHandle (procinfo.hThread);
338 		process_info->pid = procinfo.dwProcessId;
339 	} else {
340 		process_info->pid = -GetLastError ();
341 	}
342 
343 	return ret;
344 }
345 
346 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
347 static inline gboolean
mono_process_win_enum_processes(DWORD * pids,DWORD count,DWORD * needed)348 mono_process_win_enum_processes (DWORD *pids, DWORD count, DWORD *needed)
349 {
350 	return EnumProcesses (pids, count, needed);
351 }
352 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
353 
354 MonoArray *
ves_icall_System_Diagnostics_Process_GetProcesses_internal(void)355 ves_icall_System_Diagnostics_Process_GetProcesses_internal (void)
356 {
357 	MonoError error;
358 	MonoArray *procs;
359 	gboolean ret;
360 	DWORD needed;
361 	int count;
362 	DWORD *pids;
363 
364 	count = 512;
365 	do {
366 		pids = g_new0 (DWORD, count);
367 		ret = mono_process_win_enum_processes (pids, count * sizeof (guint32), &needed);
368 		if (ret == FALSE) {
369 			MonoException *exc;
370 
371 			g_free (pids);
372 			pids = NULL;
373 			exc = mono_get_exception_not_supported ("This system does not support EnumProcesses");
374 			mono_set_pending_exception (exc);
375 			return NULL;
376 		}
377 		if (needed < (count * sizeof (guint32)))
378 			break;
379 		g_free (pids);
380 		pids = NULL;
381 		count = (count * 3) / 2;
382 	} while (TRUE);
383 
384 	count = needed / sizeof (guint32);
385 	procs = mono_array_new_checked (mono_domain_get (), mono_get_int32_class (), count, &error);
386 	if (mono_error_set_pending_exception (&error)) {
387 		g_free (pids);
388 		return NULL;
389 	}
390 
391 	memcpy (mono_array_addr (procs, guint32, 0), pids, needed);
392 	g_free (pids);
393 	pids = NULL;
394 
395 	return procs;
396 }
397 
398 MonoBoolean
ves_icall_Microsoft_Win32_NativeMethods_CloseProcess(gpointer handle)399 ves_icall_Microsoft_Win32_NativeMethods_CloseProcess (gpointer handle)
400 {
401 	return CloseHandle (handle);
402 }
403 
404 MonoBoolean
ves_icall_Microsoft_Win32_NativeMethods_TerminateProcess(gpointer handle,gint32 exitcode)405 ves_icall_Microsoft_Win32_NativeMethods_TerminateProcess (gpointer handle, gint32 exitcode)
406 {
407 	return TerminateProcess (handle, exitcode);
408 }
409 
410 MonoBoolean
ves_icall_Microsoft_Win32_NativeMethods_GetExitCodeProcess(gpointer handle,gint32 * exitcode)411 ves_icall_Microsoft_Win32_NativeMethods_GetExitCodeProcess (gpointer handle, gint32 *exitcode)
412 {
413 	return GetExitCodeProcess (handle, exitcode);
414 }
415 
416 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
417 static inline MonoBoolean
mono_icall_get_process_working_set_size(gpointer handle,gsize * min,gsize * max)418 mono_icall_get_process_working_set_size (gpointer handle, gsize *min, gsize *max)
419 {
420 	return GetProcessWorkingSetSize (handle, min, max);
421 }
422 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
423 
424 MonoBoolean
ves_icall_Microsoft_Win32_NativeMethods_GetProcessWorkingSetSize(gpointer handle,gsize * min,gsize * max)425 ves_icall_Microsoft_Win32_NativeMethods_GetProcessWorkingSetSize (gpointer handle, gsize *min, gsize *max)
426 {
427 	return mono_icall_get_process_working_set_size (handle, min, max);
428 }
429 
430 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
431 static inline MonoBoolean
mono_icall_set_process_working_set_size(gpointer handle,gsize min,gsize max)432 mono_icall_set_process_working_set_size (gpointer handle, gsize min, gsize max)
433 {
434 	return SetProcessWorkingSetSize (handle, min, max);
435 }
436 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
437 
438 MonoBoolean
ves_icall_Microsoft_Win32_NativeMethods_SetProcessWorkingSetSize(gpointer handle,gsize min,gsize max)439 ves_icall_Microsoft_Win32_NativeMethods_SetProcessWorkingSetSize (gpointer handle, gsize min, gsize max)
440 {
441 	return mono_icall_set_process_working_set_size (handle, min, max);
442 }
443 
444 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
445 static inline gint32
mono_icall_get_priority_class(gpointer handle)446 mono_icall_get_priority_class (gpointer handle)
447 {
448 	return GetPriorityClass (handle);
449 }
450 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
451 
452 gint32
ves_icall_Microsoft_Win32_NativeMethods_GetPriorityClass(gpointer handle)453 ves_icall_Microsoft_Win32_NativeMethods_GetPriorityClass (gpointer handle)
454 {
455 	return mono_icall_get_priority_class (handle);
456 }
457 
458 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
459 static inline MonoBoolean
mono_icall_set_priority_class(gpointer handle,gint32 priorityClass)460 mono_icall_set_priority_class (gpointer handle, gint32 priorityClass)
461 {
462 	return SetPriorityClass (handle, (guint32) priorityClass);
463 }
464 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
465 
466 MonoBoolean
ves_icall_Microsoft_Win32_NativeMethods_SetPriorityClass(gpointer handle,gint32 priorityClass)467 ves_icall_Microsoft_Win32_NativeMethods_SetPriorityClass (gpointer handle, gint32 priorityClass)
468 {
469 	return mono_icall_set_priority_class (handle, priorityClass);
470 }
471 
472 MonoBoolean
ves_icall_Microsoft_Win32_NativeMethods_GetProcessTimes(gpointer handle,gint64 * creationtime,gint64 * exittime,gint64 * kerneltime,gint64 * usertime)473 ves_icall_Microsoft_Win32_NativeMethods_GetProcessTimes (gpointer handle, gint64 *creationtime, gint64 *exittime, gint64 *kerneltime, gint64 *usertime)
474 {
475 	return GetProcessTimes (handle, (LPFILETIME) creationtime, (LPFILETIME) exittime, (LPFILETIME) kerneltime, (LPFILETIME) usertime);
476 }
477 
478 gpointer
ves_icall_Microsoft_Win32_NativeMethods_GetCurrentProcess(void)479 ves_icall_Microsoft_Win32_NativeMethods_GetCurrentProcess (void)
480 {
481 	return GetCurrentProcess ();
482 }
483