1 // This file is written in D programming language
2 /**
3 *    Implementation of cross-platform daemon API for Windows platform.
4 *
5 *   Copyright: © 2013-2014 Anton Gushcha
6 *   License: Subject to the terms of the MIT license, as written in the included LICENSE file.
7 *   Authors: NCrashed <ncrashed@gmail.com>
8 */
9 module daemonize.windows;
10 
11 version(Windows):
12 
13 static if( __VERSION__ < 2066 ) private enum nogc;
14 
15 static if( __VERSION__ < 2070 ) import std.c.stdlib;
16 else import core.stdc.stdlib;
17 
18 static if( __VERSION__ < 2075 ) import std.datetime;
19 else import core.time;
20 
21 import core.sys.windows.windows;
22 import core.thread;
23 import std..string;
24 import std.typetuple;
25 import std.utf;
26 import std.conv : text;
27 import std.typecons;
28 
29 import daemonize.daemon;
30 import daemonize..string;
31 import daemonize.keymap;
32 import daemonize.log;
33 
34 /// Windows version doesn't use pid files
35 string defaultPidFile(string daemonName)
36 {
37     return "";
38 }
39 
40 /// Windows version doesn't use lock files
41 string defaultLockFile(string daemonName)
42 {
43     return "";
44 }
45 
46 /// Checks is $(B sig) is actually built-in
47 @nogc @safe bool isNativeSignal(Signal sig) pure nothrow
48 {
49     switch(sig)
50     {
51         case(Signal.Stop):           return true;
52         case(Signal.Continue):       return true;
53         case(Signal.Pause):          return true;
54         case(Signal.Shutdown):       return true;
55         case(Signal.Interrogate):    return true;
56         case(Signal.NetBindAdd):     return true;
57         case(Signal.NetBindDisable): return true;
58         case(Signal.NetBindEnable):  return true;
59         case(Signal.NetBindRemove):  return true;
60         case(Signal.ParamChange):    return true;
61         default: return false;
62     }
63 }
64 
65 /// Checks is $(B sig) is not actually built-in
66 @nogc @safe bool isCustomSignal(Signal sig) pure nothrow
67 {
68     return !isNativeSignal(sig);
69 }
70 
71 /**
72 *    The template holds a set of functions that build, run and send signals to daemons
73 *    that are built with $(B Daemon) or $(B DaemonClient) template.
74 *
75 *    Truncated $(B DaemonClient) aren't able to run described daemon, only signal sending
76 *    and daemon uninstalling.
77 */
78 template buildDaemon(alias DaemonInfo, DWORD startType =  SERVICE_DEMAND_START)
79     if(isDaemon!DaemonInfo || isDaemonClient!DaemonInfo)
80 {
81     /// Support functions
82     private alias daemon = readDaemonInfo!DaemonInfo;
83 
84     // DaemonClient cannot run daemon
85     static if(isDaemon!DaemonInfo)
86     {
87         /**
88         *    Starts daemon that is described by $(B DaemonInfo). Daemon is implemented as
89         *    windows service and auto-installed in SC manager. If you want to uninstall the
90         *    service, you can use $(B uninstall) function or system call:
91         *    ----------
92         *    C:\Windows\System32\sc.exe delete <daemonName>
93         *    ----------
94         *
95         *    If the service is already installed and is stopped, the function tries to start daemon.
96         *    Otherwise it fails and returns EXIT_FAILURE code.
97         *
98         *    $(B logger) is a initialized logger for the daemon, you should
99         *    use absolute names for Windows for logger files.
100         *
101         *    $(B pidFilePath), $(B lockFilePath), $(B userId) and $(B groupId)
102         *    are ignored for Windows platform.
103         *
104         *    See_Also: $(B uninstall)
105         *
106         *    Example:
107         *    ----------
108         *   // First you need to describe your daemon via template
109         *   alias daemon = Daemon!(
110         *       "DaemonizeExample1", // unique name
111         *
112         *       // Setting associative map signal -> callbacks
113         *       KeyValueList!(
114         *           Composition!(Signal.Terminate, Signal.Quit, Signal.Shutdown, Signal.Stop), (logger, signal)
115         *           {
116         *               logger.logInfo("Exiting...");
117         *               return false; // returning false will terminate daemon
118         *           },
119         *           Signal.HangUp, (logger)
120         *           {
121         *               logger.logInfo("Hello World!");
122         *               return true; // continue execution
123         *           }
124         *       ),
125         *
126         *       // Main function where your code is
127         *       (logger, shouldExit) {
128         *           // will stop the daemon in 5 minutes
129         *           auto time = Clock.currSystemTick + cast(TickDuration)5.dur!"minutes";
130         *           bool timeout = false;
131         *           while(!shouldExit() && time > Clock.currSystemTick) {  }
132         *
133         *           logger.logInfo("Exiting main function!");
134         *
135         *           return 0;
136         *       }
137         *   );
138         *
139         *   //...
140         *   buildDaemon!daemon.run(new shared DloggLogger(logFilePath));
141         *    ----------
142         */
143         int run(shared IDaemonLogger logger
144             , string pidFilePath = "", string lockFilePath = ""
145             , int userId = -1, int groupId = -1)
146         {
147             savedLogger = logger;
148 
149             auto maybeStatus = queryServiceStatus();
150             if(maybeStatus.isNull)
151             {
152                 savedLogger.logInfo("No service is installed!");
153                 serviceInstall(startType);
154                 serviceStart();
155                 return EXIT_SUCCESS;
156             }
157             else
158             {
159                 auto initResult = serviceInit();
160                 if(initResult == ServiceInitState.NotService)
161                 {
162                     auto state = maybeStatus.get.dwCurrentState;
163                     if(state == SERVICE_STOPPED)
164                     {
165                         savedLogger.logInfo("Starting installed service!");
166                         serviceStart();
167                     }
168                 } else if(initResult == initResult.OtherError)
169                 {
170                     savedLogger.logError("Service is already running!");
171                     return EXIT_FAILURE;
172                 }
173 
174                 return EXIT_SUCCESS;
175             }
176         }
177     }
178 
179     /**
180     *   Utility function that helps to uninstall the service from the system.
181     *
182     *   Note: Can be used with $(B DaemonClient) template, actually you can ommit signal list for the template.
183     */
184     void uninstall(shared IDaemonLogger logger)
185     {
186         savedLogger = logger;
187 
188         auto maybeStatus = queryServiceStatus();
189         if(!maybeStatus.isNull)
190         {
191             serviceRemove();
192         }
193         else
194         {
195             savedLogger.logWarning("Cannot find service in SC manager! No uninstallation action is performed.");
196         }
197     }
198 
199     /**
200     *   Sends singal $(B sig) for described daemon. All signals are sent via $(B ControlService) WINAPI function.
201     *
202     *   $(B logger) is used to log all errors.
203     *
204     *   $(B pidFilePath) is ignored for Windows platform.
205     *
206     *   Note: Can be used with $(B DaemonClient) template.
207     */
208     void sendSignal(shared IDaemonLogger logger, Signal sig, string pidFilePath = "")
209     {
210         savedLogger = logger;
211 
212         auto manager = getSCManager;
213         scope(exit) CloseServiceHandle(manager);
214 
215         auto service = getService(manager, daemon.getControlAccessFlag(sig));
216         scope(exit) CloseServiceHandle(service);
217 
218         if(!ControlService(service, daemon.mapSignal(sig), &serviceStatus))
219             throw new LoggedException(text("Failed to send signal to service ", DaemonInfo.daemonName, ". Details: ", getLastErrorDescr));
220 
221         logger.logInfo(text("Sending signal ", sig, " to daemon ", DaemonInfo.daemonName));
222     }
223 
224     /// ditto with dynamic service name
225     void sendSignalDynamic(shared IDaemonLogger logger, string serviceName, Signal sig, string pidFilePath = "")
226     {
227         savedLogger = logger;
228 
229         auto manager = getSCManager;
230         scope(exit) CloseServiceHandle(manager);
231 
232         auto service = OpenServiceW(manager, cast(LPWSTR)serviceName.toUTF16z, daemon.getControlAccessFlag(sig));
233         if(service is null) throw new LoggedException(text("Failed to open service! ", getLastErrorDescr));
234         scope(exit) CloseServiceHandle(service);
235 
236         if(!ControlService(service, daemon.mapSignal(sig), &serviceStatus))
237             throw new LoggedException(text("Failed to send signal to service ", serviceName, ". Details: ", getLastErrorDescr));
238 
239         logger.logInfo(text("Sending signal ", sig, " to daemon ", serviceName));
240     }
241 
242     /**
243     *   Saves info about exception into daemon $(B logger)
244     */
245     static class LoggedException : Exception
246     {
247         @safe nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
248         {
249             savedLogger.logError(msg);
250             super(msg, file, line, next);
251         }
252 
253         @safe nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__)
254         {
255             savedLogger.logError(msg);
256             super(msg, file, line, next);
257         }
258     }
259 
260     private
261     {
262         __gshared SERVICE_STATUS serviceStatus;
263         __gshared SERVICE_STATUS_HANDLE serviceStatusHandle;
264         shared IDaemonLogger savedLogger;
265 
266         bool shouldExit()
267         {
268             return serviceStatus.dwCurrentState == SERVICE_STOPPED;
269         }
270 
271         static if(isDaemon!DaemonInfo)
272         {
273             extern(System) static void serviceMain(uint argc, wchar** args) nothrow
274             {
275                 try
276                 {
277                     // Windows don't know anything about our runtime
278                     // so register the thread at druntime's thread subsystem
279                     // and manually run all TLS constructors and destructors
280                     thread_attachThis();
281                     rt_moduleTlsCtor();
282                     scope(exit) rt_moduleTlsDtor();
283 
284                     int code = EXIT_FAILURE;
285 
286                     serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
287 
288                     savedLogger.reload;
289                     savedLogger.minOutputLevel = DaemonLogLevel.Muted;
290                     savedLogger.logInfo("Registering control handler");
291 
292                     serviceStatusHandle = RegisterServiceCtrlHandlerW(cast(LPWSTR)DaemonInfo.daemonName.toUTF16z, &controlHandler);
293                     if(serviceStatusHandle is null)
294                     {
295                         savedLogger.logError("Failed to register control handler!");
296                         savedLogger.logError(getLastErrorDescr);
297                         return;
298                     }
299 
300                     savedLogger.logInfo("Running user main delegate");
301                     reportServiceStatus(SERVICE_RUNNING, NO_ERROR, 0.dur!"msecs");
302                     try code = DaemonInfo.mainFunc(savedLogger, &shouldExit);
303                     catch (Throwable ex)
304                     {
305                         savedLogger.logError(text("Catched unhandled throwable at daemon level at ", ex.file, ":", ex.line, ": ", ex.msg));
306                         savedLogger.logError("Terminating...");
307                         reportServiceStatus(SERVICE_STOPPED, EXIT_FAILURE, 0.dur!"msecs");
308                         return;
309                     }
310                     reportServiceStatus(SERVICE_STOPPED, NO_ERROR, 0.dur!"msecs");
311                 }
312                 catch(Throwable th)
313                 {
314                     savedLogger.logError(text("Internal daemon error, please bug report: ", th.file, ":", th.line, ": ", th.msg));
315                     savedLogger.logError("Terminating...");
316                 }
317             }
318 
319             extern(System) static void controlHandler(DWORD fdwControl) nothrow
320             {
321                 switch(fdwControl)
322                 {
323                     foreach(signal; DaemonInfo.signalMap.keys)
324                     {
325                         alias handler = DaemonInfo.signalMap.get!signal;
326 
327                         static if(isComposition!signal)
328                         {
329                             foreach(subsignal; signal.signals)
330                             {
331                                 case(daemon.mapSignal(subsignal)):
332                                 {
333                                     savedLogger.logInfo(text("Caught signal ", subsignal));
334                                     bool res = true;
335                                     try
336                                     {
337                                         static if(__traits(compiles, handler(savedLogger, subsignal)))
338                                             res = handler(savedLogger, subsignal);
339                                         else
340                                             res = handler(savedLogger);
341 
342                                         if(!res) reportServiceStatus(SERVICE_STOPPED, NO_ERROR, 0.dur!"msecs");
343                                     }
344                                     catch(Throwable th)
345                                     {
346                                         savedLogger.logError(text("Caught a throwable at signal ", subsignal, " handler: ", th));
347                                     }
348                                     return;
349                                 }
350                             }
351                         }
352                         else
353                         {
354                             case(daemon.mapSignal(signal)):
355                             {
356                                 savedLogger.logInfo(text("Caught signal ", signal));
357                                 bool res = true;
358                                 try
359                                 {
360                                     static if(__traits(compiles, handler(savedLogger, signal)))
361                                         res = handler(savedLogger, signal);
362                                     else
363                                         res = handler(savedLogger);
364 
365                                     if(!res) reportServiceStatus(SERVICE_STOPPED, NO_ERROR, 0.dur!"msecs");
366                                 }
367                                 catch(Throwable th)
368                                 {
369                                     savedLogger.logError(text("Caught a throwable at signal ", signal, " handler: ", th));
370                                 }
371                                 return;
372                             }
373                         }
374                     }
375                     default:
376                     {
377                         savedLogger.logWarning(text("Caught signal ", fdwControl, ". But don't have any handler binded!"));
378                     }
379                 }
380             }
381         }
382 
383         /// Wrapper for getting service manager
384         SC_HANDLE getSCManager()
385         {
386             auto manager = OpenSCManagerW(null, null, SC_MANAGER_ALL_ACCESS);
387             if(manager is null)
388                 throw new LoggedException(text("Failed to open SC manager!", getLastErrorDescr));
389 
390             return manager;
391         }
392 
393         /// Wrapper for getting service handle
394         SC_HANDLE getService(SC_HANDLE manager, DWORD accessFlags, bool supressLogging = false)
395         {
396             auto service = OpenServiceW(manager, cast(LPWSTR)DaemonInfo.daemonName.toUTF16z, accessFlags);
397             if(service is null)
398             {
399                 if(!supressLogging)
400                 {
401                     savedLogger.logError("Failed to open service!");
402                     savedLogger.logError(getLastErrorDescr);
403                 }
404                 throw new Exception(text("Failed to open service! ", getLastErrorDescr));
405             }
406             return service;
407         }
408 
409         static if(isDaemon!DaemonInfo)
410         {
411             enum ServiceInitState
412             {
413                 ServiceIsOk, // dispatcher has run successfully
414                 NotService,  // dispatcher failed with specific error
415                 OtherError
416             }
417 
418             /// Performs service initialization
419             /**
420             *    If inner $(B StartServiceCtrlDispatcherW) fails due reason that
421             *    the code is running in userspace, the function returns ServiceInitState.NotService.
422             *
423             *    If the code is run under SC manager, the dispatcher operates and the function
424             *    returns ServiceInitState.ServiceIsOk at the end of service execution.
425             *
426             *    If something wrong happens, the function returns ServiceInitState.OtherError
427             */
428             ServiceInitState serviceInit()
429             {
430                 SERVICE_TABLE_ENTRY[2] serviceTable;
431                 serviceTable[0].lpServiceName = cast(LPWSTR)DaemonInfo.daemonName.toUTF16z;
432                 serviceTable[0].lpServiceProc = &serviceMain;
433                 serviceTable[1].lpServiceName = null;
434                 serviceTable[1].lpServiceProc = null;
435 
436                 if(!StartServiceCtrlDispatcherW(serviceTable.ptr))
437                 {
438                     if(GetLastError == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
439                     {
440                         return ServiceInitState.NotService;
441                     }
442                     else
443                     {
444                         savedLogger.logError("Failed to start service dispatcher!");
445                         savedLogger.logError(getLastErrorDescr);
446                         return ServiceInitState.OtherError;
447                     }
448                 }
449 
450                 return ServiceInitState.ServiceIsOk;
451             }
452         }
453 
454         /// Registers service in SCM database
455         void serviceInstall(DWORD startType)
456         {
457             wchar[MAX_PATH] path;
458             if(!GetModuleFileNameW(null, path.ptr, MAX_PATH))
459                 throw new LoggedException("Cannot install service! " ~ getLastErrorDescr);
460 
461             auto manager = getSCManager();
462             scope(exit) CloseServiceHandle(manager);
463 
464             auto servname = cast(LPWSTR)DaemonInfo.daemonName.toUTF16z;
465             auto service = CreateServiceW(
466                 manager,
467                 servname,
468                 servname,
469                 SERVICE_ALL_ACCESS,
470                 SERVICE_WIN32_OWN_PROCESS,
471                 startType,
472                 SERVICE_ERROR_NORMAL,
473                 path.ptr,
474                 null,
475                 null,
476                 null,
477                 null,
478                 null);
479             scope(exit) CloseServiceHandle(service);
480 
481             if(service is null)
482                 throw new LoggedException("Failed to create service! " ~ getLastErrorDescr);
483 
484             savedLogger.logInfo("Service installed successfully!");
485         }
486 
487         /// Removing service from SC manager
488         void serviceRemove()
489         {
490             auto manager = getSCManager();
491             scope(exit) CloseServiceHandle(manager);
492 
493             auto service = getService(manager, SERVICE_STOP | DELETE);
494             scope(exit) CloseServiceHandle(service);
495 
496             DeleteService(service);
497             savedLogger.logInfo("Service is removed successfully!");
498         }
499 
500         /// Tries to start service and checks the running state
501         void serviceStart()
502         {
503             auto manager = getSCManager();
504             scope(exit) CloseServiceHandle(manager);
505 
506             auto service = getService(manager, SERVICE_START);
507             scope(exit) CloseServiceHandle(service);
508 
509             if(!StartServiceW(service, 0, null))
510                 throw new LoggedException(text("Failed to start service! ", getLastErrorDescr));
511 
512 
513             auto maybeStatus = queryServiceStatus();
514             if(maybeStatus.isNull)
515             {
516                 throw new LoggedException("Failed to start service! There is no service registered!");
517             }
518             else
519             {
520                 Thread.sleep(500.dur!"msecs");
521 
522                 auto status = maybeStatus.get;
523 
524                 static if( __VERSION__ < 2075 )
525                 {
526                     alias Duration = TickDuration;
527                     alias currTime = Clock.currSystemTick;
528                 }
529                 else alias currTime = MonoTime.currTime;
530 
531                 auto stamp = currTime;
532 
533                 while(status.dwCurrentState != SERVICE_RUNNING)
534                 {
535                     if(stamp + cast(Duration)30.dur!"seconds" < currTime)
536                         throw new LoggedException("Cannot start service! Timeout");
537                     if(status.dwWin32ExitCode != 0)
538                         throw new LoggedException(text("Failed to start service! Service error code: ", status.dwWin32ExitCode));
539                     if(status.dwCurrentState == SERVICE_STOPPED)
540                         throw new LoggedException("Failed to start service! The service remains in stop state!");
541 
542                     auto maybeStatus2 = queryServiceStatus();
543                     if(maybeStatus2.isNull)
544                     {
545                         throw new LoggedException("Failed to start service! There is no service registered!");
546                     }
547                     else
548                     {
549                         status = maybeStatus2.get;
550                     }
551                 }
552             }
553 
554             savedLogger.logInfo("Service is started successfully!");
555         }
556 
557         /**
558         *    Checks if the service is exist and returns its status.
559         *
560         *    If no service is installed, will return Nothing.
561         */
562         Nullable!SERVICE_STATUS queryServiceStatus()
563         {
564             auto manager = getSCManager();
565             scope(exit) CloseServiceHandle(manager);
566 
567             SC_HANDLE service;
568             try
569             {
570                 service = getService(manager, SERVICE_QUERY_STATUS, true);
571             } catch(Exception e)
572             {
573                 Nullable!SERVICE_STATUS ret;
574                 return ret;
575             }
576             scope(exit) CloseServiceHandle(service);
577 
578             SERVICE_STATUS status;
579             if(!QueryServiceStatus(service, &status))
580                 throw new LoggedException(text("Failed to query service! ", getLastErrorDescr));
581 
582             return Nullable!SERVICE_STATUS(status);
583         }
584 
585         /// Sets current service status and reports it to the SCM
586         void reportServiceStatus(DWORD currentState, DWORD exitCode, Duration waitHint)
587         {
588             static DWORD checkPoint = 1;
589 
590             serviceStatus.dwCurrentState = currentState;
591             serviceStatus.dwWin32ExitCode = exitCode;
592             serviceStatus.dwWaitHint = cast(DWORD)waitHint.total!"msecs";
593 
594             if(currentState == SERVICE_START_PENDING)
595             {
596                 serviceStatus.dwControlsAccepted = 0;
597             }
598             else
599             {
600                 serviceStatus.dwControlsAccepted = daemon.makeUsingFlag;
601             }
602 
603             SetServiceStatus(serviceStatusHandle, &serviceStatus);
604         }
605 
606         /// Reads last error id and formats it into a man-readable message
607         string getLastErrorDescr()
608         {
609             char* buffer;
610             auto error = GetLastError();
611 
612             FormatMessageA(
613                 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, null, error, MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), cast(LPSTR)&buffer, 0, null);
614             scope(exit) LocalFree(cast(void*)buffer);
615 
616             return buffer.fromStringz[0 .. $-1].idup;
617         }
618     } // private
619 }
620 private
621 {
622     /// Handles utilities for signal mapping from local representation to GNU/Linux one
623     template readDaemonInfo(alias DaemonInfo)
624         if(isDaemon!DaemonInfo || isDaemonClient!DaemonInfo)
625     {
626         template extractCustomSignals(T...)
627         {
628             static if(T.length < 2) alias extractCustomSignals = T[0];
629             else static if(isComposition!(T[1])) alias extractCustomSignals = StrictExpressionList!(T[0].expand, staticFilter!(isCustomSignal, T[1].signals));
630             else static if(isCustomSignal(T[1])) alias extractCustomSignals = StrictExpressionList!(T[0].expand, T[1]);
631             else alias extractCustomSignals = T[0];
632         }
633 
634         template extractNativeSignals(T...)
635         {
636             static if(T.length < 2) alias extractNativeSignals = T[0];
637             else static if(isComposition!(T[1])) alias extractNativeSignals = StrictExpressionList!(T[0].expand, staticFilter!(isNativeSignal, T[1].signals));
638             else static if(isNativeSignal(T[1])) alias extractNativeSignals = StrictExpressionList!(T[0].expand, T[1]);
639             else alias extractNativeSignals = T[0];
640         }
641 
642         static if(isDaemon!DaemonInfo)
643         {
644             alias customSignals = staticFold!(extractCustomSignals, StrictExpressionList!(), DaemonInfo.signalMap.keys).expand; //pragma(msg, [customSignals]);
645             alias nativeSignals = staticFold!(extractNativeSignals, StrictExpressionList!(), DaemonInfo.signalMap.keys).expand; //pragma(msg, [nativeSignals]);
646         } else
647         {
648             alias customSignals = staticFold!(extractCustomSignals, StrictExpressionList!(), DaemonInfo.signals).expand; //pragma(msg, [customSignals]);
649             alias nativeSignals = staticFold!(extractNativeSignals, StrictExpressionList!(), DaemonInfo.signals).expand; //pragma(msg, [nativeSignals]);
650         }
651 
652         DWORD mapSignal(Signal sig)
653         {
654             switch(sig)
655             {
656                 case(Signal.Stop):           return SERVICE_CONTROL_STOP;
657                 case(Signal.Continue):       return SERVICE_CONTROL_CONTINUE;
658                 case(Signal.Pause):          return SERVICE_CONTROL_PAUSE;
659                 case(Signal.Shutdown):       return SERVICE_CONTROL_SHUTDOWN;
660                 case(Signal.Interrogate):    return SERVICE_CONTROL_INTERROGATE;
661                 case(Signal.NetBindAdd):     return SERVICE_CONTROL_NETBINDADD;
662                 case(Signal.NetBindDisable): return SERVICE_CONTROL_NETBINDDISABLE;
663                 case(Signal.NetBindEnable):  return SERVICE_CONTROL_NETBINDENABLE;
664                 case(Signal.NetBindRemove):  return SERVICE_CONTROL_NETBINDREMOVE;
665                 case(Signal.ParamChange):    return SERVICE_CONTROL_PARAMCHANGE;
666                 default: return mapCustomSignal(sig);
667             }
668         }
669 
670         DWORD mapCustomSignal(Signal sig)
671         {
672             assert(!isNativeSignal(sig));
673 
674             DWORD counter = 0;
675             foreach(key; customSignals)
676             {
677                 if(key == sig) return 128 + counter;
678                 counter++;
679             }
680 
681             assert(false, "Signal isn't in custom list! Impossible state!");
682         }
683 
684         DWORD makeUsingFlag()
685         {
686             DWORD accum = 0;
687 
688             foreach(signal; nativeSignals)
689             {
690                 static if(signal == Signal.Stop)            accum |= SERVICE_ACCEPT_STOP;
691                 static if(signal == Signal.Continue)        accum |= SERVICE_ACCEPT_PAUSE_CONTINUE;
692                 static if(signal == Signal.Pause)           accum |= SERVICE_ACCEPT_PAUSE_CONTINUE;
693                 static if(signal == Signal.Shutdown)        accum |= SERVICE_ACCEPT_SHUTDOWN;
694                 static if(signal == Signal.Interrogate)     accum |= 0;
695                 static if(signal == Signal.NetBindAdd)      accum |= SERVICE_ACCEPT_NETBINDCHANGE;
696                 static if(signal == Signal.NetBindDisable)  accum |= SERVICE_ACCEPT_NETBINDCHANGE;
697                 static if(signal == Signal.NetBindEnable)   accum |= SERVICE_ACCEPT_NETBINDCHANGE;
698                 static if(signal == Signal.NetBindRemove)   accum |= SERVICE_ACCEPT_NETBINDCHANGE;
699                 static if(signal == Signal.ParamChange)     accum |= SERVICE_ACCEPT_PARAMCHANGE;
700             }
701 
702             return accum;
703         }
704 
705         DWORD getControlAccessFlag(Signal sig)
706         {
707             switch(sig)
708             {
709                 case(Signal.Stop):           return SERVICE_STOP;
710                 case(Signal.Continue):       return SERVICE_PAUSE_CONTINUE;
711                 case(Signal.Pause):          return SERVICE_PAUSE_CONTINUE;
712                 case(Signal.Shutdown):       throw new Error("Cannot send the shutdown signal!");
713                 case(Signal.Interrogate):    return SERVICE_INTERROGATE ;
714                 case(Signal.NetBindAdd):     return SERVICE_PAUSE_CONTINUE;
715                 case(Signal.NetBindDisable): return SERVICE_PAUSE_CONTINUE;
716                 case(Signal.NetBindEnable):  return SERVICE_PAUSE_CONTINUE;
717                 case(Signal.NetBindRemove):  return SERVICE_PAUSE_CONTINUE;
718                 case(Signal.ParamChange):    return SERVICE_PAUSE_CONTINUE;
719                 default: return SERVICE_USER_DEFINED_CONTROL;
720             }
721         }
722     }
723 }
724 private
725 {
726     extern (C) void  rt_moduleTlsCtor();
727     extern (C) void  rt_moduleTlsDtor();
728 }
729 // winapi defines
730 private extern(System)
731 {
732     struct SERVICE_TABLE_ENTRY
733     {
734         LPWSTR                  lpServiceName;
735         LPSERVICE_MAIN_FUNCTION lpServiceProc;
736     }
737     alias LPSERVICE_TABLE_ENTRY = SERVICE_TABLE_ENTRY*;
738 
739     alias extern(System) void function(DWORD dwArgc, LPWSTR* lpszArgv) LPSERVICE_MAIN_FUNCTION;
740 
741     BOOL StartServiceCtrlDispatcherW(const SERVICE_TABLE_ENTRY* lpServiceTable);
742 
743     struct SERVICE_STATUS
744     {
745         DWORD dwServiceType;
746         DWORD dwCurrentState;
747         DWORD dwControlsAccepted;
748         DWORD dwWin32ExitCode;
749         DWORD dwServiceSpecificExitCode;
750         DWORD dwCheckPoint;
751         DWORD dwWaitHint;
752     }
753     alias LPSERVICE_STATUS = SERVICE_STATUS*;
754 
755     alias SERVICE_STATUS_HANDLE = HANDLE;
756     alias SC_HANDLE = HANDLE;
757 
758     // dwServiceType
759     enum SERVICE_FILE_SYSTEM_DRIVER = 0x00000002;
760     enum SERVICE_KERNEL_DRIVER = 0x00000001;
761     enum SERVICE_WIN32_OWN_PROCESS = 0x00000010;
762     enum SERVICE_WIN32_SHARE_PROCESS = 0x00000020;
763     enum SERVICE_INTERACTIVE_PROCESS = 0x00000100;
764 
765     // dwCurrentState
766     enum SERVICE_CONTINUE_PENDING = 0x00000005;
767     enum SERVICE_PAUSE_PENDING = 0x00000006;
768     enum SERVICE_PAUSED = 0x00000007;
769     enum SERVICE_RUNNING = 0x00000004;
770     enum SERVICE_START_PENDING = 0x00000002;
771     enum SERVICE_STOP_PENDING = 0x00000003;
772     enum SERVICE_STOPPED = 0x00000001;
773 
774     // dwControlsAccepted
775     enum SERVICE_ACCEPT_NETBINDCHANGE = 0x00000010;
776     enum SERVICE_ACCEPT_PARAMCHANGE = 0x00000008;
777     enum SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002;
778     enum SERVICE_ACCEPT_PRESHUTDOWN = 0x00000100;
779     enum SERVICE_ACCEPT_SHUTDOWN = 0x00000004;
780     enum SERVICE_ACCEPT_STOP = 0x00000001;
781 
782     enum NO_ERROR = 0;
783 
784     alias extern(System) void function(DWORD fdwControl) LPHANDLER_FUNCTION;
785     SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerW(LPWSTR lpServiceName, LPHANDLER_FUNCTION lpHandlerProc);
786 
787     SC_HANDLE OpenSCManagerW(LPWSTR lpMachineName, LPWSTR lpDatabaseName, DWORD dwDesiredAccess);
788 
789     // dwDesiredAccess
790     enum SC_MANAGER_ALL_ACCESS = 0xF003F;
791     enum SC_MANAGER_CREATE_SERVICE = 0x0002;
792     enum SC_MANAGER_CONNECT = 0x0001;
793     enum SC_MANAGER_ENUMERATE_SERVICE = 0x0004;
794     enum SC_MANAGER_LOCK = 0x0008;
795     enum SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020;
796     enum SC_MANAGER_QUERY_LOCK_STATUS = 0x0010;
797 
798     SC_HANDLE CreateServiceW(
799           SC_HANDLE hSCManager,
800           LPWSTR lpServiceName,
801           LPWSTR lpDisplayName,
802           DWORD dwDesiredAccess,
803           DWORD dwServiceType,
804           DWORD dwStartType,
805           DWORD dwErrorControl,
806           LPWSTR lpBinaryPathName,
807          LPWSTR lpLoadOrderGroup,
808           LPDWORD lpdwTagId,
809           LPWSTR lpDependencies,
810           LPWSTR lpServiceStartName,
811           LPWSTR lpPassword
812     );
813 
814     // dwStartType
815     enum SERVICE_AUTO_START = 0x00000002;
816     enum SERVICE_BOOT_START = 0x00000000;
817     enum SERVICE_DEMAND_START = 0x00000003;
818     enum SERVICE_DISABLED = 0x00000004;
819     enum SERVICE_SYSTEM_START = 0x00000001;
820 
821     // dwDesiredAccess CreateService
822     enum SERVICE_ALL_ACCESS = 0xF01FF;
823     enum SERVICE_CHANGE_CONFIG = 0x0002;
824     enum SERVICE_ENUMERATE_DEPENDENTS = 0x0008;
825     enum SERVICE_INTERROGATE = 0x0080;
826     enum SERVICE_PAUSE_CONTINUE = 0x0040;
827     enum SERVICE_QUERY_CONFIG = 0x0001;
828     enum SERVICE_QUERY_STATUS = 0x0004;
829     enum SERVICE_START = 0x0010;
830     enum SERVICE_STOP = 0x0020;
831     enum SERVICE_USER_DEFINED_CONTROL = 0x0100;
832 
833     // dwErrorControl
834     enum SERVICE_ERROR_CRITICAL = 0x00000003;
835     enum SERVICE_ERROR_IGNORE = 0x00000000;
836     enum SERVICE_ERROR_NORMAL = 0x00000001;
837     enum SERVICE_ERROR_SEVERE = 0x00000002;
838 
839     bool CloseServiceHandle(SC_HANDLE hSCOjbect);
840 
841     SC_HANDLE OpenServiceW(
842         SC_HANDLE hSCManager,
843         LPWSTR lpServiceName,
844         DWORD dwDesiredAccess
845     );
846 
847     BOOL DeleteService(
848         SC_HANDLE hService
849     );
850 
851     BOOL StartServiceW(
852         SC_HANDLE hService,
853         DWORD dwNumServiceArgs,
854         LPWSTR *lpServiceArgVectors
855     );
856 
857     BOOL QueryServiceStatus(
858         SC_HANDLE hService,
859         LPSERVICE_STATUS lpServiceStatus
860     );
861 
862     BOOL SetServiceStatus(
863         SERVICE_STATUS_HANDLE hServiceStatus,
864         LPSERVICE_STATUS lpServiceStatus
865     );
866 
867     BOOL ControlService(
868         SC_HANDLE hService,
869           DWORD dwControl,
870           LPSERVICE_STATUS lpServiceStatus
871     );
872 
873     enum SERVICE_CONTROL_CONTINUE = 0x00000003;
874     enum SERVICE_CONTROL_INTERROGATE = 0x00000004;
875     enum SERVICE_CONTROL_NETBINDADD = 0x00000007;
876     enum SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A;
877     enum SERVICE_CONTROL_NETBINDENABLE = 0x00000009;
878     enum SERVICE_CONTROL_NETBINDREMOVE = 0x00000008;
879     enum SERVICE_CONTROL_PARAMCHANGE = 0x00000006;
880     enum SERVICE_CONTROL_PAUSE = 0x00000002;
881     enum SERVICE_CONTROL_SHUTDOWN = 0x00000005;
882     enum SERVICE_CONTROL_STOP = 0x00000001;
883 
884     enum ERROR_FAILED_SERVICE_CONTROLLER_CONNECT = 1063;
885 }