My biggest commit so far (everything compiles and apparently runs fine):
[reactos.git] / reactos / lib / kernel32 / process / proc.c
1 /* $Id: proc.c,v 1.53 2003/04/26 23:13:28 hyperion Exp $
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS system libraries
5 * FILE: lib/kernel32/proc/proc.c
6 * PURPOSE: Process functions
7 * PROGRAMMER: Ariadne ( ariadne@xs4all.nl)
8 * UPDATE HISTORY:
9 * Created 01/11/98
10 */
11
12 /* INCLUDES ****************************************************************/
13
14 #include <k32.h>
15
16
17 #define NDEBUG
18 #include <kernel32/kernel32.h>
19
20
21 /* GLOBALS *******************************************************************/
22
23 WaitForInputIdleType lpfnGlobalRegisterWaitForInputIdle;
24
25 LPSTARTUPINFO lpLocalStartupInfo = NULL;
26
27 VOID STDCALL
28 RegisterWaitForInputIdle(WaitForInputIdleType lpfnRegisterWaitForInputIdle);
29
30 WINBOOL STDCALL
31 GetProcessId (HANDLE hProcess, LPDWORD lpProcessId);
32
33
34 /* FUNCTIONS ****************************************************************/
35
36 BOOL STDCALL
37 GetProcessAffinityMask (HANDLE hProcess,
38 LPDWORD lpProcessAffinityMask,
39 LPDWORD lpSystemAffinityMask)
40 {
41 PROCESS_BASIC_INFORMATION ProcessInfo;
42 ULONG BytesWritten;
43 NTSTATUS Status;
44
45 Status = NtQueryInformationProcess (hProcess,
46 ProcessBasicInformation,
47 (PVOID)&ProcessInfo,
48 sizeof(PROCESS_BASIC_INFORMATION),
49 &BytesWritten);
50 if (!NT_SUCCESS(Status))
51 {
52 SetLastError (Status);
53 return FALSE;
54 }
55
56 *lpProcessAffinityMask = (DWORD)ProcessInfo.AffinityMask;
57
58 /* FIXME */
59 *lpSystemAffinityMask = 0x00000001;
60
61 return TRUE;
62 }
63
64
65 BOOL STDCALL
66 SetProcessAffinityMask (HANDLE hProcess,
67 DWORD dwProcessAffinityMask)
68 {
69 NTSTATUS Status;
70
71 Status = NtSetInformationProcess (hProcess,
72 ProcessAffinityMask,
73 (PVOID)&dwProcessAffinityMask,
74 sizeof(DWORD));
75 if (!NT_SUCCESS(Status))
76 {
77 SetLastError (Status);
78 return FALSE;
79 }
80
81 return TRUE;
82 }
83
84
85 WINBOOL STDCALL
86 GetProcessShutdownParameters (LPDWORD lpdwLevel,
87 LPDWORD lpdwFlags)
88 {
89 CSRSS_API_REQUEST CsrRequest;
90 CSRSS_API_REPLY CsrReply;
91 NTSTATUS Status;
92
93 CsrRequest.Type = CSRSS_GET_SHUTDOWN_PARAMETERS;
94 Status = CsrClientCallServer(&CsrRequest,
95 &CsrReply,
96 sizeof(CSRSS_API_REQUEST),
97 sizeof(CSRSS_API_REPLY));
98 if (!NT_SUCCESS(Status) || !NT_SUCCESS(CsrReply.Status))
99 {
100 SetLastError(Status);
101 return(FALSE);
102 }
103
104 *lpdwLevel = CsrReply.Data.GetShutdownParametersReply.Level;
105 *lpdwFlags = CsrReply.Data.GetShutdownParametersReply.Flags;
106
107 return(TRUE);
108 }
109
110
111 WINBOOL STDCALL
112 SetProcessShutdownParameters (DWORD dwLevel,
113 DWORD dwFlags)
114 {
115 CSRSS_API_REQUEST CsrRequest;
116 CSRSS_API_REPLY CsrReply;
117 NTSTATUS Status;
118
119 CsrRequest.Data.SetShutdownParametersRequest.Level = dwLevel;
120 CsrRequest.Data.SetShutdownParametersRequest.Flags = dwFlags;
121
122 CsrRequest.Type = CSRSS_SET_SHUTDOWN_PARAMETERS;
123 Status = CsrClientCallServer(&CsrRequest,
124 &CsrReply,
125 sizeof(CSRSS_API_REQUEST),
126 sizeof(CSRSS_API_REPLY));
127 if (!NT_SUCCESS(Status) || !NT_SUCCESS(CsrReply.Status))
128 {
129 SetLastError(Status);
130 return(FALSE);
131 }
132
133 return(TRUE);
134 }
135
136
137 WINBOOL STDCALL
138 GetProcessWorkingSetSize (HANDLE hProcess,
139 LPDWORD lpMinimumWorkingSetSize,
140 LPDWORD lpMaximumWorkingSetSize)
141 {
142 QUOTA_LIMITS QuotaLimits;
143 NTSTATUS Status;
144
145 Status = NtQueryInformationProcess(hProcess,
146 ProcessQuotaLimits,
147 &QuotaLimits,
148 sizeof(QUOTA_LIMITS),
149 NULL);
150 if (!NT_SUCCESS(Status))
151 {
152 SetLastErrorByStatus(Status);
153 return(FALSE);
154 }
155
156 *lpMinimumWorkingSetSize = (DWORD)QuotaLimits.MinimumWorkingSetSize;
157 *lpMaximumWorkingSetSize = (DWORD)QuotaLimits.MaximumWorkingSetSize;
158
159 return(TRUE);
160 }
161
162
163 WINBOOL STDCALL
164 SetProcessWorkingSetSize(HANDLE hProcess,
165 DWORD dwMinimumWorkingSetSize,
166 DWORD dwMaximumWorkingSetSize)
167 {
168 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
169 return(FALSE);
170 }
171
172
173 WINBOOL STDCALL
174 GetProcessTimes(HANDLE hProcess,
175 LPFILETIME lpCreationTime,
176 LPFILETIME lpExitTime,
177 LPFILETIME lpKernelTime,
178 LPFILETIME lpUserTime)
179 {
180 KERNEL_USER_TIMES Kut;
181 NTSTATUS Status;
182
183 Status = NtQueryInformationProcess(hProcess,
184 ProcessTimes,
185 &Kut,
186 sizeof(Kut),
187 NULL);
188 if (!NT_SUCCESS(Status))
189 {
190 SetLastErrorByStatus(Status);
191 return(FALSE);
192 }
193
194 lpCreationTime->dwLowDateTime = Kut.CreateTime.u.LowPart;
195 lpCreationTime->dwHighDateTime = Kut.CreateTime.u.HighPart;
196
197 lpExitTime->dwLowDateTime = Kut.ExitTime.u.LowPart;
198 lpExitTime->dwHighDateTime = Kut.ExitTime.u.HighPart;
199
200 lpKernelTime->dwLowDateTime = Kut.KernelTime.u.LowPart;
201 lpKernelTime->dwHighDateTime = Kut.KernelTime.u.HighPart;
202
203 lpUserTime->dwLowDateTime = Kut.UserTime.u.LowPart;
204 lpUserTime->dwHighDateTime = Kut.UserTime.u.HighPart;
205
206 return(TRUE);
207 }
208
209
210 HANDLE STDCALL
211 GetCurrentProcess(VOID)
212 {
213 return((HANDLE)NtCurrentProcess());
214 }
215
216
217 HANDLE STDCALL
218 GetCurrentThread(VOID)
219 {
220 return((HANDLE)NtCurrentThread());
221 }
222
223
224 DWORD STDCALL
225 GetCurrentProcessId(VOID)
226 {
227 return((DWORD)GetTeb()->Cid.UniqueProcess);
228 }
229
230
231 WINBOOL STDCALL
232 GetExitCodeProcess(HANDLE hProcess,
233 LPDWORD lpExitCode)
234 {
235 PROCESS_BASIC_INFORMATION ProcessBasic;
236 ULONG BytesWritten;
237 NTSTATUS Status;
238
239 Status = NtQueryInformationProcess(hProcess,
240 ProcessBasicInformation,
241 &ProcessBasic,
242 sizeof(PROCESS_BASIC_INFORMATION),
243 &BytesWritten);
244 if (!NT_SUCCESS(Status))
245 {
246 SetLastErrorByStatus(Status);
247 return(FALSE);
248 }
249
250 memcpy(lpExitCode, &ProcessBasic.ExitStatus, sizeof(DWORD));
251
252 return(TRUE);
253 }
254
255
256 WINBOOL STDCALL
257 GetProcessId(HANDLE hProcess,
258 LPDWORD lpProcessId)
259 {
260 PROCESS_BASIC_INFORMATION ProcessBasic;
261 ULONG BytesWritten;
262 NTSTATUS Status;
263
264 Status = NtQueryInformationProcess(hProcess,
265 ProcessBasicInformation,
266 &ProcessBasic,
267 sizeof(PROCESS_BASIC_INFORMATION),
268 &BytesWritten);
269 if (!NT_SUCCESS(Status))
270 {
271 SetLastErrorByStatus(Status);
272 return(FALSE);
273 }
274
275 memcpy(lpProcessId, &ProcessBasic.UniqueProcessId, sizeof(DWORD));
276
277 return(TRUE);
278 }
279
280
281 HANDLE STDCALL
282 OpenProcess(DWORD dwDesiredAccess,
283 WINBOOL bInheritHandle,
284 DWORD dwProcessId)
285 {
286 NTSTATUS errCode;
287 HANDLE ProcessHandle;
288 OBJECT_ATTRIBUTES ObjectAttributes;
289 CLIENT_ID ClientId ;
290
291 ClientId.UniqueProcess = (HANDLE)dwProcessId;
292 ClientId.UniqueThread = INVALID_HANDLE_VALUE;
293
294 ObjectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
295 ObjectAttributes.RootDirectory = (HANDLE)NULL;
296 ObjectAttributes.SecurityDescriptor = NULL;
297 ObjectAttributes.SecurityQualityOfService = NULL;
298 ObjectAttributes.ObjectName = NULL;
299
300 if (bInheritHandle == TRUE)
301 ObjectAttributes.Attributes = OBJ_INHERIT;
302 else
303 ObjectAttributes.Attributes = 0;
304
305 errCode = NtOpenProcess(&ProcessHandle,
306 dwDesiredAccess,
307 &ObjectAttributes,
308 &ClientId);
309 if (!NT_SUCCESS(errCode))
310 {
311 SetLastErrorByStatus (errCode);
312 return NULL;
313 }
314 return ProcessHandle;
315 }
316
317
318 UINT STDCALL
319 WinExec(LPCSTR lpCmdLine,
320 UINT uCmdShow)
321 {
322 STARTUPINFOA StartupInfo;
323 PROCESS_INFORMATION ProcessInformation;
324 HINSTANCE hInst;
325 DWORD dosErr;
326
327 StartupInfo.cb = sizeof(STARTUPINFOA);
328 StartupInfo.wShowWindow = uCmdShow;
329 StartupInfo.dwFlags = 0;
330
331 hInst = (HINSTANCE)CreateProcessA(NULL,
332 (PVOID)lpCmdLine,
333 NULL,
334 NULL,
335 FALSE,
336 0,
337 NULL,
338 NULL,
339 &StartupInfo,
340 &ProcessInformation);
341 if ( hInst == NULL )
342 {
343 dosErr = GetLastError();
344 return dosErr;
345 }
346 if (NULL != lpfnGlobalRegisterWaitForInputIdle)
347 {
348 lpfnGlobalRegisterWaitForInputIdle (
349 ProcessInformation.hProcess,
350 10000
351 );
352 }
353 NtClose (ProcessInformation.hProcess);
354 NtClose (ProcessInformation.hThread);
355 return 0;
356 }
357
358
359 VOID STDCALL
360 RegisterWaitForInputIdle (
361 WaitForInputIdleType lpfnRegisterWaitForInputIdle
362 )
363 {
364 lpfnGlobalRegisterWaitForInputIdle = lpfnRegisterWaitForInputIdle;
365 return;
366 }
367
368
369 DWORD STDCALL
370 WaitForInputIdle (
371 HANDLE hProcess,
372 DWORD dwMilliseconds
373 )
374 {
375 return 0;
376 }
377
378
379 VOID STDCALL
380 Sleep(DWORD dwMilliseconds)
381 {
382 SleepEx(dwMilliseconds, FALSE);
383 return;
384 }
385
386
387 DWORD STDCALL
388 SleepEx(DWORD dwMilliseconds,
389 BOOL bAlertable)
390 {
391 TIME Interval;
392 NTSTATUS errCode;
393
394 if (dwMilliseconds != INFINITE)
395 {
396 /*
397 * System time units are 100 nanoseconds (a nanosecond is a billionth of
398 * a second).
399 */
400 Interval.QuadPart = dwMilliseconds;
401 Interval.QuadPart = -(Interval.QuadPart * 10000);
402 }
403 else
404 {
405 /* Approximately 292000 years hence */
406 Interval.QuadPart = -0x7FFFFFFFFFFFFFFF;
407 }
408
409 errCode = NtDelayExecution (bAlertable, &Interval);
410 if (!NT_SUCCESS(errCode))
411 {
412 SetLastErrorByStatus (errCode);
413 return -1;
414 }
415 return 0;
416 }
417
418
419 VOID STDCALL
420 GetStartupInfoW(LPSTARTUPINFOW lpStartupInfo)
421 {
422 PRTL_USER_PROCESS_PARAMETERS Params;
423
424 if (lpStartupInfo == NULL)
425 {
426 SetLastError(ERROR_INVALID_PARAMETER);
427 return;
428 }
429
430 Params = NtCurrentPeb()->ProcessParameters;
431
432 lpStartupInfo->cb = sizeof(STARTUPINFOW);
433 lpStartupInfo->lpDesktop = Params->DesktopInfo.Buffer;
434 lpStartupInfo->lpTitle = Params->WindowTitle.Buffer;
435 lpStartupInfo->dwX = Params->dwX;
436 lpStartupInfo->dwY = Params->dwY;
437 lpStartupInfo->dwXSize = Params->dwXSize;
438 lpStartupInfo->dwYSize = Params->dwYSize;
439 lpStartupInfo->dwXCountChars = Params->dwXCountChars;
440 lpStartupInfo->dwYCountChars = Params->dwYCountChars;
441 lpStartupInfo->dwFillAttribute = Params->dwFillAttribute;
442 lpStartupInfo->dwFlags = Params->dwFlags;
443 lpStartupInfo->wShowWindow = Params->wShowWindow;
444 lpStartupInfo->lpReserved = Params->ShellInfo.Buffer;
445 lpStartupInfo->cbReserved2 = Params->RuntimeInfo.Length;
446 lpStartupInfo->lpReserved2 = (LPBYTE)Params->RuntimeInfo.Buffer;
447
448 lpStartupInfo->hStdInput = Params->hStdInput;
449 lpStartupInfo->hStdOutput = Params->hStdOutput;
450 lpStartupInfo->hStdError = Params->hStdError;
451 }
452
453
454 VOID STDCALL
455 GetStartupInfoA(LPSTARTUPINFOA lpStartupInfo)
456 {
457 PRTL_USER_PROCESS_PARAMETERS Params;
458 ANSI_STRING AnsiString;
459
460 if (lpStartupInfo == NULL)
461 {
462 SetLastError(ERROR_INVALID_PARAMETER);
463 return;
464 }
465
466 Params = NtCurrentPeb ()->ProcessParameters;
467
468 RtlAcquirePebLock ();
469
470 if (lpLocalStartupInfo == NULL)
471 {
472 /* create new local startup info (ansi) */
473 lpLocalStartupInfo = RtlAllocateHeap (RtlGetProcessHeap (),
474 0,
475 sizeof(STARTUPINFOA));
476
477 lpLocalStartupInfo->cb = sizeof(STARTUPINFOA);
478
479 /* copy window title string */
480 RtlUnicodeStringToAnsiString (&AnsiString,
481 &Params->WindowTitle,
482 TRUE);
483 lpLocalStartupInfo->lpTitle = AnsiString.Buffer;
484
485 /* copy desktop info string */
486 RtlUnicodeStringToAnsiString (&AnsiString,
487 &Params->DesktopInfo,
488 TRUE);
489 lpLocalStartupInfo->lpDesktop = AnsiString.Buffer;
490
491 /* copy shell info string */
492 RtlUnicodeStringToAnsiString (&AnsiString,
493 &Params->ShellInfo,
494 TRUE);
495 lpLocalStartupInfo->lpReserved = AnsiString.Buffer;
496
497 lpLocalStartupInfo->dwX = Params->dwX;
498 lpLocalStartupInfo->dwY = Params->dwY;
499 lpLocalStartupInfo->dwXSize = Params->dwXSize;
500 lpLocalStartupInfo->dwYSize = Params->dwYSize;
501 lpLocalStartupInfo->dwXCountChars = Params->dwXCountChars;
502 lpLocalStartupInfo->dwYCountChars = Params->dwYCountChars;
503 lpLocalStartupInfo->dwFillAttribute = Params->dwFillAttribute;
504 lpLocalStartupInfo->dwFlags = Params->dwFlags;
505 lpLocalStartupInfo->wShowWindow = Params->wShowWindow;
506 lpLocalStartupInfo->cbReserved2 = Params->RuntimeInfo.Length;
507 lpLocalStartupInfo->lpReserved2 = (LPBYTE)Params->RuntimeInfo.Buffer;
508
509 lpLocalStartupInfo->hStdInput = Params->hStdInput;
510 lpLocalStartupInfo->hStdOutput = Params->hStdOutput;
511 lpLocalStartupInfo->hStdError = Params->hStdError;
512 }
513
514 RtlReleasePebLock ();
515
516 /* copy local startup info data to external startup info */
517 memcpy (lpStartupInfo,
518 lpLocalStartupInfo,
519 sizeof(STARTUPINFOA));
520 }
521
522
523 BOOL STDCALL
524 FlushInstructionCache (HANDLE hProcess,
525 LPCVOID lpBaseAddress,
526 DWORD dwSize)
527 {
528 NTSTATUS Status;
529
530 Status = NtFlushInstructionCache(hProcess,
531 (PVOID)lpBaseAddress,
532 dwSize);
533 if (!NT_SUCCESS(Status))
534 {
535 SetLastErrorByStatus(Status);
536 return FALSE;
537 }
538 return TRUE;
539 }
540
541
542 VOID STDCALL
543 ExitProcess(UINT uExitCode)
544 {
545 CSRSS_API_REQUEST CsrRequest;
546 CSRSS_API_REPLY CsrReply;
547 NTSTATUS Status;
548
549 /* unload all dll's */
550 LdrShutdownProcess ();
551
552 /* notify csrss of process termination */
553 CsrRequest.Type = CSRSS_TERMINATE_PROCESS;
554 Status = CsrClientCallServer(&CsrRequest,
555 &CsrReply,
556 sizeof(CSRSS_API_REQUEST),
557 sizeof(CSRSS_API_REPLY));
558 if (!NT_SUCCESS(Status) || !NT_SUCCESS(CsrReply.Status))
559 {
560 DbgPrint("Failed to tell csrss about terminating process. "
561 "Expect trouble.\n");
562 }
563
564
565 NtTerminateProcess (NtCurrentProcess (),
566 uExitCode);
567
568 /* should never get here */
569 assert(0);
570 while(1);
571 }
572
573
574 WINBOOL STDCALL
575 TerminateProcess (HANDLE hProcess,
576 UINT uExitCode)
577 {
578 NTSTATUS Status;
579
580 Status = NtTerminateProcess (hProcess, uExitCode);
581 if (NT_SUCCESS(Status))
582 {
583 return TRUE;
584 }
585 SetLastErrorByStatus (Status);
586 return FALSE;
587 }
588
589
590 VOID STDCALL
591 FatalAppExitA (UINT uAction,
592 LPCSTR lpMessageText)
593 {
594 UNICODE_STRING MessageTextU;
595 ANSI_STRING MessageText;
596
597 RtlInitAnsiString (&MessageText, (LPSTR) lpMessageText);
598
599 RtlAnsiStringToUnicodeString (&MessageTextU,
600 &MessageText,
601 TRUE);
602
603 FatalAppExitW (uAction, MessageTextU.Buffer);
604
605 RtlFreeUnicodeString (&MessageTextU);
606 }
607
608
609 VOID STDCALL
610 FatalAppExitW(UINT uAction,
611 LPCWSTR lpMessageText)
612 {
613 return;
614 }
615
616
617 VOID STDCALL
618 FatalExit (int ExitCode)
619 {
620 ExitProcess(ExitCode);
621 }
622
623
624 DWORD STDCALL
625 GetPriorityClass (HANDLE hProcess)
626 {
627 HANDLE hProcessTmp;
628 DWORD CsrPriorityClass = 0; // This tells CSRSS we want to GET it!
629 NTSTATUS Status;
630
631 Status =
632 NtDuplicateObject (GetCurrentProcess(),
633 hProcess,
634 GetCurrentProcess(),
635 &hProcessTmp,
636 (PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION),
637 FALSE,
638 0);
639 if (!NT_SUCCESS(Status))
640 {
641 SetLastErrorByStatus (Status);
642 return (0); /* ERROR */
643 }
644 /* Ask CSRSS to set it */
645 CsrSetPriorityClass (hProcessTmp, &CsrPriorityClass);
646 NtClose (hProcessTmp);
647 /* Translate CSR->W32 priorities */
648 switch (CsrPriorityClass)
649 {
650 case CSR_PRIORITY_CLASS_NORMAL:
651 return (NORMAL_PRIORITY_CLASS); /* 32 */
652 case CSR_PRIORITY_CLASS_IDLE:
653 return (IDLE_PRIORITY_CLASS); /* 64 */
654 case CSR_PRIORITY_CLASS_HIGH:
655 return (HIGH_PRIORITY_CLASS); /* 128 */
656 case CSR_PRIORITY_CLASS_REALTIME:
657 return (REALTIME_PRIORITY_CLASS); /* 256 */
658 }
659 SetLastError (ERROR_ACCESS_DENIED);
660 return (0); /* ERROR */
661 }
662
663
664
665 WINBOOL STDCALL
666 SetPriorityClass (HANDLE hProcess,
667 DWORD dwPriorityClass)
668 {
669 HANDLE hProcessTmp;
670 DWORD CsrPriorityClass;
671 NTSTATUS Status;
672
673 switch (dwPriorityClass)
674 {
675 case NORMAL_PRIORITY_CLASS: /* 32 */
676 CsrPriorityClass = CSR_PRIORITY_CLASS_NORMAL;
677 break;
678 case IDLE_PRIORITY_CLASS: /* 64 */
679 CsrPriorityClass = CSR_PRIORITY_CLASS_IDLE;
680 break;
681 case HIGH_PRIORITY_CLASS: /* 128 */
682 CsrPriorityClass = CSR_PRIORITY_CLASS_HIGH;
683 break;
684 case REALTIME_PRIORITY_CLASS: /* 256 */
685 CsrPriorityClass = CSR_PRIORITY_CLASS_REALTIME;
686 break;
687 default:
688 SetLastError (ERROR_INVALID_PARAMETER);
689 return (FALSE);
690 }
691 Status =
692 NtDuplicateObject (GetCurrentProcess(),
693 hProcess,
694 GetCurrentProcess(),
695 &hProcessTmp,
696 (PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION),
697 FALSE,
698 0);
699 if (!NT_SUCCESS(Status))
700 {
701 SetLastErrorByStatus (Status);
702 return (FALSE); /* ERROR */
703 }
704 /* Ask CSRSS to set it */
705 Status = CsrSetPriorityClass (hProcessTmp, &CsrPriorityClass);
706 NtClose (hProcessTmp);
707 if (!NT_SUCCESS(Status))
708 {
709 SetLastErrorByStatus (Status);
710 return (FALSE);
711 }
712 return (TRUE);
713 }
714
715
716 DWORD STDCALL
717 GetProcessVersion (DWORD ProcessId)
718 {
719 DWORD Version = 0;
720 PIMAGE_NT_HEADERS NtHeader = NULL;
721 PVOID BaseAddress = NULL;
722
723 /* Caller's */
724 if (0 == ProcessId || GetCurrentProcessId() == ProcessId)
725 {
726 BaseAddress = (PVOID) NtCurrentPeb()->ImageBaseAddress;
727 NtHeader = RtlImageNtHeader (BaseAddress);
728 if (NULL != NtHeader)
729 {
730 Version =
731 (NtHeader->OptionalHeader.MajorOperatingSystemVersion << 16) |
732 (NtHeader->OptionalHeader.MinorOperatingSystemVersion);
733 }
734 }
735 else /* other process */
736 {
737 /* FIXME: open the other process */
738 SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
739 }
740 return (Version);
741 }
742
743 /* EOF */