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 :-( */
234 if(g_ascii_isalpha (cmd
[0]) && (cmd
[1]==':')) {
235 /* Strip off the drive letter. I can't
236 * believe that CP/M holdover is still
239 g_memmove (cmd
, cmd
+2, strlen (cmd
)-2);
240 cmd
[strlen (cmd
)-2]='\0';
243 unquoted
= g_shell_unquote (cmd
, NULL
);
244 if(unquoted
[0]=='/') {
245 /* Assume full path given */
246 prog
=g_strdup (unquoted
);
248 /* Executable existing ? */
249 if(access (prog
, X_OK
)!=0) {
251 g_message (G_GNUC_PRETTY_FUNCTION
": Couldn't find executable %s", prog
);
255 SetLastError (ERROR_FILE_NOT_FOUND
);
259 /* Search for file named by cmd in the current
262 char *curdir
=g_get_current_dir ();
264 prog
=g_strdup_printf ("%s/%s", curdir
, unquoted
);
269 args_after_prog
=args
;
274 /* Dig out the first token from args, taking quotation
278 /* First, strip off all leading whitespace */
279 args
=g_strchug (args
);
281 /* args_after_prog points to the contents of args
282 * after token has been set (otherwise argv[0] is
285 args_after_prog
=args
;
287 /* Assume the opening quote will always be the first
290 if(args
[0]=='\"' || args
[0] == '\'') {
292 for(i
=1; args
[i
]!='\0' && args
[i
]!=quote
; i
++);
293 if(g_ascii_isspace (args
[i
+1])) {
294 /* We found the first token */
295 token
=g_strndup (args
+1, i
-1);
296 args_after_prog
=args
+i
;
298 /* Quotation mark appeared in the
299 * middle of the token. Just give the
300 * whole first token, quotes and all,
307 /* No quote mark, or malformed */
308 for(i
=0; args
[i
]!='\0'; i
++) {
309 if(g_ascii_isspace (args
[i
])) {
310 token
=g_strndup (args
, i
);
311 args_after_prog
=args
+i
+1;
317 if(token
==NULL
&& args
[0]!='\0') {
318 /* Must be just one token in the string */
319 token
=g_strdup (args
);
320 args_after_prog
=NULL
;
326 g_message (G_GNUC_PRETTY_FUNCTION
327 ": Couldn't find what to exec");
330 SetLastError(ERROR_PATH_NOT_FOUND
);
334 /* Turn all the slashes round the right way. Only for the prg. name */
335 for(i
=0; i
< strlen (token
); i
++) {
336 if (token
[i
]=='\\') {
341 if(g_ascii_isalpha (token
[0]) && (token
[1]==':')) {
342 /* Strip off the drive letter. I can't
343 * believe that CP/M holdover is still
346 g_memmove (token
, token
+2, strlen (token
)-2);
347 token
[strlen (token
)-2]='\0';
351 /* Assume full path given */
352 prog
=g_strdup (token
);
354 /* Executable existing ? */
355 if(access (prog
, X_OK
)!=0) {
358 g_message (G_GNUC_PRETTY_FUNCTION
": Couldn't find executable %s", token
);
361 SetLastError (ERROR_FILE_NOT_FOUND
);
366 char *curdir
=g_get_current_dir ();
368 /* FIXME: Need to record the directory
369 * containing the current process, and check
370 * that for the new executable as the first
374 prog
=g_strdup_printf ("%s/%s", curdir
, token
);
377 /* I assume X_OK is the criterion to use,
380 if(access (prog
, X_OK
)!=0) {
382 prog
=g_find_program_in_path (token
);
385 g_message (G_GNUC_PRETTY_FUNCTION
": Couldn't find executable %s", token
);
389 SetLastError (ERROR_FILE_NOT_FOUND
);
399 g_message (G_GNUC_PRETTY_FUNCTION
": Exec prog [%s] args [%s]", prog
,
403 if(args_after_prog
!=NULL
&& *args_after_prog
) {
406 qprog
= g_shell_quote (prog
);
407 full_prog
=g_strconcat (qprog
, " ", args_after_prog
, NULL
);
410 full_prog
=g_shell_quote (prog
);
413 stored_prog
=_wapi_handle_scratch_store (full_prog
, strlen (full_prog
));
415 if(startup
!=NULL
&& startup
->dwFlags
& STARTF_USESTDHANDLES
) {
416 stdin_handle
=startup
->hStdInput
;
417 stdout_handle
=startup
->hStdOutput
;
418 stderr_handle
=startup
->hStdError
;
420 stdin_handle
=GetStdHandle (STD_INPUT_HANDLE
);
421 stdout_handle
=GetStdHandle (STD_OUTPUT_HANDLE
);
422 stderr_handle
=GetStdHandle (STD_ERROR_HANDLE
);
425 ret
=_wapi_handle_process_fork (stored_prog
, env
, stored_dir
,
426 inherit_handles
, create_flags
,
427 stdin_handle
, stdout_handle
,
428 stderr_handle
, &process_handle
,
429 &thread_handle
, &pid
, &tid
);
431 if(ret
==TRUE
&& process_info
!=NULL
) {
432 process_info
->hProcess
=process_handle
;
433 process_info
->hThread
=thread_handle
;
434 process_info
->dwProcessId
=pid
;
435 process_info
->dwThreadId
=tid
;
436 /* Wait for possible execve failure */
437 if (WaitForSingleObjectEx (process_handle
, 500, FALSE
) != WAIT_TIMEOUT
) {
438 _wapi_lookup_handle (GUINT_TO_POINTER (process_handle
),
440 (gpointer
*) &process_handle_data
,
443 if (process_handle_data
&& process_handle_data
->exec_errno
!= 0) {
445 SetLastError (ERROR_PATH_NOT_FOUND
);
448 } else if (ret
==FALSE
) {
449 /* FIXME: work out a better error code
451 SetLastError (ERROR_PATH_NOT_FOUND
);
458 if(full_prog
!=NULL
) {
462 _wapi_handle_scratch_delete (stored_prog
);
471 _wapi_handle_scratch_delete (stored_dir
);
474 _wapi_handle_scratch_delete_string_array (env
);
480 static void process_set_name (struct _WapiHandle_process
*process_handle
)
482 gchar
*progname
, *utf8_progname
, *slash
;
484 progname
=g_get_prgname ();
485 utf8_progname
=mono_utf8_from_external (progname
);
488 g_message (G_GNUC_PRETTY_FUNCTION
": using [%s] as prog name",
492 if(utf8_progname
!=NULL
) {
493 slash
=strrchr (utf8_progname
, '/');
495 process_handle
->proc_name
=_wapi_handle_scratch_store (slash
+1, strlen (slash
+1));
497 process_handle
->proc_name
=_wapi_handle_scratch_store (utf8_progname
, strlen (utf8_progname
));
500 g_free (utf8_progname
);
504 extern void _wapi_time_t_to_filetime (time_t timeval
, WapiFileTime
*filetime
);
506 static void process_set_current (void)
508 struct _WapiHandle_process
*process_handle
;
513 handle_env
=getenv ("_WAPI_PROCESS_HANDLE");
514 if(handle_env
==NULL
) {
516 g_message (G_GNUC_PRETTY_FUNCTION
517 ": Need to create my own process handle");
520 current_process
=_wapi_handle_new (WAPI_HANDLE_PROCESS
);
521 if(current_process
==_WAPI_HANDLE_INVALID
) {
522 g_warning (G_GNUC_PRETTY_FUNCTION
523 ": error creating process handle");
527 ok
=_wapi_lookup_handle (current_process
, WAPI_HANDLE_PROCESS
,
528 (gpointer
*)&process_handle
, NULL
);
530 g_warning (G_GNUC_PRETTY_FUNCTION
531 ": error looking up process handle %p",
536 process_handle
->id
=pid
;
538 /* These seem to be the defaults on w2k */
539 process_handle
->min_working_set
=204800;
540 process_handle
->max_working_set
=1413120;
542 _wapi_time_t_to_filetime (time (NULL
), &process_handle
->create_time
);
544 process_set_name (process_handle
);
546 /* Make sure the new handle has a reference so it wont go away
547 * until this process exits
549 _wapi_handle_ref (current_process
);
553 current_process
=GUINT_TO_POINTER (atoi (handle_env
));
556 g_message (G_GNUC_PRETTY_FUNCTION
557 ": Found my process handle: %p", current_process
);
560 ok
=_wapi_lookup_handle (current_process
, WAPI_HANDLE_PROCESS
,
561 (gpointer
*)&process_handle
, NULL
);
563 g_warning (G_GNUC_PRETTY_FUNCTION
564 ": error looking up process handle %p",
569 procname
=_wapi_handle_scratch_lookup (process_handle
->proc_name
);
571 if(!strcmp (procname
, "mono")) {
572 /* Set a better process name */
574 g_message (G_GNUC_PRETTY_FUNCTION
": Setting better process name");
577 _wapi_handle_scratch_delete (process_handle
->proc_name
);
578 process_set_name (process_handle
);
581 g_message (G_GNUC_PRETTY_FUNCTION
582 ": Leaving process name: %s",
592 /* Returns a pseudo handle that doesn't need to be closed afterwards */
593 gpointer
GetCurrentProcess (void)
595 mono_once (&process_current_once
, process_set_current
);
597 return((gpointer
)-1);
600 guint32
GetCurrentProcessId (void)
602 struct _WapiHandle_process
*current_process_handle
;
605 mono_once (&process_current_once
, process_set_current
);
607 ok
=_wapi_lookup_handle (current_process
, WAPI_HANDLE_PROCESS
,
608 (gpointer
*)¤t_process_handle
, NULL
);
610 g_warning (G_GNUC_PRETTY_FUNCTION
611 ": error looking up current process handle %p",
613 /* No failure return is defined. PID 0 is invalid.
614 * This should only be reached when something else has
615 * gone badly wrong anyway.
620 return(current_process_handle
->id
);
623 static gboolean
process_enum (gpointer handle
, gpointer user_data
)
625 GPtrArray
*processes
=user_data
;
627 /* Ignore processes that have already exited (ie they are signalled) */
628 if(_wapi_handle_issignalled (handle
)==FALSE
) {
629 g_ptr_array_add (processes
, handle
);
632 /* Return false to keep searching */
636 gboolean
EnumProcesses (guint32
*pids
, guint32 len
, guint32
*needed
)
638 GPtrArray
*processes
=g_ptr_array_new ();
641 mono_once (&process_current_once
, process_set_current
);
643 _wapi_search_handle (WAPI_HANDLE_PROCESS
, process_enum
, processes
,
646 fit
=len
/sizeof(guint32
);
647 for(i
=0; i
<fit
&& i
<processes
->len
; i
++) {
648 struct _WapiHandle_process
*process_handle
;
651 ok
=_wapi_lookup_handle (g_ptr_array_index (processes
, i
),
653 (gpointer
*)&process_handle
, NULL
);
655 g_warning (G_GNUC_PRETTY_FUNCTION
": error looking up process handle %p", g_ptr_array_index (processes
, i
));
656 g_ptr_array_free (processes
, FALSE
);
660 pids
[i
]=process_handle
->id
;
663 g_ptr_array_free (processes
, FALSE
);
665 *needed
=i
*sizeof(guint32
);
670 static gboolean
process_open_compare (gpointer handle
, gpointer user_data
)
672 struct _WapiHandle_process
*process_handle
;
676 ok
=_wapi_lookup_handle (handle
, WAPI_HANDLE_PROCESS
,
677 (gpointer
*)&process_handle
, NULL
);
679 g_warning (G_GNUC_PRETTY_FUNCTION
680 ": error looking up process handle %p", handle
);
684 pid
=GPOINTER_TO_UINT (user_data
);
686 /* It's possible to have more than one process handle with the
687 * same pid, but only the one running process can be
690 if(process_handle
->id
==pid
&&
691 _wapi_handle_issignalled (handle
)==FALSE
) {
698 gpointer
OpenProcess (guint32 access G_GNUC_UNUSED
, gboolean inherit G_GNUC_UNUSED
, guint32 pid
)
700 /* Find the process handle that corresponds to pid */
703 mono_once (&process_current_once
, process_set_current
);
705 handle
=_wapi_search_handle (WAPI_HANDLE_PROCESS
, process_open_compare
,
706 GUINT_TO_POINTER (pid
), NULL
, NULL
);
709 g_message (G_GNUC_PRETTY_FUNCTION
": Can't find pid %d", pid
);
712 /* Set an error code */
717 _wapi_handle_ref (handle
);
722 gboolean
GetExitCodeProcess (gpointer process
, guint32
*code
)
724 struct _WapiHandle_process
*process_handle
;
727 mono_once (&process_current_once
, process_set_current
);
733 ok
=_wapi_lookup_handle (process
, WAPI_HANDLE_PROCESS
,
734 (gpointer
*)&process_handle
, NULL
);
737 g_message (G_GNUC_PRETTY_FUNCTION
": Can't find process %p",
744 /* A process handle is only signalled if the process has exited */
745 if(_wapi_handle_issignalled (process
)==TRUE
) {
746 *code
=process_handle
->exitstatus
;
754 gboolean
GetProcessTimes (gpointer process
, WapiFileTime
*create_time
,
755 WapiFileTime
*exit_time
, WapiFileTime
*kernel_time
,
756 WapiFileTime
*user_time
)
758 struct _WapiHandle_process
*process_handle
;
761 mono_once (&process_current_once
, process_set_current
);
763 if(create_time
==NULL
|| exit_time
==NULL
|| kernel_time
==NULL
||
765 /* Not sure if w32 allows NULLs here or not */
769 ok
=_wapi_lookup_handle (process
, WAPI_HANDLE_PROCESS
,
770 (gpointer
*)&process_handle
, NULL
);
773 g_message (G_GNUC_PRETTY_FUNCTION
": Can't find process %p",
780 *create_time
=process_handle
->create_time
;
782 /* A process handle is only signalled if the process has
783 * exited. Otherwise exit_time isn't set
785 if(_wapi_handle_issignalled (process
)==TRUE
) {
786 *exit_time
=process_handle
->exit_time
;
792 gboolean
EnumProcessModules (gpointer process
, gpointer
*modules
,
793 guint32 size
, guint32
*needed
)
795 /* Store modules in an array of pointers (main module as
796 * modules[0]), using the load address for each module as a
797 * token. (Use 'NULL' as an alternative for the main module
798 * so that the simple implementation can just return one item
799 * for now.) Get the info from /proc/<pid>/maps on linux,
800 * other systems will have to implement /dev/kmem reading or
801 * whatever other horrid technique is needed.
803 if(size
<sizeof(gpointer
)) {
809 *needed
=sizeof(gpointer
);
812 *needed
=sizeof(gpointer
);
818 guint32
GetModuleBaseName (gpointer process
, gpointer module
,
819 gunichar2
*basename
, guint32 size
)
821 struct _WapiHandle_process
*process_handle
;
824 mono_once (&process_current_once
, process_set_current
);
827 g_message (G_GNUC_PRETTY_FUNCTION
828 ": Getting module base name, process handle %p module %p",
832 if(basename
==NULL
|| size
==0) {
836 ok
=_wapi_lookup_handle (process
, WAPI_HANDLE_PROCESS
,
837 (gpointer
*)&process_handle
, NULL
);
840 g_message (G_GNUC_PRETTY_FUNCTION
": Can't find process %p",
848 /* Shorthand for the main module, which has the
849 * process name recorded in the handle data
853 guchar
*procname_utf8
;
857 g_message (G_GNUC_PRETTY_FUNCTION
858 ": Returning main module name");
861 pid
=process_handle
->id
;
862 procname_utf8
=_wapi_handle_scratch_lookup (process_handle
->proc_name
);
865 g_message (G_GNUC_PRETTY_FUNCTION
": Process name is [%s]",
869 procname
=g_utf8_to_utf16 (procname_utf8
, -1, NULL
, &len
, NULL
);
872 g_free (procname_utf8
);
876 /* Add the terminator, and convert chars to bytes */
881 g_message (G_GNUC_PRETTY_FUNCTION
": Size %d smaller than needed (%ld); truncating", size
, bytes
);
884 memcpy (basename
, procname
, size
);
887 g_message (G_GNUC_PRETTY_FUNCTION
888 ": Size %d larger than needed (%ld)",
892 memcpy (basename
, procname
, bytes
);
895 g_free (procname_utf8
);
900 /* Look up the address in /proc/<pid>/maps */
906 gboolean
GetProcessWorkingSetSize (gpointer process
, size_t *min
, size_t *max
)
908 struct _WapiHandle_process
*process_handle
;
911 mono_once (&process_current_once
, process_set_current
);
913 if(min
==NULL
|| max
==NULL
) {
914 /* Not sure if w32 allows NULLs here or not */
918 ok
=_wapi_lookup_handle (process
, WAPI_HANDLE_PROCESS
,
919 (gpointer
*)&process_handle
, NULL
);
922 g_message (G_GNUC_PRETTY_FUNCTION
": Can't find process %p",
929 *min
=process_handle
->min_working_set
;
930 *max
=process_handle
->max_working_set
;
935 gboolean
SetProcessWorkingSetSize (gpointer process
, size_t min
, size_t max
)
937 struct _WapiHandle_process
*process_handle
;
940 mono_once (&process_current_once
, process_set_current
);
942 ok
=_wapi_lookup_handle (process
, WAPI_HANDLE_PROCESS
,
943 (gpointer
*)&process_handle
, NULL
);
946 g_message (G_GNUC_PRETTY_FUNCTION
": Can't find process %p",
953 process_handle
->min_working_set
=min
;
954 process_handle
->max_working_set
=max
;
961 TerminateProcess (gpointer process
, gint32 exitCode
)
963 struct _WapiHandle_process
*process_handle
;
968 ok
= _wapi_lookup_handle (process
, WAPI_HANDLE_PROCESS
,
969 (gpointer
*) &process_handle
, NULL
);
973 g_message (G_GNUC_PRETTY_FUNCTION
": Can't find process %p",
976 SetLastError (ERROR_INVALID_HANDLE
);
980 signo
= (exitCode
== -1) ? SIGKILL
: SIGTERM
;
981 return _wapi_handle_process_kill (process_handle
->id
, signo
, &err
);