[Process] Concatenate envirnoment key and value in managed
[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, gunichar2 *shell_path,
135                              MonoString *cmd, guint32 creation_flags, gunichar2 *env_vars,
136                              gunichar2 *dir, STARTUPINFO *start_info, PROCESS_INFORMATION *process_info)
137 {
138         gboolean result = FALSE;
139
140         if (mono_process_info->username) {
141                 guint32 logon_flags = mono_process_info->load_user_profile ? LOGON_WITH_PROFILE : 0;
142
143                 result = CreateProcessWithLogonW (mono_string_chars (mono_process_info->username),
144                                                   mono_process_info->domain ? mono_string_chars (mono_process_info->domain) : NULL,
145                                                   (const gunichar2 *)mono_process_info->password,
146                                                   logon_flags,
147                                                   shell_path,
148                                                   cmd ? mono_string_chars (cmd) : NULL,
149                                                   creation_flags,
150                                                   (gchar*) env_vars, dir, start_info, process_info);
151
152         } else {
153
154                 result = CreateProcess (shell_path,
155                                         cmd ? mono_string_chars (cmd): NULL,
156                                         NULL,
157                                         NULL,
158                                         TRUE,
159                                         creation_flags,
160                                         (gchar*) env_vars,
161                                         dir,
162                                         start_info,
163                                         process_info);
164
165         }
166
167         return result;
168 }
169 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
170
171 static gchar*
172 process_unquote_application_name (gchar *appname)
173 {
174         size_t len = strlen (appname);
175         if (len) {
176                 if (appname[len-1] == '\"')
177                         appname[len-1] = '\0';
178                 if (appname[0] == '\"')
179                         appname++;
180         }
181
182         return appname;
183 }
184
185 static gchar*
186 process_quote_path (const gchar *path)
187 {
188         gchar *res = g_shell_quote (path);
189         gchar *q = res;
190         while (*q) {
191                 if (*q == '\'')
192                         *q = '\"';
193                 q++;
194         }
195         return res;
196 }
197
198 /* Only used when UseShellExecute is false */
199 static gboolean
200 process_complete_path (const gunichar2 *appname, gchar **completed)
201 {
202         gchar *utf8app, *utf8appmemory;
203         gchar *found;
204
205         utf8appmemory = g_utf16_to_utf8 (appname, -1, NULL, NULL, NULL);
206         utf8app = process_unquote_application_name (utf8appmemory);
207
208         if (g_path_is_absolute (utf8app)) {
209                 *completed = process_quote_path (utf8app);
210                 g_free (utf8appmemory);
211                 return TRUE;
212         }
213
214         if (g_file_test (utf8app, G_FILE_TEST_IS_EXECUTABLE) && !g_file_test (utf8app, G_FILE_TEST_IS_DIR)) {
215                 *completed = process_quote_path (utf8app);
216                 g_free (utf8appmemory);
217                 return TRUE;
218         }
219         
220         found = g_find_program_in_path (utf8app);
221         if (found == NULL) {
222                 *completed = NULL;
223                 g_free (utf8appmemory);
224                 return FALSE;
225         }
226
227         *completed = process_quote_path (found);
228         g_free (found);
229         g_free (utf8appmemory);
230         return TRUE;
231 }
232
233 static gboolean
234 process_get_shell_arguments (MonoW32ProcessStartInfo *proc_start_info, gunichar2 **shell_path, MonoString **cmd)
235 {
236         gchar           *spath = NULL;
237         gchar           *new_cmd, *cmd_utf8;
238         MonoError       mono_error;
239
240         *shell_path = NULL;
241         *cmd = proc_start_info->arguments;
242
243         process_complete_path (mono_string_chars (proc_start_info->filename), &spath);
244         if (spath != NULL) {
245                 /* Seems like our CreateProcess does not work as the windows one.
246                  * This hack is needed to deal with paths containing spaces */
247                 if (*cmd) {
248                         cmd_utf8 = mono_string_to_utf8_checked (*cmd, &mono_error);
249                         if (!mono_error_set_pending_exception (&mono_error)) {
250                                 new_cmd = g_strdup_printf ("%s %s", spath, cmd_utf8);
251                                 *cmd = mono_string_new_wrapper (new_cmd);
252                                 g_free (cmd_utf8);
253                                 g_free (new_cmd);
254                         } else {
255                                 *cmd = NULL;
256                         }
257                 }
258                 else {
259                         *cmd = mono_string_new_wrapper (spath);
260                 }
261
262                 g_free (spath);
263         }
264
265         return (*cmd != NULL) ? TRUE : FALSE;
266 }
267
268 MonoBoolean
269 ves_icall_System_Diagnostics_Process_CreateProcess_internal (MonoW32ProcessStartInfo *proc_start_info, HANDLE stdin_handle,
270                                                              HANDLE stdout_handle, HANDLE stderr_handle, MonoW32ProcessInfo *process_info)
271 {
272         gboolean ret;
273         gunichar2 *dir;
274         STARTUPINFO startinfo={0};
275         PROCESS_INFORMATION procinfo;
276         gunichar2 *shell_path = NULL;
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, &shell_path, &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                         /* it's null-separated and null-terminated */
305                         len += sizeof (gunichar2);
306                 }
307
308                 env_vars = ptr = g_new (gunichar2, len);
309
310                 for (i = 0; i < mono_array_length (process_info->env_variables); i++) {
311                         var = mono_array_get (process_info->env_variables, MonoString*, i);
312
313                         memcpy (ptr, mono_string_chars (var), mono_string_length (var) * sizeof (gunichar2));
314                         ptr += mono_string_length (key);
315
316                         memset (ptr, 0, sizeof (gunichar2));
317                         ptr += 1;
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, shell_path, cmd, creation_flags, env_vars, dir, &startinfo, &procinfo);
330
331         g_free (env_vars);
332         if (shell_path != NULL)
333                 g_free (shell_path);
334
335         if (ret) {
336                 process_info->process_handle = procinfo.hProcess;
337                 /*process_info->thread_handle=procinfo.hThread;*/
338                 process_info->thread_handle = NULL;
339                 if (procinfo.hThread != NULL && procinfo.hThread != INVALID_HANDLE_VALUE)
340                         CloseHandle (procinfo.hThread);
341                 process_info->pid = procinfo.dwProcessId;
342                 process_info->tid = procinfo.dwThreadId;
343         } else {
344                 process_info->pid = -GetLastError ();
345         }
346         
347         return ret;
348 }
349
350 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
351 static inline gboolean
352 mono_process_win_enum_processes (DWORD *pids, DWORD count, DWORD *needed)
353 {
354         return EnumProcesses (pids, count, needed);
355 }
356 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
357
358 MonoArray *
359 ves_icall_System_Diagnostics_Process_GetProcesses_internal (void)
360 {
361         MonoError error;
362         MonoArray *procs;
363         gboolean ret;
364         DWORD needed;
365         int count;
366         DWORD *pids;
367
368         count = 512;
369         do {
370                 pids = g_new0 (DWORD, count);
371                 ret = mono_process_win_enum_processes (pids, count * sizeof (guint32), &needed);
372                 if (ret == FALSE) {
373                         MonoException *exc;
374
375                         g_free (pids);
376                         pids = NULL;
377                         exc = mono_get_exception_not_supported ("This system does not support EnumProcesses");
378                         mono_set_pending_exception (exc);
379                         return NULL;
380                 }
381                 if (needed < (count * sizeof (guint32)))
382                         break;
383                 g_free (pids);
384                 pids = NULL;
385                 count = (count * 3) / 2;
386         } while (TRUE);
387
388         count = needed / sizeof (guint32);
389         procs = mono_array_new_checked (mono_domain_get (), mono_get_int32_class (), count, &error);
390         if (mono_error_set_pending_exception (&error)) {
391                 g_free (pids);
392                 return NULL;
393         }
394
395         memcpy (mono_array_addr (procs, guint32, 0), pids, needed);
396         g_free (pids);
397         pids = NULL;
398
399         return procs;
400 }
401
402 MonoBoolean
403 ves_icall_Microsoft_Win32_NativeMethods_CloseProcess (gpointer handle)
404 {
405         return CloseHandle (handle);
406 }
407
408 MonoBoolean
409 ves_icall_Microsoft_Win32_NativeMethods_TerminateProcess (gpointer handle, gint32 exitcode)
410 {
411         return TerminateProcess (handle, exitcode);
412 }
413
414 MonoBoolean
415 ves_icall_Microsoft_Win32_NativeMethods_GetExitCodeProcess (gpointer handle, gint32 *exitcode)
416 {
417         return GetExitCodeProcess (handle, exitcode);
418 }
419
420 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
421 static inline MonoBoolean
422 mono_icall_get_process_working_set_size (gpointer handle, gsize *min, gsize *max)
423 {
424         return GetProcessWorkingSetSize (handle, min, max);
425 }
426 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
427
428 MonoBoolean
429 ves_icall_Microsoft_Win32_NativeMethods_GetProcessWorkingSetSize (gpointer handle, gsize *min, gsize *max)
430 {
431         return mono_icall_get_process_working_set_size (handle, min, max);
432 }
433
434 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
435 static inline MonoBoolean
436 mono_icall_set_process_working_set_size (gpointer handle, gsize min, gsize max)
437 {
438         return SetProcessWorkingSetSize (handle, min, max);
439 }
440 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
441
442 MonoBoolean
443 ves_icall_Microsoft_Win32_NativeMethods_SetProcessWorkingSetSize (gpointer handle, gsize min, gsize max)
444 {
445         return mono_icall_set_process_working_set_size (handle, min, max);
446 }
447
448 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
449 static inline gint32
450 mono_icall_get_priority_class (gpointer handle)
451 {
452         return GetPriorityClass (handle);
453 }
454 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
455
456 gint32
457 ves_icall_Microsoft_Win32_NativeMethods_GetPriorityClass (gpointer handle)
458 {
459         return mono_icall_get_priority_class (handle);
460 }
461
462 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT)
463 static inline MonoBoolean
464 mono_icall_set_priority_class (gpointer handle, gint32 priorityClass)
465 {
466         return SetPriorityClass (handle, (guint32) priorityClass);
467 }
468 #endif /* G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) */
469
470 MonoBoolean
471 ves_icall_Microsoft_Win32_NativeMethods_SetPriorityClass (gpointer handle, gint32 priorityClass)
472 {
473         return mono_icall_set_priority_class (handle, priorityClass);
474 }
475
476 MonoBoolean
477 ves_icall_Microsoft_Win32_NativeMethods_GetProcessTimes (gpointer handle, gint64 *creationtime, gint64 *exittime, gint64 *kerneltime, gint64 *usertime)
478 {
479         return GetProcessTimes (handle, (LPFILETIME) creationtime, (LPFILETIME) exittime, (LPFILETIME) kerneltime, (LPFILETIME) usertime);
480 }
481
482 gpointer
483 ves_icall_Microsoft_Win32_NativeMethods_GetCurrentProcess (void)
484 {
485         return GetCurrentProcess ();
486 }