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