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