1c81eacc1c177c315ece3f1913f1ce337085614b
[reactos.git] / subsystems / mvdm / ntvdm / dos / dem.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/dos/dem.c
5 * PURPOSE: DOS 32-bit Emulation Support Library -
6 * This library is used by the built-in NTVDM DOS32 and by
7 * the NT 16-bit DOS in Windows (via BOPs). It also exposes
8 * exported functions that can be used by VDDs.
9 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
10 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
11 */
12
13 /* INCLUDES *******************************************************************/
14
15 #include "ntvdm.h"
16
17 #define NDEBUG
18 #include <debug.h>
19
20 #include "emulator.h"
21 #include <isvbop.h>
22
23 #include "utils.h"
24
25 #include "dem.h"
26 #include "dos/dos32krnl/device.h"
27 #include "dos/dos32krnl/memory.h"
28 #include "dos/dos32krnl/process.h"
29 #include "cpu/bop.h"
30 #include "cpu/cpu.h"
31
32 #include "bios/bios.h"
33 #include "mouse32.h"
34
35 #include "vddsup.h"
36
37 /*
38 * EXPERIMENTAL!
39 * Activate this line if you want to have COMMAND.COM completely external.
40 */
41 // #define COMSPEC_FULLY_EXTERNAL
42
43 /* PRIVATE VARIABLES **********************************************************/
44
45 /* PRIVATE FUNCTIONS **********************************************************/
46
47 /* PUBLIC VARIABLES ***********************************************************/
48
49 /* PUBLIC FUNCTIONS ***********************************************************/
50
51
52 /******************************************************************************\
53 |** DOS DEM Kernel helpers **|
54 \******************************************************************************/
55
56
57 VOID Dem_BiosCharPrint(CHAR Character)
58 {
59 /* Save AX and BX */
60 USHORT AX = getAX();
61 USHORT BX = getBX();
62
63 /*
64 * Set the parameters:
65 * AL contains the character to print,
66 * BL contains the character attribute,
67 * BH contains the video page to use.
68 */
69 setAL(Character);
70 setBL(DEFAULT_ATTRIBUTE);
71 setBH(Bda->VideoPage);
72
73 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
74 setAH(0x0E);
75 Int32Call(&DosContext, BIOS_VIDEO_INTERRUPT);
76
77 /* Restore AX and BX */
78 setBX(BX);
79 setAX(AX);
80 }
81
82 VOID DosCharPrint(CHAR Character)
83 {
84 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
85 }
86
87
88 static VOID DemLoadNTDOSKernel(VOID)
89 {
90 BOOLEAN Success = FALSE;
91 LPCSTR DosKernelFileName = "ntdos.sys";
92 HANDLE hDosKernel;
93 ULONG ulDosKernelSize = 0;
94
95 DPRINT1("You are loading Windows NT DOS!\n");
96
97 /* Open the DOS kernel file */
98 hDosKernel = FileOpen(DosKernelFileName, &ulDosKernelSize);
99 if (hDosKernel == NULL) goto Quit;
100
101 /*
102 * Attempt to load the DOS kernel into memory.
103 * The segment where to load the DOS kernel is defined
104 * by the DOS BIOS and is found in DI:0000 .
105 */
106 Success = FileLoadByHandle(hDosKernel,
107 REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)),
108 ulDosKernelSize,
109 &ulDosKernelSize);
110
111 DPRINT1("Windows NT DOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
112 DosKernelFileName,
113 (Success ? "succeeded" : "failed"),
114 getDI(), 0x0000,
115 ulDosKernelSize,
116 GetLastError());
117
118 /* Close the DOS kernel file */
119 FileClose(hDosKernel);
120
121 Quit:
122 if (!Success)
123 {
124 /* We failed everything, stop the VDM */
125 BiosDisplayMessage("Windows NT DOS kernel file '%s' loading failed (Error: %u). The VDM will shut down.\n",
126 DosKernelFileName, GetLastError());
127 EmulatorTerminate();
128 return;
129 }
130 }
131
132 static VOID WINAPI DosSystemBop(LPWORD Stack)
133 {
134 /* Get the Function Number and skip it */
135 BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
136 setIP(getIP() + 1);
137
138 switch (FuncNum)
139 {
140 /* Load the DOS kernel */
141 case 0x11:
142 {
143 DemLoadNTDOSKernel();
144 break;
145 }
146
147 /* Call 32-bit Driver Strategy Routine */
148 case BOP_DRV_STRATEGY:
149 {
150 DeviceStrategyBop();
151 break;
152 }
153
154 /* Call 32-bit Driver Interrupt Routine */
155 case BOP_DRV_INTERRUPT:
156 {
157 DeviceInterruptBop();
158 break;
159 }
160
161 default:
162 {
163 DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum);
164 // setCF(1); // Disable, otherwise we enter an infinite loop
165 break;
166 }
167 }
168 }
169
170
171
172
173 /******************************************************************************\
174 |** DOS Command Process management **|
175 \******************************************************************************/
176
177
178 #ifndef STANDALONE
179 static ULONG SessionId = 0;
180
181 /*
182 * 16-bit Command Interpreter information for DOS reentry
183 */
184 typedef struct _COMSPEC_INFO
185 {
186 LIST_ENTRY Entry;
187 DWORD dwExitCode;
188 WORD ComSpecPsp;
189 BOOLEAN Terminated;
190 } COMSPEC_INFO, *PCOMSPEC_INFO;
191
192 static COMSPEC_INFO RootCmd;
193 static DWORD ReentrancyCount = 0;
194
195 // FIXME: Should we need list locking?
196 static LIST_ENTRY ComSpecInfoList = { &ComSpecInfoList, &ComSpecInfoList };
197
198 static PCOMSPEC_INFO
199 FindComSpecInfoByPsp(WORD Psp)
200 {
201 PLIST_ENTRY Pointer;
202 PCOMSPEC_INFO ComSpecInfo;
203
204 for (Pointer = ComSpecInfoList.Flink; Pointer != &ComSpecInfoList; Pointer = Pointer->Flink)
205 {
206 ComSpecInfo = CONTAINING_RECORD(Pointer, COMSPEC_INFO, Entry);
207 if (ComSpecInfo->ComSpecPsp == Psp) return ComSpecInfo;
208 }
209
210 return NULL;
211 }
212
213 static VOID
214 InsertComSpecInfo(PCOMSPEC_INFO ComSpecInfo)
215 {
216 InsertHeadList(&ComSpecInfoList, &ComSpecInfo->Entry);
217 }
218
219 static VOID
220 RemoveComSpecInfo(PCOMSPEC_INFO ComSpecInfo)
221 {
222 RemoveEntryList(&ComSpecInfo->Entry);
223 if (ComSpecInfo != &RootCmd)
224 RtlFreeHeap(RtlGetProcessHeap(), 0, ComSpecInfo);
225 }
226 #endif
227
228 static VOID DosProcessConsoleAttach(VOID)
229 {
230 /* Attach to the console */
231 ConsoleAttach();
232 VidBiosAttachToConsole();
233 }
234
235 static VOID DosProcessConsoleDetach(VOID)
236 {
237 /* Detach from the console */
238 VidBiosDetachFromConsole();
239 ConsoleDetach();
240 }
241
242 /*
243 * Data for the next DOS command to run
244 */
245 #ifndef STANDALONE
246 static VDM_COMMAND_INFO CommandInfo;
247 static BOOLEAN Repeat = FALSE;
248 static BOOLEAN Reentry = FALSE;
249 #endif
250 static BOOLEAN First = TRUE;
251 static CHAR CmdLine[MAX_PATH] = ""; // DOS_CMDLINE_LENGTH
252 static CHAR AppName[MAX_PATH] = "";
253 #ifndef STANDALONE
254 static CHAR PifFile[MAX_PATH] = "";
255 static CHAR CurDirectory[MAX_PATH] = "";
256 static CHAR Desktop[MAX_PATH] = "";
257 static CHAR Title[MAX_PATH] = "";
258 static ULONG EnvSize = 256;
259 static PVOID Env = NULL;
260 #endif
261
262 #pragma pack(push, 2)
263
264 /*
265 * This structure is compatible with Windows NT DOS
266 */
267 typedef struct _NEXT_CMD
268 {
269 USHORT EnvBlockSeg;
270 USHORT EnvBlockLen;
271 USHORT CurDrive;
272 USHORT NumDrives;
273 USHORT CmdLineSeg;
274 USHORT CmdLineOff;
275 USHORT Unknown0;
276 USHORT ExitCode;
277 USHORT Unknown1;
278 ULONG Unknown2;
279 USHORT CodePage;
280 USHORT Unknown3;
281 USHORT Unknown4;
282 USHORT AppNameSeg;
283 USHORT AppNameOff;
284 USHORT AppNameLen;
285 USHORT Flags;
286 } NEXT_CMD, *PNEXT_CMD;
287
288 #pragma pack(pop)
289
290 static VOID CmdStartProcess(VOID)
291 {
292 #ifndef STANDALONE
293 PCOMSPEC_INFO ComSpecInfo;
294 #endif
295 SIZE_T CmdLen;
296 PNEXT_CMD DataStruct = (PNEXT_CMD)SEG_OFF_TO_PTR(getDS(), getDX());
297
298 DPRINT1("CmdStartProcess -- DS:DX = %04X:%04X (DataStruct = 0x%p)\n",
299 getDS(), getDX(), DataStruct);
300
301 /* Pause the VM */
302 EmulatorPause();
303
304 #ifndef STANDALONE
305 /* Check whether we need to shell out now in case we were started by a 32-bit app */
306 ComSpecInfo = FindComSpecInfoByPsp(Sda->CurrentPsp);
307 if (ComSpecInfo && ComSpecInfo->Terminated)
308 {
309 RemoveComSpecInfo(ComSpecInfo);
310
311 DPRINT1("Exit DOS from start-app BOP\n");
312 setCF(1);
313 goto Quit;
314 }
315
316 /* Clear the structure */
317 RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
318
319 /* Initialize the structure members */
320 CommandInfo.TaskId = SessionId;
321 CommandInfo.VDMState = VDM_FLAG_DOS;
322 CommandInfo.CmdLine = CmdLine;
323 CommandInfo.CmdLen = sizeof(CmdLine);
324 CommandInfo.AppName = AppName;
325 CommandInfo.AppLen = sizeof(AppName);
326 CommandInfo.PifFile = PifFile;
327 CommandInfo.PifLen = sizeof(PifFile);
328 CommandInfo.CurDirectory = CurDirectory;
329 CommandInfo.CurDirectoryLen = sizeof(CurDirectory);
330 CommandInfo.Desktop = Desktop;
331 CommandInfo.DesktopLen = sizeof(Desktop);
332 CommandInfo.Title = Title;
333 CommandInfo.TitleLen = sizeof(Title);
334 CommandInfo.Env = Env;
335 CommandInfo.EnvLen = EnvSize;
336
337 if (First) CommandInfo.VDMState |= VDM_FLAG_FIRST_TASK;
338
339 Command:
340
341 if (Repeat) CommandInfo.VDMState |= VDM_FLAG_RETRY;
342 Repeat = FALSE;
343
344 /* Get the VDM command information */
345 DPRINT1("Calling GetNextVDMCommand in CmdStartProcess: wait for new VDM task...\n");
346 if (!GetNextVDMCommand(&CommandInfo))
347 {
348 DPRINT1("CmdStartProcess - GetNextVDMCommand failed, retrying... last error = %d\n", GetLastError());
349 if (CommandInfo.EnvLen > EnvSize)
350 {
351 /* Expand the environment size */
352 EnvSize = CommandInfo.EnvLen;
353 CommandInfo.Env = Env = RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, Env, EnvSize);
354
355 /* Repeat the request */
356 Repeat = TRUE;
357 goto Command;
358 }
359
360 /* Shouldn't happen */
361 DisplayMessage(L"An unrecoverable failure happened from start-app BOP; exiting DOS.");
362 setCF(1);
363 goto Quit;
364 }
365
366 // FIXME: What happens if some other 32-bit app is killed while we are waiting there??
367
368 DPRINT1("CmdStartProcess - GetNextVDMCommand succeeded, start app...\n");
369
370 #else
371
372 if (!First)
373 {
374 DPRINT1("Exit DOS from start-app BOP\n");
375 setCF(1);
376 goto Quit;
377 }
378
379 #endif
380
381 /* Compute the command line length, not counting the terminating "\r\n" */
382 CmdLen = strlen(CmdLine);
383 if (CmdLen >= 2 && CmdLine[CmdLen - 2] == '\r')
384 CmdLen -= 2;
385
386 DPRINT1("Starting '%s' ('%.*s')...\n", AppName, CmdLen, CmdLine);
387
388 /* Start the process */
389 // FIXME: Merge 'Env' with the master environment SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0)
390 // FIXME: Environment
391 RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct->AppNameSeg, DataStruct->AppNameOff), AppName, MAX_PATH);
392 *(PBYTE)(SEG_OFF_TO_PTR(DataStruct->CmdLineSeg, DataStruct->CmdLineOff)) = (BYTE)CmdLen;
393 RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct->CmdLineSeg, DataStruct->CmdLineOff + 1), CmdLine, DOS_CMDLINE_LENGTH);
394
395 #ifndef STANDALONE
396 /* Update console title if we run in a separate console */
397 if (SessionId != 0)
398 SetConsoleTitleA(AppName);
399 #endif
400
401 First = FALSE;
402 setCF(0);
403
404 DPRINT1("App started!\n");
405
406 Quit:
407 /* Resume the VM */
408 EmulatorResume();
409 }
410
411 static VOID CmdStartExternalCommand(VOID)
412 {
413 DWORD Result;
414
415 // TODO: improve: this code has strong similarities
416 // with the 'default' case of DosCreateProcess.
417
418 LPSTR Command = (LPSTR)SEG_OFF_TO_PTR(getDS(), getSI());
419 CHAR CmdLine[sizeof("cmd.exe /c ") + DOS_CMDLINE_LENGTH + 1] = "";
420 LPSTR CmdLinePtr;
421 ULONG CmdLineLen;
422
423 /* Spawn a user-defined 32-bit command preprocessor */
424
425 // FIXME: Use COMSPEC env var!!
426 CmdLinePtr = CmdLine;
427 strcpy(CmdLinePtr, "cmd.exe /c ");
428 CmdLinePtr += strlen(CmdLinePtr);
429
430 /* Build a Win32-compatible command-line */
431 CmdLineLen = min(strlen(Command), sizeof(CmdLine) - strlen(CmdLinePtr) - 1);
432 RtlCopyMemory(CmdLinePtr, Command, CmdLineLen);
433 CmdLinePtr[CmdLineLen] = '\0';
434
435 /* Remove any trailing return carriage character and NULL-terminate the command line */
436 while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
437 *CmdLinePtr = '\0';
438
439 DPRINT1("CMD Run Command '%s' ('%s')\n", Command, CmdLine);
440
441 /*
442 * No need to prepare the stack for DosStartComSpec since we won't start it.
443 */
444 Result = DosStartProcess32(Command, CmdLine,
445 SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
446 MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
447 FALSE);
448 if (Result != ERROR_SUCCESS)
449 {
450 DosDisplayMessage("Failed to start command '%s' ('%s'). Error: %u\n", Command, CmdLine, Result);
451 setCF(0);
452 setAL((UCHAR)Result);
453 }
454 else
455 {
456 DosDisplayMessage("Command '%s' ('%s') started successfully.\n", Command, CmdLine);
457 #ifndef STANDALONE
458 setCF(Repeat); // Set CF if we need to start a 16-bit process
459 #else
460 setCF(0);
461 #endif
462 }
463 }
464
465 static VOID CmdStartComSpec32(VOID)
466 {
467 DWORD Result;
468
469 // TODO: improve: this code has strong similarities with the
470 // 'default' case of DosCreateProcess and with the 'case 0x08'.
471
472 CHAR CmdLine[sizeof("cmd.exe") + 1] = "";
473
474 /* Spawn a user-defined 32-bit command preprocessor */
475
476 // FIXME: Use COMSPEC env var!!
477 strcpy(CmdLine, "cmd.exe");
478
479 DPRINT1("CMD Run 32-bit Command Interpreter '%s'\n", CmdLine);
480
481 /*
482 * No need to prepare the stack for DosStartComSpec since we won't start it.
483 */
484 Result = DosStartProcess32(CmdLine, CmdLine,
485 SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
486 MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
487 FALSE);
488 if (Result != ERROR_SUCCESS)
489 {
490 DosDisplayMessage("Failed to start 32-bit Command Interpreter '%s'. Error: %u\n", CmdLine, Result);
491 setCF(0);
492 setAL((UCHAR)Result);
493 }
494 else
495 {
496 DosDisplayMessage("32-bit Command Interpreter '%s' started successfully.\n", CmdLine);
497 #ifndef STANDALONE
498 setCF(Repeat); // Set CF if we need to start a 16-bit process
499 #else
500 setCF(0);
501 #endif
502 }
503 }
504
505 static VOID CmdSetExitCode(VOID)
506 {
507 #ifndef STANDALONE
508 BOOL Success;
509 PCOMSPEC_INFO ComSpecInfo;
510 VDM_COMMAND_INFO CommandInfo;
511 #endif
512
513 /* Pause the VM */
514 EmulatorPause();
515
516 #ifndef STANDALONE
517 /*
518 * Check whether we need to shell out now in case we were started by a 32-bit app,
519 * or we were started alone along with the root 32-bit app.
520 */
521 ComSpecInfo = FindComSpecInfoByPsp(Sda->CurrentPsp);
522 if ((ComSpecInfo && ComSpecInfo->Terminated) ||
523 (ComSpecInfo == &RootCmd && SessionId != 0))
524 {
525 RemoveComSpecInfo(ComSpecInfo);
526 #endif
527 DPRINT1("Exit DOS from ExitCode (prologue)!\n");
528 setCF(0);
529 goto Quit;
530 #ifndef STANDALONE
531 }
532
533 /* Clear the VDM structure */
534 RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
535
536 Retry:
537 /* Update the VDM state of the task */
538 // CommandInfo.TaskId = SessionId;
539 CommandInfo.ExitCode = getDX();
540 CommandInfo.VDMState = VDM_FLAG_DONT_WAIT;
541 DPRINT1("Calling GetNextVDMCommand 32bit end of VDM task\n");
542 Success = GetNextVDMCommand(&CommandInfo);
543 DPRINT1("GetNextVDMCommand 32bit end of VDM task success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
544
545 /*
546 * Check whether we were awaited because the 32-bit process was stopped,
547 * or because it started a new DOS application.
548 */
549 if (CommandInfo.CmdLen != 0 || CommandInfo.AppLen != 0 || CommandInfo.PifLen != 0)
550 {
551 DPRINT1("GetNextVDMCommand end-of-app, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
552 CommandInfo.CmdLen, CommandInfo.AppLen, CommandInfo.PifLen);
553
554 /* Repeat the request */
555 Repeat = TRUE;
556 setCF(1);
557 }
558 else
559 {
560 DPRINT1("GetNextVDMCommand end-of-app, the app stopped\n");
561
562 /* Check whether we need to shell out now in case we were started by a 32-bit app */
563 ComSpecInfo = FindComSpecInfoByPsp(Sda->CurrentPsp);
564 if (!ComSpecInfo || !ComSpecInfo->Terminated)
565 {
566 DPRINT1("Not our 32-bit app, retrying...\n");
567 goto Retry;
568 }
569
570 ASSERT(ComSpecInfo->Terminated == TRUE);
571
572 /* Record found, remove it and exit now */
573 RemoveComSpecInfo(ComSpecInfo);
574
575 DPRINT1("Exit DOS from ExitCode wait!\n");
576 setCF(0);
577 }
578 #endif
579
580 // FIXME: Use the retrieved exit code as the value of our exit code
581 // when COMMAND.COM will shell-out ??
582
583 Quit:
584 /* Resume the VM */
585 EmulatorResume();
586 }
587
588 static VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
589 {
590 /* Get the Function Number and skip it */
591 BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
592 setIP(getIP() + 1);
593
594 switch (FuncNum)
595 {
596 /* Kill the VDM */
597 case 0x00:
598 {
599 /* Stop the VDM */
600 EmulatorTerminate();
601 return;
602 }
603
604 /*
605 * Get a new app to start
606 *
607 * Input
608 * DS:DX : Data block.
609 *
610 * Output
611 * CF : 0: Success; 1: Failure.
612 */
613 case 0x01:
614 {
615 CmdStartProcess();
616 break;
617 }
618
619 /*
620 * Check binary format
621 *
622 * Input
623 * DS:DX : Program to check.
624 *
625 * Output
626 * CF : 0: Success; 1: Failure.
627 * AX : Error code.
628 */
629 case 0x07:
630 {
631 DWORD BinaryType;
632 LPSTR ProgramName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
633
634 if (!GetBinaryTypeA(ProgramName, &BinaryType))
635 {
636 /* An error happened, bail out */
637 setCF(1);
638 setAX(LOWORD(GetLastError()));
639 break;
640 }
641
642 // FIXME: We only support DOS binaries for now...
643 ASSERT(BinaryType == SCS_DOS_BINARY);
644 if (BinaryType != SCS_DOS_BINARY)
645 {
646 /* An error happened, bail out */
647 setCF(1);
648 setAX(LOWORD(ERROR_BAD_EXE_FORMAT));
649 break;
650 }
651
652 /* Return success: DOS application */
653 setCF(0);
654 break;
655 }
656
657 /*
658 * Start an external command
659 *
660 * Input
661 * DS:SI : Command to start.
662 * ES : Environment block segment.
663 * AL : Current drive number.
664 * AH : 0: Directly start the command;
665 * 1: Use "cmd.exe /c" to start the command.
666 *
667 * Output
668 * CF : 0: Shell-out; 1: Continue.
669 * AL : Error/Exit code.
670 */
671 case 0x08:
672 {
673 CmdStartExternalCommand();
674 break;
675 }
676
677 /*
678 * Start the default 32-bit command interpreter (COMSPEC)
679 *
680 * Input
681 * ES : Environment block segment.
682 * AL : Current drive number.
683 *
684 * Output
685 * CF : 0: Shell-out; 1: Continue.
686 * AL : Error/Exit code.
687 */
688 case 0x0A:
689 {
690 CmdStartComSpec32();
691 break;
692 }
693
694 /*
695 * Set exit code
696 *
697 * Input
698 * DX : Exit code
699 *
700 * Output
701 * CF : 0: Shell-out; 1: Continue.
702 */
703 case 0x0B:
704 {
705 CmdSetExitCode();
706 break;
707 }
708
709 /*
710 * Get start information
711 *
712 * Output
713 * AL : 0 (resp. 1): Started from (resp. without) an existing console.
714 */
715 case 0x10:
716 {
717 #ifndef STANDALONE
718 /*
719 * When a new instance of our (internal) COMMAND.COM is started,
720 * we check whether we need to run a 32-bit COMSPEC. This goes by
721 * checking whether we were started in a new console (no parent
722 * console process) or from an existing one.
723 *
724 * However COMMAND.COM can also be started in the case where a
725 * 32-bit process (started by a 16-bit parent) wants to start a new
726 * 16-bit process: to ensure DOS reentry we need to start a new
727 * instance of COMMAND.COM. On Windows the COMMAND.COM is started
728 * just before the 32-bit process (in fact, it is this COMMAND.COM
729 * which starts the 32-bit process via an undocumented command-line
730 * switch '/z', which syntax is:
731 * COMMAND.COM /z\bAPPNAME.EXE
732 * notice the '\b' character inserted in-between. Then COMMAND.COM
733 * issues a BOP_CMD 08h with AH=00h to start the process).
734 *
735 * Instead, we do the reverse, i.e. we start the 32-bit process,
736 * and *only* if needed, i.e. if this process wants to start a
737 * new 16-bit process, we start our COMMAND.COM.
738 *
739 * The problem we then face is that our COMMAND.COM will possibly
740 * want to start a new COMSPEC, however we do not want this.
741 * The chosen solution is to flag this case -- done with the 'Reentry'
742 * boolean -- so that COMMAND.COM will not attempt to start COMSPEC
743 * but instead will directly try to start the 16-bit process.
744 */
745 // setAL(SessionId != 0);
746 setAL((SessionId != 0) && !Reentry);
747 /* Reset 'Reentry' */
748 Reentry = FALSE;
749 #else
750 setAL(0);
751 #endif
752 break;
753 }
754
755 default:
756 {
757 DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum);
758 // setCF(1); // Disable, otherwise we enter an infinite loop
759 break;
760 }
761 }
762 }
763
764 #ifndef COMSPEC_FULLY_EXTERNAL
765 /*
766 * Internal COMMAND.COM binary data in the CommandCom array.
767 */
768 #include "command_com.h"
769 #endif
770
771 static
772 DWORD DosStartComSpec(IN BOOLEAN Permanent,
773 IN LPCSTR Environment OPTIONAL,
774 IN DWORD ReturnAddress OPTIONAL,
775 OUT PWORD ComSpecPsp OPTIONAL)
776 {
777 /*
778 * BOOLEAN Permanent
779 * TRUE to simulate the /P switch of command.com: starts AUTOEXEC.BAT/NT
780 * and makes the interpreter permanent (cannot exit).
781 */
782
783 DWORD Result;
784
785 if (ComSpecPsp) *ComSpecPsp = 0;
786
787 Result =
788 #ifndef COMSPEC_FULLY_EXTERNAL
789 DosLoadExecutableInternal(DOS_LOAD_AND_EXECUTE,
790 CommandCom,
791 sizeof(CommandCom),
792 "COMMAND.COM",
793 #else
794 DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
795 #ifndef STANDALONE // FIXME: Those values are hardcoded paths on my local test machines!!
796 "C:\\CMDCMD.COM",
797 #else
798 "H:\\DOS_tests\\CMDCMD.COM",
799 #endif // STANDALONE
800 #endif // COMSPEC_FULLY_EXTERNAL
801 NULL,
802 Permanent ? "/P" : "",
803 Environment ? Environment : "", // FIXME: Default environment!
804 ReturnAddress);
805 if (Result != ERROR_SUCCESS) return Result;
806
807 /* TODO: Read AUTOEXEC.NT/BAT */
808
809 /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
810 if (ComSpecPsp) *ComSpecPsp = Sda->CurrentPsp;
811
812 return Result;
813 }
814
815 typedef struct _DOS_START_PROC32
816 {
817 LPSTR ExecutablePath;
818 LPSTR CommandLine;
819 LPSTR Environment OPTIONAL;
820 #ifndef STANDALONE
821 PCOMSPEC_INFO ComSpecInfo;
822 HANDLE hEvent;
823 #endif
824 } DOS_START_PROC32, *PDOS_START_PROC32;
825
826 static DWORD
827 WINAPI
828 CommandThreadProc(LPVOID Parameter)
829 {
830 BOOL Success;
831 PROCESS_INFORMATION ProcessInfo;
832 STARTUPINFOA StartupInfo;
833 DWORD dwExitCode;
834 PDOS_START_PROC32 DosStartProc32 = (PDOS_START_PROC32)Parameter;
835 #ifndef STANDALONE
836 VDM_COMMAND_INFO CommandInfo;
837 PCOMSPEC_INFO ComSpecInfo = DosStartProc32->ComSpecInfo;
838 #endif
839
840 /* Set up the VDM, startup and process info structures */
841 #ifndef STANDALONE
842 RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
843 #endif
844 RtlZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
845 RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
846 StartupInfo.cb = sizeof(StartupInfo);
847
848 // FIXME: Build suitable 32-bit environment!!
849
850 #ifndef STANDALONE
851 /*
852 * Wait for signaling a new VDM task and increment the VDM re-entry count so
853 * that we can handle 16-bit apps that may be possibly started by the 32-bit app.
854 */
855 CommandInfo.VDMState = VDM_INC_REENTER_COUNT;
856 DPRINT1("Calling GetNextVDMCommand reenter++\n");
857 Success = GetNextVDMCommand(&CommandInfo);
858 DPRINT1("GetNextVDMCommand reenter++ success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
859 ++ReentrancyCount;
860 #endif
861
862 /* Start the process */
863 Success = CreateProcessA(NULL, // ProgramName,
864 DosStartProc32->CommandLine,
865 NULL,
866 NULL,
867 TRUE, // Inherit handles
868 CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED,
869 DosStartProc32->Environment,
870 NULL, // lpCurrentDirectory, see "START" command in cmd.exe
871 &StartupInfo,
872 &ProcessInfo);
873
874 #ifndef STANDALONE
875 /* Signal our caller the process was started */
876 SetEvent(DosStartProc32->hEvent);
877 // After this point, 'DosStartProc32' is not valid anymore.
878 #endif
879
880 if (Success)
881 {
882 /* Resume the process */
883 ResumeThread(ProcessInfo.hThread);
884
885 /* Wait for the process to finish running and retrieve its exit code */
886 WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
887 GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode);
888
889 /* Close the handles */
890 CloseHandle(ProcessInfo.hThread);
891 CloseHandle(ProcessInfo.hProcess);
892 }
893 else
894 {
895 dwExitCode = GetLastError();
896 }
897
898 #ifndef STANDALONE
899 ASSERT(ComSpecInfo);
900 ComSpecInfo->Terminated = TRUE;
901 ComSpecInfo->dwExitCode = dwExitCode;
902
903 /* Decrement the VDM re-entry count */
904 CommandInfo.VDMState = VDM_DEC_REENTER_COUNT;
905 DPRINT1("Calling GetNextVDMCommand reenter--\n");
906 Success = GetNextVDMCommand(&CommandInfo);
907 DPRINT1("GetNextVDMCommand reenter-- success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
908 --ReentrancyCount;
909
910 return 0;
911 #else
912 return dwExitCode;
913 #endif
914 }
915
916 DWORD DosStartProcess32(IN LPCSTR ExecutablePath,
917 IN LPCSTR CommandLine,
918 IN LPCSTR Environment OPTIONAL,
919 IN DWORD ReturnAddress OPTIONAL,
920 IN BOOLEAN StartComSpec)
921 {
922 DWORD Result = ERROR_SUCCESS;
923 HANDLE CommandThread;
924 DOS_START_PROC32 DosStartProc32;
925 #ifndef STANDALONE
926 BOOL Success;
927 VDM_COMMAND_INFO CommandInfo;
928 #endif
929
930 DosStartProc32.ExecutablePath = (LPSTR)ExecutablePath;
931 DosStartProc32.CommandLine = (LPSTR)CommandLine;
932 DosStartProc32.Environment = (LPSTR)Environment;
933
934 #ifndef STANDALONE
935 DosStartProc32.ComSpecInfo =
936 RtlAllocateHeap(RtlGetProcessHeap(),
937 HEAP_ZERO_MEMORY,
938 sizeof(*DosStartProc32.ComSpecInfo));
939 ASSERT(DosStartProc32.ComSpecInfo);
940
941 DosStartProc32.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
942 ASSERT(DosStartProc32.hEvent);
943 #endif
944
945 /* Pause the VM and detach from the console */
946 EmulatorPause();
947 DosProcessConsoleDetach();
948
949 /* Start the 32-bit process via another thread */
950 CommandThread = CreateThread(NULL, 0, &CommandThreadProc, &DosStartProc32, 0, NULL);
951 if (CommandThread == NULL)
952 {
953 DisplayMessage(L"FATAL: Failed to create the command processing thread: %d", GetLastError());
954 Result = GetLastError();
955 goto Quit;
956 }
957
958 #ifndef STANDALONE
959 /* Close the thread handle */
960 CloseHandle(CommandThread);
961
962 /* Wait for the process to be ready to start */
963 WaitForSingleObject(DosStartProc32.hEvent, INFINITE);
964
965 /* Wait for any potential new DOS app started by the 32-bit process */
966 RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
967
968 Retry:
969 CommandInfo.VDMState = VDM_FLAG_NESTED_TASK | VDM_FLAG_DONT_WAIT;
970 DPRINT1("Calling GetNextVDMCommand 32bit for possible new VDM task...\n");
971 Success = GetNextVDMCommand(&CommandInfo);
972 DPRINT1("GetNextVDMCommand 32bit awaited, success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
973
974 /*
975 * Check whether we were awaited because the 32-bit process was stopped,
976 * or because it started a new DOS application.
977 */
978 if (CommandInfo.CmdLen != 0 || CommandInfo.AppLen != 0 || CommandInfo.PifLen != 0)
979 {
980 DPRINT1("GetNextVDMCommand 32bit, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
981 CommandInfo.CmdLen, CommandInfo.AppLen, CommandInfo.PifLen);
982
983 /* Repeat the request */
984 Repeat = TRUE;
985
986 /*
987 * Set 'Reentry' to TRUE or FALSE depending on whether we are going
988 * to reenter with a new COMMAND.COM. See the comment for:
989 * BOP_CMD 0x10 'Get start information'
990 * (dem.c!DosCmdInterpreterBop) for more details.
991 */
992 Reentry = StartComSpec;
993
994 /* If needed, start a new command interpreter to handle the possible incoming DOS commands */
995 if (StartComSpec)
996 {
997 //
998 // DosStartProcess32 was only called by DosCreateProcess, called from INT 21h,
999 // so the caller stack is already prepared for running a new DOS program
1000 // (Flags, CS and IP, and the extra interrupt number, are already pushed).
1001 //
1002 Result = DosStartComSpec(FALSE, Environment, ReturnAddress,
1003 &DosStartProc32.ComSpecInfo->ComSpecPsp);
1004 if (Result != ERROR_SUCCESS)
1005 {
1006 DosDisplayMessage("Failed to start a new Command Interpreter (Error: %u).\n", Result);
1007 goto Quit;
1008 }
1009 }
1010 else
1011 {
1012 /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
1013 DosStartProc32.ComSpecInfo->ComSpecPsp = Sda->CurrentPsp;
1014 Result = ERROR_SUCCESS;
1015 }
1016
1017 /* Insert the new entry in the list; it will be freed when needed by COMMAND.COM */
1018 InsertComSpecInfo(DosStartProc32.ComSpecInfo);
1019 }
1020 else
1021 {
1022 DPRINT1("GetNextVDMCommand 32bit, 32-bit app stopped\n");
1023
1024 /* Check whether this was our 32-bit app which was killed */
1025 if (!DosStartProc32.ComSpecInfo->Terminated)
1026 {
1027 DPRINT1("Not our 32-bit app, retrying...\n");
1028 goto Retry;
1029 }
1030
1031 Result = DosStartProc32.ComSpecInfo->dwExitCode;
1032
1033 /* Delete the entry */
1034 RtlFreeHeap(RtlGetProcessHeap(), 0, DosStartProc32.ComSpecInfo);
1035 }
1036 #else
1037 /* Wait for the thread to finish */
1038 WaitForSingleObject(CommandThread, INFINITE);
1039 GetExitCodeThread(CommandThread, &Result);
1040
1041 /* Close the thread handle */
1042 CloseHandle(CommandThread);
1043
1044 DPRINT1("32-bit app stopped\n");
1045 #endif
1046
1047 Quit:
1048 #ifndef STANDALONE
1049 CloseHandle(DosStartProc32.hEvent);
1050 #endif
1051
1052 /* Attach to the console and resume the VM */
1053 DosProcessConsoleAttach();
1054 EmulatorResume();
1055
1056 return Result;
1057 }
1058
1059
1060
1061
1062 /******************************************************************************\
1063 |** DOS Bootloader emulation, Startup and Shutdown **|
1064 \******************************************************************************/
1065
1066
1067 //
1068 // This function (equivalent of the DOS bootsector) is called by the bootstrap
1069 // loader *BEFORE* jumping at 0000:7C00. What we should do is to write at 0000:7C00
1070 // a BOP call that calls DosInitialize back. Then the bootstrap loader jumps at
1071 // 0000:7C00, our BOP gets called and then we can initialize the 32-bit part of the DOS.
1072 //
1073
1074 /* 16-bit bootstrap code at 0000:7C00 */
1075 /* Of course, this is not in real bootsector format, because we don't care about it for now */
1076 static BYTE Bootsector1[] =
1077 {
1078 LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_LOAD_DOS
1079 };
1080 /* This portion of code is run if we failed to load the DOS */
1081 // NOTE: This may also be done by the BIOS32.
1082 static BYTE Bootsector2[] =
1083 {
1084 LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_UNSIMULATE
1085 };
1086
1087 static VOID WINAPI DosInitialize(LPWORD Stack);
1088
1089 VOID DosBootsectorInitialize(VOID)
1090 {
1091 /* We write the bootsector at 0000:7C00 */
1092 ULONG_PTR StartAddress = (ULONG_PTR)SEG_OFF_TO_PTR(0x0000, 0x7C00);
1093 ULONG_PTR Address = StartAddress;
1094 CHAR DosKernelFileName[] = ""; // No DOS BIOS file name, therefore we will load DOS32
1095
1096 DPRINT("DosBootsectorInitialize\n");
1097
1098 /* Write the "bootsector" */
1099 RtlCopyMemory((PVOID)Address, Bootsector1, sizeof(Bootsector1));
1100 Address += sizeof(Bootsector1);
1101 RtlCopyMemory((PVOID)Address, DosKernelFileName, sizeof(DosKernelFileName));
1102 Address += sizeof(DosKernelFileName);
1103 RtlCopyMemory((PVOID)Address, Bootsector2, sizeof(Bootsector2));
1104 Address += sizeof(Bootsector2);
1105
1106 /* Initialize the callback context */
1107 InitializeContext(&DosContext, 0x0000,
1108 (ULONG_PTR)MEM_ALIGN_UP(0x7C00 + Address - StartAddress, sizeof(WORD)));
1109
1110 /* Register the DOS Loading BOP */
1111 RegisterBop(BOP_LOAD_DOS, DosInitialize);
1112 }
1113
1114
1115 //
1116 // This function is called by the DOS bootsector in case we load DOS32.
1117 // It sets up the DOS32 start code then jumps to 0070:0000.
1118 //
1119
1120 /* 16-bit startup code for DOS32 at 0070:0000 */
1121 static BYTE Startup[] =
1122 {
1123 LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_START_DOS,
1124 LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_UNSIMULATE
1125 };
1126
1127 static VOID WINAPI DosStart(LPWORD Stack);
1128
1129 static VOID WINAPI DosInitialize(LPWORD Stack)
1130 {
1131 /* Get the DOS BIOS file name (NULL-terminated) */
1132 // FIXME: Isn't it possible to use some DS:SI instead??
1133 LPCSTR DosBiosFileName = (LPCSTR)SEG_OFF_TO_PTR(getCS(), getIP());
1134 setIP(getIP() + strlen(DosBiosFileName) + 1); // Skip it
1135
1136 DPRINT("DosInitialize('%s')\n", DosBiosFileName);
1137
1138 /*
1139 * We succeeded, deregister the DOS Loading BOP
1140 * so that no app will be able to call us back.
1141 */
1142 RegisterBop(BOP_LOAD_DOS, NULL);
1143
1144 /* Register the DOS BOPs */
1145 RegisterBop(BOP_DOS, DosSystemBop );
1146 RegisterBop(BOP_CMD, DosCmdInterpreterBop);
1147
1148 if (DosBiosFileName[0] != '\0')
1149 {
1150 BOOLEAN Success = FALSE;
1151 HANDLE hDosBios;
1152 ULONG ulDosBiosSize = 0;
1153
1154 /* Open the DOS BIOS file */
1155 hDosBios = FileOpen(DosBiosFileName, &ulDosBiosSize);
1156 if (hDosBios == NULL) goto Quit;
1157
1158 /* Attempt to load the DOS BIOS into memory */
1159 Success = FileLoadByHandle(hDosBios,
1160 REAL_TO_PHYS(TO_LINEAR(0x0070, 0x0000)),
1161 ulDosBiosSize,
1162 &ulDosBiosSize);
1163
1164 DPRINT1("DOS BIOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
1165 DosBiosFileName,
1166 (Success ? "succeeded" : "failed"),
1167 0x0070, 0x0000,
1168 ulDosBiosSize,
1169 GetLastError());
1170
1171 /* Close the DOS BIOS file */
1172 FileClose(hDosBios);
1173
1174 Quit:
1175 if (!Success)
1176 {
1177 BiosDisplayMessage("DOS BIOS file '%s' loading failed (Error: %u). The VDM will shut down.\n",
1178 DosBiosFileName, GetLastError());
1179 return;
1180 }
1181 }
1182 else
1183 {
1184 /* Load the 16-bit startup code for DOS32 and register its Starting BOP */
1185 RtlCopyMemory(SEG_OFF_TO_PTR(0x0070, 0x0000), Startup, sizeof(Startup));
1186
1187 // This is the equivalent of BOP_LOAD_DOS, function 0x11 "Load the DOS kernel"
1188 // for the Windows NT DOS.
1189 RegisterBop(BOP_START_DOS, DosStart);
1190 }
1191
1192 /* Position execution pointers for DOS startup and return */
1193 setCS(0x0070);
1194 setIP(0x0000);
1195 }
1196
1197 static VOID WINAPI DosStart(LPWORD Stack)
1198 {
1199 BOOLEAN Success;
1200 DWORD Result;
1201 #ifndef STANDALONE
1202 INT i;
1203 #endif
1204
1205 DPRINT("DosStart\n");
1206
1207 /*
1208 * We succeeded, deregister the DOS Starting BOP
1209 * so that no app will be able to call us back.
1210 */
1211 RegisterBop(BOP_START_DOS, NULL);
1212
1213 /* Initialize the callback context */
1214 InitializeContext(&DosContext, BIOS_CODE_SEGMENT, 0x0010);
1215
1216 Success = DosBIOSInitialize();
1217 // Success &= DosKRNLInitialize();
1218 if (!Success)
1219 {
1220 BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError());
1221 EmulatorTerminate();
1222 return;
1223 }
1224
1225 /* Load the mouse driver */
1226 DosMouseInitialize();
1227
1228 #ifndef STANDALONE
1229
1230 /* Parse the command line arguments */
1231 for (i = 1; i < NtVdmArgc; i++)
1232 {
1233 if (wcsncmp(NtVdmArgv[i], L"-i", 2) == 0)
1234 {
1235 /* This is the session ID (hex format) */
1236 SessionId = wcstoul(NtVdmArgv[i] + 2, NULL, 16);
1237 }
1238 }
1239
1240 /* Initialize Win32-VDM environment */
1241 Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
1242 if (Env == NULL)
1243 {
1244 DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError());
1245 EmulatorTerminate();
1246 return;
1247 }
1248
1249 /* Clear the structure */
1250 RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
1251
1252 /* Get the initial information */
1253 CommandInfo.TaskId = SessionId;
1254 CommandInfo.VDMState = VDM_GET_FIRST_COMMAND | VDM_FLAG_DOS;
1255 GetNextVDMCommand(&CommandInfo);
1256
1257 #else
1258
1259 /* Retrieve the command to start */
1260 if (NtVdmArgc >= 2)
1261 {
1262 WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[1], -1, AppName, sizeof(AppName), NULL, NULL);
1263
1264 if (NtVdmArgc >= 3)
1265 WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[2], -1, CmdLine, sizeof(CmdLine), NULL, NULL);
1266 else
1267 strcpy(CmdLine, "");
1268 }
1269 else
1270 {
1271 DosDisplayMessage("Invalid DOS command line\n");
1272 EmulatorTerminate();
1273 return;
1274 }
1275
1276 #endif
1277
1278 /*
1279 * At this point, CS:IP points to the DOS BIOS exit code. If the
1280 * root command interpreter fails to start (or if it exits), DOS
1281 * exits and the VDM terminates.
1282 */
1283
1284 /* Start the root command interpreter */
1285 // TODO: Really interpret the 'SHELL=' line of CONFIG.NT, and use it!
1286
1287 /*
1288 * Prepare the stack for DosStartComSpec:
1289 * push Flags, CS and IP, and an extra WORD.
1290 */
1291 setSP(getSP() - sizeof(WORD));
1292 *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = (WORD)getEFLAGS();
1293 setSP(getSP() - sizeof(WORD));
1294 *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = getCS();
1295 setSP(getSP() - sizeof(WORD));
1296 *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = getIP();
1297 setSP(getSP() - sizeof(WORD));
1298
1299 Result = DosStartComSpec(TRUE, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0),
1300 MAKELONG(getIP(), getCS()),
1301 #ifndef STANDALONE
1302 &RootCmd.ComSpecPsp
1303 #else
1304 NULL
1305 #endif
1306 );
1307 if (Result != ERROR_SUCCESS)
1308 {
1309 /* Unprepare the stack for DosStartComSpec */
1310 setSP(getSP() + 4*sizeof(WORD));
1311
1312 DosDisplayMessage("Failed to start the Command Interpreter (Error: %u). The VDM will shut down.\n", Result);
1313 EmulatorTerminate();
1314 return;
1315 }
1316
1317 #ifndef STANDALONE
1318 RootCmd.Terminated = FALSE;
1319 InsertComSpecInfo(&RootCmd);
1320 #endif
1321
1322 /**/
1323 /* Attach to the console and resume the VM */
1324 DosProcessConsoleAttach();
1325 EmulatorResume();
1326 /**/
1327
1328 return;
1329 }
1330
1331 BOOLEAN DosShutdown(BOOLEAN Immediate)
1332 {
1333 /*
1334 * Immediate = TRUE: Immediate shutdown;
1335 * FALSE: Delayed shutdown (notification).
1336 */
1337
1338 #ifndef STANDALONE
1339 if (Immediate)
1340 {
1341 ExitVDM(FALSE, 0);
1342 return TRUE;
1343 }
1344 else
1345 {
1346 // HACK!
1347 extern HANDLE VdmTaskEvent; // see emulator.c
1348
1349 /*
1350 * Signal the root COMMAND.COM that it should terminate
1351 * when it checks for a new command.
1352 */
1353 RootCmd.Terminated = TRUE;
1354
1355 /* If the list is already empty, or just contains only one element, bail out */
1356 // FIXME: Question: if the list has only one element, is it ALWAYS RootCmd ??
1357 // FIXME: The following is hackish.
1358 if ((IsListEmpty(&ComSpecInfoList) ||
1359 (ComSpecInfoList.Flink == &RootCmd.Entry &&
1360 ComSpecInfoList.Blink == &RootCmd.Entry)) &&
1361 ReentrancyCount == 0 &&
1362 WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT)
1363 {
1364 /* Nothing runs, so exit immediately */
1365 ExitVDM(FALSE, 0);
1366 return TRUE;
1367 }
1368
1369 return FALSE;
1370 }
1371 #else
1372 UNREFERENCED_PARAMETER(Immediate);
1373 return TRUE;
1374 #endif
1375 }
1376
1377
1378 /* PUBLIC EXPORTED APIS *******************************************************/
1379
1380 // demLFNCleanup
1381 // demLFNGetCurrentDirectory
1382
1383 // demGetFileTimeByHandle_WOW
1384 // demWOWLFNAllocateSearchHandle
1385 // demWOWLFNCloseSearchHandle
1386 // demWOWLFNEntry
1387 // demWOWLFNGetSearchHandle
1388 // demWOWLFNInit
1389
1390 DWORD
1391 WINAPI
1392 demClientErrorEx(IN HANDLE FileHandle,
1393 IN CHAR Unknown,
1394 IN BOOL Flag)
1395 {
1396 UNIMPLEMENTED;
1397 return GetLastError();
1398 }
1399
1400 DWORD
1401 WINAPI
1402 demFileDelete(IN LPCSTR FileName)
1403 {
1404 if (DeleteFileA(FileName)) SetLastError(ERROR_SUCCESS);
1405
1406 return GetLastError();
1407 }
1408
1409 DWORD
1410 WINAPI
1411 demFileFindFirst(OUT PVOID lpFindFileData,
1412 IN LPCSTR FileName,
1413 IN WORD AttribMask)
1414 {
1415 BOOLEAN Success = TRUE;
1416 WIN32_FIND_DATAA FindData;
1417 HANDLE SearchHandle;
1418 PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
1419
1420 /* Start a search */
1421 SearchHandle = FindFirstFileA(FileName, &FindData);
1422 if (SearchHandle == INVALID_HANDLE_VALUE) return GetLastError();
1423
1424 do
1425 {
1426 /* Check the attributes and retry as long as we haven't found a matching file */
1427 if (!((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
1428 FILE_ATTRIBUTE_SYSTEM |
1429 FILE_ATTRIBUTE_DIRECTORY))
1430 & ~AttribMask))
1431 {
1432 break;
1433 }
1434 }
1435 while ((Success = FindNextFileA(SearchHandle, &FindData)));
1436
1437 /* If we failed at some point, close the search and return an error */
1438 if (!Success)
1439 {
1440 FindClose(SearchHandle);
1441 return GetLastError();
1442 }
1443
1444 /* Fill the block */
1445 FindFileBlock->DriveLetter = DosData->Sda.CurrentDrive + 'A';
1446 FindFileBlock->AttribMask = AttribMask;
1447 FindFileBlock->SearchHandle = SearchHandle;
1448 FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
1449 FileTimeToDosDateTime(&FindData.ftLastWriteTime,
1450 &FindFileBlock->FileDate,
1451 &FindFileBlock->FileTime);
1452 FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
1453 : FindData.nFileSizeLow;
1454 /* Build a short path name */
1455 if (*FindData.cAlternateFileName)
1456 strncpy(FindFileBlock->FileName, FindData.cAlternateFileName, sizeof(FindFileBlock->FileName));
1457 else
1458 GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
1459
1460 return ERROR_SUCCESS;
1461 }
1462
1463 DWORD
1464 WINAPI
1465 demFileFindNext(OUT PVOID lpFindFileData)
1466 {
1467 WIN32_FIND_DATAA FindData;
1468 PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
1469
1470 do
1471 {
1472 /* Continue searching as long as we haven't found a matching file */
1473
1474 /* If we failed at some point, close the search and return an error */
1475 if (!FindNextFileA(FindFileBlock->SearchHandle, &FindData))
1476 {
1477 FindClose(FindFileBlock->SearchHandle);
1478 return GetLastError();
1479 }
1480 }
1481 while ((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
1482 FILE_ATTRIBUTE_SYSTEM |
1483 FILE_ATTRIBUTE_DIRECTORY))
1484 & ~FindFileBlock->AttribMask);
1485
1486 /* Update the block */
1487 FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
1488 FileTimeToDosDateTime(&FindData.ftLastWriteTime,
1489 &FindFileBlock->FileDate,
1490 &FindFileBlock->FileTime);
1491 FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
1492 : FindData.nFileSizeLow;
1493 /* Build a short path name */
1494 if (*FindData.cAlternateFileName)
1495 strncpy(FindFileBlock->FileName, FindData.cAlternateFileName, sizeof(FindFileBlock->FileName));
1496 else
1497 GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
1498
1499 return ERROR_SUCCESS;
1500 }
1501
1502 UCHAR
1503 WINAPI
1504 demGetPhysicalDriveType(IN UCHAR DriveNumber)
1505 {
1506 UNIMPLEMENTED;
1507 return DOSDEVICE_DRIVE_UNKNOWN;
1508 }
1509
1510 BOOL
1511 WINAPI
1512 demIsShortPathName(IN LPCSTR Path,
1513 IN BOOL Unknown)
1514 {
1515 UNIMPLEMENTED;
1516 return FALSE;
1517 }
1518
1519 DWORD
1520 WINAPI
1521 demSetCurrentDirectoryGetDrive(IN LPCSTR CurrentDirectory,
1522 OUT PUCHAR DriveNumber)
1523 {
1524 UNIMPLEMENTED;
1525 return ERROR_SUCCESS;
1526 }
1527
1528 /* EOF */