:pserver:cvsanon@mok.lvcm.com:/CVS/ReactOS reactos
[reactos.git] / subsys / system / services / database.c
1 /* $Id$
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   POBJDIR_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(OBJDIR_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   NTSTATUS Status;
426
427   DPRINT("ScmGetBootAndSystemDriverState() called\n");
428
429   ServiceEntry = ServiceListHead.Flink;
430   while (ServiceEntry != &ServiceListHead)
431     {
432       CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
433
434       if (CurrentService->Start == SERVICE_BOOT_START ||
435           CurrentService->Start == SERVICE_SYSTEM_START)
436         {
437           /* Check driver */
438           DPRINT("  Checking service: %wZ\n", &CurrentService->ServiceName);
439
440           ScmCheckDriver(CurrentService);
441         }
442       ServiceEntry = ServiceEntry->Flink;
443     }
444
445   DPRINT("ScmGetBootAndSystemDriverState() done\n");
446 }
447
448
449 static NTSTATUS
450 ScmStartService(PSERVICE Service,
451                 PSERVICE_GROUP Group)
452 {
453   RTL_QUERY_REGISTRY_TABLE QueryTable[3];
454   PROCESS_INFORMATION ProcessInformation;
455   STARTUPINFOW StartupInfo;
456   UNICODE_STRING ImagePath;
457   NTSTATUS Status;
458   ULONG Type;
459   BOOL Result;
460
461   DPRINT("ScmStartService() called\n");
462
463   Service->ControlPipeHandle = INVALID_HANDLE_VALUE;
464
465   if (Service->Type == SERVICE_KERNEL_DRIVER ||
466       Service->Type == SERVICE_FILE_SYSTEM_DRIVER ||
467       Service->Type == SERVICE_RECOGNIZER_DRIVER)
468     {
469       /* Load driver */
470       DPRINT("  Path: %wZ\n", &Service->RegistryPath);
471       Status = NtLoadDriver(&Service->RegistryPath);
472     }
473   else
474     {
475       RtlInitUnicodeString(&ImagePath, NULL);
476
477       /* Get service data */
478       RtlZeroMemory(&QueryTable,
479                     sizeof(QueryTable));
480
481       QueryTable[0].Name = L"Type";
482       QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
483       QueryTable[0].EntryContext = &Type;
484
485       QueryTable[1].Name = L"ImagePath";
486       QueryTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
487       QueryTable[1].EntryContext = &ImagePath;
488
489       Status = RtlQueryRegistryValues(RTL_REGISTRY_SERVICES,
490                                       Service->ServiceName.Buffer,
491                                       QueryTable,
492                                       NULL,
493                                       NULL);
494       if (NT_SUCCESS(Status))
495         {
496           DPRINT("ImagePath: '%S'\n", ImagePath.Buffer);
497           DPRINT("Type: %lx\n", Type);
498
499           /* FIXME: create '\\.\pipe\net\NtControlPipe' instance */
500           Service->ControlPipeHandle = CreateNamedPipeW(L"\\\\.\\pipe\\net\\NtControlPipe",
501                                                         PIPE_ACCESS_DUPLEX,
502                                                         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
503                                                         100,
504                                                         8000,
505                                                         4,
506                                                         30000,
507                                                         NULL);
508           DPRINT1("CreateNamedPipeW() done\n");
509           if (Service->ControlPipeHandle == INVALID_HANDLE_VALUE)
510             {
511               DPRINT1("Failed to create control pipe!\n");
512               Status = STATUS_UNSUCCESSFUL;
513               goto Done;
514             }
515
516           StartupInfo.cb = sizeof(StartupInfo);
517           StartupInfo.lpReserved = NULL;
518           StartupInfo.lpDesktop = NULL;
519           StartupInfo.lpTitle = NULL;
520           StartupInfo.dwFlags = 0;
521           StartupInfo.cbReserved2 = 0;
522           StartupInfo.lpReserved2 = 0;
523
524           Result = CreateProcessW(ImagePath.Buffer,
525                                   NULL,
526                                   NULL,
527                                   NULL,
528                                   FALSE,
529                                   DETACHED_PROCESS | CREATE_SUSPENDED,
530                                   NULL,
531                                   NULL,
532                                   &StartupInfo,
533                                   &ProcessInformation);
534           RtlFreeUnicodeString(&ImagePath);
535
536           if (!Result)
537             {
538               /* Close control pipe */
539               CloseHandle(Service->ControlPipeHandle);
540               Service->ControlPipeHandle = INVALID_HANDLE_VALUE;
541
542               DPRINT("Starting '%S' failed!\n", Service->ServiceName.Buffer);
543               Status = STATUS_UNSUCCESSFUL;
544             }
545           else
546             {
547               DPRINT1("Process Id: %lu  Handle %lx\n",
548                       ProcessInformation.dwProcessId,
549                       ProcessInformation.hProcess);
550               DPRINT1("Tread Id: %lu  Handle %lx\n",
551                       ProcessInformation.dwThreadId,
552                       ProcessInformation.hThread);
553
554               /* Get process and thread ids */
555               Service->ProcessId = ProcessInformation.dwProcessId;
556               Service->ThreadId = ProcessInformation.dwThreadId;
557
558               /* Resume Thread */
559               ResumeThread(ProcessInformation.hThread);
560
561               /* FIXME: connect control pipe */
562               if (ConnectNamedPipe(Service->ControlPipeHandle, NULL))
563                 {
564                   DPRINT1("Control pipe connected!\n");
565                   Status = STATUS_SUCCESS;
566                 }
567               else
568                 {
569                   DPRINT1("Connecting control pipe failed!\n");
570
571                   /* Close control pipe */
572                   CloseHandle(Service->ControlPipeHandle);
573                   Service->ControlPipeHandle = INVALID_HANDLE_VALUE;
574                   Service->ProcessId = 0;
575                   Service->ThreadId = 0;
576                   Status = STATUS_UNSUCCESSFUL;
577                 }
578
579               /* Close process and thread handle */
580               CloseHandle(ProcessInformation.hThread);
581               CloseHandle(ProcessInformation.hProcess);
582             }
583         }
584     }
585
586 Done:
587   if (NT_SUCCESS(Status))
588     {
589       if (Group != NULL)
590         {
591           Group->ServicesRunning = TRUE;
592         }
593       Service->ServiceRunning = TRUE;
594     }
595 #if 0
596   else
597     {
598       if (CurrentService->ErrorControl == 1)
599         {
600           /* Log error */
601
602         }
603       else if (CurrentService->ErrorControl == 2)
604         {
605           if (IsLastKnownGood == FALSE)
606             {
607               /* Boot last known good configuration */
608
609             }
610         }
611       else if (CurrentService->ErrorControl == 3)
612         {
613           if (IsLastKnownGood == FALSE)
614             {
615               /* Boot last known good configuration */
616
617             }
618           else
619             {
620               /* BSOD! */
621
622             }
623         }
624     }
625 #endif
626
627   return(STATUS_SUCCESS);
628 }
629
630
631 VOID
632 ScmAutoStartServices(VOID)
633 {
634   PLIST_ENTRY GroupEntry;
635   PLIST_ENTRY ServiceEntry;
636   PSERVICE_GROUP CurrentGroup;
637   PSERVICE CurrentService;
638   NTSTATUS Status;
639
640   /* Clear 'ServiceVisited' flag */
641   ServiceEntry = ServiceListHead.Flink;
642   while (ServiceEntry != &ServiceListHead)
643     {
644       CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
645       CurrentService->ServiceVisited = FALSE;
646       ServiceEntry = ServiceEntry->Flink;
647     }
648
649   /* Start all services which are members of an existing group */
650   GroupEntry = GroupListHead.Flink;
651   while (GroupEntry != &GroupListHead)
652     {
653       CurrentGroup = CONTAINING_RECORD(GroupEntry, SERVICE_GROUP, GroupListEntry);
654
655       DPRINT("Group '%wZ'\n", &CurrentGroup->GroupName);
656
657       ServiceEntry = ServiceListHead.Flink;
658       while (ServiceEntry != &ServiceListHead)
659         {
660           CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
661
662           if ((RtlEqualUnicodeString(&CurrentGroup->GroupName, &CurrentService->ServiceGroup, TRUE)) &&
663               (CurrentService->Start == SERVICE_AUTO_START) &&
664               (CurrentService->ServiceVisited == FALSE))
665             {
666               CurrentService->ServiceVisited = TRUE;
667               ScmStartService(CurrentService,
668                               CurrentGroup);
669             }
670
671           ServiceEntry = ServiceEntry->Flink;
672         }
673
674       GroupEntry = GroupEntry->Flink;
675     }
676
677   /* Start all services which are members of any non-existing group */
678   ServiceEntry = ServiceListHead.Flink;
679   while (ServiceEntry != &ServiceListHead)
680     {
681       CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
682
683       if ((CurrentGroup->GroupName.Length > 0) &&
684           (CurrentService->Start == SERVICE_AUTO_START) &&
685           (CurrentService->ServiceVisited == FALSE))
686         {
687           CurrentService->ServiceVisited = TRUE;
688           ScmStartService(CurrentService,
689                           NULL);
690         }
691
692       ServiceEntry = ServiceEntry->Flink;
693     }
694
695   /* Start all services which are not a member of any group */
696   ServiceEntry = ServiceListHead.Flink;
697   while (ServiceEntry != &ServiceListHead)
698     {
699       CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
700
701       if ((CurrentGroup->GroupName.Length == 0) &&
702           (CurrentService->Start == SERVICE_AUTO_START) &&
703           (CurrentService->ServiceVisited == FALSE))
704         {
705           CurrentService->ServiceVisited = TRUE;
706           ScmStartService(CurrentService,
707                           NULL);
708         }
709
710       ServiceEntry = ServiceEntry->Flink;
711     }
712
713   /* Clear 'ServiceVisited' flag again */
714   ServiceEntry = ServiceListHead.Flink;
715   while (ServiceEntry != &ServiceListHead)
716     {
717       CurrentService = CONTAINING_RECORD(ServiceEntry, SERVICE, ServiceListEntry);
718       CurrentService->ServiceVisited = FALSE;
719       ServiceEntry = ServiceEntry->Flink;
720     }
721 }
722
723 /* EOF */