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