Fri Sep 13 15:41:40 CEST 2002 Paolo Molaro <lupus@ximian.com>
[mono.git] / mono / io-layer / processes.c
1 /*
2  * processes.c:  Process handles
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2002 Ximian, Inc.
8  */
9
10 #include <config.h>
11 #if HAVE_BOEHM_GC
12 #include <mono/os/gc_wrapper.h>
13 #include "mono/utils/mono-hash.h"
14 #endif
15 #include <glib.h>
16 #include <string.h>
17 #include <pthread.h>
18 #include <sched.h>
19 #include <sys/time.h>
20 #include <errno.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23
24 #include <mono/io-layer/wapi.h>
25 #include <mono/io-layer/unicode.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
33 #undef DEBUG
34
35 static void process_close_shared (gpointer handle);
36
37 struct _WapiHandleOps _wapi_process_ops = {
38         process_close_shared,           /* close_shared */
39         NULL,                           /* close_private */
40         NULL,                           /* signal */
41         NULL,                           /* own */
42         NULL,                           /* is_owned */
43 };
44
45 static mono_once_t process_current_once=MONO_ONCE_INIT;
46 static gpointer current_process=NULL;
47
48 static mono_once_t process_ops_once=MONO_ONCE_INIT;
49
50 static void process_ops_init (void)
51 {
52         _wapi_handle_register_capabilities (WAPI_HANDLE_PROCESS,
53                                             WAPI_HANDLE_CAP_WAIT);
54 }
55
56 static void process_close_shared (gpointer handle G_GNUC_UNUSED)
57 {
58 #ifdef DEBUG
59         struct _WapiHandle_process *process_handle;
60         gboolean ok;
61
62         ok=_wapi_lookup_handle (handle, WAPI_HANDLE_PROCESS,
63                                 (gpointer *)&process_handle, NULL);
64         if(ok==FALSE) {
65                 g_warning (G_GNUC_PRETTY_FUNCTION
66                            ": error looking up process handle %p", handle);
67                 return;
68         }
69
70         g_message (G_GNUC_PRETTY_FUNCTION
71                    ": closing process handle %p with id %d", handle,
72                    process_handle->id);
73 #endif
74 }
75
76 gboolean CreateProcess (const gunichar2 *appname, gunichar2 *cmdline,
77                         WapiSecurityAttributes *process_attrs G_GNUC_UNUSED,
78                         WapiSecurityAttributes *thread_attrs G_GNUC_UNUSED,
79                         gboolean inherit_handles, guint32 create_flags,
80                         gpointer environ, const gunichar2 *cwd,
81                         WapiStartupInfo *startup,
82                         WapiProcessInformation *process_info)
83 {
84         gchar *cmd=NULL, *prog, *args=NULL, *dir=NULL;
85         gunichar2 *environp;
86         guint32 env=0, stored_dir=0, stored_prog=0, stored_args=0;
87         guint32 env_count=0, i;
88         gboolean ret=FALSE;
89         gpointer stdin_handle, stdout_handle, stderr_handle;
90         guint32 pid, tid;
91         gpointer process_handle, thread_handle;
92         
93         mono_once (&process_ops_once, process_ops_init);
94         
95         /* appname and cmdline specify the executable and its args:
96          *
97          * If appname is not NULL, it is the name of the executable.
98          * Otherwise the executable is the first token in cmdline.
99          *
100          * Executable searching:
101          *
102          * If appname is not NULL, it can specify the full path and
103          * file name, or else a partial name and the current directory
104          * will be used.  There is no additional searching.
105          *
106          * If appname is NULL, the first whitespace-delimited token in
107          * cmdline is used.  If the name does not contain a full
108          * directory path, the search sequence is:
109          *
110          * 1) The directory containing the current process
111          * 2) The current working directory
112          * 3) The windows system directory  (Ignored)
113          * 4) The windows directory (Ignored)
114          * 5) $PATH
115          *
116          * Just to make things more interesting, tokens can contain
117          * white space if they are surrounded by quotation marks.  I'm
118          * beginning to understand just why windows apps are generally
119          * so crap, with an API like this :-(
120          */
121         if(appname!=NULL) {
122                 cmd=_wapi_unicode_to_utf8 (appname);
123                 if(cmd==NULL) {
124 #ifdef DEBUG
125                         g_message (G_GNUC_PRETTY_FUNCTION
126                                    ": unicode conversion returned NULL");
127 #endif
128
129                         goto cleanup;
130                 }
131
132                 /* Turn all the slashes round the right way */
133                 for(i=0; i<strlen (cmd); i++) {
134                         if(cmd[i]=='\\') {
135                                 cmd[i]='/';
136                         }
137                 }
138         }
139         
140         if(cmdline!=NULL) {
141                 args=_wapi_unicode_to_utf8 (cmdline);
142                 if(args==NULL) {
143 #ifdef DEBUG
144                         g_message (G_GNUC_PRETTY_FUNCTION
145                                    ": unicode conversion returned NULL");
146 #endif
147
148                         goto cleanup;
149                 }
150
151                 /* Turn all the slashes round the right way */
152                 for(i=0; i<strlen (args); i++) {
153                         if(args[i]=='\\') {
154                                 args[i]='/';
155                         }
156                 }
157         }
158
159         if(cwd!=NULL) {
160                 dir=_wapi_unicode_to_utf8 (cwd);
161                 if(dir==NULL) {
162 #ifdef DEBUG
163                         g_message (G_GNUC_PRETTY_FUNCTION
164                                    ": unicode conversion returned NULL");
165 #endif
166
167                         goto cleanup;
168                 }
169
170                 /* Turn all the slashes round the right way */
171                 for(i=0; i<strlen (dir); i++) {
172                         if(dir[i]=='\\') {
173                                 dir[i]='/';
174                         }
175                 }
176                 stored_dir=_wapi_handle_scratch_store (dir, strlen (dir));
177         }
178         
179         /* environ is a block of NULL-terminated strings, which is
180          * itself NULL-terminated. Of course, passing an array of
181          * string pointers would have made things too easy :-(
182          */
183         /* Not sure whether I should turn the w32 env block into
184          * proper env vars, or just leave it to be read back by other
185          * w32 emulation functions.
186          */
187         if(environ!=NULL) {
188                 /* env_count counts bytes, not chars */
189                 for(environp=(gunichar2 *)environ; *environp;
190                     env_count+=2, environp++) {
191                         while(*environp) {
192                                 env_count+=2;
193                                 environp++;
194                         }
195                 }
196
197                 env=_wapi_handle_scratch_store (environ, env_count);
198         }
199
200         /* We can't put off locating the executable any longer :-( */
201         if(cmd!=NULL) {
202                 if(g_ascii_isalpha (cmd[0]) && (cmd[1]==':')) {
203                         /* Strip off the drive letter.  I can't
204                          * believe that CP/M holdover is still
205                          * visible...
206                          */
207                         memmove (cmd, cmd+2, strlen (cmd)-2);
208                         cmd[strlen (cmd)-2]='\0';
209                 }
210
211                 if(cmd[0]=='/') {
212                         /* Assume full path given */
213                         prog=g_strdup (cmd);
214                 } else {
215                         /* Search for file named by cmd in the current
216                          * directory
217                          */
218                         char *curdir=g_get_current_dir ();
219
220                         prog=g_strdup_printf ("%s/%s", curdir, cmd);
221                         g_free (curdir);
222                 }
223         } else {
224                 gchar *token=NULL;
225                 
226                 /* Dig out the first token from args, taking quotation
227                  * marks into account
228                  */
229
230                 /* FIXME: move the contents of args down when token
231                  * has been set (otherwise argv[0] is duplicated)
232                  */
233                 /* Assume the opening quote will always be the first
234                  * character
235                  */
236                 if(args[0]=='\"') {
237                         for(i=1; args[i]!='\0' && args[i]!='\"'; i++);
238                         if(g_ascii_isspace (args[i+1])) {
239                                 /* We found the first token */
240                                 token=g_strndup (args+1, i-1);
241                         } else {
242                                 /* Quotation mark appeared in the
243                                  * middle of the token.  Just give the
244                                  * whole first token, quotes and all,
245                                  * to exec.
246                                  */
247                         }
248                 }
249                 
250                 if(token==NULL) {
251                         /* No quote mark, or malformed */
252                         for(i=0; args[i]!='\0'; i++) {
253                                 if(g_ascii_isspace (args[i])) {
254                                         token=g_strndup (args, i);
255                                         break;
256                                 }
257                         }
258                 }
259
260                 if(token==NULL && args[0]!='\0') {
261                         /* Must be just one token in the string */
262                         token=g_strdup (args);
263                 }
264                 
265                 if(token==NULL) {
266                         /* Give up */
267 #ifdef DEBUG
268                         g_message (G_GNUC_PRETTY_FUNCTION
269                                    ": Couldn't find what to exec");
270 #endif
271
272                         goto cleanup;
273                 }
274                 
275                 if(g_ascii_isalpha (token[0]) && (token[1]==':')) {
276                         /* Strip off the drive letter.  I can't
277                          * believe that CP/M holdover is still
278                          * visible...
279                          */
280                         memmove (token, token+2, strlen (token)-2);
281                         token[strlen (token)-2]='\0';
282                 }
283
284                 if(token[0]=='/') {
285                         /* Assume full path given */
286                         prog=g_strdup (token);
287                 } else {
288                         char *curdir=g_get_current_dir ();
289
290                         /* FIXME: Need to record the directory
291                          * containing the current process, and check
292                          * that for the new executable as the first
293                          * place to look
294                          */
295
296                         prog=g_strdup_printf ("%s/%s", curdir, token);
297                         g_free (curdir);
298
299                         /* I assume X_OK is the criterion to use,
300                          * rather than F_OK
301                          */
302                         if(access (prog, X_OK)!=0) {
303                                 g_free (prog);
304                                 prog=g_find_program_in_path (token);
305                                 if(prog==NULL) {
306 #ifdef DEBUG
307                                         g_message (G_GNUC_PRETTY_FUNCTION ": Couldn't find executable %s", token);
308 #endif
309
310                                         g_free (token);
311                                         goto cleanup;
312                                 }
313                         }
314                 }
315
316                 g_free (token);
317         }
318
319 #ifdef DEBUG
320         g_message (G_GNUC_PRETTY_FUNCTION ": Exec prog [%s] args [%s]",
321                    prog, args);
322 #endif
323         
324         stored_prog=_wapi_handle_scratch_store (prog, strlen (prog));
325         stored_args=_wapi_handle_scratch_store (args, strlen (args));
326         
327         stdin_handle=GetStdHandle (STD_INPUT_HANDLE);
328         stdout_handle=GetStdHandle (STD_OUTPUT_HANDLE);
329         stderr_handle=GetStdHandle (STD_ERROR_HANDLE);
330         
331         if(startup!=NULL) {
332                 if(startup->dwFlags & STARTF_USESTDHANDLES) {
333                         stdin_handle=startup->hStdInput;
334                         stdout_handle=startup->hStdOutput;
335                         stderr_handle=startup->hStdError;
336                 }
337         }
338         
339         ret=_wapi_handle_process_fork (stored_prog, stored_args, env,
340                                        stored_dir, inherit_handles,
341                                        create_flags, stdin_handle,
342                                        stdout_handle, stderr_handle,
343                                        &process_handle, &thread_handle, &pid,
344                                        &tid);
345         
346         if(ret==TRUE && process_info!=NULL) {
347                 process_info->hProcess=process_handle;
348                 process_info->hThread=thread_handle;
349                 process_info->dwProcessId=pid;
350                 process_info->dwThreadId=tid;
351         }
352         
353 cleanup:
354         if(cmd!=NULL) {
355                 g_free (cmd);
356         }
357         if(prog!=NULL) {
358                 g_free (prog);
359         }
360         if(stored_prog!=0) {
361                 _wapi_handle_scratch_delete (stored_prog);
362         }
363         if(args!=NULL) {
364                 g_free (args);
365         }
366         if(stored_args!=0) {
367                 _wapi_handle_scratch_delete (stored_args);
368         }
369         if(dir!=NULL) {
370                 g_free (dir);
371         }
372         if(stored_dir!=0) {
373                 _wapi_handle_scratch_delete (stored_dir);
374         }
375         if(env!=0) {
376                 _wapi_handle_scratch_delete (env);
377         }
378         
379         return(ret);
380 }
381
382 static gboolean process_compare (gpointer handle, gpointer user_data)
383 {
384         struct _WapiHandle_process *process_handle;
385         gboolean ok;
386         pid_t pid;
387         
388         ok=_wapi_lookup_handle (handle, WAPI_HANDLE_PROCESS,
389                                 (gpointer *)&process_handle, NULL);
390         if(ok==FALSE) {
391                 g_warning (G_GNUC_PRETTY_FUNCTION
392                            ": error looking up process handle %p", handle);
393                 return(FALSE);
394         }
395
396         pid=GPOINTER_TO_UINT (user_data);
397         if(process_handle->id==pid) {
398                 return(TRUE);
399         } else {
400                 return(FALSE);
401         }
402 }
403         
404 static void process_set_current (void)
405 {
406         struct _WapiHandle_process *process_handle;
407         gboolean ok;
408         pid_t pid=getpid ();
409         
410         current_process=_wapi_search_handle (WAPI_HANDLE_PROCESS,
411                                              process_compare,
412                                              GUINT_TO_POINTER (pid),
413                                              (gpointer *)&process_handle,
414                                              NULL);
415         if(current_process==0) {
416 #ifdef DEBUG
417                 g_message (G_GNUC_PRETTY_FUNCTION
418                            ": Need to create my own process handle");
419 #endif
420
421                 current_process=_wapi_handle_new (WAPI_HANDLE_PROCESS);
422                 if(current_process==_WAPI_HANDLE_INVALID) {
423                         g_warning (G_GNUC_PRETTY_FUNCTION
424                                    ": error creating process handle");
425                         return;
426                 }
427
428                 ok=_wapi_lookup_handle (current_process, WAPI_HANDLE_PROCESS,
429                                         (gpointer *)&process_handle, NULL);
430                 if(ok==FALSE) {
431                         g_warning (G_GNUC_PRETTY_FUNCTION
432                                    ": error looking up process handle %p",
433                                    current_process);
434                         return;
435                 }
436
437                 process_handle->id=pid;
438         } else {
439 #ifdef DEBUG
440                 g_message (G_GNUC_PRETTY_FUNCTION ": Found my process handle");
441 #endif
442         }
443 }
444
445 /* Returns a pseudo handle that doesn't need to be closed afterwards */
446 gpointer GetCurrentProcess (void)
447 {
448         mono_once (&process_current_once, process_set_current);
449                 
450         return((gpointer)-1);
451 }
452
453 guint32 GetCurrentProcessId (void)
454 {
455         mono_once (&process_current_once, process_set_current);
456                 
457         return(getpid ());
458 }
459
460 static gboolean process_enum (gpointer handle, gpointer user_data)
461 {
462         GPtrArray *processes=user_data;
463         
464         g_ptr_array_add (processes, handle);
465         
466         /* Return false to keep searching */
467         return(FALSE);
468 }
469
470 gboolean EnumProcesses (guint32 *pids, guint32 len, guint32 *needed)
471 {
472         GPtrArray *processes=g_ptr_array_new ();
473         guint32 fit, i;
474         
475         _wapi_search_handle (WAPI_HANDLE_PROCESS, process_enum, processes,
476                              NULL, NULL);
477         
478         fit=len/sizeof(guint32);
479         for(i=0; i<fit && i<processes->len; i++) {
480                 struct _WapiHandle_process *process_handle;
481                 gboolean ok;
482
483                 ok=_wapi_lookup_handle (g_ptr_array_index (processes, i),
484                                         WAPI_HANDLE_PROCESS,
485                                         (gpointer *)&process_handle, NULL);
486                 if(ok==FALSE) {
487                         g_warning (G_GNUC_PRETTY_FUNCTION ": error looking up process handle %p", g_ptr_array_index (processes, i));
488                         g_ptr_array_free (processes, FALSE);
489                         return(FALSE);
490                 }
491
492                 pids[i]=process_handle->id;
493         }
494
495         g_ptr_array_free (processes, FALSE);
496         
497         *needed=i*sizeof(guint32);
498         
499         return(TRUE);
500 }
501
502 static gboolean process_open_compare (gpointer handle, gpointer user_data)
503 {
504         struct _WapiHandle_process *process_handle;
505         gboolean ok;
506         pid_t pid;
507         
508         ok=_wapi_lookup_handle (handle, WAPI_HANDLE_PROCESS,
509                                 (gpointer *)&process_handle, NULL);
510         if(ok==FALSE) {
511                 g_warning (G_GNUC_PRETTY_FUNCTION
512                            ": error looking up process handle %p", handle);
513                 return(FALSE);
514         }
515
516         pid=GPOINTER_TO_UINT (user_data);
517
518         /* It's possible to have more than one process handle with the
519          * same pid, but only the one running process can be
520          * unsignalled
521          */
522         if(process_handle->id==pid &&
523            _wapi_handle_issignalled (handle)==FALSE) {
524                 return(TRUE);
525         } else {
526                 return(FALSE);
527         }
528 }
529
530 gpointer OpenProcess (guint32 access G_GNUC_UNUSED, gboolean inherit G_GNUC_UNUSED, guint32 pid)
531 {
532         /* Find the process handle that corresponds to pid */
533         gpointer handle;
534         
535         mono_once (&process_current_once, process_set_current);
536
537         handle=_wapi_search_handle (WAPI_HANDLE_PROCESS, process_open_compare,
538                                     GUINT_TO_POINTER (pid), NULL, NULL);
539         if(handle==0) {
540 #ifdef DEBUG
541                 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find pid %d", pid);
542 #endif
543
544                 /* Set an error code */
545         
546                 return(NULL);
547         }
548
549         _wapi_handle_ref (handle);
550         
551         return(handle);
552 }
553
554 gboolean GetExitCodeProcess (gpointer process, guint32 *code)
555 {
556         struct _WapiHandle_process *process_handle;
557         gboolean ok;
558         
559         mono_once (&process_current_once, process_set_current);
560
561         if(code==NULL) {
562                 return(FALSE);
563         }
564         
565         ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
566                                 (gpointer *)&process_handle, NULL);
567         if(ok==FALSE) {
568 #ifdef DEBUG
569                 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
570                            process);
571 #endif
572                 
573                 return(FALSE);
574         }
575         
576         /* A process handle is only signalled if the process has exited */
577         if(_wapi_handle_issignalled (process)==TRUE) {
578                 *code=process_handle->exitstatus;
579         } else {
580                 *code=STILL_ACTIVE;
581         }
582         
583         return(TRUE);
584 }
585
586 gboolean GetProcessTimes (gpointer process, WapiFileTime *create_time,
587                           WapiFileTime *exit_time, WapiFileTime *kernel_time,
588                           WapiFileTime *user_time)
589 {
590         struct _WapiHandle_process *process_handle;
591         gboolean ok;
592         
593         mono_once (&process_current_once, process_set_current);
594
595         if(create_time==NULL || exit_time==NULL || kernel_time==NULL ||
596            user_time==NULL) {
597                 /* Not sure if w32 allows NULLs here or not */
598                 return(FALSE);
599         }
600         
601         ok=_wapi_lookup_handle (process, WAPI_HANDLE_PROCESS,
602                                 (gpointer *)&process_handle, NULL);
603         if(ok==FALSE) {
604 #ifdef DEBUG
605                 g_message (G_GNUC_PRETTY_FUNCTION ": Can't find process %p",
606                            process);
607 #endif
608                 
609                 return(FALSE);
610         }
611         
612         *create_time=process_handle->create_time;
613
614         /* A process handle is only signalled if the process has
615          * exited.  Otherwise exit_time isn't set
616          */
617         if(_wapi_handle_issignalled (process)==TRUE) {
618                 *exit_time=process_handle->exit_time;
619         }
620         
621         return(TRUE);
622 }