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 }