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