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