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