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>
24 #include <mono/io-layer/wapi.h>
25 #include <mono/io-layer/wapi-private.h>
26 #include <mono/io-layer/handles-private.h>
27 #include <mono/io-layer/misc-private.h>
28 #include <mono/io-layer/mono-mutex.h>
29 #include <mono/io-layer/process-private.h>
30 #include <mono/io-layer/threads.h>
31 #include <mono/utils/strenc.h>
33 /* The process' environment strings */
34 extern char **environ;
38 static void process_close_shared (gpointer handle);
40 struct _WapiHandleOps _wapi_process_ops = {
41 process_close_shared, /* close_shared */
42 NULL, /* close_private */
48 static mono_once_t process_current_once=MONO_ONCE_INIT;
49 static gpointer current_process=NULL;
51 static mono_once_t process_ops_once=MONO_ONCE_INIT;
53 static void process_ops_init (void)
55 _wapi_handle_register_capabilities (WAPI_HANDLE_PROCESS,
56 WAPI_HANDLE_CAP_WAIT);
59 static void process_close_shared (gpointer handle G_GNUC_UNUSED)
61 struct _WapiHandle_process *process_handle;
64 ok=_wapi_lookup_handle (handle, WAPI_HANDLE_PROCESS,
65 (gpointer *)&process_handle, NULL);
67 g_warning (G_GNUC_PRETTY_FUNCTION
68 ": error looking up process handle %p", handle);
73 g_message (G_GNUC_PRETTY_FUNCTION
74 ": closing process handle %p with id %d", handle,
78 if(process_handle->proc_name!=0) {
79 _wapi_handle_scratch_delete (process_handle->proc_name);
80 process_handle->proc_name=0;
84 gboolean CreateProcess (const gunichar2 *appname, gunichar2 *cmdline,
85 WapiSecurityAttributes *process_attrs G_GNUC_UNUSED,
86 WapiSecurityAttributes *thread_attrs G_GNUC_UNUSED,
87 gboolean inherit_handles, guint32 create_flags,
88 gpointer new_environ, const gunichar2 *cwd,
89 WapiStartupInfo *startup,
90 WapiProcessInformation *process_info)
92 gchar *cmd=NULL, *prog = NULL, *full_prog = NULL, *args=NULL, *args_after_prog=NULL, *dir=NULL;
93 guint32 env=0, stored_dir=0, stored_prog=0, i;
95 gpointer stdin_handle, stdout_handle, stderr_handle;
97 gpointer process_handle, thread_handle;
99 mono_once (&process_ops_once, process_ops_init);
101 /* appname and cmdline specify the executable and its args:
103 * If appname is not NULL, it is the name of the executable.
104 * Otherwise the executable is the first token in cmdline.
106 * Executable searching:
108 * If appname is not NULL, it can specify the full path and
109 * file name, or else a partial name and the current directory
110 * will be used. There is no additional searching.
112 * If appname is NULL, the first whitespace-delimited token in
113 * cmdline is used. If the name does not contain a full
114 * directory path, the search sequence is:
116 * 1) The directory containing the current process
117 * 2) The current working directory
118 * 3) The windows system directory (Ignored)
119 * 4) The windows directory (Ignored)
122 * Just to make things more interesting, tokens can contain
123 * white space if they are surrounded by quotation marks. I'm
124 * beginning to understand just why windows apps are generally
125 * so crap, with an API like this :-(
128 cmd=mono_unicode_to_external (appname);
131 g_message (G_GNUC_PRETTY_FUNCTION
132 ": unicode conversion returned NULL");
135 SetLastError(ERROR_PATH_NOT_FOUND);
139 /* Turn all the slashes round the right way */
140 for(i=0; i<strlen (cmd); i++) {
148 args=mono_unicode_to_external (cmdline);
151 g_message (G_GNUC_PRETTY_FUNCTION
152 ": unicode conversion returned NULL");
155 SetLastError(ERROR_PATH_NOT_FOUND);
161 dir=mono_unicode_to_external (cwd);
164 g_message (G_GNUC_PRETTY_FUNCTION
165 ": unicode conversion returned NULL");
168 SetLastError(ERROR_PATH_NOT_FOUND);
172 /* Turn all the slashes round the right way */
173 for(i=0; i<strlen (dir); i++) {
179 dir=g_get_current_dir ();
181 stored_dir=_wapi_handle_scratch_store (dir, strlen (dir));
184 /* new_environ is a block of NULL-terminated strings, which
185 * is itself NULL-terminated. Of course, passing an array of
186 * string pointers would have made things too easy :-(
188 * If new_environ is not NULL it specifies the entire set of
189 * environment variables in the new process. Otherwise the
190 * new process inherits the same environment.
192 if(new_environ!=NULL) {
195 gunichar2 *new_environp;
197 /* Count the number of strings */
198 for(new_environp=(gunichar2 *)new_environ; *new_environp;
201 while(*new_environp) {
205 strings=g_new0 (gchar *, count);
207 /* Copy each environ string into 'strings' turning it
208 * into utf8 (or the requested encoding) at the same
212 for(new_environp=(gunichar2 *)new_environ; *new_environp;
214 strings[count]=mono_unicode_to_external (new_environp);
216 while(*new_environp) {
221 env=_wapi_handle_scratch_store_string_array (strings);
223 g_strfreev (strings);
225 /* Use the existing environment */
226 env=_wapi_handle_scratch_store_string_array (environ);
229 /* We can't put off locating the executable any longer :-( */
231 if(g_ascii_isalpha (cmd[0]) && (cmd[1]==':')) {
232 /* Strip off the drive letter. I can't
233 * believe that CP/M holdover is still
236 memmove (cmd, cmd+2, strlen (cmd)-2);
237 cmd[strlen (cmd)-2]='\0';
241 /* Assume full path given */
244 /* Executable existing ? */
245 if(access (prog, X_OK)!=0) {
247 g_message (G_GNUC_PRETTY_FUNCTION ": Couldn't find executable %s", prog);
250 SetLastError (ERROR_FILE_NOT_FOUND);
254 /* Search for file named by cmd in the current
257 char *curdir=g_get_current_dir ();
259 prog=g_strdup_printf ("%s/%s", curdir, cmd);
263 args_after_prog=args;
267 /* Dig out the first token from args, taking quotation
271 /* First, strip off all leading whitespace */
272 args=g_strchug (args);
274 /* args_after_prog points to the contents of args
275 * after token has been set (otherwise argv[0] is
278 args_after_prog=args;
280 /* Assume the opening quote will always be the first
284 for(i=1; args[i]!='\0' && args[i]!='\"'; i++);
285 if(g_ascii_isspace (args[i+1])) {
286 /* We found the first token */
287 token=g_strndup (args+1, i-1);
288 args_after_prog=args+i;
290 /* Quotation mark appeared in the
291 * middle of the token. Just give the
292 * whole first token, quotes and all,
299 /* No quote mark, or malformed */
300 for(i=0; args[i]!='\0'; i++) {
301 if(g_ascii_isspace (args[i])) {
302 token=g_strndup (args, i);
303 args_after_prog=args+i+1;
309 if(token==NULL && args[0]!='\0') {
310 /* Must be just one token in the string */
311 token=g_strdup (args);
312 args_after_prog=NULL;
318 g_message (G_GNUC_PRETTY_FUNCTION
319 ": Couldn't find what to exec");
322 SetLastError(ERROR_PATH_NOT_FOUND);
326 /* Turn all the slashes round the right way. Only for the prg. name */
327 for(i=0; i < strlen (token); i++) {
328 if (token[i]=='\\') {
333 if(g_ascii_isalpha (token[0]) && (token[1]==':')) {
334 /* Strip off the drive letter. I can't
335 * believe that CP/M holdover is still
338 memmove (token, token+2, strlen (token)-2);
339 token[strlen (token)-2]='\0';
343 /* Assume full path given */
344 prog=g_strdup (token);
346 /* Executable existing ? */
347 if(access (prog, X_OK)!=0) {
350 g_message (G_GNUC_PRETTY_FUNCTION ": Couldn't find executable %s", token);
353 SetLastError (ERROR_FILE_NOT_FOUND);
358 char *curdir=g_get_current_dir ();
360 /* FIXME: Need to record the directory
361 * containing the current process, and check
362 * that for the new executable as the first
366 prog=g_strdup_printf ("%s/%s", curdir, token);
369 /* I assume X_OK is the criterion to use,
372 if(access (prog, X_OK)!=0) {
374 prog=g_find_program_in_path (token);
377 g_message (G_GNUC_PRETTY_FUNCTION ": Couldn't find executable %s", token);
381 SetLastError (ERROR_FILE_NOT_FOUND);
391 g_message (G_GNUC_PRETTY_FUNCTION ": Exec prog [%s] args [%s]", prog,
395 if(args_after_prog!=NULL) {
396 full_prog=g_strconcat (prog, " ", args_after_prog, NULL);
398 full_prog=g_strdup (prog);
401 stored_prog=_wapi_handle_scratch_store (full_prog, strlen (full_prog));
403 if(startup!=NULL && startup->dwFlags & STARTF_USESTDHANDLES) {
404 stdin_handle=startup->hStdInput;
405 stdout_handle=startup->hStdOutput;
406 stderr_handle=startup->hStdError;
408 stdin_handle=GetStdHandle (STD_INPUT_HANDLE);
409 stdout_handle=GetStdHandle (STD_OUTPUT_HANDLE);
410 stderr_handle=GetStdHandle (STD_ERROR_HANDLE);
413 ret=_wapi_handle_process_fork (stored_prog, env, stored_dir,
414 inherit_handles, create_flags,
415 stdin_handle, stdout_handle,
416 stderr_handle, &process_handle,
417 &thread_handle, &pid, &tid);
419 if(ret==TRUE && process_info!=NULL) {
420 process_info->hProcess=process_handle;
421 process_info->hThread=thread_handle;
422 process_info->dwProcessId=pid;
423 process_info->dwThreadId=tid;
424 } else if(ret==FALSE) {
425 /* FIXME: work out a better error code
427 SetLastError(ERROR_PATH_NOT_FOUND);
434 if(full_prog!=NULL) {
438 _wapi_handle_scratch_delete (stored_prog);
447 _wapi_handle_scratch_delete (stored_dir);
450 _wapi_handle_scratch_delete_string_array (env);
456 static void process_set_name (struct _WapiHandle_process *process_handle)
458 gchar *progname, *utf8_progname, *slash;
460 progname=g_get_prgname ();
461 utf8_progname=mono_utf8_from_external (progname);
464 g_message (G_GNUC_PRETTY_FUNCTION ": using [%s] as prog name",
468 if(utf8_progname!=NULL) {
469 slash=strrchr (utf8_progname, '/');
471 process_handle->proc_name=_wapi_handle_scratch_store (slash+1, strlen (slash+1));
473 process_handle->proc_name=_wapi_handle_scratch_store (utf8_progname, strlen (utf8_progname));
476 g_free (utf8_progname);
480 static void process_set_current (void)
482 struct _WapiHandle_process *process_handle;
487 handle_env=getenv ("_WAPI_PROCESS_HANDLE");
488 if(handle_env==NULL) {
490 g_message (G_GNUC_PRETTY_FUNCTION
491 ": Need to create my own process handle");
494 current_process=_wapi_handle_new (WAPI_HANDLE_PROCESS);
495 if(current_process==_WAPI_HANDLE_INVALID) {
496 g_warning (G_GNUC_PRETTY_FUNCTION
497 ": error creating process handle");
501 ok=_wapi_lookup_handle (current_process, WAPI_HANDLE_PROCESS,
502 (gpointer *)&process_handle, NULL);
504 g_warning (G_GNUC_PRETTY_FUNCTION
505 ": error looking up process handle %p",
510 process_handle->id=pid;
512 /* These seem to be the defaults on w2k */
513 process_handle->min_working_set=204800;
514 process_handle->max_working_set=1413120;
516 process_set_name (process_handle);
518 /* Make sure the new handle has a reference so it wont go away
519 * until this process exits
521 _wapi_handle_ref (current_process);
525 current_process=GUINT_TO_POINTER (atoi (handle_env));
528 g_message (G_GNUC_PRETTY_FUNCTION
529 ": Found my process handle: %p", current_process);
532 ok=_wapi_lookup_handle (current_process, WAPI_HANDLE_PROCESS,
533 (gpointer *)&process_handle, NULL);
535 g_warning (G_GNUC_PRETTY_FUNCTION
536 ": error looking up process handle %p",
541 procname=_wapi_handle_scratch_lookup (process_handle->proc_name);
543 if(!strcmp (procname, "mono")) {
544 /* Set a better process name */
546 g_message (G_GNUC_PRETTY_FUNCTION ": Setting better process name");
549 _wapi_handle_scratch_delete (process_handle->proc_name);
550 process_set_name (process_handle);
553 g_message (G_GNUC_PRETTY_FUNCTION
554 ": Leaving process name: %s",
564 /* Returns a pseudo handle that doesn't need to be closed afterwards */
565 gpointer GetCurrentProcess (void)
567 mono_once (&process_current_once, process_set_current);
569 return((gpointer)-1);
572 guint32 GetCurrentProcessId (void)
574 struct _WapiHandle_process *current_process_handle;
577 mono_once (&process_current_once, process_set_current);
579 ok=_wapi_lookup_handle (current_process, WAPI_HANDLE_PROCESS,
580 (gpointer *)¤t_process_handle, NULL);
582 g_warning (G_GNUC_PRETTY_FUNCTION
583 ": error looking up current process handle %p",
585 /* No failure return is defined. PID 0 is invalid.
586 * This should only be reached when something else has
587 * gone badly wrong anyway.
592 return(current_process_handle->id);
595 static gboolean process_enum (gpointer handle, gpointer user_data)
597 GPtrArray *processes=user_data;
599 /* Ignore processes that have already exited (ie they are signalled) */
600 if(_wapi_handle_issignalled (handle)==FALSE) {
601 g_ptr_array_add (processes, handle);
604 /* Return false to keep searching */
608 gboolean EnumProcesses (guint32 *pids, guint32 len, guint32 *needed)
610 GPtrArray *processes=g_ptr_array_new ();
613 mono_once (&process_current_once, process_set_current);
615 _wapi_search_handle (WAPI_HANDLE_PROCESS, process_enum, processes,
618 fit=len/sizeof(guint32);
619 for(i=0; i<fit && i<processes->len; i++) {
620 struct _WapiHandle_process *process_handle;
623 ok=_wapi_lookup_handle (g_ptr_array_index (processes, i),
625 (gpointer *)&process_handle, NULL);
627 g_warning (G_GNUC_PRETTY_FUNCTION ": error looking up process handle %p", g_ptr_array_index (processes, i));
628 g_ptr_array_free (processes, FALSE);
632 pids[i]=process_handle->id;
635 g_ptr_array_free (processes, FALSE);
637 *needed=i*sizeof(guint32);
642 static gboolean process_open_compare (gpointer handle, gpointer user_data)
644 struct _WapiHandle_process *process_handle;
648 ok=_wapi_lookup_handle (handle, WAPI_HANDLE_PROCESS,
649 (gpointer *)&process_handle, NULL);
651 g_warning (G_GNUC_PRETTY_FUNCTION
652 ": error looking up process handle %p", handle);
656 pid=GPOINTER_TO_UINT (user_data);
658 /* It's possible to have more than one process handle with the
659 * same pid, but only the one running process can be
662 if(process_handle->id==pid &&
663 _wapi_handle_issignalled (handle)==FALSE) {
670 gpointer OpenProcess (guint32 access G_GNUC_UNUSED, gboolean inherit G_GNUC_UNUSED, guint32 pid)
672 /* Find the process handle that corresponds to pid */
675 mono_once (&process_current_once, process_set_current);
677 handle=_wapi_search_handle (WAPI_HANDLE_PROCESS, process_open_compare,
678 GUINT_TO_POINTER (pid), NULL, NULL);
681 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find pid %d", pid);
684 /* Set an error code */
689 _wapi_handle_ref (handle);
694 gboolean GetExitCodeProcess (gpointer process, guint32 *code)
696 struct _WapiHandle_process *process_handle;
699 mono_once (&process_current_once, process_set_current);
705 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
706 (gpointer *)&process_handle, NULL);
709 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
716 /* A process handle is only signalled if the process has exited */
717 if(_wapi_handle_issignalled (process)==TRUE) {
718 *code=process_handle->exitstatus;
726 gboolean GetProcessTimes (gpointer process, WapiFileTime *create_time,
727 WapiFileTime *exit_time, WapiFileTime *kernel_time,
728 WapiFileTime *user_time)
730 struct _WapiHandle_process *process_handle;
733 mono_once (&process_current_once, process_set_current);
735 if(create_time==NULL || exit_time==NULL || kernel_time==NULL ||
737 /* Not sure if w32 allows NULLs here or not */
741 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
742 (gpointer *)&process_handle, NULL);
745 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
752 *create_time=process_handle->create_time;
754 /* A process handle is only signalled if the process has
755 * exited. Otherwise exit_time isn't set
757 if(_wapi_handle_issignalled (process)==TRUE) {
758 *exit_time=process_handle->exit_time;
764 gboolean EnumProcessModules (gpointer process, gpointer *modules,
765 guint32 size, guint32 *needed)
767 /* Store modules in an array of pointers (main module as
768 * modules[0]), using the load address for each module as a
769 * token. (Use 'NULL' as an alternative for the main module
770 * so that the simple implementation can just return one item
771 * for now.) Get the info from /proc/<pid>/maps on linux,
772 * other systems will have to implement /dev/kmem reading or
773 * whatever other horrid technique is needed.
775 if(size<sizeof(gpointer)) {
781 *needed=sizeof(gpointer);
784 *needed=sizeof(gpointer);
790 guint32 GetModuleBaseName (gpointer process, gpointer module,
791 gunichar2 *basename, guint32 size)
793 struct _WapiHandle_process *process_handle;
796 mono_once (&process_current_once, process_set_current);
799 g_message (G_GNUC_PRETTY_FUNCTION
800 ": Getting module base name, process handle %p module %p",
804 if(basename==NULL || size==0) {
808 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
809 (gpointer *)&process_handle, NULL);
812 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
820 /* Shorthand for the main module, which has the
821 * process name recorded in the handle data
825 guchar *procname_utf8;
829 g_message (G_GNUC_PRETTY_FUNCTION
830 ": Returning main module name");
833 pid=process_handle->id;
834 procname_utf8=_wapi_handle_scratch_lookup (process_handle->proc_name);
837 g_message (G_GNUC_PRETTY_FUNCTION ": Process name is [%s]",
841 procname=g_utf8_to_utf16 (procname_utf8, -1, NULL, &len, NULL);
844 g_free (procname_utf8);
848 /* Add the terminator, and convert chars to bytes */
853 g_message (G_GNUC_PRETTY_FUNCTION ": Size %d smaller than needed (%ld); truncating", size, bytes);
856 memcpy (basename, procname, size);
859 g_message (G_GNUC_PRETTY_FUNCTION
860 ": Size %d larger than needed (%ld)",
864 memcpy (basename, procname, bytes);
867 g_free (procname_utf8);
872 /* Look up the address in /proc/<pid>/maps */
878 gboolean GetProcessWorkingSetSize (gpointer process, size_t *min, size_t *max)
880 struct _WapiHandle_process *process_handle;
883 mono_once (&process_current_once, process_set_current);
885 if(min==NULL || max==NULL) {
886 /* Not sure if w32 allows NULLs here or not */
890 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
891 (gpointer *)&process_handle, NULL);
894 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
901 *min=process_handle->min_working_set;
902 *max=process_handle->max_working_set;
907 gboolean SetProcessWorkingSetSize (gpointer process, size_t min, size_t max)
909 struct _WapiHandle_process *process_handle;
912 mono_once (&process_current_once, process_set_current);
914 ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
915 (gpointer *)&process_handle, NULL);
918 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
925 process_handle->min_working_set=min;
926 process_handle->max_working_set=max;