Patch by Jonathon Wilson:
[reactos.git] / reactos / subsys / system / services / database.c
1 /* $Id: database.c,v 1.11 2003/11/14 17:13:33 weiden Exp $
2 *
3 * service control manager
4 *
5 * ReactOS Operating System
6 *
7 * --------------------------------------------------------------------
8 *
9 * This software is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) any later version.
13 *
14 * This software is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this software; see the file COPYING.LIB. If not, write
21 * to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
22 * MA 02139, USA.
23 *
24 */
25
26 /* INCLUDES *****************************************************************/
27
28 #define NTOS_MODE_USER
29 #include <ntos.h>
30
31 #include <windows.h>
32 #include <tchar.h>
33
34 #include "services.h"
35
36 #define NDEBUG
37 #include <debug.h>
38
39
40 /* TYPES *********************************************************************/
41
42 typedef struct _SERVICE_GROUP
43 {
44 LIST_ENTRY GroupListEntry;
45 UNICODE_STRING GroupName;
46
47 BOOLEAN ServicesRunning;
48
49 } SERVICE_GROUP, *PSERVICE_GROUP;
50
51
52 typedef struct _SERVICE
53 {
54 LIST_ENTRY ServiceListEntry;
55 UNICODE_STRING ServiceName;
56 UNICODE_STRING RegistryPath;
57 UNICODE_STRING ServiceGroup;
58
59 ULONG Start;
60 ULONG Type;
61 ULONG ErrorControl;
62 ULONG Tag;
63
64 BOOLEAN ServiceRunning;
65 BOOLEAN ServiceVisited;
66
67 HANDLE ControlPipeHandle;
68 ULONG ProcessId;
69 ULONG ThreadId;
70 } SERVICE, *PSERVICE;
71
72
73 /* GLOBALS *******************************************************************/
74
75 LIST_ENTRY GroupListHead;
76 LIST_ENTRY ServiceListHead;
77
78
79 /* FUNCTIONS *****************************************************************/
80
81 static NTSTATUS STDCALL
82 CreateGroupListRoutine(PWSTR ValueName,
83 ULONG ValueType,
84 PVOID ValueData,
85 ULONG ValueLength,
86 PVOID Context,
87 PVOID EntryContext)
88 {
89 PSERVICE_GROUP Group;
90
91 if (ValueType == REG_SZ)
92 {
93 DPRINT("Data: '%S'\n", (PWCHAR)ValueData);
94
95 Group = (PSERVICE_GROUP)HeapAlloc(GetProcessHeap(),
96 HEAP_ZERO_MEMORY,
97 sizeof(SERVICE_GROUP));
98 if (Group == NULL)
99 {
100 return(STATUS_INSUFFICIENT_RESOURCES);
101 }
102
103 if (!RtlCreateUnicodeString(&Group->GroupName,
104 (PWSTR)ValueData))
105 {
106 return(STATUS_INSUFFICIENT_RESOURCES);
107 }
108
109
110 InsertTailList(&GroupListHead,
111 &Group->GroupListEntry);
112 }
113
114 return(STATUS_SUCCESS);
115 }
116
117
118 static NTSTATUS STDCALL
119 CreateServiceListEntry(PUNICODE_STRING ServiceName)
120 {
121 RTL_QUERY_REGISTRY_TABLE QueryTable[6];
122 PSERVICE Service = NULL;
123 NTSTATUS Status;
124
125 DPRINT("Service: '%wZ'\n", ServiceName);
126
127 /* Allocate service entry */
128 Service = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
129 sizeof(SERVICE));
130 if (Service == NULL)
131 {
132 return(STATUS_INSUFFICIENT_RESOURCES);
133 }
134
135 /* Copy service name */
136 Service->ServiceName.Length = ServiceName->Length;
137 Service->ServiceName.MaximumLength = ServiceName->Length + sizeof(WCHAR);
138 Service->ServiceName.Buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
139 Service->ServiceName.MaximumLength);
140 if (Service->ServiceName.Buffer == NULL)
141 {
142 HeapFree(GetProcessHeap(), 0, Service);
143 return(STATUS_INSUFFICIENT_RESOURCES);
144 }
145 RtlCopyMemory(Service->ServiceName.Buffer,
146 ServiceName->Buffer,
147 ServiceName->Length);
148 Service->ServiceName.Buffer[ServiceName->Length / sizeof(WCHAR)] = 0;
149
150 /* Build registry path */
151 Service->RegistryPath.MaximumLength = MAX_PATH * sizeof(WCHAR);
152 Service->RegistryPath.Buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
153 MAX_PATH * sizeof(WCHAR));
154 if (Service->ServiceName.Buffer == NULL)
155 {
156 HeapFree(GetProcessHeap(), 0, Service->ServiceName.Buffer);
157 HeapFree(GetProcessHeap(), 0, Service);
158 return(STATUS_INSUFFICIENT_RESOURCES);
159 }
160 wcscpy(Service->RegistryPath.Buffer,
161 L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\");
162 wcscat(Service->RegistryPath.Buffer,
163 Service->ServiceName.Buffer);
164 Service->RegistryPath.Length = wcslen(Service->RegistryPath.Buffer) * sizeof(WCHAR);
165
166 /* Get service data */
167 RtlZeroMemory(&QueryTable,
168 sizeof(QueryTable));
169
170 QueryTable[0].Name = L"Start";
171 QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
172 QueryTable[0].EntryContext = &Service->Start;
173
174 QueryTable[1].Name = L"Type";
175 QueryTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
176 QueryTable[1].EntryContext = &Service->Type;
177
178 QueryTable[2].Name = L"ErrorControl";
179 QueryTable[2].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
180 QueryTable[2].EntryContext = &Service->ErrorControl;
181
182 QueryTable[3].Name = L"Group";
183 QueryTable[3].Flags = RTL_QUERY_REGISTRY_DIRECT;
184 QueryTable[3].EntryContext = &Service->ServiceGroup;
185
186 Status = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
187 ServiceName->Buffer,
188 QueryTable,
189 NULL,
190 NULL);
191 if (!NT_SUCCESS(Status))
192 {
193 PrintString("RtlQueryRegistryValues() failed (Status %lx)\n", Status);
194 RtlFreeUnicodeString(&Service->RegistryPath);
195 RtlFreeUnicodeString(&Service->ServiceName);
196 HeapFree(GetProcessHeap(), 0, Service);
197 return(Status);
198 }
199
200 DPRINT("ServiceName: '%wZ'\n", &Service->ServiceName);
201 DPRINT("RegistryPath: '%wZ'\n", &Service->RegistryPath);
202 DPRINT("ServiceGroup: '%wZ'\n", &Service->ServiceGroup);
203 DPRINT("Start %lx Type %lx ErrorControl %lx\n",
204 Service->Start, Service->Type, Service->ErrorControl);
205
206 /* Append service entry */
207 InsertTailList(&ServiceListHead,
208 &Service->ServiceListEntry);
209
210 return(STATUS_SUCCESS);
211 }
212
213
214 NTSTATUS
215 ScmCreateServiceDataBase(VOID)
216 {
217 RTL_QUERY_REGISTRY_TABLE QueryTable[2];
218 OBJECT_ATTRIBUTES ObjectAttributes;
219 UNICODE_STRING ServicesKeyName;
220 UNICODE_STRING SubKeyName;
221 HKEY ServicesKey;
222 ULONG Index;
223 NTSTATUS Status;
224
225 PKEY_BASIC_INFORMATION KeyInfo = NULL;
226 ULONG KeyInfoLength = 0;
227 ULONG ReturnedLength;
228
229 DPRINT("ScmCreateServiceDataBase() called\n");
230
231 /* Initialize basic variables */
232 InitializeListHead(&GroupListHead);
233 InitializeListHead(&ServiceListHead);
234
235 /* Build group order list */
236 RtlZeroMemory(&QueryTable,
237 sizeof(QueryTable));
238
239 QueryTable[0].Name = L"List";
240 QueryTable[0].QueryRoutine = CreateGroupListRoutine;
241
242 Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL,
243 L"ServiceGroupOrder",
244 QueryTable,
245 NULL,
246 NULL);
247 if (!NT_SUCCESS(Status))
248 return(Status);
249
250 RtlInitUnicodeStringFromLiteral(&ServicesKeyName,
251 L"\\Registry\\Machine\\System\\CurrentControlSet\\Services");
252
253 InitializeObjectAttributes(&ObjectAttributes,
254 &ServicesKeyName,
255 OBJ_CASE_INSENSITIVE,
256 NULL,
257 NULL);
258
259 Status = RtlpNtOpenKey(&ServicesKey,
260 0x10001,
261 &ObjectAttributes,
262 0);
263 if (!NT_SUCCESS(Status))
264 return(Status);
265
266 /* Allocate key info buffer */
267 KeyInfoLength = sizeof(KEY_BASIC_INFORMATION) + MAX_PATH * sizeof(WCHAR);
268 KeyInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, KeyInfoLength);
269 if (KeyInfo == NULL)
270 {
271 NtClose(ServicesKey);
272 return(STATUS_INSUFFICIENT_RESOURCES);
273 }
274
275 Index = 0;
276 while (TRUE)
277 {
278 Status = NtEnumerateKey(ServicesKey,
279 Index,
280 KeyBasicInformation,
281 KeyInfo,
282 KeyInfoLength,
283 &ReturnedLength);
284 if (NT_SUCCESS(Status))
285 {
286 if (KeyInfo->NameLength < MAX_PATH * sizeof(WCHAR))
287 {
288
289 SubKeyName.Length = KeyInfo->NameLength;
290 SubKeyName.MaximumLength = KeyInfo->NameLength + sizeof(WCHAR);
291 SubKeyName.Buffer = KeyInfo->Name;
292 SubKeyName.Buffer[SubKeyName.Length / sizeof(WCHAR)] = 0;
293
294 DPRINT("KeyName: '%wZ'\n", &SubKeyName);
295 Status = CreateServiceListEntry(&SubKeyName);
296 }
297 }
298
299 if (!NT_SUCCESS(Status))
300 break;
301
302 Index++;
303 }
304
305 HeapFree(GetProcessHeap(), 0, KeyInfo);
306 NtClose(ServicesKey);
307
308 DPRINT("ScmCreateServiceDataBase() done\n");
309
310 return(STATUS_SUCCESS);
311 }
312
313
314 static NTSTATUS
315 ScmCheckDriver(PSERVICE Service)
316 {
317 OBJECT_ATTRIBUTES ObjectAttributes;
318 UNICODE_STRING DirName;
319 HANDLE DirHandle;
320 NTSTATUS Status;
321 PDIRECTORY_BASIC_INFORMATION DirInfo;
322 ULONG BufferLength;
323 ULONG DataLength;
324 ULONG Index;
325 PLIST_ENTRY GroupEntry;
326 PSERVICE_GROUP CurrentGroup;
327
328 DPRINT("ScmCheckDriver(%wZ) called\n", &Service->ServiceName);
329
330 if (Service->Type == SERVICE_KERNEL_DRIVER)
331 {
332 RtlInitUnicodeStringFromLiteral(&DirName,
333 L"\\Driver");
334 }
335 else
336 {
337 RtlInitUnicodeStringFromLiteral(&DirName,
338 L"\\FileSystem");
339 }
340
341 InitializeObjectAttributes(&ObjectAttributes,
342 &DirName,
343 0,
344 NULL,
345 NULL);
346
347 Status = NtOpenDirectoryObject(&DirHandle,
348 DIRECTORY_QUERY | DIRECTORY_TRAVERSE,
349 &ObjectAttributes);
350 if (!NT_SUCCESS(Status))
351 {
352 return(Status);
353 }
354
355 BufferLength = sizeof(DIRECTORY_BASIC_INFORMATION) +
356 2 * MAX_PATH * sizeof(WCHAR);
357 DirInfo = HeapAlloc(GetProcessHeap(),
358 HEAP_ZERO_MEMORY,
359 BufferLength);
360
361 Index = 0;
362 while (TRUE)
363 {
364 Status = NtQueryDirectoryObject(DirHandle,
365 DirInfo,
366 BufferLength,
367 TRUE,
368 FALSE,
369 &Index,
370 &DataLength);
371 if (Status == STATUS_NO_MORE_ENTRIES)
372 {
373 /* FIXME: Add current service to 'failed service' list */
374 DPRINT("Service '%wZ' failed\n", &Service->ServiceName);
375 break;
376 }
377
378 if (!NT_SUCCESS(Status))
379 break;
380
381 DPRINT("Comparing: '%wZ' '%wZ'\n", &Service->ServiceName, &DirInfo->ObjectName);
382
383 if (RtlEqualUnicodeString(&Service->ServiceName, &DirInfo->ObjectName, TRUE))
384 {
385 DPRINT("Found: '%wZ' '%wZ'\n", &Service->ServiceName, &DirInfo->ObjectName);
386
387 /* Mark service as 'running' */
388 Service->ServiceRunning = TRUE;
389
390 /* Find the driver's group and mark it as 'running' */
391 if (Service->ServiceGroup.Buffer != NULL)
392 {
393 GroupEntry = GroupListHead.Flink;
394 while (GroupEntry != &GroupListHead)
395 {
396 CurrentGroup = CONTAINING_RECORD(GroupEntry, SERVICE_GROUP, GroupListEntry);
397
398 DPRINT("Checking group '%wZ'\n", &CurrentGroup->GroupName);
399 if (RtlEqualUnicodeString(&Service->ServiceGroup, &CurrentGroup->GroupName, TRUE))
400 {
401 CurrentGroup->ServicesRunning = TRUE;
402 }
403
404 GroupEntry = GroupEntry->Flink;
405 }
406 }
407 break;
408 }
409 }
410
411 HeapFree(GetProcessHeap(),
412 0,
413 DirInfo);
414 NtClose(DirHandle);
415
416 return(STATUS_SUCCESS);
417 }
418
419
420 VOID
421 ScmGetBootAndSystemDriverState(VOID)
422 {
423 PLIST_ENTRY ServiceEntry;
424 PSERVICE CurrentService;
425
426 DPRINT("ScmGetBootAndSystemDriverState() called\n");
427
428 ServiceEntry = ServiceListHead.Flink;
429 while (ServiceEntry != &ServiceListHead)
430 {
431 CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
432
433 if (CurrentService->Start == SERVICE_BOOT_START ||
434 CurrentService->Start == SERVICE_SYSTEM_START)
435 {
436 /* Check driver */
437 DPRINT(" Checking service: %wZ\n", &CurrentService->ServiceName);
438
439 ScmCheckDriver(CurrentService);
440 }
441 ServiceEntry = ServiceEntry->Flink;
442 }
443
444 DPRINT("ScmGetBootAndSystemDriverState() done\n");
445 }
446
447
448 static NTSTATUS
449 ScmStartService(PSERVICE Service,
450 PSERVICE_GROUP Group)
451 {
452 RTL_QUERY_REGISTRY_TABLE QueryTable[3];
453 PROCESS_INFORMATION ProcessInformation;
454 STARTUPINFOW StartupInfo;
455 UNICODE_STRING ImagePath;
456 NTSTATUS Status;
457 ULONG Type;
458 BOOL Result;
459
460 DPRINT("ScmStartService() called\n");
461
462 Service->ControlPipeHandle = INVALID_HANDLE_VALUE;
463
464 if (Service->Type == SERVICE_KERNEL_DRIVER ||
465 Service->Type == SERVICE_FILE_SYSTEM_DRIVER ||
466 Service->Type == SERVICE_RECOGNIZER_DRIVER)
467 {
468 /* Load driver */
469 DPRINT(" Path: %wZ\n", &Service->RegistryPath);
470 Status = NtLoadDriver(&Service->RegistryPath);
471 }
472 else
473 {
474 RtlInitUnicodeString(&ImagePath, NULL);
475
476 /* Get service data */
477 RtlZeroMemory(&QueryTable,
478 sizeof(QueryTable));
479
480 QueryTable[0].Name = L"Type";
481 QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
482 QueryTable[0].EntryContext = &Type;
483
484 QueryTable[1].Name = L"ImagePath";
485 QueryTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
486 QueryTable[1].EntryContext = &ImagePath;
487
488 Status = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
489 Service->ServiceName.Buffer,
490 QueryTable,
491 NULL,
492 NULL);
493 if (NT_SUCCESS(Status))
494 {
495 DPRINT("ImagePath: '%S'\n", ImagePath.Buffer);
496 DPRINT("Type: %lx\n", Type);
497
498 /* FIXME: create '\\.\pipe\net\NtControlPipe' instance */
499 Service->ControlPipeHandle = CreateNamedPipeW(L"\\\\.\\pipe\\net\\NtControlPipe",
500 PIPE_ACCESS_DUPLEX,
501 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
502 100,
503 8000,
504 4,
505 30000,
506 NULL);
507 DPRINT("CreateNamedPipeW() done\n");
508 if (Service->ControlPipeHandle == INVALID_HANDLE_VALUE)
509 {
510 DPRINT1("Failed to create control pipe!\n");
511 Status = STATUS_UNSUCCESSFUL;
512 goto Done;
513 }
514
515 StartupInfo.cb = sizeof(StartupInfo);
516 StartupInfo.lpReserved = NULL;
517 StartupInfo.lpDesktop = NULL;
518 StartupInfo.lpTitle = NULL;
519 StartupInfo.dwFlags = 0;
520 StartupInfo.cbReserved2 = 0;
521 StartupInfo.lpReserved2 = 0;
522
523 Result = CreateProcessW(ImagePath.Buffer,
524 NULL,
525 NULL,
526 NULL,
527 FALSE,
528 DETACHED_PROCESS | CREATE_SUSPENDED,
529 NULL,
530 NULL,
531 &StartupInfo,
532 &ProcessInformation);
533 RtlFreeUnicodeString(&ImagePath);
534
535 if (!Result)
536 {
537 /* Close control pipe */
538 CloseHandle(Service->ControlPipeHandle);
539 Service->ControlPipeHandle = INVALID_HANDLE_VALUE;
540
541 DPRINT("Starting '%S' failed!\n", Service->ServiceName.Buffer);
542 Status = STATUS_UNSUCCESSFUL;
543 }
544 else
545 {
546 DPRINT("Process Id: %lu Handle %lx\n",
547 ProcessInformation.dwProcessId,
548 ProcessInformation.hProcess);
549 DPRINT("Thread Id: %lu Handle %lx\n",
550 ProcessInformation.dwThreadId,
551 ProcessInformation.hThread);
552
553 /* Get process and thread ids */
554 Service->ProcessId = ProcessInformation.dwProcessId;
555 Service->ThreadId = ProcessInformation.dwThreadId;
556
557 /* Resume Thread */
558 ResumeThread(ProcessInformation.hThread);
559
560 /* FIXME: connect control pipe */
561 if (ConnectNamedPipe(Service->ControlPipeHandle, NULL))
562 {
563 DPRINT("Control pipe connected!\n");
564 Status = STATUS_SUCCESS;
565 }
566 else
567 {
568 DPRINT1("Connecting control pipe failed!\n");
569
570 /* Close control pipe */
571 CloseHandle(Service->ControlPipeHandle);
572 Service->ControlPipeHandle = INVALID_HANDLE_VALUE;
573 Service->ProcessId = 0;
574 Service->ThreadId = 0;
575 Status = STATUS_UNSUCCESSFUL;
576 }
577
578 /* Close process and thread handle */
579 CloseHandle(ProcessInformation.hThread);
580 CloseHandle(ProcessInformation.hProcess);
581 }
582 }
583 }
584
585 Done:
586 if (NT_SUCCESS(Status))
587 {
588 if (Group != NULL)
589 {
590 Group->ServicesRunning = TRUE;
591 }
592 Service->ServiceRunning = TRUE;
593 }
594 #if 0
595 else
596 {
597 if (CurrentService->ErrorControl == 1)
598 {
599 /* Log error */
600
601 }
602 else if (CurrentService->ErrorControl == 2)
603 {
604 if (IsLastKnownGood == FALSE)
605 {
606 /* Boot last known good configuration */
607
608 }
609 }
610 else if (CurrentService->ErrorControl == 3)
611 {
612 if (IsLastKnownGood == FALSE)
613 {
614 /* Boot last known good configuration */
615
616 }
617 else
618 {
619 /* BSOD! */
620
621 }
622 }
623 }
624 #endif
625
626 return(STATUS_SUCCESS);
627 }
628
629
630 VOID
631 ScmAutoStartServices(VOID)
632 {
633 PLIST_ENTRY GroupEntry;
634 PLIST_ENTRY ServiceEntry;
635 PSERVICE_GROUP CurrentGroup;
636 PSERVICE CurrentService;
637
638 /* Clear 'ServiceVisited' flag */
639 ServiceEntry = ServiceListHead.Flink;
640 while (ServiceEntry != &ServiceListHead)
641 {
642 CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
643 CurrentService->ServiceVisited = FALSE;
644 ServiceEntry = ServiceEntry->Flink;
645 }
646
647 /* Start all services which are members of an existing group */
648 GroupEntry = GroupListHead.Flink;
649 while (GroupEntry != &GroupListHead)
650 {
651 CurrentGroup = CONTAINING_RECORD(GroupEntry, SERVICE_GROUP, GroupListEntry);
652
653 DPRINT("Group '%wZ'\n", &CurrentGroup->GroupName);
654
655 ServiceEntry = ServiceListHead.Flink;
656 while (ServiceEntry != &ServiceListHead)
657 {
658 CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
659
660 if ((RtlEqualUnicodeString(&CurrentGroup->GroupName, &CurrentService->ServiceGroup, TRUE)) &&
661 (CurrentService->Start == SERVICE_AUTO_START) &&
662 (CurrentService->ServiceVisited == FALSE))
663 {
664 CurrentService->ServiceVisited = TRUE;
665 ScmStartService(CurrentService,
666 CurrentGroup);
667 }
668
669 ServiceEntry = ServiceEntry->Flink;
670 }
671
672 GroupEntry = GroupEntry->Flink;
673 }
674
675 /* Start all services which are members of any non-existing group */
676 ServiceEntry = ServiceListHead.Flink;
677 while (ServiceEntry != &ServiceListHead)
678 {
679 CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
680
681 if ((CurrentGroup->GroupName.Length > 0) &&
682 (CurrentService->Start == SERVICE_AUTO_START) &&
683 (CurrentService->ServiceVisited == FALSE))
684 {
685 CurrentService->ServiceVisited = TRUE;
686 ScmStartService(CurrentService,
687 NULL);
688 }
689
690 ServiceEntry = ServiceEntry->Flink;
691 }
692
693 /* Start all services which are not a member of any group */
694 ServiceEntry = ServiceListHead.Flink;
695 while (ServiceEntry != &ServiceListHead)
696 {
697 CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
698
699 if ((CurrentGroup->GroupName.Length == 0) &&
700 (CurrentService->Start == SERVICE_AUTO_START) &&
701 (CurrentService->ServiceVisited == FALSE))
702 {
703 CurrentService->ServiceVisited = TRUE;
704 ScmStartService(CurrentService,
705 NULL);
706 }
707
708 ServiceEntry = ServiceEntry->Flink;
709 }
710
711 /* Clear 'ServiceVisited' flag again */
712 ServiceEntry = ServiceListHead.Flink;
713 while (ServiceEntry != &ServiceListHead)
714 {
715 CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
716 CurrentService->ServiceVisited = FALSE;
717 ServiceEntry = ServiceEntry->Flink;
718 }
719 }
720
721 /* EOF */