2 * PROJECT: ReactOS Windows-Compatible Session Manager
3 * LICENSE: BSD 2-Clause License
4 * FILE: base/system/smss/smss.c
5 * PURPOSE: Main SMSS Code
6 * PROGRAMMERS: Alex Ionescu
9 /* INCLUDES *******************************************************************/
15 /* GLOBALS ********************************************************************/
17 RTL_CRITICAL_SECTION SmpKnownSubSysLock
;
18 LIST_ENTRY SmpKnownSubSysHead
;
19 HANDLE SmpWindowsSubSysProcess
;
20 HANDLE SmpWindowsSubSysProcessId
;
21 BOOLEAN RegPosixSingleInstance
;
22 WCHAR InitialCommandBuffer
[256];
24 /* FUNCTIONS ******************************************************************/
28 SmpCallCsrCreateProcess(IN PSB_API_MSG SbApiMsg
,
29 IN USHORT MessageLength
,
34 /* Initialize the header and send the message to CSRSS */
35 SbApiMsg
->h
.u2
.ZeroInit
= 0;
36 SbApiMsg
->h
.u1
.s1
.DataLength
= MessageLength
+ 8;
37 SbApiMsg
->h
.u1
.s1
.TotalLength
= sizeof(SB_API_MSG
);
38 SbApiMsg
->ApiNumber
= SbpCreateProcess
;
39 Status
= NtRequestWaitReplyPort(PortHandle
, &SbApiMsg
->h
, &SbApiMsg
->h
);
40 if (NT_SUCCESS(Status
)) Status
= SbApiMsg
->ReturnValue
;
46 SmpDereferenceSubsystem(IN PSMP_SUBSYSTEM SubSystem
)
48 /* Acquire the database lock while we (potentially) destroy this subsystem */
49 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
51 /* Drop the reference and see if it's terminating */
52 if (!(--SubSystem
->ReferenceCount
) && (SubSystem
->Terminating
))
54 /* Close all handles and free it */
55 if (SubSystem
->Event
) NtClose(SubSystem
->Event
);
56 if (SubSystem
->ProcessHandle
) NtClose(SubSystem
->ProcessHandle
);
57 if (SubSystem
->SbApiPort
) NtClose(SubSystem
->SbApiPort
);
58 RtlFreeHeap(SmpHeap
, 0, SubSystem
);
61 /* Release the database lock */
62 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
67 SmpLocateKnownSubSysByCid(IN PCLIENT_ID ClientId
)
69 PSMP_SUBSYSTEM Subsystem
= NULL
;
70 PLIST_ENTRY NextEntry
;
72 /* Lock the subsystem database */
73 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
75 /* Loop each subsystem in the database */
76 NextEntry
= SmpKnownSubSysHead
.Flink
;
77 while (NextEntry
!= &SmpKnownSubSysHead
)
79 /* Check if this one matches the client ID and is still valid */
80 Subsystem
= CONTAINING_RECORD(NextEntry
, SMP_SUBSYSTEM
, Entry
);
81 if ((*(PULONGLONG
)&Subsystem
->ClientId
== *(PULONGLONG
)ClientId
) &&
82 !(Subsystem
->Terminating
))
84 /* Add a reference and return it */
85 Subsystem
->ReferenceCount
++;
89 /* Reset the current pointer and keep earching */
91 NextEntry
= NextEntry
->Flink
;
94 /* Release the lock and return the subsystem we found */
95 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
101 SmpLocateKnownSubSysByType(IN ULONG MuSessionId
,
104 PSMP_SUBSYSTEM Subsystem
= NULL
;
105 PLIST_ENTRY NextEntry
;
107 /* Lock the subsystem database */
108 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
110 /* Loop each subsystem in the database */
111 NextEntry
= SmpKnownSubSysHead
.Flink
;
112 while (NextEntry
!= &SmpKnownSubSysHead
)
114 /* Check if this one matches the image and uID, and is still valid */
115 Subsystem
= CONTAINING_RECORD(NextEntry
, SMP_SUBSYSTEM
, Entry
);
116 if ((Subsystem
->ImageType
== ImageType
) &&
117 !(Subsystem
->Terminating
) &&
118 (Subsystem
->MuSessionId
== MuSessionId
))
120 /* Return it referenced for the caller */
121 Subsystem
->ReferenceCount
++;
125 /* Reset the current pointer and keep earching */
127 NextEntry
= NextEntry
->Flink
;
130 /* Release the lock and return the subsystem we found */
131 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
137 SmpLoadSubSystem(IN PUNICODE_STRING FileName
,
138 IN PUNICODE_STRING Directory
,
139 IN PUNICODE_STRING CommandLine
,
140 IN ULONG MuSessionId
,
141 OUT PHANDLE ProcessId
,
144 PSMP_SUBSYSTEM Subsystem
, NewSubsystem
, KnownSubsystem
= NULL
;
145 HANDLE SubSysProcessId
;
146 NTSTATUS Status
= STATUS_SUCCESS
;
147 SB_API_MSG SbApiMsg
, SbApiMsg2
;
148 RTL_USER_PROCESS_INFORMATION ProcessInformation
;
149 LARGE_INTEGER Timeout
;
151 PSB_CREATE_PROCESS_MSG CreateProcess
= &SbApiMsg
.CreateProcess
;
152 PSB_CREATE_SESSION_MSG CreateSession
= &SbApiMsg
.CreateSession
;
154 /* Make sure this is a found subsystem */
155 if (Flags
& SMP_INVALID_PATH
)
157 DPRINT1("SMSS: Unable to find subsystem - %wZ\n", FileName
);
158 return STATUS_OBJECT_NAME_NOT_FOUND
;
161 /* Don't use a session if the flag is set */
162 if (Flags
& 0x80) MuSessionId
= 0;
164 /* Lock the subsystems while we do a look up */
165 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
168 /* Check if we found a subsystem not yet fully iniitalized */
169 Subsystem
= SmpLocateKnownSubSysByType(MuSessionId
, -1);
170 if (!Subsystem
) break;
171 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
173 /* Wait on it to initialize */
174 NtWaitForSingleObject(Subsystem
->Event
, FALSE
, NULL
);
176 /* Dereference it and try the next one */
177 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
178 SmpDereferenceSubsystem(Subsystem
);
181 /* Check if this is a POSIX subsystem */
182 if (Flags
& SMP_POSIX_FLAG
)
184 /* Do we already have it? */
185 Subsystem
= SmpLocateKnownSubSysByType(MuSessionId
, IMAGE_SUBSYSTEM_POSIX_CUI
);
187 else if (Flags
& SMP_OS2_FLAG
)
189 /* This is an OS/2 subsystem, do we we already have it? */
190 Subsystem
= SmpLocateKnownSubSysByType(MuSessionId
, IMAGE_SUBSYSTEM_OS2_CUI
);
193 /* Check if we already have one of the optional subsystems for the session */
196 /* Dereference and return, no work to do */
197 SmpDereferenceSubsystem(Subsystem
);
198 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
199 return STATUS_SUCCESS
;
202 /* Allocate a new subsystem! */
203 NewSubsystem
= RtlAllocateHeap(SmpHeap
, SmBaseTag
, sizeof(SMP_SUBSYSTEM
));
206 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
207 return STATUS_NO_MEMORY
;
210 /* Initialize its header and reference count */
211 NewSubsystem
->ReferenceCount
= 1;
212 NewSubsystem
->MuSessionId
= MuSessionId
;
213 NewSubsystem
->ImageType
= -1;
215 /* Clear out all the other data for now */
216 NewSubsystem
->Terminating
= FALSE
;
217 NewSubsystem
->ProcessHandle
= NULL
;
218 NewSubsystem
->Event
= NULL
;
219 NewSubsystem
->PortHandle
= NULL
;
220 NewSubsystem
->SbApiPort
= NULL
;
222 /* Create the event we'll be wating on for initialization */
223 Status
= NtCreateEvent(&NewSubsystem
->Event
,
228 if (!NT_SUCCESS(Status
))
230 /* This failed, bail out */
231 RtlFreeHeap(SmpHeap
, 0, NewSubsystem
);
232 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
233 return STATUS_NO_MEMORY
;
236 /* Insert the subsystem and release the lock. It can now be found */
237 InsertTailList(&SmpKnownSubSysHead
, &NewSubsystem
->Entry
);
238 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
240 /* The OS/2 and POSIX subsystems are actually Windows applications! */
241 if (Flags
& (SMP_POSIX_FLAG
| SMP_OS2_FLAG
))
243 /* Locate the Windows subsystem for this session */
244 KnownSubsystem
= SmpLocateKnownSubSysByType(MuSessionId
,
245 IMAGE_SUBSYSTEM_WINDOWS_GUI
);
248 DPRINT1("SMSS: SmpLoadSubSystem - SmpLocateKnownSubSysByType Failed\n");
252 /* Fill out all the process details and call CSRSS to launch it */
253 CreateProcess
->In
.ImageName
= FileName
;
254 CreateProcess
->In
.CurrentDirectory
= Directory
;
255 CreateProcess
->In
.CommandLine
= CommandLine
;
256 CreateProcess
->In
.DllPath
= SmpDefaultLibPath
.Length
?
257 &SmpDefaultLibPath
: NULL
;
258 CreateProcess
->In
.Flags
= Flags
| SMP_DEFERRED_FLAG
;
259 CreateProcess
->In
.DebugFlags
= SmpDebug
;
260 Status
= SmpCallCsrCreateProcess(&SbApiMsg
,
261 sizeof(*CreateProcess
),
262 KnownSubsystem
->SbApiPort
);
263 if (!NT_SUCCESS(Status
))
265 /* Handle failures */
266 DPRINT1("SMSS: SmpLoadSubSystem - SmpCallCsrCreateProcess Failed with Status %lx\n",
271 /* Save the process information we'll need for the create session */
272 ProcessInformation
.ProcessHandle
= CreateProcess
->Out
.ProcessHandle
;
273 ProcessInformation
.ThreadHandle
= CreateProcess
->Out
.ThreadHandle
;
274 ProcessInformation
.ClientId
= CreateProcess
->Out
.ClientId
;
275 ProcessInformation
.ImageInformation
.SubSystemType
= CreateProcess
->Out
.SubsystemType
;
279 /* This must be CSRSS itself, so just launch it and that's it */
280 Status
= SmpExecuteImage(FileName
,
284 Flags
| SMP_DEFERRED_FLAG
,
285 &ProcessInformation
);
286 if (!NT_SUCCESS(Status
))
288 /* Handle failures */
289 DPRINT1("SMSS: SmpLoadSubSystem - SmpExecuteImage Failed with Status %lx\n",
295 /* Fill out the handle and client ID in the subsystem structure now */
296 NewSubsystem
->ProcessHandle
= ProcessInformation
.ProcessHandle
;
297 NewSubsystem
->ClientId
= ProcessInformation
.ClientId
;
299 /* Check if we launched a native image or a subsystem-backed image */
300 if (ProcessInformation
.ImageInformation
.SubSystemType
== IMAGE_SUBSYSTEM_NATIVE
)
302 /* This must be CSRSS itself, since it's a native subsystem image */
303 SubSysProcessId
= ProcessInformation
.ClientId
.UniqueProcess
;
304 if ((ProcessId
) && !(*ProcessId
)) *ProcessId
= SubSysProcessId
;
306 /* Was this the initial CSRSS on Session 0? */
309 /* Then save it in the global variables */
310 SmpWindowsSubSysProcessId
= SubSysProcessId
;
311 SmpWindowsSubSysProcess
= ProcessInformation
.ProcessHandle
;
313 ASSERT(NT_SUCCESS(Status
));
317 /* This is the POSIX or OS/2 subsystem process, copy its information */
318 RtlCopyMemory(&CreateSession
->ProcessInfo
,
320 sizeof(CreateSession
->ProcessInfo
));
322 /* Not sure these field mean what I think they do -- but clear them */
323 *(PULONGLONG
)&CreateSession
->ClientId
= 0;
324 CreateSession
->MuSessionId
= 0;
326 /* This should find CSRSS because they are POSIX or OS/2 subsystems */
327 Subsystem
= SmpLocateKnownSubSysByType(MuSessionId
,
328 ProcessInformation
.ImageInformation
.SubSystemType
);
331 /* Odd failure -- but handle it anyway */
332 Status
= STATUS_NO_SUCH_PACKAGE
;
333 DPRINT1("SMSS: SmpLoadSubSystem - SmpLocateKnownSubSysByType Failed with Status %lx for sessionid %ld\n",
339 /* Duplicate the parent process handle for the subsystem to have */
340 Status
= NtDuplicateObject(NtCurrentProcess(),
341 ProcessInformation
.ProcessHandle
,
342 Subsystem
->ProcessHandle
,
343 &CreateSession
->ProcessInfo
.ProcessHandle
,
347 if (!NT_SUCCESS(Status
))
349 /* Fail since this is critical */
350 DPRINT1("SMSS: SmpLoadSubSystem - NtDuplicateObject Failed with Status %lx for sessionid %ld\n",
356 /* Duplicate the initial thread handle for the subsystem to have */
357 Status
= NtDuplicateObject(NtCurrentProcess(),
358 ProcessInformation
.ThreadHandle
,
359 Subsystem
->ProcessHandle
,
360 &CreateSession
->ProcessInfo
.ThreadHandle
,
364 if (!NT_SUCCESS(Status
))
366 /* Fail since this is critical */
367 DPRINT1("SMSS: SmpLoadSubSystem - NtDuplicateObject Failed with Status %lx for sessionid %ld\n",
373 /* Allocate an internal Session ID for this subsystem */
374 MuSessionId
= SmpAllocateSessionId(Subsystem
, 0);
375 CreateSession
->SessionId
= MuSessionId
;
377 /* Send the create session message to the subsystem */
378 SbApiMsg2
.ReturnValue
= STATUS_SUCCESS
;
379 SbApiMsg2
.h
.u2
.ZeroInit
= 0;
380 SbApiMsg2
.h
.u1
.s1
.DataLength
= sizeof(SB_CREATE_SESSION_MSG
) + 8;
381 SbApiMsg2
.h
.u1
.s1
.TotalLength
= sizeof(SB_API_MSG
);
382 Status
= NtRequestWaitReplyPort(Subsystem
->SbApiPort
,
385 if (NT_SUCCESS(Status
)) Status
= SbApiMsg2
.ReturnValue
;
386 if (!NT_SUCCESS(Status
))
388 /* Delete the session and handle failure if the LPC call failed */
389 SmpDeleteSession(CreateSession
->SessionId
);
390 DPRINT1("SMSS: SmpLoadSubSystem - NtRequestWaitReplyPort Failed with Status %lx for sessionid %ld\n",
392 CreateSession
->SessionId
);
397 /* Okay, everything looks good to go, initialize this subsystem now! */
398 Status
= NtResumeThread(ProcessInformation
.ThreadHandle
, NULL
);
399 if (!NT_SUCCESS(Status
))
401 /* That didn't work -- back out of everything */
402 DPRINT1("SMSS: SmpLoadSubSystem - NtResumeThread failed Status %lx\n", Status
);
406 /* Check if this was the subsystem for a different session */
409 /* Wait up to 60 seconds for it to initialize */
410 Timeout
.QuadPart
= -600000000;
411 Status
= NtWaitForSingleObject(NewSubsystem
->Event
, FALSE
, &Timeout
);
413 /* Timeout is done -- does this session still exist? */
414 if (!SmpCheckDuplicateMuSessionId(MuSessionId
))
416 /* Nope, it died. Cleanup should've ocurred in a different path. */
417 DPRINT1("SMSS: SmpLoadSubSystem - session deleted\n");
418 return STATUS_DELETE_PENDING
;
421 /* Check if we timed our or there was another error with the wait */
422 if (Status
!= STATUS_WAIT_0
)
424 /* Something is wrong with the subsystem, so back out of everything */
425 DPRINT1("SMSS: SmpLoadSubSystem - Timeout waiting for subsystem connect with Status %lx for sessionid %ld\n",
433 /* This a session 0 subsystem, just wait for it to initialize */
434 NtWaitForSingleObject(NewSubsystem
->Event
, FALSE
, NULL
);
437 /* Subsystem is created, resumed, and initialized. Close handles and exit */
438 NtClose(ProcessInformation
.ThreadHandle
);
439 Status
= STATUS_SUCCESS
;
443 /* This is the failure path. First check if we need to detach from session */
444 if ((AttachedSessionId
== -1) || (Flags
& (SMP_POSIX_FLAG
| SMP_OS2_FLAG
)))
446 /* We were not attached, or did not launch subsystems that required it */
447 DPRINT1("SMSS: Did not detach from Session Space: SessionId=%x Flags=%x Status=%x\n",
449 Flags
| SMP_DEFERRED_FLAG
,
454 /* Get the privilege we need for detachment */
455 Status
= SmpAcquirePrivilege(SE_LOAD_DRIVER_PRIVILEGE
, &State
);
456 if (!NT_SUCCESS(Status
))
458 /* We can't detach without it */
459 DPRINT1("SMSS: Did not detach from Session Space: SessionId=%x Flags=%x Status=%x\n",
461 Flags
| SMP_DEFERRED_FLAG
,
466 /* Now detach from the session */
467 Status
= NtSetSystemInformation(SystemSessionDetach
,
469 sizeof(AttachedSessionId
));
470 if (!NT_SUCCESS(Status
))
472 /* Failed to detach. Note the DPRINT1 has a typo in Windows */
473 DPRINT1("SMSS: SmpStartCsr, Couldn't Detach from Session Space. Status=%x\n", Status
);
474 ASSERT(NT_SUCCESS(Status
));
478 /* Detachment worked, reset our attached session ID */
479 AttachedSessionId
= -1;
482 /* And release the privilege we acquired */
483 SmpReleasePrivilege(State
);
487 /* Since this is the failure path, terminate the subsystem process */
488 NtTerminateProcess(ProcessInformation
.ProcessHandle
, Status
);
489 NtClose(ProcessInformation
.ThreadHandle
);
492 /* This is the cleanup path -- first dereference our subsystems */
493 RtlEnterCriticalSection(&SmpKnownSubSysLock
);
494 if (Subsystem
) SmpDereferenceSubsystem(Subsystem
);
495 if (KnownSubsystem
) SmpDereferenceSubsystem(KnownSubsystem
);
497 /* In the failure case, destroy the new subsystem we just created */
498 if (!NT_SUCCESS(Status
))
500 RemoveEntryList(&NewSubsystem
->Entry
);
501 NtSetEvent(NewSubsystem
->Event
, 0);
502 if (NewSubsystem
) SmpDereferenceSubsystem(NewSubsystem
);
505 /* Finally, we're all done! */
506 RtlLeaveCriticalSection(&SmpKnownSubSysLock
);
512 SmpLoadSubSystemsForMuSession(IN PULONG MuSessionId
,
513 OUT PHANDLE ProcessId
,
514 IN PUNICODE_STRING InitialCommand
)
516 NTSTATUS Status
= STATUS_SUCCESS
, Status2
;
517 PSMP_REGISTRY_VALUE RegEntry
;
518 UNICODE_STRING DestinationString
, NtPath
;
519 PLIST_ENTRY NextEntry
;
520 LARGE_INTEGER Timeout
;
523 /* Write a few last registry keys with the boot partition information */
524 SmpTranslateSystemPartitionInformation();
526 /* Process "SetupExecute" values */
527 NextEntry
= SmpSetupExecuteList
.Flink
;
528 while (NextEntry
!= &SmpSetupExecuteList
)
530 /* Execute each one and move on */
531 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
532 SmpExecuteCommand(&RegEntry
->Name
, 0, NULL
, 0);
533 NextEntry
= NextEntry
->Flink
;
536 /* Now process the subsystems */
537 NextEntry
= SmpSubSystemList
.Flink
;
538 while (NextEntry
!= &SmpSubSystemList
)
540 /* Get the entry and check if this is the special Win32k entry */
541 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
542 if (!_wcsicmp(RegEntry
->Name
.Buffer
, L
"Kmode"))
545 if (!RtlDosPathNameToNtPathName_U(RegEntry
->Value
.Buffer
,
550 Status
= STATUS_OBJECT_PATH_SYNTAX_BAD
;
551 DPRINT1("Failed: %lx\n", Status
);
555 /* Get the driver privilege */
556 Status
= SmpAcquirePrivilege(SE_LOAD_DRIVER_PRIVILEGE
, &State
);
557 if (NT_SUCCESS(Status
))
559 /* Create the new session */
560 ASSERT(AttachedSessionId
== -1);
561 Status
= NtSetSystemInformation(SystemSessionCreate
,
563 sizeof(*MuSessionId
));
564 if (!NT_SUCCESS(Status
))
566 DPRINT1("SMSS: Session space creation failed\n");
567 SmpReleasePrivilege(State
);
568 RtlFreeHeap(RtlGetProcessHeap(), 0, NtPath
.Buffer
);
571 AttachedSessionId
= *MuSessionId
;
573 /* Start Win32k.sys on this session */
574 RtlInitUnicodeString(&DestinationString
,
575 L
"\\SystemRoot\\System32\\win32k.sys");
576 Status
= NtSetSystemInformation(SystemExtendServiceTableInformation
,
578 sizeof(DestinationString
));
579 RtlFreeHeap(RtlGetProcessHeap(), 0, NtPath
.Buffer
);
580 SmpReleasePrivilege(State
);
581 if (!NT_SUCCESS(Status
))
583 DPRINT1("SMSS: Load of WIN32K failed.\n");
591 NextEntry
= NextEntry
->Flink
;
594 /* Now parse the required subsystem list */
595 NextEntry
= SmpSubSystemsToLoad
.Flink
;
596 while (NextEntry
!= &SmpSubSystemsToLoad
)
598 /* Get each entry and check if it's the internal debug or not */
599 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
600 if (_wcsicmp(RegEntry
->Name
.Buffer
, L
"debug"))
602 /* Load the required subsystem */
603 Status
= SmpExecuteCommand(&RegEntry
->Value
,
610 /* Load the internal debug system */
611 Status
= SmpExecuteCommand(&RegEntry
->Value
,
614 SMP_DEBUG_FLAG
| SMP_SUBSYSTEM_FLAG
);
616 if (!NT_SUCCESS(Status
))
618 DbgPrint("SMSS: Subsystem execute failed (%WZ)\n", &RegEntry
->Value
);
622 /* Move to the next entry */
623 NextEntry
= NextEntry
->Flink
;
626 /* Process the "Execute" list now */
627 NextEntry
= SmpExecuteList
.Blink
;
628 if (NextEntry
!= &SmpExecuteList
)
630 /* Get the custom initial command */
631 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
633 /* Write the initial command and wait for 5 seconds (why??!) */
634 *InitialCommand
= RegEntry
->Name
;
635 Timeout
.QuadPart
= -50000000;
636 NtDelayExecution(FALSE
, &Timeout
);
640 /* Use the default Winlogon initial command */
641 RtlInitUnicodeString(InitialCommand
, L
"winlogon.exe");
642 InitialCommandBuffer
[0] = UNICODE_NULL
;
644 /* Check if there's a debugger for Winlogon */
645 Status2
= LdrQueryImageFileExecutionOptions(InitialCommand
,
648 InitialCommandBuffer
,
649 sizeof(InitialCommandBuffer
) -
650 InitialCommand
->Length
,
652 if ((NT_SUCCESS(Status2
)) && (InitialCommandBuffer
[0]))
654 /* Put the debugger string with the Winlogon string */
655 wcscat(InitialCommandBuffer
, L
" ");
656 wcscat(InitialCommandBuffer
, InitialCommand
->Buffer
);
657 RtlInitUnicodeString(InitialCommand
, InitialCommandBuffer
);
661 /* Finally check if there was a custom initial command */
662 NextEntry
= SmpExecuteList
.Flink
;
663 while (NextEntry
!= &SmpExecuteList
)
665 /* Execute each one */
666 RegEntry
= CONTAINING_RECORD(NextEntry
, SMP_REGISTRY_VALUE
, Entry
);
667 SmpExecuteCommand(&RegEntry
->Name
, *MuSessionId
, NULL
, 0);
668 NextEntry
= NextEntry
->Flink
;