New test.
[mono.git] / mono / metadata / process.c
1 /*
2  * process.c: System.Diagnostics.Process support
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2002 Ximian, Inc.
8  * Copyright (c) 2002-2006 Novell, Inc.
9  */
10
11 #include <config.h>
12
13 #include <glib.h>
14 #include <string.h>
15
16 #include <mono/metadata/object.h>
17 #include <mono/metadata/process.h>
18 #include <mono/metadata/assembly.h>
19 #include <mono/metadata/appdomain.h>
20 #include <mono/metadata/image.h>
21 #include <mono/metadata/cil-coff.h>
22 #include <mono/metadata/exception.h>
23 #include <mono/utils/strenc.h>
24 #include <mono/io-layer/io-layer.h>
25 /* FIXME: fix this code to not depend so much on the inetrnals */
26 #include <mono/metadata/class-internals.h>
27
28 #undef DEBUG
29
30 HANDLE ves_icall_System_Diagnostics_Process_GetProcess_internal (guint32 pid)
31 {
32         HANDLE handle;
33         
34         MONO_ARCH_SAVE_REGS;
35
36         /* GetCurrentProcess returns a pseudo-handle, so use
37          * OpenProcess instead
38          */
39         handle=OpenProcess (PROCESS_ALL_ACCESS, TRUE, pid);
40         
41         if(handle==NULL) {
42                 /* FIXME: Throw an exception */
43                 return(NULL);
44         }
45         
46         return(handle);
47 }
48
49 guint32 ves_icall_System_Diagnostics_Process_GetPid_internal (void)
50 {
51         MONO_ARCH_SAVE_REGS;
52
53         return(GetCurrentProcessId ());
54 }
55
56 void ves_icall_System_Diagnostics_Process_Process_free_internal (MonoObject *this,
57                                                                  HANDLE process)
58 {
59         MONO_ARCH_SAVE_REGS;
60
61 #ifdef THREAD_DEBUG
62         g_message (G_GNUC_PRETTY_FUNCTION ": Closing process %p, handle %p",
63                    this, process);
64 #endif
65
66         CloseHandle (process);
67 }
68
69 #define STASH_SYS_ASS(this) \
70         if(system_assembly == NULL) { \
71                 system_assembly=this->vtable->klass->image; \
72         }
73
74 static MonoImage *system_assembly=NULL;
75
76 static guint32 unicode_chars (const gunichar2 *str)
77 {
78         guint32 len=0;
79         
80         do {
81                 if(str[len]=='\0') {
82                         return(len);
83                 }
84                 len++;
85         } while(1);
86 }
87
88 static guint32 unicode_bytes (const gunichar2 *str)
89 {
90         guint32 len=0;
91         
92         do {
93                 if(str[len]=='\0') {
94                         /* Include the terminators */
95                         return((len*2)+2);
96                 }
97                 len++;
98         } while(1);
99 }
100
101 /* 
102  * compare a null-terminated utf16 string and a normal string.
103  * Can be used only for ascii or latin1 chars.
104  */
105 static gboolean
106 unicode_string_equals (const gunichar2 *str1, const gchar *str2)
107 {
108         while (*str1 && *str2) {
109                 if (*str1 != *str2)
110                         return FALSE;
111                 ++str1;
112                 ++str2;
113         }
114         return *str1 == *str2;
115 }
116
117 static void process_set_field_object (MonoObject *obj, const gchar *fieldname,
118                                       MonoObject *data)
119 {
120         MonoClassField *field;
121
122 #ifdef DEBUG
123         g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to object at %p",
124                    fieldname, data);
125 #endif
126
127         field=mono_class_get_field_from_name (mono_object_class (obj),
128                                               fieldname);
129         *(MonoObject **)(((char *)obj) + field->offset)=data;
130 }
131
132 static void process_set_field_string (MonoObject *obj, const gchar *fieldname,
133                                       const gunichar2 *val, guint32 len)
134 {
135         MonoClassField *field;
136         MonoString *string;
137
138 #ifdef DEBUG
139         g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to [%s]",
140                    fieldname, g_utf16_to_utf8 (val, len, NULL, NULL, NULL));
141 #endif
142
143         string=mono_string_new_utf16 (mono_object_domain (obj), val, len);
144         
145         field=mono_class_get_field_from_name (mono_object_class (obj),
146                                               fieldname);
147         *(MonoString **)(((char *)obj) + field->offset)=string;
148 }
149
150 static void process_set_field_string_utf8 (MonoObject *obj,
151                                            const gchar *fieldname,
152                                            const gchar *val)
153 {
154         MonoClassField *field;
155         MonoString *string;
156
157 #ifdef DEBUG
158         g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to [%s]",
159                    fieldname, val);
160 #endif
161
162         string=mono_string_new (mono_object_domain (obj), val);
163         
164         field=mono_class_get_field_from_name (mono_object_class (obj),
165                                               fieldname);
166         *(MonoString **)(((char *)obj) + field->offset)=string;
167 }
168
169 static void process_set_field_int (MonoObject *obj, const gchar *fieldname,
170                                    guint32 val)
171 {
172         MonoClassField *field;
173
174 #ifdef DEBUG
175         g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to %d",
176                    fieldname, val);
177 #endif
178         
179         field=mono_class_get_field_from_name (mono_object_class (obj),
180                                               fieldname);
181         *(guint32 *)(((char *)obj) + field->offset)=val;
182 }
183
184 static void process_set_field_bool (MonoObject *obj, const gchar *fieldname,
185                                     gboolean val)
186 {
187         MonoClassField *field;
188
189 #ifdef DEBUG
190         g_message (G_GNUC_PRETTY_FUNCTION ": Setting field %s to %s",
191                    fieldname, val?"TRUE":"FALSE");
192 #endif
193         
194         field=mono_class_get_field_from_name (mono_object_class (obj),
195                                               fieldname);
196         *(guint8 *)(((char *)obj) + field->offset)=val;
197 }
198
199 typedef struct {
200         guint16 data_len;
201         guint16 value_len;
202         guint16 type;
203         gunichar2 *key;
204 } version_data;
205
206 /* Returns a pointer to the value data, because theres no way to know
207  * how big that data is (value_len is set to zero for most blocks :-()
208  */
209 static gpointer process_get_versioninfo_block (gpointer data,
210                                                version_data *block)
211 {
212         block->data_len=*((guint16 *)data);
213         data = (char *)data + sizeof(guint16);
214         block->value_len=*((guint16 *)data);
215         data = (char *)data + sizeof(guint16);
216
217         /* No idea what the type is supposed to indicate */
218         block->type=*((guint16 *)data);
219         data = (char *)data + sizeof(guint16);
220         block->key=((gunichar2 *)data);
221
222         /* skip over the key (including the terminator) */
223         data=((gunichar2 *)data)+(unicode_chars (block->key)+1);
224
225         /* align on a 32-bit boundary */
226         data=(gpointer)((char *)data + 3);
227         data=(gpointer)((char *)data - (GPOINTER_TO_INT(data) & 3));
228         
229         return(data);
230 }
231
232 /* Returns a pointer to the byte following the Var block */
233 static gpointer process_read_var_block (MonoObject *filever, gpointer data_ptr,
234                                         guint16 data_len)
235 {
236         /* Not currently interested in the VarFileInfo block.  This
237          * might change if language support is needed for file version
238          * strings (VarFileInfo contains lists of supported
239          * languages.)
240          */
241         version_data block;
242
243         /* data_ptr is pointing at a Var block of length data_len */
244         data_ptr=process_get_versioninfo_block (data_ptr, &block);
245         data_ptr=((guchar *)data_ptr)+block.value_len;
246
247         return(data_ptr);
248 }
249
250 /* Returns a pointer to the byte following the String block, or NULL
251  * if the data read hits padding.  We can't recover from this because
252  * the data length does not include padding bytes, so it's not
253  * possible to just return the start position + length.
254  */
255 static gpointer process_read_string_block (MonoObject *filever,
256                                            gpointer data_ptr,
257                                            guint16 data_len,
258                                            gboolean store)
259 {
260         version_data block;
261         guint16 string_len=28; /* Length of the StringTable block */
262
263         /* data_ptr is pointing at an array of one or more String
264          * blocks with total length (not including alignment padding)
265          * of data_len.
266          */
267         while(string_len<data_len) {
268                 gunichar2 *value;
269                 
270                 /* align on a 32-bit boundary */
271                 data_ptr=(gpointer)((char *)data_ptr + 3);
272                 data_ptr=(gpointer)((char *)data_ptr -
273                     (GPOINTER_TO_INT(data_ptr) & 3));
274
275                 data_ptr=process_get_versioninfo_block (data_ptr, &block);
276                 if(block.data_len==0) {
277                         /* We must have hit padding, so give up
278                          * processing now
279                          */
280 #ifdef DEBUG
281                         g_message (G_GNUC_PRETTY_FUNCTION
282                                    ": Hit 0-length block, giving up");
283 #endif
284                         return(NULL);
285                 }
286                 
287                 string_len=string_len+block.data_len;
288                 value=(gunichar2 *)data_ptr;
289                 /* Skip over the value */
290                 data_ptr=((gunichar2 *)data_ptr)+block.value_len;
291
292                 if(store==TRUE) {
293                         if (unicode_string_equals (block.key, "Comments")) {
294                                 process_set_field_string (filever, "comments", value, unicode_chars (value));
295                         } else if (unicode_string_equals (block.key, "CompanyName")) {
296                                 process_set_field_string (filever, "companyname", value, unicode_chars (value));
297                         } else if (unicode_string_equals (block.key, "FileDescription")) {
298                                 process_set_field_string (filever, "filedescription", value, unicode_chars (value));
299                         } else if (unicode_string_equals (block.key, "FileVersion")) {
300                                 process_set_field_string (filever, "fileversion", value, unicode_chars (value));
301                         } else if (unicode_string_equals (block.key, "InternalName")) {
302                                 process_set_field_string (filever, "internalname", value, unicode_chars (value));
303                         } else if (unicode_string_equals (block.key, "LegalCopyright")) {
304                                 process_set_field_string (filever, "legalcopyright", value, unicode_chars (value));
305                         } else if (unicode_string_equals (block.key, "LegalTrademarks")) {
306                                 process_set_field_string (filever, "legaltrademarks", value, unicode_chars (value));
307                         } else if (unicode_string_equals (block.key, "OriginalFilename")) {
308                                 process_set_field_string (filever, "originalfilename", value, unicode_chars (value));
309                         } else if (unicode_string_equals (block.key, "PrivateBuild")) {
310                                 process_set_field_string (filever, "privatebuild", value, unicode_chars (value));
311                         } else if (unicode_string_equals (block.key, "ProductName")) {
312                                 process_set_field_string (filever, "productname", value, unicode_chars (value));
313                         } else if (unicode_string_equals (block.key, "ProductVersion")) {
314                                 process_set_field_string (filever, "productversion", value, unicode_chars (value));
315                         } else if (unicode_string_equals (block.key, "SpecialBuild")) {
316                                 process_set_field_string (filever, "specialbuild", value, unicode_chars (value));
317                         } else {
318                                 /* Not an error, just not interesting
319                                  * in this case
320                                  */
321                         }
322                 }
323         }
324         
325         return(data_ptr);
326 }
327
328 /* returns a pointer to the byte following the Stringtable block, or
329  * NULL if the data read hits padding.  We can't recover from this
330  * because the data length does not include padding bytes, so it's not
331  * possible to just return the start position + length
332  */
333 static gpointer process_read_stringtable_block (MonoObject *filever,
334                                                 gpointer data_ptr,
335                                                 guint16 data_len)
336 {
337         version_data block;
338         gchar *language;
339         guint16 string_len=36;  /* length of the StringFileInfo block */
340
341         /* data_ptr is pointing at an array of StringTable blocks,
342          * with total length (not including alignment padding) of
343          * data_len.
344          */
345
346         while(string_len<data_len) {
347                 /* align on a 32-bit boundary */
348                 data_ptr=(gpointer)((char *)data_ptr + 3);
349                 data_ptr=(gpointer)((char *)data_ptr -
350                     (GPOINTER_TO_INT(data_ptr) & 3));
351
352                 data_ptr=process_get_versioninfo_block (data_ptr, &block);
353                 if(block.data_len==0) {
354                         /* We must have hit padding, so give up
355                          * processing now
356                          */
357 #ifdef DEBUG
358                         g_message (G_GNUC_PRETTY_FUNCTION
359                                    ": Hit 0-length block, giving up");
360 #endif
361                         return(NULL);
362                 }
363                 string_len=string_len+block.data_len;
364
365                 language = g_utf16_to_utf8 (block.key, unicode_bytes (block.key), NULL, NULL, NULL);
366                 g_strdown (language);
367
368                 /* Kludge: treat en_US as neutral too */
369                 if (!strcmp (language, "007f04b0") ||
370                     !strcmp (language, "000004b0") ||
371                     !strcmp (language, "040904b0")) {
372                         /* Got the one we're interested in */
373                         process_set_field_string_utf8 (filever, "language",
374                                                        "Language Neutral");
375                         
376                         data_ptr=process_read_string_block (filever, data_ptr,
377                                                             block.data_len,
378                                                             TRUE);
379                 } else {
380                         /* Some other language.  We might want to do
381                          * something with this in the future.
382                          */
383                         data_ptr=process_read_string_block (filever, data_ptr,
384                                                             block.data_len,
385                                                             FALSE);
386                 }
387                 g_free (language);
388
389                 if(data_ptr==NULL) {
390                         /* Child block hit padding */
391 #ifdef DEBUG
392                         g_message (G_GNUC_PRETTY_FUNCTION ": Child block hit 0-length block, giving up");
393 #endif
394                         return(NULL);
395                 }
396         }
397                 
398         return(data_ptr);
399 }
400
401 static void process_read_fixedfileinfo_block (MonoObject *filever,
402                                               VS_FIXEDFILEINFO *ffi)
403 {
404 #ifdef DEBUG
405         g_message (G_GNUC_PRETTY_FUNCTION ": ffi: sig 0x%x, strucver 0x%x, fileverm 0x%x, fileverl 0x%x, prodverm 0x%x, prodverl 0x%x, ffmask 0x%x, ff 0x%x, os 0x%x, type 0x%x, subtype 0x%x, datem 0x%x, datel 0x%x", ffi->dwSignature, ffi->dwStrucVersion, ffi->dwFileVersionMS, ffi->dwFileVersionLS, ffi->dwProductVersionMS, ffi->dwProductVersionLS, ffi->dwFileFlagsMask, ffi->dwFileFlags, ffi->dwFileOS, ffi->dwFileType, ffi->dwFileSubtype, ffi->dwFileDateMS, ffi->dwFileDateLS);
406 #endif
407                 
408         process_set_field_int (filever, "filemajorpart",
409                                HIWORD (ffi->dwFileVersionMS));
410         process_set_field_int (filever, "fileminorpart",
411                                LOWORD (ffi->dwFileVersionMS));
412         process_set_field_int (filever, "filebuildpart",
413                                HIWORD (ffi->dwFileVersionLS));
414         process_set_field_int (filever, "fileprivatepart",
415                                LOWORD (ffi->dwFileVersionLS));
416                 
417         process_set_field_int (filever, "productmajorpart",
418                                HIWORD (ffi->dwProductVersionMS));
419         process_set_field_int (filever, "productminorpart",
420                                LOWORD (ffi->dwProductVersionMS));
421         process_set_field_int (filever, "productbuildpart",
422                                HIWORD (ffi->dwProductVersionLS));
423         process_set_field_int (filever, "productprivatepart",
424                                LOWORD (ffi->dwProductVersionLS));
425         
426         process_set_field_bool (filever, "isdebug",
427                                 ffi->dwFileFlags&VS_FF_DEBUG);
428         process_set_field_bool (filever, "isprerelease",
429                                 ffi->dwFileFlags&VS_FF_PRERELEASE);
430         process_set_field_bool (filever, "ispatched",
431                                 ffi->dwFileFlags&VS_FF_PATCHED);
432         process_set_field_bool (filever, "isprivatebuild",
433                                 ffi->dwFileFlags&VS_FF_PRIVATEBUILD);
434         process_set_field_bool (filever, "isspecialbuild",
435                                 ffi->dwFileFlags&VS_FF_SPECIALBUILD);
436 }
437
438 static void process_get_fileversion (MonoObject *filever, MonoImage *image)
439 {
440         MonoPEResourceDataEntry *version_info;
441         gpointer data;
442         VS_FIXEDFILEINFO *ffi;
443         gpointer data_ptr;
444         version_data block;
445         gint32 data_len; /* signed to guard against underflow */
446
447         version_info=mono_image_lookup_resource (image,
448                                                  MONO_PE_RESOURCE_ID_VERSION,
449                                                  0, NULL);
450 #ifdef DEBUG
451         g_message (G_GNUC_PRETTY_FUNCTION ": image_lookup returned %p",
452                    version_info);
453 #endif
454
455         if(version_info==NULL) {
456                 return;
457         }
458         
459         data=mono_image_rva_map (image,
460                                version_info->rde_data_offset);
461         if(data==NULL) {
462                 return;
463         }
464
465         /* See io-layer/versioninfo.h for the gory details on how this
466          * data is laid out. (data should be pointing to
467          * VS_VERSIONINFO data).
468          */
469
470         data_ptr=process_get_versioninfo_block (data, &block);
471                 
472         data_len=block.data_len;
473                 
474         if(block.value_len!=sizeof(VS_FIXEDFILEINFO)) {
475 #ifdef DEBUG
476                 g_message (G_GNUC_PRETTY_FUNCTION
477                            ": FIXEDFILEINFO size mismatch");
478 #endif
479                 return;
480         }
481
482         if (!unicode_string_equals (block.key, "VS_VERSION_INFO")) {
483 #ifdef DEBUG
484                 g_message (G_GNUC_PRETTY_FUNCTION
485                            ": VS_VERSION_INFO mismatch");
486 #endif
487                 return;
488         }
489
490         ffi=((VS_FIXEDFILEINFO *)data_ptr);
491         data_ptr = (char *)data_ptr + sizeof(VS_FIXEDFILEINFO);
492         if((ffi->dwSignature!=VS_FFI_SIGNATURE) ||
493            (ffi->dwStrucVersion!=VS_FFI_STRUCVERSION)) {
494 #ifdef DEBUG
495                 g_message (G_GNUC_PRETTY_FUNCTION
496                            ": FIXEDFILEINFO bad signature");
497 #endif
498                 return;
499         }
500         process_read_fixedfileinfo_block (filever, ffi);
501         
502         /* Subtract the 92 bytes we've already seen */
503         data_len -= 92;
504         
505         /* There now follow zero or one StringFileInfo blocks and zero
506          * or one VarFileInfo blocks
507          */
508         while(data_len > 0) {
509                 /* align on a 32-bit boundary */
510                 data_ptr=(gpointer)((char *)data_ptr + 3);
511                 data_ptr=(gpointer)((char *)data_ptr -
512                     (GPOINTER_TO_INT(data_ptr) & 3));
513
514                 data_ptr=process_get_versioninfo_block (data_ptr, &block);
515                 if(block.data_len==0) {
516                         /* We must have hit padding, so give up
517                          * processing now
518                          */
519 #ifdef DEBUG
520                         g_message (G_GNUC_PRETTY_FUNCTION
521                                    ": Hit 0-length block, giving up");
522 #endif
523                         return;
524                 }
525                 
526                 data_len=data_len-block.data_len;
527
528                 if (unicode_string_equals (block.key, "VarFileInfo")) {
529                         data_ptr=process_read_var_block (filever, data_ptr,
530                                                          block.data_len);
531                 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
532                         data_ptr=process_read_stringtable_block (filever, data_ptr, block.data_len);
533                 } else {
534                         /* Bogus data */
535 #ifdef DEBUG
536                         g_message (G_GNUC_PRETTY_FUNCTION
537                                    ": Not a valid VERSIONINFO child block");
538                         return;
539 #endif
540                 }
541
542                 if(data_ptr==NULL) {
543                         /* Child block hit padding */
544 #ifdef DEBUG
545                         g_message (G_GNUC_PRETTY_FUNCTION ": Child block hit 0-length block, giving up");
546 #endif
547                         return;
548                 }
549         }
550 }
551
552 static void process_add_module (GPtrArray *modules, MonoAssembly *ass)
553 {
554         MonoClass *proc_class, *filever_class;
555         MonoObject *item, *filever;
556         MonoDomain *domain=mono_domain_get ();
557         gchar *modulename;
558         const char* filename;
559         
560         /* Build a System.Diagnostics.ProcessModule with the data.
561          * Leave BaseAddress and EntryPointAddress set to NULL,
562          * FileName is ass->image->name, FileVersionInfo is an object
563          * constructed from the PE image data referenced by
564          * ass->image, ModuleMemorySize set to 0, ModuleName the last
565          * component of FileName.
566          */
567         proc_class=mono_class_from_name (system_assembly, "System.Diagnostics",
568                                          "ProcessModule");
569         item=mono_object_new (domain, proc_class);
570
571         filever_class=mono_class_from_name (system_assembly,
572                                             "System.Diagnostics",
573                                             "FileVersionInfo");
574         filever=mono_object_new (domain, filever_class);
575         
576 #ifdef DEBUG
577         g_message (G_GNUC_PRETTY_FUNCTION ": recording assembly: FileName [%s] FileVersionInfo [%d.%d.%d.%d], ModuleName [%s]", ass->image->name, ass->aname.major, ass->aname.minor, ass->aname.build, ass->aname.revision, ass->image->name);
578 #endif
579
580         process_get_fileversion (filever, mono_assembly_get_image (ass));
581
582         filename = mono_image_get_filename (mono_assembly_get_image (ass));
583         process_set_field_string_utf8 (filever, "filename", filename);
584         process_set_field_string_utf8 (item, "filename", filename);
585         process_set_field_object (item, "version_info", filever);
586
587         modulename=g_path_get_basename (filename);
588         process_set_field_string_utf8 (item, "modulename", modulename);
589         g_free (modulename);
590
591         g_ptr_array_add (modules, item);
592 }
593
594 static void process_scan_modules (gpointer data, gpointer user_data)
595 {
596         MonoAssembly *ass=data;
597         GPtrArray *modules=user_data;
598
599         /* The main assembly is already in the list */
600         if(mono_assembly_get_main () != ass) {
601                 process_add_module (modules, ass);
602         }
603 }
604
605
606 /* Returns an array of System.Diagnostics.ProcessModule */
607 MonoArray *ves_icall_System_Diagnostics_Process_GetModules_internal (MonoObject *this)
608 {
609         /* I was going to use toolhelp for this, but then realised I
610          * was being an idiot :)
611          *
612          * (Toolhelp would give shared libraries open by the runtime,
613          * as well as open assemblies.  On windows my tests didnt find
614          * the assemblies loaded by mono either.)
615          */
616         GPtrArray *modules_list=g_ptr_array_new ();
617         MonoArray *arr;
618         guint32 i;
619         
620         MONO_ARCH_SAVE_REGS;
621
622         STASH_SYS_ASS (this);
623         
624         /* Make sure the first entry is the main module */
625         process_add_module (modules_list, mono_assembly_get_main ());
626         
627         mono_assembly_foreach (process_scan_modules, modules_list);
628
629         /* Build a MonoArray out of modules_list */
630         arr=mono_array_new (mono_domain_get (), mono_get_object_class (),
631                             modules_list->len);
632         
633         for(i=0; i<modules_list->len; i++) {
634                 mono_array_setref (arr, i, g_ptr_array_index (modules_list, i));
635         }
636         
637         g_ptr_array_free (modules_list, TRUE);
638         
639         return(arr);
640 }
641
642 void ves_icall_System_Diagnostics_FileVersionInfo_GetVersionInfo_internal (MonoObject *this, MonoString *filename)
643 {
644         MonoImage *image;
645         gchar *filename_utf8;
646         
647         MONO_ARCH_SAVE_REGS;
648
649         STASH_SYS_ASS (this);
650         
651         filename_utf8 = mono_string_to_utf8 (filename);
652         image = mono_pe_file_open (filename_utf8, NULL);
653         g_free (filename_utf8);
654         
655         if(image==NULL) {
656                 /* FIXME: an exception might be appropriate here */
657 #ifdef DEBUG
658                 g_message (G_GNUC_PRETTY_FUNCTION ": Failed to load image");
659 #endif
660
661                 return;
662         }
663         
664         process_get_fileversion (this, image);
665         process_set_field_string_utf8 (this, "filename", mono_image_get_filename (image));
666         
667         mono_image_close (image);
668 }
669
670 /* Only used when UseShellExecute is false */
671 static gchar *
672 quote_path (const gchar *path)
673 {
674         gchar *res = g_shell_quote (path);
675 #ifdef PLATFORM_WIN32
676         {
677         gchar *q = res;
678         while (*q) {
679                 if (*q == '\'')
680                         *q = '\"';
681                 q++;
682         }
683         }
684 #endif
685         return res;
686 }
687
688 /* Only used when UseShellExecute is false */
689 static gboolean
690 complete_path (const gunichar2 *appname, gchar **completed)
691 {
692         gchar *utf8app;
693         gchar *found;
694
695         utf8app = g_utf16_to_utf8 (appname, -1, NULL, NULL, NULL);
696         if (g_path_is_absolute (utf8app)) {
697                 *completed = quote_path (utf8app);
698                 g_free (utf8app);
699                 return TRUE;
700         }
701
702         if (g_file_test (utf8app, G_FILE_TEST_IS_EXECUTABLE) && !g_file_test (utf8app, G_FILE_TEST_IS_DIR)) {
703                 *completed = quote_path (utf8app);
704                 g_free (utf8app);
705                 return TRUE;
706         }
707         
708         found = g_find_program_in_path (utf8app);
709         if (found == NULL) {
710                 *completed = NULL;
711                 g_free (utf8app);
712                 return FALSE;
713         }
714
715         *completed = quote_path (found);
716         g_free (found);
717         g_free (utf8app);
718         return TRUE;
719 }
720
721 MonoBoolean ves_icall_System_Diagnostics_Process_ShellExecuteEx_internal (MonoProcessStartInfo *proc_start_info, MonoProcInfo *process_info)
722 {
723         SHELLEXECUTEINFO shellex = {0};
724         gboolean ret;
725
726         shellex.cbSize = sizeof(SHELLEXECUTEINFO);
727         shellex.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_UNICODE;
728         shellex.nShow = SW_SHOWNORMAL;
729         
730         if (proc_start_info->filename != NULL) {
731                 shellex.lpFile = mono_string_chars (proc_start_info->filename);
732         }
733
734         if (proc_start_info->arguments != NULL) {
735                 shellex.lpParameters = mono_string_chars (proc_start_info->arguments);
736         }
737
738         if (proc_start_info->verb != NULL &&
739             mono_string_length (proc_start_info->verb) != 0) {
740                 shellex.lpVerb = mono_string_chars (proc_start_info->verb);
741         }
742
743         if (proc_start_info->working_directory != NULL &&
744             mono_string_length (proc_start_info->working_directory) != 0) {
745                 shellex.lpDirectory = mono_string_chars (proc_start_info->working_directory);
746         }
747
748         ret = ShellExecuteEx (&shellex);
749         if (ret == FALSE) {
750                 process_info->pid = -GetLastError ();
751         } else {
752                 process_info->process_handle = shellex.hProcess;
753                 process_info->thread_handle = NULL;
754                 /* It appears that there's no way to get the pid from a
755                  * process handle before windows xp.  Really.
756                  */
757 #ifdef HAVE_GETPROCESSID
758                 process_info->pid = GetProcessId (shellex.hProcess);
759 #else
760                 process_info->pid = 0;
761 #endif
762                 process_info->tid = 0;
763         }
764
765         return (ret);
766 }
767
768 MonoBoolean ves_icall_System_Diagnostics_Process_CreateProcess_internal (MonoProcessStartInfo *proc_start_info, HANDLE stdin_handle, HANDLE stdout_handle, HANDLE stderr_handle, MonoProcInfo *process_info)
769 {
770         gboolean ret;
771         gunichar2 *dir;
772         STARTUPINFO startinfo={0};
773         PROCESS_INFORMATION procinfo;
774         gunichar2 *shell_path = NULL;
775         gchar *env_vars = NULL;
776         gboolean free_shell_path = TRUE;
777 #ifdef PLATFORM_WIN32
778         gchar *newcmd, *tmp;
779 #endif
780         gchar *spath = NULL;
781         MonoString *cmd = proc_start_info->arguments;
782         
783         MONO_ARCH_SAVE_REGS;
784
785         startinfo.cb=sizeof(STARTUPINFO);
786         startinfo.dwFlags=STARTF_USESTDHANDLES;
787         startinfo.hStdInput=stdin_handle;
788         startinfo.hStdOutput=stdout_handle;
789         startinfo.hStdError=stderr_handle;
790         
791         shell_path = mono_string_chars (proc_start_info->filename);
792         complete_path (shell_path, &spath);
793         if (spath == NULL) {
794                 process_info->pid = -ERROR_FILE_NOT_FOUND;
795                 return FALSE;
796         }
797 #ifdef PLATFORM_WIN32
798         /* Seems like our CreateProcess does not work as the windows one.
799          * This hack is needed to deal with paths containing spaces */
800         shell_path = NULL;
801         free_shell_path = FALSE;
802         tmp = mono_string_to_utf8 (cmd);
803         newcmd = g_strdup_printf ("%s %s", spath, tmp);
804         cmd = mono_string_new_wrapper (newcmd);
805         g_free (newcmd);
806         g_free (tmp);
807 #else
808         shell_path = g_utf8_to_utf16 (spath, -1, NULL, NULL, NULL);
809 #endif
810         g_free (spath);
811
812         if (process_info->env_keys != NULL) {
813                 gint i, len; 
814                 MonoString *ms;
815                 MonoString *key, *value;
816                 gunichar2 *str, *ptr;
817                 gunichar2 *equals16;
818
819                 for (len = 0, i = 0; i < mono_array_length (process_info->env_keys); i++) {
820                         ms = mono_array_get (process_info->env_values, MonoString *, i);
821                         if (ms == NULL)
822                                 continue;
823
824                         len += mono_string_length (ms) * sizeof (gunichar2);
825                         ms = mono_array_get (process_info->env_keys, MonoString *, i);
826                         len += mono_string_length (ms) * sizeof (gunichar2);
827                         len += 2 * sizeof (gunichar2);
828                 }
829
830                 equals16 = g_utf8_to_utf16 ("=", 1, NULL, NULL, NULL);
831                 ptr = str = g_new0 (gunichar2, len + 1);
832                 for (i = 0; i < mono_array_length (process_info->env_keys); i++) {
833                         value = mono_array_get (process_info->env_values, MonoString *, i);
834                         if (value == NULL)
835                                 continue;
836
837                         key = mono_array_get (process_info->env_keys, MonoString *, i);
838                         memcpy (ptr, mono_string_chars (key), mono_string_length (key) * sizeof (gunichar2));
839                         ptr += mono_string_length (key);
840
841                         memcpy (ptr, equals16, sizeof (gunichar2));
842                         ptr++;
843
844                         memcpy (ptr, mono_string_chars (value), mono_string_length (value) * sizeof (gunichar2));
845                         ptr += mono_string_length (value);
846                         ptr++;
847                 }
848
849                 g_free (equals16);
850                 env_vars = (gchar *) str;
851         }
852         
853         /* The default dir name is "".  Turn that into NULL to mean
854          * "current directory"
855          */
856         if(mono_string_length (proc_start_info->working_directory)==0) {
857                 dir=NULL;
858         } else {
859                 dir=mono_string_chars (proc_start_info->working_directory);
860         }
861         
862         ret=CreateProcess (shell_path, mono_string_chars (cmd), NULL, NULL, TRUE, CREATE_UNICODE_ENVIRONMENT, env_vars, dir, &startinfo, &procinfo);
863
864         g_free (env_vars);
865         if (free_shell_path)
866                 g_free (shell_path);
867
868         if(ret) {
869                 process_info->process_handle=procinfo.hProcess;
870                 /*process_info->thread_handle=procinfo.hThread;*/
871                 process_info->thread_handle=NULL;
872                 if (procinfo.hThread != NULL)
873                         CloseHandle(procinfo.hThread);
874                 process_info->pid=procinfo.dwProcessId;
875                 process_info->tid=procinfo.dwThreadId;
876         } else {
877                 process_info->pid = -GetLastError ();
878         }
879         
880         return(ret);
881 }
882
883 MonoBoolean ves_icall_System_Diagnostics_Process_WaitForExit_internal (MonoObject *this, HANDLE process, gint32 ms)
884 {
885         guint32 ret;
886         
887         MONO_ARCH_SAVE_REGS;
888
889         if(ms<0) {
890                 /* Wait forever */
891                 ret=WaitForSingleObjectEx (process, INFINITE, TRUE);
892         } else {
893                 ret=WaitForSingleObjectEx (process, ms, TRUE);
894         }
895         if(ret==WAIT_OBJECT_0) {
896                 return(TRUE);
897         } else {
898                 return(FALSE);
899         }
900 }
901
902 gint64 ves_icall_System_Diagnostics_Process_ExitTime_internal (HANDLE process)
903 {
904         gboolean ret;
905         gint64 ticks;
906         FILETIME create_time, exit_time, kernel_time, user_time;
907         
908         MONO_ARCH_SAVE_REGS;
909
910         ret=GetProcessTimes (process, &create_time, &exit_time, &kernel_time,
911                              &user_time);
912         if(ret==TRUE) {
913                 ticks=((guint64)exit_time.dwHighDateTime << 32) +
914                         exit_time.dwLowDateTime;
915                 
916                 return(ticks);
917         } else {
918                 return(0);
919         }
920 }
921
922 gint64 ves_icall_System_Diagnostics_Process_StartTime_internal (HANDLE process)
923 {
924         gboolean ret;
925         gint64 ticks;
926         FILETIME create_time, exit_time, kernel_time, user_time;
927         
928         MONO_ARCH_SAVE_REGS;
929
930         ret=GetProcessTimes (process, &create_time, &exit_time, &kernel_time,
931                              &user_time);
932         if(ret==TRUE) {
933                 ticks=((guint64)create_time.dwHighDateTime << 32) +
934                         create_time.dwLowDateTime;
935                 
936                 return(ticks);
937         } else {
938                 return(0);
939         }
940 }
941
942 gint32 ves_icall_System_Diagnostics_Process_ExitCode_internal (HANDLE process)
943 {
944         guint32 code;
945         
946         MONO_ARCH_SAVE_REGS;
947
948         GetExitCodeProcess (process, &code);
949         
950 #ifdef DEBUG
951         g_message (G_GNUC_PRETTY_FUNCTION ": process exit code is %d", code);
952 #endif
953         
954         return(code);
955 }
956
957 MonoString *ves_icall_System_Diagnostics_Process_ProcessName_internal (HANDLE process)
958 {
959         MonoString *string;
960         gboolean ok;
961         HMODULE mod;
962         gunichar2 name[MAX_PATH];
963         guint32 needed;
964         guint32 len;
965         
966         MONO_ARCH_SAVE_REGS;
967
968         ok=EnumProcessModules (process, &mod, sizeof(mod), &needed);
969         if(ok==FALSE) {
970                 return(NULL);
971         }
972         
973         len=GetModuleBaseName (process, mod, name, MAX_PATH);
974         if(len==0) {
975                 return(NULL);
976         }
977         
978 #ifdef DEBUG
979         g_message (G_GNUC_PRETTY_FUNCTION ": process name is [%s]",
980                    g_utf16_to_utf8 (name, -1, NULL, NULL, NULL));
981 #endif
982         
983         string=mono_string_new_utf16 (mono_domain_get (), name, len);
984         
985         return(string);
986 }
987
988 /* Returns an array of pids */
989 MonoArray *ves_icall_System_Diagnostics_Process_GetProcesses_internal (void)
990 {
991         MonoArray *procs;
992         gboolean ret;
993         guint32 needed, count, i;
994         guint32 pids[1024];
995
996         MONO_ARCH_SAVE_REGS;
997
998         ret=EnumProcesses (pids, sizeof(pids), &needed);
999         if(ret==FALSE) {
1000                 /* FIXME: throw an exception */
1001                 return(NULL);
1002         }
1003         
1004         count=needed/sizeof(guint32);
1005         procs=mono_array_new (mono_domain_get (), mono_get_int32_class (),
1006                               count);
1007         for(i=0; i<count; i++) {
1008                 mono_array_set (procs, guint32, i, pids[i]);
1009         }
1010         
1011         return(procs);
1012 }
1013
1014 MonoBoolean ves_icall_System_Diagnostics_Process_GetWorkingSet_internal (HANDLE process, guint32 *min, guint32 *max)
1015 {
1016         gboolean ret;
1017         size_t ws_min, ws_max;
1018         
1019         MONO_ARCH_SAVE_REGS;
1020
1021         ret=GetProcessWorkingSetSize (process, &ws_min, &ws_max);
1022         *min=(guint32)ws_min;
1023         *max=(guint32)ws_max;
1024         
1025         return(ret);
1026 }
1027
1028 MonoBoolean ves_icall_System_Diagnostics_Process_SetWorkingSet_internal (HANDLE process, guint32 min, guint32 max, MonoBoolean use_min)
1029 {
1030         gboolean ret;
1031         size_t ws_min;
1032         size_t ws_max;
1033         
1034         MONO_ARCH_SAVE_REGS;
1035
1036         ret=GetProcessWorkingSetSize (process, &ws_min, &ws_max);
1037         if(ret==FALSE) {
1038                 return(FALSE);
1039         }
1040         
1041         if(use_min==TRUE) {
1042                 ws_min=min;
1043         } else {
1044                 ws_max=max;
1045         }
1046         
1047         ret=SetProcessWorkingSetSize (process, ws_min, ws_max);
1048
1049         return(ret);
1050 }
1051
1052 MonoBoolean
1053 ves_icall_System_Diagnostics_Process_Kill_internal (HANDLE process, gint32 sig)
1054 {
1055         MONO_ARCH_SAVE_REGS;
1056
1057         /* sig == 1 -> Kill, sig == 2 -> CloseMainWindow */
1058
1059         return TerminateProcess (process, -sig);
1060 }
1061