2 * processes.c: Process handles
5 * Dick Porter (dick@ximian.com)
7 * (C) 2002 Ximian, Inc.
12 #include <mono/os/gc_wrapper.h>
13 #include "mono/utils/mono-hash.h"
21 #include <sys/types.h>
25 #include <mono/io-layer/wapi.h>
26 #include <mono/io-layer/wapi-private.h>
27 #include <mono/io-layer/handles-private.h>
28 #include <mono/io-layer/misc-private.h>
29 #include <mono/io-layer/mono-mutex.h>
30 #include <mono/io-layer/process-private.h>
31 #include <mono/io-layer/threads.h>
32 #include <mono/utils/strenc.h>
34 /* The process' environment strings */
35 extern char **environ;
39 static void process_close_shared (gpointer handle);
41 struct _WapiHandleOps _wapi_process_ops = {
42 process_close_shared, /* close_shared */
43 NULL, /* close_private */
49 static mono_once_t process_current_once=MONO_ONCE_INIT;
50 static gpointer current_process=NULL;
52 static mono_once_t process_ops_once=MONO_ONCE_INIT;
54 static void process_ops_init (void)
56 _wapi_handle_register_capabilities (WAPI_HANDLE_PROCESS,
57 WAPI_HANDLE_CAP_WAIT);
60 static void process_close_shared (gpointer handle G_GNUC_UNUSED)
62 struct _WapiHandle_process *process_handle;
65 ok=_wapi_lookup_handle (handle, WAPI_HANDLE_PROCESS,
66 (gpointer *)&process_handle, NULL);
68 g_warning (G_GNUC_PRETTY_FUNCTION
69 ": error looking up process handle %p", handle);
74 g_message (G_GNUC_PRETTY_FUNCTION
75 ": closing process handle %p with id %d", handle,
79 if(process_handle->proc_name!=0) {
80 _wapi_handle_scratch_delete (process_handle->proc_name);
81 process_handle->proc_name=0;
85 gboolean CreateProcess (const gunichar2 *appname, gunichar2 *cmdline,
86 WapiSecurityAttributes *process_attrs G_GNUC_UNUSED,
87 WapiSecurityAttributes *thread_attrs G_GNUC_UNUSED,
88 gboolean inherit_handles, guint32 create_flags,
89 gpointer new_environ, const gunichar2 *cwd,
90 WapiStartupInfo *startup,
91 WapiProcessInformation *process_info)
93 gchar *cmd=NULL, *prog = NULL, *full_prog = NULL, *args=NULL, *args_after_prog=NULL, *dir=NULL;
94 guint32 env=0, stored_dir=0, stored_prog=0, i;
96 gpointer stdin_handle, stdout_handle, stderr_handle;
98 gpointer process_handle, thread_handle;
99 struct _WapiHandle_process *process_handle_data;
101 mono_once (&process_ops_once, process_ops_init);
103 /* appname and cmdline specify the executable and its args:
105 * If appname is not NULL, it is the name of the executable.
106 * Otherwise the executable is the first token in cmdline.
108 * Executable searching:
110 * If appname is not NULL, it can specify the full path and
111 * file name, or else a partial name and the current directory
112 * will be used. There is no additional searching.
114 * If appname is NULL, the first whitespace-delimited token in
115 * cmdline is used. If the name does not contain a full
116 * directory path, the search sequence is:
118 * 1) The directory containing the current process
119 * 2) The current working directory
120 * 3) The windows system directory (Ignored)
121 * 4) The windows directory (Ignored)
124 * Just to make things more interesting, tokens can contain
125 * white space if they are surrounded by quotation marks. I'm
126 * beginning to understand just why windows apps are generally
127 * so crap, with an API like this :-(
130 cmd=mono_unicode_to_external (appname);
133 g_message (G_GNUC_PRETTY_FUNCTION
134 ": unicode conversion returned NULL");
137 SetLastError(ERROR_PATH_NOT_FOUND);
141 /* Turn all the slashes round the right way */
142 for(i=0; i<strlen (cmd); i++) {
150 args=mono_unicode_to_external (cmdline);
153 g_message (G_GNUC_PRETTY_FUNCTION
154 ": unicode conversion returned NULL");
157 SetLastError(ERROR_PATH_NOT_FOUND);
163 dir=mono_unicode_to_external (cwd);
166 g_message (G_GNUC_PRETTY_FUNCTION
167 ": unicode conversion returned NULL");
170 SetLastError(ERROR_PATH_NOT_FOUND);
174 /* Turn all the slashes round the right way */
175 for(i=0; i<strlen (dir); i++) {
181 dir=g_get_current_dir ();
183 stored_dir=_wapi_handle_scratch_store (dir, strlen (dir));
186 /* new_environ is a block of NULL-terminated strings, which
187 * is itself NULL-terminated. Of course, passing an array of
188 * string pointers would have made things too easy :-(
190 * If new_environ is not NULL it specifies the entire set of
191 * environment variables in the new process. Otherwise the
192 * new process inherits the same environment.
194 if(new_environ!=NULL) {
197 gunichar2 *new_environp;
199 /* Count the number of strings */
200 for(new_environp=(gunichar2 *)new_environ; *new_environp;
203 while(*new_environp) {
207 strings=g_new0 (gchar *, count + 1); /* +1 -> last one is NULL */
209 /* Copy each environ string into 'strings' turning it
210 * into utf8 (or the requested encoding) at the same
214 for(new_environp=(gunichar2 *)new_environ; *new_environp;
216 strings[count]=mono_unicode_to_external (new_environp);
218 while(*new_environp) {
223 env=_wapi_handle_scratch_store_string_array (strings);
225 g_strfreev (strings);
227 /* Use the existing environment */
228 env=_wapi_handle_scratch_store_string_array (environ);
231 /* We can't put off locating the executable any longer :-( */
233 if(g_ascii_isalpha (cmd[0]) && (cmd[1]==':')) {
234 /* Strip off the drive letter. I can't
235 * believe that CP/M holdover is still
238 memmove (cmd, cmd+2, strlen (cmd)-2);
239 cmd[strlen (cmd)-2]='\0';
243 /* Assume full path given */
246 /* Executable existing ? */
247 if(access (prog, X_OK)!=0) {
249 g_message (G_GNUC_PRETTY_FUNCTION ": Couldn't find executable %s", prog);
252 SetLastError (ERROR_FILE_NOT_FOUND);
256 /* Search for file named by cmd in the current
259 char *curdir=g_get_current_dir ();
261 prog=g_strdup_printf ("%s/%s", curdir, cmd);
265 args_after_prog=args;
269 /* Dig out the first token from args, taking quotation
273 /* First, strip off all leading whitespace */
274 args=g_strchug (args);
276 /* args_after_prog points to the contents of args
277 * after token has been set (otherwise argv[0] is
280 args_after_prog=args;
282 /* Assume the opening quote will always be the first
286 for(i=1; args[i]!='\0' && args[i]!='\"'; i++);
287 if(g_ascii_isspace (args[i+1])) {
288 /* We found the first token */
289 token=g_strndup (args+1, i-1);
290 args_after_prog=args+i;
292 /* Quotation mark appeared in the
293 * middle of the token. Just give the
294 * whole first token, quotes and all,
301 /* No quote mark, or malformed */
302 for(i=0; args[i]!='\0'; i++) {
303 if(g_ascii_isspace (args[i])) {
304 token=g_strndup (args, i);
305 args_after_prog=args+i+1;
311 if(token==NULL && args[0]!='\0') {
312 /* Must be just one token in the string */
313 token=g_strdup (args);
314 args_after_prog=NULL;
320 g_message (G_GNUC_PRETTY_FUNCTION
321 ": Couldn't find what to exec");
324 SetLastError(ERROR_PATH_NOT_FOUND);
328 /* Turn all the slashes round the right way. Only for the prg. name */
329 for(i=0; i < strlen (token); i++) {
330 if (token[i]=='\\') {
335 if(g_ascii_isalpha (token[0]) && (token[1]==':')) {
336 /* Strip off the drive letter. I can't
337 * believe that CP/M holdover is still
340 memmove (token, token+2, strlen (token)-2);
341 token[strlen (token)-2]='\0';
345 /* Assume full path given */
346 prog=g_strdup (token);
348 /* Executable existing ? */
349 if(access (prog, X_OK)!=0) {
352 g_message (G_GNUC_PRETTY_FUNCTION ": Couldn't find executable %s", token);
355 SetLastError (ERROR_FILE_NOT_FOUND);
360 char *curdir=g_get_current_dir ();
362 /* FIXME: Need to record the directory
363 * containing the current process, and check
364 * that for the new executable as the first
368 prog=g_strdup_printf ("%s/%s", curdir, token);
371 /* I assume X_OK is the criterion to use,
374 if(access (prog, X_OK)!=0) {
376 prog=g_find_program_in_path (token);
379 g_message (G_GNUC_PRETTY_FUNCTION ": Couldn't find executable %s", token);
383 SetLastError (ERROR_FILE_NOT_FOUND);
393 g_message (G_GNUC_PRETTY_FUNCTION ": Exec prog [%s] args [%s]", prog,
397 if(args_after_prog!=NULL) {
398 full_prog=g_strconcat (prog, " ", args_after_prog, NULL);
400 full_prog=g_strdup (prog);
403 stored_prog=_wapi_handle_scratch_store (full_prog, strlen (full_prog));
405 if(startup!=NULL && startup->dwFlags & STARTF_USESTDHANDLES) {
406 stdin_handle=startup->hStdInput;
407 stdout_handle=startup->hStdOutput;
408 stderr_handle=startup->hStdError;
410 stdin_handle=GetStdHandle (STD_INPUT_HANDLE);
411 stdout_handle=GetStdHandle (STD_OUTPUT_HANDLE);
412 stderr_handle=GetStdHandle (STD_ERROR_HANDLE);
415 ret=_wapi_handle_process_fork (stored_prog, env, stored_dir,
416 inherit_handles, create_flags,
417 stdin_handle, stdout_handle,
418 stderr_handle, &process_handle,
419 &thread_handle, &pid, &tid);
421 if(ret==TRUE && process_info!=NULL) {
422 process_info->hProcess=process_handle;
423 process_info->hThread=thread_handle;
424 process_info->dwProcessId=pid;
425 process_info->dwThreadId=tid;
426 /* Wait for possible execve failure */
427 if (WaitForSingleObjectEx (process_handle, 500, FALSE) != WAIT_TIMEOUT) {
428 _wapi_lookup_handle (GUINT_TO_POINTER (process_handle),
430 (gpointer *) &process_handle_data,
433 if (process_handle_data && process_handle_data->exec_errno != 0) {
435 SetLastError (ERROR_PATH_NOT_FOUND);
438 } else if (ret==FALSE) {
439 /* FIXME: work out a better error code
441 SetLastError (ERROR_PATH_NOT_FOUND);
448 if(full_prog!=NULL) {
452 _wapi_handle_scratch_delete (stored_prog);
461 _wapi_handle_scratch_delete (stored_dir);
464 _wapi_handle_scratch_delete_string_array (env);
470 static void process_set_name (struct _WapiHandle_process *process_handle)
472 gchar *progname, *utf8_progname, *slash;
474 progname=g_get_prgname ();
475 utf8_progname=mono_utf8_from_external (progname);
478 g_message (G_GNUC_PRETTY_FUNCTION ": using [%s] as prog name",
482 if(utf8_progname!=NULL) {
483 slash=strrchr (utf8_progname, '/');
485 process_handle->proc_name=_wapi_handle_scratch_store (slash+1, strlen (slash+1));
487 process_handle->proc_name=_wapi_handle_scratch_store (utf8_progname, strlen (utf8_progname));
490 g_free (utf8_progname);
494 extern void _wapi_time_t_to_filetime (time_t timeval, WapiFileTime *filetime);
496 static void process_set_current (void)
498 struct _WapiHandle_process *process_handle;
503 handle_env=getenv ("_WAPI_PROCESS_HANDLE");
504 if(handle_env==NULL) {
506 g_message (G_GNUC_PRETTY_FUNCTION
507 ": Need to create my own process handle");
510 current_process=_wapi_handle_new (WAPI_HANDLE_PROCESS);
511 if(current_process==_WAPI_HANDLE_INVALID) {
512 g_warning (G_GNUC_PRETTY_FUNCTION
513 ": error creating process handle");
517 ok=_wapi_lookup_handle (current_process, WAPI_HANDLE_PROCESS,
518 (gpointer *)&process_handle, NULL);
520 g_warning (G_GNUC_PRETTY_FUNCTION
521 ": error looking up process handle %p",
526 process_handle->id=pid;
528 /* These seem to be the defaults on w2k */
529 process_handle->min_working_set=204800;
530 process_handle->max_working_set=1413120;
532 _wapi_time_t_to_filetime (time (NULL), &process_handle->create_time);
534 process_set_name (process_handle);
536 /* Make sure the new handle has a reference so it wont go away
537 * until this process exits
539 _wapi_handle_ref (current_process);
543 current_process=GUINT_TO_POINTER (atoi (handle_env));
546 g_message (G_GNUC_PRETTY_FUNCTION
547 ": Found my process handle: %p", current_process);
550 ok=_wapi_lookup_handle (current_process, WAPI_HANDLE_PROCESS,
551 (gpointer *)&process_handle, NULL);
553 g_warning (G_GNUC_PRETTY_FUNCTION
554 ": error looking up process handle %p",
559 procname=_wapi_handle_scratch_lookup (process_handle->proc_name);
561 if(!strcmp (procname, "mono")) {
562 /* Set a better process name */
564 g_message (G_GNUC_PRETTY_FUNCTION ": Setting better process name");
567 _wapi_handle_scratch_delete (process_handle->proc_name);
568 process_set_name (process_handle);
571 g_message (G_GNUC_PRETTY_FUNCTION
572 ": Leaving process name: %s",
582 /* Returns a pseudo handle that doesn't need to be closed afterwards */
583 gpointer GetCurrentProcess (void)
585 mono_once (&process_current_once, process_set_current);
587 return((gpointer)-1);
590 guint32 GetCurrentProcessId (void)
592 struct _WapiHandle_process *current_process_handle;
595 mono_once (&process_current_once, process_set_current);
597 ok=_wapi_lookup_handle (current_process, WAPI_HANDLE_PROCESS,
598 (gpointer *)¤t_process_handle, NULL);
600 g_warning (G_GNUC_PRETTY_FUNCTION
601 ": error looking up current process handle %p",
603 /* No failure return is defined. PID 0 is invalid.
604 * This should only be reached when something else has
605 * gone badly wrong anyway.
610 return(current_process_handle->id);
613 static gboolean process_enum (gpointer handle, gpointer user_data)
615 GPtrArray *processes=user_data;
617 /* Ignore processes that have already exited (ie they are signalled) */
618 if(_wapi_handle_issignalled (handle)==FALSE) {
619 g_ptr_array_add (processes, handle);
622 /* Return false to keep searching */
626 gboolean EnumProcesses (guint32 *pids, guint32 len, guint32 *needed)
628 GPtrArray *processes=g_ptr_array_new ();
631 mono_once (&process_current_once, process_set_current);
633 _wapi_search_handle (WAPI_HANDLE_PROCESS, process_enum, processes,
636 fit=len/sizeof(guint32);
637 for(i=0; i<fit && i<processes->len; i++) {
638 struct _WapiHandle_process *process_handle;
641 ok=_wapi_lookup_handle (g_ptr_array_index (processes, i),
643 (gpointer *)&process_handle, NULL);
645 g_warning (G_GNUC_PRETTY_FUNCTION ": error looking up process handle %p", g_ptr_array_index (processes, i));
646 g_ptr_array_free (processes, FALSE);
650 pids[i]=process_handle->id;
653 g_ptr_array_free (processes, FALSE);
655 *needed=i*sizeof(guint32);
660 static gboolean process_open_compare (gpointer handle, gpointer user_data)
662 struct _WapiHandle_process *process_handle;
666 ok=_wapi_lookup_handle (handle, WAPI_HANDLE_PROCESS,
667 (gpointer *)&process_handle, NULL);
669 g_warning (G_GNUC_PRETTY_FUNCTION
670 ": error looking up process handle %p", handle);
674 pid=GPOINTER_TO_UINT (user_data);
676 /* It's possible to have more than one process handle with the
677 * same pid, but only the one running process can be
680 if(process_handle->id==pid &&
681 _wapi_handle_issignalled (handle)==FALSE) {
688 gpointer OpenProcess (guint32 access G_GNUC_UNUSED, gboolean inherit G_GNUC_UNUSED, guint32 pid)
690 /* Find the process handle that corresponds to pid */
693 mono_once (&process_current_once, process_set_current);
695 handle=_wapi_search_handle (WAPI_HANDLE_PROCESS, process_open_compare,
696 GUINT_TO_POINTER (pid), NULL, NULL);
699 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find pid %d", pid);
702 /* Set an error code */
707 _wapi_handle_ref (handle);
712 gboolean GetExitCodeProcess (gpointer process, guint32 *code)
714 struct _WapiHandle_process *process_handle;
717 mono_once (&process_current_once, process_set_current);
723 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
724 (gpointer *)&process_handle, NULL);
727 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
734 /* A process handle is only signalled if the process has exited */
735 if(_wapi_handle_issignalled (process)==TRUE) {
736 *code=process_handle->exitstatus;
744 gboolean GetProcessTimes (gpointer process, WapiFileTime *create_time,
745 WapiFileTime *exit_time, WapiFileTime *kernel_time,
746 WapiFileTime *user_time)
748 struct _WapiHandle_process *process_handle;
751 mono_once (&process_current_once, process_set_current);
753 if(create_time==NULL || exit_time==NULL || kernel_time==NULL ||
755 /* Not sure if w32 allows NULLs here or not */
759 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
760 (gpointer *)&process_handle, NULL);
763 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
770 *create_time=process_handle->create_time;
772 /* A process handle is only signalled if the process has
773 * exited. Otherwise exit_time isn't set
775 if(_wapi_handle_issignalled (process)==TRUE) {
776 *exit_time=process_handle->exit_time;
782 gboolean EnumProcessModules (gpointer process, gpointer *modules,
783 guint32 size, guint32 *needed)
785 /* Store modules in an array of pointers (main module as
786 * modules[0]), using the load address for each module as a
787 * token. (Use 'NULL' as an alternative for the main module
788 * so that the simple implementation can just return one item
789 * for now.) Get the info from /proc/<pid>/maps on linux,
790 * other systems will have to implement /dev/kmem reading or
791 * whatever other horrid technique is needed.
793 if(size<sizeof(gpointer)) {
799 *needed=sizeof(gpointer);
802 *needed=sizeof(gpointer);
808 guint32 GetModuleBaseName (gpointer process, gpointer module,
809 gunichar2 *basename, guint32 size)
811 struct _WapiHandle_process *process_handle;
814 mono_once (&process_current_once, process_set_current);
817 g_message (G_GNUC_PRETTY_FUNCTION
818 ": Getting module base name, process handle %p module %p",
822 if(basename==NULL || size==0) {
826 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
827 (gpointer *)&process_handle, NULL);
830 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
838 /* Shorthand for the main module, which has the
839 * process name recorded in the handle data
843 guchar *procname_utf8;
847 g_message (G_GNUC_PRETTY_FUNCTION
848 ": Returning main module name");
851 pid=process_handle->id;
852 procname_utf8=_wapi_handle_scratch_lookup (process_handle->proc_name);
855 g_message (G_GNUC_PRETTY_FUNCTION ": Process name is [%s]",
859 procname=g_utf8_to_utf16 (procname_utf8, -1, NULL, &len, NULL);
862 g_free (procname_utf8);
866 /* Add the terminator, and convert chars to bytes */
871 g_message (G_GNUC_PRETTY_FUNCTION ": Size %d smaller than needed (%ld); truncating", size, bytes);
874 memcpy (basename, procname, size);
877 g_message (G_GNUC_PRETTY_FUNCTION
878 ": Size %d larger than needed (%ld)",
882 memcpy (basename, procname, bytes);
885 g_free (procname_utf8);
890 /* Look up the address in /proc/<pid>/maps */
896 gboolean GetProcessWorkingSetSize (gpointer process, size_t *min, size_t *max)
898 struct _WapiHandle_process *process_handle;
901 mono_once (&process_current_once, process_set_current);
903 if(min==NULL || max==NULL) {
904 /* Not sure if w32 allows NULLs here or not */
908 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
909 (gpointer *)&process_handle, NULL);
912 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
919 *min=process_handle->min_working_set;
920 *max=process_handle->max_working_set;
925 gboolean SetProcessWorkingSetSize (gpointer process, size_t min, size_t max)
927 struct _WapiHandle_process *process_handle;
930 mono_once (&process_current_once, process_set_current);
932 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
933 (gpointer *)&process_handle, NULL);
936 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
943 process_handle->min_working_set=min;
944 process_handle->max_working_set=max;
951 TerminateProcess (gpointer process, gint32 exitCode)
953 struct _WapiHandle_process *process_handle;
958 ok = _wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
959 (gpointer *) &process_handle, NULL);
963 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
966 SetLastError (ERROR_INVALID_HANDLE);
970 signo = (exitCode == -1) ? SIGKILL : SIGTERM;
971 return _wapi_handle_process_kill (process_handle->id, signo, &err);